静态Pod

在k8s集群中除了经常使用到的普通的Pod,还有一种特殊的Pod,叫做Static Pod,也就是静态Pod。

静态Pod直接由节点上的kubelet进程管理,不通过master节点上的apiserver。无法与常用的控制器Deployment或者DaemonSet进行关联,它由kubelet进程自己监控,当pod崩溃时会重启该pod,kubelet也无法对他们进行健康检查。静态pod始终绑定在某一个kubelet上,并始终运行在同一个节点上。kubelet会自动为每一个静态Pod在k8s的apiserver上创建一个镜像Pod,因此可以在apiserver中查询到该Pod,但是不能通过apiserver进行控制。

创建静态Pod有两种方式:配置文件和HTTP两种方式

配置文件

kubelet --pod-manifest-path=<the directory>来启动 kubelet 进程,kubelet 定期的去扫描这个目录,根据这个目录下出现或消失的 YAML/JSON 文件来创建或删除静态 pod。

比如在 node01 这个节点上用静态 pod 的方式来启动一个 nginx 的服务。登录到node01节点上面,可以通过下面命令找到 kubelet 对应的启动配置文件

  1. $ systemctl status kubelet
  2. # 配置文件路径为:
  3. $ vi /usr/lib/systemd/system/kubelet.service.d/10-kubeadm.conf
  4. ......
  5. Environment="KUBELET_SYSTEM_PODS_ARGS=--pod-manifest-path=/etc/kubernetes/manifests --allow-privileged=true"

打开这个文件可以看到其中有一个参数--pod-manifest-path的配置, 所以如果通过kubeadm 的方式来安装的集群环境,对应的 kubelet 已经配置了静态 Pod 文件的路径,默认地址为 /etc/kubernetes/manifests,所以只需要在该目录下面创建一个标准的 Pod 的 JSON 或者 YAML 文件即可,如果 kubelet 启动参数中没有配置上面的—pod-manifest-path 参数的话,那么添加上这个参数然后重启 kubelet 即可:

[root@ node1 ~] $ cat <<EOF >/etc/kubernetes/manifest/static-web.yaml
apiVersion: v1
kind: Pod
metadata:
  name: static-web
  labels:
    app: static
spec:
  containers:
    - name: web
      image: nginx
      ports:
        - name: web
          containerPort: 80
EOF


通过HTTP创建静态Pod

kubelet周期性的从-manifest-url=参数指定的地址下载文件,并且把它翻译成JSON/YAML格式的pod定义。此后的操作方式与-pod-manifest-path相同,kubelet会不时的重新下载该文件,当文件出现变化时对应的终止或启动静态pod。

kubelet启动时,由-pod-manifest-path或者-pod-mainfest-url参数指定的目录下定义的所有pod都会自动创建。(上面的static-web)

$ docker ps
CONTAINER ID IMAGE         COMMAND  CREATED        STATUS         PORTS     NAMES
f6d05272b57e nginx:latest  "nginx"  8 minutes ago  Up 8 minutes             k8s_web.6f802af4_static-web-fk-node1_default_67e24ed9466ba55986d120c867395f3c_378e5f3c

通过kubectl工具可以看到这里创建了一个新的镜像 Pod:

$ kubectl get pods
NAME                       READY     STATUS    RESTARTS   AGE
static-web-my-node01        1/1       Running   0          2m

静态 pod 的标签会传递给镜像 Pod,可以用来过滤或筛选。 需要注意的是,不能通过 API 服务器来删除静态 pod(例如,通过kubectl命令),kubelet 不会删除它。

$ kubectl delete pod static-web-my-node01
$ kubectl get pods
NAME                       READY     STATUS    RESTARTS   AGE
static-web-my-node01        1/1       Running   0          12s

尝试手动终止容器,可以看到kubelet很快就会自动重启容器。

$ docker ps
CONTAINER ID        IMAGE         COMMAND                CREATED       ...
5b920cbaf8b1        nginx:latest  "nginx -g 'daemon of   2 seconds ago ...

静态Pod的动态增加和删除

运行中的kubelet周期扫描配置的目录下文件的变化,当这个目录中有文件出现或消失时会创建或者删除pod:

$ mv /etc/kubernetes/manifests/static-web.yaml /tmp
$ sleep 20
$ docker ps
// no nginx container is running
$ mv /tmp/static-web.yaml  /etc/kubernetes/manifests
$ sleep 20
$ docker ps
CONTAINER ID        IMAGE         COMMAND                CREATED           ...
e7a62e3427f1        nginx:latest  "nginx -g 'daemon of   27 seconds ago

