---
# System prepended metadata

title: ConfigMap & Secret 介紹
tags: [K3D]

---

# ConfigMap & Secret 介紹
前面部署 App 時有用到 Secret 來儲存資料庫的敏感資料，例如登入帳號、密碼等等。ConfigMap 也是類似的用途，可以拿來存放設定檔。今天就來介紹這兩種 object。

## [ConfigMap](https://kubernetes.io/docs/concepts/configuration/configmap/)
ConfigMap 以 key-value 形式儲存非機密資料。Pod 可以將 ConfigMap 作為**環境變數**、**命令列參數**或 **volume 中的設定檔**使用。

ConfigMap 可讓您將特定於環境的配置與 container images 解耦，以便您的應用程式易於攜帶。

:::warning
:warning: **警告：**

ConfigMap 不提供保密或加密。如果您要儲存的資料是機密的，請使用 Secret 而不是 ConfigMap，或使用其他（第三方）工具來保持資料的隱私。
:::

:::success
:notebook: **Note:**

ConfigMap 並不是為了儲存大量資料而設計的。ConfigMap 中儲存的資料不能超過 1 MiB。如果您需要儲存大於此限制的設置，您可能需要考慮掛載 volume 或使用另外的資料庫或檔案服務。
:::

ConfigMap 具有 **`data`** 和 **`binaryData`** 字段。這些字段接受 key-value 形式作為其值。**`data`** 和 **`binaryData`** 字段都是可選的。**`data`** 字段設計為包含 UTF-8 字串，而 **`binaryData`** 字段設計為包含作為 base64 編碼字串的二進位資料。

