Kubernetes-Secret

1. 簡介

Secret 是一種包含少量敏感資訊例如密碼、令牌或密鑰的對象。 這樣的資訊可能會被放在 Pod 規約中或者鏡像中。 使用 Secret 意味著你不需要在應用程式程式碼中包含機密數據。

由於創建 Secret 可以獨立於使用它們的 Pod, 因此在創建、查看和編輯 Pod 的工作流程中暴露 Secret(及其數據)的風險較小。 Kubernetes 和在集群中運行的應用程式也可以對 Secret 採取額外的預防措施, 例如避免將機密數據寫入非易失性存儲。

Secret 類似於 ConfigMap 但專門用於保存機密數據。

2. 4種創建方式

$ kubectl create secret -h
Create a secret using specified subcommand.

Available Commands:
  docker-registry 創建一個給 Docker registry 使用的 secret
  generic         從本地 file, directory 或者 literal value 創建一個 secret
  tls             創建一個 TLS secret

2.1 通過key-value創建

$ kubectl create secret generic secret-test-1 --from-literal=username=zhangtieniu --from-literal=password=123456

還有種簡單的創建方式創建此種類型的ConfigMap

$ kubectl create secret generic secret-test-1 --from-env-file=./secret-env.txt

2.2 通過配置文件創建

$ kubectl create secret generic ssh-key-secret \
   --from-file=ssh-privatekey=./.ssh/id_rsa \
   --from-file=ssh-publickey=./.ssh/id_rsa.pub 

2.3 通過配置文件的目錄創建

$ kubectl create secret generic ssh-key-secret   --from-file=./

2.4 直接編寫Secret文件

在創建 Secret 編寫配置文件時,你可以設置 data 與/或 stringData 欄位。 data 和 stringData 欄位都是可選的。

data 欄位中所有鍵值都必須是 base64 編碼的字元串。

如果不希望執行這種 base64 字元串的轉換操作,你可以選擇設置 stringData 欄位,其中可以使用任何字元串作為其取值。

kubernetes.io/basic-auth 類型用來存放用於基本身份認證所需的憑據資訊。 使用這種 Secret 類型時,Secret 的 data 欄位必須包含以下兩個鍵:

  • username: 用於身份認證的用戶名;
  • password: 用於身份認證的密碼或令牌。

在創建 Secret 時使用 stringData 欄位來提供明文形式的內容。 下面的 YAML 是基本身份認證 Secret 的一個示例清單:

apiVersion: v1
kind: Secret
metadata:
  name: secret-basic-auth
type: kubernetes.io/basic-auth
stringData:
  username: zhangtieniu
  # 值必須為string類型,所以數值類型的密碼必須加引號
  password: "123456"

直接編寫secret data文件如下

apiVersion: v1
data:
  password: MTIzNDU2
  username: emhhbmd0aWVuaXU=
kind: Secret
metadata:
  name: secret-basic-auth
type: kubernetes.io/basic-auth

3. 查看/解密secret資訊

secret資訊默認是使用base64加密的,所以可以通過base64解密來查看secret配置的auth資訊

shell base64

加密:

base64 file

$ echo Hello World | base64
SGVsbG8gV29ybGQK

解密:

$ echo SGVsbG8gV29ybGQK | base64 -d
Hello World

4. secret type

創建 Secret 時,你可以使用 Secret 資源的 type 欄位, 或者與其等價的 kubectl 命令行參數(如果有的話)為其設置類型。 Secret 的 type 有助於對不同類型機密數據的編程處理。

Kubernetes 提供若干種內置的類型,用於一些常見的使用場景。 針對這些類型,Kubernetes 所執行的合法性檢查操作以及對其所實施的限制各不相同。

內置類型 用法
Opaque 用戶定義的任意數據
kubernetes.io/service-account-token 服務帳號令牌
kubernetes.io/dockercfg ~/.dockercfg 文件的序列化形式
kubernetes.io/dockerconfigjson ~/.docker/config.json 文件的序列化形式
kubernetes.io/basic-auth 用於基本身份認證的憑據
kubernetes.io/ssh-auth 用於 SSH 身份認證的憑據
kubernetes.io/tls 用於 TLS 客戶端或者伺服器端的數據
bootstrap.kubernetes.io/token 啟動引導令牌數據

