Logo

Personal Ops Runbook

Personal runbook covering infrastructure operations for Cloud, Kubernetes, OpenStack, and Ceph environments. Includes deployment and teardown procedures, node management, cluster monitoring setup, and incident response workflows compiled from day-to-day operational work. Intended strictly for personal reference — configurations and scripts are environment-specific and not guaranteed to work as-is elsewhere.

Cài đặt Docker

Cài đặt Docker

apt-get update
apt-get install ca-certificates curl gnupg lsb-release -y
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null

apt-get update
apt-get install docker-ce docker-ce-cli containerd.io -y
chmod 666 /var/run/docker.sock
systemctl restart docker && systemctl enable docker

Gắn Proxy Server cho Docker

mkdir -p /etc/systemd/system/docker.service.d
cat > /etc/systemd/system/docker.service.d/http-proxy.conf << 'OEF'
[Service]
Environment="HTTP_PROXY=http://10.237.7.250:3128" "HTTPS_PROXY=http://10.237.7.250:3128" "NO_PROXY=localhost,127.0.0.1,docker-registry.somecorporation.com"
OEF
systemctl daemon-reload
systemctl restart docker

Cài đặt Docker Compose

curl -L "https://github.com/docker/compose/releases/download/v2.6.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
docker-compose -v

Chuẩn bị cấu hình

Create folder for MySQL on node1 and node2

mkdir -p /opt/mysql
mkdir -p /opt/mysql/data
mkdir -p /opt/mysql/backup

Move to MySQL folder on node1 and node2

cd /opt/mysql

Create environment file for MySQL on all nodes

cat > /opt/mysql/backup/.env << 'OEF'
MARIADB_ROOT_PASSWORD=db_root_password
MARIADB_USER=db_user
MARIADB_PASSWORD=password
MARIADB_DATABASE=db_name
MARIADB_IP_NODE1=10.237.7.77
MARIADB_IP_NODE2=10.237.7.78
KEEPALIVED_VIP=10.237.7.36/24
KEEPALIVED_PASSWORD=keepalived_password
KEEPALIVED_INTERFACE=ens160
OEF

Copy /opt/mysql/backup/.env to /opt/mysql/.env

cp /opt/mysql/backup/.env /opt/mysql/.env
source /opt/mysql/backup/.env

Create 50-server.cnf file for MySQL on all nodes

cat > /opt/mysql/50-server.cnf << 'OEF'
[server]
[mysqld]
pid-file                = /run/mysqld/mysqld.pid
basedir                 = /usr
expire_logs_days        = 10
character-set-server  = utf8mb4
collation-server      = utf8mb4_general_ci

# Load plugins
plugin_load_add = server_audit
plugin_load_add = simple_password_check

# Audit settings
server_audit_events = connect,query_dcl,query_ddl
server_audit_logging = ON
server_audit_output_type = FILE

# Password check settings
simple_password_check_other_characters = 1
simple_password_check_minimal_length = 14
simple_password_check_digits = 1
simple_password_check_letters_same_case = 1
strict_password_validation = ON
simple_password_check = OFF

[embedded]
[mariadb]
[mariadb-10.6]
OEF

Create galera.cnf file for MySQL on first node

cat > /opt/mysql/galera.cnf << OEF
[galera]
wsrep_on                 = ON
wsrep_cluster_name       = "MariaDB Galera Cluster"
wsrep_provider           = /usr/lib/galera/libgalera_smm.so
wsrep_cluster_address    = "gcomm://" # This is the default value. Change it to the IP address of the first node.
# wsrep_cluster_address    = "gcomm://${MARIADB_IP_NODE1},${MARIADB_IP_NODE2}"
binlog_format            = row
default_storage_engine   = InnoDB
innodb_autoinc_lock_mode = 2
bind-address = 0.0.0.0
wsrep_node_address="${MARIADB_IP_NODE1}"
OEF

Create galera.cnf file for MySQL on second node

cat > /opt/mysql/galera.cnf << OEF
[galera]
wsrep_on                 = ON
wsrep_cluster_name       = "MariaDB Galera Cluster"
wsrep_provider           = /usr/lib/galera/libgalera_smm.so
wsrep_cluster_address    = "gcomm://${MARIADB_IP_NODE1},${MARIADB_IP_NODE2}"
binlog_format            = row
default_storage_engine   = InnoDB
innodb_autoinc_lock_mode = 2
bind-address = 0.0.0.0
wsrep_node_address="${MARIADB_IP_NODE2}"
OEF

