想提高運維效率,那就把MySQL數據庫部署到Kubernetes 集群中


摘要:Kubernetes 很多看起來比較“繁瑣”的設計的主要目的,都是希望為開發者提供更多的“可擴展性”,給使用者帶來更多的“穩定性”和“安全感”。

本文分享自華為雲社區《如何在 Kubernetes 集群中搭建一個復雜的 MySQL 數據庫?》,作者:zuozewei 。

前言

實際生產環境中,為了穩定和高可用,運維團隊一般不會把 MySQL 數據庫部署在 Kubernetes 集群中,一般是用雲廠商的數據庫或者自己在高性能機器(如裸金屬服務器)上搭建。

但是,對於測試開發環境,我們完全可以把 MySQL 部署到各自的 Kubernetes 集群中,非常有助於提升運維效率,而且還有助於Kubernetes 使用的經驗積累。

簡易部署

​如下所示,我們僅需設置 root 用戶密碼(環境變量 MYSQL_ROOT_PASSWORD), 便可輕松的使用 MySQL 官方鏡像構建一個 MySQL 數據庫。

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  labels:
    app: mysql-min
  name: mysql-min
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mysql-min
  template:
    metadata:
      labels:
        app: mysql-min
    spec:
      containers:
      - image: centos/mysql-57-centos7:latest
        name: mysql-min
        imagePullPolicy: IfNotPresent
        env:
        - name: MYSQL_ROOT_PASSWORD
          value: admin@123

​創建一 Service 以便集群內外均可訪問數據庫,其中集群外需通過 nodePort 設置的 30336 端口訪問。

apiVersion: v1
kind: Service
metadata:
  labels:
    app: mysql-min
    release: mysql-min
  name: mysql-min
  namespace: default
spec:
  ports:
  - name: mysql
    port: 3306
    protocol: TCP
    nodePort: 30336
    targetPort: mysql
  selector:
    app: mysql-min
  #目前sessionAffinity可以提供"None""ClientIP"兩種設定:
  #None: 以round robin的方式輪詢下面的Pods。
  #ClientIP: 以client ip的方式固定request到同一台機器。
  sessionAffinity: None
  type: NodePort
#status:
#  loadBalancer: {}

接着,訪問數據庫並驗證其運行正常:

# kubectl get pod   # 當前Pod名稱
NAME                         READY     STATUS    RESTARTS   AGE
mysql-min-5b5668c448-t44ml   1/1       Running   0          3h

# 通過本機訪問
# kubectl exec -it mysql-min-5b5668c448-t44ml -- mysql -uroot -padmin@123
mysql> select 1;
+---+
| 1 |
+---+
| 1 |
+---+

# 集群內部通過mysql service訪問:
# kubectl exec -it mysql-min-5b5668c448-t44ml -- mysql -uroot -padmin@123 -hmysql

mysql> select now();
+---------------------+
| now()               |
+---------------------+
| 2021-03-13 07:19:14 |
+---------------------+

# 集群外部,可通過任何一個 K8S 節點訪問數據庫:
# mysql -uroot -padmin@123 -hworker-1 -P30336 

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
+--------------------+

擴展部署

持久化存儲

若要確保 MySQL 重啟后數據仍然存在,我們需為其配置可持久化存儲,我這里的實驗環境使用的是 Local Persistent Volume,也就是說,我希望 Kubernetes 能夠直接使用宿主機上的本地磁盤目錄,而不依賴於遠程存儲服務,來提供“持久化”的容器 Volume。這樣做的好處很明顯,由於這個 Volume 直接使用的是本地磁盤,尤其是 SSD 盤,它的讀寫性能相比於大多數遠程存儲來說,要好得多。這個需求對本地物理服務器部署的私有 Kubernetes 集群來說,非常常見。

值得指出的是其次,相比於正常的 PV,一旦這些節點宕機且不能恢復時,本地存儲 Volume 的數據就可能丟失。這就要求使用 其的應用必須具備數據備份和恢復的能力,允許你把這些數據定時備份在其他位置。

不難想象, Local Persistent Volume 的設計,主要面臨兩個難點。

第一個難點在於:如何把本地磁盤抽象成 PV。

