從應用訪問Pod元資料-DownwardApi的應用

語言: CN / TW / HK

對於某些需要排程之後才能知道的資料,比如 pod 的 ip,主機名,或者 pod 自身的名稱等等,k8s 依舊很貼心的提供了 Downward API 的方式來獲取此類資料,並且可以通過環境變數或者檔案(downwardApi卷中)來傳遞 pod 的元資料。

可以傳遞的容器資料包括如下:

  • pod的名稱,IP,所在名稱空間,執行節點的名稱,執行所歸屬的服務賬戶名稱
  • 每個容器請求的 CPU 和記憶體的使用量
  • 每個容器可以使用的 CPU 和記憶體的限制
  • pod 的標籤
  • pod 的註解

通過環境變數暴露元資料

建立一個但容器的 pod

$ vim ./dowanwardapi-learn.yaml

apiVersion: v1
kind: Pod
metadata:
  name: downward-learn
spec:
  containers:
  - name: main
    image: busybox
    command: ["sleep", "999999"]
    resources:
      requests:
        cpu: "15m"
        memory: "100Ki"
      limits:
        cpu: "100m"
        memory: "4Mi"
    env:
    - name: POD_NAME
      valueFrom:
        fieldRef:
          fieldPath: metadata.name
    - name: POD_IP
      valueFrom:
        fieldRef:
          fieldPath: status.podIP
    - name: NODE_NAME
      valueFrom:
        fieldRef:
          fieldPath: spec.nodeName
    - name: CONTAINER_CPU_REQUEST_MILLICORES
      valueFrom:
        resourceFieldRef:
          resource: requests.cpu
          divisor: "1m"
    - name: CONTAINER_MEMORY_LIMIT_KIBIBYTES
      valueFrom:
        resourceFieldRef:
          resource: limits.memory
          divisor: "1Ki"

建立完成後我們可以使用 kubectl exec 命令來檢視容器中的所有環境變數,如下:

$ kubectl exec downward-learn env
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl kubectl exec [POD] -- [COMMAND] instead.
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=downward-learn
POD_NAME=downward-learn
POD_IP=10.44.0.3
NODE_NAME=node1
CONTAINER_CPU_REQUEST_MILLICORES=15
CONTAINER_MEMORY_LIMIT_KIBIBYTES=4096
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP_PORT=443
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
KUBERNETES_SERVICE_HOST=10.96.0.1
KUBERNETES_SERVICE_PORT=443
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_PORT=tcp://10.96.0.1:443
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
HOME=/root

但是有一些變數並不能根據環境變數暴露,比如 lables 標籤和 annotations 註釋等等,但是這些可以使用過 dowanwardApi 卷的方式來進行載入。

通過 downwardAPI 捲來傳遞元資料

和環境變數一樣,通過檔案卷的方式也需要顯式指定元資料欄位來暴露給程序,如下:

apiVersion: v1
kind: Pod
metadata:
  name: downward-learn
spec:
  containers:
  - name: main
    image: busybox
    command: ["sleep", "999999"]
    resources:
      requests:
        cpu: "15m"
        memory: "100Ki"
      limits:
        cpu: "100m"
        memory: "4Mi"
    volumeMounts:
    - name: downward
      mountPath: /etc/downard
  volumes:
  - name: downward
    downwardAPI:
      iterms:
      - path: "podName"
        fileRef:
          fieldPath: metadata.name
......    

其中宣告元資料和配置的方式沒什麼不同。

但是其中需要注意的是,如果是暴露容器級的元資料時,比如容器可使用的資源限制和資源請求(如使用欄位 resourceFieldRef),必須指定引入資源欄位的容器名稱,比如:

spec:
  volumes:
  - name: downward
    downwardAPI:
      items:
      - path: "containerCpuRequestMilliCores"
        resourceFieldRef: 
          containerName: main      ## 容器名稱
          resource: requests.cpu
          divisor: 1m

這裡由於引入了cpu資源限制,所以也貼一下所用到的,在 pod 內查詢當前的 cpu 使用程序程式碼,因為 pod 容器內的查詢 cpu 使用和物理機上的還不太一樣,在網上找了一些但是都不太適合容器使用,所以自己寫了一個

$ vim cpu.go

package handler

import (
    "fmt"
    "os"

    linuxproc "github.com/c9s/goprocinfo/linux"
)

var (
    prevUsageUser   int64 = 0
    prevUsageSystem int64 = 0
    cpuNum = 1    // 設定該容器內使用的cpu個數
)

func GetCpuCount(stat *linuxproc.Stat) (count float64) {
    cfsQuota := getCgoupValueByPath("/sys/fs/cgroup/cpu/cpu.cfs_quota_us")
    cfsPeriod := getCgoupValueByPath("/sys/fs/cgroup/cpu/cpu.cfs_period_us")

    if cfsQuota == -1 {
        return float64(len(stat.CPUStats))
    }

    return float64(cfsQuota) / float64(cfsPeriod)
}

func CpuCountToString(c float64) string {
    if c == float64(int64(c)) {
        return fmt.Sprintf("%v", c)
    }
    return fmt.Sprintf("%0.1f", c)
}

// GetCpuUsage should be called every 1 seconds. not quite precise.
func GetCpuUsage(cpus float64) (user, system, idle float64) {
    var currentUsageUser, currentUsageSystem int64
    currentUsageUser = getCgoupValueByPath("/sys/fs/cgroup/cpuacct/cpuacct.usage_user")
    currentUsageSystem = getCgoupValueByPath("/sys/fs/cgroup/cpuacct/cpuacct.usage_sys")

    if prevUsageUser == 0 && prevUsageSystem == 0 {
        prevUsageUser = currentUsageUser
        prevUsageSystem = currentUsageSystem
        return
    }

    user = float64(currentUsageUser-prevUsageUser) / 10000000 / cpus       // / 1000,000,000 * 100 = /10,000,000
    system = float64(currentUsageSystem-prevUsageSystem) / 10000000 / cpus // / 1000,000,000 * 100 = /10,000,000
    idle = 100 - user - system
    if idle < 0 {
        idle = 0
    }

    prevUsageUser = currentUsageUser
    prevUsageSystem = currentUsageSystem

    return
}

func getCgoupValueByPath(path string) int64 {
    data, err := os.ReadFile(path)
    if err != nil {
        return 0
    }

    var value int64
    n, err := fmt.Sscanf(string(data), "%d", &value)
    if err != nil || n != 1 {
        return 0
    }
    return value
}

計算出來當前的容器數值換算為:use 80% = 800

這樣在容器內就能根據元資料和監控指令碼時刻監控容器的 cpu 使用率了。