はじめに
コンテナイメージは、複数の環境で再利用できるようにする必要があります。Kubernetes では、機密性のない設定を ConfigMap に、認証情報を Secret に分離し、それぞれのオブジェクトを環境変数またはファイルとして注入できます。
この実験では、両方の仕組みを使って 1 つのアプリケーションを設定し、安全に結果を確認します。その後、設定を更新し、環境変数とマウントされたボリュームで更新の反映方法が異なる理由を確認します。
目的は、2 つの API オブジェクトを作成することだけではありません。Kubernetes に保存された各値が実行中のコンテナへ届くまでの流れを確認し、さらに値を変更して、受け渡し方法が更新動作にどのような影響を与えるかを観察します。
アプリケーション設定の作成
このステップでは、名前空間を作成し、リテラル値から ConfigMap を作成します。ConfigMap は data の下に文字列のキーを保存しますが、値を暗号化するものではありません。
まず実験用ワークスペースへ移動します。この名前空間によって、この例で使用するすべてのオブジェクトをまとめて管理できます。また、他の実験にある同名の設定が誤って選択されるのを防げます。
cd /home/labex/project/application-config
kubectl create namespace practice-config
kubectl create configmap app-settings -n practice-config --from-literal=APP_MODE=production --from-literal=LOG_LEVEL=info
kubectl get configmap app-settings -n practice-config -o yaml
各 --from-literal=KEY=VALUE は 1 つのエントリを追加します。YAML の出力から保存されたキーを確認でき、機密性のない設定を扱う場合に適しています。
data: の下にある APP_MODE と LOG_LEVEL を確認してください。Kubernetes は指定された値を保存しますが、まだそれを利用するものはありません。ConfigMap を作成しただけでは、Pod の動作は変わりません。
出力の該当部分は、意図的に単純なテキストになっています。
data:
APP_MODE: production
LOG_LEVEL: info
kind: ConfigMap
uid、resourceVersion、creationTimestamp などのフィールドは Kubernetes が生成するため、環境によって異なります。このステップでは data だけを確認してください。そこに指定した設定値が含まれています。
Secret の安全な作成
このステップでは、値を YAML ファイルに記述せずに Secret を作成します。API 上では Secret のデータは Base64 エンコードされますが、オブジェクトの種類が Secret だからといって暗号化されるわけではありません。RBAC と保存時の暗号化も引き続き重要です。
generic は、TLS 用やイメージプル用の特殊な Secret ではなく、通常のキーと値の Secret であることを示します。パスワードを引用符で囲むことで、値に特殊文字が含まれる場合でも、シェルがその記号を解釈するのを防げます。
kubectl create secret generic app-credentials -n practice-config --from-literal=username=appuser --from-literal=password='practice-pass'
kubectl get secret app-credentials -n practice-config
kubectl get secret app-credentials -n practice-config -o jsonpath='{.data.username}' | base64 --decode; echo
最後のパイプラインは、説明のためにユーザー名だけをデコードします。通常の運用出力でパスワードを表示することは避けてください。
パイプ記号は、エンコードされた JSONPath の結果を base64 --decode に渡します。セミコロンの後にある echo は、デコードされた値の後ろに改行を追加し、次のシェルプロンプトを読みやすくします。appuser と表示されることでエンコードの仕組みを確認できますが、同時に Base64 をセキュリティ境界として扱ってはならない理由も分かります。
中央のコマンドは、どちらの値も表示せずにメタデータだけを出力します。
NAME TYPE DATA AGE
app-credentials Opaque 2 ...
appuser
DATA=2 は Secret に 2 つのキーが含まれていることを示します。バイト数を表しているわけではありません。Opaque は、通常のキーと値の Secret に使用される API タイプです。
Pod への設定の注入
このステップでは、ConfigMap の値を環境変数として使用し、Secret を読み取り専用ファイルとしてマウントします。
Pod を作成する前に、マニフェスト内の 2 つの受け渡し方法を分けて確認しましょう。
envFromは、ConfigMap のすべてのキーをコンテナの環境変数に変換します。volumesは Secret を Pod に接続し、volumeMountsはそのボリュームを今回のコンテナ内のどこに表示するかを指定します。
Pod 内のループはアプリケーション処理を行いません。両方の受け渡し方法を確認できるよう、コンテナを実行し続けるだけです。
cd /home/labex/project/application-config
cat <<'EOF' > configured-app.yaml
apiVersion: v1
kind: Pod
metadata:
name: configured-app
namespace: practice-config
spec:
containers:
- name: app
image: busybox:1.36
imagePullPolicy: IfNotPresent
command: ["sh", "-c", "while true; do sleep 30; done"]
envFrom:
- configMapRef:
name: app-settings
volumeMounts:
- name: credentials
mountPath: /etc/app-credentials
readOnly: true
volumes:
- name: credentials
secret:
secretName: app-credentials
EOF
マニフェストを適用し、Pod が準備完了になるまで待ちます。
kubectl apply -f configured-app.yaml
kubectl wait --for=condition=Ready pod/configured-app -n practice-config --timeout=60s
次に、コンテナ内部からそれぞれの受け渡し方法を確認します。printenv はプロセスの環境変数を読み取り、ls は Secret ボリュームによって投影されたファイルを表示します。
kubectl exec configured-app -n practice-config -- printenv APP_MODE LOG_LEVEL
kubectl exec configured-app -n practice-config -- ls -l /etc/app-credentials
envFrom は ConfigMap のすべてのキーを取り込みます。Secret ボリュームはキーごとに 1 つのファイルを作成し、コンテナ内では読み取り専用として扱われます。
環境変数の出力は production、続いて info になるはずです。ディレクトリには username と password という名前のファイルが含まれます。ファイル名は Secret のキーに対応しています。
実際のコンテナ内では、一覧は次のようになります。
production
info
total 0
lrwxrwxrwx ... password -> ..data/password
lrwxrwxrwx ... username -> ..data/username
これらのエントリは Kubernetes が管理するシンボリックリンクです。この間接参照により、アプリケーションがファイルを読み取っている最中でも、kubelet はファイル自体を書き換えることなく、マウントされたデータを新しいバージョンへ切り替えられます。アプリケーションからは、引き続き /etc/app-credentials/username と /etc/app-credentials/password という通常のパスを開けます。
マウントされた Secret データの更新
このステップでは、Secret を更新して、マウントされたファイルの変化を確認します。kubectl create ... --dry-run=client -o yaml | kubectl apply -f - は、目的の YAML をローカルで生成し、そのまま apply にパイプで渡します。- は標準入力を意味します。
この方法は、命令形式で生成したオブジェクトを削除せずに更新する場合に便利です。左側では Secret の完全なマニフェストを出力し、右側ではそのマニフェストを API サーバーへ送信します。
kubectl create secret generic app-credentials -n practice-config --from-literal=username=serviceuser --from-literal=password='rotated-pass' --dry-run=client -o yaml | kubectl apply -f -
Secret はもともと kubectl create で作成されているため、最初の更新では成功メッセージの前に次の警告が表示されることがあります。
Warning: resource secrets/app-credentials is missing the kubectl.kubernetes.io/last-applied-configuration annotation ...
secret/app-credentials configured
この警告は、apply が比較対象となる以前の宣言的なスナップショットを持っていないことを説明しています。この更新中に Kubernetes がアノテーションを自動的に追加するため、configured と表示されれば成功です。Secret を削除して再作成する必要はありません。
kubelet は、投影された Secret ボリュームの内容を非同期で更新します。新しいユーザー名が表示されるまで確認を繰り返します。
current_username=""
for attempt in $(seq 1 24); do
current_username=$(kubectl exec configured-app -n practice-config -- cat /etc/app-credentials/username)
if [ "$current_username" = "serviceuser" ]; then
break
fi
sleep 5
done
echo "$current_username"
test "$current_username" = "serviceuser"
ボリュームの投影は最終的に整合する仕組みであるため、ループは最大 2 分間再試行します。検証環境では API オブジェクトはすぐに変更されましたが、マウントされたファイルの更新には 1 分以上かかりました。
$(...) はコマンド置換です。シェルは seq 1 24 を実行し、その数値をループの値として使用します。各反復では、現在のファイル内容を current_username に保存します。break は最初に一致した時点でループを終了し、sleep 5 は API を連続的にポーリングするのを防ぎます。最後の test は値が変わらなかった場合に失敗するため、古いデータのまま処理が黙って続くことはありません。成功時の出力は serviceuser です。
環境変数による設定の更新
このステップでは、LOG_LEVEL を更新し、実行中の Pod と比較します。環境変数はコンテナの起動時に確定するため、ConfigMap を変更しても、現在のプロセスの環境変数は書き換えられません。
マージパッチでは、data の下にある 1 つのエントリだけを変更します。実行中のプロセスをすぐに確認することで、マウントされたボリュームとの重要な違いが分かります。
kubectl patch configmap app-settings -n practice-config --type=merge -p '{"data":{"LOG_LEVEL":"debug"}}'
kubectl exec configured-app -n practice-config -- printenv LOG_LEVEL
Pod は引き続き info と出力します。保存済みのマニフェストから Pod を再作成し、新しい値を読み込ませます。
configmap/app-settings patched
info
これはパッチに失敗したことを示すものではなく、想定された結果です。ConfigMap には debug が保存されていますが、すでに実行中のプロセスは起動時に作成された環境変数を保持しているためです。
cd /home/labex/project/application-config
kubectl delete pod configured-app -n practice-config --wait=true
kubectl apply -f configured-app.yaml
kubectl wait --for=condition=Ready pod/configured-app -n practice-config --timeout=60s
kubectl exec configured-app -n practice-config -- printenv LOG_LEVEL
再作成された Pod では debug と出力されます。この違いがあるため、実運用のワークロードでは、環境変数を使った設定を変更した後にロールアウトを実行することが一般的です。
pod "configured-app" deleted
pod/configured-app created
pod/configured-app condition met
debug
マニフェスト自体は引き続き名前で ConfigMap を参照しており、値が configured-app.yaml にコピーされたわけではありません。Pod を再作成すると、kubelet がその参照を再び解決し、最新のデータを注入します。
まとめ
イメージからアプリケーション設定と認証情報を分離し、ConfigMap のデータを環境変数として利用し、Secret のデータをファイルとしてマウントしました。さらに、投影された Secret を更新し、環境変数による設定を反映するために Pod を再作成しました。