可能你會說,Local Persistent Volume 不就等同於 hostPath 加 NodeAffinity 嗎?

比如,一個 Pod 可以聲明使用類型為 Local 的 PV,而這個 PV 其實就是一個 hostPath 類型的 Volume。如果這個 hostPath 對應的目錄,已經在節點 A 上被事先創建好了。那么,我只需要再給這個 Pod 加上一個 nodeAffinity=nodeA,不就可以使用這個 Volume 了嗎?

事實上,你絕不應該把一個宿主機上的目錄當作 PV 使用。這是因為,這種本地目錄的存儲行為完全不可控,它所在的磁盤隨時都可能被應用寫滿,甚至造成整個宿主機宕機。而且,不同的本地目錄之間也缺乏哪怕最基礎的 I/O 隔離機制。

所以,一個 本地存儲 Volume 對應的存儲介質,一定是一塊額外掛載在宿主機的磁盤或者塊設備(“額外”的意思是,它不應該是宿主機根目錄所使用的主硬盤)。這個原則,我們可以稱為“一個 PV 一塊盤”

第二個難點在於:調度器如何保證 Pod 始終能被正確地調度到它所請求的本地 Volume 所在的節點上呢?

造成這個問題的原因在於,對於常規的 PV 來說,Kubernetes 都是先調度 Pod 到某個節點上,然后,再通過“兩階段處理”來“持久化”這台機器上的 Volume 目錄,進而完成 Volume 目錄與容器的綁定掛載。

可是,對於 Local PV 來說,節點上可供使用的磁盤(或者塊設備),必須是運維人員提前准備好的。它們在不同節點上的掛載情況可以完全不同,甚至有的節點可以沒這種磁盤。

所以,這時候,調度器就必須能夠知道所有節點與 Local Persistent Volume 對應的磁盤的關聯關系,然后根據這個信息來調度 Pod。

這個原則,我們可以稱為“在調度的時候考慮 Volume 分布”。在 Kubernetes 的調度器里,有一個叫作 VolumeBindingChecker 的過濾條件專門負責這個事情。在 Kubernetes v1.11 中,這個過濾條件已經默認開啟了。

基於上述講述,在開始使用 Local Persistent Volume 之前,你首先需要在集群里配置好磁盤或者塊設備。在公有雲上,這個操作等同於給虛擬機額外掛載一個磁盤,比如 GCE 的 Local SSD 類型的磁盤就是一個典型例子。

而在我們部署的私有環境中,你有兩種辦法來完成這個步驟。

  • 第一種,當然就是給你的宿主機掛載並格式化一個可用的本地磁盤,這也是最常規的操作;
  • 第二種,對於實驗環境,你其實可以在宿主機上掛載幾個 RAM Disk(內存盤)來模擬本地磁盤。

接下來,我會使用第二種方法,在我們之前部署的 Kubernetes 集群上進行實踐。首先,在名叫 node-1 的宿主機上創建一個掛載點,比如 /mnt/disks;然后,用幾個 RAM Disk 來模擬本地磁盤,如下所示:

# 在node-1上執行
$ mkdir /mnt/disks
$ for vol in vol1 vol2 vol3; do
    mkdir /mnt/disks/$vol
    mount -t tmpfs $vol /mnt/disks/$vol
done

需要注意的是,如果你希望其他節點也能支持 Local Persistent Volume 的話,那就需要為它們也執行上述操作,並且確保這些磁盤的名字(vol1、vol2 等)都不重復。接下來,我們就可以為這些本地磁盤定義對應的 PV 了,如下所示:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: mysql-min-pv-local
  namespace: default
spec:
  capacity:
    storage: 5Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  storageClassName: "mysql-min-storageclass-local"
  persistentVolumeReclaimPolicy: Retain
  #表示使用本地存儲
  local:
    path: /mnt/disks/vol1
  #使用local pv時必須定義nodeAffinity,Kubernetes Scheduler需要使用PV的nodeAffinity描述信息來保證Pod能夠調度到有對應local volume的Node上。
  #創建local PV之前,你需要先保證有對應的storageClass已經創建。
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - key: kubernetes.io/hostname
          operator: In
          values:
          # pod 需要分不到的主機名,這台主機上開啟了 local-pv 資源。
          - node-1

