1. 程式人生 > >部署高可用的MySQL_Kubernetes中文社群

部署高可用的MySQL_Kubernetes中文社群

1、MySQL簡介

MySQL 是一個開源的關係型資料庫管理系統,使用標準的sql語言,由瑞典 MySQL AB 公司開發,當前屬於 Oracle 公司。能夠 支援大型的資料庫,可以處理上千萬條的資料記錄。可以運行於在Windows、Linux等多種系統上;支援C、C++、Python、Java、Perl、PHP、Eiffel、Ruby和Tcl等程式語言。對於32位系統,MySQL的表文件最大可支援4GB,對於64位系統,MySQL支援最大的表文件為8TB。

2、MySQL的高可用方案

本文的MySQL高可用方案為主從複製+讀寫分離,即由單一的master和多個slave所構成。其中,客戶端通過master對資料庫進行寫操作,通過slave端進行讀操作。master出現問題後,可以將應用切換到slave端。 此方案是MySQL官方提供的一種高可用解決方案,節點間的資料同步採用MySQL Replication技術。

MySQL Replication從一個MySQL資料庫伺服器(master)的資料複製到一個或多個MySQL資料庫伺服器(slave)。在預設情況下,複製是非同步的;slave不需要一直接收來自主機的更新。根據配置,可以複製資料庫中的所有資料庫、選定的資料庫,或者特定的表。

MySQL中複製的優點包括:

  • 擴容解決方案:在多個slave之間擴充套件負載以提高效能。在這種模式下,所有的寫入和更新操作都必須在主伺服器上進行。然而,讀取操作通過slave映象。該模型可以提高寫入操作的效能,同時,也能夠通過增加slave的節點數量,從而顯著地提升讀取速度。
  • 資料安全:資料從master被複制到slave,並且slave可以暫停複製過程。因此,可以在不損壞master的情況下,在slave上執行備份服務。
  • 分析:現場資料可以在master上建立,而對資訊的分析可以在slave進行,而不影響master的效能。
  • 遠端資料分發:可以使用複製為遠端站點建立本地資料的副本,而不必一直通過訪問master。

此高可用的解決方案適用於對資料實時性要求不是特別嚴格的場景,在使用時可以通過廉價的硬體來擴充套件slave的節點數量,將讀壓力分散到多臺slave的機器上面。此方案能夠在很大的程度上解決資料庫讀取資料的壓力瓶頸問題,這是因為在大多的應用系統中,讀壓力要比寫壓力大很多多。

3、安裝部署

3.1 環境要求

  • 已有Kubernetes 1.6+環境;
  • 在Kubernetes中提供多個(具體數量根據有狀態副本集的個數而定)容量大於10g的持久化儲存卷。

3.2 部署MySql

此示例由一個ConfigMap、兩個Service和一個StatefulSet所組成。

3.2.1 建立ConfigMap

通過YAML檔案建立名為mysql的ConfigMap:

$ kubectl create -f {path}/mysql-configmap.yaml --namespace=kube-public 
apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql
  labels:
    app: mysql
data:
  master.cnf: |
    # Apply this config only on the master.
    [mysqld]
    log-bin
    log_bin_trust_function_creators=1
    lower_case_table_names=1
  slave.cnf: |
    # Apply this config only on slaves.
    [mysqld]
    super-read-only
    log_bin_trust_function_creators=1

3.2.2 建立Services

通過yaml檔案建立mysql和mysql-read這兩個Service:

$ kubectl create -f {path}/mysql-services.yaml --namespace=kube-public 
# Headless service for stable DNS entries of StatefulSet members.
apiVersion: v1
kind: Service
metadata:
  name: mysql
  labels:
    app: mysql
spec:
  ports:
  - name: mysql
    port: 3306
  clusterIP: None
  selector:
    app: mysql
---
# Client service for connecting to any MySQL instance for reads.
# For writes, you must instead connect to the master: mysql-0.mysql.
apiVersion: v1
kind: Service
metadata:
  name: mysql-read
  labels:
    app: mysql
spec:
  ports:
  - name: mysql
    port: 3306
  selector:
    app: mysql

StatefulSet控制器為Pod建立了一個DNS條目,而Headless服務為DNS條目提供一個主機。因為Headless服務的名稱為mysql,其他Pod通過<pod-name>.mysql訪問此Pod。客戶端訪問被稱為mysql-read,客戶端服務通過訪問mysql-read讀取資料。通過連線myql執行寫入資料的操作。

3.2.3 建立StatefulSet

通過yaml檔案建立名為mysql的StatefulSet:

$ kubectl create -f {path}/mysql-statefulset.yaml --namespace=kube-public 
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  selector:
    matchLabels:
      app: mysql
  serviceName: mysql
  replicas: 3
  template:
    metadata:
      labels:
        app: mysql
    spec:
      initContainers:
      - name: init-mysql
        image: mysql:5.7
        command:
        - bash
        - "-c"
        - |
          set -ex
          # Generate mysql server-id from pod ordinal index.
          [[ `hostname` =~ -([0-9]+)$ ]] || exit 1
          ordinal=${BASH_REMATCH[1]}
          echo [mysqld] > /mnt/conf.d/server-id.cnf
          # Add an offset to avoid reserved server-id=0 value.
          echo server-id=$((100 + $ordinal)) >> /mnt/conf.d/server-id.cnf
          # Copy appropriate conf.d files from config-map to emptyDir.
          if [[ $ordinal -eq 0 ]]; then
            cp /mnt/config-map/master.cnf /mnt/conf.d/
          else
            cp /mnt/config-map/slave.cnf /mnt/conf.d/
          fi
        volumeMounts:
        - name: conf
          mountPath: /mnt/conf.d
        - name: config-map
          mountPath: /mnt/config-map
      - name: clone-mysql
        image: gcr.io/google-samples/xtrabackup:1.0
        command:
        - bash
        - "-c"
        - |
          set -ex
          # Skip the clone if data already exists.
          [[ -d /var/lib/mysql/mysql ]] && exit 0
          # Skip the clone on master (ordinal index 0).
          [[ `hostname` =~ -([0-9]+)$ ]] || exit 1
          ordinal=${BASH_REMATCH[1]}
          [[ $ordinal -eq 0 ]] && exit 0
          # Clone data from previous peer.
          ncat --recv-only mysql-$(($ordinal-1)).mysql 3307 | xbstream -x -C /var/lib/mysql
          # Prepare the backup.
          xtrabackup --prepare --target-dir=/var/lib/mysql
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
          subPath: mysql
        - name: conf
          mountPath: /etc/mysql/conf.d
      containers:
      - name: mysql
        image: mysql:5.7
        env:
        - name: MYSQL_ALLOW_EMPTY_PASSWORD
          value: "1"
        ports:
        - name: mysql
          containerPort: 3306
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
          subPath: mysql
        - name: conf
          mountPath: /etc/mysql/conf.d
        resources:
          requests:
            cpu: 500m
            memory: 1Gi
        livenessProbe:
          exec:
            command: ["mysqladmin", "ping"]
          initialDelaySeconds: 30
          periodSeconds: 10
          timeoutSeconds: 5
        readinessProbe:
          exec:
            # Check we can execute queries over TCP (skip-networking is off).
            command: ["mysql", "-h", "127.0.0.1", "-e", "SELECT 1"]
          initialDelaySeconds: 5
          periodSeconds: 2
          timeoutSeconds: 1
      - name: xtrabackup
        image: gcr.io/google-samples/xtrabackup:1.0
        ports:
        - name: xtrabackup
          containerPort: 3307
        command:
        - bash
        - "-c"
        - |
          set -ex
          cd /var/lib/mysql
          # Determine binlog position of cloned data, if any.
          if [[ -f xtrabackup_slave_info ]]; then
            # XtraBackup already generated a partial "CHANGE MASTER TO" query
            # because we're cloning from an existing slave.
            mv xtrabackup_slave_info change_master_to.sql.in
            # Ignore xtrabackup_binlog_info in this case (it's useless).
            rm -f xtrabackup_binlog_info
          elif [[ -f xtrabackup_binlog_info ]]; then
            # We're cloning directly from master. Parse binlog position.
            [[ `cat xtrabackup_binlog_info` =~ ^(.*?)[[:space:]]+(.*?)$ ]] || exit 1
            rm xtrabackup_binlog_info
            echo "CHANGE MASTER TO MASTER_LOG_FILE='${BASH_REMATCH[1]}',\
                  MASTER_LOG_POS=${BASH_REMATCH[2]}" > change_master_to.sql.in
          fi
          # Check if we need to complete a clone by starting replication.
          if [[ -f change_master_to.sql.in ]]; then
            echo "Waiting for mysqld to be ready (accepting connections)"
            until mysql -h 127.0.0.1 -e "SELECT 1"; do sleep 1; done
            echo "Initializing replication from clone position"
            # In case of container restart, attempt this at-most-once.
            mv change_master_to.sql.in change_master_to.sql.orig
            mysql -h 127.0.0.1 <<EOF
          $(<change_master_to.sql.orig),
            MASTER_HOST='mysql-0.mysql',
            MASTER_USER='root',
            MASTER_PASSWORD='',
            MASTER_CONNECT_RETRY=10;
          START SLAVE;
          EOF
          fi
          # Start a server to send backups when requested by peers.
          exec ncat --listen --keep-open --send-only --max-conns=1 3307 -c \
            "xtrabackup --backup --slave-info --stream=xbstream --host=127.0.0.1 --user=root"
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
          subPath: mysql
        - name: conf
          mountPath: /etc/mysql/conf.d
        resources:
          requests:
            cpu: 100m
            memory: 100Mi
      volumes:
      - name: conf
        emptyDir: {}
      - name: config-map
        configMap:
          name: mysql
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 10Gi