通過為 Secret 對象的 type 欄位設置一個非空的字元串值,你也可以定義並使用自己 Secret 類型。如果 type 值為空字元串,則被視為 Opaque 類型。 Kubernetes 並不對類型的名稱作任何限制。不過,如果你要使用內置類型之一, 則你必須滿足為該類型所定義的所有要求。

4.1 Opaque

當 Secret 配置文件中未作顯式設定時,默認的 Secret 類型是 Opaque。 當你使用 kubectl 來創建一個 Secret 時,你會使用 generic 子命令來標明 要創建的是一個 Opaque 類型 Secret。 例如,下面的命令會創建一個空的 Opaque 類型 Secret 對象:

$ kubectl create secret generic test-secret

4.2 dockercfg/dockerconfigjson

你可以使用下面兩種 type 值之一來創建 Secret,用以存放訪問 Docker 倉庫 來下載鏡像的憑據。

  • kubernetes.io/dockercfg:舊版的docker login之後會在伺服器上生成配置文件~/.dockercfg,用來記錄docker倉庫的auth資訊
  • kubernetes.io/dockerconfigjson:新版的docker login之後會在伺服器上生成配置文件~/.docker/config.json,用來記錄docker倉庫的auth資訊

kubernetes.io/dockercfg 是一種保留類型,用來存放 ~/.dockercfg 文件的 序列化形式。該文件是配置 Docker 命令行的一種老舊形式。 使用此 Secret 類型時,你需要確保 Secret 的 data 欄位中包含名為 .dockercfg 的主鍵,其對應鍵值是用 base64 編碼的 ~/.dockercfg 文件的內容。

類型 kubernetes.io/dockerconfigjson 被設計用來保存 JSON 數據的序列化形式, 該 JSON 也遵從 ~/.docker/config.json 文件的格式規則,是 ~/.dockercfg 的新版本格式。 使用此 Secret 類型時,Secret 對象的 data 欄位必須包含 .dockerconfigjson 鍵,其對應鍵值是用 base64 編碼的 ~/.docker/config.json 文件的內容。

下面是一個 kubernetes.io/dockercfg 類型 Secret 的示例:

apiVersion: v1
kind: Secret
metadata:
  name: secret-dockercfg
type: kubernetes.io/dockercfg
data:
  .dockercfg: |
        "<base64 encoded ~/.dockercfg file>"
apiVersion: v1
kind: Secret
metadata:
  name: secret-dockercfg
type: kubernetes.io/dockerconfigjson
data:
  .dockerconfigjson: |
        "<base64 encoded ~/.docker/config.json file>"

當你使用清單文件來創建這兩類 Secret 時,API 伺服器會檢查 data 欄位中是否 存在所期望的主鍵,並且驗證其中所提供的鍵值是否是合法的 JSON 數據。 不過,API 伺服器不會檢查 JSON 數據本身是否是一個合法的 Docker 配置文件內容。

kubectl create secret docker-registry secret-tiger-docker \
  --docker-username=tiger \
  --docker-password=pass113 \
  [email protected]

上面的命令創建一個類型為 kubernetes.io/dockerconfigjson 的 Secret。 如果你對 data 欄位中的 .dockerconfigjson 內容進行轉儲,你會得到下面的 JSON 內容,而這一內容是一個合法的 Docker 配置文件。

{
  "auths": {
    "//index.docker.io/v1/": {
      "username": "tiger",
      "password": "pass113",
      "email": "[email protected]",
      "auth": "dGlnZXI6cGFzczExMw=="
    }
  }
}

4.3 basic-auth

kubernetes.io/basic-auth 類型用來存放用於基本身份認證所需的憑據資訊。 使用這種 Secret 類型時,Secret 的 data 欄位必須包含以下兩個鍵:

  • username: 用於身份認證的用戶名;
  • password: 用於身份認證的密碼或令牌。