可以看到,這個 PV 的定義里:local 字段,指定了它是一個 Local Persistent Volume;而 path 字段,指定的正是這個 PV 對應的本地磁盤的路徑,即:/mnt/disks/vol1。

當然了,這也就意味着如果 Pod 要想使用這個 PV,那它就必須運行在 node-1 上。所以,在這個 PV 的定義里,需要有一個 nodeAffinity 字段指定 node-1 這個節點的名字。這樣,調度器在調度 Pod 的時候,就能夠知道一個 PV 與節點的對應關系,從而做出正確的選擇。這正是 Kubernetes 實現“在調度的時候就考慮 Volume 分布”的主要方法。

接下來要創建一個 StorageClass 來描述這個 PV,如下所示:

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: mysql-min-storageclass-local
#指定存儲類的供應者,比如aws, nfs等,具體取值參考官方說明。
#存儲類有一個供應者的參數域,此參數域決定PV使用什么存儲卷插件。參數必需進行設置
#由於demo中使用的是本地存儲,所以這里寫kubernetes.io/no-provisioner.
provisioner: kubernetes.io/no-provisioner
#volumeBindingMode 參數將延遲PVC綁定,直到 pod 被調度。
volumeBindingMode: WaitForFirstConsumer

這個 StorageClass 的名字,叫作 local-storage。需要注意的是,在它的 provisioner 字段,我們指定的是 no-provisioner。這是因為 Local Persistent Volume 目前尚不支持 Dynamic Provisioning,所以它沒辦法在用戶創建 PVC 的時候,就自動創建出對應的 PV。也就是說,我們前面創建 PV 的操作,是不可以省略的。

與此同時,這個 StorageClass 還定義了一個 volumeBindingMode=WaitForFirstConsumer 的屬性。它是 Local Persistent Volume 里一個非常重要的特性,即:延遲綁定。

通過這個延遲綁定機制,原本實時發生的 PVC 和 PV 的綁定過程,就被延遲到了 Pod 第一次調度的時候在調度器中進行,從而保證了這個綁定結果不會影響 Pod 的正常調度。

接下來,我們只需要定義一個非常普通的 PVC,就可以讓 Pod 使用到上面定義好的 Local Persistent Volume 了,如下所示:

apiVersion: v1
items:
- apiVersion: v1
  kind: PersistentVolumeClaim
  metadata:
    #當啟用PVC 保護 alpha 功能時,如果用戶刪除了一個 pod 正在使用的 PVC,則該 PVC 不會被立即刪除。PVC 的刪除將被推遲,直到 PVC 不再被任何 pod 使用。
    #可以看到,當 PVC 的狀態為 Teminatiing 時,PVC 受到保護,Finalizers 列表中包含 kubernetes.io/pvc-protection:
    finalizers:
    - kubernetes.io/pvc-protection
    labels:
      app: mysql-min
      release: mysql-min
    name: mysql-min
    namespace: default
  spec:
    #PV 的訪問模式(accessModes)有三種:
    #ReadWriteOnce(RWO):是最基本的方式,可讀可寫,但只支持被單個 Pod 掛載。
    #ReadOnlyMany(ROX):可以以只讀的方式被多個 Pod 掛載。
    #ReadWriteMany(RWX):這種存儲可以以讀寫的方式被多個 Pod 共享。
    accessModes:
    - ReadWriteOnce
    resources:
      requests:
        storage: 10Gi
    storageClassName: mysql-min-storageclass-local
    #表示使用本地磁盤,實際生產中一般都使用nfs。
    volumeMode: Filesystem
    volumeName: mysql-min-pv-local
#  status:
#    accessModes:
#    - ReadWriteOnce
#    capacity:
#      storage: 1Gi
kind: List

可以看到,這個 PVC 沒有任何特別的地方。唯一需要注意的是,它聲明的 storageClassName 是 mysql-min-storageclass-local。所以,將來 Kubernetes 的 Volume Controller 看到這個 PVC 的時候,不會為它進行綁定操作。

