以下主要介绍 k8s 中的 deployment ,deployment 是 k8s 中最常用的资源类型之一,用于管理 pod 的生命周期。

改造之前的 pod

之前我们使用 pod 来管理应用,但是 pod 有如下缺点:

  • pod 无法实现滚动升级
  • pod 无法实现回滚
  • pod 无法实现自动伸缩
  • pod 无法实现自动恢复
  • pod 无法实现自动发布
  • pod 无法实现自动扩容
  • pod 无法实现自动缩容
  • pod 无法实现自动重启

为了解决这些问题,我们需要使用 deployment 来管理应用。

创建 deployment 文件

首先创建数据的文件,将其改造为 deployment 方式 deploy-mysql.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql
  namespace: test
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      hostNetwork: true
      volumes: 
      - name: mysql-data
        hostPath: 
          path: /opt/mysql/data
      nodeSelector:
        component: mysql
      containers:
      - name: mysql
        image: mariadb:11.1
        args:
          - --character-set-server=utf8mb4
          - --collation-server=utf8mb4_unicode_ci
        ports:
          - containerPort: 3306
        env:
          - name: MYSQL_USER
            valueFrom:
              secretKeyRef:
                name: myblog-secret
                key: MYSQL_USER
          - name: MYSQL_ROOT_PASSWORD
            valueFrom:
              secretKeyRef:
                name: myblog-secret
                key: MYSQL_PASSWD
          - name: MYSQL_DATABASE
            valueFrom:
              configMapKeyRef:
                name: myblog
                key: MYSQL_DATABASE
        volumeMounts:
          - name: mysql-data
            mountPath: /var/lib/mysql

之后创建业务应用的 deployment 文件 deploy-myblog.yaml,为了更好的实验,我把 blog 的应用换成 wordpress 了.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myblog
  namespace: test
spec:
  replicas: 1
  selector:
    matchLabels:
      app: myblog
  template:
    metadata:
      labels:
        app: myblog
    spec:
      containers:
        - name: myblog
          image: wordpress:php8.2-alpine
          imagePullPolicy: IfNotPresent
          env:
            - name: WORDPRESS_DB_HOST
              valueFrom:
                configMapKeyRef:
                  name: myblog
                  key: MYSQL_HOST
            - name: WORDPRESS_DB_USER
              valueFrom:
                secretKeyRef:
                  name: myblog-secret
                  key: MYSQL_USER
            - name: WORDPRESS_DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: myblog-secret
                  key: MYSQL_PASSWD
            - name: WORDPRESS_DB_NAME
              valueFrom:
                configMapKeyRef:
                  name: myblog
                  key: MYSQL_DATABASE
          ports:
            - containerPort: 80

创建 deployment

kubectl apply -f deploy-mysql.yaml kubectl apply -f deploy-myblog.yaml

查看 deployment

此时可以通过 kubectl get deploy -n test 查看 deployment 的状态:

$ kubectl get deploy -n test
NAME     READY   UP-TO-DATE   AVAILABLE   AGE
mysql    1/1     1            1           2m
myblog   1/1     1            1           2m
  • NAME 列出了集群中 Deployments 的名称。
  • READY显示当前正在运行的副本数/期望的副本数。
  • UP-TO-DATE显示已更新以实现期望状态的副本数。
  • AVAILABLE显示应用程序可供用户使用的副本数。
  • AGE显示应用程序运行的时间量。

查看 pod

$ kubectl get rs -n test
NAME               DESIRED   CURRENT   READY   AGE
mysql-7b4f8f8d5f   1         1         1       2m
myblog-7c96c9f76b  1         1         1       2m

查看 deployment 详细信息