以上兩個鍵的鍵值都是 base64 編碼的字元串。 當然你也可以在創建 Secret 時使用 stringData 欄位來提供明文形式的內容。 下面的 YAML 是基本身份認證 Secret 的一個示例清單:

apiVersion: v1
kind: Secret
metadata:
  name: secret-basic-auth
type: kubernetes.io/basic-auth
stringData:
  username: admin
  password: t0p-Secret

提供基本身份認證類型的 Secret 僅僅是出於用戶方便性考慮。 你也可以使用 Opaque 類型來保存用於基本身份認證的憑據。

不過,使用內置的 Secret 類型的有助於對憑據格式進行歸一化處理,並且 API 伺服器確實會檢查 Secret 配置中是否提供了所需要的主鍵。

4.4 ssh-auth

Kubernetes 所提供的內置類型 kubernetes.io/ssh-auth 用來存放 SSH 身份認證中 所需要的憑據。使用這種 Secret 類型時,你就必須在其 data (或 stringData) 欄位中提供一個 ssh-privatekey 鍵值對,作為要使用的 SSH 憑據。

下面的 YAML 是一個 SSH 身份認證 Secret 的配置示例:

apiVersion: v1
kind: Secret
metadata:
  name: secret-ssh-auth
type: kubernetes.io/ssh-auth
data:
  # 此例中的實際數據被截斷
  ssh-privatekey: |
          MIIEpQIBAAKCAQEAulqb/Y ...

提供 SSH 身份認證類型的 Secret 僅僅是出於用戶方便性考慮。 你也可以使用 Opaque 類型來保存用於 SSH 身份認證的憑據。 不過,使用內置的 Secret 類型的有助於對憑據格式進行歸一化處理,並且 API 伺服器確實會檢查 Secret 配置中是否提供了所需要的主鍵。

注意: SSH 私鑰自身無法建立 SSH 客戶端與伺服器端之間的可信連接。 需要其它方式來建立這種信任關係,以緩解「中間人(Man In The Middle)」 攻擊,例如向 ConfigMap 中添加一個 known_hosts 文件。

4.5 tls

Kubernetes 提供一種內置的 kubernetes.io/tls Secret 類型,用來存放證書 及其相關密鑰(通常用在 TLS 場合)。 此類數據主要提供給 Ingress 資源,用以終結 TLS 鏈接,不過也可以用於其他 資源或者負載。當使用此類型的 Secret 時,Secret 配置中的 data (或 stringData)欄位必須包含 tls.key 和 tls.crt 主鍵,儘管 API 伺服器 實際上並不會對每個鍵的取值作進一步的合法性檢查。

下面的 YAML 包含一個 TLS Secret 的配置示例:

apiVersion: v1
kind: Secret
metadata:
  name: secret-tls
type: kubernetes.io/tls
data:
  # 此例中的數據被截斷
  tls.crt: |
        MIIC2DCCAcCgAwIBAgIBATANBgkqh ...
  tls.key: |
        MIIEpgIBAAKCAQEA7yn3bRHQ5FHMQ ...

提供 TLS 類型的 Secret 僅僅是出於用戶方便性考慮。 你也可以使用 Opaque 類型來保存用於 TLS 伺服器與/或客戶端的憑據。 不過,使用內置的 Secret 類型的有助於對憑據格式進行歸一化處理,並且 API 伺服器確實會檢查 Secret 配置中是否提供了所需要的主鍵。

當使用 kubectl 來創建 TLS Secret 時,你可以像下面的例子一樣使用 tls 子命令:

kubectl create secret tls my-tls-secret \
  --cert=path/to/cert/file \
  --key=path/to/key/file

這裡的公鑰/私鑰對都必須事先已存在。用於 --cert 的公鑰證書必須是 .PEM 編碼的 (Base64 編碼的 DER 格式),且與 --key 所給定的私鑰匹配。 私鑰必須是通常所說的 PEM 私鑰格式,且未加密。對這兩個文件而言,PEM 格式數據 的第一行和最後一行(例如,證書所對應的 --------BEGIN CERTIFICATE----- 和 -------END CERTIFICATE----)都不會包含在其中。

5. 2種掛載方式

5.1 通過volume方式掛載