最后,我們創建 Local Persistent Volume 資源文件:

kubectl apply -f mysql-min-pv-local.yaml
kubectl apply -f mysql-min-storageclass-local.yaml
kubectl apply -f mysql-min-pvc.yaml

而后,調整 Deploy 並掛載卷:

   spec:
      containers:
      - image: centos/mysql-57-centos7:latest
...
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
      volumes:
      - name: data
        persistentVolumeClaim:
          claimName: mysql-min

自定義配置文件

通過創建 configmap 並掛載到容器中,我們可自定義 MySQL 配置文件。如下所示,名為 mysql-config 的 cm 包含一個 my.cnf 文件:

apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql-config
data:
  my.cnf: |
    [mysqld]
    default_storage_engine=innodb
    skip_external_locking
    lower_case_table_names=1
    skip_host_cache
    skip_name_resolve
    max_connections=2000
    innodb_buffer_pool_size=8589934592
    init_connect='SET collation_connection = utf8_unicode_ci'
    init_connect='SET NAMES utf8'
    character-set-server=utf8
    collation-server=utf8_unicode_ci
    skip-character-set-client-handshake
    query_cache_type=0
    innodb_flush_log_at_trx_commit = 0
    sync_binlog = 0
    query_cache_size = 104857600
    slow_query_log =1
    slow_query_log_file=/var/lib/mysql/slow-query.log
    log-error=/var/lib/mysql/mysql.err
    long_query_time = 0.02
    table_open_cache_instances=16
    table_open_cache = 6000
    skip-grant-tables
    sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION

將 configmap 掛載到容器內:

   spec:
...
      containers:
      - image: centos/mysql-57-centos7:latest
...
        volumeMounts:
        - name: mysql-config
          mountPath: /etc/my.cnf.d/my.cnf
          subPath: my.cnf
...
      volumes:
      - name: mysql-config
        - name: mysql-config
          configMap:
            name: mysql-config
...

設置容器時區

最傻瓜也最方便的處理方式,設置宿主機時區和時間文件與容器的映射。

    spec:
...
      containers:
      - image: centos/mysql-57-centos7:latest
...
        volumeMounts:
        - name: localtime
          readOnly: true
          mountPath: /etc/localtime
...
      volumes:
       - name: localtime
         hostPath:
           type: File
           path: /etc/localtime
...

加密敏感數據

用戶密碼等敏感數據以 Secret 加密保存,而后被 Deployment 通過 volume 掛載或環境變量引用。如本例,我們創建root、user用戶,將用戶的密碼加密保存:

apiVersion: v1
data:
  #將mysql數據庫的所有user的password配置到secret,統一管理
  mysql-password: YWRtaW4=
  mysql-root-password: OVplTmswRGdoSA==
kind: Secret
metadata:
  labels:
    app: mysql-min
    release: mysql-min
  name: mysql-min
  namespace: default
#Secret有三種類型:
#Opaque:base64編碼格式的Secret,用來存儲密碼、密鑰等;但數據也通過base64 –decode解碼得到原始數據,所有加密性很弱。
#kubernetes.io/dockerconfigjson:用來存儲私有docker registry的認證信息。
#kubernetes.io/service-account-token: 用於被serviceaccount引用。serviceaccout創建時Kubernetes會默認創建對應的secret。Pod如果使用了serviceaccount,對應的secret會自動掛載到Pod目錄/run/secrets/ kubernetes.io/serviceaccount中。
type: Opaque

Secret 創建完成后,我們將用戶明文密碼從 Deployment 去除,采用環境變量方式引用 Secret 數據,參見如下 Yaml 修改:

  • root 用戶及 MYSQL_USER 用戶,其密碼均通過 secretKeyRef 從 secret 獲取。
  spec:
...
      containers:
      - image: centos/mysql-57-centos7:latest
        name: mysql-min
        imagePullPolicy: IfNotPresent
        env:
           #password存儲在secret中
           - name: MYSQL_ROOT_PASSWORD
             valueFrom:
               secretKeyRef:
                 key: mysql-root-password
                 name: mysql-min
           - name: MYSQL_PASSWORD
             valueFrom:
               secretKeyRef:
                 key: mysql-password
                 name: mysql-min
           - name: MYSQL_USER
             value: zuozewei