通過執行如下的命令可以檢視啟動過程:

$ kubectl get pods -l app=mysql --watch --namespace=kube-public

在啟動後,應該能夠看到如下的資訊:

4、理解有狀態Pod的初始化

StatefulSet控制器按Pod的序號索引一次啟動一個Pod,控制器每個Pod指派一個唯一的、穩定的名稱,名稱的格式為<statefulset-name>-<ordinal-index>。在此示例中,Pod的名稱為mysql-0,此節點為master主節點;mysql-1和mysql-2,這兩個節點為slave從節點。

4.1 建立配置檔案

在開始啟動Pod規格中的任何容器之前,Pod首先會按照YAML配置中定義的順序執行初始化容器。

第一個初始化容器為init-mysql,將以順序索引建立MySQL配置檔案。

指令碼從Pod名稱的結尾處獲取並確定它的順序索引,順序索引通過hostname命令獲取。然後,它會按照順序儲存在conf.d目錄下的server-id.cnf檔案中。此行為將StatefulSet控制器提供的唯一和穩定的身份標識轉為mysql服務Id的域。在init-mysql容器中,指令碼使用來自於ConfigMap中master.cnf或slave.cnf。

在此例子的拓撲關係中,存在一個MySQL master節點和多個MySQL slave節點,指令碼簡單的指派順序0給主節點。這能夠保證MySQL主節點在建立從節點之前就已經準備就緒。

4.2 克隆已存在的資料

一般來說,當一個新的Pod加入進來作為從節點時,必須假設MySQL master已經有關於它的資料。也假設slave副本的日誌必須重新開始的。這些假設對於StatefulSet的擴縮容是很關鍵。

第二個初始化容器是clone-mysql,它在空的PersistentVolume上執行克隆從節點Pod的行為。這意味著它將從已在執行的Pod中拷貝資料,因此,它的當前狀態能夠與從master開始的副本節點一致。

MySQL自身並沒有提供能夠做到上述能力的機制,因此,此例子使用開源的Percona XtraBackup工具來實現。在克隆的過程中,為了對MySQL主節點影響的最小化,指令碼會要求每一個新的Pod從順序索引值小的Pod中進行克隆。這樣做的原因是,StatefulSet控制器需要一直保證Pod N需要在Pod N+1之前準備就緒。

4.3 啟動副本

在初始化容器完成後,容器將正常執行。MySQL Pod由執行實際mysqld服務的MySQL容器組成,xtrabacekup容器只是作為備份的工具。xtrabackup負責監控克隆資料檔案,並確定是否在從節點初始化MySQL副本。如果需要,它將等待MySQL就緒,然後執行 CHANGE MASTER TO和START SLAVE命令。

一旦一個從節點開始複製,它將記住MySQL master,並自動進行重新連線,因為從節點尋找主節點作為穩定DNS名稱(mysql-0.mysql),它們自動的發現主節點。最後,在啟動副本後,xtrabackup容器也監聽來自於其它Pod對資料克隆的請求。

5、MySQL部署環境驗證

1)通過執行一個臨時的容器(使用mysql:5.7映象),使用MySQL 客戶端傳送測試請求給MySQL master節點(主機名為mysql-0.mysql;跨名稱空間的話,主機名請使用mysql-0.mysql.kube-public)

$ kubectl run mysql-client --image=mysql:5.7 -it --rm --restart=Never -- mysql -h mysql-0.mysql.kube-public
CREATE DATABASE demo; 
CREATE TABLE demo.messages (message VARCHAR(250)); 
INSERT INTO demo.messages VALUES ('hello');

在master節點上建立demo資料庫,並建立一個只有message欄位的demo.messages的表,併為message欄位插入hello值。

2)使用主機名為mysql-read來發送測試請求給伺服器:

$ kubectl run mysql-client --image=mysql:5.7 -i -t --rm --restart=Never -- mysql -h mysql-read.kube-public

6、擴容slave的數量

1)對於mysql副本,通過新增從節點進行擴容。

$ kubectl scale statefulset mysql --replicas=5 --namespace=kube-public 

2)通過下面的命令檢視新的Pod:

$ kubectl get pods -l app=mysql --watch --namespace=kube-public 

3)縮容也是無縫的:

$ kubectl scale statefulset mysql --replicas=3 --namespace=kube-public

參考資料

作者簡介:
季向遠,北京神舟航天軟體技術有限公司產品經理。本文版權歸原作者所有。