Kubernetes入門 第9回:ConfigMapとSecretによる設定管理

Kubernetes入門
第9回:ConfigMapとSecretによる設定管理

〜 設定ファイルをサーバーに置くな、Kubernetesに預けろ 〜


はじめに

VMを運用していると、設定ファイルの変更作業はつきものです。複数台のサーバーに同じ設定を反映させるために、SSHで各サーバーにログインしたり、Ansibleで一斉配信したり。それでも「あのサーバーだけ設定が古かった」という事故は起こりがちです。

Kubernetesでは、設定ファイルをサーバーの外に出し、コンテナには起動時に注入するアプローチを取ります。これを実現するのが「ConfigMap」と「Secret」です。

前回、PersistentVolumeでデータの永続化を学びました。今回は、アプリケーションの振る舞いを決める設定を、コンテナイメージから分離する技術を習得します。


9.1 疎結合:環境依存の設定をコンテナの外に出す

9.1.1 「/etc」を直接いじらない運用:設定ファイルをK8sリソース化する利点

VMの世界では、設定変更といえば/etc配下のファイルを編集することでした。

# VMでの典型的な作業フロー
ssh admin@web-server-01
sudo vi /etc/nginx/nginx.conf
sudo systemctl reload nginx
# → 残り19台に対して同じ作業を繰り返す...

この運用には、3つの構造的な問題があります。

問題具体例結果
状態の散在設定ファイルが各サーバーに分散「どのサーバーが最新か」が不明確に
変更履歴の不在viで直接編集すると履歴が残らない「誰がいつ何を変えたか」が追えない
環境差異の蓄積手作業のたびに微妙な差異が生まれる「本番だけ動かない」問題の温床

Kubernetesでは、設定ファイルをConfigMapというリソースとして定義します。ConfigMapはYAMLファイルとしてGitで管理でき、kubectl apply一発で全Podに反映されます。

設定ファイルの居場所の変化:

【VM時代】
  サーバーA:/etc/nginx/nginx.conf  ─┐
  サーバーB:/etc/nginx/nginx.conf  ─┼─ 各サーバーに分散(状態がバラバラ)
  サーバーC:/etc/nginx/nginx.conf  ─┘

【K8s時代】
  ConfigMap (nginx-config)  ───→  Pod A, B, C に一斉配信
  (Gitで管理、変更履歴も残る)

9.1.2 2つの役割:設定を配る「ConfigMap」と、機密を守る「Secret」

Kubernetesには、設定を外部化するための2つのリソースがあります。

ConfigMap:一般的な設定情報

アプリケーションの設定ファイル、環境変数、コマンドライン引数など、機密性のない設定情報を格納します。

用途の例:

  • Nginxのnginx.conf
  • アプリケーションのログレベル設定
  • タイムゾーン、言語設定

Secret:機密情報

パスワード、APIキー、TLS証明書など、機密性のある情報を格納します。

用途の例:

  • データベース接続パスワード
  • 外部APIのアクセストークン
  • HTTPS用の秘密鍵・証明書

両者の技術的な違いを整理します。

観点ConfigMapSecret
用途一般的な設定機密情報
保存形式平文Base64エンコード
etcdでの暗号化なし(デフォルト)オプションで有効化可能
kubectl getでの表示そのまま表示値は非表示(-o yamlで確認可)
メモリ上の扱い通常tmpfs(RAMディスク)にマウント可能

重要な注意点 Secretの値はBase64エンコードされているだけで、暗号化ではありませんecho "cGFzc3dvcmQ=" | base64 -d で簡単にデコードできます。本格的なセキュリティが必要な場合は、外部のシークレット管理ツール(HashiCorp Vault、AWS Secrets Manager等)との連携を検討してください。

9.1.3 【比較】VMのゴールデンイメージ管理 vs K8sの「1イメージ多環境」運用

VM時代、多くの現場ではゴールデンイメージ方式を採用していました。

【ゴールデンイメージ方式】

  ベースイメージ
       │
       ├─→ 開発用イメージ(dev用設定を焼き込み)
       ├─→ ステージング用イメージ(stg用設定を焼き込み)
       └─→ 本番用イメージ(prod用設定を焼き込み)

  問題:イメージが3種類に増殖、メンテナンスコストが3倍

