Controller

由 kube-controller-manager 组件来完成控制,常见的控制器比如 deployment 等,所有控制器存放在 pkg/controller/ 目录下。

  1. $ cd kubernetes/pkg/controller/
  2. $ ls -d */
  3. deployment/ job/ podautoscaler/
  4. cloud/ disruption/ namespace/
  5. replicaset/ serviceaccount/ volume/
  6. cronjob/ garbagecollector/ nodelifecycle/ replication/ statefulset/ daemon/
  7. ...

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?)
image.png
在 metadata 中都有一个 ownerReference 的信息,用于保存当前这个 API 对象的拥有者(Owner)的信息

ReplicaSet

ReplicaSet 本身也是一个 API 对象,它被 Deployment 控制器直接控制,然后 ReplicaSet 控制 Pod,所以 Deployment 不直接控制 Pod,Pod 属于 ReplicaSet。帮助 deployment 实现 Pod 的水平扩展/收缩,滚动更新。
image.png
正因如此 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 的信息查看如下:

  1. 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
                                          # 这个字段也可以用百分比来表示

image.png
场景操作记录

# 设置 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 是一种有状态应用,描述了两种状态:

  1. 拓扑状态:指服务拉起顺序的拓扑关系,比如 A 为主,B 为从,需要先拉起 A ,而拉起 B 时需要从 A 获取所需信息;
  2. 存储状态:常见于存储实例中,多个存储节点之间的数据状态;

Headless Service

在 K8S 中服务访问的方式有两种:

  1. 通过 Service 的 VIP 方式,➕ Port 提供服务访问;
  2. 通过 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 的名字,进行了编号,编号规则是:- ,而这个编号从 0 开始,每个 Pod 实例的 IP 一一对应。通过编号的方式,可以很好的控制 Pod 拉起的顺序,保证了 Pod 网络标识的稳定性。如果这个时候删除了 Pod,那 StatefulSet 会根据原先的顺序,创建出两个新的 Pod ,Pod IP 地址可能会改变,但是最终绑定的都是原先的 DNS 。
所以对于有状态应用实例的访问时,必须使用 DNS 记录或者 hostname 方式,而不应该直接访问这些 Pod 的 IP 地址。

PVC 和 PV

存储状态的实现方式是通过 Persistent Volume Claim (PVC)的功能。正常情况下,如果 Pod 创建需要存储资源,需要声明清除 Pod 所需的存储的详细信息,各类存储信息开发使用者无法准确掌握。这样存在一些问题:

  1. 信息过于详细,会对外暴露存储过分细致的信息;
  2. 使用者不方便,不应该让开发或者应用者了解过分详细的存储信息;

所以 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 名字是以--< number. >。这里也出现了编号的问题,在 Pod 删除重建的时候,也会按照这个编号拉起 Pod,保证前后数据不丢失。


$ 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