$ kubectl describe deploy mysql -n test
Name:                   mysql
Namespace:              test
CreationTimestamp:      Fri, 03 Sep 2021 23:59:15 +0800
Labels:                 <none>
Annotations:            deployment.kubernetes.io/revision: 1
Selector:               app=mysql
Replicas:               1 desired | 1 updated | 1 total | 1 available | 0 unavailable
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  25% max unavailable, 25% max surge
Pod Template:
  Labels:  app=mysql
  Containers:
   mysql:
    Image:      mariadb:11.1
    Port:       3306/TCP
    Host Port:  0/TCP
    Args:
      --character-set-server=utf8mb4
      --collation-server=utf8mb4_unicode_ci
    Environment:
      MYSQL_USER:           <set to the key 'MYSQL_USER' in secret 'myblog-secret'>    Optional: false
      MYSQL_ROOT_PASSWORD:  <set to the key 'MYSQL_PASSWD' in secret 'myblog-secret'>  Optional: false
      MYSQL_DATABASE:       <set to the key 'MYSQL_DATABASE' of config map 'myblog'>   Optional: false
    Mounts:
      /var/lib/mysql from mysql-data (rw)
  Volumes:
   mysql-data:
    Type:          HostPath (bare host directory volume)
    Path:          /opt/mysql/data
    HostPathType:
Conditions:
  Type           Status  Reason
  ----           ------  ------
  Available      True    MinimumReplicasAvailable
  Progressing    True    NewReplicaSetAvailable
OldReplicaSets:  <none>
NewReplicaSet:   mysql-7f97cb6cc9 (1/1 replicas created)
Events:          <none>

副本保障机制

controller实时检测pod状态,并保障副本数一直处于期望的值。

# 删除pod,观察pod状态变化
$ kubectl -n test delete pod myblog-7c96c9f76b-qbbg7

# 观察pod
$ kubectl get pods -o wide -n test

# 设置两个副本, 或者通过 kubectl -n test edit deploy myblog 的方式,最好通过修改文件,然后 apply 的方式,这样 yaml 文件可以保持同步
$ kubectl -n test scale deploy myblog --replicas=2
deployment.extensions/myblog scaled

# 观察pod
$ kubectl get pods -o wide -n test
NAME                      READY   STATUS    RESTARTS   AGE
mysql-7b4f8f8d5f-4j4q4    1/1     Running   0          3m18s
myblog-7c96c9f76b-qbbg7   1/1     Running   0          11m
myblog-7c96c9f76b-s6brm   1/1     Running   0          3m18s

Pod驱逐策略

K8S 有个特色功能叫 pod eviction,它在某些场景下如节点 NotReady,或者资源不足时,把 pod 驱逐至其它节点,这也是出于业务保护的角度去考虑的。

  1. Kube-controller-manager: 周期性检查所有节点状态,当节点处于 NotReady 状态超过一段时间后,驱逐该节点上所有 pod。
  • pod-eviction-timeout:NotReady 状态节点超过该时间后,执行驱逐,默认 5 min,适用于k8s 1.13版本之前
  • 1.13版本后,集群开启 TaintBasedEvictionsTaintNodesByCondition 功能,即taint-based-evictions,即节点若失联或者出现各种异常情况,k8s会自动为node打上污点,同时为pod默认添加如下容忍设置:
tolerations:
- effect: NoExecute
key: node.kubernetes.io/not-ready
operator: Exists
tolerationSeconds: 300
- effect: NoExecute
key: node.kubernetes.io/unreachable
operator: Exists
tolerationSeconds: 300

即各pod可以独立设置驱逐容忍时间。
2. Kubelet: 周期性检查本节点资源,当资源不足时,按照优先级驱逐部分 pod

  • memory.available:节点可用内存
  • nodefs.available:节点根盘可用存储空间
  • nodefs.inodesFree:节点inodes可用数量
  • imagefs.available:镜像存储盘的可用空间
  • imagefs.inodesFree:镜像存储盘的inodes可用数量

服务更新方法

修改服务,重新打tag模拟服务更新至下一个版本。
更新方式:

  1. 修改yaml文件,使用kubectl -n test apply -f deploy-myblog.yaml来应用更新
  2. kubectl -n test edit deploy myblog在线更新
  3. kubectl -n test set image deploy myblog myblog=wordpress:php8.2 --record

更新策略

...
spec:
  replicas: 2
  selector:
    matchLabels:
      app: myblog
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    #指定更新方式为滚动更新,默认策略,通过get deploy yaml查看
    type: RollingUpdate
    ...

