在 K8S 中部署 MySQL 双主复制高可用服务器

之前因为某些需要,写了一个运行于 K8S 的 MySQL 双主复制高可用的编排。

核心使用 StatefulSet,通过 initContainers 初始化的方式完成初次启动配置及同步, 完全自动化。

当然,这只是用于测试环境的一个编排,因为缺少存储部分,因此不推荐用于生产。

编排代码如下:

mysql-mm-rbac.yml

RBAC 权限定义。

 1apiVersion: v1
 2kind: Namespace
 3metadata:
 4  name: mysql-ha
 5
 6---
 7apiVersion: v1
 8kind: ServiceAccount
 9metadata:
10  name: cluster-helper
11  namespace: mysql-ha
12
13---
14kind: Role
15apiVersion: rbac.authorization.k8s.io/v1
16metadata:
17  name: cluster-helper
18  namespace: mysql-ha
19rules:
20  - apiGroups: ["apps"]
21    resources: ["statefulsets"]
22    verbs: ["get", "list"]
23
24---
25kind: RoleBinding
26apiVersion: rbac.authorization.k8s.io/v1
27metadata:
28  name: cluster-helper
29  namespace: mysql-ha
30subjects:
31  - kind: ServiceAccount
32    name: cluster-helper
33roleRef:
34  kind: Role
35  name: cluster-helper
36  apiGroup: rbac.authorization.k8s.io

mysql-mm.yml

