Kubernetes(k8s)通过环境变量将 Pod 信息呈现给容器
阅读原文时间:2023年07月10日阅读:2

Downward API

有两种方式可以将 Pod 和 Container 字段呈现给运行中的容器:

  • 环境变量
  • 卷文件

这两种呈现 Pod 和 Container 字段的方式统称为 Downward API。

使用环境变量的方式

Pod 的配置文件

cat test_pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: dapi-envars-fieldref
spec:
  containers:
    - name: test-container
      image: busybox
      command: [ "sh", "-c"]
      args:
      - while true; do
          echo -en '\n';
          printenv MY_NODE_NAME MY_POD_NAME MY_POD_NAMESPACE;
          printenv MY_POD_IP MY_POD_SERVICE_ACCOUNT;
          sleep 10;
        done;
      env:
        - name: MY_NODE_NAME
          valueFrom:
            fieldRef:
              fieldPath: spec.nodeName
        - name: MY_POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: MY_POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        - name: MY_POD_IP
          valueFrom:
            fieldRef:
              fieldPath: status.podIP
        - name: MY_POD_SERVICE_ACCOUNT
          valueFrom:
            fieldRef:
              fieldPath: spec.serviceAccountName
  restartPolicy: Never

这个配置文件中,你可以看到五个环境变量。env 字段是一个 EnvVars. 对象的数组。 数组中第一个元素指定 MY_NODE_NAME 这个环境变量从 Pod 的 spec.nodeName 字段获取变量值。 同样,其它环境变量也是从 Pod 的字段获取它们的变量值。

说明: 本示例中的字段是 Pod 字段,不是 Pod 中 Container 的字段。

# 创建Pod
# kubectl apply -f test_pod.yaml
pod/dapi-envars-fieldref created

# 验证 Pod 中的容器运行正常
# kubectl get pods
NAME                   READY   STATUS    RESTARTS   AGE
dapi-envars-fieldref   1/1     Running   0          5s

# 查看容器日志,输出信息显示了所选择的环境变量的值
# kubectl logs dapi-envars-fieldref

develop-worker-2
dapi-envars-fieldref
default
10.0.2.110
default

要了解为什么这些值在日志中,请查看配置文件中的command和 args字段。 当容器启动时,它将五个环境变量的值写入stdout。每十秒重复执行一次。

接下来,通过打开一个 Shell 进入 Pod 中运行的容器:

# kubectl exec -it dapi-envars-fieldref /bin/sh
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.

# 在 Shell 中,查看环境变量
/ # printenv
MY_POD_SERVICE_ACCOUNT=default
KUBERNETES_SERVICE_PORT=443
KUBERNETES_PORT=tcp://10.3.255.1:443
DASHBOARD_KUBERNETES_DASHBOARD_SERVICE_HOST=10.3.255.88
HOSTNAME=dapi-envars-fieldref
DASHBOARD_KUBERNETES_DASHBOARD_PORT_9090_TCP_ADDR=10.3.255.88
SHLVL=1
HOME=/root
DASHBOARD_KUBERNETES_DASHBOARD_PORT_9090_TCP_PORT=9090
DASHBOARD_KUBERNETES_DASHBOARD_PORT_9090_TCP_PROTO=tcp
DASHBOARD_KUBERNETES_DASHBOARD_SERVICE_PORT=9090
DASHBOARD_KUBERNETES_DASHBOARD_PORT=tcp://10.3.255.88:9090
MY_POD_NAMESPACE=default
DASHBOARD_KUBERNETES_DASHBOARD_PORT_9090_TCP=tcp://10.3.255.88:9090
TERM=xterm
MY_POD_IP=10.0.2.110
KUBERNETES_PORT_443_TCP_ADDR=10.3.255.1
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
KUBERNETES_PORT_443_TCP_PORT=443
KUBERNETES_PORT_443_TCP_PROTO=tcp
MY_NODE_NAME=develop-worker-2
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_PORT_443_TCP=tcp://10.3.255.1:443
KUBERNETES_SERVICE_HOST=10.3.255.1
PWD=/
DASHBOARD_KUBERNETES_DASHBOARD_SERVICE_PORT_HTTP=9090
MY_POD_NAME=dapi-envars-fieldref

包含一个容器的 Pod 的配置文件

# cat test_pod1.yaml
apiVersion: v1
kind: Pod
metadata:
  name: dapi-envars-resourcefieldref
