Back (Current repo: terraform-mariadb-replica-homelab)

A terraform homelab with mariadb and maxscale for my own understanding and learning.
To clone this repository:
git clone https://git.viktor1993.net/terraform-mariadb-replica-homelab.git
Log | Download | Files | Refs | README

commit 50d7f968c7ca6bbfaa35736ede1da57f2a4a3ef7
Author: root <root>
Date:   Sun, 15 Mar 2026 19:58:08 +0100

initial commit

Diffstat:
A.gitignore | 12++++++++++++
AMakefile | 40++++++++++++++++++++++++++++++++++++++++
AREADME.md | 25+++++++++++++++++++++++++
Aconfig.mk | 5+++++
Ascripts/delete_batch_dir.sh | 20++++++++++++++++++++
Ascripts/deploy_to_new_batch.sh | 24++++++++++++++++++++++++
Ascripts/destroy_in_batch.sh | 23+++++++++++++++++++++++
Ascripts/list_batches.sh | 10++++++++++
Ascripts/new_batch.sh | 19+++++++++++++++++++
Aterraform-template/.terraform.lock.hcl | 43+++++++++++++++++++++++++++++++++++++++++++
Aterraform-template/Makefile | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aterraform-template/README.md | 25+++++++++++++++++++++++++
Aterraform-template/awk_scripts/create_maxscale.awk | 44++++++++++++++++++++++++++++++++++++++++++++
Aterraform-template/awk_scripts/destroy_maxscale.awk | 38++++++++++++++++++++++++++++++++++++++
Aterraform-template/awk_scripts/port_check.awk | 17+++++++++++++++++
Aterraform-template/awk_scripts/service_discovery.awk | 32++++++++++++++++++++++++++++++++
Aterraform-template/boundary.txt.last | 1+
Aterraform-template/config.mk | 5+++++
Aterraform-template/config.mk.example | 5+++++
Aterraform-template/create_maxscale.sh | 29+++++++++++++++++++++++++++++
Aterraform-template/created_servers.txt | 1+
Aterraform-template/destroy_maxscale.sh | 29+++++++++++++++++++++++++++++
Aterraform-template/docker/Dockerfile | 19+++++++++++++++++++
Aterraform-template/docker/entrypoint.sh | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aterraform-template/docker/my.cnf | 17+++++++++++++++++
Aterraform-template/dump/01_queries.sql | 10++++++++++
Aterraform-template/init.sh | 37+++++++++++++++++++++++++++++++++++++
Aterraform-template/main.tf | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Aterraform-template/service_discovery.sh | 30++++++++++++++++++++++++++++++
29 files changed, 746 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,12 @@ +terraform-template/.terraform/ +terraform-template/terraform.tfstate +terraform-template/terrafor,.tfstate.* +terraform-template/*.tfstate +terraform-template/*.tfstate.backup +terraform-template/locals.tf.last +terraform-template/locals.tf +terraform-template/boundary.txt +terraform-template/boundary.mk +terraform-template/dump/00_dump.sql +terraform-template/dump/gtid_pos.txt +batches/ diff --git a/Makefile b/Makefile @@ -0,0 +1,40 @@ +-include config.mk +BATCH_NAME ?= batch-$(shell date +%Y%m%d-%H%M%S) +THIS_BATCH := $(BATCH_DIR)/$(BATCH_NAME) +WORKING_DIR := $(THIS_BATCH)/terraform-template +NUMBER ?= 2 +PORT ?= 3313 + +help: + @echo "Batch Deployment Orchestrator" + @echo "" + @echo "Targets:" + @echo " make new [BATCH_NAME=name] - Create new batch" + @echo " make deploy BATCH_NAME=name [NUMBER=2] - Deploy batch" + @echo " make destroy BATCH_NAME=name [NUMBER=2] - Destroy batch" + @echo " make list - List all batches" + @echo " make clean BATCH_NAME=name - Delete batch directory" + @echo "" + @echo "Examples:" + @echo " make new BATCH_NAME=batch-1" + @echo " make deploy BATCH_NAME=batch-1" + @echo " make destroy BATCH_NAME=batch-1" + +new: + @bash $(SCRIPTS_DIR)/new_batch.sh $(BATCH_NAME) $(THIS_BATCH) $(BATCH_DIR) $(TEMPLATE_DIR) + +deploy: + @bash $(SCRIPTS_DIR)/deploy_to_new_batch.sh $(BATCH_NAME) $(THIS_BATCH) $(WORKING_DIR) $(NUMBER) $(PORT) $(TEMPLATE_DIR) $(BATCH_REGISTRY) + +destroy: + @bash $(SCRIPTS_DIR)/destroy_in_batch.sh $(BATCH_NAME) $(THIS_BATCH) $(WORKING_DIR) $(NUMBER) $(TEMPLATE_DIR) $(BATCH_REGISTRY) + +list: + @bash $(SCRIPTS_DIR)/list_batches.sh $(BATCH_NAME) $(THIS_BATCH) $(BATCH_DIR) $(TEMPLATE_DIR) $(BATCH_REGISTRY) + +clean: + @bash $(SCRIPTS_DIR)/delete_batch_dir.sh $(BATCH_NAME) $(THIS_BATCH) $(TEMPLATE_DIR) $(BATCH_REGISTRY) + +.PHONY: new deploy destroy list clean help + +.DEFAULT_GOAL := help diff --git a/README.md b/README.md @@ -0,0 +1,25 @@ +# What is this? + +This repository contains a small project that does two things: + +* -> create terraform managed docker containers with mariadb, and add them as read-only to an existing maxscale cluster +* -> remove terraform managed mariadb docker containers from maxscale, and destroy the containers + +## Assumptions: + +* -> Maxscale already exists on the machine where terraform is executed from +* -> A Master server, and 2 Replica servers are already plugged into maxscale, users for maxscale and replication already set up +* -> Terraform does not manage these 3 initial servers, it just needs to know how to add extra servers on top +* -> Naming scheme of servers is just "server1, server2, server3" etc. +* -> It is OK to just restart maxscale, in reality there would be a HA mechanism, but not in this homelab + +## Requirements: + +### HOST + +- MariaDB (or just mariadb-client with access to a master server), Maxscale, Make, Terraform, gnu awk, gnu sed, bash, ssh access to target server + +### TARGET SERVER + +- docker + diff --git a/config.mk b/config.mk @@ -0,0 +1,5 @@ +ROOT_SRC=$(CURDIR) +TEMPLATE_DIR=$(ROOT_SRC)/terraform-template +BATCH_DIR=$(ROOT_SRC)/batches +BATCH_REGISTRY=$(BATCH_DIR)/batch_registry.txt +SCRIPTS_DIR=$(ROOT_SRC)/scripts diff --git a/scripts/delete_batch_dir.sh b/scripts/delete_batch_dir.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +BATCH_NAME=$1 +THIS_BATCH=$2 +TEMPLATE_DIR=$3 +BATCH_REGISTRY=$4 + +if [ ! -d "${THIS_BATCH}" ]; then + echo "Error: Batch ${BATCH_NAME} not found!" + exit 1; +fi +echo "Removing batch directory: ${THIS_BATCH}" +read -p "Are you sure? [y/N] " CONFIRM; +if [ "$CONFIRM" = "y" ] || [ "$CONFIRM" = "Y" ]; then + rm -rf ${THIS_BATCH}; + sed -i "/^${BATCH_NAME}:/d" ${BATCH_REGISTRY} + echo "Batch removed!"; +else + echo "Cancelled"; +fi diff --git a/scripts/deploy_to_new_batch.sh b/scripts/deploy_to_new_batch.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +BATCH_NAME=$1 +THIS_BATCH=$2 +WORKING_DIR=$3 +NUMBER=$4 +PORT=$5 +TEMPLATE_DIR=$6 +BATCH_REGISTRY=$7 + +if [ ! -d "${THIS_BATCH}" ]; then + echo "Error: Batch ${BATCH_NAME} not found" + exit 1 +fi + +if [ ! -d "${WORKING_DIR}" ]; then + echo "Error: Batch ${BATCH_NAME} has no template dir!" + exit 1 +fi + +echo "Deploying batch: ${BATCH_NAME} with ${NUMBER} servers" +cd ${WORKING_DIR} && make OPERATION=APPLY NUMBER=${NUMBER} PORT=${PORT} BATCH_ID=${BATCH_NAME} +echo "${BATCH_NAME}: ${NUMBER} servers" >> ${BATCH_REGISTRY} +echo "Batch deployed successfully!" diff --git a/scripts/destroy_in_batch.sh b/scripts/destroy_in_batch.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +BATCH_NAME=$1 +THIS_BATCH=$2 +WORKING_DIR=$3 +NUMBER=$4 +TEMPLATE_DIR=$5 +BATCH_REGISTRY=$6 + +if [ ! -d "${THIS_BATCH}" ]; then + echo "Error: Batch ${BATCH_NAME} not found" + exit 1 +fi + +if [ ! -d "${WORKING_DIR}" ]; then + echo "Error: Batch ${BATCH_NAME} has no template dir!" + exit 1 +fi + +echo "Destroying batch: ${BATCH_NAME}" +cd ${WORKING_DIR} && make OPERATION=DESTROY NUMBER=${NUMBER} +sed -i "/^${BATCH_NAME}:/d" ${BATCH_REGISTRY} +echo "Batch destroyed successfully!" diff --git a/scripts/list_batches.sh b/scripts/list_batches.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +BATCH_NAME=$1 +THIS_BATCH=$2 +BATCH_DIR=$3 +TEMPLATE_DIR=$4 +BATCH_REGISTRY=$5 + +echo "[=== Deployed Batches ===]" +cat $BATCH_REGISTRY | awk -v dir="$(echo $BATCH_DIR/)" 'BEGIN{FS=":"}{print "echo "$1" && cat "dir$1"/terraform-template/created_servers.txt"}' | bash diff --git a/scripts/new_batch.sh b/scripts/new_batch.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +BATCH_NAME=$1 +THIS_BATCH=$2 +BATCH_DIR=$3 +TEMPLATE_DIR=$4 + +echo "Creating batch: ${BATCH_NAME}" +if [ -d "${THIS_BATCH}" ]; then + echo "Error: Batch ${BATCH_NAME} already exists" + exit 1 +fi + +mkdir -p "$BATCH_DIR" && mkdir -p "$THIS_BATCH" + +cp -r ${TEMPLATE_DIR} ${THIS_BATCH} +cd ${THIS_BATCH} && rm -f terraform.tfstate* .terraform.lock.hcl boundary.txt boundary.mk boundary.txt.last locals.tf locals.tf.last created_servers.txt dump/00_dump.sql dump/gtid_pos.txt +cd ${THIS_BATCH} && rm -rf .terraform/ +echo "Batch created @ ${THIS_BATCH}" diff --git a/terraform-template/.terraform.lock.hcl b/terraform-template/.terraform.lock.hcl @@ -0,0 +1,43 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/local" { + version = "2.7.0" + constraints = "~> 2.0" + hashes = [ + "h1:2RYa3j7m/0WmET2fqotY4CHxE1Hpk0fgn47/126l+Og=", + "zh:261fec71bca13e0a7812dc0d8ae9af2b4326b24d9b2e9beab3d2400fab5c5f9a", + "zh:308da3b5376a9ede815042deec5af1050ec96a5a5410a2206ae847d82070a23e", + "zh:3d056924c420464dc8aba10e1915956b2e5c4d55b11ffff79aa8be563fbfe298", + "zh:643256547b155459c45e0a3e8aab0570db59923c68daf2086be63c444c8c445b", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:7aa4d0b853f84205e8cf79f30c9b2c562afbfa63592f7231b6637e5d7a6b5b27", + "zh:7dc251bbc487d58a6ab7f5b07ec9edc630edb45d89b761dba28e0e2ba6b1c11f", + "zh:7ee0ca546cd065030039168d780a15cbbf1765a4c70cd56d394734ab112c93da", + "zh:b1d5d80abb1906e6c6b3685a52a0192b4ca6525fe090881c64ec6f67794b1300", + "zh:d81ea9856d61db3148a4fc6c375bf387a721d78fc1fea7a8823a027272a47a78", + "zh:df0a1f0afc947b8bfc88617c1ad07a689ce3bd1a29fd97318392e6bdd32b230b", + "zh:dfbcad800240e0c68c43e0866f2a751cff09777375ec701918881acf67a268da", + ] +} + +provider "registry.terraform.io/kreuzwerker/docker" { + version = "3.6.2" + constraints = "~> 3.0" + hashes = [ + "h1:1K3j0xUY2D0+E+DBDQc6k1u6Al9MkuNWrIC9rnvwFSM=", + "zh:22b51a8fb63481d290bdad9a221bc8c9e45d66d1a0cd45beed3f3627bf1debd8", + "zh:2b902eb80a1ae033af1135cc165d192668820a7f8ea15beb5472f811c18bea1f", + "zh:57815dcea28aedb86ed33924cd186aaee8bd31670bd78437a2a2daf2b00ce2ae", + "zh:583af9c6fe7e3bfc04f50aec046a9b4f98b7eddd6d1e143454e5d06a66afcf87", + "zh:80f8cba54f639a53c4d7714edb7246064b7f4f48ba93a70f18c914d656d799db", + "zh:894709f0c393c4ee91fdb849128e7f0bce688f293cd1643a6d4e39c842367278", + "zh:a91b41dbcb203d6dae2bb72b98c4c21c41255026b35df01895882784c4650071", + "zh:aec40a8157aae093412a1fb9a71ab2bea370db152e285c2d81e37ed378444b9c", + "zh:b87d7def2485dde6e57723c1265158f371440a8a84954c9fdb0580cf89de66bf", + "zh:b9dc243200ad9cd00250cb8c793ecea4ee3c57a121faf8efdb289f30008b5778", + "zh:dcb103831db6d3ef95468685cd104be3928793996542a1f675dc34a2ce67951d", + "zh:e59b4a0f2b5881016896d4417b1ab2fb87f34450663efeb01f3bcf7c3606fbbb", + "zh:fbd068c01114f0712578cf02f363b5521338ab1befedddf7090da532298b43d0", + ] +} diff --git a/terraform-template/Makefile b/terraform-template/Makefile @@ -0,0 +1,67 @@ +-include config.mk +-include boundary.mk +OPERATION ?= +VALID_OPERATIONS := APPLY DESTROY +NUMBER ?= +PORT ?= +BATCH_ID ?= + +ifeq ($(NUMBER),) + NUMBER = 2 +endif + +ifeq ($(PORT),) + PORT = 3313 +endif + +ifeq ($(BATCH_ID),) + BATCH_ID = 0 +endif + +ifneq ($(OPERATION),APPLY) + ifneq ($(OPERATION),DESTROY) + $(error Invalid value for OPERATION: "$(OPERATION)". Must be "APPLY" or "DESTROY") + endif +endif + +IS_MAXSCALE_ACTIVE := $(shell systemctl is-active maxscale) + +ifneq ($(IS_MAXSCALE_ACTIVE),active) +$(error Maxscale service is not running, can not do service discovery) +endif + +ifeq ($(OPERATION),APPLY) + TARGETS := build apply +else ifeq ($(OPERATION),DESTROY) + TARGETS := destroy +endif + +operate: service_discovery + $(MAKE) execute_operation +#we need a dynamically generated value from a file, so we must call make from make to refresh state + +execute_operation: + $(MAKE) $(TARGETS) + +#checks maxctrl, tells what's currently running gives the number of current servers, and builds a locals.tf file +service_discovery: + rm -f boundary.mk + bash service_discovery.sh $(NUMBER) $(PORT) $(OPERATION) $(ROOT_SRC) + @echo "BOUNDARY=$$(cat boundary.txt)" > boundary.mk + +#builds and transports the docker images to the target server, along with a fresh mysqldump +build: + bash init.sh $(TARGET_SRV) $(ROOT_SRC) $(REMOTE_DOCKER_DIR) $(REMOTE_DOCKER_SCRIPTS_DIR) $(DOCKER_BUILD) + +#runs the new docker containers, updates maxscale +apply: + terraform init + terraform apply -var="target_ip=$(TARGET_SRV)" -var="docker_image=$(DOCKER_BUILD)" -var="batch_id=$(BATCH_ID)" + bash create_maxscale.sh $(BOUNDARY) $(NUMBER) $(PORT) $(ROOT_SRC) $(TARGET_SRV) + +#destroys the docker containers, updates maxscale +destroy: + bash destroy_maxscale.sh $(ROOT_SRC) + terraform destroy -var="target_ip=$(TARGET_SRV)" -var="docker_image=$(DOCKER_BUILD)" + +.PHONY: service_discovery execute_operation operate build apply destroy diff --git a/terraform-template/README.md b/terraform-template/README.md @@ -0,0 +1,25 @@ +# What is this? + +This repository contains a small project that does two things: + +* -> create terraform managed docker containers with mariadb, and add them as read-only to an existing maxscale cluster +* -> remove terraform managed mariadb docker containers from maxscale, and destroy the containers + +## Assumptions: + +* -> Maxscale already exists on the machine where terraform is executed from +* -> A Master server, and 2 Replica servers are already plugged into maxscale, users for maxscale and replication already set up +* -> Terraform does not manage these 3 initial servers, it just needs to know how to add extra servers on top +* -> Naming scheme of servers is just "server1, server2, server3" etc. +* -> It is OK to just restart maxscale, in reality there would be a HA mechanism, but not in this homelab + +## Requirements: + +### HOST + +- MariaDB (or just mariadb-client with access to a master server), Maxscale, Make, Terraform, gnu awk, gnu sed, bash, ssh access to target server + +### TARGET SERVER + +- docker + diff --git a/terraform-template/awk_scripts/create_maxscale.awk b/terraform-template/awk_scripts/create_maxscale.awk @@ -0,0 +1,44 @@ +#!/bin/awk -f + +BEGIN{ + found = 0; + if(boundary == "") {boundary = 4;} + if(number == "") {number = 2;} + if(port == "") {port = 3313;} + if(address == "") {address="192.168.2.99";} + pat="\\[server"(boundary-1)"\\]"; + pat2=""; + repl=""; + for(i=boundary-2; i<boundary; i++) { + pat2=pat2"server"i","; + } + pat2=substr(pat2, 1, length(pat2) - 1); + for(i=boundary; i<boundary+number; i++) { + repl=repl",server"i; + } +} +{ + if($0 ~ pat2) { + sub(pat2, pat2""repl, $0); + print $0; + } else { + print $0; + } + if($0 ~ pat) { + found = 1; + } + if((found == 1) && ($0 ~ /protocol=MariaDBBackend/)) { + for(i = boundary; i < (boundary+number); i++) { + port++; + print "\n[server"i"]"; + print "type=server"; + print "address="address; + print "port="port; + print "protocol=MariaDBBackend\n"; + } + found = 0; + next; + } +} +END{ +} diff --git a/terraform-template/awk_scripts/destroy_maxscale.awk b/terraform-template/awk_scripts/destroy_maxscale.awk @@ -0,0 +1,38 @@ +#!/bin/awk -f + +BEGIN{ + if(number == "") {exit 1;} + if(boundary == "") {exit 1;} + startpos = boundary - number; + endpos = boundary - 1; + skip = 0; + blank_seen = 0; + + for (i = startpos; i <= endpos; i++) { + managed["[server"i"]"] = 1; + } + +} +{ + if(skip > 0) { + skip--; + next; + } + if($0 in managed) { + skip = 4; + next; + } + + if ($0 ~ /^[[:space:]]*$/) { + if (blank_seen) { + next; + } + blank_seen = 1; + print $0; + next; + } + + blank_seen = 0; + print $0; +} + diff --git a/terraform-template/awk_scripts/port_check.awk b/terraform-template/awk_scripts/port_check.awk @@ -0,0 +1,17 @@ +#!/bin/awk -f + +BEGIN{ + FS="│"; + max_port = 0; +} +{ + if($4 ~ /[0-9]+/) { + match($4, /[0-9]+/, port); + if (port[0] > max_port) { + max_port = port[0]; + } + } +} +END{ + print max_port; +} diff --git a/terraform-template/awk_scripts/service_discovery.awk b/terraform-template/awk_scripts/service_discovery.awk @@ -0,0 +1,32 @@ +#!/bin/awk -f + +BEGIN{ + FS="│"; + max_id = 0; + if (port == ""){port = 3313;} + if (number == ""){number = 2;} +} +{ + if($2 ~ /server[0-9]+/){ + for (i=1; i<=NF; i++) { + gsub(/^[ \t]+|[ \t]+$/, "", $i); + } + match($2, /([0-9]+)$/, current_id); + if (current_id[1] > max_id) { + max_id = current_id[1]; + } + } +} +END{ + boundary = max_id+1; + print boundary; + print "locals {"; + print " servers = {"; + for(i = boundary; i < boundary+number; i++) { + port++; + print " \"server"i"\" = { port = "port", server_id = "i" },"; + } + print " }" + print "}"; +} + diff --git a/terraform-template/boundary.txt.last b/terraform-template/boundary.txt.last @@ -0,0 +1 @@ +4 diff --git a/terraform-template/config.mk b/terraform-template/config.mk @@ -0,0 +1,5 @@ +ROOT_SRC=$(CURDIR) +TARGET_SRV="192.168.2.99" +REMOTE_DOCKER_DIR=/opt/docker/mariadb-pkg-tf +REMOTE_DOCKER_SCRIPTS_DIR=$(REMOTE_DOCKER_DIR)/docker-entrypoint-initdb.d +DOCKER_BUILD="mariadb-pkg-tf:dev" diff --git a/terraform-template/config.mk.example b/terraform-template/config.mk.example @@ -0,0 +1,5 @@ +ROOT_SRC=$(CURDIR) +TARGET_SRV="{{IP OF SERVER WHERE DOCKER CONTAINERS SHOULD BE CREATED}}" +REMOTE_DOCKER_DIR={{/path/to/directory/with/dockerfile}} +REMOTE_DOCKER_SCRIPTS_DIR=$(REMOTE_DOCKER_DIR)/docker-entrypoint-initdb.d +DOCKER_BUILD="{{docker_image:tag}}" diff --git a/terraform-template/create_maxscale.sh b/terraform-template/create_maxscale.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +BOUNDARY=$1 +NUMBER=$2 +PORT=$3 +ROOT_SRC=$4 +TARGET_SRV=$5 +TMP=$(mktemp) +CREATED_SERVERS_FILE=$ROOT_SRC/created_servers.txt + +if [[ $BOUNDARY -lt 4 ]]; then + exit 1 +fi + +CREATED_SERVERS_VAR="" +ENDPOINT=$(echo "$BOUNDARY + $NUMBER" | bc) +for ((i = $BOUNDARY; i < $ENDPOINT; i++ )); do + CREATED_SERVERS_VAR="${CREATED_SERVERS_VAR}server${i}," +done + +echo "$CREATED_SERVERS_VAR" | sed 's/,$//' > $CREATED_SERVERS_FILE + +awk -v boundary=$BOUNDARY -v number=$NUMBER -v port=$PORT -v address=$TARGET_SRV -f $ROOT_SRC/awk_scripts/create_maxscale.awk /etc/maxscale.cnf > $TMP +cat /etc/maxscale.cnf > /etc/maxscale.cnf.last +cat $TMP > /etc/maxscale.cnf + +systemctl restart maxscale + +rm $TMP diff --git a/terraform-template/created_servers.txt b/terraform-template/created_servers.txt @@ -0,0 +1 @@ +server4,server5 diff --git a/terraform-template/destroy_maxscale.sh b/terraform-template/destroy_maxscale.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +ROOT_SRC=$1 + +if [[ ! -s "$ROOT_SRC/created_servers.txt" ]]; then + echo "No created servers file found, aborting" + exit 1 +fi + +MANAGED_SERVERS=$(awk '(NR==1){print $0}' $ROOT_SRC/created_servers.txt) +echo "DELETING SERVERS ${MANAGED_SERVERS} FROM maxscale.cnf" +LAST_SERVER_ID=$(echo $MANAGED_SERVERS | grep -Eo 'server[0-9]+$' | sed 's/server//') +NEXT_SERVER_ID=$(echo "$LAST_SERVER_ID + 1" | bc) +NUMBER=$(awk 'BEGIN{FS=","}{if(NR==1){print NF}}' $ROOT_SRC/created_servers.txt) + +if [[ $LAST_SERVER_ID -lt 4 ]]; then + exit 1 +fi + +TMP=$(mktemp) + +awk -v boundary=$NEXT_SERVER_ID -v number=$NUMBER -f $ROOT_SRC/awk_scripts/destroy_maxscale.awk /etc/maxscale.cnf > $TMP +cat /etc/maxscale.cnf > /etc/maxscale.cnf.last +cat $TMP > /etc/maxscale.cnf +sed -E -i "s/,$MANAGED_SERVERS//g" /etc/maxscale.cnf + +systemctl restart maxscale + +rm $TMP diff --git a/terraform-template/docker/Dockerfile b/terraform-template/docker/Dockerfile @@ -0,0 +1,19 @@ +FROM debian:12-slim + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + mariadb-server mariadb-client \ + ca-certificates tzdata \ + && rm -rf /var/lib/apt/lists/* + +COPY docker-entrypoint-initdb.d/ /docker-entrypoint-initdb.d/ + +COPY entrypoint.sh /usr/local/bin/entrypoint.sh +RUN chmod +x /usr/local/bin/entrypoint.sh + +VOLUME ["/var/lib/mysql"] + +ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] +CMD ["mariadbd"] diff --git a/terraform-template/docker/entrypoint.sh b/terraform-template/docker/entrypoint.sh @@ -0,0 +1,66 @@ +#!/bin/bash + +MARIADB_PORT=${MARIADB_PORT:-3311} +MARIADB_SERVER_ID=${MARIADB_SERVER_ID:-4} +DATADIR="/var/lib/mysql" +RUNDIR="/run/mysqld" +LOCK_FILE=/var/lib/mysql/setup.lock + +if ! [ -f "$LOCK_FILE" ]; then +#Don't do all this in case of an accidental docker restart + sed -E -i "s/(server-id=)[0-9]+/\1${MARIADB_SERVER_ID}/" /docker-entrypoint-initdb.d/my.cnf + cat /docker-entrypoint-initdb.d/my.cnf > /etc/mysql/my.cnf + echo "lock" > $LOCK_FILE + + mkdir -p "$RUNDIR" + chown -R mysql:mysql "$RUNDIR" + chown -R mysql:mysql "$DATADIR" + + if [ ! -d "${DATADIR}/mysql" ]; then + echo "Initializing MariaDB data directory..." + mariadb-install-db --user=mysql --datadir="$DATADIR" >/dev/null + fi + + #Initialize DB here for importing SQL files, don't allow connections + echo "Starting temporary MariaDB (no networking) for init..." + mariadbd --user=mysql --datadir="$DATADIR" --skip-networking --socket=/tmp/mysqld.sock & + pid="$!" + + #Wait until mariadb server finished initializing before trying to import anything + for i in {1..60}; do + if mariadb-admin --protocol=socket --socket=/tmp/mysqld.sock ping >/dev/null 2>&1; then + break + fi + sleep 0.5 + done + + echo "Running init scripts in /docker-entrypoint-initdb.d ..." + shopt -s nullglob + for f in /docker-entrypoint-initdb.d/*; do + case "$f" in + *.sql) + echo " -> importing $f" + mariadb --protocol=socket --socket=/tmp/mysqld.sock < "$f" + ;; + *.sql.gz) + echo " -> importing $f" + gunzip -c "$f" | mariadb --protocol=socket --socket=/tmp/mysqld.sock + ;; + *.sh) + echo " -> running $f" + bash "$f" + ;; + *) + echo " -> ignoring $f" + ;; + esac + done + + echo "Shutting down temporary MariaDB..." + mariadb-admin --protocol=socket --socket=/tmp/mysqld.sock shutdown + wait "$pid" || true + +fi + +echo "Starting MariaDB on port ${MARIADB_PORT}..." +exec "$@" --user=mysql --datadir="$DATADIR" --bind-address=0.0.0.0 --port="$MARIADB_PORT" diff --git a/terraform-template/docker/my.cnf b/terraform-template/docker/my.cnf @@ -0,0 +1,17 @@ +[mysqld] +server-id=2 +gtid_strict_mode=1 +log_bin=/var/lib/mysql/bin-mariadb.log +expire_logs_days=8 +sync_binlog=1 +binlog_format = row +log_slave_updates = 1 + +[client-server] +# Port or socket location where to connect +# port = 3306 +socket = /run/mysqld/mysqld.sock + +# Import all .cnf files from configuration directory +!includedir /etc/mysql/conf.d/ +!includedir /etc/mysql/mariadb.conf.d/ diff --git a/terraform-template/dump/01_queries.sql b/terraform-template/dump/01_queries.sql @@ -0,0 +1,10 @@ +CHANGE MASTER TO +MASTER_HOST='192.168.2.37', +MASTER_PORT=3306, +MASTER_USER='repl', +MASTER_PASSWORD=[REDACTED] +MASTER_USE_GTID=slave_pos; + +SET GLOBAL gtid_slave_pos='0-1-61043'; + +START SLAVE; diff --git a/terraform-template/init.sh b/terraform-template/init.sh @@ -0,0 +1,37 @@ +#!/bin/bash + +TARGET_SRV=$1 +ROOT_SRC=$2 +REMOTE_DOCKER_DIR=$3 +REMOTE_DOCKER_SCRIPTS_DIR=$4 +DOCKER_BUILD=$5 +DUMP_DIR=$ROOT_SRC/dump +DOCKER_DIR=$ROOT_SRC/docker +mkdir -p $DUMP_DIR +mkdir -p $DOCKER_DIR + +if [[ $(ps aux | grep -v grep | grep -c mariadbd) -lt 1 ]]; then + echo "Can not find active mariadb instance" + exit 1 +fi + +sed -E -i "s/(gtid_slave_pos=)'[0-9-]+'/\1'%SLAVE%'/" $DUMP_DIR/01_queries.sql + +mysqldump --all-databases --master-data=2 --single-transaction -u root > $DUMP_DIR/00_dump.sql +mysql -u root --skip-column-names -e "SELECT @@gtid_binlog_pos;" > $DUMP_DIR/gtid_pos.txt +GTID=$(cat $DUMP_DIR/gtid_pos.txt) +sed -E -i "s/%SLAVE%/${GTID}/" $DUMP_DIR/01_queries.sql +# The dump captures a consistent snapshot at a point in time, and the GTID position captured alongside it marks exactly where that snapshot ends. The replication can then pick up from this marker. + +scp $DUMP_DIR/00_dump.sql root@$TARGET_SRV:$REMOTE_DOCKER_SCRIPTS_DIR +scp $DUMP_DIR/01_queries.sql root@$TARGET_SRV:$REMOTE_DOCKER_SCRIPTS_DIR + +scp $DOCKER_DIR/Dockerfile root@$TARGET_SRV:$REMOTE_DOCKER_DIR +scp $DOCKER_DIR/entrypoint.sh root@$TARGET_SRV:$REMOTE_DOCKER_DIR + +scp $DOCKER_DIR/my.cnf root@$TARGET_SRV:$REMOTE_DOCKER_SCRIPTS_DIR + +ssh root@$TARGET_SRV "docker build -t $DOCKER_BUILD $REMOTE_DOCKER_DIR" + + + diff --git a/terraform-template/main.tf b/terraform-template/main.tf @@ -0,0 +1,53 @@ +terraform { + required_version = ">= 1.5.0" + + required_providers { + docker = { + source = "kreuzwerker/docker" + version = "~> 3.0" + } + local = { + source = "hashicorp/local" + version = "~> 2.0" + } + } +} + +variable "target_ip" { type = string } +variable "docker_image" { type = string } +variable "batch_id" {type = string} + +provider "docker" { + host = "ssh://root@${var.target_ip}" +} + +resource "docker_network" "lab" { + name = "tf-mariadb-lab-net${var.batch_id}" +} + +resource "docker_container" "server" { + for_each = local.servers + + name = each.key + image = "${var.docker_image}" + hostname = each.key + + env = [ + "MARIADB_PORT=${each.value.port}", + "MARIADB_SERVER_ID=${each.value.server_id}", + ] + + ports { + internal = each.value.port + external = each.value.port + } + + networks_advanced { + name = docker_network.lab.name + } + + labels { + label = "deployed_by" + value = "terraform" + } +} diff --git a/terraform-template/service_discovery.sh b/terraform-template/service_discovery.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +NUMBER=$1 +PORT=$2 +OPERATION=$3 +ROOT_SRC=$4 +TMP=$(mktemp) +PORT_CHECK=$(maxctrl list servers | awk -f $ROOT_SRC/awk_scripts/port_check.awk) +DESIRED_PORT=$(echo "$PORT + 1" | bc) + +if [[ $DESIRED_PORT -le $PORT_CHECK && $OPERATION =~ "APPLY" ]]; then + echo "Desired port of $DESIRED_PORT is unavailable, failed check against $PORT_CHECK, try setting a higher port!" + exit 1 +fi + +maxctrl list servers | awk -v number=$NUMBER -v port=$PORT -f $ROOT_SRC/awk_scripts/service_discovery.awk | sed -e 'H;1h;$!d;x;s/\(.*\),/\1/' > $TMP + +if ! head -n1 "$TMP" | grep -Eq '^[0-9]+$'; then + echo "Failed to derive boundary from maxctrl output" >&2 + exit 1 +fi + +cat $ROOT_SRC/boundary.txt > $ROOT_SRC/boundary.txt.last +awk '(NR==1){print $0}' $TMP > $ROOT_SRC/boundary.txt +sed -i '1d' $TMP + +cat $ROOT_SRC/locals.tf > $ROOT_SRC/locals.tf.last +cat $TMP > $ROOT_SRC/locals.tf + +rm $TMP