k8s 深入篇———— pod 深入實戰[七]

前言

深入一下pod 實戰。

正文

在 Kubernetes 中,有幾種特殊的 Volume,它們存在的意義不是爲了存放容器裏的數據,也不是用來進行容器和宿主機之間的數據交換。

這些特殊 Volume 的作用,是爲容器提供預先定義好的數據。

所以,從容器的角度來看,這些 Volume 裏的信息就是彷彿是被Kubernetes“投射”(Project)進入容器當中的。這正是 Projected Volume 的含義。

到目前爲止,Kubernetes 支持的 Projected Volume 一共有四種:

  1. Secret;
  2. ConfigMap;
  3. Downward API;
  4. ServiceAccountToken。

在今天這篇文章中,我首先和你分享的是 Secret。它的作用,是幫你把 Pod 想要訪問的加密數據,存放到 Etcd 中。

然後,你就可以通過在 Pod 的容器裏掛載 Volume 的方式,訪問到這些 Secret 裏保存的信息了。

Secret 最典型的使用場景,莫過於存放數據庫的 Credential 信息,比如下面這個例子:

例如:

apiVersion: v1
kind: Pod
metadata:
  name: test-projected-volume
spec:
  containers:
  - name: test-secret-volume
    image: busybox
    args:
      - sleep
      - "86400"
    volumeMounts:
    - name: mysql-cred
      mountPath: "/projected-volume"
      readOnly: true
  volumes:
  - name: mysql-cred
    projected:
      sources:
      - secret:
          name: user
      - secret:
          name: pass

這個啓動會報錯,因爲現在還沒有創建secret/user資源和 secret/pass 資源。

那麼創建一下:

kubectl create secret generic user --from-file=./username.txt
kubectl create secret generic pass --from-file=./password.txt

裏面可以隨便寫一些東西。

那麼我們看下掛載的是什麼吧。

可以看到,將映射後的內容到了裏面。

來看下docker的情況吧。

其實就是創建了一個volume,然後將兩個文件放入進去了,然後容器再進行映射。

在這個 Pod 中,我定義了一個簡單的容器。它聲明掛載的 Volume,並不是常見的emptyDir 或者 hostPath 類型,

而是 projected 類型。而這個 Volume 的數據來源(sources),則是名爲 user 和 pass 的 Secret 對象,分別對應的是數據庫的用戶名和密碼。

還可以通過yaml的方式聲明,一般都是這種方式哈:

可以看到,通過編寫 YAML 文件創建出來的 Secret 對象只有一個。但它的 data 字段,卻以 Key-Value 的格式保存了兩份 Secret 數據。其中,“user”就是第一份數據的Key,“pass”是第二份數據的 Key。

需要注意的是,Secret 對象要求這些數據必須是經過 Base64 轉碼的,以免出現明文密碼
的安全隱患。這個轉碼操作也很簡單,比如:

這裏需要注意的是,像這樣創建的 Secret 對象,它裏面的內容僅僅是經過了轉碼,而並沒有被加密。

在真正的生產環境中,你需要在 Kubernetes 中開啓 Secret 的加密插件,增強數據的安全性。

從返回結果中,我們可以看到,保存在 Etcd 裏的用戶名和密碼信息,已經以文件的形式出
現在了容器的 Volume 目錄裏。而這個文件的名字,就是 kubectl create secret 指定的
Key,或者說是 Secret 對象的 data 字段指定的 Key。

更重要的是,像這樣通過掛載方式進入到容器裏的 Secret,一旦其對應的 Etcd 裏的數據被
更新,這些 Volume 裏的文件內容,同樣也會被更新。其實,這是 kubelet 組件在定時維
護這些 Volume。

需要注意的是,這個更新可能會有一定的延時。所以在編寫應用程序時,在發起數據庫連接的代碼處寫好重試和超時的邏輯,絕對是個好習慣。

與 Secret 類似的是 ConfigMap,它與 Secret 的區別在於,ConfigMap 保存的是不需要加密的、應用所需的配置信息。

而 ConfigMap 的用法幾乎與 Secret 完全相同:你可以使用 kubectl create configmap 從文件或者目錄創建 ConfigMap,也可以直接編寫ConfigMap 對象的 YAML 文件。

備註:kubectl get -o yaml 這樣的參數,會將指定的 Pod API 對象以YAML 的方式展示出來。

接下來是 Downward API,它的作用是:讓 Pod 裏的容器能夠直接獲取到這個 Pod API對象本身的信息。

下面是downward api:

apiVersion: v1
kind: Pod
metadata:
  name: test-downwardapi-volume
  labels:
    zone: us-est-coast
    cluster: test-cluster1
    rack: rack-22
spec:
  containers:
    - name: client-container
      image: busybox
      imagePullPolicy: IfNotPresent
      command: ["sh", "-c"]
      args:
      - while true; do
          if [[ -e /etc/podinfo/labels ]]; then
            echo -en '\n\n'; cat /etc/podinfo/labels; fi;
          sleep 500;
        done;
      volumeMounts:
        - name: podinfo
          mountPath: /etc/podinfo
          readOnly: false
  volumes:
    - name: podinfo
      projected:
        sources:
        - downwardAPI:
            items:
              - path: "labels"
                fieldRef:
                  fieldPath: metadata.labels