這裡使用2.4創建的secret進行掛載

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mypod
    image: redis
    volumeMounts:
    - name: foo
      mountPath: "/etc/foo"
      readOnly: true
    imagePullPolicy: IfNotPresent
  volumes:
  - name: foo
    secret:
      secretName: secret-basic-auth

將 Secret 鍵名映射到特定路徑

我們還可以控制 Secret 鍵名在存儲卷中映射的的路徑。 你可以使用 spec.volumes[].secret.items 欄位修改每個鍵對應的目標路徑:

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mypod
    image: redis
    volumeMounts:
    - name: foo
      mountPath: "/etc/foo"
      readOnly: true
    imagePullPolicy: IfNotPresent
  volumes:
  - name: foo
    secret:
      secretName: secret-basic-auth
      items:
      - key: username
        path: my-group/my-username

將會發生什麼呢:

  • username Secret 存儲在 /etc/foo/my-group/my-username 文件中而不是 /etc/foo/username 中。
  • password Secret 沒有被映射

如果使用了 spec.volumes[].secret.items,只有在 items 中指定的鍵會被映射。 要使用 Secret 中所有鍵,就必須將它們都列在 items 欄位中。 所有列出的鍵名必須存在於相應的 Secret 中。否則,不會創建卷。

5.2 通過環境變數方式掛載

apiVersion: v1
kind: Pod
metadata:
  name: secret-env-pod
spec:
  containers:
  - name: mycontainer
    image: redis
    env:
      - name: SECRET_USERNAME
        valueFrom:
          secretKeyRef:
            name: secret-basic-auth
            key: username
      - name: SECRET_PASSWORD
        valueFrom:
          secretKeyRef:
            name: secret-basic-auth
            key: password
    imagePullPolicy: IfNotPresent
  restartPolicy: Never

Secret 更新之後對應的環境變數不會被更新

如果某個容器已經在通過環境變數使用某 Secret,對該 Secret 的更新不會被 容器馬上看見,除非容器被重啟。有一些第三方的解決方案能夠在 Secret 發生 變化時觸發容器重啟。

6. docker-registry auth

如4.2中描述的,可以使用以下的方式創建docker-registry secret

6.1 直接創建docker-registry secret

創建 Secret,命名為 regcred:

kubectl create secret docker-registry regcred \
  --docker-server=<你的鏡像倉庫伺服器> \
  --docker-username=<你的用戶名> \
  --docker-password=<你的密碼> \
  --docker-email=<你的郵箱地址>

在這裡:

  • <your-registry-server> 是你的私有 Docker 倉庫全限定域名(FQDN)。 DockerHub 使用 //index.docker.io/v1/。
  • <your-name> 是你的 Docker 用戶名。
  • <your-pword> 是你的 Docker 密碼。
  • <your-email> 是你的 Docker 郵箱。

這樣你就成功地將集群中的 Docker 憑證設置為名為 regcred 的 Secret。

如果已經有 Docker 憑據文件,則可以將憑據文件導入為 Kubernetes Secret, 而不是執行上面的命令。 基於已有的 docker 憑據創建 secret 解釋了如何完成這一操作。

如果你在使用多個私有容器倉庫,這種技術將特別有用。 原因是 kubectl create secret docker-registry 創建的是僅適用於某個私有倉庫的 Secret。

說明: Pod 只能引用位於自身所在名字空間中的 Secret,因此需要針對每個名字空間 重複執行上述過程。

6.2 基於已有的 docker 憑據創建 secret

當我們直接使用 docker login 登錄docker registry後,會生成~/.docker/config.json文件用於記錄docker登錄的auth資訊

我們可以直接使用該文件來創建secret(其實在4.2中已經記錄過)

4.2 中提到了,可以直接指定file地址用來創建kubernetes.io/dockerconfigjson(官方文檔中摘抄的),但是本人測試時報錯,可能是k8s版本的問題,但是以下的方式是經過測試沒問題的

apiVersion: v1
kind: Secret
metadata:
  name: secret-dockercfg
