本記事には広告(アフィリエイトリンク)が含まれます。

ConfigMap・Secret・SA基礎【CKAD第10回】

広告
広告

第10回スコープ・学習目標・今ここマップ

動作確認バージョン: kind v0.31.0 / kindest/node:v1.35.0 / kubectl v1.35.0 (Kustomize v5.7.1) / fanclub-backend:0.1.0 (eclipse-temurin:25-jre + Payara Micro 7.2026.4) / nginx:1.27-alpine / busybox:1.36 / Docker CE 29.4.3 / containerd 2.2.3 / AlmaLinux 10.1(kernel 6.12.0-124.55.3.el10_1)(2026-05-10 時点・k8s-ops 実機検証済・SP_vol1-pre-17 起点)

本回は Kubernetes 実践教科書 第1巻(CKAD 対応・全 19 回)の第10回です。第3部「アプリリソース」第4回として、ConfigMap による非機密設定の外部化・Secret による機密データの管理・envFrom による一括注入・ConfigMap volume mount・ServiceAccount と Bounded Token・automountServiceAccountToken: false による最小権限化を扱います。

CKAD ドメイン D4「Application Environment, Configuration and Security」(出題比率 25 %)の中核 Competency「ConfigMap の理解」「Secret の作成と利用」「ServiceAccount の理解」を 1 回でまとめて網羅する重要回です。

第9回からの継承状態確認(SP_vol1-pre-17 状態):

項目状態出典
kind クラスタkind-control-plane Ready(v1.35.0・8h old)Lead 実機観察
fanclub-backend Poddefault ns で 1/1 Running(DB 接続 env 6 項目 平文埋め込み)Lead 実機観察
fanclub-backend ServiceClusterIP 10.96.150.60:80 稼働中Lead 実機観察
fanclub-db StatefulSetfanclub-db-0 Pod Running(PostgreSQL 18.3)Lead 実機観察
fanclub-db Service / fanclub-db-headless ServiceClusterIP 10.96.131.167:5432 / clusterIP: NoneLead 実機観察
postgres-data-fanclub-db-0 PVCBound(1Gi・standard SC)Lead 実機観察
ConfigMapfanclub-db-init(InitDB SQL)+ kube-root-ca.crt(auto)のみLead 実機観察
Secretなし(ep10 で初めて作成する)Lead 実機観察
ServiceAccountdefault のみ(auto)Lead 実機観察
~/fanclub-manifests/ディレクトリ作成済(ep7 から継続・ep9 マニフェスト群格納済)ep9 完了済

今ここマップ(第1巻 19 回中の現在位置):

第1部 コンテナとDocker
    第1回: コンテナ技術概念 + Docker環境準備  [完了]
    第2回: Docker基本操作  [完了]
    第3回: Dockerfile + マルチステージビルド + JDK 25/Payara Micro  [完了]
    第4回: コンテナレジストリ + イメージタグ戦略 + Trivy スキャン  [完了]

第2部 Kubernetes基礎
    第5回: K8s全体像 + kind で軽量K8s  [完了]
    第6回: kubectl基本操作 + Observability・Debug  [完了]

第3部 アプリリソース
    第7回: Pod + Multi-containerパターン  [完了]
    第8回: Service とネットワーキング  [完了]
    第9回: ストレージ(PVC + StatefulSet)+ PostgreSQL DB追加  [完了]
  ★ 第10回: ConfigMap + Secret + ServiceAccount 基礎  ← 今ここ
    第11回: Job + CronJob + DaemonSet

第4部 ワークロード戦略(第12〜14回)
第5部 セキュリティ基礎(第15〜16回)
第6部 パッケージ管理 + HTTPS公開(第17〜19回)

第10回を終えると、以下を習得した状態になります。

  • ConfigMap の 3 種の作成方法(リテラル / ファイル / YAML)と 3 種の使い方(env / envFrom / volume mount)を説明できる
  • Secret の Opaque type を stringData で作成し、base64 がエンコードであり暗号化ではない事実を説明できる
  • envFrom を使って ConfigMap / Secret を Pod に一括注入し、環境変数として参照できることを実機で確認できる
  • ConfigMap を volume mount で nginx.conf として Pod に注入し、ファイルとして機能することを確認できる
  • ServiceAccount の役割と default SA の挙動を理解し、automountServiceAccountToken: false の効果を実機で確認できる

模擬アプリ進捗(第10回):第9回までで Backend + Service + DB の 3 層構成が完成しましたが、Backend Pod の DB 接続情報は YAML に平文で埋め込まれた状態です。本回ではこの状態をリファクタリングします。

DB_HOST / DB_PORT / DB_NAME / JAVA_OPTS の 4 項目を ConfigMap、DB_USER / DB_PASSWORD の 2 項目を Secret に分離し、Backend Pod を envFrom で両者から一括注入する版に再 apply します。あわせて ConfigMap の volume mount による nginx.conf 注入と、ServiceAccount を新規作成して automountServiceAccountToken: false を設定する流れを学びます。

第10回完了後の模擬アプリ状態:fanclub-backend Pod(envFrom 版・ConfigMap + Secret 注入・fanclub-backend-sa 設定済・automountServiceAccountToken: false)+ fanclub-backend Service(ClusterIP・ep8 から継続)+ fanclub-db StatefulSet(ep9 から継続)+ fanclub-db-headless / fanclub-db Service + postgres-data-fanclub-db-0 PVC + members 永続データ + fanclub-config ConfigMap + fanclub-secret Secret + fanclub-backend-sa ServiceAccount + nginx-config-demo ConfigMap(演習②用)+ nginx-configmap-demo Pod(演習②用)。

第11回で DB マイグレーション Job を追加します。

設定の外部化のなぜ — コードと設定を分ける 12-Factor App の原則

第9回で Backend + DB を疎通させるために、fanclub-backend Pod の YAML には DB 接続情報を平文で書きました。動作確認のためには有効な手段でしたが、本番運用では採用してはいけない設計です。

本セクションでは、なぜ Pod YAML への直接埋め込みが本番運用の禁忌なのかを 2 つの問題点として整理し、Kubernetes が ConfigMap と Secret という別リソースを用意している思想的背景を確認します。

env 平文埋め込みの 2 つの問題点

第9回時点の fanclub-backend-with-db-pod.yaml は以下の構造でした(抜粋)。

env:
  - name: DB_HOST
    value: "fanclub-db"
  - name: DB_PORT
    value: "5432"
  - name: DB_NAME
    value: "fanclubdb"
  - name: DB_USER
    value: "appuser"
  - name: DB_PASSWORD
    value: "apppassword"
  - name: JAVA_OPTS
    value: "-XX:MaxRAMPercentage=75.0"

このマニフェストには 2 種類の異なる情報が混在しています。DB_HOST / DB_PORT / DB_NAME / JAVA_OPTS は環境ごとに変わる設定であり、一方 DB_USER / DB_PASSWORD は機密情報です。両者を同じ YAML に平文で書く設計には、以下の 2 つの問題があります。

  • 問題 1:Git にコミットすると認証情報が履歴に残るDB_PASSWORD: "apppassword" をリポジトリに push した瞬間、Git の履歴から削除しても git log -p で復元可能になる。リポジトリが Public になった場合や、退職者の手元に残った clone が流出した場合、Backend → DB の認証情報が世界中に晒される
  • 問題 2:kubectl describe pod で誰でも env を読める。Pod の閲覧権限を持つすべてのユーザーが kubectl describe pod fanclub-backendDB_PASSWORD の値を確認できる。本番では「Pod を見られるが Secret は見られない」という権限分離が必要なところ、Pod に平文で書くとその分離が機能しない

本番ではこの 2 つの問題のどちらか 1 つだけでも認証情報の漏洩につながります。Pod YAML への機密情報の直接埋め込みはアンチパターンとして広く認識されており、本番に持ち込んではいけない設計です。

12-Factor App Factor III「設定」の原則

クラウドネイティブアプリ設計の基本原則として広く参照される 12-Factor App のうち、Factor III は「設定」を扱っています。要点は以下のとおりです。

  • アプリのコードと、環境ごとに変わる設定(DB 接続情報・外部サービス URL・認証情報)を分離する
  • 同じビルド成果物(コンテナイメージ)を dev / staging / prod のすべての環境で使い回せる状態を保つ
  • 設定はコードではなく、環境変数や外部の設定ストアから注入する

「同じイメージを複数環境で使い回す」という原則が重要です。コンテナイメージはビルドのたびにイメージタグを進めて registry に push しますが、もしイメージ自体に環境ごとの設定が焼き込まれていると、dev 用・staging 用・prod 用で別々のイメージをビルドして tag を分けることになり、デプロイ時のリスクが増えます。

「dev で動いた image をそのまま prod にデプロイすれば設定だけ差し替えれば動く」状態を保つには、設定をイメージから外に出す必要があります。

Kubernetes での実現 — ConfigMap と Secret の役割分担

Kubernetes は 12-Factor App Factor III を実現する仕組みとして、ConfigMap と Secret の 2 つの API オブジェクトを提供しています。両者の役割分担は以下のとおりです。

項目ConfigMapSecret
用途非機密の設定データ機密データ(パスワード・トークン・証明書)
etcd への保存形式平文テキストbase64 エンコード(暗号化ではない)
主な type(type なし)Opaque / kubernetes.io/tls / kubernetes.io/dockerconfigjson 等
Pod への注入方法env / envFrom / volume mountenv / envFrom / volume mount
本回での割り当てDB_HOST / DB_PORT / DB_NAME / JAVA_OPTSDB_USER / DB_PASSWORD

注入方法は 3 種類とも ConfigMap・Secret で同じであり、API としても近い構造になっています。違いは「機密扱いされるかどうか」「etcd 上で base64 に変換されて保存されるか」「kubectl の出力で値がデフォルトで隠されるか」といった運用面の差です。

「base64 化されているから暗号化されている」という誤解についてはセクション H2-4 と H2-12 で詳しく扱いますが、現時点ではその誤解を強く戒めておきます。base64 はあくまでテキスト表現の変換であり、暗号化ではありません。

ここから次の H2-3・H2-4・H2-5 で、ConfigMap・Secret・envFrom の API としての挙動を順に見ていきます。理論パートを終えたあと、H2-6 で実際に fanclub-backend Pod を envFrom 版に書き換える演習①を行います。