從 v1.19 開始，您可以為 ConfigMap 定義新增 **`immutable`** 字段來建立 [**immutable ConfigMap**](https://kubernetes.io/docs/concepts/configuration/configmap/#configmap-immutable)。

### 與 Pod 的使用方式
您可以編寫一個 Pod **`spec`**，引用一個 ConfigMap 並根據 ConfigMap 中的資料配置該 Pod 中的 Container。Pod 和 ConfigMap 必須在同一個 Namespace 中。

下面是一個 ConfigMap 範例，其中一些 key 具有單一值，而其他 key 的值看起來像是配置格式的片段。
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: game-demo
data:
  # property-like keys; each key maps to a simple value
  APP_COLOR: blue
  APP_MODE: prod

  # file-like keys
  game.properties: |
    enemy.types=aliens,monsters
    player.maximum-lives=5    
  user-interface.properties: |
    color.good=purple
    color.bad=yellow
    allow.textmode=true
```
您可以透過四種不同的方式使用 ConfigMap 在 Pod 內設定 Container：
1. Container 內的 **`command`** 和 **`args`**。
2. Container 的環境變數。
3. 在唯讀 volume 中添加一個檔案，供應用程式讀取。
4. 編寫程式碼在 Pod 內運行，使用 Kubernetes API 讀取 ConfigMap。

對於前三種方法，kubelet 在為 Pod 啟動 Container 時使用 ConfigMap 中的資料。

第四種方法意味著您需要編寫代碼來讀取 ConfigMap 及其數據。然而，由於您直接使用 Kubernetes API，您的應用程序可以在 ConfigMap 變更時獲得更新，並在發生變化時做出反應。通過直接訪問 Kubernetes API，這種技術還允許您訪問不同 Namespace 中的 ConfigMap。

接下來，我們示範一下三種 Pod **`spec`** 引用 ConfigMap 的語法：
1. 利用 **`envFrom`**

    ```yaml
    apiVersion: v1
    kind: Pod
    metadata:
      name: configmap-envfrom-pod
    spec:
      containers:
        - name: configmap-envfrom-pod
          image: alpine
          command: ["sleep", "3600"]
          envFrom:
            - configMapRef:
                name: game-demo # 前面先建好的 ConfigMap name
    ```
    用 **`envFrom`** 的方式可以直接 reference 整個 ConfigMap。用 **`printenv`** 來看看是不是真的設定好了我們定義的 key-value。
    ```bash
    kubectl exec -it configmap-envfrom-pod -- /bin/sh
    / $ printenv
    # ...
    # APP_COLOR=blue
    # ...
    / $ echo $APP_COLOR
    # blue
    ```
    
    :::warning
    :warning: **警告：**

    但我們發現，如果是使用 file-like key 得到的變數會不如預期，這時需要使用 **`volumes`** 的方式去掛 ConfigMap 才能讀到正確的內容。

    以下是不如預期的狀況：
    ```bash
    / $ printenv
    # ...
    # game.properties=enemy.types=aliens,monsters
    # ...
    / $ echo $game.properties
    # .properties
    ```
    :::

2. 利用 **`env`**
    ```yaml
    apiVersion: v1
    kind: Pod
    metadata:
      name: configmap-env-pod
    spec:
      containers:
        - name: configmap-env-pod
          image: alpine
          command: ["sleep", "3600"]
          env:
            - name: APP_COLOR_NEW # 這邊是自行定義，不需要跟 ConfigMap 裡的 key name 一致
              valueFrom:
                configMapKeyRef:
                  name: game-demo # ConfigMap name
                  key: APP_COLOR # 這邊的 key 是 ConfigMap 裡面使用的 key name
    ```
    **`env`** 則是可以分別定義單一個環境變數，注意這邊 **`env.name`** 與 ConfigMap 定義的名字不同。
    ```bash
    kubectl exec -it configmap-env-pod -- /bin/sh
    / $ printenv
    # ...
    # APP_COLOR_NEW=blue
    # ...
    / $ echo $APP_COLOR_NEW
    # blue
    ```

3. 利用 **`volumes`**
    ```yaml
    apiVersion: v1
    kind: Pod
    metadata:
      name: configmap-volume-pod
    spec:
      containers:
        - name: configmap-volume-pod
          image: alpine
          command: ["sleep", "3600"]
          volumeMounts:
            - name: game-demo-volume # volume 的名稱，定義如下
              mountPath: "/config"
              readOnly: true
      volumes:
        - name: game-demo-volume
          configMap:
            name: game-demo # 前面先建好的 Configmap name
    ```
    這個方式會將 ConfigMap 定義的 key 轉成一個一個檔案。
    ```bash
    kubectl exec -it configmap-volume-pod -- /bin/sh
    / $ cd /config
    /config $ ls
    # APP_COLOR    APP_MODE    game.properties    user-interface.properties
    /config $ cat APP_COLOR
    # blue
    /config $ cat game.properties
    # enemy.types=aliens,monsters
    # player.maximum-lives=5
    /config $ cat user-interface.properties
    # color.good=purple
    # color.bad=yellow
    # allow.textmode=true
    ```
最後來看 property-like key 和 file-like key 同時使用的設定：
```yaml
apiVersion: v1
kind: Pod
metadata:
  name: configmap-demo-pod
spec:
  containers:
    - name: configmap-demo-pod
      image: alpine
      command: ["sleep", "3600"]
      env:
        # Define the environment variable
        - name: APP_COLOR       # Notice that the case is different here from the key name in the ConfigMap.
          valueFrom:
            configMapKeyRef:
              name: game-demo   # The ConfigMap this value comes from.
              key: APP_COLOR    # The key to fetch.
        - name: APP_MODE
          valueFrom:
            configMapKeyRef:
              name: game-demo
              key: APP_MODE
      volumeMounts:
      - name: config
        mountPath: "/config"
        readOnly: true
  volumes:
  # You set volumes at the Pod level, then mount them into containers inside that Pod
  - name: config
    configMap:
      # Provide the name of the ConfigMap you want to mount.
      name: game-demo
      # An array of keys from the ConfigMap to create as files
      items:
      - key: "game.properties"
        path: "game.properties"
      - key: "user-interface.properties"
        path: "user-interface.properties"
```
定義一個 volume 並將其掛載到 **`demo`** Container 內的 **`/config`**，即使 ConfigMap 中有四個 key，也會創建兩個檔案，**`/config/game.properties`** 和 **`/config/user-interface.properties`**。這是因為在 **`volumes`** 部分指定了一個 **`items`** 陣列。如果完全省略 **`items`** 陣列，則 ConfigMap 中的每個 key 都會成為與該 key 同名的檔案，並且您將獲得 4 個檔案。

### 已掛載的 ConfigMap 會自動更新
當前在 volume 中使用的 ConfigMap 更新時，projected keys 也會最終更新。

作為**環境變數**使用的 ConfigMap 不會自動更新，需要重新啟動 Pod。
:::success
:notebook: **Note:**

使用 ConfigMap 作為 [subPath](https://kubernetes.io/docs/concepts/storage/volumes#using-subpath) volume 掛載的 Container 將不會接收 ConfigMap 更新。
:::

### 不可變的 ConfigMaps
您可以將 **`immutable`** 字段設為 **`true`** 來建立不可變的 ConfigMap。
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
  ...
data:
  ...
immutable: true
```
一旦 ConfigMap 被標記為不可變，就**無法**恢復此更改，也無法更改 **`data`** 或 **`binaryData`** 字段的內容。您只能刪除並重新建立 ConfigMap。由於現有 Pod 維護已刪除的 ConfigMap 的掛載點，因此建議重新建立這些 Pod。

## [Secret](https://kubernetes.io/docs/concepts/configuration/secret/)
建立 Secret 的 yaml 範例如下：
```yaml
apiVersion: v1
kind: Secret
metadata:
  name: secret-basic-auth
type: kubernetes.io/basic-auth
stringData:
  username: admin # required field for kubernetes.io/basic-auth
  password: t0p-Secret # required field for kubernetes.io/basic-auth
```
[Secret type 官方文件](https://kubernetes.io/docs/concepts/configuration/secret/#secret-types)

一般使用比較熟悉的是 **`Opaque`**，如果不指定 type 預設也會是這個種類。之前部署時底下內容是用 **`data`** 字段，必須是 base64-encoded 字串。這邊用 **`stringData`** 就會是明文啦。

Pod 使用 Secret 的方式與 ConfigMap 相同，可以用三種方式引用：
1. 利用 **`envFrom`**

    ```yaml
    apiVersion: v1
    kind: Pod
    metadata:
      name: secret-envfrom-pod
    spec:
      containers:
        - name: secret-envfrom-pod
          image: alpine
          command: ["sleep", "3600"]
          envFrom:
            - secretRef:
                name: secret-basic-auth
    ```
2. 利用 **`env`**
    ```yaml
    apiVersion: v1
    kind: Pod
    metadata:
      name: secret-env-pod
    spec:
      containers:
        - name: secret-env-pod
          image: alpine
          command: ["sleep", "3600"]
          env:
            - name: DB_PWD
              valueFrom:
                secretKeyRef:
                  name: secret-basic-auth
                  key: password
    ```
3. 利用 **`volumes`**
    ```yaml
    apiVersion: v1
    kind: Secret
    metadata:
      name: dotfile-secret
    data:
      .secret-file: SGVsbG8gV29ybGQ= # Hello World
    ---
    apiVersion: v1
    kind: Pod
    metadata:
      name: secret-dotfiles-pod
    spec:
      containers:
        - name: dotfile-test-container
          image: alpine
          command: ["sleep", "3600"]
          volumeMounts:
            - name: secret-volume
              readOnly: true
              mountPath: "/etc/secret-volume"
      volumes:
        - name: secret-volume
          secret:
            secretName: dotfile-secret
    ```
    我們進去到 Container 裡面查看 **`/etc/secret-volume/.secret-file`** 檔案的內容：
    ```bash
    kubectl exec -it secret-dotfiles-pod -- /bin/sh
    / $ cat /etc/secret-volume/.secret-file 
    # Hello World
    ```
    再執行 **`echo -n "SGVsbG8gV29ybGQ=" | base64 --decode`** 指令，看看結果是不是一樣。
    ```bash
    echo -n "SGVsbG8gV29ybGQ=" | base64 --decode
    # Hello World
    ```