Certified Kubernetes Administrator (CKA) #12 ConfigMap と Secret の深掘り

読了 9分

#11 までワークロードオブジェクトを一巡しました。ここからは、そのワークロードに 設定と秘密値を注入する 2 つのオブジェクト である ConfigMap と Secret を、運用者の視点で深く覗いていきます。

アプリ開発者視点の入門は K8s 基礎 #6 で一サイクル扱いました。この記事はその上に立ち、試験と運用で実際に手が動く部分 に集中します。空のターミナルから素早く作る方法、注入方式ごとの動作の違い、base64 が暗号化ではないという事実、そして immutable の効果までです。

2 つのオブジェクトの役割の区別 #

ConfigMap と Secret は 設定値をワークロード定義から切り離す という同じ目的を持ちます。マニフェストにポートやドメインを直接埋め込まず別オブジェクトに置けば、同じイメージを環境ごとに異なる設定で起動できます。

区分ConfigMapSecret
用途秘密ではない設定値パスワード、トークン、キー、証明書
保存形式平文base64 エンコード
etcd 保存平文デフォルトは平文 (エンコードされるだけ)
タイプ単一Opaque、kubernetes.io/tls など多数

表の最後の 2 行が運用者の最も誤解しやすい点です。Secret という名前のせいで暗号化されると思いがちですが、デフォルト設定で Secret は暗号化されません。この部分は後で別に扱います。

ConfigMap を作る: 3 つのソース #

試験では空のターミナルから ConfigMap を素早く作ることが多いです。ソースは 3 つあります。

–from-literal: キーと値を直接 #

# キー=値を直接並べる
k create configmap app-config \
  --from-literal=APP_COLOR=blue \
  --from-literal=APP_MODE=prod

キーが 2〜3 個なら最も速いです。ここに --dry-run=client -o yaml を付けて YAML を先に確認する習慣が安全です。

–from-file: ファイルから #

# ファイル 1 つを丸ごと (キーはファイル名、値は内容)
k create configmap nginx-config --from-file=nginx.conf

# キー名を直接指定
k create configmap nginx-config --from-file=conf=nginx.conf

# ディレクトリ全体 (ディレクトリ内の各ファイルがキーになる)
k create configmap all-config --from-file=./config-dir/

設定ファイル全体をそのまま入れる必要があるときに使います。キーを指定しなければ ファイル名がそのままキー になります。

–from-env-file: env 形式のファイルから #

# KEY=VALUE の各行がそれぞれ別のキーになる
k create configmap app-config --from-env-file=app.properties

--from-file はファイル 1 つがキー 1 つになりますが、--from-env-fileファイル内の各行が別のキー になります。この違いが、envFrom で丸ごと注入するときに結果を分けます。

YAML で直接定義 #

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  APP_COLOR: blue
  APP_MODE: prod
  nginx.conf: |
    server {
      listen 80;
    }

| ブロックで複数行の設定ファイルをそのまま入れられます。

Secret を作る: タイプを意識する #

Secret は ConfigMap とほぼ同じ作り方ですが、タイプ が加わります。試験と運用では次の 3 つを知っておけば十分です。

generic (Opaque) #

最もよく使う汎用タイプです。ConfigMap と同じソースオプションをそのまま使います。

k create secret generic db-secret \
  --from-literal=DB_USER=admin \
  --from-literal=DB_PASS=s3cr3t

docker-registry #

非公開レジストリからイメージを受け取るときに使う資格情報です。Pod の imagePullSecrets がこのタイプを参照します。

k create secret docker-registry regcred \
  --docker-server=registry.example.com \
  --docker-username=admin \
  --docker-password=s3cr3t \
  --docker-email=ops@example.com

tls #