ConfigMap 基礎 — 作成方法 3 種と使い方 3 種

ConfigMap は「非機密の設定データをキーバリュー形式で格納する Namespace スコープの API オブジェクト」です。アプリのコードから設定を切り出す目的で使われます。本セクションでは作成方法 3 種・使い方 3 種を整理し、本回で採用する形(YAML 直書き + envFrom)の位置付けを明確化します。

作成方法 3 種

ConfigMap の作成方法は 3 通りあります。

方法コマンド例使い所
① リテラル指定kubectl create configmap demo-cm --from-literal=DB_HOST=fanclub-db --from-literal=DB_PORT=5432少数のキーを素早く作成したいとき・CKAD 試験での即席作成
② ファイルから作成kubectl create configmap demo-cm --from-file=app.properties既存の設定ファイルをそのまま ConfigMap 化したいとき
③ YAML 直書きkubectl apply -f fanclub-configmap.yamlGit 管理・本番運用・本回での採用方式

本回では ③ YAML 直書き方式を採用します。理由は次の 3 点です。

  • マニフェストを Git 管理することで変更履歴が追跡できる
  • kubectl apply による宣言的管理が可能になる(リテラル方式は命令的)
  • 本シリーズ全体で ~/fanclub-manifests/ ディレクトリに YAML を集約する方針と整合する

CKAD 試験ではリテラル方式の即席作成も頻出するため、コマンド構文(kubectl create configmap <name> --from-literal=<key>=<value>)も覚えておく必要があります。本回の演習では YAML 直書きで作成しますが、補足としてリテラル指定の例も示します。

本回で作成する fanclub-config の YAML 全量

fanclub-backend Pod に注入する非機密設定 4 項目を ConfigMap として定義します。ファイル名は ~/fanclub-manifests/fanclub-configmap.yaml とします。

apiVersion: v1
kind: ConfigMap
metadata:
  name: fanclub-config
  namespace: default
  labels:
    app: fanclub-backend
data:
  DB_HOST: "fanclub-db"
  DB_PORT: "5432"
  DB_NAME: "fanclubdb"
  JAVA_OPTS: "-XX:MaxRAMPercentage=75.0"

各キーの意味と出典を確認します。

キー用途
DB_HOSTfanclub-dbBackend が接続する DB の Service 名(ep9 で作成した ClusterIP Service)
DB_PORT5432PostgreSQL の標準ポート
DB_NAMEfanclubdbInitDB で作成した DB 名
JAVA_OPTS-XX:MaxRAMPercentage=75.0JVM のヒープ上限を Pod memory limit の 75 % に設定

すべて文字列型として扱われます。DB_PORT: "5432" はクォートで囲んでいることに注目してください。

ConfigMap の data: フィールドは値が文字列でなければならず、数値リテラルを書くと kubectl apply 時に「cannot convert int64 to string」というエラーが出ます。Kubernetes の API 仕様では ConfigMap の値はすべて string 型と定められているためです。

使い方 3 種 — env / envFrom / volume mount

作成した ConfigMap を Pod から参照する方法は 3 通りあります。

方法記述形式特徴
env + valueFromenv[].valueFrom.configMapKeyRefConfigMap の特定キーのみを選択して環境変数として注入。環境変数名を変更できる
envFromenvFrom[].configMapRefConfigMap の全キーを一括で環境変数として注入。Pod YAML がシンプル。本回での採用方式
volume mountvolumes[].configMap + volumeMountsConfigMap をファイルとして Pod 内に注入。アプリが設定ファイルを読み込む場合に使う。kubelet sync 周期で自動更新(Pod 再起動不要)

3 つの方法はそれぞれ得意な場面が異なります。少数のキーだけを使いたいときや環境変数名を変えたいときは env + valueFrom、複数キーをまとめて流し込みたいときは envFrom、アプリが設定ファイルを直接読み込む実装になっている場合は volume mount を選びます。

本回の演習①では envFrom、演習②では volume mount を使う構成にして、両方のパターンを実機で体験します。env[].valueFrom についても CKAD 試験では出題されるため、構文を理解しておく必要があります。具体例を以下に示します。

env:
  - name: DB_HOST
    valueFrom:
      configMapKeyRef:
        name: fanclub-config
        key: DB_HOST
  - name: APP_DB_NAME    # 環境変数名を変えることもできる
    valueFrom:
      configMapKeyRef:
        name: fanclub-config
        key: DB_NAME

immutable: true による更新ロック

ConfigMap には immutable: true フィールドがあります(Kubernetes v1.21 GA)。これを設定すると ConfigMap は更新できなくなり、変更したい場合は削除して再作成するしかありません。一見不便そうに見えますが、本番では以下のメリットがあります。

  • 誤って ConfigMap を変更してしまう事故を防げる
  • kubelet が ConfigMap の更新を監視するコストが削減され、API Server の負荷が軽くなる(大規模クラスタで効果が出る)
  • 「変更したいなら新しい ConfigMap を作って Pod から参照先を切り替える」という Blue/Green 的な運用スタイルが自然になる

本回の演習では immutable: true は付けません。学習の流れで ConfigMap の更新挙動を観察するためです。本番では ConfigMap を頻繁に更新しない設計にしたうえで immutable: true を付けるのが推奨されます。

Secret 基礎 — Opaque type・base64 エンコードの本質・stringData

Secret は機密データを格納するための Namespace スコープの API オブジェクトです。ConfigMap と API 構造はよく似ていますが、運用上の扱いが異なる点が複数あります。本セクションでは Secret の type 一覧、本回採用の stringData フィールド、そして「base64 は暗号化ではない」という最重要事実を実機デモで確認します。

Secret type 一覧

Secret には複数の type が定義されており、用途ごとに使い分けます。

type用途
Opaque任意のキーバリューデータ。デフォルトの汎用 type。本回での採用
kubernetes.io/tlsTLS 証明書と秘密鍵のペア。tls.crttls.key の 2 キーを持つ。第18回の cert-manager で間接利用
kubernetes.io/dockerconfigjsonプライベートレジストリの認証情報。imagePullSecrets から参照
kubernetes.io/service-account-tokenSA トークン。Kubernetes v1.24 以降は自動作成されなくなった
kubernetes.io/basic-authBasic 認証用(username / password キー)
kubernetes.io/ssh-authSSH 鍵認証用(ssh-privatekey キー)
bootstrap.kubernetes.io/tokenkubeadm のブートストラップトークン

本回の DB_USER / DB_PASSWORD は型が決まっていない汎用データなので、Opaque を選びます。kubernetes.io/tlskubernetes.io/dockerconfigjson は型ごとに必須キーが決まっており、Kubernetes 側でバリデーションが入るため、汎用データには Opaque を選ぶのが定石です。

data フィールドと stringData フィールドの違い

Secret の値を YAML に書く方法は 2 通りあります。

フィールド記述形式動作
data:base64 エンコード済みの値を手動で記述読みづらく、エンコードミスが起きやすい
stringData:平文テキストを記述kubectl apply 時に Kubernetes が自動で base64 に変換して data: に保存。読みやすい。本回での採用

本回では stringData: を採用します。理由は次の 2 点です。

  • YAML を読むときに値の意味がわかりやすい(DB_PASSWORD: "apppassword" のように平文で書ける)
  • base64 エンコードを手で行うと改行コードや末尾のパディング(=)でミスを起こしやすく、エンコード時の echo のオプション(-n)を忘れると末尾改行が混入する

stringData: は書き込み専用フィールドであり、kubectl get secret -o yaml で取得すると data: フィールドに base64 エンコードされた値として表示されます。これは演習①で実機確認します。

本回で作成する fanclub-secret の YAML 全量

機密情報 2 項目を Secret として定義します。ファイル名は ~/fanclub-manifests/fanclub-secret.yaml とします。

apiVersion: v1
kind: Secret
metadata:
  name: fanclub-secret
  namespace: default
  labels:
    app: fanclub-backend
type: Opaque
stringData:
  DB_USER: "appuser"
  DB_PASSWORD: "apppassword"

type: Opaque + stringData: の組み合わせが最もシンプルかつ間違いが起きにくいパターンです。stringData: に書いた値は kubectl apply 時に Kubernetes API Server が base64 にエンコードして data: に保存します。

base64 は暗号化ではない — 実機デモで確認する

Secret の data: フィールドに格納される値は base64 でエンコードされていますが、これは「暗号化」ではありません。base64 は単なるテキスト変換であり、鍵を持たなくても誰でもデコードできます。実機で確認します。

実行コマンド(エンコード):

$ echo -n 'apppassword' | base64

実行結果:

YXBwcGFzc3dvcmQ=

実行コマンド(デコード):

$ echo -n 'YXBwcGFzc3dvcmQ=' | base64 -d

実行結果:

apppassword

このように、base64 でエンコードされた文字列は base64 -d 一発で平文に戻ります。エンコードする側もデコードする側も鍵を必要としません。Secret の data: に格納されている値は etcd 上では base64 形式で保存されますが、kubectl 経由で値を取得した攻撃者は即座に平文化できます。

本番運用警告(その 1):Kubernetes Secret は「base64 化された値が etcd に保存される」だけであり、これは暗号化ではありません。本番では etcd 暗号化(Encryption at Rest)と RBAC による Secret アクセス権限の最小化、そして外部 Secrets 管理(Vault・External Secrets Operator)の組み合わせが必須です。

これらは H2-11 で詳述し、本格実装は第3巻 CKS 編で扱います。

なぜ Kubernetes は Secret を base64 にしているのか

「base64 が暗号化でないなら、なぜわざわざエンコードするのか」という疑問が出てきます。理由は次の 2 点です。

  • バイナリデータを安全に運ぶため:YAML はテキスト形式なので、バイナリデータ(TLS 証明書の秘密鍵や SSH 鍵など)を直接書き込めない。base64 で文字列化することでテキスト YAML に格納できる
  • kubectl describe secret で値が表示されないようにするため:base64 エンコードされた値はランダムな文字列に見えるので、画面共有や端末ログから偶発的に漏れるリスクが下がる。kubectl get secret -o yaml で明示的に取得しない限り平文は出てこない

つまり base64 は「うっかり画面に出ない」「テキスト YAML に格納できる」という運用上の利便のための選択であり、機密保護のための暗号化ではありません。本物の機密保護には別途仕組みが必要です。

