邯城往事 邯城往事

来自邯郸社畜的呐喊

目录
KUbernets实践之pod控制器
/    

KUbernets实践之pod控制器

为什么用 pod 控制器?

只使用 Pod, 将会面临如下需求:

  1. 业务应用启动多个副本
  2. Pod 重建后 IP 会变化,外部如何访问 Pod 服务
  3. 运行业务 Pod 的某个节点挂了,可以自动帮我把 Pod 转移到集群中的可用节点启动起来
  4. 我的业务应用功能是收集节点监控数据,需要把 Pod 运行在 k8 集群的各个节点上

Workload (工作负载)

控制器又称工作负载是用于实现管理 pod 的中间层,确保 pod 资源符合预期的状态,pod 的资源出现故障时,会尝试 进行重启,当根据重启策略无效,则会重新新建 pod 的资源。

workload.png

  • ReplicaSet: 代用户创建指定数量的 pod 副本数量,确保 pod 副本数量符合预期状态,并且支持滚动式自动扩容和缩容功能
  • Deployment:工作在 ReplicaSet 之上,用于管理无状态应用,目前来说最好的控制器。支持滚动更新和回滚功能,还提供声明式配置
  • DaemonSet:用于确保集群中的每一个节点只运行特定的 pod 副本,通常用于实现系统级后台任务。比如 ELK 服务
  • Job:只要完成就立即退出,不需要重启或重建
  • Cronjob:周期性任务控制,不需要持续后台运行
  • StatefulSet:管理有状态应用

Deployment

[root@k8s-master deployment]# cat deploy-mysql.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql
  namespace: demo
spec:
  replicas: 1	#指定Pod副本数
  selector:		#指定Pod的选择器
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:	#给Pod打label
        app: mysql
    spec:
      hostNetwork: true	# 声明pod的网络模式为host模式,效果通docker run --net=host
      volumes: 
      - name: mysql-data
        hostPath: 
          path: /data/mysql/data
      nodeSelector:   # 使用节点选择器将Pod调度到指定label的节点
        component: mysql
      containers:
      - name: mysql
        image: 192.168.51.209:5000/mysql:5.7-utf8
        args:
        - "--character-set-server=utf8"     #
        - "--collation-server=utf8_general_ci"    #  指定字符编码
        ports:
        - containerPort: 3306
        env:
        - name: MYSQL_USER
          valueFrom:
            secretKeyRef:
              name: myblog
              key: MYSQL_USER
        - name: MYSQL_PASSWD
          valueFrom:
            secretKeyRef:
              name: myblog
              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: 15
          periodSeconds: 20
        volumeMounts:
        - name: mysql-data
          mountPath: /var/lib/mysql
[root@k8s-master deployment]# cat deploy-myblog.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myblog
  namespace: demo
spec:
  replicas: 1	#指定Pod副本数
  selector:		#指定Pod的选择器
    matchLabels:
      app: myblog
  template:
    metadata:
      labels:	#给Pod打label
        app: myblog
    spec:
      containers:
      - name: myblog
        image: 192.168.51.209:5000/myblog
        imagePullPolicy: IfNotPresent
        env:
        - name: MYSQL_HOST
          valueFrom:
            configMapKeyRef:
              name: myblog
              key: MYSQL_HOST
        - name: MYSQL_PORT
          valueFrom:
            configMapKeyRef:
              name: myblog
              key: MYSQL_PORT
        - name: MYSQL_USER
          valueFrom:
            secretKeyRef:
              name: myblog
              key: MYSQL_USER
        - name: MYSQL_PASSWD
          valueFrom:
            secretKeyRef:
              name: myblog
              key: MYSQL_PASSWD
        ports:
        - containerPort: 8002
        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
[root@k8s-master deployment]# cat configmap.yaml 
apiVersion: v1
kind: ConfigMap
metadata:
  name: myblog
  namespace: demo
data:
  MYSQL_HOST: "192.168.51.210"
  MYSQL_PORT: "3306"
[root@k8s-master deployment]# cat secret.yaml 
apiVersion: v1
kind: Secret
metadata:
  name: myblog
  namespace: demo
type: Opaque
data:
  MYSQL_USER: cm9vdA==
  MYSQL_PASSWD: MTIzNDU2

创建 Deployment