Kubernetesでは、1つのイメージを全環境で使い回し、環境固有の設定だけを外から注入します。

【K8sの1イメージ多環境方式】

  nginx:1.27(1つのイメージ)
       │
       ├─→ 開発環境 + ConfigMap(dev)  + Secret(dev)
       ├─→ ステージング + ConfigMap(stg) + Secret(stg)
       └─→ 本番環境 + ConfigMap(prod) + Secret(prod)

  利点:イメージは1つ、設定だけが環境ごとに異なる

この「Immutable Infrastructure(不変のインフラ)」という考え方により、「開発では動いたのに本番で動かない」という問題が劇的に減ります。なぜなら、動いているコンテナイメージは全環境で同一だからです。


9.2 実践:ConfigMapによる設定ファイル注入

では、実際にConfigMapを使ってNginxの設定を外部化してみましょう。

9.2.1 Nginxの設定ファイルをConfigMapとして作成する

まず、カスタマイズしたNginx設定ファイルを用意します。ここでは、レスポンスヘッダーにサーバー識別情報を追加する設定を作成します。

作業ディレクトリを準備します。

[Execution User: developer]

mkdir -p ~/k8s-config-demo
cd ~/k8s-config-demo

Nginx用のカスタム設定ファイルを作成します。

[Execution User: developer]

cat << 'EOF' > custom-nginx.conf
user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log notice;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    keepalive_timeout  65;

    server {
        listen       80;
        server_name  localhost;

        # ConfigMapから注入された設定であることを示すカスタムヘッダー
        add_header X-Config-Source "ConfigMap-Managed";
        add_header X-Environment "development";

        location / {
            root   /usr/share/nginx/html;
            index  index.html index.htm;
        }

        # ヘルスチェック用エンドポイント
        location /health {
            access_log off;
            return 200 "healthy\n";
            add_header Content-Type text/plain;
        }
    }
}
EOF

この設定ファイルからConfigMapを作成します。--from-fileオプションを使うと、ファイルの内容をそのままConfigMapに格納できます。

[Execution User: developer]

kubectl create configmap nginx-config \
  --from-file=nginx.conf=custom-nginx.conf \
  --dry-run=client -o yaml > nginx-configmap.yaml

生成されたマニフェストを確認します。

[Execution User: developer]

cat nginx-configmap.yaml

出力例:

apiVersion: v1
kind: ConfigMap
metadata:
  creationTimestamp: null
  name: nginx-config
data:
  nginx.conf: |
    user  nginx;
    worker_processes  auto;
    ... (設定内容が続く)

ConfigMapをクラスターに適用します。

[Execution User: developer]

kubectl apply -f nginx-configmap.yaml

作成されたConfigMapを確認します。

[Execution User: developer]

kubectl get configmap nginx-config
kubectl describe configmap nginx-config

9.2.2 マニフェスト:作成したConfigMapをPod内の特定のパスにマウントする

次に、このConfigMapをNginx Podにマウントするマニフェストを作成します。

[Execution User: developer]

cat << 'EOF' > nginx-with-configmap.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx-custom-config
  labels:
    app: nginx
    config: custom
spec:
  containers:
  - name: nginx
    image: nginx:1.27
    ports:
    - containerPort: 80
    # ConfigMapをボリュームとしてマウント
    volumeMounts:
    - name: nginx-config-volume
      mountPath: /etc/nginx/nginx.conf  # マウント先のパス
      subPath: nginx.conf                # ConfigMap内のキー名
      readOnly: true
    # 起動確認用のプローブ
    readinessProbe:
      httpGet:
        path: /health
        port: 80
      initialDelaySeconds: 5
      periodSeconds: 5
  volumes:
  - name: nginx-config-volume
    configMap:
      name: nginx-config  # 参照するConfigMap名
EOF

ここで重要なのはsubPathの使い方です。

設定項目説明
mountPathコンテナ内でファイルを配置するパス
subPathConfigMap内の特定のキーだけをマウント(ディレクトリ全体を上書きしない)