Create docker-compose.yml file for MySQL on all nodes

cat > /opt/mysql/docker-compose.yml << 'OEF'
version: "3"
services: 
 mariadb:
   image: mariadb:10.11
   container_name: mariadb
   network_mode: host
   restart: always
   env_file: .env
   environment:
    - MARIADB_USER=${MARIADB_USER}
    - MARIADB_PASSWORD=${MARIADB_PASSWORD}
    - MARIADB_DATABASE=${MARIADB_DATABASE}
    - MARIADB_ROOT_PASSWORD=${MARIADB_ROOT_PASSWORD}
   volumes:
    - /opt/mysql/data:/var/lib/mysql
    - /opt/mysql/backup:/opt/mysql/backup
    - /opt/mysql/galera.cnf:/etc/mysql/mariadb.conf.d/galera.cnf
    - /opt/mysql/50-server.cnf:/etc/mysql/mariadb.conf.d/50-server.cnf
   cap_add:
    - all
OEF

Deploy MySQL on first node

docker-compose up -d

Check wsrep_cluster_size on first node

docker-compose exec mariadb mysql -u root -p${MARIADB_ROOT_PASSWORD} -e "SHOW STATUS LIKE 'wsrep_cluster_size';"

Example output

shell> docker-compose exec mariadb mysql -u root -p${MARIADB_ROOT_PASSWORD} -e "SHOW STATUS LIKE 'wsrep_cluster_size';"
+--------------------+-------+
| Variable_name      | Value |
+--------------------+-------+
| wsrep_cluster_size | 1     |
+--------------------+-------+

Deploy MySQL on second node

docker-compose up -d

Check wsrep_cluster_size on any node

docker-compose exec mariadb mysql -u root -p${MARIADB_ROOT_PASSWORD} -e "SHOW STATUS LIKE 'wsrep_cluster_size';"

Example output

shell> docker-compose exec mariadb mysql -u root -p${MARIADB_ROOT_PASSWORD} -e "SHOW STATUS LIKE 'wsrep_cluster_size';"
+--------------------+-------+
| Variable_name      | Value |
+--------------------+-------+
| wsrep_cluster_size | 2     |
+--------------------+-------+

Deploy Garbd node

Create folder for MySQL on Garbd node

mkdir -p /opt/mysql

Move to MySQL folder on Garbd node

cd /opt/mysql

Create Dockerfile for Garbd on other node

cat > /opt/mysql/Dockerfile << 'EOF'
# Use an official MariaDB as a parent image
FROM mariadb:10.11

# Set up proxy arguments
ARG http_proxy
ARG https_proxy
ARG no_proxy

# Set environment variables for the proxy
ENV http_proxy=${http_proxy}
ENV https_proxy=${https_proxy}
ENV no_proxy=${no_proxy}

# Update the package list and install necessary packages
RUN apt-get update && \
    apt-get install -y galera-4 galera-arbitrator-4

# Create necessary directories and files, set permissions
RUN mkdir -p /var/log/ && \
    touch /var/log/garbd.log && \
    chown nobody:nogroup /var/log/garbd.log && \
    chmod 644 /var/log/garbd.log
EOF

Create docker-compose.yml file for Garbd

cat > /opt/mysql/garbd-docker-compose.yml << OEF
version: "3"
services: 
  garbd: 
    image: garbd:latest
    container_name: garbd
    network_mode: host
    restart: always
    command: >
      garbd
      --address "gcomm://${MARIADB_IP_NODE1}:4567,${MARIADB_IP_NODE2}:4567"
      --group "MariaDB Galera Cluster"
      --options "pc.wait_prim=no"
      --log "/var/log/garbd.log"
OEF

Build Garbd

docker build --build-arg http_proxy=http://10.237.7.250:3128 --build-arg https_proxy=http://10.237.7.250:3128 --build-arg no_proxy="localhost,127.0.0.1,10.237.7.0/24" -t garbd

Deploy Garbd

docker-compose up -d

Check wsrep_cluster_size on node1 or node2

shell> docker-compose exec mariadb mysql -u root -p"db_root_password" -e "SHOW STATUS LIKE 'wsrep_cluster_size';"
+--------------------+-------+
| Variable_name      | Value |
+--------------------+-------+
| wsrep_cluster_size | 3     |
+--------------------+-------+