在這個 Pod 的 YAML 文件中,我定義了一個簡單的容器,聲明瞭一個 projected 類型的
Volume。只不過這次 Volume 的數據來源,變成了 Downward API。而這個 Downward
API Volume,則聲明瞭要暴露 Pod 的 metadata.labels 信息給容器。
通過這樣的聲明方式,當前 Pod 的 Labels 字段的值,就會被 Kubernetes 自動掛載成爲
容器裏的 /etc/podinfo/labels 文件。
而這個容器的啓動命令,則是不斷打印出 /etc/podinfo/labels 裏的內容。所以,當我創建
了這個 Pod 之後,就可以通過 kubectl logs 指令,查看到這些 Labels 字段被打印出來,
如下所示:

其實就是把label的信息掛載到了labels文件下面。

具體可查看官方文檔。

其實,Secret、ConfigMap,以及 Downward API 這三種 Projected Volume 定義的信息,大多還可以通過環境變量的方式出現在容器裏。

但是,通過環境變量獲取這些信息的方式,不具備自動更新的能力。所以,一般情況下,我都建議你使用 Volume 文件的方式獲取這些信息。

相信你一定有過這樣的想法:我現在有了一個 Pod,我能不能在這個 Pod 裏安裝一個
Kubernetes 的 Client,這樣就可以從容器裏直接訪問並且操作這個 Kubernetes 的 API 了
呢?
這當然是可以的。
不過,你首先要解決 API Server 的授權問題。
Service Account 對象的作用,就是 Kubernetes 系統內置的一種“服務賬戶”,它是
Kubernetes 進行權限分配的對象。比如,Service Account A,可以只被允許對
Kubernetes API 進行 GET 操作,而 Service Account B,則可以有 Kubernetes API 的所
有操作的權限。
像這樣的 Service Account 的授權信息和文件,實際上保存在它所綁定的一個特殊的
Secret 對象裏的。這個特殊的 Secret 對象,就叫作ServiceAccountToken。任何運行在
Kubernetes 集羣上的應用,都必須使用這個 ServiceAccountToken 裏保存的授權信息,
也就是 Token,纔可以合法地訪問 API Server。
所以說,Kubernetes 項目的 Projected Volume 其實只有三種,因爲第四種
ServiceAccountToken,只是一種特殊的 Secret 而已。

另外,爲了方便使用,Kubernetes 已經爲你提供了一個的默認“服務賬戶”(default
Service Account)。並且,任何一個運行在 Kubernetes 裏的 Pod,都可以直接使用這個
默認的 Service Account,而無需顯示地聲明掛載它。

這是如何做到的呢?
當然還是靠 Projected Volume 機制。
如果你查看一下任意一個運行在 Kubernetes 集羣裏的 Pod,就會發現,每一個 Pod,都
已經自動聲明一個類型是 Secret、名爲 default-token-xxxx 的 Volume,然後 自動掛載
在每個容器的一個固定目錄上。比如:

默認掛載了這兩個。

這個 Secret 類型的 Volume,正是默認 Service Account 對應的
ServiceAccountToken。所以說,Kubernetes 其實在每個 Pod 創建的時候,自動在它的
spec.volumes 部分添加上了默認 ServiceAccountToken 的定義,然後自動給每個容器加
上了對應的 volumeMounts 字段。這個過程對於用戶來說是完全透明的。
這樣,一旦 Pod 創建完成,容器裏的應用就可以直接從這個默認 ServiceAccountToken
的掛載目錄裏訪問到授權信息和文件。這個容器內的路徑在 Kubernetes 裏是固定的,
即:/var/run/secrets/kubernetes.io/serviceaccount ,而這個 Secret 類型的 Volume
裏面的內容如下所示:

所以,你的應用程序只要直接加載這些授權文件,就可以訪問並操作 Kubernetes API 了。
而且,如果你使用的是 Kubernetes 官方的 Client 包(k8s.io/client-go)的話,它還
可以自動加載這個目錄下的文件,你不需要做任何配置或者編碼操作。

這種把 Kubernetes 客戶端以容器的方式運行在集羣裏,然後使用 default Service
Account 自動授權的方式,被稱作“InClusterConfig”。

當然,考慮到自動掛載默認 ServiceAccountToken 的潛在風險,Kubernetes 允許你設置
默認不爲 Pod 裏的容器自動掛載這個 Volume。

除了這個默認的 Service Account 外,我們很多時候還需要創建一些我們自己定義的
Service Account,來對應不同的權限設置。這樣,我們的 Pod 裏的容器就可以通過掛載
這些 Service Account 對應的 ServiceAccountToken,來使用這些自定義的授權信息。

在 Kubernetes 中,你可以爲 Pod 裏的容器定義一個健康檢查“探針”(Probe)。這樣,kubelet 就會根據這個 Probe 的返回值決定這個容器的狀態,而不是直接以容器進行是否運行(來自 Docker 返回的信息)作爲依據。