subPathを指定しない場合、/etc/nginx/ディレクトリ全体がConfigMapの内容で置き換えられてしまいます。これは多くの場合、意図した動作ではありません。

Podを作成します。

[Execution User: developer]

kubectl apply -f nginx-with-configmap.yaml

Podの起動を待ちます。

[Execution User: developer]

kubectl wait --for=condition=Ready pod/nginx-custom-config --timeout=60s

9.2.3 反映の確認:Podの中の設定ファイルが、マニフェスト通りに書き換わっているか検証

設定が正しく反映されているか、複数の方法で確認します。

方法1:Pod内の設定ファイルを直接確認

[Execution User: developer]

kubectl exec nginx-custom-config -- cat /etc/nginx/nginx.conf | head -20

カスタム設定の内容が表示されれば成功です。

方法2:カスタムヘッダーの確認

ポートフォワードを設定してHTTPリクエストを送信します。

[Execution User: developer]

# バックグラウンドでポートフォワード
kubectl port-forward pod/nginx-custom-config 8080:80 &
PF_PID=$!
sleep 3

# レスポンスヘッダーを確認
curl -I http://localhost:8080/

# ポートフォワードを停止
kill $PF_PID 2>/dev/null

期待される出力:

HTTP/1.1 200 OK
...
X-Config-Source: ConfigMap-Managed
X-Environment: development
...

X-Config-Source: ConfigMap-Managedというヘッダーが返ってくれば、ConfigMapの設定が正しく適用されています。

方法3:ヘルスチェックエンドポイントの確認

[Execution User: developer]

kubectl port-forward pod/nginx-custom-config 8080:80 &
PF_PID=$!
sleep 3

curl http://localhost:8080/health

kill $PF_PID 2>/dev/null

healthyと返ってくれば、カスタム設定の/healthエンドポイントが機能しています。


9.3 セキュリティ:Secretを使ったパスワードや証明書の保護

ConfigMapで一般的な設定を扱う方法を学びました。次は、パスワードなどの機密情報を安全に扱うSecretを実践します。

9.3.1 生パスワードをYAMLに書かないために:Secretリソースの作成(CLI/YAML)

VMの世界では、パスワードを環境変数や設定ファイルに直接書き込んでいたかもしれません。Kubernetesでは、Secretリソースを使って機密情報を分離します。

方法1:CLIから直接作成(推奨)

最も安全な方法は、kubectl create secretコマンドを使うことです。

[Execution User: developer]

# データベース接続用のSecretを作成
kubectl create secret generic db-credentials \
  --from-literal=username=app_user \
  --from-literal=password='S3cur3P@ssw0rd!' \
  --from-literal=host=db.example.com \
  --from-literal=port=5432

この方法なら、パスワードがYAMLファイルとして残りません。

作成されたSecretを確認します。

[Execution User: developer]

kubectl get secret db-credentials
kubectl describe secret db-credentials

出力例:

Name:         db-credentials
Namespace:    default
Labels:       <none>
Annotations:  <none>

Type:  Opaque

Data
====
host:      14 bytes
password:  15 bytes
port:      4 bytes
username:  8 bytes

describeでは値そのものは表示されません。値を確認したい場合は次のコマンドを使います。

[Execution User: developer]

# Base64エンコードされた状態で表示
kubectl get secret db-credentials -o jsonpath='{.data.password}'
echo  # 改行

# デコードして表示(検証目的のみ、本番では慎重に)
kubectl get secret db-credentials -o jsonpath='{.data.password}' | base64 -d
echo  # 改行

方法2:YAMLマニフェストから作成

GitOps等でマニフェストを管理したい場合は、YAMLで定義することもできます。ただし、平文のパスワードをGitにコミットしないよう注意してください。

[Execution User: developer]

# まずBase64エンコード
echo -n 'S3cur3P@ssw0rd!' | base64
# 出力: UzNjdXIzUEBzc3cwcmQh

[Execution User: developer]

cat << 'EOF' > db-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: db-credentials-yaml
type: Opaque
data:
  # 値はBase64エンコードが必要
  username: YXBwX3VzZXI=          # app_user
  password: UzNjdXIzUEBzc3cwcmQh  # S3cur3P@ssw0rd!
  host: ZGIuZXhhbXBsZS5jb20=      # db.example.com
  port: NTQzMg==                  # 5432