用kubeadm安装的集群,master节点上面的几个重要组件都是用的静态pod的方式运行的,登录到master节点上查看/etc/kubernetes/mainfest目录:

$ ls /etc/kubernetes/manifests/
etcd.yaml  kube-apiserver.yaml  kube-controller-manager.yaml  kube-scheduler.yaml

这种方式也为我们将集群的一些组件容器化提供了可能,因为这些 Pod 都不会受到 apiserver 的控制,不然我们这里kube-apiserver怎么自己去控制自己呢?万一不小心把这个 Pod 删掉了呢?所以只能有kubelet自己来进行控制,这就是我们所说的静态 Pod。

Downward API

前面从 Pod 的原理到生命周期介绍了 Pod 的一些使用,作为 Kubernetes 中最核心的资源对象、最基本的调度单元,发现 Pod 中的属性还是非常繁多的,前面使用过一个 volumes 的属性,表示声明一个数据卷,可以通过命令kubectl explain pod.spec.volumes去查看该对象下面的属性非常多,前面只是简单使用了 hostPath 和 emptyDir{} 这两种模式,其中还有一种模式叫做downwardAPI,这个模式和其他模式不一样的地方在于它不是为了存放容器的数据也不是用来进行容器和宿主机的数据交换的,而是让 Pod 里的容器能够直接获取到这个 Pod 对象本身的一些信息。
目前Downward API提供了两种方式用于将Pod的信息注入到容器内部:

  • 环境变量:用于单个变量,可以将Pod信息和容器信息直接注入到容器内部
  • Volume挂载:将Pod信息生成为文件,直接挂载到容器内部

环境变量

通过Downward API来将Pod的IP、名称以及所对应的namespace注入到容器的环境变量中去,然后在容器中打印全部的环境变量来进行验证,对应的资源清单文件如下:(env-pod.yaml)

apiVersion: v1
kind: Pod
metadata:
  name: env-pod
  namespace: kube-system
spec:
  containers:
  - name: env-pod
    image: busybox
    command: ["/bin/sh", "-c", "env"]
    env:
    - name: POD_NAME
      valueFrom:
        fieldRef:
          fieldPath: metadata.name
    - name: POD_NAMESPACE
      valueFrom:
        fieldRef:
          fieldPath: metadata.namespace
    - name: POD_IP
      valueFrom:
        fieldRef:
          fieldPath: status.podIP

上面使用了一种新的方式来设置env的值:valueFrom,由于Pod的name和namespace属于元数据,是在pod创建之前就已经定下来的,所以使用metadata就可以获取到,但是对于Pod的IP则不一样,因为Pod的IP是不固定的,Pod重建了就变了,他属于状态数据,所以使用status这个属性去获取。另外除了使用fieldRef获取Pod的基本信息外,还可以通过resourceFieldRef去获取容器的资源请求和资源限制信息。

直接创建上面的Pod:

$ kubectl create -f env-pod.yaml
pod "env-pod" created

Pod创建成功后,可以查看日志:

$ kubectl logs -f env-pod -n kube-system
POD_IP=10.244.1.121
KUBERNETES_SERVICE_PORT=443
KUBERNETES_PORT=tcp://10.96.0.1:443
KUBE_DNS_SERVICE_PORT_DNS_TCP=53
HOSTNAME=env-pod
SHLVL=1
HOME=/root
KUBE_DNS_SERVICE_HOST=10.96.0.10
KUBE_DNS_PORT_9153_TCP_ADDR=10.96.0.10
KUBE_DNS_PORT_9153_TCP_PORT=9153
KUBE_DNS_PORT_9153_TCP_PROTO=tcp
KUBE_DNS_SERVICE_PORT=53
KUBE_DNS_PORT=udp://10.96.0.10:53
POD_NAME=env-pod
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
KUBE_DNS_PORT_53_TCP_ADDR=10.96.0.10
KUBERNETES_PORT_443_TCP_PORT=443
KUBE_DNS_SERVICE_PORT_METRICS=9153
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBE_DNS_PORT_9153_TCP=tcp://10.96.0.10:9153
KUBE_DNS_PORT_53_UDP_ADDR=10.96.0.10
KUBE_DNS_PORT_53_TCP_PORT=53
KUBE_DNS_PORT_53_TCP_PROTO=tcp
KUBE_DNS_PORT_53_UDP_PORT=53
KUBE_DNS_SERVICE_PORT_DNS=53
KUBE_DNS_PORT_53_UDP_PROTO=udp
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
POD_NAMESPACE=kube-system
KUBERNETES_SERVICE_HOST=10.96.0.1
PWD=/
KUBE_DNS_PORT_53_TCP=tcp://10.96.0.10:53
KUBE_DNS_PORT_53_UDP=udp://10.96.0.10:53