這種機制,是生產環境中保證應用健康存活的重要手段。


apiVersion: v1
kind: Pod
metadata:
  labels:
    test: liveness
  name: test-liveness-exec
spec:
  containers:
  - name: liveness
    image: daocloud.io/library/nginx
    args:
    - /bin/sh
    - -c  
    - touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 50
    livenessProbe:    #探針,健康檢查
      exec:    #類型
        command:  #命令
        - cat 
        - /tmp/healthy
      initialDelaySeconds: 5   #健康檢查,在容器啓動 5 s 後開始執行
      periodSeconds: 5   #每 5 s 執行一次

然後這個監控檢查不通過之後,那麼就會出現一個東西,叫做pod的恢復機制,也叫restartPolicy。

。它是 Pod 的 Spec 部
分的一個標準字段(pod.spec.restartPolicy),默認值是 Always,即:任何時候這個容
器發生了異常,它一定會被重新創建。
但一定要強調的是,Pod 的恢復過程,永遠都是發生在當前節點上,而不會跑到別的節點
上去。事實上,一旦一個 Pod 與一個節點(Node)綁定,除非這個綁定發生了變化
(pod.spec.node 字段被修改),否則它永遠都不會離開這個節點。這也就意味着,如果
這個宿主機宕機了,這個 Pod 也不會主動遷移到其他節點上去。

而如果你想讓 Pod 出現在其他的可用節點上,就必須使用 Deployment 這樣的“控制器”來管理 Pod,哪怕你只需要一個 Pod 副本。

而作爲用戶,你還可以通過設置 restartPolicy,改變 Pod 的恢復策略。除了 Always,它
還有 OnFailure 和 Never 兩種情況:
Always:在任何情況下,只要容器不在運行狀態,就自動重啓容器;
OnFailure: 只在容器 異常時才自動重啓容器;
Never: 從來不重啓容器。

  1. 只要 Pod 的 restartPolicy 指定的策略允許重啓異常的容器(比如:Always),那麼
    這個 Pod 就會保持 Running 狀態,並進行容器重啓。否則,Pod 就會進入 Failed 狀
    態 。
  2. 對於包含多個容器的 Pod,只有它裏面所有的容器都進入異常狀態後,Pod 纔會進入
    Failed 狀態。在此之前,Pod 都是 Running 狀態。此時,Pod 的 READY 字段會顯示
    正常容器的個數,

所以,假如一個 Pod 裏只有一個容器,然後這個容器異常退出了。那麼,只有當
restartPolicy=Never 時,這個 Pod 纔會進入 Failed 狀態。而其他情況下,由於
Kubernetes 都可以重啓這個容器,所以 Pod 的狀態保持 Running 不變。
而如果這個 Pod 有多個容器,僅有一個容器異常退出,它就始終保持 Running 狀態,哪
怕即使 restartPolicy=Never。只有當所有容器也異常退出之後,這個 Pod 纔會進入
Failed 狀態

在 Kubernetes 的 Pod 中,還有一個叫 readinessProbe 的字段。雖然它的用法與
livenessProbe 類似,但作用卻大不一樣。readinessProbe 檢查結果的成功與否,決定的
這個 Pod 是不是能被通過 Service 的方式訪問到,而並不影響 Pod 的生命週期。

下面是Pod Preset例子:

kind: PodPreset
apiVersion: settings.k8s.io/v1alpha1
metadata:
  name: allow-database
  namespace: myns
spec:
  selector:
    matchLabels:
      role: frontend
  env:
    - name: DB_PORT
      value: "6379"
  volumeMounts:
    - mountPath: /cache
      name: cache-volume
  volumes:
    - name: cache-volume
      emptyDir: {}

然後編寫pod.yaml:


apiVersion: v1
kind: Pod
metadata:
  name: website
  labels:
    app: website
    role: frontend
spec:
  containers:
    - name: website
      image: ecorp/website
      ports:
        - containerPort: 80

然後最終生成的yaml爲:


apiVersion: v1
kind: Pod
metadata:
  name: website
  labels:
    app: website
    role: frontend
  annotations:
    podpreset.admission.kubernetes.io/allow-database: "resource version"
spec:
  containers:
    - name: website
      image: ecorp/website
      volumeMounts:
        - mountPath: /cache
          name: cache-volume
      ports:
        - containerPort: 80
      env:
        - name: DB_PORT
          value: "6379"
  volumes:
    - name: cache-volume
      emptyDir: {}

比如,我們現在提交的是一個 nginx-deployment,那麼這個 Deployment 對象本身是永
遠不會被 PodPreset 改變的,被修改的只是這個 Deployment 創建出來的所有 Pod。這
一點請務必區分清楚。
這裏有一個問題:如果你定義了同時作用於一個 Pod 對象的多個 PodPreset,會發生什麼
呢?
實際上,Kubernetes 項目會幫你合併(Merge)這兩個 PodPreset 要做的修改。而如果
它們要做的修改有衝突的話,這些衝突字段就不會被修改。

PodPreset 這個被棄用了。

下一節,編排。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章