Kubernets - 工作负载
k8s 的工作负载、各种控制器
Workload (工作负载)
只使用 Pod, 将会面临如下需求难以解决
- 业务应用如何启动多个副本
- Pod 重建后 IP 会变化,如何保证依然能通信
- 外部如何访问 Pod 服务
- 运行业务 Pod 的某个节点挂了,如何故障自动转移
- 若需求是收集各节点监控数据,如何将 Pod 运行在 k8s 集群的各个节点上
控制器 Controller
控制器又称 工作负载,是 管理 Pod 的中间层,确保 Pod 资源符合预期的状态
- 资源出现故障时,会尝试 进行重启
- 当根据重启策略无效,则会重新创建 Pod 资源
简要预览如下控制器
ReplicaSet: 代用户创建指定数量的 Pod 副本 数量,确保副本数量符合预期状态,并且 支持滚动式自动扩容和缩容功能
Deployment: 工作在 ReplicaSet 之上,用于 管理无状态应用,目前来说 最好 的控制器,支持滚动更新和回滚 功能,还提供声明式配置
DaemonSet:用于确保集群中,每一个节点只运行特定的 Pod 副本(通常也就一个),通常用于实现系统级后台任务,比如ELK 日志服务、监控服务
Job: 只要完成就立即退出,不需要重启或重建
Cronjob: 周期性任务 控制,不需要持续后台运行
StatefulSet: 管理 有状态应用
1. Deployments(无状态)
Deployments 是管理 Pod 的控制器,用于 管理无状态应用,工作在 ReplicaSet 之上,之前 Pod 的配置,会指定在 template:
之下
中间件容器:
apiVersion: apps/v1
kind: Deployment # 此时为控制器 Deployment
metadata:
name: mysql
namespace: uit
labels:
component: zz
spec:
replicas: 1 # 指定 Pod 副本数
selector: # 指定 Pod 选择器
matchLabels: # 必定与下文 labels 对应
app: mysql
template:
metadata:
labels: # 给 Pod 打 label,与上文 matchLabels: 对应
app: mysql
spec:
hostNetwork: true # 声明 pod 的网络模式为host模式,效果同 docker run --net=host
volumes:
- name: mysql-data
hostPath:
path: /opt/mysql/data
nodeSelector: # 使用节点选择器将 Pod 调度到指定 label 的节点
component: zz
containers:
- name: mysql
image: 192.168.3.171:5000/mysql:5.7
imagePullPolicy: IfNotPresent
ports:
- containerPort: 3306
env:
- name: MYSQL_PASSWD
valueFrom:
secretKeyRef:
name: ublog
key: MYSQL_PASSWD
- name: MYSQL_DATABASE
value: "myblog"
resources:
requests:
memory: 100Mi
cpu: 50m
limits:
memory: 500Mi
cpu: 100m
readinessProbe:
tcpSocket:
port: 3306
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
tcpSocket:
port: 3306
initialDelaySeconds: 5
periodSeconds: 20
volumeMounts:
- name: mysql-data
mountPath: /var/lib/mysql
控制器通过 label 可以找到对应的 Pod ,此时可通过 label 进行过滤
$ kubectl -n uit get po -l component=zz
业务容器:(改造同上)
apiVersion: apps/v1
kind: Deployment
metadata:
name: ublog
namespace: uit
spec:
replicas: 1
selector:
matchLabels:
app: myblog
template:
metadata:
labels:
app: myblog
spec:
containers:
- name: myblog
image: 192.168.3.171:5000/myblog:v1
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8002
env:
- name: MYSQL_HOST
valueFrom: # 同上 映射配置 configmap.txt 指定的环境变量
configMapKeyRef:
name: ublog
key: MYSQL_HOST
- name: MYSQL_PASSWD
valueFrom:
secretKeyRef:
name: ublog
key: MYSQL_PASSWD
resources:
requests:
memory: 100Mi
cpu: 50m
limits:
memory: 500Mi
cpu: 100m
livenessProbe:
httpGet:
path: /blog/index/
port: 8002
scheme: HTTP
initialDelaySeconds: 10 # 容器启动后第一次执行探测是需要等待多少秒
periodSeconds: 15 # 执行探测的频率
timeoutSeconds: 2 # 探测超时时间
readinessProbe:
httpGet:
path: /blog/index/
port: 8002
scheme: HTTP
initialDelaySeconds: 10
timeoutSeconds: 2
periodSeconds: 15
操作
Deployment 创建、查看
# 创建
$ kubectl create -f deployxx.yaml
# 查看
$ kubectl -n uit get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
mysql 1/1 1 1 11m
ublog 1/1 1 1 10m
* `NAME` # 列出了集群中 Deployments 的名称
* `READY` # 显示当前 正在运行的/期望的 副本数
* `UP-TO-DATE` # 显示已更新 以实现期望状态的 副本数
* `AVAILABLE` # 显示应用程序 可供用户使用的 副本数
* `AGE` # 显示应用程序运行的 时间量
# 查看 replicasets 或是 rs 简写
$ kubectl -n uit get replicasets
NAME DESIRED CURRENT READY AGE
mysql-6fbb5cc967 1 1 1 10m
ublog-5ff678657f 1 1 1 10m
# 查看 pod
kubectl -n uit get po
NAME READY STATUS RESTARTS AGE
mysql-6fbb5cc967-48dfd 1/1 Running 0 12m
ublog-5ff678657f-m42hz 1/1 Running 0 11m
"此时会发现 Pod 的名字是 deployment -> replicasets -> pod 一步步根据 被管理者名称 拼接的"
Deployment 更新,有如下三种方式
#修改yaml文件,使用 apply 应用更新
$ kubectl -n uit apply -f ublog.yaml
# 通过命令更新,太长太难记
$ kubectl set image deploy ublog ublog=192.168.3.171:5000/myblog:v2 --record
# 推荐:直接在线编辑
$ kubectl -n uit edit deploy ublog
2. ReplicaSet(副本保障机制)
前身是 Replication Controller(已废弃) ,现配合 Deployment 自动管理,和之前的唯一区别是 支持标签选择器
# 修改 yaml 文件的副本数
...
replicas: 2 # 修改为2,然后执行 apply 即可更新
...
将上文的 deployment 的副本数量修改为 2,然后执行 apply
即可更新
$ kubectl apply -f mysql.yaml
# 查看 Pod,此时发现新的副本正在创建中...但由于调度指定,且端口被占了,中间件是不会创建成功的
kubectl -n uit get po
NAME READY STATUS RESTARTS AGE
mysql-6fbb5cc967-48dfd 1/1 Running 0 23m
mysql-6fbb5cc967-rdlrb 0/1 Pending 0 58s
ublog-5ff678657f-m42hz 1/1 Running 0 22m
当然,还可以 通过命令 修改副本数量
# 通过命令设置 3个副本
$ kubectl -n uit scale deploy mysql --replicas=3
deployment.apps/mysql scaled
# 观察 Pod, 此时发现又有一个新的副本正在创建中...
$ kubectl -n uit get po
NAME READY STATUS RESTARTS AGE
mysql-6fbb5cc967-48dfd 1/1 Running 0 25m
mysql-6fbb5cc967-957gj 0/1 Pending 0 8s
mysql-6fbb5cc967-rdlrb 0/1 Pending 0 3m27s
ublog-5ff678657f-m42hz 1/1 Running 0 25m
其他方式
当然,还可通过 kubectl -n uit edit deploy mysql
的方式,但最好还是上文改 yaml 文件然后 apply
的方式,有迹可循
ReplicaSet 高可用
ReplicaSet 会实时检测 Pod 状态,并保障副本数一直处于期望的值,此时是无法通过普通的指定来删除 Pod 的,由于要保证预期副本数,执行删除,也会被 ReplicaSet 自动拉起
# 删除 Pod,观察 Pod 状态变化
$ kubectl -n uit delete pod ublog-5ff678657f-m42hz
# 发现 旧副本执行删除,并有新副本创建
$ kubectl -n uit get pod
NAME READY STATUS RESTARTS AGE
mysql-6fbb5cc967-48dfd 1/1 Running 0 34m
ublog-5ff678657f-8xcp7 0/1 ContainerCreating 0 1s
ublog-5ff678657f-nbs77 1/1 Terminating 0 82s
# 然后 新副本成功运行,继续删除旧副本
mysql-6fbb5cc967-48dfd 1/1 Running 0 35m
ublog-5ff678657f-8xcp7 1/1 Running 0 39s
ublog-5ff678657f-nbs77 0/1 Terminating 0 2m
# 最后 恢复如初
mysql-6fbb5cc967-48dfd 1/1 Running 0 35m
ublog-5ff678657f-8xcp7 1/1 Running 0 41s
2.1 Replicaset 服务更新
修改 Dockerfile,重新打 tag 可以模拟服务更新(v1 -> v2)
# 修改前端的显示文案
$ vim ./python-demo/blog/templates/index.html
# 重新 build 镜像,但是指定 tag 未生效,只有镜像ID
$ docker build . -t 192.168.3.171:5000/myblog:v2 -f Dockerfile
# 手动打 tag 并上传
$ docker tag 728e50e60021 192.168.3.171:5000/myblog:v2
$ docker push 192.168.3.171:5000/myblog:v2
此时副本数为 3 的情况下,修改镜像版本
# 查看 Pod,发现 新版本镜像正在 滚动更新
kubectl -nuit get po -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
mysql-6fbb5cc967-48dfd 1/1 Running 0 11h 192.168.3.172 k8s-slave-172 <none> <none>
ublog-5f55c5568b-2ggd7 1/1 Running 0 48s 10.244.1.27 k8s-slave-172 <none> <none>
ublog-5f55c5568b-jstms 1/1 Running 0 24s 10.244.2.24 k8s-slave-173 <none> <none>
ublog-5f55c5568b-mgn96 0/1 ContainerCreating 0 1s <none> k8s-slave-172 <none> <none>
ublog-5ff678657f-5k2d7 1/1 Terminating 0 2m35s 10.244.1.26 k8s-slave-172 <none> <none>
ublog-5ff678657f-8xcp7 1/1 Running 0 10h 10.244.2.22 k8s-slave-173 <none> <none>
ublog-5ff678657f-d8fkk 1/1 Terminating 0 2m35s 10.244.2.23 k8s-slave-173 <none> <none>
更新策略
也可通过执行如下命令查看到
$ kubectl -nuit get deploy ublog -oyaml|grep -n7 strategy
...
spec:
progressDeadlineSeconds: 600
replicas: 3 # 上文修改 Pod 副本数
revisionHistoryLimit: 10 # 保留的历史版本
selector: # 上文指定的 Pod 选择器
matchLabels:
app: myblog
strategy: # 此时发现会有如下默认配置项
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate # 指定更新方式为 滚动更新,默认策略
template:
metadata:
creationTimestamp: null
labels:
app: myblog
...
策略控制
maxSurge: 最大激增(存活)数,指更新过程中,最多可比 replicas (预设副本数)多出的 Pod 数量
- 可为 固定值 或 百分比,默认为 desired Pods 数的 25%,
- 计算时 向上取整 (比如 3.2,取 4),更新过程中最多会有 replicas + maxSurge 个 Pod
maxUnavailable: 最大不可用数,指更新过程中,最多有几个 Pod 处于无法服务状态 ,
- 可以为 固定值 或 百分比,默认为 desired Pods 数的 25%
- 计算时 向下取整 (比如 2.8,取 2)
Deployment rollingUpdate 时,需要
保证 Running 活着的(非异常状态的) Pods 数不超过 desired pods number + maxSurge
保证 Available(Ready状态的) Pods 数不低于 desired pods number - maxUnavailable
假设 3 副本的 ublog
Running 状态的 Pod 最多不超过 3 + 1(3 x 25%=0.75) = 4 个
Running 状态的 Pod 最少不低于 3 - 1(3 x 25%=0.75) = 3 个
- 先 新增一个 v2 版本的 Pod,此时 3 个 v1 版本 + 1 个v2 版本的,当前 Pod=4 不能再增加
- 然后 删掉一个 v1 版本的 Pod,此时 2 个 v1 版本 + 1 个v2 版本的,当前 Pod=3 不能再删除
- 滚动重复 1、2 步骤,直到更新结束
# 查看副本rs 发现有两个ublog,一个 3副本,一个 0副本
$ kubectl -n uit get rs
NAME DESIRED CURRENT READY AGE
mysql-6fbb5cc967 1 1 1 2d
ublog-5f55c5568b 3 3 3 37h
ublog-5ff678657f 0 0 0 2d
# 此时,edit 回退到 v1版本,查看滚动更新事件
$ kubectl -n uit describe deploy ublog
...
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing True NewReplicaSetAvailable
OldReplicaSets: <none>
NewReplicaSet: ublog-5ff678657f (3/3 replicas created)
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ScalingReplicaSet 89s (x2 over 2d) deployment-controller Scaled up replica set ublog-5ff678657f to 1
Normal ScalingReplicaSet 66s deployment-controller Scaled down replica set ublog-5f55c5568b to 2
Normal ScalingReplicaSet 66s deployment-controller Scaled up replica set ublog-5ff678657f to 2
Normal ScalingReplicaSet 49s (x2 over 37h) deployment-controller Scaled up replica set ublog-5ff678657f to 3
Normal ScalingReplicaSet 49s deployment-controller Scaled down replica set ublog-5f55c5568b to 1
Normal ScalingReplicaSet 27s deployment-controller Scaled down replica set ublog-5f55c5568b to 0
$ kubectl -n uit get rs
NAME DESIRED CURRENT READY AGE
mysql-6fbb5cc967 1 1 1 2d
ublog-5f55c5568b 0 0 0 37h
ublog-5ff678657f 3 3 3 2d
replicaset 会创建不同的名称的 rs 副本,然后仅调节对应的副本数更新即可,默认会缓存 10 个历史版本数便于回滚
2.2 Replicaset 服务回滚
通过滚动升级策略,可平滑升级 Deployment,但升级出现问题,需要最快、最好的方式回退到上个正常版本,就需要回滚机制
revision:
- 更新应用时,k8s 会记录当前的 revision (版本号),当升级出现问题时,可通过 revision 回滚
- 默认配置下,只保留最近的几个 revision,可通过 Deployment 配置的
spec.revisionHistoryLimit
属性增加数量,默认为10 - 历史记录,记录的是 操作过 replicaset 的 历史
# 查看版本历史记录
$ kubectl -nuit rollout history deploy ublog
deployment.apps/ublog
REVISION CHANGE-CAUSE
2 <none>
3 <none>
# 配置如下,revisionHistoryLimit 为默认 10条记录
$ kubectl -n uit get deploy ublog -oyaml|grep revision
deployment.kubernetes.io/revision: "3"
revisionHistoryLimit: 10
"但查看时不会全部显示完整的10条,而是默认显示最近的 2条记录,所以上文从 REVISION 2 开始"
发现 CHANGE-CAUSE 为 <none> 是由于没有添加记录参数,记录回滚
# 使用命令来修改配置文件的版本 v2 -> v1 -> v2
$ kubectl -nuit set image deploy ublog myblog=192.168.3.171:5000/myblog:v1 --record
deployment.apps/ublog image updated
$ kubectl -nuit set image deploy ublog myblog=192.168.3.171:5000/myblog:v2 --record
deployment.apps/ublog image updated
# 再次查看 版本历史记录,发现已经有了命令显示
$ kubectl -nuit rollout history deploy ublog
deployment.apps/ublog
REVISION CHANGE-CAUSE
5 kubectl set image deploy ublog myblog=192.168.3.171:5000/myblog:v1 --namespace=uit --record=true
6 kubectl set image deploy ublog myblog=192.168.3.171:5000/myblog:v2 --namespace=uit --record=true
删除 Deployment 再重建,版本记录会清空,并从 1 记录
$ kubectl delete -f ublog.yaml
deployment.apps "ublog" deleted
# 将版本记录再改为 v1
$ vim ublog.yaml
# 增加 --record 参数,重新创建
$ kubectl create -f ublog.yaml --record
deployment.apps/ublog created
# 通过命令修改版本
$ kubectl -nuit set image deploy ublog myblog=192.168.3.171:5000/myblog:v2 --record
# 查看历史记录
$ kubectl -nuit rollout history deploy ublog
deployment.apps/ublog
REVISION CHANGE-CAUSE
1 kubectl create --filename=ublog.yaml --record=true
2 kubectl set image deploy ublog myblog=192.168.3.171:5000/myblog:v2 --namespace=uit --record=true
回滚到具体的 revision
# 执行回滚操作
$ kubectl -nuit rollout undo deploy ublog --to-revision=1
deployment.apps/ublog rolled back
# 此时历史记录更新,重复记录了回滚记录
$ kubectl -nuit rollout history deploy ublog
deployment.apps/ublog
REVISION CHANGE-CAUSE
2 kubectl set image deploy ublog myblog=192.168.3.171:5000/myblog:v2 --namespace=uit --record=true
3 kubectl create --filename=ublog.yaml --record=true
# 此时访问应用,查看输出版本,此时又变回 v1
curl 10.244.1.35:8002/blog/index/
后续见
3. StatefulSet
StatefulSet 同上也是管 Pod 的,区别是用来管理 有状态应用 的,且为每个 Pod 维护一个 有粘性、永久不变 的 ID ,常用来弄中间件,需持久化数据的应用
命名
每个 Pod 在重新调度时依然会保留持久的标识符,格式为 StatefulSetName-Number ,如创建名字是 Redis-Sentinel 的 StatefulSet 起 3 个 Pod ,名字通常为 Redis-Sentinel-0 / 1 / 2,一般格式如下
"statefulSetName-{0..N-1}.serviceName.namespace.svc.cluster.local"
- serviceName 是 Headless Service 的名字,必须指定
- 0..N-1 为 Pod 序号
- .cluster.local 是 Cluster Domain(集群域 )
- 同一命名空间下,namespace.svc.cluster.local 可省略
StatefulSet 启动时,只有 当前一个容器完全启动,后一个容器才会被调度,并且每个容器的标识符固定,可以根据标识符来断定当前 Pod 的角色,因此可以配置集群应用
说明
如配置主从 redis,名为 redis-ms,使用 StatefulSet 进行部署
- 启动第一个容器,标识符为 redis-ms-0 ,此时就可以根据 0 来认为它是 Master 节点
- 后面再启动第二个 redis 就可以通过标识符来连接 Master 节点
- 第二个启动后则为 Slave 节点 redis-ms-1
StatefulSet 使用 Headless Service(无头服务 ) 进行通信,即没有 Cluster IP ,而是用 Endpoint 来通信,但 yaml 中必须要指定一个 serviceName 进行绑定,因此通常和 Service 放一起,结构如下
apiVersion: v1
kind: Service
metadata:
name: nignx
namespace: dev
labels:
app: nginx
spce:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
namespace: dev
spec:
serviceName: "nginx"
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spce:
containers:
- name: nginx
image: nginx: 1.15.2
ports:
- containerPort: 80
name: web
3.1 扩容缩容
扩容: 原则同启动,只有 当前一个容器完全启动,后一个容器才会接着启动 若,中途前面的容器异常,后续容器的启动会等待前一个恢复
缩容: 与扩容相反
3.2 更新策略
默认的更新策略为 RollingUpdate,与 Deployment 区别的是,StatefulSet 更新是有顺序的,为 Pod 序号逆序更新,当前面序号的容器异常时,同样会停止更新动作直到正常为止
OnDelete:
4. DaemonSet
DaemonSet 同上也是管 Pod 的,区别是用来管理 有状态应用 的