可以看到Pod的IP、Name、NAMESPACE都通过环境变量打印出来了。

Volume挂载

Downward API除了提供环境变量的方式外,还提供了通过 Volume 挂载的方式去获取 Pod 的基本信息。接下来我们通过Downward API将 Pod 的 Label、Annotation 等信息通过 Volume 挂载到容器的某个文件中去,然后在容器中打印出该文件的值来验证,对应的资源清单文件如下所示:(volume-pod.yaml)

apiVersion: v1
kind: Pod
metadata:
  name: volume-pod
  namespace: kube-system
  labels:
    k8s-app: test-volume
    node-env: test
  annotations:
    own: youdianzhishi
    build: test
spec:
  volumes:
  - name: podinfo
    downwardAPI:
      items:
      - path: labels
        fieldRef:
          fieldPath: metadata.labels
      - path: annotations
        fieldRef:
          fieldPath: metadata.annotations
  containers:
  - name: volume-pod
    image: busybox
    args: 
    - sleep 
    - "3600"
    volumeMounts:
    - name: podinfo
      mountPath: /etc/podinfo

将元数据 labels 和 annotaions 以文件的形式挂载到了 /etc/podinfo 目录下,创建上面的 Pod:

$ kubectl create -f volume-pod.yaml
pod "volume-pod" created

创建成功后,可以进入到容器中查看元信息是不是已经存入到文件中了:

$ kubectl exec -it volume-pod /bin/sh -n kube-system
/ # ls /etc/podinfo/
..2019_11_13_09_57_15.990445016/  annotations
..data/                           labels
/ # cat /etc/podinfo/labels
k8s-app="test-volume"
/ # cat /etc/podinfo/annotations
build="test"
kubectl.kubernetes.io/last-applied-configuration="{\"apiVersion\":\"v1\",\"kind\":\"Pod\",\"metadata\":{\"annotations\":{\"build\":\"test\",\"own\":\"youdianzhishi\"},\"labels\":{\"k8s-app\":\"test-volume\",\"node-env\":\"test\"},\"name\":\"volume-pod\",\"namespace\":\"kube-system\"},\"spec\":{\"containers\":[{\"args\":[\"sleep\",\"3600\"],\"image\":\"busybox\",\"name\":\"volume-pod\",\"volumeMounts\":[{\"mountPath\":\"/etc/podinfo\",\"name\":\"podinfo\"}]}],\"volumes\":[{\"downwardAPI\":{\"items\":[{\"fieldRef\":{\"fieldPath\":\"metadata.labels\"},\"path\":\"labels\"},{\"fieldRef\":{\"fieldPath\":\"metadata.annotations\"},\"path\":\"annotations\"}]},\"name\":\"podinfo\"}]}}\n"
kubernetes.io/config.seen="2019-11-13T17:57:15.320894744+08:00"
kubernetes.io/config.source="api"

可以看到 Pod 的 Labels 和 Annotations 信息都被挂载到 /etc/podinfo 目录下面的 lables 和 annotations 文件了。
目前,Downward API 支持的字段已经非常丰富了,比如:

1. 使用 fieldRef 可以声明使用:

spec.nodeName - 宿主机名字
status.hostIP - 宿主机IP
metadata.name - Pod的名字
metadata.namespace - Pod的Namespace
status.podIP - Pod的IP
spec.serviceAccountName - Pod的Service Account的名字
metadata.uid - Pod的UID
metadata.labels['<KEY>'] - 指定<KEY>的Label值
metadata.annotations['<KEY>'] - 指定<KEY>的Annotation值
metadata.labels - Pod的所有Label
metadata.annotations - Pod的所有Annotation

2. 使用 resourceFieldRef 可以声明使用:

容器的 CPU limit
容器的 CPU request
容器的 memory limit
容器的 memory request

在实际应用中,如果你的应用有获取 Pod 的基本信息的需求,一般就可以利用Downward API来获取基本信息,然后编写一个启动脚本或者利用initContainer将 Pod 的信息注入到容器中去,然后在自己的应用中就可以正常的处理相关逻辑了。
除了通过 Downward API 可以获取到 Pod 本身的信息之外,其实还可以通过映射其他资源对象来获取对应的信息,比如 Secret、ConfigMap 资源对象,同样可以通过环境变量和挂载 Volume 的方式来获取他们的信息,但是,通过环境变量获取这些信息的方式,不具备自动更新的能力。所以,一般情况下,都建议使用 Volume 文件的方式获取这些信息,因为通过 Volume 的方式挂载的文件在 Pod 中会进行热更新。

PodPreset