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 则会。