评论

收藏

[Linux] 详解MeterSphere 配置外部Mysql5.7的全过程

服务系统 服务系统 发布于:2022-09-28 11:15 | 阅读数:409 | 评论:0

# 详解MeterSphere 配置外部Mysql5.7的全过程
我从cnaaa.com购买了云服务器。
## 搭建一个主从复制(Master-Slave)的MySQL集群
> 
>
> ![img](https:////upload-images.jianshu.io/upload_images/27470379-e5d16b88ff6795eb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1080/format/webp)
## 本地存储原理
> 这里使用了本地存储,也就是说,用户希望Kubernetes能够直接使用宿主机上的本地磁盘目录,而不依赖于远程存储服务,来提供持久化的容器Volume
>  1、我们把存储固定在一个节点上,但是Pod在调度的时候,是飘来飘去的,怎么能让Pod通过PVC也能固定在PVC上?
>  2、给这个Pod加上一个nodeAffinity行不行?
> 当然行,但是这变相破坏了开发人员对资源对象的定义规范了,开发人员应该不需要时刻考虑调度的细节。调度的改动应该交给运维就行。所以我们为了实现本地存储,我们采用了**延迟绑定**的方法。方法很简单,我们都知道StorageClass一般由运维人员设计,我们只需要在StorageClass指定no-provisioner。这是因为Local Persistent Volume目前尚不支持Dynamic Provisioning,所以它没办法在用户创建PVC的时候,就自动创建出对应的PV。与此同时,这个StorageClass还定义了一个volumeBindingMode=WaitForFirstConsumer的属性。它是Local Persistent Volume里一个非常重要的特性,即:延迟绑定。
## 创建PV
> 先在Node (实验用的Node节点IP是node1)节点上,预先分配几个PV(不建议在生产上这样操作),要保证在node1上有对应的目录

```css
ssh root@node1
mkdir -pv /data/svr/projects/{mysql,mysql2,mysql3}
```
###### vim 01-persistentVolume-1.yaml

```bash
apiVersion: v1
kind: PersistentVolume
metadata:
  name: example-mysql-pv
spec:
  capacity:
  storage: 15Gi
  volumeMode: Filesystem
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Delete
  storageClassName: local-storage
  local:
  path: /data/svr/projects/mysql
  nodeAffinity:
  required:
    nodeSelectorTerms:
    - matchExpressions:
    - key: kubernetes.io/hostname
      operator: In
      values:
      - node1
```
###### vim 01-persistentVolume-2.yaml

```bash
apiVersion: v1
kind: PersistentVolume
metadata:
  name: example-mysql-pv-2
spec:
  capacity:
  storage: 15Gi
  volumeMode: Filesystem
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Delete
  storageClassName: local-storage
  local:
  path: /data/svr/projects/mysql2
  nodeAffinity:
  required:
    nodeSelectorTerms:
    - matchExpressions:
    - key: kubernetes.io/hostname
      operator: In
      values:
      - node1
```
###### vim 01-persistentVolume-3.yaml

```bash
apiVersion: v1
kind: PersistentVolume
metadata:
  name: example-mysql-pv-3
spec:
  capacity:
  storage: 15Gi
  volumeMode: Filesystem
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Delete
  storageClassName: local-storage
  local:
  path: /data/svr/projects/mysql3
  nodeAffinity:
  required:
    nodeSelectorTerms:
    - matchExpressions:
    - key: kubernetes.io/hostname
      operator: In
      values:
      - node1
```
> 记住,这是在生产上不推荐的做法,我只是实验用途才这样手动预先创建,**正规的做法应该通过StorageClass采用Dynamic Provisioning, 而不是Static Provisioning机制生产PV

```csharp
[root@master mysql]# kubectl apply -f 01-persistentVolume-1.yaml 
persistentvolume/example-mysql-pv created
[root@master mysql]# kubectl apply -f 01-persistentVolume-2.yaml 
persistentvolume/example-mysql-pv-2 created
[root@master mysql]# kubectl apply -f 01-persistentVolume-3.yaml 
persistentvolume/example-mysql-pv-3 created
[root@master mysql]# kubectl get pv
NAME         CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS    CLAIM   STORAGECLASS  REASON   AGE
example-mysql-pv   15Gi     RWO      Delete       Available       local-storage      8s
example-mysql-pv-2   15Gi     RWO      Delete       Available       local-storage      6s
example-mysql-pv-3   15Gi     RWO      Delete       Available       local-storage      3s
```
###### 02-storageclass.yaml

```bash
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
```

```bash
[root@master mysql]# kubectl apply -f 02-storageclass.yaml 
storageclass.storage.k8s.io/local-storage created
[root@master mysql]# kubectl get sc
NAME      PROVISIONER          RECLAIMPOLICY   VOLUMEBINDINGMODE    ALLOWVOLUMEEXPANSION   AGE
local-storage   kubernetes.io/no-provisioner   Delete      WaitForFirstConsumer   false          5s
```
## 创建Namespace
###### 03-mysql-namespace.yaml

```php
apiVersion: v1
kind: Namespace
metadata:
  name: mysql
  labels:
  app: mysql
```

```tsx
[root@master mysql]# kubectl apply -f 03-mysql-namespace.yaml 
namespace/mysql unchanged
[root@master mysql]# kubectl get ns -n mysql
NAME        STATUS   AGE
mysql       Active   3d
```
## 创建数据库的配置文件configmap
###### 使用ConfigMap为Master/Slave节点分配不同的配置文件
###### vim 04-mysql-configmap.yaml

```bash
apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql
  namespace: mysql
  labels:
  app: mysql
data:
  master.cnf: |
  # Master配置
  [mysqld]
  log-bin=mysqllog
  skip-name-resolve
  slave.cnf: |
  # Slave配置
  [mysqld]
  super-read-only
  skip-name-resolve
  log-bin=mysql-bin
  replicate-ignore-db=mysql
```

```csharp
[root@master mysql]# kubectl apply -f 04-mysql-configmap.yaml 
configmap/mysql created
[root@master mysql]# kubectl get cm -n mysql
NAME         DATA   AGE
mysql        2    5s
```
## 创建MySQL密码Secret
###### 05-mysql-secret.yaml

```cpp
apiVersion: v1
kind: Secret
metadata:
  name: mysql-secret
  namespace: mysql
  labels:
  app: mysql
type: Opaque
data:
  password: MTIzNDU2 # echo -n "123456" | base64
```

```csharp
[root@master mysql]# kubectl apply -f 05-mysql-secret.yaml 
secret/mysql-secret created
[root@master mysql]# kubectl get secret -n mysql
NAME          TYPE                  DATA   AGE
mysql-secret      Opaque                1    18s
```
## 使用Service为MySQL提供读写分离
###### 06-mysql-services.yaml

```cpp
apiVersion: v1
kind: Service
metadata:
  name: mysql
  namespace: mysql
  labels:
  app: mysql
spec:
  ports:
  - name: mysql
  port: 3306
  clusterIP: None
  selector:
  app: mysql
---
apiVersion: v1
kind: Service
metadata:
  name: mysql-read
  namespace: mysql
  labels:
  app: mysql
spec:
  ports:
  - name: mysql
  port: 3306
  selector:
  app: mysql
```
> 用户所有写请求,必须以DNS记录的方式直接访问到Master节点,也就是mysql-0.mysql这条DNS记录。
> 用户所有读请求,必须访问自动分配的DNS记录可以被转发到任意一个Master或Slave节点上,也就是mysql-read这条DNS记录。

```csharp
[root@master mysql]# kubectl apply -f 06-mysql-services.yaml 
service/mysql created
service/mysql-read created
[root@master mysql]# kubectl get svc -n mysql
NAME     TYPE    CLUSTER-IP   EXTERNAL-IP   PORT(S)  AGE
mysql    ClusterIP   None           3306/TCP   3s
mysql-read   ClusterIP   10.107.90.19       3306/TCP   3s
```
## 创建MySQL集群实例
###### 使用StatefulSet搭建MySQL主从集群
###### 07-mysql-statefulset.yaml

```bash
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
  namespace: mysql
  labels:
  app: mysql
spec:
  selector:
  matchLabels:
    app: mysql
  serviceName: mysql
  replicas: 2
  template:
  metadata:
    labels:
    app: mysql
  spec:
    initContainers:
    - name: init-mysql
    image: mysql:5.7
    env:
    - name: MYSQL_ROOT_PASSWORD
      valueFrom:
      secretKeyRef:
        name: mysql-secret
        key: password
    command:
    - bash
    - "-c"
    - |
      set -ex
      # 从Pod的序号,生成server-id
      [[ $(hostname) =~ -([0-9]+)$ ]] || exit 1
      ordinal=${BASH_REMATCH[1]}
      echo [mysqld] > /mnt/conf.d/server-id.cnf
      # 由于server-id不能为0,因此给ID加100来避开它
      echo server-id=$((100 + $ordinal)) >> /mnt/conf.d/server-id.cnf
      # 如果Pod的序号为0,说明它是Master节点,从ConfigMap里把Master的配置文件拷贝到/mnt/conf.d目录下
      # 否则,拷贝ConfigMap里的Slave的配置文件
      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
    env:
    - name: MYSQL_ROOT_PASSWORD
      valueFrom:
      secretKeyRef:
        name: mysql-secret
        key: password
    command:
    - bash
    - "-c"
    - |
      set -ex
      # 拷贝操作只需要在第一次启动时进行,所以数据已经存在则跳过
      [[ -d /var/lib/mysql/mysql ]] && exit 0
      # Master 节点(序号为 0)不需要这个操作
      [[ $(hostname) =~ -([0-9]+)$ ]] || exit 1
      ordinal=${BASH_REMATCH[1]}
      [[ $ordinal == 0 ]] && exit 0
      # 使用ncat指令,远程地从前一个节点拷贝数据到本地
      ncat --recv-only mysql-$(($ordinal-1)).mysql 3307 | xbstream -x -C /var/lib/mysql
      # 执行 --prepare,这样拷贝来的数据就可以用作恢复了
      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"
    - name: MYSQL_ROOT_PASSWORD
      valueFrom:
      secretKeyRef:
        name: mysql-secret
        key: password
    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", "-uroot", "-p${MYSQL_ROOT_PASSWORD}"]
      initialDelaySeconds: 30
      periodSeconds: 10
      timeoutSeconds: 5
    readinessProbe:
      exec:
      command: ["mysqladmin", "ping", "-uroot", "-p${MYSQL_ROOT_PASSWORD}"]
      initialDelaySeconds: 5
      periodSeconds: 2
      timeoutSeconds: 1
    - name: xtrabackup
    image: gcr.io/google-samples/xtrabackup:1.0
    ports:
    - name: xtrabackup
      containerPort: 3307
    env:
    - name: MYSQL_ROOT_PASSWORD
      valueFrom:
      secretKeyRef:
        name: mysql-secret
        key: password
    command:
    - bash
    - "-c"
    - |
      set -ex
      cd /var/lib/mysql
      # 从备份信息文件里读取MASTER_LOG_FILE和MASTER_LOG_POS这2个字段的值,用来拼装集群初始化SQL
      if [[ -f xtrabackup_slave_info ]]; then
      # 如果xtrabackup_slave_info文件存在,说明这个备份数据来自于另一个Slave节点
      # 这种情况下,XtraBackup工具在备份的时候,就已经在这个文件里自动生成了“CHANGE MASTER TO”SQL语句
      # 所以,只需要把这个文件重命名为change_master_to.sql.in,后面直接使用即可
      mv xtrabackup_slave_info change_master_to.sql.in
      # 所以,也就用不着xtrabackup_binlog_info了
      rm -f xtrabackup_binlog_info
      elif [[ -f xtrabackup_binlog_info ]]; then
      # 如果只是存在xtrabackup_binlog_info文件,说明备份来自于Master节点,就需要解析这个备份信息文件,读取所需的两个字段的值
      [[ $(cat xtrabackup_binlog_info) =~ ^(.*?)[[:space:]]+(.*?)$ ]] || exit 1
      rm xtrabackup_binlog_info
      # 把两个字段的值拼装成SQL,写入change_master_to.sql.in文件
      echo "CHANGE MASTER TO MASTER_LOG_FILE='${BASH_REMATCH[1]}',\
          MASTER_LOG_POS=${BASH_REMATCH[2]}" > change_master_to.sql.in
      fi
      # 如果存在change_master_to.sql.in,就意味着需要做集群初始化工作
      if [[ -f change_master_to.sql.in ]]; then
      # 但一定要先等MySQL容器启动之后才能进行下一步连接MySQL的操作
      echo "Waiting for mysqld to be ready(accepting connections)"
      until mysql -h 127.0.0.1 -uroot -p${MYSQL_ROOT_PASSWORD} -e "SELECT 1"; do sleep 1; done
      echo "Initializing replication from clone position"
      # 将文件change_master_to.sql.in改个名字
      # 防止这个Container重启的时候,因为又找到了change_master_to.sql.in,从而重复执行一遍初始化流程
      mv change_master_to.sql.in change_master_to.sql.orig
      # 使用change_master_to.sql.orig的内容,也就是前面拼装的SQL,组成一个完整的初始化和启动Slave的SQL语句
      mysql -h 127.0.0.1 -uroot -p${MYSQL_ROOT_PASSWORD}  整体的StatefulSet有两个Replicas,一个Master,一个Slave,然后使用init-mysql这个initContainers进行配置文件的初始化。接着使用clone-mysql这个initContainers进行数据的传输;同时使用xtrabackup这个**sidecar容器**进行SQL初始化和数据传输功能。
>  创建 StatefulSet

```css
kubectl apply -f 07-mysql-statefulset.yaml
```
> 可以看到,StatefulSet启动成功后,会有两个Pod运行。接下来,我们可以尝试向这个MySQL集群 发起请求,执行一些SQL操作来验证它是否正常。整个过程因为拉取mysql和一个gcr.io/google-samples/xtrabackup:1.0国外的镜像会很慢,但是在创建mysql-0拉取一次之后,后续创建m>ysql-1就相对很快了。
>  最后,容器检查pod的运行状态

```csharp
[root@master mysql]# kubectl get po -n mysql
NAME    READY   STATUS  RESTARTS   AGE
mysql-0   2/2   Running   0      2m7s
mysql-1   2/2   Running   0      116s
```
## 服务验证
###### 验证主从状态

```dart
[root@master mysql]# kubectl -n mysql exec mysql-1 -c mysql -- bash -c "mysql -uroot -p123456 -e 'show slave status \G'"
*************************** 1. row ***************************
         Slave_IO_State: Waiting for master to send event
          Master_Host: mysql-0.mysql.mysql
          Master_User: root
          Master_Port: 3306
        Connect_Retry: 10
        Master_Log_File: mysqllog.000003
      Read_Master_Log_Pos: 154
         Relay_Log_File: mysql-1-relay-bin.000002
        Relay_Log_Pos: 319
    Relay_Master_Log_File: mysqllog.000003
       Slave_IO_Running: Yes
      Slave_SQL_Running: Yes
        Replicate_Do_DB: 
      Replicate_Ignore_DB: mysql0
       Replicate_Do_Table: 
     Replicate_Ignore_Table: 
    Replicate_Wild_Do_Table: 
  Replicate_Wild_Ignore_Table: 
           Last_Errno: 0
           Last_Error: 
         Skip_Counter: 0
      Exec_Master_Log_Pos: 154
        Relay_Log_Space: 528
        Until_Condition: None
         Until_Log_File: 
        Until_Log_Pos: 0
       Master_SSL_Allowed: No
       Master_SSL_CA_File: 
       Master_SSL_CA_Path: 
        Master_SSL_Cert: 
      Master_SSL_Cipher: 
         Master_SSL_Key: 
    Seconds_Behind_Master: 0
Master_SSL_Verify_Server_Cert: No
        Last_IO_Errno: 0
        Last_IO_Error: 
         Last_SQL_Errno: 0
         Last_SQL_Error: 
  Replicate_Ignore_Server_Ids: 
       Master_Server_Id: 100
          Master_UUID: f8c7768a-6702-11ec-a648-c204e42fb62e
       Master_Info_File: /var/lib/mysql/master.info
          SQL_Delay: 0
      SQL_Remaining_Delay: NULL
    Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates
       Master_Retry_Count: 86400
          Master_Bind: 
    Last_IO_Error_Timestamp: 
   Last_SQL_Error_Timestamp: 
         Master_SSL_Crl: 
       Master_SSL_Crlpath: 
       Retrieved_Gtid_Set: 
      Executed_Gtid_Set: 
        Auto_Position: 0
     Replicate_Rewrite_DB: 
         Channel_Name: 
       Master_TLS_Version: 
mysql: [Warning] Using a password on the command line interface can be insecure.
```
###### 接下来,我们通过Master容器创建数据库和表、插入数据库

```swift
[root@master mysql]# kubectl -n mysql exec mysql-0 -c mysql -- bash -c "mysql -uroot -p123456 -e 'create database test'"
[root@master mysql]# kubectl -n mysql exec mysql-0 -c mysql -- bash -c "mysql -uroot -p123456 -e 'use test;create table counter(c int);'"
[root@master mysql]# kubectl -n mysql exec mysql-0 -c mysql -- bash -c "mysql -uroot -p123456 -e 'use test;insert into counter values(123)'"
[root@master mysql]# kubectl -n mysql exec mysql-1 -c mysql -- bash -c "mysql -uroot -p123456 -e 'use test;select * from counter'"  
c
123
```
###### 当看到输出结果,主从同步正常了
###### 扩展节点

```csharp
[root@master mysql]# kubectl -n mysql scale statefulset mysql -—replicas=3
[root@master mysql]# kubectl get po -n mysql
NAME    READY   STATUS  RESTARTS   AGE
mysql-0   2/2   Running   0      11m
mysql-1   2/2   Running   0      11m
mysql-2   2/2   Running   0      20s
```
###### 查看扩容节点数据是否已同步

```swift
kubectl -n mysql exec mysql-2 -c mysql -- bash -c "mysql -uroot -p123456 -e 'use test;select * from counter’"  
c
123
```