spec:
  containers:
    - name: test-container
      image: busybox
      command: [ "sh", "-c"]
      args:
      - while true; do
          echo -en '\n';
          printenv MY_CPU_REQUEST MY_CPU_LIMIT;
          printenv MY_MEM_REQUEST MY_MEM_LIMIT;
          sleep 10;
        done;
      resources:
        requests:
          memory: "32Mi"
          cpu: "125m"
        limits:
          memory: "64Mi"
          cpu: "250m"
      env:
        - name: MY_CPU_REQUEST
          valueFrom:
            resourceFieldRef:
              containerName: test-container # 上面设置的容器名称
              resource: requests.cpu
        - name: MY_CPU_LIMIT
          valueFrom:
            resourceFieldRef:
              containerName: test-container
              resource: limits.cpu
        - name: MY_MEM_REQUEST
          valueFrom:
            resourceFieldRef:
              containerName: test-container
              resource: requests.memory
        - name: MY_MEM_LIMIT
          valueFrom:
            resourceFieldRef:
              containerName: test-container
              resource: limits.memory
  restartPolicy: Never

这个配置文件中,你可以看到四个环境变量。env 字段是一个 EnvVars. 对象的数组。数组中第一个元素指定 MY_CPU_REQUEST 这个环境变量从 Container 的 requests.cpu 字段获取变量值。同样,其它环境变量也是从 Container 的字段获取它们的变量值。

说明: 本例中使用的是 Container 的字段而不是 Pod 的字段。

# 创建Pod
# kubectl apply -f test_pod1.yaml
pod/dapi-envars-resourcefieldref created

# 验证 Pod 中的容器运行正常
# kubectl get pods
NAME                           READY   STATUS    RESTARTS   AGE
dapi-envars-resourcefieldref   1/1     Running   0          6s

# 查看容器日志,输出信息显示了所选择的环境变量的值
# kubectl logs dapi-envars-resourcefieldref

1
1
33554432
67108864

使用文件的方式

包含一个容器的 Pod的配置文件

# cat test_pod2.yaml
apiVersion: v1
kind: Pod
metadata:
  name: kubernetes-downwardapi-volume-example
  labels:
    zone: us-est-coast
    cluster: test-cluster1
    rack: rack-22
  annotations:
    build: two
    builder: john-doe
spec:
  containers:
    - name: client-container
      image: busybox
      command: ["sh", "-c"]
      args:
      - while true; do
          if [[ -e /etc/podinfo/labels ]]; then
            echo -en '\n\n'; cat /etc/podinfo/labels; fi;
          if [[ -e /etc/podinfo/annotations ]]; then
            echo -en '\n\n'; cat /etc/podinfo/annotations; fi;
          sleep 5;
        done;
      volumeMounts:
        - name: podinfo
          mountPath: /etc/podinfo
  volumes:
    - name: podinfo
      downwardAPI:
        items:
          - path: "labels"
            fieldRef:
              fieldPath: metadata.labels
          - path: "annotations"
            fieldRef:
              fieldPath: metadata.annotations

在配置文件中,你可以看到 Pod 有一个 downwardAPI 类型的卷,并且挂载到容器中的 /etc/podinfo 目录。

查看 downwardAPI 下面的 items 数组。 每个数组元素都是一个 DownwardAPIVolumeFile 对象。 第一个元素指示 Pod 的 metadata.labels 字段的值保存在名为 labels 的文件中。 第二个元素指示 Pod 的 annotations 字段的值保存在名为 annotations 的文件中。

说明: 本示例中的字段是Pod字段,不是Pod中容器的字段。

# 创建 Pod
# kubectl apply -f test_pod2.yaml
pod/kubernetes-downwardapi-volume-example created

# 验证Pod中的容器运行正常
# kubectl get pods
NAME                                    READY   STATUS    RESTARTS   AGE
kubernetes-downwardapi-volume-example   1/1     Running   0          47s

# 查看容器的日志,输出显示 labels 和 annotations 文件的内容
# kubectl logs kubernetes-downwardapi-volume-example

cluster="test-cluster1"
rack="rack-22"
zone="us-est-coast"