envFrom の仕組み — ConfigMap と Secret を一括注入する

ConfigMap と Secret を作成したら、これらを Pod に注入する必要があります。本回では envFrom による一括注入を採用します。本セクションでは envFrom の仕様、env[].valueFrom との比較、複数 envFrom を指定したときの上書き挙動、そして本番で必ず踏む「envFrom 更新は Pod 再起動が必要」という落とし穴の予告を行います。

envFrom と env[].valueFrom の違い

両者の比較を整理します。

項目env[].valueFromenvFrom
注入対象ConfigMap / Secret の特定キー 1 つConfigMap / Secret の全キー
環境変数名自由に変更可能(name: で指定)ConfigMap / Secret のキー名がそのまま環境変数名
YAML の冗長度キーごとに記述(行数が増える)configMapRef / secretRef 1 行で完結
使い所特定キーだけ使う・名前を変えたい全キーを一括注入したい
本回での採用採用

本回 envFrom を採用する理由は次の 3 点です。

  • fanclub-backend が必要とする 6 環境変数(ConfigMap)+ 2 環境変数(Secret)はすべて Backend 側で同じ名前で参照する設計のため、名前変更が不要
  • env[] で 8 個書くより envFrom: 2 行 の方が Pod YAML が読みやすい
  • 「ConfigMap と Secret を別々に envFrom できる」という重要パターンを学習教材として示せる

複数 envFrom の上書き順序

envFrom は配列で複数指定でき、複数の ConfigMap や Secret を 1 つの Pod に混在させられます。同名キーが含まれる場合は後に書いたものが先に書いたものを上書きするルールです。

envFrom:
  - configMapRef:
      name: fanclub-config   # 先に評価される
  - secretRef:
      name: fanclub-secret   # 後に評価される(同名キーがあれば上書き)

本回では ConfigMap のキー(DB_HOST / DB_PORT / DB_NAME / JAVA_OPTS)と Secret のキー(DB_USER / DB_PASSWORD)が完全に分離されているため、上書きは発生しません。

ただし本番運用では「dev 用の ConfigMap で値を上書きする」「機密キーだけ Secret で差し替える」といった運用パターンが存在するため、上書き順序の仕様は理解しておく必要があります。

envFrom 注入時の最終形 Pod 仕様

本回最終的に到達する fanclub-backend Pod の仕様(演習①完了後に演習③でさらに serviceAccountNameautomountServiceAccountToken: false を追加した第 4 版)を以下に示します。

これは演習①+演習③をすべて終えたあとの最終形であり、演習①の段階では serviceAccountNameautomountServiceAccountToken はまだ含めません(演習①時点では fanclub-backend-sa がまだ作成されていないため、含めると Pod が起動できません)。

apiVersion: v1
kind: Pod
metadata:
  name: fanclub-backend
  namespace: default
  labels:
    app: fanclub-backend
spec:
  serviceAccountName: fanclub-backend-sa
  automountServiceAccountToken: false
  initContainers:
    - name: wait-for-db
      image: busybox:1.36
      command:
        - sh
        - -c
        - "until nc -z fanclub-db 5432; do echo 'waiting for DB...'; sleep 2; done"
  containers:
    - name: fanclub-backend
      image: fanclub-backend:0.1.0
      imagePullPolicy: IfNotPresent
      ports:
        - containerPort: 8080
      envFrom:
        - configMapRef:
            name: fanclub-config
        - secretRef:
            name: fanclub-secret
      resources:
        requests:
          memory: "256Mi"
          cpu: "250m"
        limits:
          memory: "512Mi"
          cpu: "1000m"

第9回からの差分は以下のとおりです。

  • env: セクション(6 項目の平文値)を削除
  • envFrom: セクションを追加(configMapRef + secretRef の 2 件)
  • spec.serviceAccountName: fanclub-backend-sa を追加(演習③で適用)
  • spec.automountServiceAccountToken: false を追加(演習③で適用)

envFrom 更新タイミングの注意

事前に重要な落とし穴を予告します。envFrom で注入した環境変数は Pod 起動時に一度だけ評価されます。Pod が起動したあとに ConfigMap や Secret を更新しても、実行中の Pod の環境変数は古い値のまま固定されます。新しい値を反映させるには Pod を削除して再作成する必要があります。

これは本番でよく踏むトラブルであり、ヒヤリハット 1(H2-12)で詳細なシナリオと対処を示します。本回の演習①でも「Pod を一度 delete してから再 apply する」手順を採用するのは、この更新タイミングの仕様に沿った正しい手順だからです。

ConfigMap と Secret から Pod への 3 種の注入方式を比較した図。上段に非機密データを持つ ConfigMap fanclub-config(DB_HOST など 4 キー)と機密データを持つ Secret fanclub-secret(DB_USER と DB_PASSWORD の 2 キー)を並べ、中段に env(valueFrom で個別キー指定)・envFrom(全キー一括注入・第10回採用で太線強調)・volume mount(ファイルとして注入)の 3 列比較を配置。下段の Pod fanclub-backend へ 3 本の矢印が集約し、ConfigMap と Secret を分離管理する原則を下部の注記が示している。

やってみよう①: ConfigMap + Secret を作成して Backend Pod を envFrom 版に更新する

第9回で平文 env を 6 項目埋め込んだ fanclub-backend Pod を、ConfigMap + Secret + envFrom の構成にリファクタリングします。所要時間目安は約 25 分です。

演習の全体フローは以下のとおりです。

  1. 前提状態の確認
  2. ConfigMap YAML を作成して apply
  3. Secret YAML を作成して apply
  4. ConfigMap / Secret の作成結果を確認
  5. Secret の data: フィールドが base64 化されていることを確認
  6. Backend Pod YAML を envFrom 版に書き換え
  7. 既存 Pod を削除して再 apply
  8. Pod 内で env コマンドを実行して注入結果を確認

Step 1: 前提状態の確認

k8s-ops の作業端末で ~/fanclub-manifests/ に移動し、現在のクラスタ状態を確認します。

実行コマンド:

$ cd ~/fanclub-manifests/
$ kubectl get pods,svc,configmap,secret,serviceaccount -n default

実行結果(Step 1 実機確認・fanclub-backend Pod・fanclub-db-0 Pod・各種 Service・fanclub-db-init ConfigMap・kube-root-ca.crt ConfigMap・default SA のみ表示。Secret はゼロ。):

NAME                  READY   STATUS    RESTARTS   AGE
pod/fanclub-backend   1/1     Running   0          8h
pod/fanclub-db-0      1/1     Running   0          8h

NAME                          TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
service/fanclub-backend       ClusterIP   10.96.150.60    <none>        80/TCP     8h
service/fanclub-db            ClusterIP   10.96.131.167   <none>        5432/TCP   8h
service/fanclub-db-headless   ClusterIP   None            <none>        5432/TCP   8h
service/kubernetes            ClusterIP   10.96.0.1       <none>        443/TCP    8h

NAME                              DATA   AGE
configmap/fanclub-db-init         1      8h
configmap/kube-root-ca.crt        1      8h

No resources found in default namespace.

NAME                             SECRETS   AGE
serviceaccount/default           0         8h

fanclub-backend Pod が Running、fanclub-db-0 Pod が Running、Secret はゼロ件であることを確認します。

Step 2: ConfigMap YAML を作成して apply

fanclub-backend に注入する非機密設定 4 項目を ConfigMap として定義します。エディタで fanclub-configmap.yaml を新規作成します。

実行コマンド:

$ vi ~/fanclub-manifests/fanclub-configmap.yaml

ファイル内容:

apiVersion: v1
kind: ConfigMap
metadata:
  name: fanclub-config
  namespace: default
  labels:
    app: fanclub-backend
data:
  DB_HOST: "fanclub-db"
  DB_PORT: "5432"
  DB_NAME: "fanclubdb"
  JAVA_OPTS: "-XX:MaxRAMPercentage=75.0"

保存して apply します。

実行コマンド:

$ kubectl apply -f ~/fanclub-manifests/fanclub-configmap.yaml

実行結果:

configmap/fanclub-config created

configmap/fanclub-config created が表示されれば成功です。

Step 3: Secret YAML を作成して apply

機密情報 2 項目を Secret として定義します。fanclub-secret.yaml を新規作成します。

実行コマンド:

$ vi ~/fanclub-manifests/fanclub-secret.yaml

ファイル内容:

apiVersion: v1
kind: Secret
metadata:
  name: fanclub-secret
  namespace: default
  labels:
    app: fanclub-backend
type: Opaque
stringData:
  DB_USER: "appuser"
  DB_PASSWORD: "apppassword"

保存して apply します。

実行コマンド:

$ kubectl apply -f ~/fanclub-manifests/fanclub-secret.yaml

実行結果:

secret/fanclub-secret created

secret/fanclub-secret created が表示されれば成功です。

Step 4: ConfigMap / Secret の作成結果を確認

作成されたリソースを一覧表示します。

実行コマンド:

$ kubectl get configmap,secret -n default

実行結果(fanclub-config・fanclub-db-init・kube-root-ca.crt・fanclub-secret が一覧表示):

NAME                              DATA   AGE
configmap/fanclub-config   4      3s
configmap/fanclub-db-init          1      8h
configmap/kube-root-ca.crt         1      8h

NAME                      TYPE     DATA   AGE
secret/fanclub-secret   Opaque   2      1s

ConfigMap の中身を確認します。

実行コマンド:

$ kubectl describe configmap fanclub-config

実行結果(6 キーすべての値が平文で表示される):

Name:         fanclub-config
Namespace:    default
Labels:       app=fanclub-backend
Annotations:  <none>

Data
====
DB_HOST:
----
fanclub-db

DB_NAME:
----
fanclubdb

DB_PORT:
----
5432

JAVA_OPTS:
----
-XX:MaxRAMPercentage=75.0


BinaryData
====

Events:  <none>

ConfigMap は非機密データのため、describe で値が平文表示されます。

Step 5: Secret の data: フィールドが base64 化されていることを確認

Secret は describe しても値が表示されません。

実行コマンド:

$ kubectl describe secret fanclub-secret

実行結果(Data: DB_PASSWORD: 11 bytes / DB_USER: 7 bytes のようにバイト数のみ表示):