[root@k8s-master deployment]# kubectl create -f deploy-mysql.yaml 
deployment.apps/mysql created
[root@k8s-master deployment]# kubectl create -f configmap.yaml 
configmap/myblog created
[root@k8s-master deployment]# kubectl create -f  secret.yaml 
secret/myblog created
[root@k8s-master deployment]# kubectl create -f deploy-myblog.yaml 
deployment.apps/myblog created
[root@k8s-master deployment]#  kubectl api-resources
[root@k8s-master deployment]# kubectl -n demo get deploy 
NAME     READY   UP-TO-DATE   AVAILABLE   AGE
myblog   1/1     1            1           3m9s
mysql    1/1     1            1           28h
  • NAME 列出了集群中 Deployments 的名称。
  • READY 显示当前正在运行的副本数/期望的副本数。
  • UP-TO-DATE 显示已更新以实现期望状态的副本数。
  • AVAILABLE 显示应用程序可供用户使用的副本数。
  • AGE 显示应用程序运行的时间量。
[root@k8s-master deployment]# kubectl -n demo get po
NAME                     READY   STATUS    RESTARTS   AGE
myblog-b66554b87-rms8b   1/1     Running   0          8m53s
mysql-8fcc5595f-dj4gb    1/1     Running   0          9m45s

命令指示:

  • 删除 deploy: kubectl delete -n demo deploy myblog 或者 kubectl delete -f deploy-mysql.yaml
  • 查看详情:kubectl describe -n demo pod mysql-55745b9899-dpzrj

副本保障机制

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

删除 pod,观察 pod 状态变化

[root@k8s-master deployment]# kubectl -n demo get pods -o wide
NAME                     READY   STATUS    RESTARTS   AGE     IP               NODE         NOMINATED NODE   READINESS GATES
myblog-b66554b87-zp9nf   1/1     Running   0          4m31s   10.244.2.12      k8s-slave2   <none>           <none>
mysql-8fcc5595f-trnx9    1/1     Running   0          28h     192.168.51.210   k8s-slave1   <none>           <none>
[root@k8s-master deployment]# 
[root@k8s-master deployment]# kubectl -n demo delete pod myblog-b66554b87-zp9nf 
pod "myblog-b66554b87-zp9nf" deleted
[root@k8s-master deployment]#  kubectl -n demo get pods -o wide -w
NAME                     READY   STATUS        RESTARTS   AGE     IP               NODE         NOMINATED NODE   READINESS GATES
myblog-b66554b87-phv4x   1/1     Running       0          30s     10.244.2.13      k8s-slave2   <none>           <none>
myblog-b66554b87-zp9nf   1/1     Terminating   0          5m28s   10.244.2.12      k8s-slave2   <none>           <none>
mysql-8fcc5595f-trnx9    1/1     Running       0          28h     192.168.51.210   k8s-slave1   <none>           <none>
myblog-b66554b87-zp9nf   0/1     Terminating   0          5m30s   <none>           k8s-slave2   <none>           <none>
myblog-b66554b87-zp9nf   0/1     Terminating   0          5m30s   <none>           k8s-slave2   <none>           <none>
myblog-b66554b87-zp9nf   0/1     Terminating   0          5m42s   <none>           k8s-slave2   <none>           <none>
myblog-b66554b87-zp9nf   0/1     Terminating   0          5m42s   <none>           k8s-slave2   <none>           <none>
[root@k8s-master deployment]# kubectl -n demo get pods -o wide
NAME                     READY   STATUS    RESTARTS   AGE   IP               NODE         NOMINATED NODE   READINESS GATES
myblog-b66554b87-phv4x   1/1     Running   0          73s   10.244.2.13      k8s-slave2   <none>           <none>
mysql-8fcc5595f-trnx9    1/1     Running   0          28h   192.168.51.210   k8s-slave1   <none>           <none>

设置两个副本, 或者通过 kubectl -n demo edit deploy myblog 的方式,最好通过修改文件,然后 apply 的方式,这样 YAML 文件可以保持同步

[root@k8s-master deployment]# kubectl -n demo scale deploy myblog --replicas=2
deployment.apps/myblog scaled

观察 pods

[root@k8s-master deployment]# kubectl -n demo get pods -o wide
NAME                     READY   STATUS    RESTARTS   AGE     IP               NODE         NOMINATED NODE   READINESS GATES
myblog-b66554b87-jg9k8   1/1     Running   0          49s     10.244.1.25      k8s-slave1   <none>           <none>
myblog-b66554b87-phv4x   1/1     Running   0          4m17s   10.244.2.13      k8s-slave2   <none>           <none>
mysql-8fcc5595f-trnx9    1/1     Running   0          28h     192.168.51.210   k8s-slave1   <none>           <none>

