Controller
由 kube-controller-manager 组件来完成控制,常见的控制器比如 deployment 等,所有控制器存放在 pkg/controller/ 目录下。
$ cd kubernetes/pkg/controller/$ ls -d */deployment/ job/ podautoscaler/cloud/ disruption/ namespace/replicaset/ serviceaccount/ volume/cronjob/ garbagecollector/ nodelifecycle/ replication/ statefulset/ daemon/...
Pod 对象有两个状态:
- 实际状态:来自 kubernetes 本身,比如监控,心跳,节点状态等等;
- 期望状态:由用户提及的 YAML 文件定义;
用 go 语言的伪代码可以表示成下面这个逻辑:
for {
实际状态 := 获取集群中对象X的实际状态(Actual State)
期望状态 := 获取集群中对象X的期望状态(Desired State)
if 实际状态 == 期望状态{
什么都不做
} else {
执行编排动作,将实际状态调整为期望状态
}
}
在实际使用过程中,比如 Deployment 在获取到对象(Pod)的实际状态和期望状态之后,通过调谐(Reconcile)来完成控制。也称作 reconcile loop 或者 sync loop ,其实都是控制循环。
Deployment 对象 Yaml 文件定义如下,分两部分控制器定义和被控制对象。template 字段中的内容和 Pod 对象定义基本一致,所以叫作 PodTemplate。(是否一个控制器可以同时定义多个 Template,除了 Pod 是否还有其他类型的 template?)
在 metadata 中都有一个 ownerReference 的信息,用于保存当前这个 API 对象的拥有者(Owner)的信息
ReplicaSet
ReplicaSet 本身也是一个 API 对象,它被 Deployment 控制器直接控制,然后 ReplicaSet 控制 Pod,所以 Deployment 不直接控制 Pod,Pod 属于 ReplicaSet。帮助 deployment 实现 Pod 的水平扩展/收缩,滚动更新。
正因如此 Deployment 只允许容器的 restartPolicy = Always 的主要原因:只有容器能保证自己始终是 Running 状态的前提下, ReplicaSet 调整 Pod 的个数才有意义。
deployment 创建拉起之后的状态查看:
$ kubectl get deployments
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
nginx-deployment 3 0 0 0 1s
- Desired:用户期望的 Pod 副本个数;
- Current:当前处于 Running 状态的 Pod 的个数;
- UP-TO-Date:当前处于最新版本的 Pod 的个数;
- AVAILABLE: 当前已经可用的 Pod 的个数。需要满足 running 状态,最新版本,处于ready
也可以用 rollout 查看 Deployment 对象的状态变化
$ kubectl rollout status deployment/nginx-deployment
Waiting for rollout to finish: 2 out of 3 new replicas have been updated...
deployment.apps/nginx-deployment successfully rolled out
水平扩展和收缩实现简单,即修改 yaml 文件就行。
滚动更新:常用于版本升级回退的场景下。
举🌰:一个 Deployment 创建 3 个 Pod,会先拉起一个 ReplicaSet(rs1),并运行到终态。当提交一次 Deployment 升级时,比如修改了新的容器版本,就会创建一个新的 ReplicaSet(rs2) 出来。rs2 拉起一个 Pod ,rs1 继续持有 2 个 Pod,只有当 rs2 的这个pod 到终态,且继续拉起一个 Pod 时, rs1 的 pod 数量才会减少。可以通过 describe 查看事件过程。
$ kubectl describe deployment nginx-deployment
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
...
Normal ScalingReplicaSet 24s deployment-controller Scaled up replica set nginx-deployment-1764197365 to 1
Normal ScalingReplicaSet 22s deployment-controller Scaled down replica set nginx-deployment-3167673210 to 2
Normal ScalingReplicaSet 22s deployment-controller Scaled up replica set nginx-deployment-1764197365 to 2
Normal ScalingReplicaSet 19s deployment-controller Scaled down replica set nginx-deployment-3167673210 to 1
Normal ScalingReplicaSet 19s deployment-controller Scaled up replica set nginx-deployment-1764197365 to 3
Normal ScalingReplicaSet 14s deployment-controller Scaled down replica set nginx-deployment-3167673210 to 0
ReplicaSet 的信息查看如下:
- NAME 字段:由 Deployment 的名字 + 一个随机字符串组成; ```shell
$ kubectl get rs NAME DESIRED CURRENT READY AGE nginx-deployment-1764197365 3 3 3 6s nginx-deployment-3167673210 0 0 0 30s
实现滚动更新的策略是 Deployment 对象的一个字段,叫 RollingUpdateStrategy,如下所示:
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
...
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # 指定的是除了 DESIRED 数量之外,在一次滚动中,创建多少个新 Pod
maxUnavailable: 1 # 在一次滚动中,Deployment 控制器可以删除多少个旧 Pod
# 这个字段也可以用百分比来表示

场景操作记录
# 设置 deployment 镜像
$ kubectl set image deployment/nginx-deployment nginx=nginx:1.91
deployment.extensions/nginx-deployment image updated
# 回滚 deployment 此次更新
$ kubectl rollout undo deployment/nginx-deployment
deployment.extensions/nginx-deployment
# 查看滚动更新操作历史
$ kubectl rollout history deployment/nginx-deployment
deployments "nginx-deployment"
REVISION CHANGE-CAUSE
1 kubectl create -f nginx-deployment.yaml --record
2 kubectl edit deployment/nginx-deployment
3 kubectl set image deployment/nginx-deployment nginx=nginx:1.91
# 指定滚动更新的版本
$ kubectl rollout history deployment/nginx-deployment --revision=2
# 如果 deployment 的变动不想引起,滚动更新,引起 ReplicaSet 新增
# 可以先停止 Deployment 控制器,然后修改完成之后再启用;
# 暂停
$ kubectl rollout pause deployment/nginx-deployment
deployment.extensions/nginx-deployment paused
# 恢复
$ kubectl rollout resume deployment/nginx-deployment
deployment.extensions/nginx-deployment resumed
# 通过配置 spec.revisionHistoryLimit 设置 ReplicaSet 保存的数量, 0 表示不保存,你将永远无法回滚
StatefulSet
在介绍 StatefulSet 之前需要提到“有状态应用”和“无状态应用”:
- 有状态应用:指实例对外部数据有依赖关系的应用;
- 无状态应用:指实例对外部无数据依赖关系,处理单次的请求并直接返回结果;
而 StatefulSet 是一种有状态应用,描述了两种状态:
- 拓扑状态:指服务拉起顺序的拓扑关系,比如 A 为主,B 为从,需要先拉起 A ,而拉起 B 时需要从 A 获取所需信息;
- 存储状态:常见于存储实例中,多个存储节点之间的数据状态;
Headless Service
在 K8S 中服务访问的方式有两种:
- 通过 Service 的 VIP 方式,➕ Port 提供服务访问;
- 通过 DNS 来访问,而 DNS 访问又可以分为两种,一种是 Normal Service,DNS 后端解析的是 Service VIP 地址;另一种是 Headless Servcie,不需要分配 VIP ,Cluster IP 为 None;
当创建一个 Headless Service 之后,它所代理的 Pod 的 IP 地址都会绑定到这种格式 DNS 记录下:
<pod-name>.<svc-name>.<namespace>.svc.cluster.local
StatefulSet 在拉起 Pod 时,StatefulSet 给它所管理的所有 Pod 的名字,进行了编号,编号规则是:
所以对于有状态应用实例的访问时,必须使用 DNS 记录或者 hostname 方式,而不应该直接访问这些 Pod 的 IP 地址。
PVC 和 PV
存储状态的实现方式是通过 Persistent Volume Claim (PVC)的功能。正常情况下,如果 Pod 创建需要存储资源,需要声明清除 Pod 所需的存储的详细信息,各类存储信息开发使用者无法准确掌握。这样存在一些问题:
- 信息过于详细,会对外暴露存储过分细致的信息;
- 使用者不方便,不应该让开发或者应用者了解过分详细的存储信息;
所以 Kubernetes 项目引入了一组叫作 Persistent Volume Claim(PVC)和 Persistent Volume(PV)的 API 对象,大大降低了用户声明和使用持久化 Volume 的门槛。
PVC 使用步骤:
# 1.PVC 声明
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: pv-claim
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
# 2.创建 Pod 时,使用声明的 PVC
apiVersion: v1
kind: Pod
metadata:
name: pv-pod
spec:
containers:
- name: pv-container
image: nginx
ports:
- containerPort: 80
name: "http-server"
volumeMounts:
- mountPath: "/usr/share/nginx/html"
name: pv-storage
volumes:
- name: pv-storage
persistentVolumeClaim:
claimName: pv-claim
# 3.运维人员提前定义好 PV
kind: PersistentVolume
apiVersion: v1
metadata:
name: pv-volume
labels:
type: local
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
rbd:
monitors:
# 使用 kubectl get pods -n rook-ceph 查看 rook-ceph-mon- 开头的 POD IP 即可得下面的列表
- '10.16.154.78:6789'
- '10.16.154.82:6789'
- '10.16.154.83:6789'
pool: kube
image: foo
fsType: ext4
readOnly: true
user: admin
keyring: /etc/ceph/keyring
而在 StatefulSet 中可以如下使用,额外添加一个 VolumeClaimTemplates ,类似 Deployment 中 PodTemplate。在创建时,这个 PVC 的名字会被分配一个与这个 Pod 完全一致的编号。
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: "nginx"
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.9.1
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
创建结果如下,状态是 Bound ,Volume 名字是以
$ kubectl create -f statefulset.yaml
$ kubectl get pvc -l app=nginx
NAME STATUS VOLUME CAPACITY ACCESSMODES AGE
www-web-0 Bound pvc-15c268c7-b507-11e6-932f-42010a800002 1Gi RWO 48s
www-web-1 Bound pvc-15c79307-b507-11e6-932f-42010a800002 1Gi RWO 48s