Name:         fanclub-secret
Namespace:    default
Labels:       app=fanclub-backend
Annotations:  <none>

Type:  Opaque

Data
====
DB_PASSWORD:  11 bytes
DB_USER:      7 bytes

describe ではキー名とバイト数だけが表示されます。kubectl get secret -o yaml で生のフィールドを取得してみます。

実行コマンド:

$ kubectl get secret fanclub-secret -o yaml

実行結果(data: セクションに DB_USER: YXBwdXNlcg==DB_PASSWORD: YXBwcGFzc3dvcmQ= が含まれる。stringData は表示されない):

apiVersion: v1
data:
  DB_PASSWORD: YXBwcGFzc3dvcmQ=
  DB_USER: YXBwdXNlcg==
kind: Secret
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","kind":"Secret","metadata":{"annotations":{},"labels":{"app":"fanclub-backend"},"name":"fanclub-secret","namespace":"default"},"stringData":{"DB_PASSWORD":"apppassword","DB_USER":"appuser"},"type":"Opaque"}
  creationTimestamp: "2026-05-10T09:50:57Z"
  labels:
    app: fanclub-backend
  name: fanclub-secret
  namespace: default
  resourceVersion: "49304"
  uid: 9e162248-2c46-4f02-bc9a-fa2c9a43ad15
type: Opaque

YAML に書いた stringData:data: に変換されていることを確認できます。値は base64 エンコードされていますが、これは暗号化ではありません。実際にデコードしてみます。

実行コマンド:

$ kubectl get secret fanclub-secret -o jsonpath='{.data.DB_PASSWORD}' | base64 -d

実行結果(apppassword が表示される):

apppassword

Secret の閲覧権限を持つユーザーであれば、このコマンド一発で平文 DB パスワードを取得できます。これが Kubernetes Secret のデフォルト状態です。本番では RBAC で Secret の閲覧権限を業務上必要なサービスのみに限定する必要があります(第15回で本格実装)。

Step 6: Backend Pod YAML を envFrom 版に書き換え

第9回で作成した fanclub-backend-with-db-pod.yaml を編集します。このステップでは env: セクションを削除し、envFrom: を追加するのみとします

serviceAccountNameautomountServiceAccountToken: false の追加は演習③で行います(この段階では fanclub-backend-sa がまだ作成されていないため、Pod に serviceAccountName: fanclub-backend-sa を書くと Pod が「該当 SA が見つからない」エラーで起動しません)。

実行コマンド:

$ vi ~/fanclub-manifests/fanclub-backend-with-db-pod.yaml

ファイル内容(演習①完了時点での全量・第3版):

apiVersion: v1
kind: Pod
metadata:
  name: fanclub-backend
  namespace: default
  labels:
    app: fanclub-backend
spec:
  initContainers:
    - name: wait-for-db
      image: busybox:1.36
      command:
        - sh
        - -c
        - "until nc -z fanclub-db 5432; do echo 'waiting for DB...'; sleep 2; done"
  containers:
    - name: fanclub-backend
      image: fanclub-backend:0.1.0
      imagePullPolicy: IfNotPresent
      ports:
        - containerPort: 8080
      envFrom:
        - configMapRef:
            name: fanclub-config
        - secretRef:
            name: fanclub-secret
      resources:
        requests:
          memory: "256Mi"
          cpu: "250m"
        limits:
          memory: "512Mi"
          cpu: "1000m"

第9回からの変更点は次の 2 点だけです。

  • env: セクション(DB_HOST / DB_PORT / DB_NAME / DB_USER / DB_PASSWORD / JAVA_OPTS の 6 項目)を削除
  • envFrom: セクションを追加(configMapRef + secretRef の 2 件)

Step 7: 既存 Pod を削除して再 apply

Pod は immutable なフィールドが多く、envenvFrom の変更は kubectl apply でその場で更新できません。一度削除してから再作成します(第12回で扱う Deployment を使えば Rolling Update で自動再起動できますが、本回は Pod 単体運用のため手動削除する手順を踏みます)。

実行コマンド:

$ kubectl delete pod fanclub-backend

実行結果:

pod "fanclub-backend" deleted

削除を確認したら、新しい YAML を apply します。

実行コマンド:

$ kubectl apply -f ~/fanclub-manifests/fanclub-backend-with-db-pod.yaml

実行結果:

pod/fanclub-backend created

Pod の起動状況を確認します。

実行コマンド:

$ kubectl get pod fanclub-backend -w

実行結果(Init:0/1PodInitializingRunning の遷移):

NAME              READY   STATUS     RESTARTS   AGE
fanclub-backend   0/1     Init:0/1   0          0s
fanclub-backend   0/1     PodInitializing   0   10s
fanclub-backend   1/1     Running    0          21s

Init Container wait-for-db が DB の 5432 ポート疎通を確認したあとメインコンテナが起動し、Running 状態に到達することを確認します。Ctrl+C で -w ウォッチを抜けます。

Step 8: Pod 内で env コマンドを実行して注入結果を確認

envFrom で注入された環境変数を確認します。

実行コマンド:

$ kubectl exec fanclub-backend -- env | sort | grep -E '^(DB_|API_|JAVA_)'

実行結果(DB_HOST=fanclub-db・DB_NAME=fanclubdb・DB_PASSWORD=apppassword・DB_PORT=5432・DB_USER=appuser・JAVA_OPTS=-XX:MaxRAMPercentage=75.0 の 6 項目が表示):

DB_HOST=fanclub-db
DB_NAME=fanclubdb
DB_PASSWORD=apppassword
DB_PORT=5432
DB_USER=appuser
JAVA_OPTS=-XX:MaxRAMPercentage=75.0

ConfigMap の 4 項目と Secret の 2 項目、合わせて 6 項目すべてが Pod の環境変数として注入されていることを確認できます。Backend のソースコード側から見ると System.getenv("DB_HOST") 等で取得できる挙動は ep9 と同じです。

注入元が ConfigMap / Secret に変わっただけで、Backend には変更を加えていません。これが「設定をコードから分離する」12-Factor App Factor III の実装です。

演習①の確認はここまでです。続いて H2-7 で ConfigMap volume mount の仕組みを学び、H2-8 で演習②(nginx.conf 注入)を実施します。

ConfigMap volume mount — ファイルとして Pod に注入する仕組み

ConfigMap は環境変数として注入するだけでなく、ファイルとして Pod 内にマウントすることもできます。アプリが「設定ファイルを読み込む」前提で実装されている場合(nginx・Apache・Postgres などのミドルウェアや、Spring Boot の application.properties 等)はこちらが自然です。

本セクションでは volume mount の仕組み、subPath の使い分け、そして envFrom との更新タイミングの違いを整理します。

volume mount の記述パターン

ConfigMap を volume mount で Pod に注入するには、Pod YAML の 2 箇所を編集します。

spec:
  volumes:
    - name: nginx-config
      configMap:
        name: nginx-config-demo   # ConfigMap 名を指定
  containers:
    - name: nginx
      image: nginx:1.27-alpine
      volumeMounts:
        - name: nginx-config
          mountPath: /etc/nginx/conf.d/default.conf
          subPath: nginx.conf      # ConfigMap の "nginx.conf" キー値だけを default.conf として配置

ポイントは 2 つあります。

  • spec.volumes[].configMap.name でマウント元の ConfigMap を指定する
  • spec.containers[].volumeMounts[] でコンテナ内のマウント先パスを指定する

subPath の有無による違い

subPath をつけるかどうかでマウント挙動が変わります。

パターン挙動使い所
subPath なしConfigMap の全キーが mountPath ディレクトリ内の各ファイルとして展開される。mountPathディレクトリとして扱われ、既存ファイルがあれば隠される複数ファイルを一括配置したい場合(証明書一式・複数 conf 等)
subPath ありConfigMap の指定キーの値だけが、mountPath という単一ファイルとして配置される。同階層の他ファイルは隠されない既存設定ファイル 1 本だけを上書きしたい場合(nginx.conf 等・本回の演習②)

nginx の /etc/nginx/conf.d/ ディレクトリには複数の設定ファイルが存在します(default.conf がデフォルト設定)。本回の演習では default.conf 1 ファイルだけを差し替えたいため、subPath: nginx.conf を指定します。subPath を指定しないとディレクトリごと上書きされてしまい、nginx の本体起動設定が壊れる可能性があります。

envFrom と volume mount の更新タイミング比較

ConfigMap を更新したときに、Pod 側でその変更がいつ反映されるかは注入方式によって異なります。

注入方式更新反映タイミングPod 再起動補足
envFrom / env[].valueFrom次回 Pod 起動時のみ必要実行中 Pod の環境変数は固定。再作成または Rolling Update が必要
volume mount(subPath なし)kubelet sync 周期(デフォルト 60 秒)で自動更新不要ファイルの中身が変わる。アプリ側で reload できれば再起動なしで反映
volume mount(subPath あり)更新されない(Pod 再作成が必要)必要subPath マウントは bind mount として扱われるため

注意したいのは「volume mount でも subPath を使うと自動更新が効かない」点です。これは Linux の bind mount の仕様によるもので、Kubernetes のバグではありません。subPath マウントは元ファイルへのパス参照を固定するため、ConfigMap が更新されてもマウント先のファイルの中身は変わりません。

本回の演習②では学習目的で「subPath を使ったあとに ConfigMap を更新してみて、ファイルが変わらないことを確認する」という追加観察を行います。本番で「設定ファイルを動的更新したい」場合は subPath を使わずディレクトリマウントするか、Reloader のような ConfigMap 更新検知ツールで Pod を Rolling Update するパターンを採用します。

やってみよう②: ConfigMap volume mount で nginx.conf を Pod に注入する

nginx:1.27-alpine の Pod に対して、ConfigMap で定義した nginx.conf/etc/nginx/conf.d/default.conf として volume mount で注入します。所要時間目安は約 20 分です。fanclub-api のデモには直接関係しない、ConfigMap volume mount 単体の学習を目的とした独立演習です。

演習の全体フローは以下のとおりです。

  1. nginx:1.27-alpine イメージを kind クラスタにロード
  2. nginx 用 ConfigMap YAML を作成して apply
  3. nginx-configmap-demo Pod YAML を作成して apply
  4. マウントされた nginx.conf の内容を確認
  5. nginx 内部の設定が反映されていることを確認
  6. Port-forward で /hello エンドポイントの応答を確認
  7. 後片付け(Pod と ConfigMap は H2-13 まで残しておく)