Pod 驱逐策略

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

  1. Kube-controller-manager: 周期性检查所有节点状态,当节点处于 NotReady 状态超过一段时间后,驱逐该节点上所有 pod。停掉 kubelet
  • pod-eviction-timeout:NotReady 状态节点超过该时间后,执行驱逐,默认 5 min
  1. Kubelet: 周期性检查本节点资源,当资源不足时,按照优先级驱逐部分 pod
  • memory.available:节点可用内存
  • nodefs.available:节点根盘可用存储空间
  • nodefs.inodesFree:节点 inodes 可用数量
  • imagefs.available:镜像存储盘的可用空间
  • imagefs.inodesFree:镜像存储盘的 inodes 可用数量

服务更新

修改 dockerfile,重新打 tag 模拟服务更新。
更新方式:

  • 修改 YAML 文件,使用 kubectl -n demo apply -f deploy-myblog.yaml 来应用更新
  • kubectl -n demo edit deploy myblog 在线更新
  • kubectl set image deploy myblog myblog=192.168.51.209:5000/myblog:v2 --record

修改测试文件

$ vi mybolg/blog/template/index.html

$ docker build . -t 192.168.51.209:5000/myblog:v2 -f Dockerfile_optimized
$ docker push 192.168.51.209:5000/myblog:v2

更新策略

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

update.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 demo describe deploy myblog

服务回滚

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

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

查看当前

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

记录回滚:

$ kubectl create -f deploy-myblog.yaml --record
$ kubectl -n demo set image deploy myblog myblog=192.168.51.209:5000/myblog:v2 --record=true

查看 deployment 更新历史:
kubectl -n demo rollout history deploy myblog
回滚到具体的 REVISION:
kubectl -n demo rollout undo deploy myblog --to-revision=1

为何要控制 Pod 应该如何调度

  • 集群中有些机器的配置高(SSD,更好的内存等),我们希望核心的服务(比如说数据库)运行在上面
  • 某两个服务的网络传输很频繁,我们希望它们最好在同一台机器上

NodeSelector

labelkubernetes 中一个非常重要的概念,用户可以非常灵活的利用 label 来管理集群中的资源,POD 的调度可以根据节点的 label 进行特定的部署。

查看节点的 label:

kubectl get nodes --show-labels

为节点打 label:
kubectl label node k8s-master disktype=ssd
当 node 被打上了相关标签后,在调度的时候就可以使用这些标签了,只需要在 spec 字段中添加 nodeSelector 字段,里面是我们需要被调度的节点的 label。

...
spec:
  hostNetwork: true	# 声明pod的网络模式为host模式,效果通docker run --net=host
  volumes: 
  - name: mysql-data
    hostPath: 
      path: /opt/mysql/data
  nodeSelector:   # 使用节点选择器将Pod调度到指定label的节点
    component: mysql
  containers:
  - name: mysql
  	image: 192.168.51.209:5000/demo/mysql:5.7
...

nodeAffinity

节点亲和性 , 比上面的 nodeSelector 更加灵活,它可以进行一些简单的逻辑组合,不只是简单的相等匹配 。分为两种,软策略和硬策略。

preferredDuringSchedulingIgnoredDuringExecution:软策略,如果你没有满足调度要求的节点的话,Pod 就会忽略这条规则,继续完成调度过程,说白了就是满足条件最好了,没有满足就忽略掉的策略。

requiredDuringSchedulingIgnoredDuringExecution : 硬策略,如果没有满足条件的节点的话,就不断重试直到满足条件为止,简单说就是你必须满足我的要求,不然我就不会调度 Pod。

要求 Pod 不能运行在 128 和 132 两个节点上,如果有个节点满足 disktype=ssd 的话就优先调度到这个节点上

...
spec:
      containers:
      - name: demo
        image: 172.21.32.6:5000/demo/myblog
        ports:
        - containerPort: 8002
      affinity:
          nodeAffinity:
            requiredDuringSchedulingIgnoredDuringExecution:
                nodeSelectorTerms:
                - matchExpressions:
                    - key: kubernetes.io/hostname
                      operator: NotIn
                      values:
                        - 192.168.136.128
                        - 192.168.136.132
            preferredDuringSchedulingIgnoredDuringExecution:
                - weight: 1
                  preference:
                    matchExpressions:
                    - key: disktype
                      operator: In
                      values:
                        - ssd
                        - sas
...

这里的匹配逻辑是 label 的值在某个列表中,现在 Kubernetes 提供的操作符有下面的几种:

  • In:label 的值在某个列表中
  • NotIn:label 的值不在某个列表中
  • Gt:label 的值大于某个值
  • Lt:label 的值小于某个值
  • Exists:某个 label 存在
  • DoesNotExist:某个 label 不存在

如果 nodeSelectorTerms 下面有多个选项的话,满足任何一个条件就可以了;如果 matchExpressions 有多个选项的话,则必须同时满足这些条件才能正常调度 Pod

污点(Taints)与容忍(tolerations)

对于 nodeAffinity 无论是硬策略还是软策略方式,都是调度 Pod 到预期节点上,而 Taints 恰好与之相反,如果一个节点标记为 Taints ,除非 Pod 也被标识为可以容忍污点节点,否则该 Taints 节点不会被调度 Pod。

Taints(污点)是 Node 的一个属性,设置了 Taints(污点)后,因为有了污点,所以 Kubernetes 是不会将 Pod 调度到这个 Node 上的。于是 Kubernetes 就给 Pod 设置了个属性 Tolerations(容忍),只要 Pod 能够容忍 Node 上的污点,那么 Kubernetes 就会忽略 Node 上的污点,就能够(不是必须)把 Pod 调度过去。

比如用户希望把 Master 节点保留给 Kubernetes 系统组件使用,或者把一组具有特殊资源预留给某些 Pod,则污点就很有用了,Pod 不会再被调度到 taint 标记过的节点。taint 标记节点举例如下:

设置污点:

$ kubectl taint node [node_name] key=value:[effect]
其中[effect] 可取值: [ NoSchedule | PreferNoSchedule | NoExecute ]
NoSchedule:一定不能被调度。
PreferNoSchedule:尽量不要调度。
NoExecute:不仅不会调度,还会驱逐 Node 上已有的 Pod。
示例:kubectl taint node k8s-master smoke=true:NoSchedule 去除污点:

去除指定 key 及其 effect:
kubectl taint nodes [node_name] key:[effect]- #这里的 key 不用指定 value

去除指定 key 所有的 effect:
kubectl taint nodes node_name key-

示例:
kubectl taint node k8s-master smoke=true:NoSchedule
kubectl taint node k8s-master smoke:NoExecute-
kubectl taint node k8s-master smoke-污点演示:

给 k8s-slave1 打上污点,smoke=true:NoSchedule

$ kubectl taint node k8s-slave1 smoke=true:NoSchedule
$ kubectl taint node k8s-slave2 drunk=true:NoSchedule

扩容 myblog 的 Pod,观察新 Pod 的调度情况

$ kuebctl -n demo scale deploy myblog --replicas=3
$ kubectl -n demo get po -w    ## pending

Pod 容忍污点示例:myblog/deployment/deploy-myblog-taint.yaml

[root@k8s-master deployment]# cat deploy-myblog-taint.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myblog
  namespace: demo
spec:
  replicas: 1	#指定Pod副本数
  selector:		#指定Pod的选择器
    matchLabels:
      app: myblog
  template:
    metadata:
      labels:	#给Pod打label
        app: myblog
    spec:
      tolerations: #设置容忍性
      - key: "smoke" 
        operator: "Equal"  #如果操作符为Exists,那么value属性可省略,不指定operator,默认为Equal
        value: "true"
        effect: "NoSchedule"
      - key: "drunk" 
        operator: "Equal"  #如果操作符为Exists,那么value属性可省略,不指定operator,默认为Equal
        value: "true"
        effect: "NoSchedule"
      containers:
      - name: myblog
        image: 192.168.51.209:5000/myblog
        imagePullPolicy: IfNotPresent
        env:
        - name: MYSQL_HOST
          valueFrom:
            configMapKeyRef:
              name: myblog
              key: MYSQL_HOST
        - name: MYSQL_PORT
          valueFrom:
            configMapKeyRef:
              name: myblog
              key: MYSQL_PORT
        - name: MYSQL_USER
          valueFrom:
            secretKeyRef:
              name: myblog
              key: MYSQL_USER
        - name: MYSQL_PASSWD
          valueFrom:
            secretKeyRef:
              name: myblog
              key: MYSQL_PASSWD
        ports:
        - containerPort: 8002
        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

$ kubectl apply -f deploy-myblog-taint.yaml

spec:
      containers:
      - name: demo
        image: 192.168.51.209:5000/demo/myblog
      tolerations:
        - operator: "Exists"

标题:KUbernets实践之pod控制器
作者:cuijianzhe
地址:https://cjzshilong.cn/articles/2020/04/22/1587528230774.html