亲宝软件园·资讯

展开

Kubernetes应用服务质量管理详解

运维开发故事 人气:0

服务质量管理

在Kubernetes中,Pod是最小的调度单元,所以跟资源和调度相关的属性都是Pod对象的字段,而其中最重要的就是CPU和内存。

如下所示:

---
apiVersion: v1
kind: Pod
metadata:
  name: pod-demo
spec:
  containers:
  - name: myweb
    image: wordpress
    imagePullPolicy: IfNotPresent
    resources:
      requests:
        memory: "128Mi"
        cpu: "250m"
      limits:
        memory: "256Mi"
        cpu: "500m"

其中resources就是资源限制部分。

注:由于一个Pod里可以定义多个Containers,而每个资源限制都是配置在各自的Container,所以Pod的整体配置资源是所有Containers的总和。

在Kubernetes中,CPU这样的资源被称为"可压缩资源",所谓可压缩资源就是当可用资源不足的时候,Pod只会"饥饿",不会退出。而向Memory这样的资源被称为"不可压缩资源",所谓的不可压缩资源就是当资源不足的时候Pod只会OOM。

其中CPU的设置单位是CPU的个数,比如CPU=1就表示这个Pod的CPU限额是1个CPU,而到底是1个CPU核心、是1个vCPU还是1个CPU超线程,这要取决于宿主机上CPU实现方式,而Kunernetes只需要保证该Pod能够使用到1个CPU的使用能力。

Kubernetes允许将CPU的限额设置位分数,比如上面我们设置的CPU.limits的值为500m,而所谓的500m就是500milliCPU,也就是0.5个CPU,这样,这个Pod就会被分到一个CPU一半的计算能力。所以我们可以直接把配置写成cpu=0.5,不过官方推荐500m的写法,这是Kubernetes内部的CPU计算方式。

在Kubernetes中,内存资源的单位是bytes,支持使用Ei,Pi,Ti,Gi,Mi,Ki的方式作为bytes的值,其中需要注意Mi和M的区别(1Mi=1024_1024,1M=1000_1000)。

Kubernetes中Pod的CPU和内存的资源限制,实际上分为requests和limits两种情况。

spec.containers[].resources.limits.cpu
spec.containers[].resources.limits.memory
spec.containers[].resources.requests.cpu
spec.containers[].resources.requests.memory

这两者的区别如下:

QoS模型

Kubernetes中支持三种QoS模型。其分类是基于requests和limits的不同配置。

Guaranteed

当Pod里的每一个Containers都设置了requests和limits,并且其值都相等的时候,这种Pod就属于Guaranteed类别,如下:

apiVersion: v1
kind: Pod
metadata:
  name: qos-demo
  namespace: qos-example
spec:
  containers:
  - name: qos-demo-ctr
    image: nginx
    resources:
      limits:
        memory: "200Mi"
        cpu: "700m"
      requests:
        memory: "200Mi"
        cpu: "700m"

注意,当这Pod仅设置limits,没有设置requests的时候,系统默认为它分配于limits相等的requests值,也就会被划分为Guaranteed类别。

Burstable

而当这个Pod不满足Guaranteed条件,但至少有一个Contaienrs设置了requests,那么这个Pod就会被划分为Burstable类别。如下:

apiVersion: v1
kind: Pod
metadata:
  name: qos-demo-2
  namespace: qos-example
spec:
  containers:
  - name: qos-demo-2-ctr
    image: nginx
    resources:
      limits
        memory: "200Mi"
      requests:
        memory: "100Mi"

BestEffort

如果这个Pod既没有设置requests值,也没有设置limits的值的时候,那么它的QoS类别就是BestEffort类别。

apiVersion: v1
kind: Pod
metadata:
  name: qos-demo-3
  namespace: qos-example
spec:
  containers:
  - name: qos-demo-3-ctr
    image: nginx

而QoS划分的主要场景就是当宿主机资源紧张的时候,kubelet对资源进行Eviction时需要用到。目前Kubernetes设置的默认Eviction的阈值如下:

memory.available<100Mi
nodefs.available<10%
nodefs.inodesFree<5%
imagefs.available<15%

上述条件可以在kubelet中设置:

kubelet --eviction-hard=imagefs.available<10%,memory.available<500Mi,nodefs.available<5%,nodefs.inodesFree<5% --eviction-soft=imagefs.available<30%,nodefs.available<10% --eviction-soft-grace-period=imagefs.available=2m,nodefs.available=2m --eviction-max-pod-grace-period=600

Kubernetes中的Eviction分为Soft Eviction和Hard Eviction两种模式。

当宿主机的Eviction阈值达到后,就会进入MemoryPressure或者DiskPressure状态,从而避免新的Pod调度到上面去。而当Eviction发生时,kubelet删除Pod的先后顺序如下:

cpuset

cpuset,就是把容器绑定到某个CPU核上,减少CPU的上下文切换。

spec:
  containers:
  - name: nginx
    image: nginx
    resources:
      limits:
        memory: "200Mi"
        cpu: "2"
      requests:
        memory: "200Mi"
        cpu: "2"

LimitRange

在正常配置应用Pod的时候,都会把服务质量加上,也就是配置好requests和limits,但是,如果Pod非常多,而且很多Pod只需要相同的限制,我们还是像上面那样一个一个的加就非常繁琐了,这时候我们就可以通过LimitRange做一个全局限制。如果在部署Pod的时候指定了requests和Limits,则指定的生效。反之则由全局的给Pod加上默认的限制。

总结,LimitRange可以实现的功能:

常用的场景如下(来自《Kubernetes权威指南》)

(1)、首先创建一个namespace

apiVersion: v1
kind: Namespace
metadata:
  name: coolops

(2)、为namespace配置LimitRange

apiVersion: v1
kind: LimitRange
metadata:
  name: mylimit
  namespace: coolops
spec:
  limits:
  - max:
      cpu: "1"
      memory: 1Gi
    min:
      cpu: 100m
      memory: 10Mi
    maxLimitRequestRatio:
      cpu: 3
      memory: 4
    type: Pod
  - default:
      cpu: 300m
      memory: 200Mi
    defaultRequest:
      cpu: 200m
      memory: 100Mi
    max:
      cpu: "2"
      memory: 1Gi
    min:
      cpu: 100m
      memory: 10Mi
    maxLimitRequestRatio:
      cpu: 5
      memory: 4
    type: Container

参数说明:

注意:

(1)、如果container设置了maxpod中的容器必须设置limit,如果未设置,则使用defaultlimt的值,如果defaultlimit也没有设置,则无法成功创建

(2)、如果设置了containermin,创建容器的时候必须设置request的值,如果没有设置,则使用defaultrequest,如果没有defaultrequest,则默认等于容器的limit值,如果limit也没有,启动就会报错

创建上面配置的LimitRange:

$ kubectl apply -f limitrange.yaml 
limitrange/mylimit created
$ kubectl get limitrange -n coolops
NAME      CREATED AT
mylimit   2022-08-02T06:08:43Z
$ kubectl describe limitranges -n coolops mylimit
Name:       mylimit
Namespace:  coolops
Type        Resource  Min   Max  Default Request  Default Limit  Max Limit/Request Ratio
----        --------  ---   ---  ---------------  -------------  -----------------------
Pod         cpu       100m  1    -                -              3
Pod         memory    10Mi  1Gi  -                -              4
Container   cpu       100m  2    200m             300m           5
Container   memory    10Mi  1Gi  100Mi            200Mi          4

(3)、创建一个允许范围之内的requests和limits的pod

apiVersion: v1
kind: Pod
metadata:
  name: pod01
  namespace: coolops
spec:
  containers:
  - name: pod-01
    image: nginx
    imagePullPolicy: IfNotPresent
    resources:
      requests:
        cpu: 200m
        memory: 30Mi
      limits:
        cpu: 300m
        memory: 50Mi

我们通过kubectl apply -f pod-01.yaml可以正常创建Pod。

(4)、创建一个cpu超出允许访问的Pod

apiVersion: v1
kind: Pod
metadata:
  name: pod02
  namespace: coolops
spec:
  containers:
  - name: pod-02
    image: nginx
    imagePullPolicy: IfNotPresent
    resources:
      requests:
        cpu: 200m
        memory: 30Mi
      limits:
        cpu: 2
        memory: 50Mi

然后我们创建会报如下错误:

# kubectl apply -f pod-02.yaml 
Error from server (Forbidden): error when creating "pod-02.yaml": pods "pod02" is forbidden: [maximum cpu usage per Pod is 1, but limit is 2, cpu max limit to request ratio per Pod is 3, but provided ratio is 10.000000, cpu max limit to request ratio per Container is 5, but provided ratio is 10.000000]

(5)创建低于允许范围的Pod

apiVersion: v1
kind: Pod
metadata:
  name: pod03
  namespace: coolops
spec:
  containers:
  - name: pod-03
    image: nginx
    imagePullPolicy: IfNotPresent
    resources:
      requests:
        cpu: 200m
        memory: 30Mi
      limits:
        cpu: 100m
        memory: 10Mi

然后会报如下错误:

# kubectl apply -f pod-03.yaml 
The Pod "pod03" is invalid: 
* spec.containers[0].resources.requests: Invalid value: "200m": must be less than or equal to cpu limit
* spec.containers[0].resources.requests: Invalid value: "30Mi": must be less than or equal to memory limit

(6)、创建一个未定义request或Limits的Pod

apiVersion: v1
kind: Pod
metadata:
  name: pod04
  namespace: coolops
spec:
  containers:
  - name: pod-04
    image: nginx
    imagePullPolicy: IfNotPresent
    resources:
      requests:
        cpu: 200m
        memory: 200Mi

然后我们创建完Pod后会发现自动给我们加上了limits。如下:

# kubectl describe pod -n coolops pod04
...
    Limits:
      cpu:     300m
      memory:  200Mi
    Requests:
      cpu:        200m
      memory:     200Mi
...

上面我指定了requests,LimitRange自动给我们加上了defaultLimits,你也可以试一下全都不加或者加一个,道理是一样的。值得注意的是这里要注意一下我们设置的maxLimitRequestRatio,配置的比列必须小于等于我们设置的值。

上文有介绍LimitRange还可以限制还可以限制PVC,如下:

apiVersion: v1
kind: LimitRange
metadata:
  name: storagelimits
  namespace: coolops
spec:
  limits:
  - type: PersistentVolumeClaim
    max:
      storage: 2Gi
    min:
      storage: 1Gi

创建完后即可查看:

 kubectl describe limitranges -n coolops storagelimits 
Name:                  storagelimits
Namespace:             coolops
Type                   Resource  Min  Max  Default Request  Default Limit  Max Limit/Request Ratio
----                   --------  ---  ---  ---------------  -------------  -----------------------
PersistentVolumeClaim  storage   1Gi  2Gi  -                -              -

你可以创建PVC进行测试,道理是一样的。

服务可用性管理

高可用

生产级别应用,为了保证应用的可用性,除了特殊应用(例如批次应用)都会保持高可用,所以在设计应用Pod的时候,就要考虑应用的高可用。

最简单的就是多副本,也就是在创建应用的时候,至少需要2个副本,如下指定replicas为3就表示该应用有3个副本:

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx
  name: nginx-deployment
spec:
  progressDeadlineSeconds: 600
  replicas: 3
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: nginx
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx:1.8
        imagePullPolicy: IfNotPresent
        name: nginx
        resources:
          requests:
            cpu: 0.5
            memory: 500M
          limits:
            cpu: 0.5
            memory: 500M
        ports:
        - containerPort: 80
          name: http
          protocol: TCP

但是光配置多副本就够了么?

如果这三个副本都调度到一台服务器上,该服务器因某些原因宕机了,那上面的应用是不是就不可用?

为了解决这个问题,我们需要为同一个应用配置反亲和性,也就是不让同一应用的Pod调度到同一主机上,将上面的应用YAML改造成如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx
  name: nginx-deployment
spec:
  progressDeadlineSeconds: 600
  replicas: 3
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: nginx
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: nginx
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - nginx
            topologyKey: kubernetes.io/hostname
      containers:
      - image: nginx:1.8
        imagePullPolicy: IfNotPresent
        name: nginx
        resources:
          requests:
            cpu: 0.5
            memory: 500M
          limits:
            cpu: 0.5
            memory: 500M
        ports:
        - containerPort: 80
          name: http
          protocol: TCP

这样能保证同应用不会被调度到同节点,基本的高可用已经做到了。

可用性

但是光保证应用的高可用,应用本身不可用,也会导致异常。

我们知道Kubernetes的Deployment的默认更新策略是滚动更新,如何保证新应用更新后是可用的,这就要使用readinessProbe,用来确保应用可用才会停止老的版本,上面的YAML修改成如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx
  name: nginx-deployment
spec:
  progressDeadlineSeconds: 600
  replicas: 3
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: nginx
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: nginx
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - nginx
            topologyKey: kubernetes.io/hostname
      containers:
      - image: nginx:1.8
        imagePullPolicy: IfNotPresent
        name: nginx
        resources:
          requests:
            cpu: 0.5
            memory: 500M
          limits:
            cpu: 0.5
            memory: 500M
        readinessProbe:
          failureThreshold: 3
          httpGet:
            path: /
            port: http
            scheme: HTTP
          initialDelaySeconds: 10
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 3
        ports:
        - containerPort: 80
          name: http
          protocol: TCP

这样至少能保证只有新版本可访问才接收外部流量。

但是应用运行过程中异常了呢?这就需要使用livenessProbe来保证应用持续可用,上面的YAML修改成如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx
  name: nginx-deployment
spec:
  progressDeadlineSeconds: 600
  replicas: 3
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: nginx
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: nginx
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - nginx
            topologyKey: kubernetes.io/hostname
      containers:
      - image: nginx:1.8
        imagePullPolicy: IfNotPresent
        name: nginx
        resources:
          requests:
            cpu: 0.5
            memory: 500M
          limits:
            cpu: 0.5
            memory: 500M
        readinessProbe:
          failureThreshold: 3
          httpGet:
            path: /
            port: http
            scheme: HTTP
          initialDelaySeconds: 10
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 3
        livenessProbe:
          failureThreshold: 3
          httpGet:
            path: /
            port: http
            scheme: HTTP
          initialDelaySeconds: 20
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 3
        ports:
        - containerPort: 80
          name: http
          protocol: TCP

上面的readinessProbe和livenessProbe都是应用在运行过程中如何保证其可用,那应用在退出的时候如何保证其安全退出?

所谓安全退出,也就是能正常处理退出逻辑,能够正常处理退出信号,也就是所谓的优雅退出。

优雅退出有两种常见的解决方法:

这里抛开应用本身可以处理SIGTERM信号不谈,默认其能够处理,我们要做的就是协助其能优雅退出。在Kubernetes中,使用preStop hook来协助处理,我们可以将上面的YAML修改成如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx
  name: nginx-deployment
spec:
  progressDeadlineSeconds: 600
  replicas: 3
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: nginx
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: nginx
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - nginx
            topologyKey: kubernetes.io/hostname
      containers:
      - image: nginx:1.8
        imagePullPolicy: IfNotPresent
        name: nginx
        lifecycle:
          preStop:
            exec:
              command:
              - /bin/sh
              - -c
              - sleep 15
        resources:
          requests:
            cpu: 0.5
            memory: 500M
          limits:
            cpu: 0.5
            memory: 500M
        readinessProbe:
          failureThreshold: 3
          httpGet:
            path: /
            port: http
            scheme: HTTP
          initialDelaySeconds: 10
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 3
        livenessProbe:
          failureThreshold: 3
          httpGet:
            path: /
            port: http
            scheme: HTTP
          initialDelaySeconds: 20
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 3
        ports:
        - containerPort: 80
          name: http
          protocol: TCP

当然,这里只是一个样例,实际的配置还需要根据企业情况做跳转,比如企业使用了注册中心如zk或者nacos,我们就需要把服务从注册中心下掉。

PDB

上面的那些配置基本可以让应用顺利的在Kubernetes里跑了,但是不可避免有维护节点的需求,比如升级内核,重启服务器等。

而且也不是所有的应用都可以多副本,当我们使用kubectl drain的时候,为了避免某个或者某些应用直接销毁而不可用,Kubernetes引入了PodDisruptionBudget(PDB)控制器,用来控制集群中Pod的运行个数。

在PDB中,主要通过两个参数来控制Pod的数量:

注意:minAvailable和maxUnavailable是互斥了,也就是说两者同一时刻只能出现一种。

kubectl drain命令已经支持了PodDisruptionBudget控制器,在进行kubectl drain操作时会根据PodDisruptionBudget控制器判断应用POD集群数量,进而保证在业务不中断或业务SLA不降级的情况下进行应用POD销毁。在进行kubectl drain或者Pod主动逃离的时候,Kubernetes会通过以下几种情况来进行判断:

在极端的情况下,比如将maxUnavailable设置成0,或者设置成100%,那么就表示不能进行kubectl drain操作。同理将minAvailable设置成100%,或者设置成应用POD集群最大副本数,也表示不能进行kubectl drain操作。

注意:使用PodDisruptionBudget控制器并不能保证任何情况下都对业务POD集群进行约束,PodDisruptionBudget控制器只能保证POD主动逃离的情况下业务不中断或者业务SLA不降级,例如在执行kubectldrain命令时。

(1)、定义minAvailable

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: pdb-demo
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: nginx

(2)、定义maxUnavailable

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: pdb-demo
spec:
  maxUnavailable: 1
  selector:
    matchLabels:
      app: nginx

可以看到PDB是通过label selectors和应用Pod建立关联,而后在主动驱逐Pod的时候,会保证app: nginx的Pod最大不可用数为1,假如本身是3副本,至少会保证2副本正常运行。

总结

上面只是对Kubernetes中应用做了简单的可用性保障,在生产中,应用不仅仅是它自己,还关联上游、下游的应用,所以全链路的应用可用性保障才能让应用更稳定。

加载全部内容

相关教程
猜你喜欢
用户评论