Step 1: nginx:1.27-alpine イメージを kind クラスタにロード

kind クラスタはローカルの Docker daemon と独立したコンテナランタイムを持つため、ホスト側で pull したイメージは kind 内では参照できません。kind load docker-image でイメージをノードにロードします。第8回でも同じ手順を踏んだので復習に近いですが、本回でも実施します。

実行コマンド:

$ docker pull nginx:1.27-alpine

実行結果(既に pull 済の場合は Status: Image is up to date for nginx:1.27-alpine が表示):

1.27-alpine: Pulling from library/nginx
Digest: sha256:4bc3f5c4d8dae8bb069cceea6e9cb6f93c594a6bfd91ec6b06b0775a3261ea52
Status: Image is up to date for nginx:1.27-alpine
docker.io/library/nginx:1.27-alpine

続けて kind ノードにイメージをロードします。

実行コマンド:

$ kind load docker-image nginx:1.27-alpine --name kind

実行結果:

Image: "nginx:1.27-alpine" with ID "sha256:4bc3f5..." not yet present on node "kind-control-plane", loading...
Image: "nginx:1.27-alpine" with ID "sha256:4bc3f5..." loaded successfully

Step 2: nginx 用 ConfigMap YAML を作成して apply

nginx の設定ファイルを ConfigMap として定義します。

実行コマンド:

$ vi ~/fanclub-manifests/nginx-configmap-demo-configmap.yaml

ファイル内容:

apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-config-demo
  namespace: default
data:
  nginx.conf: |
    server {
        listen 80;
        server_name _;
        location /hello {
            return 200 'ConfigMap volume mount demo: Hello from nginx\n';
            add_header Content-Type text/plain;
        }
    }

YAML の nginx.conf: | という記法はブロックスカラーで、| 以下のインデントされた複数行テキストをそのまま値として保持します。改行も含めて保持されるので、設定ファイルの記述に向いています。apply します。

実行コマンド:

$ kubectl apply -f ~/fanclub-manifests/nginx-configmap-demo-configmap.yaml

実行結果:

configmap/nginx-config-demo created

Step 3: nginx-configmap-demo Pod YAML を作成して apply

ConfigMap を volume mount する nginx Pod を定義します。

実行コマンド:

$ vi ~/fanclub-manifests/nginx-configmap-demo-pod.yaml

ファイル内容:

apiVersion: v1
kind: Pod
metadata:
  name: nginx-configmap-demo
  namespace: default
  labels:
    app: nginx-configmap-demo
spec:
  containers:
    - name: nginx
      image: nginx:1.27-alpine
      imagePullPolicy: IfNotPresent
      ports:
        - containerPort: 80
      volumeMounts:
        - name: nginx-config
          mountPath: /etc/nginx/conf.d/default.conf
          subPath: nginx.conf
  volumes:
    - name: nginx-config
      configMap:
        name: nginx-config-demo

apply します。

実行コマンド:

$ kubectl apply -f ~/fanclub-manifests/nginx-configmap-demo-pod.yaml

実行結果:

pod/nginx-configmap-demo created

Pod が Running になるまで待ちます。

実行コマンド:

$ kubectl get pod nginx-configmap-demo -w

実行結果(ContainerCreating → Running):

NAME                   READY   STATUS              RESTARTS   AGE
nginx-configmap-demo   0/1     ContainerCreating   0          0s
nginx-configmap-demo   1/1     Running             0          5s

Step 4: マウントされた nginx.conf の内容を確認

Pod 内部の /etc/nginx/conf.d/default.conf に ConfigMap の値が配置されているか確認します。

実行コマンド:

$ kubectl exec nginx-configmap-demo -- cat /etc/nginx/conf.d/default.conf

実行結果(ConfigMap で書いた server { ... location /hello { ... } } がそのまま表示):

server {
    listen 80;
    server_name _;
    location /hello {
        return 200 'ConfigMap volume mount demo: Hello from nginx\n';
        add_header Content-Type text/plain;
    }
}

ConfigMap で定義した nginx.conf がコンテナ内のファイルとして配置されていることを確認できます。

Step 5: nginx 内部の設定が反映されていることを確認

nginx には nginx -T という現在の有効設定を出力するコマンドがあります。これを実行して、ConfigMap の内容が nginx の本体設定として読み込まれていることを確認します。

実行コマンド:

$ kubectl exec nginx-configmap-demo -- nginx -T 2>&1 | grep -A 5 'location /hello'

実行結果(location /hello { return 200 'ConfigMap volume mount demo: Hello from nginx\n'; ... } が表示):

        location /hello {
            return 200 'ConfigMap volume mount demo: Hello from nginx\n';
            add_header Content-Type text/plain;
        }

Step 6: Port-forward で /hello エンドポイントの応答を確認

kubectl port-forward でホストの 8088 番ポートを Pod の 80 番ポートに転送し、curl で疎通確認します。

実行コマンド(別ターミナルで実行・port-forward は動かしっぱなし):

$ kubectl port-forward pod/nginx-configmap-demo 8088:80

実行結果(Forwarding from 127.0.0.1:8088 -> 80 が表示):

Forwarding from 127.0.0.1:8088 -> 80
Forwarding from [::1]:8088 -> 80

元のターミナルから curl で確認します。

実行コマンド:

$ curl http://127.0.0.1:8088/hello

実行結果(ConfigMap volume mount demo: Hello from nginx):

ConfigMap volume mount demo: Hello from nginx

ConfigMap で定義した nginx 設定が実際のリクエスト処理に反映されていることを確認できました。port-forward は Ctrl+C で停止します。

Step 7: 後片付け方針

nginx-configmap-demo Pod と nginx-config-demo ConfigMap は ep11 以降では使用しません。学習目的の独立リソースのため、ep10 完了後に以下のコマンドで削除しても問題ありません(任意)。

実行コマンド(任意・本回の最後で実行):

$ kubectl delete pod nginx-configmap-demo
$ kubectl delete configmap nginx-config-demo

本回中は H2-13 の状態確認まで残しておきます。次の H2-9 で ServiceAccount の基礎を学び、H2-10 で演習③に進みます。

ServiceAccount 基礎 — Pod に紐づく K8s 内の認証アイデンティティ

ServiceAccount(以下 SA)は Pod が Kubernetes API Server と通信する際の認証アイデンティティを表す Namespace スコープのリソースです。CKAD 試験では SA の概念と automountServiceAccountToken による無効化が出題範囲に入ります。

本セクションでは SA の役割、default SA の自動挙動、Kubernetes v1.24 以降の Bounded Token 方式、そして automountServiceAccountToken: false による最小権限化を扱います。

SA とは何か

Kubernetes クラスタ内では、API Server に対するアクセスはすべて何らかの「主体」によって行われます。主体には人間ユーザー(kubectl 実行者)と SA の 2 種類があり、Pod 内のプロセスが API Server を呼び出す場合は SA を使います。SA の主な特徴は以下のとおりです。

  • Namespace スコープのリソース(同名 SA を別 Namespace に置ける)
  • SA そのものは権限を持たない(権限は RBAC の Role / RoleBinding で付与する)
  • Pod は spec.serviceAccountName で SA を指定する。指定しない場合は同 Namespace の default SA が割り当てられる
  • SA トークンが Pod 内の /var/run/secrets/kubernetes.io/serviceaccount/ にマウントされる(無効化可能)

SA は「誰として認証するか」だけを担当し、「何ができるか」は別レイヤー(RBAC)で決まります。本回では SA の作成と Pod への紐付けを扱い、RBAC 本格実装は第15回で行います。

default SA の自動挙動

Kubernetes は各 Namespace を作成したときに default という名前の SA を自動生成します。kubectl get serviceaccount で確認できます。

実行コマンド:

$ kubectl get serviceaccount -n default

実行結果(NAME SECRETS AGE / default 0 8h):

NAME      SECRETS   AGE
default   0         8h

SECRETS 列が 0 になっている点に注目します。Kubernetes v1.23 以前ではここに永続 Secret トークンの個数が入っていましたが、v1.24 以降では永続 Secret 方式が廃止され、Bounded Token 方式に移行したため、デフォルトでは紐づく Secret がゼロになります。

Kubernetes v1.24 以降の Bounded Token

Kubernetes v1.24 以降、SA トークンの仕組みは大きく変わりました。

項目v1.23 以前v1.24 以降
SA トークンの実体永続 Secret(type: kubernetes.io/service-account-token)が SA 作成時に自動生成TokenRequest API で要求された Bounded Token(有効期限あり)
マウント方式Secret volume として Pod にマウントProjected Volume として Pod にマウント
有効期限無期限(リボーク不可)デフォルト 1 時間(kubelet が自動更新)
Pod 内のマウントパス/var/run/secrets/kubernetes.io/serviceaccount/同上(kube-api-access-XXXXX Projected Volume 経由)

v1.24 以降の重要な変更点は「永続トークンが自動作成されなくなった」ことです。これにより、SA を削除してもトークンが Secret として残る心配がなくなり、トークンが期限切れで自動失効するため、漏洩時の影響範囲が時間的に限定されるメリットが生まれました。

Pod 内のマウントパスは v1.23 以前と同じ /var/run/secrets/kubernetes.io/serviceaccount/ ですが、ボリュームの実体は Projected Volume になっています。

kubectl describe podMounts: セクションを見ると、kube-api-access-XXXXX という名前の Projected Volume が表示されます。

マウントパスには 3 つのファイルが配置されます。

  • token:API Server 認証用の JWT トークン(Bounded Token)
  • ca.crt:API Server の TLS 証明書を検証するための CA 証明書
  • namespace:Pod が所属する Namespace 名

Pod 内のアプリは cat /var/run/secrets/kubernetes.io/serviceaccount/token で JWT を取得し、Authorization: Bearer <token> ヘッダを付けて https://kubernetes.default.svc/api/... を呼び出すことで API Server と通信できます。

Operator や Sidecar として API Server を呼ぶ Pod ではこの仕組みが必須です。

automountServiceAccountToken: false の効果

