本文共 6419 字,大约阅读时间需要 21 分钟。
在 Kubernetes 环境中,管理有状态服务(StatefulSet)是一个复杂但关键的任务。与无状态服务(ReplicaSet)不同,StatefulSet 有状态的 pods 需要保持特定的网络标识符和唯一的网络接口。通过本地存储解决方案,我们可以避免服务漂移导致的数据丢失问题。本文通过一个 MySQL 主从集群的案例,探讨如何利用 Kubernetes 的 StatefulSet 实现状态管理。
为了快速搭建测试环境,我们采用本地存储策略,即将存储固定在特定节点上。这种方法的关键在于容器级别的持久化存储(PersistentVolume),ensure each pod can mount a stable volume. 但是,由于本地存储不支持动态供给(Dynamic Provisioning),因此我们需要手动预先创建 PersistentVolume(PVs)。
为了实现动态配置,我们使用了以下方法:
kind: StorageClassapiVersion: storage.k8s.io/v1metadata: name: local-storage provider: kubernetes.io/no-provisionervolumeBindingMode: WaitForFirstConsumer
通过这种方式,我们确保 PersistentVolume 在第一次消费时创建。
apiVersion: v1kind: PersistentVolumemetadata: name: example-mysql-pvspec: capacity: 15Gi volumeMode: Filesystem accessModes: [ReadWriteOnce] persistentVolumeReclaimPolicy: Delete storageClassName: local-storage local: path: /data/svr/projects/mysql nodeAffinity: required: hostname: 172.31.170.51
手动创建 3 个 PersistentVolumes 为实验准备,并确保它们绑定在特定节点。
kind: StorageClassapiVersion: storage.k8s.io/v1metadata: name: local-storage provider: kubernetes.io/no-provisionervolumeBindingMode: WaitForFirstConsumer
确保 StorageClass 已创建,并支持延迟绑定。
apiVersion: v1kind: Namespacemetadata: name: mysql labels: app: mysql
为 MySQL 集群创建一个 isolated 的 Namespace。
apiVersion: v1kind: ConfigMapmetadata: name: mysql namespace: mysql labels: app: mysqldata: master.cnf: | [mysqld] log-bin=mysqllog skip-name-resolve slave.cnf: | [mysqld] super-read-only skip-name-resolve log-bin=mysql-bin replicate-ignore-db=mysql
为主节点和从节点分别配置不同的 MySQL 配置文件。
apiVersion: v1kind: Secretmetadata: name: mysql-secret namespace: mysql labels: app: mysqltype: Opaquedata: password: MTIzNDU2
创建一个 Secret 提供 MySQL 密码。
以下是 StatefulSet 的配置文件:
apiVersion: apps/v1kind: StatefulSetmetadata: name: mysql namespace: mysql labels: app: mysqlspec: 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: mysql-secret, password command: [bash] -c: | set -ex [[ $(hostname) =~ -([-9]+) ]] || exit 1 ordinal=${BASH_REMATCH[1]} echo "[mysqld]" > /mnt/conf.d/server-id.cnf echo "server-id=$((100 + $ordinal))" >> /mnt/conf.d/server-id.cnf if [ $(hostname) == "mysql-0.mysql" ]; 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: mysql-secret, password volumeMounts: - name: data mountPath: /var/lib/mysql subPath: mysql - name: conf mountPath: /etc/mysql/conf.d - name: config-map configMap: name: mysql volumeClaimTemplate: metadata: name: data spec: accessModes: [ReadWriteOnce] storageClassName: local-storage resources: storage: 3Gi - name: xtrabackup image: gcr.io/google-samples/xtrabackup:1.0 ports: - containerPort: 3307 env: - name: MYSQL_ROOT_PASSWORD valueFrom: secretKeyRef: mysql-secret, password command: [bash] -c: | set -ex cd /var/lib/mysql if [ -f xtrabackup_slave_info ]; then mv xtrabackup_slave_info change_master_to.sql.in echo "CHANGE MASTER TO" > change_master_to.sql.in mysql -h 127.0.0.1 -uroot -p$(MYSQL_ROOT_PASSWORD) < change_master_to.sql.in mv change_master_to.sql.in change_master_to.sql.orig mysql -h 127.0.0.1 -uroot -p$(MYSQL_ROOT_PASSWORD) < change_master_to.sql.orig > /dev/null elif [ -f xtrabackup_binlog_info ]; then mysql -h 127.0.0.1 -uroot -p$(MYSQL_ROOT_PASSWORD) < xtrabackup_binlog_info rm -f xtrabackup_binlog_info echo "CHANGE MASTER TO ..." > change_master_to.sql.in mysql -h 127.0.0.1 -uroot -p$(MYSQL_ROOT_PASSWORD) < change_master_to.sql.in mv change_master_to.sql.in change_master_to.sql.orig mysql -h 127.0.0.1 -uroot -p$(MYSQL_ROOT_PASSWORD) < change_master_to.sql.orig > /dev/null fi if [ -f change_master_to.sql.in ]; then echo "Waiting for mysqld to be ready..." 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..." mv change_master_to.sql.in change_master_to.sql.orig mysql -h 127.0.0.1 -uroot -p$(MYSQL_ROOT_PASSWORD) < change_master_to.sql.orig > /dev/null fi ncat --listen --keep-open --send-only --max-conns=1 3307 -c "xtrabackup --backup --slave-info --stream=xbstream --host=127.0.0.1 --user=root --password=$(MYSQL_ROOT_PASSWORD)"
kubectl apply -f 07-mysql-statefulset.yaml
创建 StatefulSet 后,可以通过以下命令验证其状态:
kubectl get po -n mysql
随后,可以通过以下命令执行 SQL 操作:
kubectl -n mysql exec mysql-0 sh -c "mysql -uroot -p123456 -e 'create database test; use test; create table counter (c int); insert into counter values(123)'"
kubectl -n mysql exec mysql-1 sh -c "mysql -uroot -p123456 -e 'use test; select * from counter'"
此时,您应该看到主节点和从节点同步正常的状态。
StatefulSet 的一个重要优势是支持动态扩展。比如,如果您需要扩展节点数量,可以执行以下命令:
kubectl scale statefulset mysql --replicas=3
扩展后,可以通过 kubectl get po -n mysql
检查新节点的状态,并验证其数据同步情况。通过严格控制主节点的写操作和从节点的读操作,确保主从集群的高可用性和数据一致性。
通过以上步骤,我们成功利用 Kubernetes 的 StatefulSet 和本地存储策略,搭建了一个 MySQL 主从集群,实现了状态管理的关键功能。这一解决方案突破了传统 Kubernetes 集群存储的局限,为高可用性和状态管理提供了强有力的支持。
转载地址:http://nlryk.baihongyu.com/