build="two"
builder="john-doe"
kubectl.kubernetes.io/last-applied-configuration="{\"apiVersion\":\"v1\",\"kind\":\"Pod\",\"metadata\":{\"annotations\":{\"build\":\"two\",\"builder\":\"john-doe\"},\"labels\":{\"cluster\":\"test-cluster1\",\"rack\":\"rack-22\",\"zone\":\"us-est-coast\"},\"name\":\"kubernetes-downwardapi-volume-example\",\"namespace\":\"default\"},\"spec\":{\"containers\":[{\"args\":[\"while true; do if [[ -e /etc/podinfo/labels ]]; then echo -en '\\\\n\\\\n'; cat /etc/podinfo/labels; fi; if [[ -e /etc/podinfo/annotations ]]; then echo -en '\\\\n\\\\n'; cat /etc/podinfo/annotations; fi; sleep 5; done;\"],\"command\":[\"sh\",\"-c\"],\"image\":\"busybox\",\"name\":\"client-container\",\"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="2021-05-17T15:31:02.676723051+08:00"
kubernetes.io/config.source="api"

# 进入 Pod 中运行的容器,打开一个 Shell
# kubectl exec -it kubernetes-downwardapi-volume-example -- sh

# 在该 Shell中,查看 labels 文件,输出显示 Pod 的所有标签都已写入 labels 文件
/ # cat /etc/podinfo/labels
cluster="test-cluster1"
rack="rack-22"

# 查看annotations文件,
/ # cat /etc/podinfo/annotations
build="two"
builder="john-doe"
kubectl.kubernetes.io/last-applied-configuration="{\"apiVersion\":\"v1\",\"kind\":\"Pod\",\"metadata\":{\"annotations\":{\"build\":\"two\",\"builder\":\"john-doe\"},\"labels\":{\"cluster\":\"test-cluster1\",\"rack\":\"rack-22\",\"zone\":\"us-est-coast\"},\"name\":\"kubernetes-downwardapi-volume-example\",\"namespace\":\"default\"},\"spec\":{\"containers\":[{\"args\":[\"while true; do if [[ -e /etc/podinfo/labels ]]; then echo -en '\\\\n\\\\n'; cat /etc/podinfo/labels; fi; if [[ -e /etc/podinfo/annotations ]]; then echo -en '\\\\n\\\\n'; cat /etc/podinfo/annotations; fi; sleep 5; done;\"],\"command\":[\"sh\",\"-c\"],\"image\":\"busybox\",\"name\":\"client-container\",\"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="2021-05-17T15:31:02.676723051+08:00"

# 查看/etc/podinfo目录下的文件
/ # ls -al /etc/podinfo/
total 4
drwxrwxrwt    3 root     root           120 May 17 07:31 .
drwxr-xr-x    1 root     root          4096 May 17 07:31 ..
drwxr-xr-x    2 root     root            80 May 17 07:31 ..2021_05_17_07_31_03.519523692
lrwxrwxrwx    1 root     root            31 May 17 07:31 ..data -> ..2021_05_17_07_31_03.519523692
lrwxrwxrwx    1 root     root            18 May 17 07:31 annotations -> ..data/annotations
lrwxrwxrwx    1 root     root            13 May 17 07:31 labels -> ..data/labels

# 在输出中可以看到,labels 和 annotations 文件都在一个临时子目录中。 在这个例子是..2021_05_17_07_31_03.519523692。 在 /etc/podinfo 目录中,..data 是一个指向临时子目录 的符号链接。/etc/podinfo 目录中,labels 和 annotations 也是符号链接。

/ # ls -al /etc/podinfo/..2021_05_17_07_31_03.519523692/
total 8
drwxr-xr-x    2 root     root            80 May 17 07:31 .
drwxrwxrwt    3 root     root           120 May 17 07:31 ..
-rw-r--r--    1 root     root          1102 May 17 07:31 annotations
-rw-r--r--    1 root     root            58 May 17 07:31 labels

# 用符号链接可实现元数据的动态原子性刷新;更新将写入一个新的临时目录, 然后通过使用rename(2) 完成 ..data 符号链接的原子性更新。
# 说明: 如果容器以 subPath卷挂载方式来使用 Downward API,则该容器无法收到更新事件。

包含一个容器的 Pod 的配置文件

# cat test_pod3.yaml
apiVersion: v1
kind: Pod
metadata:
  name: kubernetes-downwardapi-volume-example-2