多くの Pod は API Server を呼び出しません。例えば fanclub-backend Pod は PostgreSQL に接続するだけで、Kubernetes API には直接アクセスしません。それなのに SA トークンが Pod にマウントされていると、以下のリスクが生まれます。

  • Pod のアプリに脆弱性があり攻撃者にシェル取得された場合、攻撃者は SA トークンを使って API Server を呼び出せる
  • default SA であっても、`get pods` 等の最小権限を持っているケースがあれば、Namespace 内の他 Pod の情報を読み取られる
  • クラスタの構造(Namespace 一覧・Pod 一覧)を偵察される

これを防ぐ仕組みが automountServiceAccountToken: false です。これを Pod または SA に設定すると、API Server 認証情報がマウントされなくなります。設定箇所は 2 箇所あり、優先順位は以下のとおりです。

設定箇所挙動使い所
SA レベル(ServiceAccount リソースの spec)その SA を参照するすべての Pod に対して、デフォルトで token をマウントしないSA 単位で「この SA を使う Pod は API 不要」と決められる場合
Pod レベル(Pod リソースの spec)その Pod のみ token をマウントしない(SA レベルの設定を上書き)個別 Pod で例外的に無効化したい場合

本回の演習③では SA レベルと Pod レベルの両方に automountServiceAccountToken: false を設定します。SA レベルだけでも十分ですが、Pod 側に明示することで「この Pod は API Server を呼ばない」という設計意図を YAML から読み取れるメリットがあります。

本番ではゼロトラスト原則に沿って、API Server を呼ばない Pod では基本 automountServiceAccountToken: false を付ける運用が推奨されます。

本番運用警告(その 2):API Server を呼ばない Pod に SA トークンをマウントしたままにするのは、不要な権限を Pod に持たせている状態です。最小権限の原則(Principle of Least Privilege)に反します。

本番では「fanclub-backend のように API を呼ばない Pod では SA トークンをマウントしない」という設計を標準化する必要があります。

imagePullSecrets — SA に紐づくレジストリ認証情報

SA には imagePullSecrets というフィールドがあります。プライベートレジストリから image を pull するための認証情報を Secret として作成し、その Secret 名を SA に紐づけると、その SA を使う Pod は自動的にレジストリ認証付きで image pull を行えます。

本回では imagePullSecrets概念紹介のみとします。本シリーズの k8s-registry は第4回でホスト名 k8s-registry:5000insecure-registries に登録しているため認証なしで pull できる構成であり、本回時点では imagePullSecrets の実装は不要です。

実際にプライベートレジストリ認証 Secret を作って使う設計は、第17〜18回の Helm + cert-manager + Gateway API による HTTPS 公開のあたりで再度扱います。

automountServiceAccountToken の true と false の挙動を左右対比した図。左の default SA を使う Pod は token・ca.crt・namespace を含む Projected Volume(Bounded Token)が /var/run/secrets/kubernetes.io/serviceaccount/ にマウントされる。右の fanclub-backend-sa を使う Pod はマウントされず、当該ディレクトリ自体が存在しない。中段の通知バンドは KUBERNETES_* 環境変数が automountServiceAccountToken の設定と独立して注入されることを補足し、下部の注記が不要な SA トークンを Pod に渡さない最小権限の原則を示している。

やってみよう③: ServiceAccount を作成して automountServiceAccountToken の効果を確認する

fanclub-backend 専用の SA fanclub-backend-sa を作成し、SA レベルと Pod レベルの両方に automountServiceAccountToken: false を設定して、Pod 内に SA トークンがマウントされないことを実機で確認します。所要時間目安は約 15 分です。

演習の全体フローは以下のとおりです。

  1. 変更前の Pod で SA トークンマウント状況を確認
  2. fanclub-backend-sa SA を作成して apply
  3. SA の作成内容を確認
  4. Backend Pod YAML に serviceAccountName と automountServiceAccountToken: false を追加
  5. 既存 Pod を削除して再 apply
  6. 変更後の Pod で SA トークンがマウントされていないことを確認
  7. describe pod で Service Account 設定を確認

Step 1: 変更前の Pod で SA トークンマウント状況を確認

演習①完了直後の fanclub-backend Pod は default SA を使っており、SA トークンがマウントされた状態です。まずその状態を確認します。

実行コマンド:

$ kubectl exec fanclub-backend -- ls /var/run/secrets/kubernetes.io/serviceaccount/

実行結果(ca.crt / namespace / token の 3 ファイルが表示):

total 0
drwxrwxrwt   3 root root 140 May 10 09:51 .
drwxr-xr-x+  3 root root  28 May 10 09:51 ..
drwxr-xr-x+  2 root root 100 May 10 09:51 ..2026_05_10_09_51_44.3865656249
lrwxrwxrwx+  1 root root  32 May 10 09:51 ..data -> ..2026_05_10_09_51_44.3865656249
lrwxrwxrwx+  1 root root  13 May 10 09:51 ca.crt -> ..data/ca.crt
lrwxrwxrwx+  1 root root  16 May 10 09:51 namespace -> ..data/namespace
lrwxrwxrwx+  1 root root  12 May 10 09:51 token -> ..data/token

続けて describe pod で Mounts セクションを確認します。

実行コマンド:

$ kubectl describe pod fanclub-backend | grep -A 3 -E '(Service Account|Mounts:)'

実行結果(Service Account: default および Mounts: /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-XXXXX (ro) が表示):

Service Account:  default
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-xb9qz (ro)

Projected Volume kube-api-access-XXXXX として Bounded Token がマウントされていることが確認できます。これが v1.24 以降のデフォルト挙動です。

Step 2: fanclub-backend-sa SA を作成して apply

fanclub-backend 専用の SA を作成します。SA レベルで automountServiceAccountToken: false を指定します。

実行コマンド:

$ vi ~/fanclub-manifests/fanclub-backend-sa.yaml

ファイル内容:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: fanclub-backend-sa
  namespace: default
  labels:
    app: fanclub-backend
automountServiceAccountToken: false

apply します。

実行コマンド:

$ kubectl apply -f ~/fanclub-manifests/fanclub-backend-sa.yaml

実行結果:

serviceaccount/fanclub-backend-sa created

Step 3: SA の作成内容を確認

SA が作成されたことを確認します。

実行コマンド:

$ kubectl get serviceaccount -n default
$ kubectl describe serviceaccount fanclub-backend-sa

実行結果(SA 一覧に default と fanclub-backend-sa の 2 件・describe では Tokens: <none> および Image pull secrets: <none> が表示される):

NAME                 SECRETS   AGE
default              0         8h
fanclub-backend-sa   0         2s

Name:                fanclub-backend-sa
Namespace:           default
Labels:              app=fanclub-backend
Annotations:         <none>
Image pull secrets:  <none>
Mountable secrets:   <none>
Tokens:              <none>
Events:              <none>

v1.24 以降は SA を作っても永続 Secret トークンが自動生成されないため、Tokens: <none> となります。これが現バージョンの正しい挙動です。

Step 4: Backend Pod YAML に serviceAccountName と automountServiceAccountToken: false を追加

演習① で envFrom 版に書き換えた fanclub-backend-with-db-pod.yaml に、SA 関連の 2 行を追加します。

実行コマンド:

$ vi ~/fanclub-manifests/fanclub-backend-with-db-pod.yaml

ファイル内容(演習③完了時点での全量・第4版):

apiVersion: v1
kind: Pod
metadata:
  name: fanclub-backend
  namespace: default
  labels:
    app: fanclub-backend
spec:
  serviceAccountName: fanclub-backend-sa
  automountServiceAccountToken: false
  initContainers:
    - name: wait-for-db
      image: busybox:1.36
      command:
        - sh
        - -c
        - "until nc -z fanclub-db 5432; do echo 'waiting for DB...'; sleep 2; done"
  containers:
    - name: fanclub-backend
      image: fanclub-backend:0.1.0
      imagePullPolicy: IfNotPresent
      ports:
        - containerPort: 8080
      envFrom:
        - configMapRef:
            name: fanclub-config
        - secretRef:
            name: fanclub-secret
      resources:
        requests:
          memory: "256Mi"
          cpu: "250m"
        limits:
          memory: "512Mi"
          cpu: "1000m"

演習① 完了時点(第3版)からの追加は次の 2 行です。

  • spec.serviceAccountName: fanclub-backend-sa
  • spec.automountServiceAccountToken: false

Step 5: 既存 Pod を削除して再 apply

spec.serviceAccountName も immutable フィールドのため、Pod を削除して再作成します。

実行コマンド:

$ kubectl delete pod fanclub-backend
$ kubectl apply -f ~/fanclub-manifests/fanclub-backend-with-db-pod.yaml
$ kubectl get pod fanclub-backend -w

実行結果(削除完了 → apply 成功 → Init:0/1 → PodInitializing → Running の遷移):

pod "fanclub-backend" deleted
pod/fanclub-backend created
NAME              READY   STATUS     RESTARTS   AGE
fanclub-backend   0/1     Init:0/1   0          0s
fanclub-backend   0/1     PodInitializing   0   10s
fanclub-backend   1/1     Running    0          21s

Pod が Running になったら Ctrl+C で -w ウォッチを抜けます。

Step 6: 変更後の Pod で SA トークンがマウントされていないことを確認

SA トークンが Pod 内にマウントされていないことを確認します。

実行コマンド:

$ kubectl exec fanclub-backend -- ls /var/run/secrets/kubernetes.io/serviceaccount/

実行結果(ls: cannot access '/var/run/secrets/kubernetes.io/serviceaccount/': No such file or directory エラー・終了コード非ゼロ):

ls: cannot access '/var/run/secrets/kubernetes.io/serviceaccount/': No such file or directory
command terminated with exit code 2

このエラーは演習③が成功した証拠です。automountServiceAccountToken: false によって SA トークンのマウント自体が無効化された結果、/var/run/secrets/kubernetes.io/serviceaccount/ ディレクトリそのものが存在しなくなったため、ls コマンドが「ディレクトリがない」というエラーで失敗しています。

Pod の挙動としては正常で、想定した状態に到達しています。

Step 7: describe pod で Service Account 設定を確認

describe pod で SA 関連の設定を確認します。

実行コマンド:

$ kubectl describe pod fanclub-backend | grep -E '(Service Account|Mounts:|kube-api-access)'

実行結果(Service Account: fanclub-backend-sa が表示される。kube-api-access-XXXXX Mount は表示されない):