容器健康檢查

K8S 鏡像控制器可通過 livenessProbe 判斷容器是否異常,進而決定是否重建容器;而 Service 服務可通過 readinessProbe 判斷容器服務是否正常,從而確保服務可用性。

​本例配置的 livenessProbe 與 readinessProbe 是一樣的,即連續 3 次查詢數據庫失敗,則定義為異常。對 livenessProbe 與readinessProbe 詳細用法,不在本文的討論范圍內,可參考 K8S 官方文檔:

  • Configure Liveness and Readiness Probes
  • Pod Lifecycle
  spec:
      containers:
          image: centos/mysql-57-centos7:latest
...
          #kubelet 使用 liveness probe(存活探針)來確定何時重啟容器。例如,當應用程序處於運行狀態但無法做進一步操作,liveness 探針將捕獲到 deadlock,重啟處於該狀態下的容器,使應用程序在存在 bug 的情況下依然能夠繼續運行下去
          livenessProbe:
            exec:
              command:
                - /bin/sh
                - "-c"
                - MYSQL_PWD="${MYSQL_ROOT_PASSWORD}"
                - mysql -h 127.0.0.1 -u root -e "SELECT 1"
            failureThreshold: 3 #探測成功后,最少連續探測失敗多少次才被認定為失敗。默認是 3。最小值是 1。
            initialDelaySeconds: 30 #容器啟動后第一次執行探測是需要等待多少秒。
            periodSeconds: 10 #執行探測的頻率。默認是10秒,最小1秒。
            successThreshold: 1 #探測失敗后,最少連續探測成功多少次才被認定為成功。默認是 1。對於 liveness 必須是 1。最小值是 1。
            timeoutSeconds: 5 #探測超時時間。默認1秒,最小1秒。
          #Kubelet 使用 readiness probe(就緒探針)來確定容器是否已經就緒可以接受流量。只有當 Pod 中的容器都處於就緒狀態時 kubelet 才會認定該 Pod處於就緒狀態。該信號的作用是控制哪些 Pod應該作為service的后端。如果 Pod 處於非就緒狀態,那么它們將會被從 service 的 load balancer中移除。
          readinessProbe:
            exec:
              command:
                - /bin/sh
                - "-c"
                - MYSQL_PWD="${MYSQL_ROOT_PASSWORD}"
                - mysql -h 127.0.0.1 -u root -e "SELECT 1"
            failureThreshold: 3
            initialDelaySeconds: 5
            periodSeconds: 10
            successThreshold: 1
            timeoutSeconds: 1

容器初始化

容器的一些初始化操作顯然適合通過 InitContainer 來完成,這里的 initContainer 是為了保證在 POD 啟動前,PV盤 要先行綁定成功,同時為了避免 MySQL 數據庫目錄內的 lost+found 目錄被誤認為是數據庫,初始化容器中將其刪除;

  #Init 容器支持應用容器的全部字段和特性,包括資源限制、數據卷和安全設置。 然而,Init 容器對資源請求和限制的處理稍有不同,在下面 資源 處有說明。 而且 Init 容器不支持 Readiness Probe,因為它們必須在 Pod 就緒之前運行完成。
      #如果為一個 Pod 指定了多個 Init 容器,那些容器會按順序一次運行一個。 每個 Init 容器必須運行成功,下一個才能夠運行。 當所有的 Init 容器運行完成時,Kubernetes 初始化 Pod 並像平常一樣運行應用容器。
      #mysql這里的initContainer是為了保證在POD啟動前,PV盤要先行綁定成功。
      initContainers:
        - command:
            - rm
            - -fr
            - /var/lib/mysql/lost+found
          image: busybox:1.29.3
          imagePullPolicy: IfNotPresent
          name: remove-lost-found
          resources: {}
          terminationMessagePath: /dev/termination-log
          terminationMessagePolicy: File
          volumeMounts:
            - mountPath: /var/lib/mysql
              name: data
      restartPolicy: Always
      #scheduler 是 kubernetes 的調度器,主要的任務是把定義的 pod 分配到集群的節點上。
      schedulerName: default-scheduler
      securityContext: {}
      #如果您的Pod通常需要超過30秒才能關閉,請確保增加優雅終止寬限期。可以通過在Pod YAML中設置terminationGracePeriodSeconds選項來實現.
      #如果容器在優雅終止寬限期后仍在運行,則會發送SIGKILL信號並強制刪除。與此同時,所有的Kubernetes對象也會被清除。
      terminationGracePeriodSeconds: 30
      #定義數據卷PVC,與PV匹配。
      volumes:
        - name: data
          persistentVolumeClaim:
            claimName: mysql-min
        - name: mysql-config
          configMap:
            name: mysql-config
        - name: localtime
          hostPath:
            type: File
            path: /etc/localtime