EOF

Base64エンコードの落とし穴 -nオプションを忘れると、改行文字も一緒にエンコードされてしまいます。

echo 'password' | base64      # cGFzc3dvcmQK (末尾に改行が含まれる)
echo -n 'password' | base64   # cGFzc3dvcmQ= (正しい)

この差異が原因で、アプリケーションが認証に失敗するトラブルは非常に多いです。

9.3.2 環境変数としての注入:Pod内のプロセスからSecretの値を読み出す方法

Secretの値をPodに渡す方法は2つあります。

方法用途特徴
環境変数接続文字列、パスワード等アプリケーションから参照しやすい
ボリュームマウント証明書ファイル、鍵ファイル等ファイルとして配置される

ここでは、データベース接続情報を環境変数として注入する例を示します。

[Execution User: developer]

cat << 'EOF' > app-with-secret.yaml
apiVersion: v1
kind: Pod
metadata:
  name: app-with-db-secret
  labels:
    app: demo-app
spec:
  containers:
  - name: app
    image: nginx:1.27  # 実際にはアプリケーションイメージを使用
    ports:
    - containerPort: 80
    env:
    # 方法A:個別のキーを環境変数にマッピング
    - name: DB_USERNAME
      valueFrom:
        secretKeyRef:
          name: db-credentials
          key: username
    - name: DB_PASSWORD
      valueFrom:
        secretKeyRef:
          name: db-credentials
          key: password
    - name: DB_HOST
      valueFrom:
        secretKeyRef:
          name: db-credentials
          key: host
    - name: DB_PORT
      valueFrom:
        secretKeyRef:
          name: db-credentials
          key: port
    # ConfigMapからの環境変数も併用可能
    - name: APP_ENV
      value: "development"
EOF

Podを作成して環境変数を確認します。

[Execution User: developer]

kubectl apply -f app-with-secret.yaml
kubectl wait --for=condition=Ready pod/app-with-db-secret --timeout=60s

Pod内から環境変数を確認します。

[Execution User: developer]

kubectl exec app-with-db-secret -- env | grep -E '^DB_|^APP_'

出力例:

DB_USERNAME=app_user
DB_PASSWORD=S3cur3P@ssw0rd!
DB_HOST=db.example.com
DB_PORT=5432
APP_ENV=development

環境変数として注入されたSecretの値は、Pod内では平文として見えます。これは正常な動作です。Secretの保護は「クラスター外部への漏洩防止」と「アクセス権限の制御」にあります。

envFromを使った一括注入

キーが多い場合は、envFromで一括注入することもできます。

spec:
  containers:
  - name: app
    image: nginx:1.27
    envFrom:
    - secretRef:
        name: db-credentials
      prefix: DB_  # 環境変数に接頭辞を付与(オプション)

この場合、Secretの各キーがそのまま(または接頭辞付きで)環境変数になります。

9.3.3 【次回予告】サーバーに入らずに状態を知る「オブザーバビリティ」

設定を外部化し、データを永続化し、アプリケーションが動く基盤は整いました。しかし、「今、本当に正常に動いているのか?」をどう確認すればよいでしょうか。

VM時代なら、SSHでサーバーに入り、topでリソースを確認し、tail -fでログを追いかけていました。Kubernetesでは、サーバーに入らずにこれらの情報を収集する仕組みが用意されています。

次回は、メトリクス収集、ログ集約、分散トレーシングという「オブザーバビリティ」の3本柱を学びます。監視の世界も、SSHからの解放が待っています。


9.4 トラブルシューティングのTips

ConfigMapとSecretを運用する上で、よく遭遇する問題とその解決策をまとめます。

ConfigMap更新時の反映タイミング

ConfigMapを更新した後、Pod内のファイルがいつ反映されるかは、マウント方法によって異なります

マウント方法反映タイミング備考
subPathなしでボリュームマウント数十秒〜数分で自動反映kubeletの同期間隔に依存
subPathありでボリュームマウントPodの再起動が必要subPathは初回マウント時の内容で固定
環境変数として注入Podの再起動が必要環境変数はプロセス起動時に決定