093C2B81-5E26-4F1A-B6E3-C6ADD48AAA29.png
策略控制:

  • maxSurge:最大激增数, 指更新过程中, 最多可以比replicas预先设定值多出的pod数量, 可以为固定值或百分比,默认为desired Pods数的25%。计算时向上取整(比如3.4,取4),更新过程中最多会有replicas + maxSurge个pod
  • maxUnavailable: 指更新过程中, 最多有几个pod处于无法服务状态 , 可以为固定值或百分比,默认为desired Pods数的25%。计算时向下取整(比如3.6,取3)

在Deployment rollout时,需要保证Available(Ready) Pods数不低于 desired pods number - maxUnavailable; 保证所有的非异常状态Pods数不多于 desired pods number + maxSurge。

以myblog为例,使用默认的策略,更新过程:

  1. maxSurge 25%,2个实例,向上取整,则maxSurge为1,意味着最多可以有2+1=3个Pod,那么此时会新创建1个ReplicaSet,RS-new,把副本数置为1,此时呢,副本控制器就去创建这个新的Pod
  2. 同时,maxUnavailable是25%,副本数2*25%,向下取整,则为0,意味着,滚动更新的过程中,不能有少于2个可用的Pod,因此,旧的Replica(RS-old)会先保持不动,等RS-new管理的Pod状态Ready后,此时已经有3个Ready状态的Pod了,那么由于只要保证有2个可用的Pod即可,因此,RS-old的副本数会有2个变成1个,此时,会删掉一个旧的Pod
  3. 删掉旧的Pod的时候,由于总的Pod数量又变成2个了,因此,距离最大的3个还有1个Pod可以创建,所以,RS-new把管理的副本数由1改成2,此时又会创建1个新的Pod,等RS-new管理了2个Pod都ready后,那么就可以把RS-old的副本数由1置为0了,这样就完成了滚动更新
#查看滚动更新事件
$ kubectl -n test describe deploy myblog
...
Events:
  Type    Reason             Age   From                   Message
  ----    ------             ----  ----                   -------
  Normal  ScalingReplicaSet  11s   deployment-controller  Scaled up replica set myblog-6cf56fc848 to 1
  Normal  ScalingReplicaSet  11s   deployment-controller  Scaled down replica set myblog-6fdcf98f9 to 1
  Normal  ScalingReplicaSet  11s   deployment-controller  Scaled up replica set myblog-6cf56fc848 to 2
  Normal  ScalingReplicaSet  6s    deployment-controller  Scaled down replica set myblog-6fdcf98f9 to 0
$ kubectl get rs
NAME                     DESIRED   CURRENT   READY   AGE
myblog-6cf56fc848   2         2         2       16h
myblog-6fdcf98f9    0         0         0       16h

除了滚动更新以外,还有一种策略是Recreate,直接在当前的pod基础上先删后建:

...
  strategy:
    type: Recreate
...

我们的mysql服务应该使用Recreate来管理:

$ kubectl -n test edit deploy mysql
...
  selector:
    matchLabels:
      app: mysql
  strategy:
    type: Recreate
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: mysql
...

服务回滚

通过滚动升级的策略可以平滑的升级Deployment,若升级出现问题,需要最快且最好的方式回退到上一次能够提供正常工作的版本。为此K8S提供了回滚机制。

revision:更新应用时,K8S都会记录当前的版本号,即为revision,当升级出现问题时,可通过回滚到某个特定的revision,默认配置下,K8S只会保留最近的几个revision,可以通过Deployment配置文件中的spec.revisionHistoryLimit属性增加revision数量,默认是10。
查看当前:

$ kubectl -n test rollout history deploy myblog ##CHANGE-CAUSE为空
$ kubectl delete -f deploy-myblog.yaml ## 方便演示到具体效果,删掉已有deployment

记录回滚:

$ kubectl apply -f deploy-myblog.yaml --record
$ kubectl -n test set image deploy myblog myblog=wordpress:php8.2 --record=true

查看deployment更新历史:

$ kubectl -n test rollout history deploy myblog
deployment.extensions/myblog
REVISION  CHANGE-CAUSE
1         kubectl create --filename=deploy-myblog.yaml --record=true
2         kubectl set image deploy myblog myblog=wordpress:php8.2-apache --record=true

回滚到具体的REVISION:

$ kubectl -n test rollout undo deploy myblog --to-revision=1
deployment.extensions/myblog rolled back

# 访问应用测试
curl 10.244.1.150:8002/