spec:
  containers:
    - name: client-container
      image: busybox
      command: ["sh", "-c"]
      args:
      - while true; do
          echo -en '\n';
          if [[ -e /etc/podinfo/cpu_limit ]]; then
            echo -en '\n'; cat /etc/podinfo/cpu_limit; fi;
          if [[ -e /etc/podinfo/cpu_request ]]; then
            echo -en '\n'; cat /etc/podinfo/cpu_request; fi;
          if [[ -e /etc/podinfo/mem_limit ]]; then
            echo -en '\n'; cat /etc/podinfo/mem_limit; fi;
          if [[ -e /etc/podinfo/mem_request ]]; then
            echo -en '\n'; cat /etc/podinfo/mem_request; fi;
          sleep 5;
        done;
      resources:
        requests:
          memory: "32Mi"
          cpu: "125m"
        limits:
          memory: "64Mi"
          cpu: "250m"
      volumeMounts:
        - name: podinfo
          mountPath: /etc/podinfo
  volumes:
    - name: podinfo
      downwardAPI:
        items:
          - path: "cpu_limit"
            resourceFieldRef:
              containerName: client-container
              resource: limits.cpu
              divisor: 1m
          - path: "cpu_request"
            resourceFieldRef:
              containerName: client-container
              resource: requests.cpu
              divisor: 1m
          - path: "mem_limit"
            resourceFieldRef:
              containerName: client-container
              resource: limits.memory
              divisor: 1Mi
          - path: "mem_request"
            resourceFieldRef:
              containerName: client-container
              resource: requests.memory
              divisor: 1Mi

在这个配置文件中,你可以看到 Pod 有一个 downwardAPI 类型的卷,并且挂载到容器的 /etc/podinfo 目录。查看 downwardAPI 下面的 items 数组。每个数组元素都是一个 DownwardAPIVolumeFile。第一个元素指定名为 client-container 的容器中 limits.cpu 字段的值应保存在名为 cpu_limit 的文件中。

# 创建Pod
# kubectl apply -f test_pod3.yaml
pod/kubernetes-downwardapi-volume-example-2 created

# 查看pod是否运行正常
# kubectl get pods
NAME                                      READY   STATUS    RESTARTS   AGE
kubernetes-downwardapi-volume-example-2   1/1     Running   0          7s

# 打开一个 Shell,进入 Pod 中运行的容器
# kubectl exec -it kubernetes-downwardapi-volume-example-2 -- sh

# 在 Shell 中,查看 cpu_limit 文件
/ # cat /etc/podinfo/cpu_limit
250
/ # 

# 可以使用同样的命令查看 cpu_request、mem_limit 和 mem_request 文件

Downward API 的能力

下面这些信息可以通过环境变量和 downwardAPI 卷提供给容器:

  • 能通过 fieldRef 获得的:

    • metadata.name - Pod 名称
    • metadata.namespace - Pod 名字空间
    • metadata.uid - Pod 的 UID
    • metadata.labels[''] - Pod 标签 的值 (例如, metadata.labels['mylabel'])
    • metadata.annotations[''] - Pod 的注解 的值(例如, - metadata.annotations['myannotation'])
  • 能通过 resourceFieldRef 获得的:

    • 容器的 CPU 约束值
    • 容器的 CPU 请求值
    • 容器的内存约束值
    • 容器的内存请求值
    • 容器的巨页限制值(前提是启用了 DownwardAPIHugePages 特性门控)
    • 容器的巨页请求值(前提是启用了 DownwardAPIHugePages 特性门控)
    • 容器的临时存储约束值
    • 容器的临时存储请求值

此外,以下信息可通过 downwardAPI 卷从 fieldRef 获得:

  • metadata.labels - Pod 的所有标签,以 label-key="escaped-label-value" 格式显示,每行显示一个标签
  • metadata.annotations - Pod 的所有注解,以 annotation-key="escaped-annotation-value" 格式显示,每行显示一个标签

以下信息可通过环境变量获得:

  • status.podIP - 节点 IP
  • spec.serviceAccountName - Pod 服务帐号名称, 版本要求 v1.4.0-alpha.3
  • spec.nodeName - 节点名称, 版本要求 v1.4.0-alpha.3
  • status.hostIP - 节点 IP, 版本要求 v1.7.0-alpha.1

说明: 如果容器未指定 CPU 和内存限制,则 Downward API 默认将节点可分配值 视为容器的 CPU 和内存限制。

Downward API的动机

对于容器来说,有时候拥有自己的信息是很有用的,可避免与 Kubernetes 过度耦合。 Downward API 使得容器使用自己或者集群的信息,而不必通过 Kubernetes 客户端或 API 服务器来获得。

一个例子是有一个现有的应用假定要用一个非常熟悉的环境变量来保存一个唯一标识。 一种可能是给应用增加处理层,但这样是冗余和易出错的,而且它违反了低耦合的目标。 更好的选择是使用 Pod 名称作为标识,把 Pod 名称注入这个环境变量中。