Install Keepalived on MARIADB-NODE1 and MARIADB-NODE2

On MARIADB-NODE1

apt-get install linux-headers-$(uname -r)
apt-get update
apt-get install keepalived -y

cat > /etc/keepalived/keepalived.conf << OEF
global_defs {
        script_user root
        enable_script_security
}

vrrp_script check_process {
    script "/usr/bin/nc -zv 127.0.0.1 3306"
    interval 2
    timeout 3
    fall 2
    rise 2
}

# Virtual interface
# The priority specifies the order in which the assigned interface to take over in a failover
vrrp_instance VI_240 {
        state MASTER
        interface ${KEEPALIVED_INTERFACE}
        virtual_router_id 240
        priority 200
        advert_int 2

        authentication {
                auth_type PASS
                auth_pass ${KEEPALIVED_PASSWORD}
        }

    unicast_src_ip ${MARIADB_IP_NODE1}
    unicast_peer {
            ${MARIADB_IP_NODE2}
    }

        # The virtual ip address shared between the two loadbalancers
        virtual_ipaddress {
                ${KEEPALIVED_VIP} dev ${KEEPALIVED_INTERFACE}
        }

        track_script {
                check_process
        }
}
OEF

On MARIADB-NODE2

cat > /etc/keepalived/keepalived.conf << OEF
global_defs {
        script_user root
        enable_script_security
}

vrrp_script check_process {
    script "/usr/bin/nc -zv 127.0.0.1 3306"
    interval 2
    timeout 3
    fall 2
    rise 2
}

# Virtual interface
# The priority specifies the order in which the assigned interface to take over in a failover
vrrp_instance VI_240 {
        state BACKUP
        interface ${KEEPALIVED_INTERFACE}
        virtual_router_id 240
        priority 100
        advert_int 2

        authentication {
                auth_type PASS
                auth_pass ${KEEPALIVED_PASSWORD}
        }

        unicast_src_ip ${MARIADB_IP_NODE2}
        unicast_peer {
            ${MARIADB_IP_NODE1}
        }

        virtual_ipaddress {
                ${KEEPALIVED_VIP} dev ${KEEPALIVED_INTERFACE}
        }

        track_script {
                check_process
        }
}
OEF

Restart, enable and check the status of Keepalived on MARIADB-NODE1 and MARIADB-NODE2

systemctl restart keepalived
systemctl enable keepalived
systemctl status keepalived

Check the status of Keepalived on MARIADB-NODE1 and MARIADB-NODE2

ip a | grep ${KEEPALIVED_VIP}

Import DB

Enter the MariaDB container on MARIADB-NODE1

docker exec -it mariadb bash

Source the environment file

source /opt/mysql/backup/.env

Install MariaDB client on MARIADB-NODE1

apt install mariadb-client-core-10.6 -y

Restore the backup of db_name on VIP

mysql -h $(echo ${KEEPALIVED_VIP} | cut -d'/' -f1) -u db_user -p${MARIADB_PASSWORD} ${MARIADB_DATABASE} < /opt/mysql/backup/db_name.sql

Verify the connection to MariaDB olsn MARIADB-NODE1 and MARIADB-NODE2 from VIP

mysql -h $(echo ${KEEPALIVED_VIP} | cut -d'/' -f1) -u db_user -p${MARIADB_PASSWORD} -D ${MARIADB_DATABASE} -e "SELECT COUNT(*) FROM user;"
+----------+
| COUNT(*) |
+----------+
|     1349 |
+----------+

Create again galera.cnf file for MySQL on first node

cat > /opt/mysql/galera.cnf << OEF
[galera]
wsrep_on                 = ON
wsrep_cluster_name       = "MariaDB Galera Cluster"
wsrep_provider           = /usr/lib/galera/libgalera_smm.so
# wsrep_cluster_address    = "gcomm://" # This is the default value. Change it to the IP address of the first node.
wsrep_cluster_address    = "gcomm://${MARIADB_IP_NODE1},${MARIADB_IP_NODE2}"
binlog_format            = row
default_storage_engine   = InnoDB
innodb_autoinc_lock_mode = 2
bind-address = 0.0.0.0
wsrep_node_address="${MARIADB_IP_NODE1}"
OEF