Kubernetes: Resource: Pod

 14th June 2022 at 11:22am

k8s 的 pod 是最小的程序运行单位。一个 pod 可能包含多个容器,但多个容器只能运行在同一个 node,不会运行在多个 node。

为什么最小运行单位是用 pod 而不是容器?

因为容器的最佳实践是,一个容器只运行一个程序(除了这个程序本身会创建子进程)。比如一个容器往往不会同时跑多个不同的 web 服务。

但有时候你需要额外的容器来配合主容器运作,比如一个同步外部文件的程序、日志收集的程序、某种本地 agent 等等。因此 k8s 设计了 pod 这种结构,来包裹多个容器并实现容器间的网络、文件共享(通过 Volumn)等。

Pod 间的网络访问

k8s 会为集群中的 pod 生成一个软件定义的网络。每个 pod 在这个网络中会被分配一个唯一的 IP,通过这个 IP pod 间可以互相访问,即使它们在不同的 node。k8s 的 namespace 不会隔离这种访问。kubectl get pods -o wide 可以看到 pod IP。

最简单的 YAML 定义

apiVersion: v1
kind: Pod
metadata:
  name: kubia-manual
spec:
  containers:
  - image: luksa/kubia
    name: kubia
    ports: 
    - containerPort: 8080
      protocol: TCP

其中 ports 部分只有文档作用,k8s 不会根据它做逻辑。

kubectl explain 命令可以描述 YAML 中的字段,如:

kubectl explain pods
kubectl explain pods.spec

使用 YAML 文件创建 pod

kubectl create -f kubia-manual.yaml

以 YAML / JSON 形式查看 pod

kubectl get po kubia-manual -o yaml
kubectl get po kubia-manual -o json

查看 pod 日志(stdout、stderr)

# 单容器
kubectl logs kubia-manual
# 多容器用 -c 指定容器
kubectl logs kubia-manual -c kubia
# 如果前面的 pod 挂了,想看它的日志
kubectl logs kubia-manual --previous

Log 可能会滚动(rotated),这里 有详细信息。

发请求到 pod 中

一种方法是创建 service 将 pod 的服务公开出来。可以使用 Kubernetes: Resource: ReplicationController 中提及的 kubectl expose 命令将 rc / deployment expose 出来。

另外一种方法是使用 kubectl port-forward,比如:

kubectl port-forward kubia-manual 8888:8080

kubectl 会在本地起一个进程侦听 8888 端口,并将访问这个端口的请求转发到 kubia-manual pod 中的 8080 端口。这种方法非常适合用于调试。

Label

Label 可以用来筛选 pod:

apiVersion: v1                                         
kind: Pod                                              
metadata:                                              
  name: kubia-manual-v2
  labels:    
    creation_method: manual          
    env: prod                        
spec: 
  containers: 
  - image: luksa/kubia
    name: kubia
    ports: 
    - containerPort: 8080
      protocol: TCP

查看 pod 的 label:

$ kubectl get po --show-labels
NAME            READY  STATUS   RESTARTS  AGE LABELS
kubia-manual    1/1    Running  0         16m <none>
kubia-manual-v2 1/1    Running  0         2m  creat_method=manual,env=prod
kubia-zxzij     1/1    Running  0         1d  run=kubia

$ kubectl get po -L creation_method,env
NAME            READY   STATUS    RESTARTS   AGE   CREATION_METHOD   ENV
kubia-manual    1/1     Running   0          16m   <none>            <none>
kubia-manual-v2 1/1     Running   0          2m    manual            prod
kubia-zxzij     1/1     Running   0          1d    <none>            <none>

还可以:

  • 修改已有 pod 的 label
  • 按 label 筛选出 pod。筛选逻辑支持:
    • Label 存在与否
    • Label 值匹配
  • 批量删除某些 label 下的 pod

Label 与 selector 是配合使用的。可以给 node 设置 label,再要求 pod 被调度到符合条件的 pod:

$ kubectl label node gke-kubia-85f6-node-0rrx gpu=true
node "gke-kubia-85f6-node-0rrx" labeled

$ kubectl get nodes -l gpu=true
NAME                      STATUS AGE
gke-kubia-85f6-node-0rrx  Ready  1d

注意下面的 nodeSelector

apiVersion: v1                                         
kind: Pod                                              
metadata:                                              
  name: kubia-gpu
spec: 
  nodeSelector:               
    gpu: "true"               
  containers: 
  - image: luksa/kubia
    name: kubia

如果你想把 pod 调度到指定的某一个 node,可以使用 k8s 分配给每个 node 的 label kubernetes.io/hostname。但这种做法不被推荐,如果该 node 挂掉了,这个 pod 也就无法运行了。

Annotation

Annotation 类似 label 也是键值对,但是不参与筛选,而是放入较长文本被程序所使用。Label 的值能放入的文本较少。

$ kubectl get po kubia-zxzij -o yaml
apiVersion: v1
kind: pod
metadata:
  annotations:
    kubernetes.io/created-by: |
      {"kind":"SerializedReference", "apiVersion":"v1", 
      "reference":{"kind":"ReplicationController", "namespace":"default", ...

k8s 如何终止一个 pod

先向 pod 中的各容器发 SIGTERM,并把该 pod 从 service 对应的 pod 列表中删除。各容器主进程应该开始 graceful stop。等待一段 grace period 后(默认 15s),如果容器还没停止,k8s 会向容器发 SIGKILL 杀掉容器进程。

健康检查

k8s 通过 liveness probe 机制,让 pod 中的程序可以告知 k8s 自己是否正常。在 pod 的 spec 中可以定义:

  livenessProbe:
     httpGet:
       path: /
       port: 8080
     initialDelaySeconds: 15

Web 服务常用的惯例是在 /health 接口中返回成功失败。好的实践是,/health 接口不止是简单返回 HTTP 200,而且应该 检查外部依赖是否正常,比如依赖的数据库等等。

Liveness probe 的检查过程应该足够 轻量。避免过多、频繁的资源浪费;不要在其中实现重试逻辑,比如检查数据库连接失败后,再检查一次。

Pod 是否可提供服务

Pod 中有 readinessProbe 机制,用于判断一个 pod 是否可以正常提供服务。Pod 只有 ready 了,service 才会把请求转到该 pod。

readinessProbe 提供的探测方式与 livenessProbe 一致。

你可能看到这种情况:

$ kubectl get po kubia-2r1qb
NAME          READY     STATUS    RESTARTS   AGE
kubia-2r1qb   0/1       Running   0          2m

Running 表示 liveness probe 通过,READY 0/1 表示 readinessProbe 没有通过。

三种 probe 的区别

见 k8s 官方文档

简单地说:

  • Startup probe 及 Liveness probe 失败时,容器会被杀掉,会根据 restart policy 做重启操作
  • Readiness probe 失败时,标记 pod 为 unready(从 endpoint 列表中被移除,不再接受请求),同时会继续执行健康检查。一旦检查又通过了(也考虑 successThreshold),该 pod 又会被加入 endpoint 列表中

Managed vs Unmanaged

通过 ReplicationController 或者 Deployment 创建的 pod 为 managed。直接创建的 pod 为 unmanaged。

Unmanaged pod 在 node fail 时,k8s 不会自动帮其重新创建;managed 则会。