type: kubernetes.io/dockerconfigjson
data:
  .dockerconfigjson: ewogICJhdXRocyI6IHsKICAgICJodHRwczovL2luZGV4LmRvY2tlci5pby92MS8iOiB7CiAgICAgICJ1c2VybmFtZSI6ICJ0aWdlciIsCiAgICAgICJwYXNzd29yZCI6ICJwYXNzMTEzIiwKICAgICAgImVtYWlsIjogInRpZ2VyQGFjbWUuY29tIiwKICAgICAgImF1dGgiOiAiZEdsblpYSTZjR0Z6Y3pFeE13PT0iCiAgICB9CiAgfQp9Cg==

.dockerconfigjson 欄位的值是 Docker 憑證的 base64 編碼值,也就是把~/.docker/config.json 轉成了base64值。

$ base64 ~/.docker/config.json
ewogICJhdXRocyI6IHsKICAgICJodHRwczovL2luZGV4LmRvY2tlci5pby92MS8iOiB7CiAgICAg
ICJ1c2VybmFtZSI6ICJ0aWdlciIsCiAgICAgICJwYXNzd29yZCI6ICJwYXNzMTEzIiwKICAgICAg
ImVtYWlsIjogInRpZ2VyQGFjbWUuY29tIiwKICAgICAgImF1dGgiOiAiZEdsblpYSTZjR0Z6Y3pF
eE13PT0iCiAgICB9CiAgfQp9Cg==

6.3 使用docker-registry secret

在創建 Pod 時,可以在 Pod 定義中增加 imagePullSecrets 部分來引用該 Secret,如下配置:

apiVersion: v1
kind: Pod
metadata:
  name: private-reg
spec:
  containers:
  - name: private-reg-container
    image: redis
  # 指定docker-registry secret
  imagePullSecrets:
  # docker-registry secret name
  - name: regcred

7. subPath

如果我們想掛載配置文件時,只覆蓋指定mountPath下的指定文件(因為默認會把Secret中的資訊掛載到mountPath下,此時mountPath下只有Secret的配置文件,mountPath下本來有的配置文件會被清除掉),那麼需要使用subpath來指定要被覆蓋的文件

我在volume的6.0中詳細的寫了相關的配置,感興趣的可以了解下。

8. 掛載的 Secret 會被自動更新

當已經存儲於卷中被使用的 Secret 被更新時,被映射的鍵也將終將被更新。 組件 kubelet 在周期性同步時檢查被掛載的 Secret 是不是最新的。 但是,它會使用其本地快取的數值作為 Secret 的當前值。

快取的類型可以使用 KubeletConfiguration 結構 中的 ConfigMapAndSecretChangeDetectionStrategy 欄位來配置。 它可以通過 watch 操作來傳播(默認),基於 TTL 來刷新,也可以 將所有請求直接重定向到 API 伺服器。 因此,從 Secret 被更新到將新 Secret 被投射到 Pod 的那一刻的總延遲可能與 kubelet 同步周期 + 快取傳播延遲一樣長,其中快取傳播延遲取決於所選的快取類型。 對應於不同的快取類型,該延遲或者等於 watch 傳播延遲,或者等於快取的 TTL, 或者為 0。

說明: 使用 Secret 作為子路徑卷掛載的容器 不會收到 Secret 更新。

9. 不可更改的 Secret

Kubernetes 的特性 不可變的 Secret 和 ConfigMap 提供了一種可選配置, 可以設置各個 Secret 和 ConfigMap 為不可變的。 對於大量使用 Secret 的集群(至少有成千上萬各不相同的 Secret 供 Pod 掛載), 禁止變更它們的數據有下列好處:

  • 防止意外(或非預期的)更新導致應用程式中斷
  • 通過將 Secret 標記為不可變來關閉 kube-apiserver 對其的監視,從而顯著降低 kube-apiserver 的負載,提升集群性能。

這個特性通過 ImmutableEmphemeralVolumes 來控制,從 v1.19 開始默認啟用。 你可以通過將 Secret 的 immutable 欄位設置為 true 創建不可更改的 Secret。 例如:

apiVersion: v1
kind: Secret
metadata:
  ...
data:
  ...
immutable: true