Black lives matter.
We stand in solidarity with the Black community.
Racism is unacceptable.
It conflicts with the core values of the Kubernetes project and our community does not tolerate it.
We stand in solidarity with the Black community.
Racism is unacceptable.
It conflicts with the core values of the Kubernetes project and our community does not tolerate it.
该页面显示如何使用StatefulSet 控制器去运行一个有状态的应用程序。此例是一主多从的 MySQL 集群。
请注意 这不是生产配置。 重点是, MySQL 设置保留在不安全的默认值上,使重点放在 Kubernetes 中运行有状态应用程序的常规模式。
kubectl version
.
* 您需要有一个带有默认StorageClass的动态持续卷供应程序,或者自己静态的提供持久卷来满足这里使用的持久卷请求。
* This tutorial assumes you are familiar with [PersistentVolumes](/docs/concepts/storage/persistent-volumes/) and [StatefulSets](/docs/concepts/workloads/controllers/statefulset/), as well as other core concepts like [Pods](/docs/concepts/workloads/pods/pod/), [Services](/docs/concepts/services-networking/service/), and [ConfigMaps](/docs/tasks/configure-pod-container/configure-pod-configmap/). * Some familiarity with MySQL helps, but this tutorial aims to present general patterns that should be useful for other systems. -->你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。 如果你还没有集群,你可以通过 Minikube 构建一 个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入kubectl version
.
您需要有一个带有默认StorageClass的动态持续卷供应程序,或者自己静态的提供持久卷来满足这里使用的持久卷请求。
部署 MySQL 示例,包含一个 ConfigMap,两个 Services,与一个 StatefulSet。
从以下的 YAML 配置文件创建 ConfigMap :
application/mysql/mysql-configmap.yaml
|
---|
|
kubectl apply -f https://k8s.io/examples/application/mysql/mysql-configmap.yaml
这个 ConfigMap 提供 my.cnf
覆盖,使您可以独立控制 MySQL 主服务器和从服务器的配置。
在这种情况下,您希望主服务器能够将复制日志提供给从服务器,并且希望从服务器拒绝任何不是通过复制进行的写操作。
ConfigMap 本身没有什么特别之处,它可以使不同部分应用于不同的 Pod。 每个 Pod 都会决定在初始化时要看基于 StatefulSet 控制器提供的信息。
从以下 YAML 配置文件创建服务:
application/mysql/mysql-services.yaml
|
---|
|
kubectl apply -f https://k8s.io/examples/application/mysql/mysql-services.yaml
Headless Service 给 StatefulSet 控制器为集合中每个 Pod 创建的 DNS 条目提供了一个宿主。因为 Headless Service 名为 mysql
,所以可以通过在同一 Kubernetes 集群和 namespace 中的任何其他 Pod 内解析 <pod-name>.mysql
来访问 Pod。
客户端 Service 称为 mysql-read
,是一种常规 Service,具有其自己的群集 IP,该群集 IP 在报告为就绪的所有MySQL Pod 中分配连接。可能端点的集合包括 MySQL 主节点和所有从节点。
请注意,只有读取查询才能使用负载平衡的客户端 Service。因为只有一个 MySQL 主服务器,所以客户端应直接连接到 MySQL 主服务器 Pod (通过其在 Headless Service 中的 DNS 条目)以执行写入操作。
最后,从以下 YAML 配置文件创建 StatefulSet:
application/mysql/mysql-statefulset.yaml
|
---|
|
kubectl apply -f https://k8s.io/examples/application/mysql/mysql-statefulset.yaml
您可以通过运行以下命令查看启动进度:
kubectl get pods -l app=mysql --watch
一段时间后,您应该看到所有3个 Pod 都开始运行:
NAME READY STATUS RESTARTS AGE
mysql-0 2/2 Running 0 2m
mysql-1 2/2 Running 0 1m
mysql-2 2/2 Running 0 1m
输入Ctrl+C取消观察。 如果您看不到任何进度,确保已启用前提条件中提到的动态 PersistentVolume 预配器。
该清单使用多种技术来管理作为 StatefulSet 一部分的有状态 Pod。下一节重点介绍其中一些技巧,以解释 StatefulSet 创建 Pod 时发生的状况。
StatefulSet 控制器一次按顺序启动 Pod 序数索引。它一直等到每个 Pod 报告就绪为止,然后再开始下一个 Pod。
此外,控制器为每个 Pod 分配一个唯一,稳定的表单名称 <statefulset-name>-<ordinal-index>
其结果是 Pods 名为 mysql-0,mysql-1 和 mysql-2。
上述 StatefulSet 清单中的 Pod 模板利用这些属性来执行 MySQL 复制的有序启动。
在启动 Pod 规范中的任何容器之前, Pod 首先运行任何初始容器按照定义的顺序。
第一个名为 init-mysql
的初始化容器,根据序号索引生成特殊的 MySQL 配置文件。
该脚本通过从 Pod 名称的末尾提取索引来确定自己的序号索引,该名称由 hostname
命令返回。
然后将序数(带有数字偏移量以避免保留值)保存到 MySQL conf.d 目录中的文件 server-id.cnf 中。
这将转换 StatefulSet 提供的唯一,稳定的身份控制器进入需要相同属性的 MySQL 服务器 ID 的范围。
通过将内容复制到 conf.d 中,init-mysql 容器中的脚本也可以应用 ConfigMap 中的 master.cnf 或 slave.cnf。由于示例拓扑由单个 MySQL 主节点和任意数量的从节点组成,因此脚本仅将序数 0
指定为主节点,而将其他所有人指定为从节点。与 StatefulSet 控制器的部署顺序保证,这样可以确保 MySQL 主服务器在创建从服务器之前已准备就绪,以便它们可以开始复制。
通常,当新的 Pod 作为从节点加入集合时,必须假定 MySQL 主节点可能已经有数据。还必须假设复制日志可能不会一直追溯到时间的开始。
这些保守假设的关键是允许正在运行的 StatefulSet 随时间扩大和缩小而不是固定在其初始大小。
第二个名为 clone-mysql
的初始化容器,第一次在从属 Pod 上以空 PersistentVolume 启动时,会对从属 Pod 执行克隆操作。这意味着它将从另一个运行的 Pod 复制所有现有数据,因此其本地状态足够一致,可以开始主从服务器复制。
MySQL 本身不提供执行此操作的机制,因此该示例使用了一种流行的开源工具 Percona XtraBackup。 在克隆期间,源 MySQL 服务器可能会降低性能。 为了最大程度地减少对 MySQL 主机的影响,该脚本指示每个 Pod 从序号较低的 Pod 中克隆。 可以这样做的原因是 StatefulSet 控制器始终确保在启动 Pod N + 1 之前 Pod N 已准备就绪。
初始化容器成功完成后,常规容器将运行。
MySQL Pods 由运行实际 mysqld
服务器的 mysql
容器和充当辅助工具的 xtrabackup 容器组成。
xtrabackup
辅助工具查看克隆的数据文件,并确定是否有必要在从属服务器上初始化 MySQL 复制。
如果是这样,它将等待 mysqld
准备就绪,然后执行带有从 XtraBackup 克隆文件中提取的复制参数 CHANGE MASTER TO
和 START SLAVE
命令。
一旦从服务器开始复制后,它会记住其 MySQL 主服务器。并且如果服务器重新启动或连接中断,则会自动重新连接。
另外,因为从服务器会以其稳定的 DNS 名称查找主服务器(mysql-0.mysql
),即使由于重新安排而获得新的 Pod IP,他们也会自动找到主服务器。
最后,开始复制后,xtrabackup
容器监听来自其他 Pod 的连接数据克隆请求。
如果 StatefulSet 扩大规模,或者下一个 Pod 失去其 PersistentVolumeClaim 并需要重新克隆,则此服务器将无限期保持运行。
您可以通过运行带有 mysql:5.7
镜像的临时容器并运行 mysql
客户端二进制文件,将测试查询发送到 MySQL 主服务器(主机名 mysql-0.mysql
)。
kubectl run mysql-client --image=mysql:5.7 -i --rm --restart=Never --\
mysql -h mysql-0.mysql <<EOF
CREATE DATABASE test;
CREATE TABLE test.messages (message VARCHAR(250));
INSERT INTO test.messages VALUES ('hello');
EOF
使用主机名 mysql-read
将测试查询发送到任何报告为就绪的服务器:
kubectl run mysql-client --image=mysql:5.7 -i -t --rm --restart=Never --\
mysql -h mysql-read -e "SELECT * FROM test.messages"
您应该获得如下输出:
Waiting for pod default/mysql-client to be running, status is Pending, pod ready: false
+---------+
| message |
+---------+
| hello |
+---------+
pod "mysql-client" deleted
为了演示 mysql-read
服务在服务器之间分配连接,您可以在循环中运行 SELECT @@server_id
:
kubectl run mysql-client-loop --image=mysql:5.7 -i -t --rm --restart=Never --\
bash -ic "while sleep 1; do mysql -h mysql-read -e 'SELECT @@server_id,NOW()'; done"
您应该看到报告的 @@server_id
发生随机变化,因为每次尝试连接时都可能选择了不同的端点:
+-------------+---------------------+
| @@server_id | NOW() |
+-------------+---------------------+
| 100 | 2006-01-02 15:04:05 |
+-------------+---------------------+
+-------------+---------------------+
| @@server_id | NOW() |
+-------------+---------------------+
| 102 | 2006-01-02 15:04:06 |
+-------------+---------------------+
+-------------+---------------------+
| @@server_id | NOW() |
+-------------+---------------------+
| 101 | 2006-01-02 15:04:07 |
+-------------+---------------------+
要停止循环时可以按 Ctrl+C ,但是让它在另一个窗口中运行非常有用,这样您就可以看到以下步骤的效果。
为了证明从从节点缓存而不是单个服务器读取数据的可用性提高,请在使 Pod 退出 Ready 状态时,保持 SELECT @@server_id
循环从上面运行。
mysql
容器的readiness probe运行命令 mysql -h 127.0.0.1 -e 'SELECT 1'
,以确保服务器已启动并能够执行查询。
迫使 readiness probe 失败的一种方法就是执行该命令:
kubectl exec mysql-2 -c mysql -- mv /usr/bin/mysql /usr/bin/mysql.off
这进入 Pod mysql-2
的实际容器文件系统,并重命名 mysql
命令,以便 readiness probe 无法找到它。
几秒钟后, Pod 会将其中一个容器报告为未就绪,您可以通过运行以下命令进行检查:
kubectl get pod mysql-2
在 就绪
列中查找 1/2
:
NAME READY STATUS RESTARTS AGE
mysql-2 1/2 Running 0 3m
此时,您应该会看到 SELECT @@server_id
循环继续运行,尽管它不再报告 102
。
回想一下,init-mysql
脚本将 server-id
定义为 100 + $ordinal
,因此服务器 ID 102
对应于 Pod mysql-2
。
现在修复 Pod,几秒钟后它应该重新出现在循环输出中:
kubectl exec mysql-2 -c mysql -- mv /usr/bin/mysql.off /usr/bin/mysql
如果删除了 Pod,则 StatefulSet 还会重新创建 Pod,类似于 ReplicaSet 对无状态 Pod 所做的操作。
kubectl delete pod mysql-2
StatefulSet 控制器注意到不再存在 mysql-2
Pod,并创建了一个具有相同名称并链接到相同 PersistentVolumeClaim 的新 Pod。
您应该看到服务器 ID 102
从循环输出中消失了一段时间,然后自行返回。
如果您的 Kubernetes 集群具有多个节点,则可以通过发出以下命令drain来模拟节点停机时间(例如升级节点时)。
首先确定 MySQL Pods 之一在哪个节点上:
kubectl get pod mysql-2 -o wide
节点名称应显示在最后一列中:
NAME READY STATUS RESTARTS AGE IP NODE
mysql-2 2/2 Running 0 15m 10.244.5.27 kubernetes-node-9l2t
然后通过运行以下命令耗尽节点,该命令将其封锁,以使新的 Pod 不能在那里调度,然后驱逐任何现有的 Pod。
将 <node-name>
替换为在上一步中找到的 Node 的名称。
这可能会影响节点上的其他应用程序,因此最好 仅在测试集群中执行此操作。
kubectl drain <node-name> --force --delete-local-data --ignore-daemonsets
现在,您可以观察 Pod 在其他节点上的重新安排:
kubectl get pod mysql-2 -o wide --watch
它看起来应该像这样:
NAME READY STATUS RESTARTS AGE IP NODE
mysql-2 2/2 Terminating 0 15m 10.244.1.56 kubernetes-node-9l2t
[...]
mysql-2 0/2 Pending 0 0s <none> kubernetes-node-fjlm
mysql-2 0/2 Init:0/2 0 0s <none> kubernetes-node-fjlm
mysql-2 0/2 Init:1/2 0 20s 10.244.5.32 kubernetes-node-fjlm
mysql-2 0/2 PodInitializing 0 21s 10.244.5.32 kubernetes-node-fjlm
mysql-2 1/2 Running 0 22s 10.244.5.32 kubernetes-node-fjlm
mysql-2 2/2 Running 0 30s 10.244.5.32 kubernetes-node-fjlm
再次,您应该看到服务器 ID 102
从 SELECT @@server_id
循环输出一段时间,然后返回。
现在 uncordon 节点,使其恢复为正常模式:
kubectl uncordon <node-name>
使用 MySQL 复制,您可以通过添加从节点来扩展读取查询的能力。 使用 StatefulSet,您可以使用单个命令执行此操作:
kubectl scale statefulset mysql --replicas=5
查看新的 Pod 的运行情况:
kubectl get pods -l app=mysql --watch
一旦 Pod 启动,您应该看到服务器 IDs 103
和 104
开始出现在 SELECT @@server_id
循环输出中。
您还可以验证这些新服务器在存在之前已添加了数据:
kubectl run mysql-client --image=mysql:5.7 -i -t --rm --restart=Never --\
mysql -h mysql-3.mysql -e "SELECT * FROM test.messages"
Waiting for pod default/mysql-client to be running, status is Pending, pod ready: false
+---------+
| message |
+---------+
| hello |
+---------+
pod "mysql-client" deleted
向下缩放也是不停顿的:
kubectl scale statefulset mysql --replicas=3
但是请注意,按比例放大会自动创建新的 PersistentVolumeClaims,而按比例缩小不会自动删除这些 PVC。 这使您可以选择保留那些初始化的 PVC,以更快地进行缩放,或者在删除它们之前提取数据。
您可以通过运行以下命令查看此信息:
kubectl get pvc -l app=mysql
这表明,尽管将 StatefulSet 缩小为3,所有5个 PVC 仍然存在:
NAME STATUS VOLUME CAPACITY ACCESSMODES AGE
data-mysql-0 Bound pvc-8acbf5dc-b103-11e6-93fa-42010a800002 10Gi RWO 20m
data-mysql-1 Bound pvc-8ad39820-b103-11e6-93fa-42010a800002 10Gi RWO 20m
data-mysql-2 Bound pvc-8ad69a6d-b103-11e6-93fa-42010a800002 10Gi RWO 20m
data-mysql-3 Bound pvc-50043c45-b1c5-11e6-93fa-42010a800002 10Gi RWO 2m
data-mysql-4 Bound pvc-500a9957-b1c5-11e6-93fa-42010a800002 10Gi RWO 2m
如果您不打算重复使用多余的 PVC,则可以删除它们:
kubectl delete pvc data-mysql-3
kubectl delete pvc data-mysql-4
通过在终端上按 Ctrl+C 取消 SELECT @@server_id
循环,或从另一个终端运行以下命令:
kubectl delete pod mysql-client-loop --now
删除 StatefulSet。这也开始终止 Pod。
kubectl delete statefulset mysql
验证 Pod 消失。 他们可能需要一些时间才能完成终止。
kubectl get pods -l app=mysql
当以上内容返回时,您将知道 Pod 已终止:
No resources found.
删除 ConfigMap,Services 和 PersistentVolumeClaims。
kubectl delete configmap,service,pvc -l app=mysql