完整Deployment

通過如上多步調整,MySQL 數據庫的 Deplyment 如下所示:

apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    deployment.kubernetes.io/revision: "1"
  generation: 1
  labels:
    app: mysql-min
    release: mysql-min
  name: mysql-min
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mysql-min
  strategy:
    rollingUpdate:
      maxSurge: 1 #滾動升級時會先啟動1個pod
      maxUnavailable: 1 #滾動升級時允許的最大Unavailable的pod個數
    type: RollingUpdate #滾動升級
  template:
    metadata:
      labels:
        app: mysql-min
    spec:
      containers:
        - env:
            #password存儲在secret中
            - name: MYSQL_ROOT_PASSWORD
              valueFrom:
                secretKeyRef:
                  key: mysql-root-password
                  name: mysql-min
            - name: MYSQL_PASSWORD
              valueFrom:
                secretKeyRef:
                  key: mysql-password
                  name: mysql-min
            - name: MYSQL_USER
              value: apollo
          image: centos/mysql-57-centos7:latest
          imagePullPolicy: IfNotPresent
          #kubelet 使用 liveness probe(存活探針)來確定何時重啟容器。例如,當應用程序處於運行狀態但無法做進一步操作,liveness 探針將捕獲到 deadlock,重啟處於該狀態下的容器,使應用程序在存在 bug 的情況下依然能夠繼續運行下去
          livenessProbe:
            exec:
              command:
                - /bin/sh
                - "-c"
                - MYSQL_PWD="${MYSQL_ROOT_PASSWORD}"
                - mysql -h 127.0.0.1 -u root -e "SELECT 1"
            failureThreshold: 3 #探測成功后,最少連續探測失敗多少次才被認定為失敗。默認是 3。最小值是 1。
            initialDelaySeconds: 30 #容器啟動后第一次執行探測是需要等待多少秒。
            periodSeconds: 10 #執行探測的頻率。默認是10秒,最小1秒。
            successThreshold: 1 #探測失敗后,最少連續探測成功多少次才被認定為成功。默認是 1。對於 liveness 必須是 1。最小值是 1。
            timeoutSeconds: 5 #探測超時時間。默認1秒,最小1秒。
          name: mysql-min
          ports:
            - containerPort: 3306
              name: mysql
              protocol: TCP
          #Kubelet 使用 readiness probe(就緒探針)來確定容器是否已經就緒可以接受流量。只有當 Pod 中的容器都處於就緒狀態時 kubelet 才會認定該 Pod處於就緒狀態。該信號的作用是控制哪些 Pod應該作為service的后端。如果 Pod 處於非就緒狀態,那么它們將會被從 service 的 load balancer中移除。
          readinessProbe:
            exec:
              command:
                - /bin/sh
                - "-c"
                - MYSQL_PWD="${MYSQL_ROOT_PASSWORD}"
                - mysql -h 127.0.0.1 -u root -e "SELECT 1"
            failureThreshold: 3
            initialDelaySeconds: 5
            periodSeconds: 10
            successThreshold: 1
            timeoutSeconds: 1
          resources:
            requests:
              cpu: 100m
              memory: 256Mi
          #為了達到一個相當高水平的實用性,特別是為了積極開發應用,快速調試失敗是很重要的。除了一般的日志采集,Kubernetes還能通過查出重大錯誤原因來加速調試,並在某種程度上通過kubectl或者UI陳列出來。可以指定一個’terminationMessagePath’來讓容器寫下它的“death rattle“,比如聲明失敗消息,堆棧跟蹤,免責條款等等。默認途徑是‘/dev/termination-log’。
          terminationMessagePath: /dev/termination-log
          # 此字段默認為 “File“,這意味着僅從終止消息文件中檢索終止消息。 通過將 terminationMessagePolicy 設置為 “FallbackToLogsOnError“,你就可以告訴 Kubernetes,在容器因錯誤退出時,如果終止消息文件為空,則使用容器日志輸出的最后一塊作為終止消息。 日志輸出限制為 2048 字節或 80 行,以較小者為准。
          terminationMessagePolicy: File
          #要使用的數據盤目錄,在initContainer中會關聯此處目錄。
          volumeMounts:
            - mountPath: /var/lib/mysql
              name: data
            - name: mysql-config
              mountPath: /etc/my.cnf.d/my.cnf
              subPath: my.cnf
            - name: localtime
              readOnly: true
              mountPath: /etc/localtime
      dnsPolicy: ClusterFirst
      #Init 容器支持應用容器的全部字段和特性,包括資源限制、數據卷和安全設置。 然而,Init 容器對資源請求和限制的處理稍有不同,在下面 資源 處有說明。 而且 Init 容器不支持 Readiness Probe,因為它們必須在 Pod 就緒之前運行完成。
      #如果為一個 Pod 指定了多個 Init 容器,那些容器會按順序一次運行一個。 每個 Init 容器必須運行成功,下一個才能夠運行。 當所有的 Init 容器運行完成時,Kubernetes 初始化 Pod 並像平常一樣運行應用容器。
      #mysql這里的initContainer是為了保證在POD啟動前,PV盤要先行綁定成功。
      initContainers:
        - command:
            - rm
            - -fr
            - /var/lib/mysql/lost+found
          image: busybox:1.29.3
          imagePullPolicy: IfNotPresent
          name: remove-lost-found
          resources: {}
          terminationMessagePath: /dev/termination-log
          terminationMessagePolicy: File
          volumeMounts:
            - mountPath: /var/lib/mysql
              name: data
      restartPolicy: Always
      #scheduler 是 kubernetes 的調度器,主要的任務是把定義的 pod 分配到集群的節點上。
      schedulerName: default-scheduler
      securityContext: {}
      #如果您的Pod通常需要超過30秒才能關閉,請確保增加優雅終止寬限期。可以通過在Pod YAML中設置terminationGracePeriodSeconds選項來實現.
      #如果容器在優雅終止寬限期后仍在運行,則會發送SIGKILL信號並強制刪除。與此同時,所有的Kubernetes對象也會被清除。
      terminationGracePeriodSeconds: 30
      #定義數據卷PVC,與PV匹配。
      volumes:
        - name: data
          persistentVolumeClaim:
            claimName: mysql-min
        - name: mysql-config
          configMap:
            name: mysql-config
        - name: localtime
          hostPath:
            type: File
            path: /etc/localtime