TLS 証明書とキーを入れます。Ingress の TLS 終端 (#19) で参照します。

k create secret tls web-tls \
  --cert=tls.crt \
  --key=tls.key

YAML で定義するとき: data と stringData #

apiVersion: v1
kind: Secret
metadata:
  name: db-secret
type: Opaque
data:
  DB_USER: YWRtaW4=        # base64 でエンコードした値
stringData:
  DB_PASS: s3cr3t          # 平文で書くと apiserver がエンコード

data には base64 でエンコードした値 を入れる必要があり、stringData には 平文 を入れれば apiserver が保存時にエンコードします。手でエンコードするときは次を使います。

echo -n 's3cr3t' | base64       # エンコード
echo 'czNjcjN0' | base64 -d     # デコード

echo-n を付け忘れると改行文字までエンコードされて値がずれます。この 1 文字が試験でよく点数を削ります。

base64 は暗号化ではない #

Secret を扱うとき運用者が必ず刻んでおくべき一点です。base64 はエンコードであって暗号化ではありません。 誰でも base64 -d で即座に平文へ戻せます。つまり Secret を YAML で見たり etcd のダンプを手に入れたりした人は、秘密値をそのまま読めます。

# Secret の値を平文で見るのに特別な権限はいらない
k get secret db-secret -o jsonpath='{.data.DB_PASS}' | base64 -d

それでも Secret が ConfigMap より優れる点は次のとおりです。

  • RBAC 分離。Secret の読み取り権限を ConfigMap と別に掛けて、秘密値へのアクセスを狭められます (#9)。
  • 露出面の縮小。一部のコンポーネントは、ログや環境ダンプに ConfigMap は出しても Secret は伏せるよう設計されます。
  • etcd 暗号化の対象。Secret だけを選んで保存時の暗号化を有効にできます。

etcd 暗号化 (encryption at rest) を一段落 #

デフォルトのクラスターでは Secret は etcd に base64 の状態で、つまり事実上平文で保存されます。これを防ぐには、apiserver に --encryption-provider-config で EncryptionConfiguration を掛けて 保存時の暗号化 (encryption at rest) を有効にする必要があります。そうすれば etcd ディスクを奪われても秘密値がすぐには読まれません。この設定とキーのローテーション、そして KMS provider は control plane のセキュリティ主題なので、#24 と後続の CKS トラックで別に扱います。ここでは Secret はデフォルトで暗号化されず、それを有効にする別の設定がある という事実だけを押さえておけば十分です。

注入 1: 環境変数で #

作成した値をコンテナに渡す注入方式の説明に入ります。1 つ目は環境変数です。

env valueFrom: キーを 1 つずつ #

spec:
  containers:
    - name: app
      image: nginx
      env:
        - name: APP_COLOR
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: APP_COLOR
        - name: DB_PASS
          valueFrom:
            secretKeyRef:
              name: db-secret
              key: DB_PASS

特定のキーだけを選び、コンテナ内で使う 変数名を別に指定 するときに使います。ConfigMap は configMapKeyRef、Secret は secretKeyRef です。

envFrom: 丸ごと #

spec:
  containers:
    - name: app
      image: nginx
      envFrom:
        - configMapRef:
            name: app-config
        - secretRef:
            name: db-secret

オブジェクトの すべてのキーを環境変数として一度に 注入します。このときキー名がそのまま環境変数名になるので、キーを環境変数として使う名前に合わせておくことが重要です。先ほど --from-env-file で作った ConfigMap が envFrom とよくかみ合う理由です。

キー名に接頭辞を付けたければ prefix を使います。

      envFrom:
        - configMapRef:
            name: app-config
          prefix: CONF_       # CONF_APP_COLOR のようになる

注入 2: ボリュームで #

2 つ目の方式はボリュームマウントです。設定ファイル自体をコンテナのファイルシステムに載せる必要があるときに使います。

spec:
  containers:
    - name: app
      image: nginx
      volumeMounts:
        - name: config-vol
          mountPath: /etc/app
  volumes:
    - name: config-vol
      configMap:
        name: app-config

この場合 /etc/app/ の下に 各キーがファイルとして できます。APP_COLOR キーは /etc/app/APP_COLOR ファイルになり、その内容が値です。Secret も同じ形で secret: ボリュームを使います。

subPath: ファイル 1 つだけ #

ボリュームをそのままマウントすると mountPath ディレクトリの既存の内容が隠れます。ディレクトリは残して ファイル 1 つだけを差し込みたいとき に subPath を使います。

      volumeMounts:
        - name: config-vol
          mountPath: /etc/nginx/nginx.conf
          subPath: nginx.conf
  volumes:
    - name: config-vol
      configMap:
        name: nginx-config

こうすると /etc/nginx/ ディレクトリはそのまま残し、nginx.conf ファイルだけを ConfigMap の値で上書きします。ただし subPath には重要な落とし穴があります。

env と volume の自動更新の違い #

運用で必ず知っておくべき核心です。ConfigMap や Secret の内容を後で変えたとき、コンテナに反映される方式が注入方式ごとに異なります。

注入方式元を変更したとき
env / envFrom自動反映されない。Pod の再起動が必要
volume mountしばらく後にファイルが自動更新される
volume + subPath自動更新されない

環境変数は コンテナの起動時に一度注入 され、その後は変わりません。そのため env で注入した設定を変えるには Pod を立て直す必要があります (#10kubectl rollout restart がこのとき使われます)。

ボリュームマウントは kubelet が定期的に同期するので しばらく後にファイルの内容が変わります。ただしアプリがそのファイルを読み直してこそ実際に反映されるので、ファイルを watch しないアプリなら結局再起動が必要です。そして subPath でマウントしたファイルはこの自動更新から除外されるので、設定を頻繁に変えながら無停止反映を望むなら subPath を避ける方が適切です。

immutable: 性能と安全をともに #

頻繁に変わらない設定なら、ConfigMap と Secret を immutable として宣言 できます。

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  APP_MODE: prod
immutable: true

immutable: true にすると 2 つの効果があります。

  • 安全。誤って運用設定を変える事故を防ぎます。一度 immutable になると data を修正できず、変えるにはオブジェクトを消して作り直す必要があります。
  • 性能。kubelet が変更を監視する必要がなくなり、大規模クラスターで apiserver の負荷が減ります。オブジェクト数が多いほど効果が大きいです。

immutable にした後は、名前を変えて新しいオブジェクトとしてデプロイし、ワークロードがその新しい名前を参照するようにロールアウトするのが自然な運用です。

試験ポイント #

  • 3 つのソース を手に覚えます。キーが数個なら --from-literal、ファイル丸ごとなら --from-file、env 形式ファイルなら --from-env-file です。
  • Secret タイプ を問題に合わせて選びます。汎用は generic、レジストリの資格は docker-registry、証明書は tls です。
  • base64 エンコード時に echo -n を抜かしません。-n の抜けがよく点数を削ります。
  • 注入方式を問題の文言で区別します。「環境変数で」なら env/envFrom、「ファイルで」なら volume mount、「特定のファイルだけ上書き」なら subPath です。
  • 設定を変えた後に反映されないときは、env 注入は Pod の再起動 が必要だという点を思い出します。
  • dry-run で YAML を先に抜き出して手編集する流れが、空のマニフェストを最初から書くより速く正確です。

まとめ #

この記事で押さえたこと:

  • ConfigMap と Secret は設定をワークロードから分離 します。同じイメージを環境ごとに違えて起動する土台です。
  • 生成ソースは --from-literal--from-file--from-env-file の 3 つで、env 形式ファイルは各行が別のキーになります。
  • Secret タイプは genericdocker-registrytls を覚えます。base64 は暗号化ではなくエンコード であり、デフォルトの etcd 保存は事実上平文です。暗号化するには別に encryption at rest を有効にします (#24・CKS)。
  • 注入は env valueFrom (キー 1 つ)、envFrom (丸ごと)、volume mount (ファイル)、subPath (ファイル 1 つ) に分かれます。
  • env は自動更新されず再起動が必要 で、volume mount は自動更新され、subPath は更新から除外されます。
  • immutable: true は誤操作の防止と大規模クラスターの性能をともに得ます。

次へ — Scheduling 1 #

設定の注入までワークロードの内側を固めました。ここからはそのワークロードを どのノードに配置するか を決めるスケジューリングへ進みます。

#13 Scheduling 1 では、nodeSelector でラベルベースの配置を掛ける基本から、nodeAffinity でより柔軟なノード選択ルールを表現する方法、そして podAffinity/podAntiAffinity で Pod 同士を集めたり散らしたりするトポロジー制御まで、scheduler がどんな順序でノードを絞り込むのかを直接マニフェストで追いながら整理します。

X