核心编排定义。

  1---
  2apiVersion: v1
  3kind: ConfigMap
  4metadata:
  5  name: mysql-scripts
  6  namespace: mysql-ha
  7data:
  8  common.sh: |
  9    #!/bin/bash
 10    update_data() {
 11      local svc=${HOSTNAME%-*}
 12      local token=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
 13      local ns=${POD_NAMESPACE}
 14      local url=https://${KUBERNETES_SERVICE_HOST}/apis/apps/v1/namespaces/${ns}/statefulsets/${svc}
 15      curl -kfsSL -H "Authorization: Bearer ${token}" ${url} > /ha-info/state.json
 16    }
 17    get_data() {
 18      if [ ! -f /ha-info/state.json ]; then
 19        return
 20      fi
 21      cat /ha-info/state.json
 22    }
 23    get_replicas() {
 24      get_data | grep '"replicas":' | head -n 1 | awk '{print $2}' | tr -d ","
 25    }
 26    get_ready_replicas() {
 27      get_data | grep '"readyReplicas":' | awk '{print $2}' | tr -d ","
 28    }
 29    get_current_replicas() {
 30      get_data | grep '"currentReplicas":' | awk '{print $2}' | tr -d ","
 31    }
 32    sync_done() {
 33      echo "Sync has been done."
 34      # Infinite blocking
 35      while true; do
 36        sleep 3600
 37      done
 38    }
 39  init-cluster.sh: |
 40    #!/bin/sh
 41    if [[ -f /var/lib/mysql/CLUSTER_WAS_INIT ]]; then
 42      exit 0
 43    fi
 44    echo Initialize cluster configuration...
 45    source /scripts/common.sh
 46    SERVER_ID=$(hostname | grep -oE '[^-]+$')
 47    cat > /etc/mysql/conf.d/master-to-master.cnf <<EOF
 48    [mysql]
 49    no-auto-rehash
 50    [mysqld]
 51    skip-host-cache
 52    skip-name-resolve
 53    server_id=1${SERVER_ID}
 54    log-bin=mysql-bin
 55    auto_increment_increment=2
 56    auto_increment_offset=$((${SERVER_ID}+1))
 57    EOF
 58    echo Initialize cluster configuration successful.
 59  sync-ha.sh: |
 60    #!/bin/bash
 61    set -e
 62    if [ -f /var/lib/mysql/CLUSTER_WAS_INIT ]; then
 63      sync_done
 64    fi
 65    source /scripts/common.sh
 66    echo "Waiting all replicas up..."
 67    while [ `get_current_replicas` -ne `get_ready_replicas` ]
 68    do
 69      sleep 5
 70    done
 71    get_another_host() {
 72      local svc=${HOSTNAME%-*}
 73      local server_id=$(hostname | grep -oE '[^-]+$')
 74      local another_id
 75      if [[ $server_id == 0 ]]; then
 76        another_id=1
 77      else
 78        another_id=0
 79      fi
 80      echo "${svc}-${another_id}"
 81    }
 82    get_binlog_file() {
 83      local host=$1
 84      mysql -h $host -p"${MYSQL_ROOT_PASSWORD}" -e 'show master status\G;' | grep 'File' | awk '{print $2}'
 85    }
 86    get_binlog_pos() {
 87      local host=$1
 88      mysql -h $host -p"${MYSQL_ROOT_PASSWORD}" -e 'show master status\G;' | grep 'Position' | awk '{print $2}'
 89    }
 90    ensure_db() {
 91      while ! mysql -p"${MYSQL_ROOT_PASSWORD}" -e 'SELECT 1' > /dev/null; do
 92        sleep 5
 93      done
 94      mysql -p"${MYSQL_ROOT_PASSWORD}" -e "GRANT RELOAD, SUPER, REPLICATION SLAVE ON *.* TO 'root'@'%' identified by '${MYSQL_ROOT_PASSWORD}'"
 95    }
 96    wait_db() {
 97      local host=$1
 98      while ! mysql -h $host -p"${MYSQL_ROOT_PASSWORD}" -e 'SELECT 1' > /dev/null; do
 99        sleep 5
100      done
101    }
102    sync_core() {
103      local mainHost=$1
104      local targetHost=$2
105      local file=$3
106      local pos=$4
107      echo Synchionize "${mainHost}" "file=${file}" "pos=${pos}"
108      mysql -h "${mainHost}" -p"${MYSQL_ROOT_PASSWORD}" -e "CHANGE MASTER TO MASTER_HOST='${targetHost}', 
109        MASTER_USER='root',
110        MASTER_PASSWORD='${MYSQL_ROOT_PASSWORD}',
111        MASTER_LOG_FILE='${file}',
112        MASTER_LOG_POS=${pos};"
113      mysql -h "${mainHost}" -p"${MYSQL_ROOT_PASSWORD}" -e 'start slave;'
114    }
115    sync_db() {
116      local svc=${HOSTNAME%-*}
117      local right_fqdn=`get_another_host`.${svc}.${POD_NAMESPACE}.svc.cluster.local
118      local left_fqdn=${HOSTNAME}.${svc}.${POD_NAMESPACE}.svc.cluster.local
119      echo "Wait db $right_fqdn ..."
120      wait_db $right_fqdn
121      local left_file=`get_binlog_file ${left_fqdn}`
122      local left_pos=`get_binlog_pos ${left_fqdn}`
123      local right_file=`get_binlog_file ${right_fqdn}`
124      local right_pos=`get_binlog_pos ${right_fqdn}`
125      echo Sync right -> left ...
126      sync_core "$left_fqdn" "$right_fqdn" "$right_file" "$right_pos"
127      echo Sync left -> right ...
128      sync_core "$right_fqdn" "$left_fqdn" "$left_file" "$left_pos"
129    }
130    echo "Ensuring db..."
131    ensure_db
132    server_id=$(hostname | grep -oE '[^-]+$')
133    if [[ $server_id -eq 0 ]]; then
134      sync_db
135      echo "master-$server_id synchionize suffessful."
136    else
137      echo "master-$server_id are ready."
138    fi
139    touch /var/lib/mysql/CLUSTER_WAS_INIT
140    sync_done
141  sync-ha-metadata.sh: |
142    #!/bin/sh
143    if [ -f /var/lib/mysql/CLUSTER_WAS_INIT ]; then
144      sync_done
145    fi
146    apk add curl
147    source /scripts/common.sh
148    while [ ! -f /var/lib/mysql/CLUSTER_WAS_INIT ]
149    do
150      update_data
151      sleep 3
152    done
153    sync_done
154
155---
156apiVersion: apps/v1beta2
157kind: StatefulSet
158metadata:
159  name: mysql-mm
160  namespace: mysql-ha
161spec:
162  serviceName: mysql-mm
163  replicas: 2
164  selector:
165    matchLabels:
166      app: mysql-mm
167  template:
168    metadata:
169      labels:
170        app: mysql-mm
171    spec:
172      initContainers:
173        - name: init-cluster
174          image: alpine
175          command:
176            - /scripts/init-cluster.sh
177          volumeMounts:
178          - name: scripts
179            mountPath: /scripts
180            readOnly: true
181          - name: mysql-conf
182            mountPath: /etc/mysql/conf.d
183          - name: ha-metadata
184            mountPath: /ha-info
185            readOnly: true
186      containers:
187      - name: mysql
188        image: mysql:5.7
189        env:
190        - name: "MYSQL_ROOT_PASSWORD"
191          value: "123456!!"
192        - name: "MYSQL_ROOT_HOST"
193          value: "%"
194        volumeMounts:
195        - name: scripts
196          mountPath: /scripts
197          readOnly: true
198        - name: mysql-conf
199          mountPath: /etc/mysql/conf.d
200        - name: mysql-sock
201          mountPath: /var/run/mysqld
202        - name: mysql-data
203          mountPath: /var/lib/mysql
204        ports:
205        - containerPort: 3306
206      - name: sync-ha
207        image: mysql:5.7
208        command:
209          - /scripts/sync-ha.sh
210        env:
211        - name:  "MYSQL_ROOT_PASSWORD"
212          value: "123456!!"
213        - name: POD_NAMESPACE
214          valueFrom:
215            fieldRef:
216              fieldPath: metadata.namespace
217        volumeMounts:
218        - name: scripts
219          mountPath: /scripts
220          readOnly: true
221        - name: mysql-sock
222          mountPath: /var/run/mysqld
223        - name: ha-metadata
224          mountPath: /ha-info
225        - name: mysql-data
226          mountPath: /var/lib/mysql
227      - name: sync-ha-metadata
228        image: alpine
229        command:
230          - /scripts/sync-ha-metadata.sh
231        env:
232          - name: POD_NAMESPACE
233            valueFrom:
234              fieldRef:
235                fieldPath: metadata.namespace
236        volumeMounts:
237        - name: scripts
238          mountPath: /scripts
239          readOnly: true
240        - name: ha-metadata
241          mountPath: /ha-info
242        - name: mysql-data
243          mountPath: /var/lib/mysql
244      volumes:
245        - name: scripts
246          configMap:
247            name: mysql-scripts
248            defaultMode: 0755
249        - name: ha-metadata
250          emptyDir: {}
251        - name: mysql-conf
252          emptyDir: {}
253        - name: mysql-sock
254          emptyDir: {}
255        - name: mysql-data
256          emptyDir: {}
257      serviceAccountName: cluster-helper
258  # volumeClaimTemplates:
259  # - metadata:
260  #     name: pvc-mysql-data
261  #   spec:
262  #     accessModes: [ "ReadWriteOnce" ]
263  #     resources:
264  #       requests:
265  #         storage: 10Gi
266
267---
268# Service
269apiVersion: v1
270kind: Service
271metadata:
272  name: mysql-mm
273  namespace: mysql-ha
274spec:
275  ports:
276  - port: 3306
277    targetPort: 3306
278  selector:
279    app: mysql-mm
View Comments