創建此 Deployment 后,我們有如下組件:

# kubectl get all,pvc,cm,secret -l app=mysql-min
# MySQL pod:
NAME                           READY   STATUS    RESTARTS   AGE
pod/mysql-min-f9c9b7b5-q9br4   1/1     Running   6          14d

# Service:
NAME                TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
service/mysql-min   NodePort   10.96.184.130   <none>        3306:30336/TCP   16d

# MySQL Deployment:
NAME                        READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/mysql-min   1/1     1            1           16d

# 副本集ReplicaSet被Deployment調用,其是自動生成的
NAME                                   DESIRED   CURRENT   READY   AGE
replicaset.apps/mysql-min-587cf9fd48   0         0         0       16d
replicaset.apps/mysql-min-589bf8cdc5   0         0         0       16d
replicaset.apps/mysql-min-6b7447c7dd   0         0         0       14d
replicaset.apps/mysql-min-6cc9887459   0         0         0       16d
replicaset.apps/mysql-min-7759579d77   0         0         0       16d
replicaset.apps/mysql-min-84d4d6bd56   0         0         0       15d
replicaset.apps/mysql-min-f9c9b7b5     1         1         1       14d

# Pvc:
NAME                              STATUS   VOLUME               CAPACITY   ACCESS MODES   STORAGECLASS                   AGE
persistentvolumeclaim/mysql-min   Bound    mysql-min-pv-local   5Gi       RWO            mysql-min-storageclass-local   16d