Service Account:  fanclub-backend-sa

SA は fanclub-backend-sa に切り替わり、Projected Volume kube-api-access-XXXXX は Mount セクションから消えていることを確認できます。Step 1 の状態と比較すると効果が明らかです。

演習③の確認はここまでです。これで本回の 3 演習がすべて完了しました。続いて H2-11 で Secret の限界と本番設計の考え方、H2-12 で現場ヒヤリハット 2 本を扱います。

etcd 暗号化と外部 Secrets 管理 — Secret の限界と本番設計の考え方

本回ここまでで Kubernetes Secret の基本仕様を扱いましたが、本番運用では Secret だけでは十分なセキュリティを確保できません。本セクションでは Secret の限界と、本番で組み合わせるべき仕組み(etcd 暗号化・RBAC 最小権限・外部 Secrets 管理)を概念レベルで整理します。実装の詳細は第3巻 CKS 編で扱います。

Secret の実体は etcd 内の base64 文字列

Kubernetes クラスタの全リソースは etcd というキーバリューストアに保存されます。Secret も例外ではなく、etcd 上に data: フィールドの値が base64 エンコードされた状態で格納されます。etcd への書き込み・読み出しは平文 base64 文字列であり、暗号化はデフォルトでは適用されません

これが意味するのは次のとおりです。

  • etcd のディスクが盗まれた場合、攻撃者は base64 を逆変換するだけで Secret の平文値を取得できる
  • etcd のバックアップファイルが流出した場合も同様
  • API Server に kubectl で読み取り権限があるユーザーは kubectl get secret -o yaml + base64 -d で平文取得できる

本回 H2-4 でこの事実を実機デモで確認しましたが、本番では etcd レベルでの暗号化が追加で必要です。

etcd 暗号化(Encryption at Rest)

Kubernetes には etcd への書き込み時に Secret を暗号化する機能(Encryption at Rest)があります。Kubernetes v1.13 で GA となり、現在は kubeadm で構築するクラスタでも以下の手順で有効化できます。

  • EncryptionConfiguration リソースを記述したファイルを Control Plane Node に配置(/etc/kubernetes/encryption-provider-config.yaml など)
  • kube-apiserver 起動オプションに --encryption-provider-config=<path> を追加(kubeadm では /etc/kubernetes/manifests/kube-apiserver.yaml を編集)
  • 暗号化プロバイダとして AES-GCM(推奨)/ AES-CBC / KMS v2(外部 KMS と連携)から選択
  • 既存 Secret を再書き込みして暗号化適用(kubectl get secret --all-namespaces -o json | kubectl replace -f -

有効化すると、etcd に書き込まれる Secret の値が暗号化され、etcd のディスクやバックアップが流出しても暗号鍵がない限り平文化できなくなります。kind 環境では Control Plane Node の構成を直接いじれないためデフォルトで etcd 暗号化は無効ですが、kubeadm 環境(第2巻以降)では設定可能です。

本回では概念紹介に留め、第3巻 CKS 編で実機ハンズオンを行います。

RBAC による Secret アクセス権限の最小化

etcd 暗号化を有効化しても、API Server 経由で kubectl get secret が許可されているユーザーは平文を取得できます。次のレイヤとして、RBAC(Role-Based Access Control)で Secret への get / list 権限を必要なサービスのみに限定する必要があります。

  • 開発者ロールには Secret の閲覧権限を渡さない(必要なら個別 Secret に限定)
  • fanclub-backend-sa のような業務 SA も、自分が必要とする Secret 以外は読めないようにする
  • クラスタ管理者ロールであっても、本番 Secret の閲覧は監査ログで追跡する

RBAC の実装は第15回(Role / RoleBinding / ClusterRole / ClusterRoleBinding)で扱います。

外部 Secrets 管理(Vault / External Secrets Operator)

etcd 暗号化と RBAC を併用しても、Secret のライフサイクル管理(ローテーション・監査・複数クラスタ共有・期限付き発行)は Kubernetes 側だけでは賄いきれません。本番では外部の Secrets 管理基盤と連携するパターンが定石です。

仕組み役割
HashiCorp Vaultエンタープライズ向け Secret 管理基盤。動的 Secret 発行・ローテーション・監査ログを集中管理
External Secrets OperatorVault / AWS Secrets Manager / GCP Secret Manager 等の外部ストアと Kubernetes Secret を同期する Operator
SealedSecretsSecret を暗号化したまま Git にコミットできる仕組み。GitOps と相性が良い

これらは第3巻 CKS 編 第5回で実機実装します。

本番での定石

本番 Kubernetes クラスタの Secret 管理は、以下の 3 層を組み合わせるのが定石です。

  1. etcd 暗号化(Encryption at Rest):etcd ディスクやバックアップ流出時の保護
  2. RBAC で Secret アクセス最小化:API Server 経由の不要な閲覧を遮断
  3. 外部 Secrets 管理(Vault / External Secrets Operator):ローテーションとライフサイクル管理

本番運用警告(その 3):「Secret に入れているから安全」と判断するのは誤りです。本回 H2-4 で実機確認したとおり、Kubernetes Secret の base64 はエンコードであり暗号化ではありません。本番では上記 3 層の対策を実装することが必須です。

CKAD 試験ではここまで深い設計は問われませんが、現場で「Secret に入れたから OK」という設計判断をすると Security Review で必ず指摘されます。

現場ヒヤリハット — envFrom 更新後の Pod 再起動忘れ / base64 を暗号化と誤解してセキュリティ事故

ConfigMap・Secret・SA は CKAD 試験で頻出する一方、本番でも誤解と運用ミスが起きやすい領域です。本セクションでは現場で実際に踏むトラブル 2 本を、シナリオ・実機コマンド・根本原因・解決策・本番ガードレールの 5 点セットで整理します。

ヒヤリハット 1:envFrom の ConfigMap を更新したのに Pod の env が変わらない

シナリオ:fanclub-backend が接続する DB のホスト名を fanclub-db から fanclub-db-new に切り替えたいので、ConfigMap fanclub-configDB_HOSTkubectl edit configmap で書き換えた。

エンジニア A は「ConfigMap を更新したから新しい接続先で動くはず」と判断したが、Backend ログには相変わらず fanclub-db への接続試行が記録されており、新しい DB に切り替わらない。

実行コマンド(ConfigMap 側の更新を確認):

$ kubectl get configmap fanclub-config -o jsonpath='{.data.DB_HOST}'

実行結果(ConfigMap は更新済):

fanclub-db-new

実行コマンド(Pod 内の環境変数を確認):

$ kubectl exec fanclub-backend -- printenv DB_HOST

実行結果(Pod の env はまだ古い値):

fanclub-db

根本原因envFrom で注入した環境変数は Pod の起動時に一度だけ評価され、その後 ConfigMap が更新されても実行中 Pod には反映されません。これは Kubernetes の API 仕様であり、バグではありません。Pod のプロセスツリーが起動した時点で環境変数は固定され、プロセスを再起動しないと変わらないためです。

解決策:Pod を削除して再作成します。

$ kubectl delete pod fanclub-backend
$ kubectl apply -f ~/fanclub-manifests/fanclub-backend-with-db-pod.yaml
$ kubectl exec fanclub-backend -- printenv DB_HOST

新しい Pod の printenv DB_HOST 出力は更新後の値(例: fanclub-db-new)が表示されます。

第12回で扱う Deployment を使えば、ConfigMap 変更後に kubectl rollout restart deployment で Rolling Update を起こして自動再起動できます。本番ではこの方法が定石です。

本番ガードレール

  • ConfigMap や Secret を更新したら、参照している Pod を必ず再起動する運用ルールを設ける
  • 本番で Pod 単体運用は避け、Deployment / StatefulSet 経由で Rolling Update できる構成にする(第12回で扱う)
  • Reloader のようなツールを導入して、ConfigMap / Secret 更新を検知して自動 Rolling Update する仕組みを追加するのも有効

volume mount で注入したファイル(subPath なし)の場合は kubelet sync 周期で自動更新されますが、アプリ側で reload する仕組みがなければ結局再起動が必要です。「ConfigMap 更新 = 再起動が必要」と覚えておくのが現場の安全策です。

ヒヤリハット 2:base64 を暗号化と誤解して Security Review で指摘された事例

シナリオ:チーム B はリリース前のセキュリティレビューで「DB パスワードはどこに保存していますか」と問われた。担当者は「Kubernetes Secret に格納しているので暗号化されており安全です」と回答した。

レビュアーが kubectl get secret fanclub-secret -o yaml を実行し、data: フィールドの値を base64 -d でデコードしたところ、平文 DB パスワードが即座に画面に表示された。これによりチーム B のリリース判定はブロックされ、機密管理の設計変更が必要になった。

レビュアーが実行した確認手順を実機で再現する

実行コマンド:

$ kubectl get secret fanclub-secret -o yaml

実行結果(抜粋・data: DB_PASSWORD: YXBwcGFzc3dvcmQ= / DB_USER: YXBwdXNlcg== が表示):

apiVersion: v1
data:
  DB_PASSWORD: YXBwcGFzc3dvcmQ=
  DB_USER: YXBwdXNlcg==
kind: Secret
metadata:
  name: fanclub-secret
  namespace: default
  ...
type: Opaque

実行コマンド(即座にデコード):

$ echo -n 'YXBwcGFzc3dvcmQ=' | base64 -d

実行結果:

apppassword

根本原因:base64 は可逆エンコードであり、暗号化鍵を必要としない単なる文字列変換です。Kubernetes Secret の data: フィールドは「base64 化された平文」が保存されているにすぎず、API Server に get secret 権限を持つユーザーは誰でも 1 コマンドで平文化できます。

「Secret = 暗号化されている」という前提で設計してしまうと、本番で機密漏洩のリスクをゼロと誤認します。

解決策

  1. etcd 暗号化(Encryption at Rest)を有効化する。これで etcd ディスク・バックアップ流出に対する防御が機能する(第3巻 CKS で実装)
  2. RBAC で Secret の get / list 権限を必要最小限にする。アプリの SA・運用 SRE・特定の管理者ロールのみが Secret を読めるよう設計する(第15回で実装)
  3. 外部 Secrets 管理(HashiCorp Vault・External Secrets Operator)を採用する。Secret を Kubernetes 側で永続管理せず、外部ストアから動的に取り出す(第3巻 CKS で実装)

本番ガードレール

  • セキュリティレビューで「Secret に入れています」と回答するのは設計の説明として不十分。何で暗号化しているか・誰が読めるか・どうローテーションするかをセットで説明する
  • 新規プロジェクトの設計初期から「etcd 暗号化 + RBAC + 外部 Secrets 管理」の 3 層構成をテンプレート化して、設計ドキュメントに必須項目として組み込む
  • Git に Kubernetes マニフェストをコミットする際は、Secret YAML を直接 push しない。SealedSecrets 等で暗号化したまま GitOps するパターンを採用する

このヒヤリハットは、Kubernetes 初学者が必ず通る誤解です。本回 H2-4 と本セクションで実機デモを 2 度繰り返したのは、「base64 は暗号化ではない」という事実を体に染み込ませるためです。本番設計でこの誤解を避けるだけで、機密漏洩リスクの相当部分を抑えられます。

第10回完了後の模擬アプリ状態と第11回への橋渡し

本回の演習 3 本がすべて完了したあとの fanclub-api 構成を整理し、第11回への接続を確認します。

第10回完了後の模擬アプリ状態

リソース状態本回の変更
fanclub-backend PodRunning(envFrom 版・SA 設定済・automountServiceAccountToken: false)第3版 → 第4版に再 apply(env 平文削除・envFrom 追加・SA 関連 2 行追加)
fanclub-backend Service (ClusterIP)残存変更なし(ep8 から継続)
fanclub-db StatefulSet(fanclub-db-0)Running変更なし(ep9 から継続)
fanclub-db Service / fanclub-db-headless Service残存変更なし(ep9 から継続)
postgres-data-fanclub-db-0 PVCBound変更なし(ep9 から継続)
members テーブル永続データ2 行残存(山田太郎・鈴木花子)変更なし
fanclub-db-init ConfigMap残存変更なし(ep9 から継続)
fanclub-config ConfigMap新規作成本回新規(4 キー)
fanclub-secret Secret新規作成本回新規(2 キー・stringData)
fanclub-backend-sa ServiceAccount新規作成本回新規(automountServiceAccountToken: false)
nginx-config-demo ConfigMap残存(任意削除可)本回演習②で作成
nginx-configmap-demo Pod残存(任意削除可)本回演習②で作成

fanclub-backend の構成は次のように進化しました。

第8回完了時:
  [Backend Pod (env なし)] → [Backend Service]

第9回完了時:
  [Backend Pod (env 6項目 平文)] → [Backend Service]
                ↓ env で DB 接続情報
  [DB StatefulSet] ← [DB Service / Headless Service] ← [PVC]

第10回完了時:
  [Backend Pod (envFrom + SA)] → [Backend Service]
       ↑ envFrom        ↑ SA
  [ConfigMap]        [SA: fanclub-backend-sa]
  [Secret]           (automountServiceAccountToken: false)
                ↓ envFrom 経由で DB 接続情報
  [DB StatefulSet] ← [DB Service / Headless Service] ← [PVC]

同じ image を dev / staging / prod で使い回す設計の足場ができました。実際の環境分離は第14回(Multi-tenant Namespace)で扱います。

第11回への橋渡し

本回でアプリ設定の外部化と SA の基礎が整いました。次回は Kubernetes のバッチ処理・定期実行・デーモン配置を扱います。

  • Job:ワンショット実行のワークロード。完了後に Pod が Completed 状態になる。本回 fanclub-api 用の DB マイグレーション Job を実装し、スキーマ変更をバッチ処理で実行するパターンを学ぶ
  • CronJob:cron スケジュールで Job を定期実行するワークロード。バックアップやレポート生成に使う
  • DaemonSet:すべての Workload Node に 1 Pod ずつ配置するワークロード。ログ収集 Agent やノード監視 Agent などに使う

これで Pod 系・StatefulSet 系・Job 系のワークロードリソースが揃い、第12回の Deployment + 3 Probe + Rolling Update に進む準備が整います。

理解度チェック・第10回まとめ・次回予告・シリーズ一覧

理解度チェック(○×形式・9 問)

問 1:ConfigMap は機密情報(パスワード等)を格納するために使うべき Kubernetes リソースである。

問 2envFrom.configMapRef を使うと、ConfigMap のすべてのキーを環境変数として Pod に一括注入できる。

問 3:Kubernetes の Secret は etcd に格納される際に base64 エンコードされているため、暗号化によって保護されている。

問 4:ConfigMap を subPath を指定せずに volume mount でファイルとして注入した場合、ConfigMap を更新すると Pod を再起動しなくても kubelet の sync 周期でファイルが自動更新される。

問 5envFrom で注入した環境変数は、ConfigMap を更新するだけで実行中の Pod に即座に反映される。

問 6:Kubernetes v1.24 以降、ServiceAccount トークンは永続 Secret として自動作成されなくなり、有効期限付きの Bounded Token(Projected Volume)が使われるようになった。

問 7automountServiceAccountToken: false を Pod に設定すると、Pod が API Server に認証するための Token がマウントされなくなる。

問 8:Secret の stringData: フィールドに平文テキストを記述すると、kubectl apply 時に自動で base64 エンコードされて data: フィールドに保存される。

問 9:fanclub-backend Pod で automountServiceAccountToken: false を設定した場合、/var/run/secrets/kubernetes.io/serviceaccount/ ディレクトリは存在しなくなる。

解答

解答解説
問 1×ConfigMap は非機密データを格納するためのリソース。機密データには Secret を使う。役割分担を厳守する
問 2envFrom[].configMapRef.name で ConfigMap の全キーを一括注入する。secretRef も同様に Secret 全キーを一括注入できる
問 3×base64 はエンコードであり暗号化ではない。base64 -d で誰でもデコード可能。本番では etcd 暗号化(Encryption at Rest)を別途有効化する必要がある
問 4volume mount(subPath なし)の場合、kubelet sync 周期(デフォルト 60 秒)で自動更新される。Pod 再起動は不要。ただし subPath を使うと自動更新は効かない(bind mount のため)
問 5×envFrom で注入した環境変数は Pod 起動時に固定される。ConfigMap を更新しても実行中 Pod には反映されない。Pod の削除・再作成または Deployment の Rolling Update が必要
問 6v1.24 以降、SA 作成時に永続 Secret トークンが自動生成されなくなった。代わりに TokenRequest API による Bounded Token(デフォルト 1 時間有効・kubelet 自動更新)が使われる。Projected Volume として Pod にマウントされる
問 7SA レベルまたは Pod レベルで automountServiceAccountToken: false を設定すると、API Server 認証用 Token のマウントが無効化される。Pod レベルが SA レベルを上書きする優先順位
問 8stringData: は書き込み専用フィールドで、kubectl apply 時に Kubernetes が自動で base64 変換する。kubectl get secret -o yaml で取得すると data: 側に変換済の値が表示される
問 9automountServiceAccountToken: false によって SA トークン用の Projected Volume 自体がマウントされなくなる。kubectl exec ... -- ls /var/run/secrets/kubernetes.io/serviceaccount/No such file or directory エラーで失敗する。これが期待動作

第10回まとめ

第10回では以下を実施しました。

  • Pod YAML への env 平文埋め込みは本番運用の禁忌である理由を整理した。Git 履歴に残ること、kubectl describe pod で誰でも値が読めることの 2 点が本質的な問題であり、Kubernetes は ConfigMap(非機密)と Secret(機密)の役割分担で 12-Factor App Factor III「設定」を実現する。本回 fanclub-backend に対して 6 キー(ConfigMap)+ 2 キー(Secret)の分離を行い、env 平文埋め込み版から envFrom 版へ再 apply した
  • envFrom[].configMapRef + envFrom[].secretRef の 2 行で ConfigMap と Secret の全キーを一括注入する設計を採用した。env[].valueFrom(特定キー注入)との違いを比較表で整理し、Pod YAML がシンプルになる利点を確認した。複数 envFrom を指定したときの上書き順序(後勝ち)も仕様として理解した
  • Kubernetes Secret の data: フィールドは base64 エンコードされているが、これは暗号化ではないecho -n 'apppassword' | base64echo -n 'YXBwcGFzc3dvcmQ=' | base64 -d の往復を実機で実行し、鍵なしで往復できる事実を確認した。本番では etcd 暗号化 + RBAC 最小権限 + 外部 Secrets 管理(Vault / External Secrets Operator)の 3 層構成が必須であり、第3巻 CKS で実機実装する
  • ConfigMap を volume mount でファイルとして注入する仕組みを nginx:1.27-alpine の nginx.conf 注入で実機確認した。subPath なしの場合は kubelet sync 周期(60 秒)で自動更新される一方、subPath を使うと bind mount のため更新が反映されない仕様を整理した。envFrom(Pod 再起動必要)と volume mount(自動更新)の更新タイミング比較表を作成した
  • ServiceAccount は Pod が API Server と通信する際の認証アイデンティティであり、Kubernetes v1.24 以降は永続 Secret トークン方式から Bounded Token(Projected Volume)方式に切り替わった。automountServiceAccountToken: false を SA レベルと Pod レベルの両方で設定し、fanclub-backend Pod 内に SA トークンがマウントされないことを ls /var/run/secrets/kubernetes.io/serviceaccount/No such file or directory エラーで実機確認した。API Server を呼ばない Pod では SA トークンを切る設計が最小権限の原則に沿う

次回予告

第11回 Job + CronJob + DaemonSetでは、Kubernetes のバッチ処理・定期実行・デーモン配置を扱います。fanclub-api の DB マイグレーション Job を実装してスキーマ変更をバッチ処理で実行するパターンを体験し、CronJob によるバックアップ定期実行、DaemonSet によるノード単位デーモンの配置を学びます。

これで Pod / StatefulSet / Job / CronJob / DaemonSet のワークロードリソースが揃い、第12回の Deployment + 3 Probe + Rolling Update に進む準備が整います。CKAD ドメイン D1(Application Design and Build)の補完となる重要回です。

シリーズ一覧

第1部:コンテナと Docker

第2部:Kubernetes 基礎

第3部:アプリリソース

第4部:ワークロード戦略

第5部:セキュリティ基礎

第6部:パッケージ管理 + HTTPS 公開

広告
kubernetes
スポンサーリンク