実験:subPathなしの場合の自動反映

[Execution User: developer]

# subPathを使わないPodを作成
cat << 'EOF' > nginx-auto-reload.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx-auto-reload
spec:
  containers:
  - name: nginx
    image: nginx:1.27
    volumeMounts:
    - name: config
      mountPath: /etc/nginx/conf.d  # ディレクトリごとマウント(subPathなし)
  volumes:
  - name: config
    configMap:
      name: nginx-config
EOF

この場合、ConfigMapを更新すると、しばらく後にPod内のファイルも自動的に更新されます。ただし、Nginxプロセスが設定を再読み込みするわけではないので、設定を反映させるにはnginx -s reloadの実行やPodの再起動が必要です。

確実に反映させる方法:Podのローリング再起動

Deploymentを使っている場合、以下のコマンドでPodを再起動できます。

[Execution User: developer]

# Deploymentのアノテーションを更新してローリング再起動をトリガー
kubectl rollout restart deployment/<deployment-name>

SecretのBase64エンコード忘れによるエラー

YAMLでSecretを定義する際、Base64エンコードを忘れると次のようなエラーが発生します。

error: error validating "secret.yaml": error validating data: 
invalid value for field data: illegal base64 data

解決策1:stringDataを使う

Kubernetes 1.10以降では、stringDataフィールドを使うと平文で記述できます。

[Execution User: developer]

cat << 'EOF' > db-secret-stringdata.yaml
apiVersion: v1
kind: Secret
metadata:
  name: db-credentials-easy
type: Opaque
stringData:  # dataではなくstringData
  username: app_user
  password: S3cur3P@ssw0rd!
  host: db.example.com
  port: "5432"
EOF

stringDataで定義した値は、適用時に自動的にBase64エンコードされます。

解決策2:CLIで作成する

前述の通り、kubectl create secretコマンドを使えばエンコードの心配は不要です。

ConfigMapやSecretが見つからないエラー

Podが参照するConfigMapやSecretが存在しない場合、Podは起動に失敗します。

Warning  FailedMount  ... MountVolume.SetUp failed for volume "config" : 
configmap "nginx-config" not found

解決策:optional指定

ConfigMapやSecretが存在しなくてもPodを起動させたい場合は、optional: trueを指定します。

volumes:
- name: config
  configMap:
    name: nginx-config
    optional: true  # ConfigMapが存在しなくてもPod起動を継続

デバッグ用コマンド集

[Execution User: developer]

# ConfigMapの一覧表示
kubectl get configmap

# ConfigMapの内容を確認
kubectl get configmap nginx-config -o yaml

# Secretの一覧表示(値は表示されない)
kubectl get secret

# Secretの値をデコードして確認
kubectl get secret db-credentials -o jsonpath='{.data.password}' | base64 -d

# PodがConfigMap/Secretを正しくマウントしているか確認
kubectl describe pod <pod-name> | grep -A 10 "Mounts:"

# Pod内の環境変数を確認
kubectl exec <pod-name> -- env | sort

後片付け

今回作成したリソースを削除します。

[Execution User: developer]

kubectl delete pod nginx-custom-config app-with-db-secret nginx-auto-reload --ignore-not-found
kubectl delete configmap nginx-config --ignore-not-found
kubectl delete secret db-credentials db-credentials-yaml db-credentials-easy --ignore-not-found
rm -rf ~/k8s-config-demo

まとめ:VMエンジニアが今日から使える3つの知識

VM時代の常識K8sの新常識今日学んだこと
設定は/etcに直接書く設定はConfigMapに外部化kubectl create configmap
パスワードは設定ファイルに記述機密情報はSecretで分離kubectl create secret
環境ごとにイメージを作り分け1つのイメージ + 環境別の設定Immutable Infrastructureの実現

ConfigMapとSecretにより、コンテナイメージを「不変」に保ったまま、環境ごとの設定を柔軟に切り替えられるようになりました。これは、VM時代のゴールデンイメージ方式では実現できなかった、K8sならではの運用パターンです。