# Secret:
NAME               TYPE     DATA   AGE
secret/mysql-min   Opaque   2      16d

定期自動備份

考慮到數據安全性,我們定期備份數據庫,在K8S集群中,我們可配置 CronJob 實現自動備份作業。首先,創建一個持久化存儲供備份用:

apiVersion: v1
items:
- apiVersion: v1
  kind: PersistentVolumeClaim
  metadata:
    #當啟用PVC 保護 alpha 功能時,如果用戶刪除了一個 pod 正在使用的 PVC,則該 PVC 不會被立即刪除。PVC 的刪除將被推遲,直到 PVC 不再被任何 pod 使用。
    #可以看到,當 PVC 的狀態為 Teminatiing 時,PVC 受到保護,Finalizers 列表中包含 kubernetes.io/pvc-protection:
    finalizers:
    - kubernetes.io/pvc-protection
    labels:
      app: mysql-min
      release: mysql-min
    name: mysql-min-backup
    namespace: default
  spec:
    #PV 的訪問模式(accessModes)有三種:
    #ReadWriteOnce(RWO):是最基本的方式,可讀可寫,但只支持被單個 Pod 掛載。
    #ReadOnlyMany(ROX):可以以只讀的方式被多個 Pod 掛載。
    #ReadWriteMany(RWX):這種存儲可以以讀寫的方式被多個 Pod 共享。
    accessModes:
    - ReadWriteOnce
    resources:
      requests:
        storage: 10Gi
    storageClassName: mysql-min-storageclass-nfs
    #表示使用本地磁盤,實際生產中一般都使用nfs。
    volumeMode: Filesystem
    volumeName: mysql-min-pv-local
#  status:
#    accessModes:
#    - ReadWriteOnce
#    capacity:
#      storage: 1Gi
kind: List

繼而,配置實際的自動化作業任務,如下所示,每天凌晨零點點將使用 mysqldump 備份 mall 數據庫。

apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: mysql-backup
spec:
  schedule: "0 0 * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
            - name: mysql-min-backup
              imagePullPolicy: IfNotPresent
              image: centos/mysql-57-centos7:latest
              env:
                #password存儲在secret中
                - name: MYSQL_ROOT_PASSWORD
                  valueFrom:
                    secretKeyRef:
                      key: mysql-root-password
                      name: mysql-min
                - name: MYSQL_PASSWORD
                  valueFrom:
                    secretKeyRef:
                      key: mysql-password
                      name: mysql-min
                - name: MYSQL_HOST
                  value: mysql-min
              command:
                - /bin/sh
                - -c
                - |
                  set -ex
                  mysqldump --host=$MYSQL_HOST --user=$MYSQL_ROOT_PASSWORD \
                            --password=$mysql-root-password \
                            --routines --databases mall --single-transaction \
                            > /mysql-backup/mysql-`date +"%Y%m%d"`.sql
              volumeMounts:
                - name: mysql-min-backup
                  mountPath: /mysql-min-backup
          restartPolicy: OnFailure
          volumes:
            - name: mysql-min-backup
              persistentVolumeClaim:
                claimName: mysql-min-backup

小結

​Kubernetes 很多看起來比較“繁瑣”的設計的主要目的,都是希望為開發者提供更多的“可擴展性”,給使用者帶來更多的“穩定性”和“安全感”。這兩個能力的高低,是衡量開源基礎設施項目水平的重要標准。 示例中揉合 Kubernetes 多項技術,構建了一個復雜且可做生產使用的單實例數據庫。

本文源碼:https://github.com/zuozewei/blog-example/tree/master/Kubernetes/k8s-mysql-pv-local

參考資料:

[1]:《深入剖析Kubernetes》

 

點擊關注,第一時間了解華為雲新鮮技術~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM