2.17 应用保障

2.17 应用保障 #

2.17.1 容器资源配额 #

创建容器有三大隔离技术:namespace、cgroup、chroot。其中的 namespace 实现了独立的进程空间,chroot 实现了独立的文件系统,cgroup 的作用是管控 CPU、内存,保证容器不会无节制地占用基础资源,进而影响到系统里的其他应用。

因为 CPU、内存与存储卷有明显的不同,它是直接 “内置” 在系统里的,不像硬盘那样需要 “外挂”,所以申请和管理的过程会简单很多。Kubernetes 在管控容器使用 CPU 和内存的做法是,只要在 Pod 容器的描述部分添加一个新字段 resources 就可以了,它就相当于申请资源的 Claim。

以下是一个 YAML 描述示例:

apiVersion: v1
kind: Pod
metadata:
  name: ngx-pod-resources

spec:
  containers:
    - image: nginx:alpine
      name: ngx

      resources:
        requests:
          cpu: 10m
          memory: 100Mi
        limits:
          cpu: 20m
          memory: 200Mi
  • requests 意思是容器要申请的资源,也就是说要求 Kubernetes 在创建 Pod 的时候必须分配这里列出的资源,否则容器就无法运行。

  • limits 意思是容器使用资源的上限,不能超过设定值,否则就有可能被强制停止运行。

内存的写法和磁盘容量一样,使用 Ki、Mi、Gi 来表示 KB、MB、GB,比如 512Ki、100Mi、0.5Gi 等。

因为 CPU 因为在计算机中数量有限,非常宝贵,所以 Kubernetes 允许容器精细分割 CPU,既可以 1 个、2 个地完整使用 CPU,也可以用小数 0.1、0.2 的方式来部分使用 CPU。这其实是效仿了 UNIX “时间片” 的用法,意思是进程最多可以占用多少 CPU 时间。CPU 时间也不能无限分割,Kubernetes 里 CPU 的最小使用单位是 0.001,为了方便表示用了一个特别的单位 m,就是 “milli” “毫” 的意思,比如说 500m 就相当于 0.5。

上面的示例 YAML 描述向系统申请的是 1% 的 CPU 时间和 100MB 的内存,运行时的资源上限是 2%CPU 时间和 200MB 内存。有了这个申请,Kubernetes 就会在集群中查找最符合这个资源要求的节点去运行 Pod。

Kubernetes 会根据每个 Pod 声明的需求,像搭积木或者玩俄罗斯方块一样,把节点尽量 “塞满”,充分利用每个节点的资源,让集群的效益最大化。

如果 Pod 不写 resources 字段,就意味着 Pod 对运行的资源要求 “既没有下限,也没有上限”,Kubernetes 不用管 CPU 和内存是否足够,可以把 Pod 调度到任意的节点上,而且后续 Pod 运行时也可以无限制地使用 CPU 和内存。如果是生产环境就很危险了,Pod 可能会因为资源不足而运行缓慢,或者是占用太多资源而影响其他应用,所以应当合理评估 Pod 的资源使用情况,尽量为 Pod 加上限制。

如果预估错误,Pod 申请的资源太多,系统无法满足,比如申请 10 个 CPU,但是系统里没有节点能满足这个要求,Kubernetes 会调度失败,当前集群里的所有节点都无法运行这个 Pod。

1.17.2 容器状态探针 #

使用 resources 字段加上资源配额之后,Pod 在 Kubernetes 里的运行就有了初步保障,Kubernetes 会监控 Pod 的资源使用情况,让它既不会 “饿死” 也不会 “撑死”。 如果还希望 Kubernetes 能够更细致地监控 Pod 的状态,除了保证崩溃重启,还必须要能够探查到 Pod 的内部运行状态,定时给应用做 “体检”,让应用时刻保持 “健康”,能够满负荷稳定工作,这就需要用到 “探针”(Probe)。

Kubernetes 为检查应用状态定义了三种探针,分别对应容器不同的状态:

  • Startup 启动探针,用来检查应用是否已经启动成功,适合那些有大量初始化工作要做,启动很慢的应用。

  • Liveness 存活探针,用来检查应用是否正常运行,是否存在死锁、死循环。

  • Readiness 就绪探针,用来检查应用是否可以接收流量,是否能够对外提供服务。

