摘要: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》

点击关注,第一时间了解华为云新鲜技术~

想提高运维效率,那就把MySQL数据库部署到Kubernetes 集群中的更多相关文章

  1. 提高运维效率(二)桌面显示IP

    运维人员远控电脑询问IP时,总要告诉用户找ip的步骤,岂不很烦? 以下方法直观地把ip地址显示在桌面上,再做个入职培训,即可提高运维效率. 1.  下载bginfo.exe软件,放到域控下的netlo ...

  2. [转载] 运维角度浅谈:MySQL数据库优化

    一个成熟的数据库架构并不是一开始设计就具备高可用.高伸缩等特性的,它是随着用户量的增加,基础架构才逐渐完善. 作者:zhenliang8,本文转自51CTO博客,http://lizhenliang. ...

  3. 运维角度浅谈:MySQL数据库优化

    日志君导读: 一个成熟的数据库架构并非一開始设计就具备高可用.高伸缩等特性的.它是随着用户量的添加,基础架构才逐渐完好. 作者:zhenliang8.本文转自51CTO博客,点击原文阅读查看网页版文章 ...

  4. 阿里巴巴大规模神龙裸金属 Kubernetes 集群运维实践

    作者 | 姚捷(喽哥)阿里云容器平台集群管理高级技术专家 本文节选自<不一样的 双11 技术:阿里巴巴经济体云原生实践>一书,点击即可完成下载. 导读:值得阿里巴巴技术人骄傲的是 2019 ...

  5. 企业运维实践-丢弃手中的 docker build , 使用Kaniko直接在Kubernetes集群或Containerd环境中快速进行构建推送容器镜像

    关注「WeiyiGeek」公众号 设为「特别关注」每天带你玩转网络安全运维.应用开发.物联网IOT学习! 希望各位看友[关注.点赞.评论.收藏.投币],助力每一个梦想. 本章目录 目录 首发地址: h ...

  6. 提高Linux运维效率的命令行常用快捷键

    提高Linux运维效率的命令行常用快捷键 tab 命令或路径等的补全键,Linux最有用快捷键 Ctrl+a 光标回到命令行首 Ctrl+e 光标回到命令行尾 Ctrl+k 剪切(删除)光标处到行尾的 ...

  7. 提高Linux运维效率的30个命令行常用快捷键

    提高Linux运维效率的30个命令行常用快捷键 表4-1  30个常用快捷键 快捷键 功能说明 最有用快捷键 tab 命令或路径等的补全键,Linux最有用快捷键* 移动光标快捷键 Ctrl+a 光标 ...

  8. linux运维、架构之路-Kubernetes集群部署

    一.kubernetes介绍        Kubernetes简称K8s,它是一个全新的基于容器技术的分布式架构领先方案.Kubernetes(k8s)是Google开源的容器集群管理系统(谷歌内部 ...

  9. vivo大规模 Kubernetes 集群自动化运维实践

    作者:vivo 互联网服务器团队-Zhang Rong 一.背景 随着vivo业务迁移到K8s的增长,我们需要将K8s部署到多个数据中心.如何高效.可靠的在数据中心管理多个大规模的K8s集群是我们面临 ...

  10. 企业运维实践-还不会部署高可用的kubernetes集群?使用kubeadm方式安装高可用k8s集群v1.23.7

    关注「WeiyiGeek」公众号 设为「特别关注」每天带你玩转网络安全运维.应用开发.物联网IOT学习! 希望各位看友[关注.点赞.评论.收藏.投币],助力每一个梦想. 文章目录: 0x00 前言简述 ...

随机推荐

  1. docker 仓库-Harbor

    docker 仓库之分布式 Harbor: Harbor 是一个用于存储和分发docker镜像的企业级Registry服务器,由于Vmware 开源,其通过添加一些企业必须的功能特性,例如安全.标识和 ...

  2. 将.View.dll文件反编译出来的*Views*.cs文件转换成.cshtml

    先使用反编译工具将.View.dll文件反编译放入文件夹,然后将文件夹整体复制进\src\viewcs2cshtml\viewcs2cshtml\bin\Debug\net6.0\viewcs 复制完 ...

  3. c#中单例模式详解

    基础介绍:   确保一个类只有一个实例,并提供一个全局访问点.   适用于需要频繁实例化然后销毁的对象,创建对象消耗资源过多,但又经常用到的对象,频繁访问数据库或文件的对象.   其本质就是保证在整个 ...

  4. Docker学习资料集(从入门到实践)

    前言 昨天分享了一篇介绍Docker可视化管理工具的文章,然后在公众号后台收到了挺多同学的私信问:学习Docker有好的资料值得推荐的吗?想要学习Docker但是无从下手.其实之前我有断断续续的分享过 ...

  5. 2023浙江省大学生信息安全竞赛决赛 Cry+Misc wp

    搞到了一些附件,做做看难度如何. CRYPTO R_r 1.题目信息 查看代码 from Crypto.Util.number import * import random from gmpy2 im ...

  6. String 的 indexOf 与 search 方便的区别

    String 这个对象里面包含许多方法 今天只要讲 indexOf 与 search 1.indexOf stringObject.indexOf(searchvalue,fromindex) 2.s ...

  7. PVE 下虚拟机 Ubuntu 无法进入恢复模式的解决方案——提取原有系统文件

    问题说明 某天重启虚拟机 Ubuntu,发现虚拟机只有容器IP,桥接的接口在虚拟机显示状态为 DOWN: 想重启进入恢复模式,却发现恢复模式一直花屏,无法使用: 没有办法了,只能想办法提取原有系统内原 ...

  8. 《最新出炉》系列初窥篇-Python+Playwright自动化测试-32-JavaScript的调用执行-下篇

    1.简介 在实际工作中,我们需要对处理的元素进行高亮显示,或者有时候为了看清楚操作过程和步骤我们需要跟踪鼠标点击了哪些元素需要标记出来.虽然很少遇到,但是为了以后大家可以参考或者提供一种思路,今天宏哥 ...

  9. C语言编写一个程序,从键盘读入一个矩形的两个边的值(整数),输出矩形面积。

    #include<stdio.h> int main() { int i, j, s;//定义矩形的长,宽,面积变量 scanf_s("%d,%d", &i, ...

  10. 现代统计分析软件Datainside在学生成绩分析中的应用

    Datainside(薪火数据)是一款非常常用的统计分析软件,广泛应用于学术研究和商业领域. 在学生成绩分析方面,Datainside提供了丰富的功能和工具,可以帮助教育机构和研究人员深入理解学生的学 ...