运行应用
- 1: 使用 Deployment 运行一个无状态应用
- 2: 运行一个单实例有状态应用
- 3: 运行一个有状态的应用程序
- 4: 删除 StatefulSet
- 5: 强制删除 StatefulSet 中的 Pods
- 6: Pod 水平自动扩缩
- 7: Horizontal Pod Autoscaler 演练
- 8: 为应用程序设置干扰预算(Disruption Budget)
- 9: 从 Pod 中访问 Kubernetes API
- 10: 扩缩 StatefulSet
1 - 使用 Deployment 运行一个无状态应用
本文介绍如何通过 Kubernetes Deployment 对象去运行一个应用.
教程目标
- 创建一个 nginx Deployment.
- 使用 kubectl 列举关于 Deployment 的信息.
- 更新 Deployment。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。 建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
您的 Kubernetes 服务器版本必须不低于版本 v1.9. 要获知版本信息,请输入kubectl version
.
创建并了解一个 nginx Deployment
你可以通过创建一个 Kubernetes Deployment 对象来运行一个应用, 且你可以在一个 YAML 文件中描述 Deployment。例如, 下面这个 YAML 文件描述了一个运行 nginx:1.14.2 Docker 镜像的 Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
replicas: 2 # tells deployment to run 2 pods matching the template
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
-
通过 YAML 文件创建一个 Deployment:
kubectl apply -f https://k8s.io/examples/application/deployment.yaml
-
显示 Deployment 相关信息:
kubectl describe deployment nginx-deployment
输出类似于这样:
Name: nginx-deployment Namespace: default CreationTimestamp: Tue, 30 Aug 2016 18:11:37 -0700 Labels: app=nginx Annotations: deployment.kubernetes.io/revision=1 Selector: app=nginx Replicas: 2 desired | 2 updated | 2 total | 2 available | 0 unavailable StrategyType: RollingUpdate MinReadySeconds: 0 RollingUpdateStrategy: 1 max unavailable, 1 max surge Pod Template: Labels: app=nginx Containers: nginx: Image: nginx:1.7.9 Port: 80/TCP Environment: <none> Mounts: <none> Volumes: <none> Conditions: Type Status Reason ---- ------ ------ Available True MinimumReplicasAvailable Progressing True NewReplicaSetAvailable OldReplicaSets: <none> NewReplicaSet: nginx-deployment-1771418926 (2/2 replicas created) No events.
-
列出 Deployment 创建的 Pods:
kubectl get pods -l app=nginx
输出类似于这样:
NAME READY STATUS RESTARTS AGE nginx-deployment-1771418926-7o5ns 1/1 Running 0 16h nginx-deployment-1771418926-r18az 1/1 Running 0 16h
-
展示某一个 Pod 信息:
kubectl describe pod <pod-name>
这里的
<pod-name>
是某一 Pod 的名称。
更新 Deployment
你可以通过更新一个新的 YAML 文件来更新 Deployment。下面的 YAML 文件指定该 Deployment 镜像更新为 nginx 1.16.1。
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
replicas: 2
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.16.1 # Update the version of nginx from 1.14.2 to 1.16.1
ports:
- containerPort: 80
-
应用新的 YAML:
kubectl apply -f https://k8s.io/examples/application/deployment-update.yaml
-
查看该 Deployment 以新的名称创建 Pods 同时删除旧的 Pods:
kubectl get pods -l app=nginx
通过增加副本数来扩缩应用
你可以通过应用新的 YAML 文件来增加 Deployment 中 Pods 的数量。
下面的 YAML 文件将 replicas
设置为 4,指定该 Deployment 应有 4 个 Pods:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
replicas: 4 # Update the replicas from 2 to 4
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
-
应用新的 YAML 文件:
kubectl apply -f https://k8s.io/examples/application/deployment-scale.yaml
-
验证 Deployment 有 4 个 Pods:
kubectl get pods -l app=nginx
输出的结果类似于:
NAME READY STATUS RESTARTS AGE nginx-deployment-148880595-4zdqq 1/1 Running 0 25s nginx-deployment-148880595-6zgi1 1/1 Running 0 25s nginx-deployment-148880595-fxcez 1/1 Running 0 2m nginx-deployment-148880595-rwovn 1/1 Running 0 2m
删除 Deployment
基于名称删除 Deployment:
kubectl delete deployment nginx-deployment
ReplicationControllers -- 旧的方式
创建一个多副本应用首选方法是使用 Deployment,Deployment 内部使用 ReplicaSet。 在 Deployment 和 ReplicaSet 被引入到 Kubernetes 之前,多副本应用通过 ReplicationController 来配置。
接下来
- 进一步了解 Deployment 对象。
2 - 运行一个单实例有状态应用
本文介绍在 Kubernetes 中如何使用 PersistentVolume 和 Deployment 运行一个单实例有状态应用。该应用是 MySQL.
教程目标
- 在你的环境中创建一个引用磁盘的 PersistentVolume
- 创建一个 MySQL Deployment.
- 在集群内以一个已知的 DNS 名称将 MySQL 暴露给其他 Pod
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。 建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入kubectl version
.您需要有一个带有默认StorageClass的动态持续卷供应程序,或者自己静态的提供持久卷来满足这里使用的持久卷请求。
部署 MySQL
你可以通过创建一个 Kubernetes Deployment 并使用 PersistentVolumeClaim 将其连接到 某已有的 PV 卷来运行一个有状态的应用。 例如,这里的 YAML 描述的是一个运行 MySQL 的 Deployment,其中引用了 PVC 申领。 文件为 /var/lib/mysql 定义了加载卷,并创建了一个 PVC 申领,寻找一个 20G 大小的卷。 该申领可以通过现有的满足需求的卷来满足,也可以通过动态供应卷的机制来满足。
注意:在配置的 YAML 文件中定义密码的做法是不安全的。具体安全解决方案请参考 Kubernetes Secrets.
apiVersion: v1
kind: Service
metadata:
name: mysql
spec:
ports:
- port: 3306
selector:
app: mysql
clusterIP: None
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql
spec:
selector:
matchLabels:
app: mysql
strategy:
type: Recreate
template:
metadata:
labels:
app: mysql
spec:
containers:
- image: mysql:5.6
name: mysql
env:
# Use secret in real usage
- name: MYSQL_ROOT_PASSWORD
value: password
ports:
- containerPort: 3306
name: mysql
volumeMounts:
- name: mysql-persistent-storage
mountPath: /var/lib/mysql
volumes:
- name: mysql-persistent-storage
persistentVolumeClaim:
claimName: mysql-pv-claim
apiVersion: v1
kind: PersistentVolume
metadata:
name: mysql-pv-volume
labels:
type: local
spec:
storageClassName: manual
capacity:
storage: 20Gi
accessModes:
- ReadWriteOnce
hostPath:
path: "/mnt/data"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pv-claim
spec:
storageClassName: manual
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
-
部署 YAML 文件中定义的 PV 和 PVC:
kubectl apply -f https://k8s.io/examples/application/mysql/mysql-pv.yaml
-
部署 YAML 文件中定义的 Deployment:
kubectl apply -f https://k8s.io/examples/application/mysql/mysql-deployment.yaml
-
展示 Deployment 相关信息:
kubectl describe deployment mysql
输出类似于:
Name: mysql Namespace: default CreationTimestamp: Tue, 01 Nov 2016 11:18:45 -0700 Labels: app=mysql Annotations: deployment.kubernetes.io/revision=1 Selector: app=mysql Replicas: 1 desired | 1 updated | 1 total | 0 available | 1 unavailable StrategyType: Recreate MinReadySeconds: 0 Pod Template: Labels: app=mysql Containers: mysql: Image: mysql:5.6 Port: 3306/TCP Environment: MYSQL_ROOT_PASSWORD: password Mounts: /var/lib/mysql from mysql-persistent-storage (rw) Volumes: mysql-persistent-storage: Type: PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace) ClaimName: mysql-pv-claim ReadOnly: false Conditions: Type Status Reason ---- ------ ------ Available False MinimumReplicasUnavailable Progressing True ReplicaSetUpdated OldReplicaSets: <none> NewReplicaSet: mysql-63082529 (1/1 replicas created) Events: FirstSeen LastSeen Count From SubobjectPath Type Reason Message --------- -------- ----- ---- ------------- -------- ------ ------- 33s 33s 1 {deployment-controller } Normal ScalingReplicaSet Scaled up replica set mysql-63082529 to 1
-
列举出 Deployment 创建的 pods:
kubectl get pods -l app=mysql
输出类似于:
NAME READY STATUS RESTARTS AGE mysql-63082529-2z3ki 1/1 Running 0 3m
-
查看 PersistentVolumeClaim:
kubectl describe pvc mysql-pv-claim
输出类似于:
Name: mysql-pv-claim Namespace: default StorageClass: Status: Bound Volume: mysql-pv-volume Labels: <none> Annotations: pv.kubernetes.io/bind-completed=yes pv.kubernetes.io/bound-by-controller=yes Capacity: 20Gi Access Modes: RWO Events: <none>
访问 MySQL 实例
前面 YAML 文件中创建了一个允许集群内其他 Pod 访问的数据库服务。该服务中选项
clusterIP: None
让服务 DNS 名称直接解析为 Pod 的 IP 地址。
当在一个服务下只有一个 Pod 并且不打算增加 Pod 的数量这是最好的.
运行 MySQL 客户端以连接到服务器:
kubectl run -it --rm --image=mysql:5.6 --restart=Never mysql-client -- mysql -h mysql -ppassword
此命令在集群内创建一个新的 Pod 并运行 MySQL 客户端,并通过 Service 连接到服务器。 如果连接成功,你就知道有状态的 MySQL 数据库正处于运行状态。
Waiting for pod default/mysql-client-274442439-zyp6i to be running, status is Pending, pod ready: false
If you don't see a command prompt, try pressing enter.
mysql>
更新
Deployment 中镜像或其他部分同往常一样可以通过 kubectl apply
命令更新。
以下是特定于有状态应用的一些注意事项:
- 不要对应用进行规模扩缩。这里的设置仅适用于单实例应用。下层的 PersistentVolume 仅只能挂载到一个 Pod 上。对于集群级有状态应用,请参考 StatefulSet 文档.
- 在 Deployment 的 YAML 文件中使用
strategy:
type: Recreate
。 该选项指示 Kubernetes 不 使用滚动升级。滚动升级无法工作,因为这里一次不能 运行多个 Pod。在使用更新的配置文件创建新的 Pod 前,Recreate
策略将 保证先停止第一个 Pod。
删除 Deployment
通过名称删除部署的对象:
kubectl delete deployment,svc mysql
kubectl delete pvc mysql-pv-claim
kubectl delete pv mysql-pv-volume
如果通过手动的方式供应 PersistentVolume, 那么也需要手动删除它以释放下层资源。 如果是用动态供应方式创建的 PersistentVolume,在删除 PersistentVolumeClaim 后 PersistentVolume 将被自动删除。 一些存储服务(比如 EBS 和 PD)也会在 PersistentVolume 被删除时自动回收下层资源。
接下来
-
欲进一步了解 Deployment 对象,请参考 Deployment 对象
-
进一步了解部署应用
3 - 运行一个有状态的应用程序
本页展示如何使用 StatefulSet 控制器运行一个有状态的应用程序。此例是多副本的 MySQL 数据库。 示例应用的拓扑结构有一个主服务器和多个副本,使用异步的基于行(Row-Based) 的数据复制。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。 建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入kubectl version
.
您需要有一个带有默认StorageClass的动态持续卷供应程序,或者自己静态的提供持久卷来满足这里使用的持久卷请求。
- 本教程假定你熟悉 PersistentVolumes 与 StatefulSet, 以及其他核心概念,例如 Pod、 服务 与 ConfigMap.
- 熟悉 MySQL 会有所帮助,但是本教程旨在介绍对其他系统应该有用的常规模式。
- 您正在使用默认命名空间或不包含任何冲突对象的另一个命名空间。
教程目标
- 使用 StatefulSet 控制器部署多副本 MySQL 拓扑架构。
- 发送 MySQL 客户端请求
- 观察对宕机的抵抗力
- 扩缩 StatefulSet 的规模
部署 MySQL
MySQL 示例部署包含一个 ConfigMap、两个 Service 与一个 StatefulSet。
ConfigMap
使用以下的 YAML 配置文件创建 ConfigMap :
apiVersion: v1
kind: ConfigMap
metadata:
name: mysql
labels:
app: mysql
data:
master.cnf: |
# Apply this config only on the master.
[mysqld]
log-bin
slave.cnf: |
# Apply this config only on slaves.
[mysqld]
super-read-only
kubectl apply -f https://k8s.io/examples/application/mysql/mysql-configmap.yaml
这个 ConfigMap 提供 my.cnf
覆盖设置,使你可以独立控制 MySQL 主服务器和从服务器的配置。
在这里,你希望主服务器能够将复制日志提供给副本服务器,并且希望副本服务器拒绝任何不是通过
复制进行的写操作。
ConfigMap 本身没有什么特别之处,因而也不会出现不同部分应用于不同的 Pod 的情况。 每个 Pod 都会在初始化时基于 StatefulSet 控制器提供的信息决定要查看的部分。
服务
使用以下 YAML 配置文件创建服务:
# Headless service for stable DNS entries of StatefulSet members.
apiVersion: v1
kind: Service
metadata:
name: mysql
labels:
app: mysql
spec:
ports:
- name: mysql
port: 3306
clusterIP: None
selector:
app: mysql
---
# Client service for connecting to any MySQL instance for reads.
# For writes, you must instead connect to the master: mysql-0.mysql.
apiVersion: v1
kind: Service
metadata:
name: mysql-read
labels:
app: mysql
spec:
ports:
- name: mysql
port: 3306
selector:
app: mysql
kubectl apply -f https://k8s.io/examples/application/mysql/mysql-services.yaml
这个无头服务给 StatefulSet 控制器为集合中每个 Pod 创建的 DNS 条目提供了一个宿主。
因为服务名为 mysql
,所以可以通过在同一 Kubernetes 集群和名字中的任何其他 Pod
内解析 <Pod 名称>.mysql
来访问 Pod。
客户端服务称为 mysql-read
,是一种常规服务,具有其自己的集群 IP。
该集群 IP 在报告就绪的所有MySQL Pod 之间分配连接。
可能的端点集合包括 MySQL 主节点和所有副本节点。
请注意,只有读查询才能使用负载平衡的客户端服务。 因为只有一个 MySQL 主服务器,所以客户端应直接连接到 MySQL 主服务器 Pod (通过其在无头服务中的 DNS 条目)以执行写入操作。
StatefulSet
最后,使用以下 YAML 配置文件创建 StatefulSet:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
spec:
selector:
matchLabels:
app: mysql
serviceName: mysql
replicas: 3
template:
metadata:
labels:
app: mysql
spec:
initContainers:
- name: init-mysql
image: mysql:5.7
command:
- bash
- "-c"
- |
set -ex
# Generate mysql server-id from pod ordinal index.
[[ `hostname` =~ -([0-9]+)$ ]] || exit 1
ordinal=${BASH_REMATCH[1]}
echo [mysqld] > /mnt/conf.d/server-id.cnf
# Add an offset to avoid reserved server-id=0 value.
echo server-id=$((100 + $ordinal)) >> /mnt/conf.d/server-id.cnf
# Copy appropriate conf.d files from config-map to emptyDir.
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
command:
- bash
- "-c"
- |
set -ex
# Skip the clone if data already exists.
[[ -d /var/lib/mysql/mysql ]] && exit 0
# Skip the clone on master (ordinal index 0).
[[ `hostname` =~ -([0-9]+)$ ]] || exit 1
ordinal=${BASH_REMATCH[1]}
[[ $ordinal -eq 0 ]] && exit 0
# Clone data from previous peer.
ncat --recv-only mysql-$(($ordinal-1)).mysql 3307 | xbstream -x -C /var/lib/mysql
# Prepare the backup.
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"
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"]
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
readinessProbe:
exec:
# Check we can execute queries over TCP (skip-networking is off).
command: ["mysql", "-h", "127.0.0.1", "-e", "SELECT 1"]
initialDelaySeconds: 5
periodSeconds: 2
timeoutSeconds: 1
- name: xtrabackup
image: gcr.io/google-samples/xtrabackup:1.0
ports:
- name: xtrabackup
containerPort: 3307
command:
- bash
- "-c"
- |
set -ex
cd /var/lib/mysql
# Determine binlog position of cloned data, if any.
if [[ -f xtrabackup_slave_info && "x$(<xtrabackup_slave_info)" != "x" ]]; then
# XtraBackup already generated a partial "CHANGE MASTER TO" query
# because we're cloning from an existing slave. (Need to remove the tailing semicolon!)
cat xtrabackup_slave_info | sed -E 's/;$//g' > change_master_to.sql.in
# Ignore xtrabackup_binlog_info in this case (it's useless).
rm -f xtrabackup_slave_info xtrabackup_binlog_info
elif [[ -f xtrabackup_binlog_info ]]; then
# We're cloning directly from master. Parse binlog position.
[[ `cat xtrabackup_binlog_info` =~ ^(.*?)[[:space:]]+(.*?)$ ]] || exit 1
rm -f xtrabackup_binlog_info xtrabackup_slave_info
echo "CHANGE MASTER TO MASTER_LOG_FILE='${BASH_REMATCH[1]}',\
MASTER_LOG_POS=${BASH_REMATCH[2]}" > change_master_to.sql.in
fi
# Check if we need to complete a clone by starting replication.
if [[ -f change_master_to.sql.in ]]; then
echo "Waiting for mysqld to be ready (accepting connections)"
until mysql -h 127.0.0.1 -e "SELECT 1"; do sleep 1; done
echo "Initializing replication from clone position"
mysql -h 127.0.0.1 \
-e "$(<change_master_to.sql.in), \
MASTER_HOST='mysql-0.mysql', \
MASTER_USER='root', \
MASTER_PASSWORD='', \
MASTER_CONNECT_RETRY=10; \
START SLAVE;" || exit 1
# In case of container restart, attempt this at-most-once.
mv change_master_to.sql.in change_master_to.sql.orig
fi
# Start a server to send backups when requested by peers.
exec ncat --listen --keep-open --send-only --max-conns=1 3307 -c \
"xtrabackup --backup --slave-info --stream=xbstream --host=127.0.0.1 --user=root"
volumeMounts:
- name: data
mountPath: /var/lib/mysql
subPath: mysql
- name: conf
mountPath: /etc/mysql/conf.d
resources:
requests:
cpu: 100m
memory: 100Mi
volumes:
- name: conf
emptyDir: {}
- name: config-map
configMap:
name: mysql
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
kubectl apply -f https://k8s.io/examples/application/mysql/mysql-statefulset.yaml
你可以通过运行以下命令查看启动进度:
kubectl get pods -l app=mysql --watch
一段时间后,你应该看到所有 3 个 Pod 进入 Running 状态:
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 结束 watch 操作。 如果你看不到任何进度,确保已启用前提条件 中提到的动态 PersistentVolume 预配器。
此清单使用多种技术来管理作为 StatefulSet 的一部分的有状态 Pod。 下一节重点介绍其中的一些技巧,以解释 StatefulSet 创建 Pod 时发生的状况。
了解有状态的 Pod 初始化
StatefulSet 控制器按序数索引顺序地每次启动一个 Pod。 它一直等到每个 Pod 报告就绪才再启动下一个 Pod。
此外,控制器为每个 Pod 分配一个唯一、稳定的名称,形如 <statefulset 名称>-<序数索引>
,
其结果是 Pods 名为 mysql-0
、mysql-1
和 mysql-2
。
上述 StatefulSet 清单中的 Pod 模板利用这些属性来执行 MySQL 副本的有序启动。
生成配置
在启动 Pod 规约中的任何容器之前,Pod 首先按顺序运行所有的 Init 容器。
第一个名为 init-mysql
的 Init 容器根据序号索引生成特殊的 MySQL 配置文件。
该脚本通过从 Pod 名称的末尾提取索引来确定自己的序号索引,而 Pod 名称由 hostname
命令返回。
然后将序数(带有数字偏移量以避免保留值)保存到 MySQL conf.d 目录中的文件 server-id.cnf。
这一操作将 StatefulSet 所提供的唯一、稳定的标识转换为 MySQL 服务器的 ID,
而这些 ID 也是需要唯一性、稳定性保证的。
通过将内容复制到 conf.d 中,init-mysql
容器中的脚本也可以应用 ConfigMap 中的
primary.cnf
或 replica.cnf
。
由于示例部署结构由单个 MySQL 主节点和任意数量的副本节点组成,
因此脚本仅将序数 0
指定为主节点,而将其他所有节点指定为副本节点。
与 StatefulSet 控制器的 部署顺序保证 相结合, 可以确保 MySQL 主服务器在创建副本服务器之前已准备就绪,以便它们可以开始复制。
克隆现有数据
通常,当新 Pod 作为副本节点加入集合时,必须假定 MySQL 主节点可能已经有数据。 还必须假设复制日志可能不会一直追溯到时间的开始。
这些保守的假设是允许正在运行的 StatefulSet 随时间扩大和缩小而不是固定在其初始大小的关键。
第二个名为 clone-mysql
的 Init 容器,第一次在带有空 PersistentVolume 的副本 Pod
上启动时,会在从属 Pod 上执行克隆操作。
这意味着它将从另一个运行中的 Pod 复制所有现有数据,使此其本地状态足够一致,
从而可以开始从主服务器复制。
MySQL 本身不提供执行此操作的机制,因此本示例使用了一种流行的开源工具 Percona XtraBackup。 在克隆期间,源 MySQL 服务器性能可能会受到影响。 为了最大程度地减少对 MySQL 主服务器的影响,该脚本指示每个 Pod 从序号较低的 Pod 中克隆。 可以这样做的原因是 StatefulSet 控制器始终确保在启动 Pod N + 1 之前 Pod N 已准备就绪。
开始复制
Init 容器成功完成后,应用容器将运行。
MySQL Pod 由运行实际 mysqld
服务的 mysql
容器和充当
辅助工具
的 xtrabackup 容器组成。
xtrabackup
sidecar 容器查看克隆的数据文件,并确定是否有必要在副本服务器上初始化 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 和 Node 的宕机时间
为了证明从副本节点缓存而不是单个服务器读取数据的可用性提高,请在使 Pod 退出 Ready
状态时,保持上述 SELECT @@server_id
循环一直运行。
破坏就绪态探测
mysql
容器的
就绪态探测
运行命令 mysql -h 127.0.0.1 -e 'SELECT 1'
,以确保服务器已启动并能够执行查询。
迫使就绪态探测失败的一种方法就是中止该命令:
kubectl exec mysql-2 -c mysql -- mv /usr/bin/mysql /usr/bin/mysql.off
此命令会进入 Pod mysql-2
的实际容器文件系统,重命名 mysql
命令,导致就绪态探测无法找到它。
几秒钟后, Pod 会报告其中一个容器未就绪。你可以通过运行以下命令进行检查:
kubectl get pod mysql-2
在 READY
列中查找 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
删除 Pods
如果删除了 Pod,则 StatefulSet 还会重新创建 Pod,类似于 ReplicaSet 对无状态 Pod 所做的操作。
kubectl delete pod mysql-2
StatefulSet 控制器注意到不再存在 mysql-2
Pod,于是创建一个具有相同名称并链接到相同
PersistentVolumeClaim 的新 Pod。
你应该看到服务器 ID 102
从循环输出中消失了一段时间,然后又自行出现。
腾空节点
如果你的 Kubernetes 集群具有多个节点,则可以通过发出以下 drain 命令来模拟节点停机(就好像节点在被升级)。
首先确定 MySQL Pod 之一在哪个节点上:
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。将 <节点名称>
替换为在上一步中找到的节点名称。
这可能会影响节点上的其他应用程序,因此最好 仅在测试集群中执行此操作。
kubectl drain <节点名称> --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 <节点名称>
扩展副本节点数量
使用 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
- 如果你手动供应 PersistentVolume,则还需要手动删除它们,并释放下层资源。 如果你使用了动态预配器,当得知你删除 PersistentVolumeClaims 时,它将自动删除 PersistentVolumes。 一些动态预配器(例如用于 EBS 和 PD 的预配器)也会在删除 PersistentVolumes 时释放下层资源。
接下来
- 进一步了解为 StatefulSet 扩缩容.
- 进一步了解调试 StatefulSet.
- 进一步了解删除 StatefulSet.
- 进一步了解强制删除 StatefulSet Pods.
- 在 Helm Charts 仓库中查找其他有状态的应用程序示例。
4 - 删除 StatefulSet
本任务展示如何删除 StatefulSet。
准备开始
- 本任务假设在你的集群上已经运行了由 StatefulSet 创建的应用。
删除 StatefulSet
你可以像删除 Kubernetes 中的其他资源一样删除 StatefulSet:使用 kubectl delete
命令,并按文件或者名字指定 StatefulSet。
kubectl delete -f <file.yaml>
kubectl delete statefulsets <statefulset 名称>
删除 StatefulSet 之后,你可能需要单独删除关联的无头服务。
kubectl delete service <服务名称>
当通过 kubectl
删除 StatefulSet 时,StatefulSet 会被缩容为 0。
属于该 StatefulSet 的所有 Pod 也被删除。
如果你只想删除 StatefulSet 而不删除 Pod,使用 --cascade=orphan
。
kubectl delete -f <file.yaml> --cascade=orphan
通过将 --cascade=orphan
传递给 kubectl delete
,在删除 StatefulSet 对象之后,
StatefulSet 管理的 Pod 会被保留下来。如果 Pod 具有标签 app=myapp
,则可以按照
如下方式删除它们:
kubectl delete pods -l app=myapp
持久卷
删除 StatefulSet 管理的 Pod 并不会删除关联的卷。这是为了确保你有机会在删除卷之前从卷中复制数据。 在 Pod 离开终止状态 后删除 PVC 可能会触发删除背后的 PV 持久卷,具体取决于存储类和回收策略。 永远不要假定在 PVC 删除后仍然能够访问卷。
完全删除 StatefulSet
要删除 StatefulSet 中的所有内容,包括关联的 pods,你可以运行 一系列如下所示的命令:
grace=$(kubectl get pods <stateful-set-pod> --template '{{.spec.terminationGracePeriodSeconds}}')
kubectl delete statefulset -l app=myapp
sleep $grace
kubectl delete pvc -l app=myapp
在上面的例子中,Pod 的标签为 app=myapp
;适当地替换你自己的标签。
强制删除 StatefulSet 的 Pod
如果你发现 StatefulSet 的某些 Pod 长时间处于 'Terminating' 或者 'Unknown' 状态, 则可能需要手动干预以强制从 API 服务器中删除这些 Pod。 这是一项有点危险的任务。详细信息请阅读 删除 StatefulSet 类型的 Pods。
接下来
进一步了解强制删除 StatefulSet 的 Pods。
5 - 强制删除 StatefulSet 中的 Pods
本文介绍如何删除 StatefulSet 管理的 Pods,并解释这样操作时需要记住的注意事项。
准备开始
- 这是一项相当高级的任务,并且可能会违反 StatefulSet 固有的某些属性。
- 继续任务之前,请熟悉下面列举的注意事项。
StatefulSet 注意事项
在 StatefulSet 的正常操作中,永远不需要强制删除 StatefulSet 管理的 Pod。 StatefulSet 控制器负责创建、 扩缩和删除 StatefulSet 管理的 Pods。它尝试确保指定数量的从序数 0 到 N-1 的 Pod 处于活跃状态并准备就绪。StatefulSet 确保在任何时候,集群中最多只有一个具有给定标识的 Pod。 这就是所谓的由 StatefulSet 提供的*最多一个(At Most One)*的语义。
应谨慎进行手动强制删除操作,因为它可能会违反 StatefulSet 固有的至多一个的语义。 StatefulSets 可用于运行分布式和集群级的应用,这些应用需要稳定的网络标识和可靠的存储。 这些应用通常配置为具有固定标识固定数量的成员集合。 具有相同身份的多个成员可能是灾难性的,并且可能导致数据丢失 (例如:票选系统中的脑裂场景)。
删除 Pods
你可以使用下面的命令执行体面地删除 Pod:
kubectl delete pods <pod>
为了让上面操作能够体面地终止 Pod,Pod 一定不能 设置 pod.Spec.TerminationGracePeriodSeconds
为 0。
将 pod.Spec.TerminationGracePeriodSeconds
设置为 0s 的做法是不安全的,强烈建议 StatefulSet 类型的
Pod 不要使用。体面删除是安全的,并且会在 kubelet 从 API 服务器中删除资源名称之前确保
体面地结束 pod 。
当某个节点不可达时,不会引发自动删除 Pod。 在无法访问的节点上运行的 Pod 在 超时 后会进入'Terminating' 或者 'Unknown' 状态。 当用户尝试体面地删除无法访问的节点上的 Pod 时 Pod 也可能会进入这些状态。 从 API 服务器上删除处于这些状态 Pod 的仅有可行方法如下:
- 删除 Node 对象(要么你来删除, 要么节点控制器 来删除)
- 无响应节点上的 kubelet 开始响应,杀死 Pod 并从 API 服务器上移除 Pod 对象
- 用户强制删除 pod
推荐使用第一种或者第二种方法。如果确认节点已经不可用了 (比如,永久断开网络、断电等), 则应删除 Node 对象。 如果节点遇到网裂问题,请尝试解决该问题或者等待其解决。 当网裂愈合时,kubelet 将完成 Pod 的删除并从 API 服务器上释放其名字。
通常,Pod 一旦不在节点上运行,或者管理员删除了节点,系统就会完成其删除动作。 你也可以通过强制删除 Pod 来绕过这一机制。
强制删除
强制删除不会等待来自 kubelet 对 Pod 已终止的确认消息。 无论强制删除是否成功杀死了 Pod,它都会立即从 API 服务器中释放该名字。 这将让 StatefulSet 控制器创建一个具有相同标识的替身 Pod;因而可能导致正在运行 Pod 的重复, 并且如果所述 Pod 仍然可以与 StatefulSet 的成员通信,则将违反 StatefulSet 所要保证的 最多一个的语义。
当你强制删除 StatefulSet 类型的 Pod 时,你要确保有问题的 Pod 不会再和 StatefulSet 管理的其他 Pod 通信并且可以安全地释放其名字以便创建替代 Pod。
如果要使用 kubectl 1.5 以上版本强制删除 Pod,请执行下面命令:
kubectl delete pods <pod> --grace-period=0 --force
如果你使用 kubectl 的 1.4 以下版本,则应省略 --force
选项:
kubectl delete pods <pod> --grace-period=0
如果在这些命令后 Pod 仍处于 Unknown
状态,请使用以下命令从集群中删除 Pod:
kubectl patch pod <pod> -p '{"metadata":{"finalizers":null}}'
请始终谨慎地执行强制删除 StatefulSet 类型的 pods,并完全了解所涉及地风险。
接下来
进一步了解调试 StatefulSet。
6 - Pod 水平自动扩缩
Pod 水平自动扩缩(Horizontal Pod Autoscaler) 可以基于 CPU 利用率自动扩缩 ReplicationController、Deployment、ReplicaSet 和 StatefulSet 中的 Pod 数量。 除了 CPU 利用率,也可以基于其他应程序提供的 自定义度量指标 来执行自动扩缩。 Pod 自动扩缩不适用于无法扩缩的对象,比如 DaemonSet。
Pod 水平自动扩缩特性由 Kubernetes API 资源和控制器实现。资源决定了控制器的行为。 控制器会周期性地调整副本控制器或 Deployment 中的副本数量,以使得类似 Pod 平均 CPU 利用率、平均内存利用率这类观测到的度量值与用户所设定的目标值匹配。
Pod 水平自动扩缩工作机制
Pod 水平自动扩缩器的实现是一个控制回路,由控制器管理器的 --horizontal-pod-autoscaler-sync-period
参数指定周期(默认值为 15 秒)。
每个周期内,控制器管理器根据每个 HorizontalPodAutoscaler 定义中指定的指标查询资源利用率。 控制器管理器可以从资源度量指标 API(按 Pod 统计的资源用量)和自定义度量指标 API(其他指标)获取度量值。
-
对于按 Pod 统计的资源指标(如 CPU),控制器从资源指标 API 中获取每一个 HorizontalPodAutoscaler 指定的 Pod 的度量值,如果设置了目标使用率, 控制器获取每个 Pod 中的容器资源使用情况,并计算资源使用率。 如果设置了 target 值,将直接使用原始数据(不再计算百分比)。 接下来,控制器根据平均的资源使用率或原始值计算出扩缩的比例,进而计算出目标副本数。
需要注意的是,如果 Pod 某些容器不支持资源采集,那么控制器将不会使用该 Pod 的 CPU 使用率。 下面的算法细节章节将会介绍详细的算法。
- 如果 Pod 使用自定义指示,控制器机制与资源指标类似,区别在于自定义指标只使用 原始值,而不是使用率。
- 如果 Pod 使用对象指标和外部指标(每个指标描述一个对象信息)。
这个指标将直接根据目标设定值相比较,并生成一个上面提到的扩缩比例。
在
autoscaling/v2beta2
版本 API 中,这个指标也可以根据 Pod 数量平分后再计算。
通常情况下,控制器将从一系列的聚合 API(metrics.k8s.io
、custom.metrics.k8s.io
和 external.metrics.k8s.io
)中获取度量值。
metrics.k8s.io
API 通常由 Metrics 服务器(需要额外启动)提供。
可以从 metrics-server 获取更多信息。
另外,控制器也可以直接从 Heapster 获取指标。
Kubernetes 1.11 [deprecated]
自 Kubernetes 1.11 起,从 Heapster 获取指标特性已废弃。
关于指标 API 更多信息,请参考度量值指标 API 的支持。
自动扩缩控制器使用 scale 子资源访问相应可支持扩缩的控制器(如副本控制器、
Deployment 和 ReplicaSet)。
scale
是一个可以动态设定副本数量和检查当前状态的接口。
关于 scale 子资源的更多信息,请参考这里.
算法细节
从最基本的角度来看,Pod 水平自动扩缩控制器根据当前指标和期望指标来计算扩缩比例。
期望副本数 = ceil[当前副本数 * (当前指标 / 期望指标)]
例如,当前度量值为 200m
,目标设定值为 100m
,那么由于 200.0/100.0 == 2.0
,
副本数量将会翻倍。
如果当前指标为 50m
,副本数量将会减半,因为50.0/100.0 == 0.5
。
如果计算出的扩缩比例接近 1.0
(根据--horizontal-pod-autoscaler-tolerance
参数全局配置的容忍值,默认为 0.1),
将会放弃本次扩缩。
如果 HorizontalPodAutoscaler 指定的是 targetAverageValue
或 targetAverageUtilization
,
那么将会把指定 Pod 度量值的平均值做为 currentMetricValue
。
然而,在检查容忍度和决定最终扩缩值前,我们仍然会把那些无法获取指标的 Pod 统计进去。
所有被标记了删除时间戳(Pod 正在关闭过程中)的 Pod 和失败的 Pod 都会被忽略。
如果某个 Pod 缺失度量值,它将会被搁置,只在最终确定扩缩数量时再考虑。
当使用 CPU 指标来扩缩时,任何还未就绪(例如还在初始化)状态的 Pod 或 最近的指标 度量值采集于就绪状态前的 Pod,该 Pod 也会被搁置。
由于受技术限制,Pod 水平扩缩控制器无法准确的知道 Pod 什么时候就绪,
也就无法决定是否暂时搁置该 Pod。
--horizontal-pod-autoscaler-initial-readiness-delay
参数(默认为 30s)用于设置 Pod 准备时间,
在此时间内的 Pod 统统被认为未就绪。
--horizontal-pod-autoscaler-cpu-initialization-period
参数(默认为5分钟)
用于设置 Pod 的初始化时间,
在此时间内的 Pod,CPU 资源度量值将不会被采纳。
在排除掉被搁置的 Pod 后,扩缩比例就会根据 currentMetricValue/desiredMetricValue
计算出来。
如果缺失任何的度量值,我们会更保守地重新计算平均值, 在需要缩小时假设这些 Pod 消耗了目标值的 100%, 在需要放大时假设这些 Pod 消耗了 0% 目标值。 这可以在一定程度上抑制扩缩的幅度。
此外,如果存在任何尚未就绪的 Pod,我们可以在不考虑遗漏指标或尚未就绪的 Pod 的情况下进行扩缩, 我们保守地假设尚未就绪的 Pod 消耗了期望指标的 0%,从而进一步降低了扩缩的幅度。
在扩缩方向(缩小或放大)确定后,我们会把未就绪的 Pod 和缺少指标的 Pod 考虑进来再次计算使用率。 如果新的比率与扩缩方向相反,或者在容忍范围内,则跳过扩缩。 否则,我们使用新的扩缩比例。
注意,平均利用率的原始值会通过 HorizontalPodAutoscaler 的状态体现( 即使使用了新的使用率,也不考虑未就绪 Pod 和 缺少指标的 Pod)。
如果创建 HorizontalPodAutoscaler 时指定了多个指标,
那么会按照每个指标分别计算扩缩副本数,取最大值进行扩缩。
如果任何一个指标无法顺利地计算出扩缩副本数(比如,通过 API 获取指标时出错),
并且可获取的指标建议缩容,那么本次扩缩会被跳过。
这表示,如果一个或多个指标给出的 desiredReplicas
值大于当前值,HPA 仍然能实现扩容。
最后,在 HPA 控制器执行扩缩操作之前,会记录扩缩建议信息。
控制器会在操作时间窗口中考虑所有的建议信息,并从中选择得分最高的建议。
这个值可通过 kube-controller-manager
服务的启动参数
--horizontal-pod-autoscaler-downscale-stabilization
进行配置,
默认值为 5 分钟。
这个配置可以让系统更为平滑地进行缩容操作,从而消除短时间内指标值快速波动产生的影响。
API 对象
HorizontalPodAutoscaler 是 Kubernetes autoscaling
API 组的资源。
在当前稳定版本(autoscaling/v1
)中只支持基于 CPU 指标的扩缩。
API 的 beta 版本(autoscaling/v2beta2
)引入了基于内存和自定义指标的扩缩。
在 autoscaling/v2beta2
版本中新引入的字段在 autoscaling/v1
版本中以注解
的形式得以保留。
创建 HorizontalPodAutoscaler 对象时,需要确保所给的名称是一个合法的 DNS 子域名。 有关 API 对象的更多信息,请查阅 HorizontalPodAutoscaler 对象设计文档。
kubectl 对 Horizontal Pod Autoscaler 的支持
与其他 API 资源类似,kubectl
以标准方式支持 HPA。
我们可以通过 kubectl create
命令创建一个 HPA 对象,
通过 kubectl get hpa
命令来获取所有 HPA 对象,
通过 kubectl describe hpa
命令来查看 HPA 对象的详细信息。
最后,可以使用 kubectl delete hpa
命令删除对象。
此外,还有个简便的命令 kubectl autoscale
来创建 HPA 对象。
例如,命令 kubectl autoscale rs foo --min=2 --max=5 --cpu-percent=80
将会为名
为 foo 的 ReplicationSet 创建一个 HPA 对象,
目标 CPU 使用率为 80%
,副本数量配置为 2 到 5 之间。
滚动升级时扩缩
Kubernetes 允许你在 Deployment 上执行滚动更新。在这种情况下,Deployment 为你管理下层的 ReplicaSet。
当你为一个 Deployment 配置自动扩缩时,你要为每个 Deployment 绑定一个 HorizontalPodAutoscaler。
HorizontalPodAutoscaler 管理 Deployment 的 replicas
字段。
Deployment Controller 负责设置下层 ReplicaSet 的 replicas
字段,
以便确保在上线及后续过程副本个数合适。
如果你对一个副本个数被自动扩缩的 StatefulSet 执行滚动更新, 该 StatefulSet 会直接管理它的 Pod 集合 (不存在类似 ReplicaSet 这样的中间资源)。
冷却/延迟支持
当使用 Horizontal Pod Autoscaler 管理一组副本扩缩时, 有可能因为指标动态的变化造成副本数量频繁的变化,有时这被称为 抖动(Thrashing)。
从 v1.6 版本起,集群操作员可以调节某些 kube-controller-manager
的全局参数来
缓解这个问题。
从 v1.12 开始,算法调整后,扩容操作时的延迟就不必设置了。
--horizontal-pod-autoscaler-downscale-stabilization
: 设置缩容冷却时间窗口长度。 水平 Pod 扩缩器能够记住过去建议的负载规模,并仅对此时间窗口内的最大规模执行操作。 默认值是 5 分钟(5m0s
)。
对资源指标的支持
HPA 的任何目标资源都可以基于其中的 Pods 的资源用量来实现扩缩。
在定义 Pod 规约时,类似 cpu
和 memory
这类资源请求必须被设定。
这些设定值被用来确定资源利用量并被 HPA 控制器用来对目标资源完成扩缩操作。
要使用基于资源利用率的扩缩,可以像下面这样指定一个指标源:
type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60
基于这一指标设定,HPA 控制器会维持扩缩目标中的 Pods 的平均资源利用率在 60%。 利用率是 Pod 的当前资源用量与其请求值之间的比值。关于如何计算利用率以及如何计算平均值 的细节可参考算法小节。
由于所有的容器的资源用量都会被累加起来,Pod 的总体资源用量值可能不会精确体现 各个容器的资源用量。这一现象也会导致一些问题,例如某个容器运行时的资源用量非常 高,但因为 Pod 层面的资源用量总值让人在可接受的约束范围内,HPA 不会执行扩大 目标对象规模的操作。
容器资源指标
Kubernetes v1.20 [alpha]
HorizontalPodAutoscaler
也支持容器指标源,这时 HPA 可以跟踪记录一组 Pods 中各个容器的
资源用量,进而触发扩缩目标对象的操作。
容器资源指标的支持使得你可以为特定 Pod 中最重要的容器配置规模缩放阈值。
例如,如果你有一个 Web 应用和一个执行日志操作的边车容器,你可以基于 Web 应用的
资源用量来执行扩缩,忽略边车容器的存在及其资源用量。
如果你更改缩放目标对象,令其使用新的、包含一组不同的容器的 Pod 规约,你就需要 修改 HPA 的规约才能基于新添加的容器来执行规模扩缩操作。 如果指标源中指定的容器不存在或者仅存在于部分 Pods 中,那么这些 Pods 会被忽略, HPA 会重新计算资源用量值。参阅算法小节进一步了解计算细节。 要使用容器资源用量来完成自动扩缩,可以像下面这样定义指标源:
type: ContainerResource
containerResource:
name: cpu
container: application
target:
type: Utilization
averageUtilization: 60
在上面的例子中,HPA 控制器会对目标对象执行扩缩操作以确保所有 Pods 中
application
容器的平均 CPU 用量为 60%。
如果你要更改 HorizontalPodAutoscaler 所跟踪记录的容器的名称,你可以按一定顺序 来执行这一更改,确保在应用更改的过程中用来判定扩缩行为的容器可用。 在更新定义容器的资源(如 Deployment)之前,你需要更新相关的 HPA,使之能够同时 跟踪记录新的和老的容器名称。这样,HPA 就能够在整个更新过程中继续计算并提供扩缩操作建议。
一旦你已经将容器名称变更这一操作应用到整个负载对象至上,就可以从 HPA 的规约中去掉老的容器名称,完成清理操作。
多指标支持
Kubernetes 1.6 开始支持基于多个度量值进行扩缩。
你可以使用 autoscaling/v2beta2
API 来为 Horizontal Pod Autoscaler 指定多个指标。
Horizontal Pod Autoscaler 会根据每个指标计算,并生成一个扩缩建议。
幅度最大的扩缩建议会被采纳。
自定义指标支持
自 Kubernetes 1.6 起,Horizontal Pod Autoscaler 支持使用自定义指标。
你可以使用 autoscaling/v2beta2
API 为 Horizontal Pod Autoscaler 指定用户自定义指标。
Kubernetes 会通过用户自定义指标 API 来获取相应的指标。
关于指标 API 的要求,请参阅对 Metrics API 的支持。
对 Metrics API 的支持
默认情况下,HorizontalPodAutoscaler 控制器会从一系列的 API 中检索度量值。 集群管理员需要确保下述条件,以保证 HPA 控制器能够访问这些 API:
-
启用了 API 聚合层
-
相应的 API 已注册:
-
对于资源指标,将使用
metrics.k8s.io
API,一般由 metrics-server 提供。 它可以作为集群插件启动。 -
对于自定义指标,将使用
custom.metrics.k8s.io
API。 它由其他度量指标方案厂商的“适配器(Adapter)” API 服务器提供。 确认你的指标流水线,或者查看已知方案列表。 如果你想自己编写,请从 boilerplate开始。 -
对于外部指标,将使用
external.metrics.k8s.io
API。可能由上面的自定义指标适配器提供。
-
-
--horizontal-pod-autoscaler-use-rest-clients
参数设置为true
或者不设置。 如果设置为 false,则会切换到基于 Heapster 的自动扩缩,这个特性已经被弃用了。
关于指标来源以及其区别的更多信息,请参阅相关的设计文档, the HPA V2、 custom.metrics.k8s.io 和 external.metrics.k8s.io。
关于如何使用它们的示例,请参考 使用自定义指标的教程 和使用外部指标的教程。
支持可配置的扩缩
从 v1.18
开始,v2beta2
API 允许通过 HPA 的 behavior
字段配置扩缩行为。
在 behavior
字段中的 scaleUp
和 scaleDown
分别指定扩容和缩容行为。
可以两个方向指定一个稳定窗口,以防止扩缩目标中副本数量的波动。
类似地,指定扩缩策略可以控制扩缩时副本数的变化率。
扩缩策略
在 spec 字段的 behavior
部分可以指定一个或多个扩缩策略。
当指定多个策略时,默认选择允许更改最多的策略。
下面的例子展示了缩容时的行为:
behavior:
scaleDown:
policies:
- type: Pods
value: 4
periodSeconds: 60
- type: Percent
value: 10
periodSeconds: 60
periodSeconds
表示在过去的多长时间内要求策略值为真。
第一个策略(Pods)允许在一分钟内最多缩容 4 个副本。第二个策略(Percent)
允许在一分钟内最多缩容当前副本个数的百分之十。
由于默认情况下会选择容许更大程度作出变更的策略,只有 Pod 副本数大于 40 时, 第二个策略才会被采用。如果副本数为 40 或者更少,则应用第一个策略。 例如,如果有 80 个副本,并且目标必须缩小到 10 个副本,那么在第一步中将减少 8 个副本。 在下一轮迭代中,当副本的数量为 72 时,10% 的 Pod 数为 7.2,但是这个数字向上取整为 8。 在 autoscaler 控制器的每个循环中,将根据当前副本的数量重新计算要更改的 Pod 数量。 当副本数量低于 40 时,应用第一个策略(Pods),一次减少 4 个副本。
可以指定扩缩方向的 selectPolicy
字段来更改策略选择。
通过设置 Min
的值,它将选择副本数变化最小的策略。
将该值设置为 Disabled
将完全禁用该方向的缩放。
稳定窗口
当用于扩缩的指标持续抖动时,使用稳定窗口来限制副本数上下振动。
自动扩缩算法使用稳定窗口来考虑过去计算的期望状态,以防止扩缩。
在下面的例子中,稳定化窗口被指定为 scaleDown
。
scaleDown:
stabilizationWindowSeconds: 300
当指标显示目标应该缩容时,自动扩缩算法查看之前计算的期望状态,并使用指定时间间隔内的最大值。 在上面的例子中,过去 5 分钟的所有期望状态都会被考虑。
默认行为
要使用自定义扩缩,不必指定所有字段。 只有需要自定义的字段才需要指定。 这些自定义值与默认值合并。 默认值与 HPA 算法中的现有行为匹配。
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 100
periodSeconds: 15
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100
periodSeconds: 15
- type: Pods
value: 4
periodSeconds: 15
selectPolicy: Max
用于缩小稳定窗口的时间为 300 秒(或是 --horizontal-pod-autoscaler-downscale-stabilization
参数设定值)。
只有一种缩容的策略,允许 100% 删除当前运行的副本,这意味着扩缩目标可以缩小到允许的最小副本数。
对于扩容,没有稳定窗口。当指标显示目标应该扩容时,目标会立即扩容。
这里有两种策略,每 15 秒添加 4 个 Pod 或 100% 当前运行的副本数,直到 HPA 达到稳定状态。
示例:更改缩容稳定窗口
将下面的 behavior 配置添加到 HPA 中,可提供一个 1 分钟的自定义缩容稳定窗口:
behavior:
scaleDown:
stabilizationWindowSeconds: 60
示例:限制缩容速率
将下面的 behavior 配置添加到 HPA 中,可限制 Pod 被 HPA 删除速率为每分钟 10%:
behavior:
scaleDown:
policies:
- type: Percent
value: 10
periodSeconds: 60
为了确保每分钟删除的 Pod 数不超过 5 个,可以添加第二个缩容策略,大小固定为 5,
并将 selectPolicy
设置为最小值。
将 selectPolicy
设置为 Min
意味着 autoscaler 会选择影响 Pod 数量最小的策略:
behavior:
scaleDown:
policies:
- type: Percent
value: 10
periodSeconds: 60
- type: Pods
value: 5
periodSeconds: 60
selectPolicy: Min
示例:禁用缩容
selectPolicy
的值 Disabled
会关闭对给定方向的缩容。
因此使用以下策略,将会阻止缩容:
behavior:
scaleDown:
selectPolicy: Disabled
隐式维护状态禁用
你可以在不必更改 HPA 配置的情况下隐式地为某个目标禁用 HPA。
如果此目标的期望副本个数被设置为 0,而 HPA 的最小副本个数大于 0,
则 HPA 会停止调整目标(并将其自身的 ScalingActive
状况设置为 false
),
直到你通过手动调整目标的期望副本个数或 HPA 的最小副本个数来重新激活。
接下来
- 设计文档:Horizontal Pod Autoscaling
kubectl autoscale
命令:kubectl autoscale.- 使用示例:Horizontal Pod Autoscaler.
7 - Horizontal Pod Autoscaler 演练
Horizontal Pod Autoscaler 可以根据 CPU 利用率自动扩缩 ReplicationController、 Deployment、ReplicaSet 或 StatefulSet 中的 Pod 数量 (也可以基于其他应用程序提供的度量指标,目前这一功能处于 beta 版本)。
本文将引领你了解如何为 php-apache 服务器配置和使用 Horizontal Pod Autoscaler。 与 Horizontal Pod Autoscaler 相关的更多信息请参阅 Horizontal Pod Autoscaler 用户指南。
准备开始
本文示例需要一个运行中的 Kubernetes 集群以及 kubectl,版本为 1.2 或更高。 Metrics 服务器 需要被部署到集群中,以便通过 Metrics API 提供度量数据。 Horizontal Pod Autoscaler 根据此 API 来获取度量数据。 要了解如何部署 metrics-server,请参考 metrics-server 文档 。
如果需要为 Horizontal Pod Autoscaler 指定多种资源度量指标,你的 Kubernetes 集群以及 kubectl 至少需要达到 1.6 版本。 此外,如果要使用自定义度量指标,你的 Kubernetes 集群还必须能够与提供这些自定义指标 的 API 服务器通信。 最后,如果要使用与 Kubernetes 对象无关的度量指标,则 Kubernetes 集群版本至少需要 达到 1.10 版本,同样,需要保证集群能够与提供这些外部指标的 API 服务器通信。 更多详细信息,请参阅 Horizontal Pod Autoscaler 用户指南。
运行 php-apache 服务器并暴露服务
为了演示 Horizontal Pod Autoscaler,我们将使用一个基于 php-apache 镜像的 定制 Docker 镜像。Dockerfile 内容如下:
FROM php:5-apache
COPY index.php /var/www/html/index.php
RUN chmod a+rx index.php
该文件定义了一个 index.php 页面来执行一些 CPU 密集型计算:
<?php
$x = 0.0001;
for ($i = 0; $i <= 1000000; $i++) {
$x += sqrt($x);
}
echo "OK!";
?>
首先,我们使用下面的配置启动一个 Deployment 来运行这个镜像并暴露一个服务:
apiVersion: apps/v1
kind: Deployment
metadata:
name: php-apache
spec:
selector:
matchLabels:
run: php-apache
replicas: 1
template:
metadata:
labels:
run: php-apache
spec:
containers:
- name: php-apache
image: k8s.gcr.io/hpa-example
ports:
- containerPort: 80
resources:
limits:
cpu: 500m
requests:
cpu: 200m
---
apiVersion: v1
kind: Service
metadata:
name: php-apache
labels:
run: php-apache
spec:
ports:
- port: 80
selector:
run: php-apache
运行下面的命令:
kubectl apply -f https://k8s.io/examples/application/php-apache.yaml
deployment.apps/php-apache created
service/php-apache created
创建 Horizontal Pod Autoscaler
现在,php-apache 服务器已经运行,我们将通过 kubectl autoscale 命令创建 Horizontal Pod Autoscaler。 以下命令将创建一个 Horizontal Pod Autoscaler 用于控制我们上一步骤中创建的 Deployment,使 Pod 的副本数量维持在 1 到 10 之间。 大致来说,HPA 将(通过 Deployment)增加或者减少 Pod 副本的数量以保持所有 Pod 的平均 CPU 利用率在 50% 左右。由于每个 Pod 请求 200 毫核的 CPU,这意味着平均 CPU 用量为 100 毫核。 算法的详情请参阅相关文档。
kubectl autoscale deployment php-apache --cpu-percent=50 --min=1 --max=10
horizontalpodautoscaler.autoscaling/php-apache autoscaled
我们可以通过以下命令查看 Autoscaler 的状态:
kubectl get hpa
NAME REFERENCE TARGET MINPODS MAXPODS REPLICAS AGE
php-apache Deployment/php-apache/scale 0% / 50% 1 10 1 18s
请注意当前的 CPU 利用率是 0%,这是由于我们尚未发送任何请求到服务器
(CURRENT
列显示了相应 Deployment 所控制的所有 Pod 的平均 CPU 利用率)。
增加负载
现在,我们将看到 Autoscaler 如何对增加负载作出反应。 我们将启动一个容器,并通过一个循环向 php-apache 服务器发送无限的查询请求 (请在另一个终端中运行以下命令):
kubectl run -i --tty load-generator --rm --image=busybox --restart=Never -- /bin/sh -c "while sleep 0.01; do wget -q -O- http://php-apache; done"
一分钟时间左右之后,通过以下命令,我们可以看到 CPU 负载升高了:
kubectl get hpa
NAME REFERENCE TARGET MINPODS MAXPODS REPLICAS AGE
php-apache Deployment/php-apache/scale 305% / 50% 1 10 1 3m
这时,由于请求增多,CPU 利用率已经升至请求值的 305%。 可以看到,Deployment 的副本数量已经增长到了 7:
kubectl get deployment php-apache
NAME READY UP-TO-DATE AVAILABLE AGE
php-apache 7/7 7 7 19m
停止负载
我们将通过停止负载来结束我们的示例。
在我们创建 busybox 容器的终端中,输入<Ctrl> + C
来终止负载的产生。
然后我们可以再次检查负载状态(等待几分钟时间):
kubectl get hpa
NAME REFERENCE TARGET MINPODS MAXPODS REPLICAS AGE
php-apache Deployment/php-apache/scale 0% / 50% 1 10 1 11m
kubectl get deployment php-apache
NAME READY UP-TO-DATE AVAILABLE AGE
php-apache 1/1 1 1 27m
这时,CPU 利用率已经降到 0,所以 HPA 将自动缩减副本数量至 1。
基于多项度量指标和自定义度量指标自动扩缩
利用 autoscaling/v2beta2
API 版本,你可以在自动扩缩 php-apache 这个
Deployment 时使用其他度量指标。
首先,将 HorizontalPodAutoscaler 的 YAML 文件改为 autoscaling/v2beta2
格式:
kubectl get hpa php-apache -o yaml > /tmp/hpa-v2.yaml
在编辑器中打开 /tmp/hpa-v2.yaml
:
apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
name: php-apache
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: php-apache
minReplicas: 1
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50
status:
observedGeneration: 1
lastScaleTime: <some-time>
currentReplicas: 1
desiredReplicas: 1
currentMetrics:
- type: Resource
resource:
name: cpu
current:
averageUtilization: 0
averageValue: 0
需要注意的是,targetCPUUtilizationPercentage
字段已经被名为 metrics
的数组所取代。
CPU 利用率这个度量指标是一个 resource metric(资源度量指标),因为它表示容器上指定资源的百分比。
除 CPU 外,你还可以指定其他资源度量指标。默认情况下,目前唯一支持的其他资源度量指标为内存。
只要 metrics.k8s.io
API 存在,这些资源度量指标就是可用的,并且他们不会在不同的 Kubernetes 集群中改变名称。
你还可以指定资源度量指标使用绝对数值,而不是百分比,你需要将 target.type
从
Utilization
替换成 AverageValue
,同时设置 target.averageValue
而非 target.averageUtilization
的值。
还有两种其他类型的度量指标,他们被认为是 custom metrics(自定义度量指标): 即 Pod 度量指标和 Object 度量指标。 这些度量指标可能具有特定于集群的名称,并且需要更高级的集群监控设置。
第一种可选的度量指标类型是 Pod 度量指标。这些指标从某一方面描述了 Pod,
在不同 Pod 之间进行平均,并通过与一个目标值比对来确定副本的数量。
它们的工作方式与资源度量指标非常相像,只是它们仅支持 target
类型为 AverageValue
。
pod 度量指标通过如下代码块定义:
type: Pods
pods:
metric:
name: packets-per-second
target:
type: AverageValue
averageValue: 1k
第二种可选的度量指标类型是对象(Object)度量指标。这些度量指标用于描述
在相同名字空间中的别的对象,而非 Pods。
请注意这些度量指标不一定来自某对象,它们仅用于描述这些对象。
对象度量指标支持的 target
类型包括 Value
和 AverageValue
。
如果是 Value
类型,target
值将直接与 API 返回的度量指标比较,
而对于 AverageValue
类型,API 返回的度量值将按照 Pod 数量拆分,
然后再与 target
值比较。
下面的 YAML 文件展示了一个表示 requests-per-second
的度量指标。
type: Object
object:
metric:
name: requests-per-second
describedObject:
apiVersion: networking.k8s.io/v1
kind: Ingress
name: main-route
target:
type: Value
value: 2k
如果你指定了多个上述类型的度量指标,HorizontalPodAutoscaler 将会依次考量各个指标。 HorizontalPodAutoscaler 将会计算每一个指标所提议的副本数量,然后最终选择一个最高值。
比如,如果你的监控系统能够提供网络流量数据,你可以通过 kubectl edit
命令
将上述 Horizontal Pod Autoscaler 的定义更改为:
apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
metadata:
name: php-apache
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: php-apache
minReplicas: 1
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: AverageUtilization
averageUtilization: 50
- type: Pods
pods:
metric:
name: packets-per-second
target:
type: AverageValue
averageValue: 1k
- type: Object
object:
metric:
name: requests-per-second
describedObject:
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
name: main-route
target:
kind: Value
value: 10k
status:
observedGeneration: 1
lastScaleTime: <some-time>
currentReplicas: 1
desiredReplicas: 1
currentMetrics:
- type: Resource
resource:
name: cpu
current:
averageUtilization: 0
averageValue: 0
- type: Object
object:
metric:
name: requests-per-second
describedObject:
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
name: main-route
current:
value: 10k
这样,你的 HorizontalPodAutoscaler 将会尝试确保每个 Pod 的 CPU 利用率在 50% 以内, 每秒能够服务 1000 个数据包请求, 并确保所有在 Ingress 后的 Pod 每秒能够服务的请求总数达到 10000 个。
基于更特别的度量值来扩缩
许多度量流水线允许你通过名称或附加的 标签 来描述度量指标。
对于所有非资源类型度量指标(Pod、Object 和后面将介绍的 External),
可以额外指定一个标签选择算符。例如,如果你希望收集包含 verb
标签的
http_requests
度量指标,可以按如下所示设置度量指标块,使得扩缩操作仅针对
GET 请求执行:
type: Object
object:
metric:
name: `http_requests`
selector: `verb=GET`
这个选择算符使用与 Kubernetes 标签选择算符相同的语法。
如果名称和标签选择算符匹配到多个系列,监测管道会决定如何将多个系列合并成单个值。
选择算符是可以累加的,它不会选择目标以外的对象(类型为 Pods
的目标 Pods 或者
类型为 Object
的目标对象)。
基于与 Kubernetes 对象无关的度量指标执行扩缩
运行在 Kubernetes 上的应用程序可能需要基于与 Kubernetes 集群中的任何对象 没有明显关系的度量指标进行自动扩缩, 例如那些描述与任何 Kubernetes 名字空间中的服务都无直接关联的度量指标。 在 Kubernetes 1.10 及之后版本中,你可以使用外部度量指标(external metrics)。
使用外部度量指标时,需要了解你所使用的监控系统,相关的设置与使用自定义指标时类似。
外部度量指标使得你可以使用你的监控系统的任何指标来自动扩缩你的集群。
你需要在 metric
块中提供 name
和 selector
,同时将类型由 Object
改为 External
。
如果 metricSelector
匹配到多个度量指标,HorizontalPodAutoscaler 将会把它们加和。
外部度量指标同时支持 Value
和 AverageValue
类型,这与 Object
类型的度量指标相同。
例如,如果你的应用程序处理来自主机上消息队列的任务, 为了让每 30 个任务有 1 个工作者实例,你可以将下面的内容添加到 HorizontalPodAutoscaler 的配置中。
- type: External
external:
metric:
name: queue_messages_ready
selector:
matchLabels:
queue: "worker_tasks"
target:
type: AverageValue
averageValue: 30
如果可能,还是推荐定制度量指标而不是外部度量指标,因为这便于让系统管理员加固定制度量指标 API。 而外部度量指标 API 可以允许访问所有的度量指标。 当暴露这些服务时,系统管理员需要仔细考虑这个问题。
附录:Horizontal Pod Autoscaler 状态条件
使用 autoscaling/v2beta2
格式的 HorizontalPodAutoscaler 时,你将可以看到
Kubernetes 为 HorizongtalPodAutoscaler 设置的状态条件(Status Conditions)。
这些状态条件可以显示当前 HorizontalPodAutoscaler 是否能够执行扩缩以及是否受到一定的限制。
status.conditions
字段展示了这些状态条件。
可以通过 kubectl describe hpa
命令查看当前影响 HorizontalPodAutoscaler
的各种状态条件信息:
kubectl describe hpa cm-test
Name: cm-test
Namespace: prom
Labels: <none>
Annotations: <none>
CreationTimestamp: Fri, 16 Jun 2017 18:09:22 +0000
Reference: ReplicationController/cm-test
Metrics: ( current / target )
"http_requests" on pods: 66m / 500m
Min replicas: 1
Max replicas: 4
ReplicationController pods: 1 current / 1 desired
Conditions:
Type Status Reason Message
---- ------ ------ -------
AbleToScale True ReadyForNewScale the last scale time was sufficiently old as to warrant a new scale
ScalingActive True ValidMetricFound the HPA was able to successfully calculate a replica count from pods metric http_requests
ScalingLimited False DesiredWithinRange the desired replica count is within the acceptable range
Events:
对于上面展示的这个 HorizontalPodAutoscaler,我们可以看出有若干状态条件处于健康状态。
首先,AbleToScale
表明 HPA 是否可以获取和更新扩缩信息,以及是否存在阻止扩缩的各种回退条件。
其次,ScalingActive
表明 HPA 是否被启用(即目标的副本数量不为零) 以及是否能够完成扩缩计算。
当这一状态为 False
时,通常表明获取度量指标存在问题。
最后一个条件 ScalingLimitted
表明所需扩缩的值被 HorizontalPodAutoscaler
所定义的最大或者最小值所限制(即已经达到最大或者最小扩缩值)。
这通常表明你可能需要调整 HorizontalPodAutoscaler 所定义的最大或者最小副本数量的限制了。
附录:量纲
HorizontalPodAutoscaler 和 度量指标 API 中的所有的度量指标使用 Kubernetes 中称为
量纲(Quantity)
的特殊整数表示。
例如,数量 10500m
用十进制表示为 10.5
。
如果可能的话,度量指标 API 将返回没有后缀的整数,否则返回以千分单位的数量。
这意味着你可能会看到你的度量指标在 1
和 1500m
(也就是在十进制记数法中的 1
和 1.5
)之间波动。
附录:其他可能的情况
以声明式方式创建 Autoscaler
除了使用 kubectl autoscale
命令,也可以文件创建 HorizontalPodAutoscaler:
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
name: php-apache
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: php-apache
minReplicas: 1
maxReplicas: 10
targetCPUUtilizationPercentage: 50
使用如下命令创建 autoscaler:
kubectl create -f https://k8s.io/examples/application/hpa/php-apache.yaml
horizontalpodautoscaler.autoscaling/php-apache created
8 - 为应用程序设置干扰预算(Disruption Budget)
Kubernetes v1.21 [stable]
本文展示如何限制应用程序的并发干扰数量,在允许集群管理员管理集群节点的同时保证高可用。
准备开始
您的 Kubernetes 服务器版本必须不低于版本 v1.21. 要获知版本信息,请输入kubectl version
.
- 你是 Kubernetes 集群中某应用的所有者,该应用有高可用要求。
- 你应了解如何部署无状态应用 和/或有状态应用。
- 你应当已经阅读过关于 Pod 干扰 的文档。
- 用户应当与集群所有者或服务提供者确认其遵从 Pod 干扰预算(Pod Disruption Budgets)的规则。
用 PodDisruptionBudget 来保护应用
- 确定想要使用 PodDisruptionBudget (PDB) 来保护的应用。
- 考虑应用对干扰的反应。
- 以 YAML 文件形式定义 PDB 。
- 通过 YAML 文件创建 PDB 对象。
确定要保护的应用
用户想要保护通过内置的 Kubernetes 控制器指定的应用,这是最常见的使用场景:
- Deployment
- ReplicationController
- ReplicaSet
- StatefulSet
在这种情况下,在控制器的 .spec.selector
字段中做记录,并在 PDB 的
.spec.selector
字段中加入同样的选择算符。
从 1.15 版本开始,PDB 支持启用 scale 子资源 的自定义控制器。
用户也可以用 PDB 来保护不受上述控制器控制的 Pod,或任意的 Pod 集合,但是正如 任意控制器和选择算符中描述的,这里存在一些限制。
考虑应用对干扰的反应
确定在自发干扰时,多少实例可以在短时间内同时关闭。
- 无状态的前端:
- 关注:不能降低服务能力 10% 以上。
- 解决方案:例如,使用 PDB,指定其 minAvailable 值为 90%。
- 关注:不能降低服务能力 10% 以上。
- 单实例有状态应用:
- 关注:不要在不通知的情况下终止该应用。
- 可能的解决方案 1:不使用 PDB,并忍受偶尔的停机。
- 可能的解决方案 2:设置 maxUnavailable=0 的 PDB。 意为(Kubernetes 范畴之外的)集群操作人员需要在终止应用前与用户协商, 协商后准备停机,然后删除 PDB 表示准备接受干扰,后续再重新创建。
- 关注:不要在不通知的情况下终止该应用。
- 多实例有状态应用,如 Consul、ZooKeeper 或 etcd:
- 关注:不要将实例数量减少至低于仲裁规模,否则将出现写入失败。
- 可能的解决方案 1:设置 maxUnavailable 值为 1 (适用于不同规模的应用)。
- 可能的解决方案 2:设置 minAvailable 值为仲裁规模(例如规模为 5 时设置为 3)。 (允许同时出现更多的干扰)。
- 关注:不要将实例数量减少至低于仲裁规模,否则将出现写入失败。
- 可重新启动的批处理任务:
- 关注:自发干扰的情况下,需要确保任务完成。
- 可能的解决方案:不创建 PDB。 任务控制器会创建一个替换 Pod。
- 关注:自发干扰的情况下,需要确保任务完成。
指定百分比时的舍入逻辑
minAvailable
或 maxUnavailable
的值可以表示为整数或百分比。
- 指定整数值时,它表示 Pod 个数。例如,如果将 minAvailable 设置为 10, 那么即使在干扰期间,也必须始终有 10 个Pod可用。
- 通过将值设置为百分比的字符串表示形式(例如 “50%”)来指定百分比时,它表示占总 Pod 数的百分比。 例如,如果将 "minUnavailable" 设置为 “50%”,则干扰期间只允许 50% 的 Pod 不可用。
如果将值指定为百分比,则可能无法映射到确切数量的 Pod。例如,如果你有 7 个 Pod,
并且你将 minAvailable
设置为 "50%"
,具体是 3 个 Pod 或 4 个 Pod 必须可用
并非显而易见。
Kubernetes 采用向上取整到最接近的整数的办法,因此在这种情况下,必须有 4 个 Pod。
你可以检查控制此行为的
代码。
指定 PodDisruptionBudget
一个 PodDisruptionBudget
有 3 个字段:
- 标签选择算符
.spec.selector
用于指定其所作用的 Pod 集合,该字段为必需字段。 .spec.minAvailable
表示驱逐后仍须保证可用的 Pod 数量。即使因此影响到 Pod 驱逐 (即该条件在和 Pod 驱逐发生冲突时优先保证)。minAvailable
值可以是绝对值,也可以是百分比。.spec.maxUnavailable
(Kubernetes 1.7 及更高的版本中可用)表示驱逐后允许不可用的 Pod 的最大数量。其值可以是绝对值或是百分比。
policy/v1beta1
和 policy/v1
API 中 PodDisruptionBudget 的空选择算符的行为
略有不同。在 policy/v1beta1
中,空的选择算符不会匹配任何 Pods,而
policy/v1
中,空的选择算符会匹配名字空间中所有 Pods。
用户在同一个 PodDisruptionBudget
中只能够指定 maxUnavailable
和 minAvailable
中的一个。
maxUnavailable
只能够用于控制存在相应控制器的 Pod 的驱逐(即不受控制器控制的 Pod 不在
maxUnavailable
控制范围内)。在下面的示例中,
“所需副本” 指的是相应控制器的 scale
,控制器对 PodDisruptionBudget
所选择的 Pod 进行管理。
示例 1:设置 minAvailable
值为 5 的情况下,驱逐时需保证 PodDisruptionBudget 的 selector
选中的 Pod 中 5 个或 5 个以上处于健康状态。
示例 2:设置 minAvailable
值为 30% 的情况下,驱逐时需保证 Pod 所需副本的至少 30% 处于健康状态。
示例 3:设置 maxUnavailable
值为 5 的情况下,驱逐时需保证所需副本中最多 5 个处于不可用状态。
示例 4:设置 maxUnavailable
值为 30% 的情况下,驱逐时需保证所需副本中最多 30% 处于不可用状态。
在典型用法中,干扰预算会被用于一个控制器管理的一组 Pod 中 —— 例如:一个 ReplicaSet 或 StatefulSet 中的 Pod。
设置 maxUnavailable
值为 0%(或 0)或设置 minAvailable
值为 100%(或等于副本数)
可能会阻塞节点,导致资源耗尽。按照 PodDisruptionBudget
的语义,这是允许的。
用户可以在下面看到 pod 干扰预算定义的示例,它们与带有 app: zookeeper
标签的 pod 相匹配:
使用 minAvailable 的PDB 示例:
使用 maxUnavailable 的 PDB 示例:
例如,如果上述 zk-pdb
选择的是一个规格为 3 的 StatefulSet 对应的 Pod,
那么上面两种规范的含义完全相同。
推荐使用 maxUnavailable
,因为它自动响应控制器副本数量的变化。
创建 PDB 对象
你可以使用 kubectl 创建或更新 PDB 对象。
kubectl apply -f mypdb.yaml
PDB 对象无法更新,必须删除后重新创建。
检查 PDB 的状态
使用 kubectl 来确认 PDB 被创建。
假设用户的名字空间下没有匹配 app: zookeeper
的 Pod,用户会看到类似下面的信息:
kubectl get poddisruptionbudgets
NAME MIN AVAILABLE MAX UNAVAILABLE ALLOWED DISRUPTIONS AGE
zk-pdb 2 N/A 0 7s
假设有匹配的 Pod (比如说 3 个), 那么用户会看到类似下面的信息:
kubectl get poddisruptionbudgets
NAME MIN AVAILABLE MAX UNAVAILABLE ALLOWED DISRUPTIONS AGE
zk-pdb 2 N/A 1 7s
ALLOWED-DISRUPTIONS
值非 0 意味着干扰控制器已经感知到相应的 Pod,对匹配的 Pod 进行统计,
并更新了 PDB 的状态。
用户可以通过以下命令获取更多 PDB 状态相关信息:
kubectl get poddisruptionbudgets zk-pdb -o yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
anntation: {}
creationTimestamp: "2020-03-04T04:22:56Z"
generation: 1
name: zk-pdb
…
status:
currentHealthy: 3
desiredHealthy: 2
disruptionsAllowed: 1
expectedPods: 3
observedGeneration: 1
任意控制器和选择算符
如果你只使用与内置的应用控制器(Deployment、ReplicationController、ReplicaSet 和 StatefulSet) 对应的 PDB,也就是 PDB 的选择算符与 控制器的选择算符相匹配,那么可以跳过这一节。
你可以使用这样的 PDB:它对应的 Pod 可能由其他类型的控制器控制,可能由 "operator" 控制, 也可能为“裸的(不受控制器控制)” Pod,但该类 PDB 存在以下限制:
- 只能够使用
.spec.minAvailable
,而不能够使用.spec.maxUnavailable。
- 只能够使用整数作为
.spec.minAvailable
的值,而不能使用百分比。
你可以令选择算符选择一个内置控制器所控制 Pod 的子集或父集。 然而,当名字空间下存在多个 PDB 时,用户必须小心,保证 PDB 的选择算符之间不重叠。
9 - 从 Pod 中访问 Kubernetes API
本指南演示了如何从 Pod 中访问 Kubernetes API。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。 建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
从 Pod 中访问 API
从 Pod 内部访问 API 时,定位 API 服务器和向服务器认证身份的操作 与外部客户端场景不同。
从 Pod 使用 Kubernetes API 的最简单的方法就是使用官方的 客户端库。 这些库可以自动发现 API 服务器并进行身份验证。
使用官方客户端库
从一个 Pod 内部连接到 Kubernetes API 的推荐方式为:
-
对于 Go 语言客户端,使用官方的 Go 客户端库。 函数
rest.InClusterConfig()
自动处理 API 主机发现和身份认证。 参见这里的一个例子。 -
对于 Python 客户端,使用官方的 Python 客户端库。 函数
config.load_incluster_config()
自动处理 API 主机的发现和身份认证。 参见这里的一个例子。 -
还有一些其他可用的客户端库,请参阅客户端库页面。
在以上场景中,客户端库都使用 Pod 的服务账号凭据来与 API 服务器安全地通信。
直接访问 REST API
在运行在 Pod 中时,可以通过 default
命名空间中的名为 kubernetes
的服务访问
Kubernetes API 服务器。也就是说,Pod 可以使用 kubernetes.default.svc
主机名
来查询 API 服务器。官方客户端库自动完成这个工作。
向 API 服务器进行身份认证的推荐做法是使用
服务账号凭据。
默认情况下,每个 Pod 与一个服务账号关联,该服务账户的凭证(令牌)放置在此 Pod 中
每个容器的文件系统树中的 /var/run/secrets/kubernetes.io/serviceaccount/token
处。
如果证书包可用,则凭证包被放入每个容器的文件系统树中的
/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
处,
且将被用于验证 API 服务器的服务证书。
最后,用于命名空间域 API 操作的默认命名空间放置在每个容器中的
/var/run/secrets/kubernetes.io/serviceaccount/namespace
文件中。
使用 kubectl proxy
如果你希望不使用官方客户端库就完成 API 查询,可以将 kubectl proxy
作为
command
在 Pod 中启动一个边车(Sidecar)容器。这样,kubectl proxy
自动完成对 API
的身份认证,并将其暴露到 Pod 的 localhost
接口,从而 Pod 中的其他容器可以
直接使用 API。
不使用代理
通过将认证令牌直接发送到 API 服务器,也可以避免运行 kubectl proxy 命令。 内部的证书机制能够为链接提供保护。
# 指向内部 API 服务器的主机名
APISERVER=https://kubernetes.default.svc
# 服务账号令牌的路径
SERVICEACCOUNT=/var/run/secrets/kubernetes.io/serviceaccount
# 读取 Pod 的名字空间
NAMESPACE=$(cat ${SERVICEACCOUNT}/namespace)
# 读取服务账号的持有者令牌
TOKEN=$(cat ${SERVICEACCOUNT}/token)
# 引用内部证书机构(CA)
CACERT=${SERVICEACCOUNT}/ca.crt
# 使用令牌访问 API
curl --cacert ${CACERT} --header "Authorization: Bearer ${TOKEN}" -X GET ${APISERVER}/api
输出类似于:
{
"kind": "APIVersions",
"versions": [
"v1"
],
"serverAddressByClientCIDRs": [
{
"clientCIDR": "0.0.0.0/0",
"serverAddress": "10.0.1.149:443"
}
]
}
10 - 扩缩 StatefulSet
本文介绍如何扩缩StatefulSet。StatefulSet 的扩缩指的是增加或者减少副本个数。
准备开始
-
StatefulSets 仅适用于 Kubernetes 1.5 及以上版本。
-
不是所有 Stateful 应用都能很好地执行扩缩操作。 如果你不是很确定是否要扩缩你的 StatefulSet,可先参阅 StatefulSet 概念 或者 StatefulSet 教程。
-
仅当你确定你的有状态应用的集群是完全健康的,才可执行扩缩操作.
扩缩 StatefulSet
使用 kubectl
扩缩 StatefulSet
首先,找到你要扩缩的 StatefulSet。
kubectl get statefulsets <statefulset 名称>
更改 StatefulSet 中副本个数:
kubectl scale statefulsets <statefulset 名称> --replicas=<新的副本数>
对 StatefulSet 执行就地更新
另外, 你可以就地更新 StatefulSet。
如果你的 StatefulSet 最初通过 kubectl apply
或 kubectl create --save-config
创建,
你可以更新 StatefulSet 清单中的 .spec.replicas
, 然后执行命令 kubectl apply
:
kubectl apply -f <更新后的 statefulset 文件>
否则,可以使用 kubectl edit
编辑副本字段:
kubectl edit statefulsets <statefulset 名称>
或者使用 kubectl patch
:
kubectl patch statefulsets <statefulset 名称> -p '{"spec":{"replicas":<new-replicas>}}'
故障排查
缩容操作无法正常工作
当 Stateful 所管理的任何 Pod 不健康时,你不能对该 StatefulSet 执行缩容操作。 仅当 StatefulSet 的所有 Pod 都处于运行状态和 Ready 状况后才可缩容.
如果 spec.replicas
大于 1,Kubernetes 无法判定 Pod 不健康的原因。
Pod 不健康可能是由于永久性故障造成也可能是瞬态故障。
瞬态故障可能是节点升级或维护而引起的节点重启造成的。
如果该 Pod 不健康是由于永久性故障导致, 则在不纠正该故障的情况下进行缩容可能会导致 StatefulSet 进入一种状态,其成员 Pod 数量低于应正常运行的副本数。 这种状态也许会导致 StatefulSet 不可用。
如果由于瞬态故障而导致 Pod 不健康并且 Pod 可能再次变为可用,那么瞬态错误可能会干扰 你对 StatefulSet 的扩容/缩容操作。 一些分布式数据库在同时有节点加入和离开时 会遇到问题。在这些情况下,最好是在应用级别进行分析扩缩操作的状态, 并且只有在确保 Stateful 应用的集群是完全健康时才执行扩缩操作。
接下来
- 进一步了解删除 StatefulSet