这三种探针是递进的关系:应用程序先启动,加载完配置文件等基本的初始化数据就进入了 Startup 状态,之后如果没有什么异常就是 Liveness 存活状态,但可能有一些准备工作没有完成,还不一定能对外提供服务,只有到最后的 Readiness 状态才是一个容器最健康可用的状态。

如果一个 Pod 里的容器配置了探针,Kubernetes 在启动容器后就会不断地调用探针来检查容器的状态:

  • 如果 Startup 探针失败,Kubernetes 会认为容器没有正常启动,就会尝试反复重启,其后面的 Liveness 探针和 Readiness 探针也不会启动。

  • 如果 Liveness 探针失败,Kubernetes 就会认为容器发生了异常,也会重启容器。

  • 如果 Readiness 探针失败,Kubernetes 会认为容器虽然在运行,但内部有错误,不能正常提供服务,就会把容器从 Service 对象的负载均衡集合中排除,不会给它分配流量。

2.17.3 使用容器状态探针 #

startupProbe、livenessProbe、readinessProbe 三种探针的配置方式都是一样的,关键字段有:

  • periodSeconds 执行探测动作的时间间隔,默认是 10 秒探测一次。

  • timeoutSeconds 探测动作的超时时间,如果超时就认为探测失败,默认是 1 秒。

  • successThreshold 连续几次探测成功才认为是正常,对于 startupProbe 和 livenessProbe 来说它只能是 1。

  • failureThreshold 连续探测失败几次才认为是真正发生了异常,默认是 3 次。

Kubernetes 支持 3 种探测方式,分别是:Shell、TCP Socket、HTTP GET,需要在探针里配置:

  • exec,执行一个 Linux 命令,比如 ps、cat 等等,和 container 的 command 字段很类似。

  • tcpSocket,使用 TCP 协议尝试连接容器的指定端口。

  • httpGet,连接端口并发送 HTTP GET 请求。

要使用这些探针,就必须要在开发应用时预留出 “检查口”,这样 Kubernetes 才能调用探针获取信息。这里以 Nginx 作为示例,用 ConfigMap 编写一个配置文件:

apiVersion: v1
kind: ConfigMap
metadata:
  name: ngx-conf

data:
  default.conf: |
    server {
      listen 80;
      location = /ready {
        return 200 'I am ready';
      }
    }    

在这个配置文件里,启用 80 端口,然后用 location 指令定义了 HTTP 路径 /ready,把它作为对外暴露的 “检查口”,用来检测就绪状态,返回简单的 200 状态码和一个字符串表示工作正常。

接下来是 Pod 里三种探针的具体定义:

apiVersion: v1
kind: Pod
metadata:
  name: ngx-pod-probe

spec:
  volumes:
    - name: ngx-conf-vol
      configMap:
        name: ngx-conf

  containers:
    - image: nginx:alpine
      name: ngx
      ports:
        - containerPort: 80
      volumeMounts:
        - mountPath: /etc/nginx/conf.d
          name: ngx-conf-vol

      startupProbe:
        periodSeconds: 1
        exec:
          command: [ "cat", "/var/run/nginx.pid" ]

      livenessProbe:
        periodSeconds: 10
        tcpSocket:
          port: 80

      readinessProbe:
        periodSeconds: 5
        httpGet:
          path: /ready
          port: 80

StartupProbe 使用了 Shell 方式,使用 cat 命令检查 Nginx 存在磁盘上的进程号文件(/var/run/nginx.pid),如果存在就认为是启动成功,它的执行频率是每秒探测一次。

LivenessProbe 使用了 TCP Socket 方式,尝试连接 Nginx 的 80 端口,每 10 秒探测一次。

ReadinessProbe 使用的是 HTTP GET 方式,访问容器的 /ready 路径,每 5 秒发一次请求。

apply 创建 Pod 后,然后查看它的状态,也可以使用 kubectl logs 来查看 Nginx 的访问日志,里面会记录 HTTP GET 探针的执行情况:

可以看到 Kubernetes 正是以大约 5 秒一次的频率,向 URI /ready 发送 HTTP 请求,不断地检查容器是否处于就绪状态。

探针可以配置 “initialDelaySeconds” 字段,表示容器启动后多久才执行探针动作,适用于某些启动比较慢的应用,默认值是 0。

在容器里还可以配置 “lifecycle” 字段,在启动后和终止前安装两个钩子 “postStart” “preStop”,执行 Shell 命令或者发送 HTTP 请求做一些初始化和收尾工作。