シリーズ記事一覧
- 第1回:全体像と環境構築
- 第2回:Pod・ReplicaSet・Deployment
- 第3回:Service・Ingress
- 第4回:ConfigMap・Secret・PersistentVolume
- 第5回:模擬プロジェクト(Web+API+DB+Redis)
- 第6回:トラブルシュートと運用
- 第7回:総まとめと次のステップ
📑 目次
- この記事について
- この記事のゴール
- 前提条件
- なぜ設定と永続化を分離するのか?
- ConfigMapとは?
- Secretとは?
- Secretの保護レイヤーと実務運用
- データの永続化 ― PersistentVolume(PV)とPVC
- PVの自動作成 ― StorageClassと動的プロビジョニング
- DBをk8sで動かす ― StatefulSet
- YAMLの書き方 ― ConfigMap・Secret・PVCの定義
- ハンズオン:ConfigMap・Secret・PVCを操作する
- まとめ
1. この記事について
1-1. シリーズ概要
Docker Compose経験者が「素のKubernetes」を
1週間で実践レベルまで習得することを目指す学習記録です。
1-2. シリーズ構成
| 回 | テーマ | 内容 |
|---|---|---|
| 第1回 | 全体像と環境構築 | Docker Composeとの対比、minikube導入 |
| 第2回 | Pod と Deployment | コンテナ起動〜スケーリング〜ローリングアップデート |
| 第3回 | Service と Ingress | ネットワークと外部公開 |
| 第4回(本記事) | ConfigMap / Secret / PV | 設定管理と永続化 |
| 第5回 | 模擬プロジェクト | 全概念を組み合わせて実践 |
| 第6回 | トラブルシュートと運用 | エラー対応、ログ確認 |
| 第7回 | 総まとめ | 振り返り |
1-3. 対象読者
- 第3回を読み終えた方(Service・Ingress・CoreDNSが理解済み)
- Docker Composeの
environmentやvolumesを使ったことがある方 - 「k8sでパスワードやデータをどう管理するの?」を知りたい方
2. この記事のゴール
| # | ゴール | 確認方法 |
|---|---|---|
| ① | なぜ設定と永続化を分離するかを説明できる | Docker Composeの課題と、k8sの3リソースによる解決策を語れる |
| ② | ConfigMapの2つの使い方を実践できる | 環境変数注入とファイルマウントの違いを語れる |
| ③ | SecretとConfigMapの違いを説明できる | Base64の本質、保護レイヤーの構成を語れる |
| ④ | PV/PVCの関係とStorageClassの 動的プロビジョニングを説明できる |
手動/動的の違い、volumeBindingModeを語れる |
| ⑤ | DeploymentとStatefulSetの 使い分けを判断できる |
ステートレス/ステートフルの選定基準を語れる |
3. 前提条件
3-1. 環境情報
| 項目 | バージョン / 詳細 |
|---|---|
| OS | Windows 11 + WSL2 Ubuntu |
| minikube | インストール済み(第1回で構築) |
| kubectl | インストール済み(第1回で構築) |
3-2. 前提知識
- 第3回の内容(Service・Ingress・CoreDNS・YAML書き方)
- Docker Composeの
environmentとvolumesの意味
4. なぜ設定と永続化を分離するのか?
4-1. Docker Composeの課題
# docker-compose.yml
services:
api:
image: myapp-api:1.0
environment:
- RAILS_ENV=production
- DATABASE_URL=postgresql://user:password@db:5432/myapp
- SECRET_KEY_BASE=abc123verysecretkey
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:
| 課題 | 問題 |
|---|---|
| パスワードが丸見え |
docker-compose.yml にDB接続情報が平文 |
| 環境ごとの切替が面倒 | dev/staging/prodで別々のファイルが必要 |
| 設定変更 = コンテナ再起動 | 環境変数を変えるには docker-compose up -d が必要 |
| ボリュームがNode固定 | データが特定のホストに紐づく |
4-2. k8sの解決策:3つのリソースで責務分離
| リソース | 責務 | Docker Composeとの対応 |
|---|---|---|
| ConfigMap | 一般的な設定(環境変数、設定ファイル) |
environment: の非機密部分 |
| Secret | 機密情報(パスワード、APIキー、証明書) |
environment: の機密部分 |
| PersistentVolume | データの永続化 | volumes: |
🍽️
Docker Compose = 「レシピ・材料リスト・金庫の鍵を全部同じノートに書く」。
k8s =
「レシピ(Deployment)」/「材料リスト(ConfigMap)」/「金庫の鍵(Secret)」/「食材倉庫(PV)」を別々に管理。
5. ConfigMapとは?
5-1. 基本概念
ConfigMapは「アプリケーションの設定情報を外部化して管理するリソース」
| 観点 | Docker Compose | k8s ConfigMap |
|---|---|---|
| 設定の定義場所 |
docker-compose.yml の中 |
別リソース(YAML) |
| 設定の変更 | ファイル書き換え → up -d
|
ConfigMap更新 → Pod再起動 or 自動反映 |
| 環境の切替 | ファイルを丸ごと差し替え | ConfigMapだけ差し替え |
| 再利用性 | 低い | 複数のDeploymentから参照可能 |
🍽️
Docker Compose = レシピと材料リストが同じノートに書いてある。
ConfigMap = 材料リストだけ別のカードに書く、
「塩の量を変えたい」→ カードだけ差し替え。
レシピ(Deployment)は変更不要。
5-2. ConfigMapに入れるもの / 入れないもの
| 入れる ✅ | 入れない ❌ |
|---|---|
RAILS_ENV=production |
パスワード |
DATABASE_HOST=db-service |
APIキー |
LOG_LEVEL=info |
秘密鍵・証明書 |
| nginx.conf 等の設定ファイル | → これらはSecretへ |
5-3. 2つの使い方
① 環境変数として注入
Docker Composeの environment: と同じ感覚。
ConfigMapの全キーがPodの環境変数になります。
② ファイルとしてマウント
Docker Composeの volumes: ./nginx.conf:/etc/nginx/nginx.conf と同じ感覚。
ConfigMapの中身をファイルとしてPod内にマウントします。
| 使い方 | 適するケース | Docker Composeとの対応 |
|---|---|---|
| 環境変数 | 少数のキー=値ペア | environment: |
| ファイルマウント | 設定ファイル丸ごと |
volumes: でホストファイルをマウント |
5-4. 更新時の反映ルール
| マウント方式 | ConfigMap更新時の挙動 | Pod再起動 |
|---|---|---|
| 環境変数 | 反映されない | 必要 |
| ファイルマウント | 自動反映(数十秒〜数分) | 不要(ただしアプリの再読み込みが必要) |
🍽️
環境変数 = 「入社時に渡すマニュアル印刷版」→ 改訂しても既存スタッフは古い版のまま。
ファイルマウント = 「共有フォルダのマニュアル」→ 更新すればみんな最新版が見える。
5-5. 共有パターン
1つのConfigMapを複数のDeploymentから参照できます。
| パターン | 構成 | ユースケース |
|---|---|---|
| 共通 + 個別 | common + web/api/worker | 最も一般的 |
| 環境別 | dev/staging/prod | 同じアプリの環境切替 |
| 設定ファイル共有 | nginx.conf等をマウント | 複数PodでProxy設定を統一 |
🍽️
app-common-config= 全店舗共通の営業マニュアル。
web-config= ホール担当専用のマニュアル。
api-config= 厨房担当専用のマニュアル。
環境別の切替
| ポイント | 説明 |
|---|---|
| Deploymentは全く同じYAML | 環境差分はConfigMapだけ |
| ConfigMap名を同じにする | 中身だけ違う |
| Namespaceで分離 | dev用/prod用Namespaceにそれぞれ同名のConfigMapを配置 |
⚠️ 共有ConfigMap更新時の注意
共通ConfigMapを変更すると参照している全Deploymentに影響します。
対策として、
immutable: true でイミュータブルにしたり、
名前にバージョン番号を含めて明示的に参照先を切り替える方法があります。
# イミュータブルConfigMap(変更不可)
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config-v2
immutable: true
data:
LOG_LEVEL: "debug"
| フィールド | 意味 |
|---|---|
immutable: true |
作成後に変更不可(誤変更を防ぐ安全策) |
| 変更したい場合 | ConfigMapを削除→再作成が必要 |
🔰 いつ使う? DB接続先やAPIキーなど「絶対に動的変更してほしくない値」に有効です。
6. Secretとは?
6-1. 基本概念
SecretはConfigMapの「機密情報版」 です。
構造はほぼ同じですが、機密データ向けの追加保護があります。
| 観点 | ConfigMap | Secret |
|---|---|---|
| 用途 | 一般的な設定 | 機密情報 |
| データ形式 | 平文 | Base64エンコード |
| メモリ上の扱い | 通常 | tmpfs(ディスクに書かない) |
| アクセス制御 | RBAC | RBAC + より厳格な制限が可能 |
🍽️
ConfigMap = 店舗の営業マニュアル(棚に置いてある)。
Secret = 金庫の暗証番号メモ(鍵付きの引き出しに保管)、中身の書き方は同じ、
保管方法と閲覧権限が違う。
6-2. Secretの種類
| type | 用途 | 例 |
|---|---|---|
| Opaque | 汎用(デフォルト) | パスワード、APIキー |
| kubernetes.io/tls | TLS証明書 | 第3回で学んだIngress用証明書 |
| kubernetes.io/dockerconfigjson | Dockerレジストリ認証 | プライベートレジストリのログイン情報 |
| kubernetes.io/basic-auth | Basic認証 | ユーザー名 + パスワード |
🔰 Memo:
Opaque(オペイク)は
英語で「中身が見えない・不透明」という意味。
つまり「k8sが中身の形式を気にしない、何でも入れられる汎用タイプ」ということ。
他のtypeはk8sが中身の形式をチェックしますが、Opaqueはノーチェックで自由に使えます。
ほとんどのケースで Opaque を使います。
🍽️
Opaque = 「中身おまかせ弁当箱」。
他のtypeは「TLS専用ケース」「認証情報専用ケース」のように形が決まっている。
6-3. Base64は「暗号化」ではない
🔰誤解しやすいポイントだと思いました。
# Base64エンコード(誰でも元に戻せる)
echo -n "mypassword" | base64
# → bXlwYXNzd29yZA==
# Base64デコード(1コマンドで元に戻る)
echo "bXlwYXNzd29yZA==" | base64 -d
# → mypassword
| 誤解 | 現実 |
|---|---|
| 「Base64だから安全」 | ❌ Base64はただのエンコード。暗号化ではない |
| 「Secretに入れれば安心」 | ❌ デフォルトではetcdに平文保存される |
6-4. 使い分けの判断
🔰 Memo:
迷ったらSecretにしておくのが安全。
後からConfigMapに変更するのは簡単ですが、漏れた情報は取り消せない。
| 情報 | ConfigMap or Secret | 理由 |
|---|---|---|
RAILS_ENV=production |
ConfigMap | 漏れても問題なし |
DATABASE_HOST=db-service |
ConfigMap | 接続先ホスト名は機密ではない |
DATABASE_PASSWORD=xxx |
Secret | パスワードは機密 |
API_KEY=sk-xxxx |
Secret | APIキーは機密 |
| TLS証明書 | Secret | 秘密鍵は機密 |
6-5. data: と stringData: の違い
# stringData:(平文で書ける ★推奨)
stringData:
POSTGRES_PASSWORD: "secretpass123"
# data:(Base64エンコードが必要)
data:
POSTGRES_PASSWORD: "c2VjcmV0cGFzczEyMw=="
どちらもk8sが保存する時はBase64に変換されます。
stringData: の方が読みやすいので実務ではこちらを使います。
7. Secretの保護レイヤーと実務運用
セクション6-3で、
「Base64は暗号化ではない」と書きました。
では、Secretはどうやって守られているのか?、
については、k8sには複数の保護の仕組みが重なっているので整理してみます。
7-1. Secretの保護レイヤー
| 保護レイヤー | 説明 | デフォルト |
|---|---|---|
| RBAC | 誰がSecretを読めるか制御 | ✅ 有効 |
| tmpfs マウント | Pod内でディスクに書かず、メモリ上のみ | ✅ 有効 |
| etcd暗号化 | etcd内のSecret値を暗号化 | ❌ 手動で有効化が必要 |
| 外部シークレット管理 | AWS Secrets Manager等と連携 | ❌ 別途設定が必要 |
🔰 Memo:
RBAC(Role-Based Access Control)は「役割ベースのアクセス制御」です。
「この人はSecretを読める」「この人は読めない」を役割(Role)で管理する仕組みです。
🍽️
RBAC = 「金庫の鍵を持てる人を役職で決める」、
店長は金庫を開けられるが、アルバイトは開けられない。
🍽️
Base64 = 「暗証番号を逆さまに書く」(見ればわかる)。
RBAC = 「引き出しに鍵をかける」。
etcd暗号化 = 「金庫に入れる」。
外部シークレット管理 = 「銀行の貸金庫」。
7-2. etcd暗号化
デフォルトではSecretはetcdにBase64のまま保存されます。
暗号化を有効にするとAPI Serverが暗号化してから保存します。
| 環境 | etcd暗号化の状況 |
|---|---|
| AWS EKS | ✅ デフォルトで有効(AWS KMS連携) |
| GCP GKE | ✅ デフォルトで有効(Google KMS連携) |
| Azure AKS | ✅ デフォルトで有効 |
| minikube | ❌ デフォルトで無効(手動設定が必要) |
🔰 マネージドk8s(EKS/GKE/AKS)を使えばetcd暗号化は最初から有効、
素のk8sの場合のみ手動設定が必要です。
7-3. Secretとgit ― やってはいけないこと
| やり方 | 安全性 | 説明 |
|---|---|---|
| ❌SecretのYAMLをそのままgitに入れる | ❌危険 | パスワードが平文でリポジトリに残る |
| ✅ Sealed Secrets | 安全 | 暗号化されたSecretをgitに入れる |
| ✅ External Secrets Operator | 安全 | 外部サービスから自動取得 |
| ✅ kubectl で手動作成 | 安全 | gitに入れず手動でクラスタに投入 |
7-4. 比較 Sealed Secrets vs External Secrets Operator
✅Sealed Secrets — git内で暗号化して管理:
✅External Secrets Operator — 外部サービスから自動取得:
比較:
| 観点 | Sealed Secrets | External Secrets Operator |
|---|---|---|
| 秘密情報の保管場所 | gitリポジトリ内(暗号化済み) | 外部サービス |
| 外部サービス不要 | ✅ k8s内で完結 | ❌ AWS SM等が必要 |
| シークレットローテーション | 手動 | 自動対応可能 |
| 導入の手軽さ | ★★★ 簡単 | ★★☆ やや複雑 |
| 本番推奨度 | 小〜中規模 | 中〜大規模 |
8. データの永続化 ― PersistentVolume(PV)とPVC
ここまでConfigMap(設定)とSecret(機密情報)を学びました。
この記事の最後のテーマは 「データの永続化」 です。
ConfigMap/Secretは「アプリに渡す設定値」でしたが、
PersistentVolumeは「アプリが生成するデータの保存先」です。
| リソース | 何を扱う | 例 |
|---|---|---|
| ConfigMap | 設定値 | 環境変数、設定ファイル |
| Secret | 機密情報 | パスワード、APIキー |
| PersistentVolume | アプリが生成するデータ | DBのデータ、アップロードファイル |
8-1. なぜPodにデータを保存できないのか?
Podは使い捨てで、消えると中のデータも消えます。
🍽️
Pod = 紙皿。
料理(データ)を載せても、皿を捨てたら料理も消える。
料理を 別の保管庫(PersistentVolume) に置いておけば、
皿を替えても料理は残る。
8-2. 3つの登場人物
| リソース | 誰が作る? | 役割 |
|---|---|---|
| PV | インフラ管理者(or 自動) | ストレージの実体 |
| PVC | 開発者 | ストレージの要求 |
| Pod | 開発者 | PVCを参照してマウント |
🍽️
PV = 飲食チェーンの食材倉庫(「渋谷の倉庫A、100㎡あります」)。
PVC = 店舗からの倉庫利用申請書(「100㎡の倉庫が欲しいです」)。
Pod = 店舗のスタッフ(「割り当てられた倉庫から食材を取り出して使う」)。
8-3. PVの主な設定項目
PVを作る時に決める設定は主に3つです。
| 設定項目 | 何を決める? | 一番よく使う値 |
|---|---|---|
| 容量(storage) | ストレージのサイズ | 用途に応じて |
| アクセスモード | 誰が読み書きできるか | RWO(1つのNode) |
| 回収ポリシー | PVC削除時にデータをどうするか | 本番: Retain / 開発: Delete |
🍽️ 倉庫を借りる時に、
「広さは?」「個室か共有か?」、
「解約時に中身をどうするか?」を決めるのと同じです。
8-4. アクセスモード
| モード | 略称 | 意味 | ユースケース |
|---|---|---|---|
| ReadWriteOnce | RWO | 1つのNodeだけが読み書き | DB、一般的なアプリ |
| ReadOnlyMany | ROX | 複数Nodeが読み取り専用 | 共有設定ファイル |
| ReadWriteMany | RWX | 複数Nodeが読み書き | 共有ファイルストレージ |
🔰最も使うのはRWOです。
🍽️
RWO = 個室の倉庫。
ROX = ショーケース(見るだけ)。
RWX = 共有倉庫。
8-5. 回収ポリシー(ReclaimPolicy)
PVCが削除された時、PVのデータをどうするかの設定です。
| ポリシー | 動作 | ユースケース |
|---|---|---|
| Retain | PVとデータを残す | 本番DB |
| Delete | PVとデータを自動削除 | 開発環境 |
⚠️ 本番DBには必ず Retain を使う、
Delete だとPVC削除でデータが消えます。
8-6. データのライフサイクル
| シナリオ | PVC | PV | データ |
|---|---|---|---|
| Podだけ削除 | 残る | 残る | 残る ✅ |
| Pod + PVC を削除(Retain) | 消える | 残る | 残る ✅ |
| Pod + PVC を削除(Delete) | 消える | 消える | 消える ❌ |
9. PVの自動作成 ― StorageClassと動的プロビジョニング
セクション8ではPV/PVCの仕組みを学びました。
しかし、PVを毎回手動で作るのは大変です。
アプリが増えるたびに管理者がPVを用意するのは現実的ではありません。
そこで登場するのがStorageClassです。
PVCを作るだけでPVが自動的に用意される「動的プロビジョニング」を実現します。
🍽️
セクション8 = 「倉庫を1つずつ手作業で契約」。
セクション9 = 「不動産会社に頼めば自動で倉庫が見つかる」。
9-1. 手動 vs 動的プロビジョニング
| 方式 | PVの作成 | 使う場面 |
|---|---|---|
| 手動 | 管理者が手動で作成 | オンプレミス、特殊要件 |
| 動的 | StorageClassが自動作成 | クラウド環境(主流) |
🍽️
手動 = 「倉庫が欲しい」→ 管理者が物件を探して契約。
動的 = 「倉庫が欲しい」→ 不動産会社(StorageClass)が自動で物件を用意してくれる。
9-2. StorageClassの裏側 ― CSIドライバー
StorageClassが「PVを自動作成」すると書きましたが、
実際にクラウドのストレージを操作するのは、
CSI(Container Storage Interface)ドライバーです。
ストレージ制御はCSIという標準インターフェースで分離されています。
Ingress ControllerやCoreDNSと同じ設計パターン:
「仕様」と「実装」を分離して、プラグインで差し替え可能にしています。
| クラウド | StorageClass例 | Provisioner |
|---|---|---|
| AWS | gp3 | ebs.csi.aws.com |
| GCP | standard | pd.csi.storage.gke.io |
| Azure | managed-premium | disk.csi.azure.com |
| minikube | standard | k8s.io/minikube-hostpath |
9-3. いつPVを作る? ― volumeBindingMode
| モード | 動作 | 推奨 |
|---|---|---|
| Immediate | PVC作成時にすぐPVを作成 | デフォルト |
| WaitForFirstConsumer | PodがスケジュールされてからPVを作成 | ★推奨 |
🔰 WaitForFirstConsumer が推奨な理由: EBSはAZに紐づくため、Podの配置先が決まる前にEBSを作ると、PodとEBSが別のAZになりマウントできない事態が起きます。
🍽️ Immediate = 「先に渋谷に倉庫を借りた」→ スタッフが新宿配属に → 遠い! WaitForFirstConsumer = 「配属先が決まってから近くの倉庫を借りる」→ ✅
9-4. どんなストレージを選ぶ? ― AWS EBSボリュームタイプ
StorageClassでは「どのタイプのストレージを使うか」も指定できます。
AWS EKSの場合、EBSのボリュームタイプを選択します。
🔰 Memo:
minikubeでの学習中はボリュームタイプの選択は不要です。
本番(AWS EKS等)に進む際の参考として紹介します。
| タイプ | 用途 | ベースIOPS | コスト |
|---|---|---|---|
| gp3 | 汎用(★推奨) | 3,000 | $ |
| gp2 | 汎用(旧世代) | 容量×3 | $ |
| io2 | 高性能DB | 指定 | $$$ |
| st1 | ログ、ビッグデータ | - | ¢ |
gp3の優位点:
・IOPS/スループットが容量と独立して設定可能。
・10GBでもベースラインIOPS 3,000が無料で付いてきます。
ワークロード別の設計指針
| ワークロード | 推奨タイプ | IOPS | 容量目安 |
|---|---|---|---|
| 開発環境のDB | gp3(デフォルト) | 3,000 | 20GB |
| 本番PostgreSQL(小規模) | gp3 | 3,000〜6,000 | 50〜100GB |
| 本番PostgreSQL(中規模) | gp3 | 6,000〜10,000 | 100〜500GB |
| 本番MySQL(高負荷) | io2 | 10,000〜30,000 | 200GB〜 |
実務的なアプローチ
| ステップ | やること |
|---|---|
| ① まずgp3デフォルトで始める | IOPS: 3,000, スループット: 125 MB/s |
| ② CloudWatch で監視 |
VolumeReadOps, VolumeWriteOps 等 |
| ③ ボトルネック判明後に調整 | gp3はオンラインで変更可能(ダウンタイムなし) |
# 本番環境用 StorageClass(gp3カスタム)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: gp3-prod-db
provisioner: ebs.csi.aws.com
parameters:
type: gp3
iops: "6000"
throughput: "250"
encrypted: "true"
reclaimPolicy: Retain
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
🍽️
gp3デフォルト = 「普通の冷蔵庫」。
gp3カスタム = 「業務用冷蔵庫」。
io2 = 「超低温冷凍庫」。
まずは普通の冷蔵庫で始めて、足りなくなったら業務用に切り替える。
10. DBをk8sで動かす ― StatefulSet
セクション8-9でPV/PVCとStorageClassを学びました。
ではDBのようなステートフルなアプリをk8sで動かすには、Deploymentで十分でしょうか?
実は、DBにはDeploymentでは対応できない要件があります。
10-1. Deploymentの問題点:ステートフルなアプリ
| Deploymentの特性 | DBでの問題 |
|---|---|
| Podの名前がランダム | どのPodがマスターかわからない |
| 起動順序が保証されない | レプリカがマスターより先に起動するかも |
| 全Podが同じPVCを共有 | データ競合 |
10-2. Deployment vs StatefulSet
| 観点 | Deployment | StatefulSet |
|---|---|---|
| Pod名 | ランダム | 連番(db-0, db-1) |
| 起動順序 | 並列 | 順番に起動(0→1→2) |
| 停止順序 | 並列 | 逆順に停止(2→1→0) |
| PVC | 全Pod共有 | 各Podに専用PVC |
| DNS名 | なし | 各Podに固定DNS名 |
| ユースケース | Webアプリ、API | DB、メッセージキュー |
🍽️ Deployment = 使い捨てのアルバイトチーム(名前はニックネーム、共有の道具箱)。StatefulSet = 正社員のシェフチーム(序列がある、自分専用の包丁セットを持つ、入社順に研修)。
10-3. 各Podに固定名でアクセス ― Headless Service
StatefulSetはHeadless Serviceと組み合わせて、各Podに固定DNS名を提供します。
| 比較 | 通常のService | Headless Service |
|---|---|---|
| ClusterIP | ✅ VIPあり | ❌ None |
| DNS応答 | VIP 1つを返す | 全PodのIPを返す |
| 負荷分散 | kube-proxyが振り分け | ❌ クライアント側で選択 |
| 個別Podへのアクセス | ❌ | ✅ Pod名でアクセス可能 |
個別PodのDNS名
<Pod名>.<Headless Service名>.<Namespace>.svc.cluster.local
| Pod | DNS名 | 用途 |
|---|---|---|
| db-0 | db-0.db-headless.default.svc.cluster.local |
マスターに直接アクセス |
| db-1 | db-1.db-headless.default.svc.cluster.local |
レプリカ1に直接アクセス |
なぜDBにはHeadless Serviceが必要か
通常のServiceはランダム振り分けするため、書き込みがレプリカに飛ぶ可能性があります。Headless Serviceならマスターを名前で指定できます。
| 通信パターン | DNS名 | 用途 |
|---|---|---|
| マスターに書き込み | db-0.db-headless |
個別Pod指定 |
| レプリカから読み取り |
db-headless(全Pod返る) |
読み取り分散 |
🍽️ Headless Service = 「各シェフの個人携帯番号」→ 料理長に直接電話できる。通常のService = 「ホールの代表番号」→ 空いてるウェイターに自動でつながる。
10-4. 使い分けの判断
| アプリ | 選択 | 理由 |
|---|---|---|
| nginx(Web) | Deployment | ステートレス |
| Rails API | Deployment(+ PVC) | アップロードファイルがあればPVC追加 |
| PostgreSQL | StatefulSet | マスター/レプリカ構成 |
| Elasticsearch | StatefulSet | 各ノードに専用データ |
11. YAMLの書き方 ― ConfigMap・Secret・PVCの定義
11-1. Docker Composeとの全体対比
やりたいこと: Rails API + PostgreSQL。環境変数でDB接続情報を管理し、DBデータを永続化。
Docker Compose
services:
api:
image: myapp-api:1.0
environment:
- RAILS_ENV=production
- DATABASE_HOST=db
- DATABASE_PASSWORD=secretpass123
ports:
- "3000:3000"
db:
image: postgres:14
environment:
- POSTGRES_PASSWORD=secretpass123
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:
k8s(責務ごとにファイル分離)
| ファイル | 役割 | Docker Composeとの対応 |
|---|---|---|
| configmap.yaml | 一般設定 |
environment: の非機密部分 |
| secret.yaml | 機密情報 |
environment: の機密部分 |
| pvc.yaml | ストレージ要求 | volumes: db-data: |
| deployment-api.yaml | APIアプリ | services: api: |
| deployment-db.yaml | DB | services: db: |
| service-*.yaml | 通信 | ports: |
11-2. ConfigMap YAML
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: api-config
data:
RAILS_ENV: "production"
DATABASE_HOST: "db-service"
DATABASE_PORT: "5432"
LOG_LEVEL: "info"
| フィールド | 値 | 意味 |
|---|---|---|
data |
キー: 値のペア | Podに環境変数として注入される設定値 |
RAILS_ENV |
"production" | アプリの動作モード |
DATABASE_HOST |
"db-service" | DB用Service名(CoreDNSで名前解決される) |
🔰
data:の値はすべて文字列で指定します(数値の5432も"5432"と書く)。
11-3. Secret YAML
# secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: db-secret
type: Opaque
stringData:
POSTGRES_PASSWORD: "secretpass123"
DATABASE_PASSWORD: "secretpass123"
| フィールド | 意味 |
|---|---|
type: Opaque |
汎用的なSecret(他に kubernetes.io/tls 等がある) |
stringData |
平文で書ける(apply時に自動でBase64エンコード) |
data との違い |
data は自分でBase64エンコードが必要 |
🔰 ConfigMapとの使い分け: パスワード・APIキーなど漏れたら困る値はSecret、それ以外はConfigMap。
11-4. PVC YAML
# pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: db-data-pvc
spec:
accessModes:
- ReadWriteOnce
storageClassName: standard # minikubeのデフォルト
resources:
requests:
storage: 1Gi
| フィールド | 値 | 意味 |
|---|---|---|
accessModes |
ReadWriteOnce | 1つのNodeからのみ読み書き可能 |
storageClassName |
standard | minikubeのデフォルトStorageClass(動的にPVを作成) |
resources.requests.storage |
1Gi | 要求するディスクサイズ |
🔰 PVCは「ストレージの申請書」 です。PVC を作ると、StorageClass が自動でPV(実体のディスク)を割り当ててくれます。
11-5. Deployment YAML:全てを参照する
# deployment-db.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: db
spec:
replicas: 1
selector:
matchLabels:
app: db
template:
metadata:
labels:
app: db
spec:
containers:
- name: postgres
image: postgres:14
ports:
- containerPort: 5432
# ★ ConfigMapから環境変数を一括読み込み
envFrom:
- configMapRef:
name: api-config
# ★ Secretから個別の環境変数を読み込み
env:
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: POSTGRES_PASSWORD
# ★ PVCをマウント
volumeMounts:
- name: db-storage
mountPath: /var/lib/postgresql/data
subPath: pgdata
# ★ PVCをボリュームとして定義
volumes:
- name: db-storage
persistentVolumeClaim:
claimName: db-data-pvc
11-6. 参照方法の全パターン
| 参照方法 | ConfigMap | Secret | 用途 |
|---|---|---|---|
| 全キーを環境変数 | envFrom.configMapRef |
envFrom.secretRef |
まとめて読み込み |
| 1キーだけ環境変数 | env.valueFrom.configMapKeyRef |
env.valueFrom.secretKeyRef |
特定のキーだけ |
| ファイルとしてマウント | volumes.configMap |
volumes.secret |
設定ファイル/証明書 |
11-7. Docker Compose → k8s 対応マップ
| Docker Compose | k8s リソース | k8s YAML フィールド |
|---|---|---|
environment: (一般) |
ConfigMap | envFrom.configMapRef |
environment: (機密) |
Secret | env.valueFrom.secretKeyRef |
volumes: ./file:/path |
ConfigMap + volumeMount | volumes.configMap |
volumes: vol-name:/path |
PVC + volumeMount | volumes.persistentVolumeClaim |
volumes: vol-name: (定義) |
PVC + StorageClass | PersistentVolumeClaim |
12. ハンズオン:ConfigMap・Secret・PVCを操作する
このハンズオンでは、セクション11で学んだYAMLを実際にkubectl applyして動作を確認します。
| ステップ | やること | 確認ポイント |
|---|---|---|
| 12-3 | ConfigMap作成 | 設定値が保存されているか |
| 12-4 | Secret作成 | Base64で保存されているか |
| 12-5 | PVC作成 | StatusがBoundになるか |
| 12-6 | Deployment作成 | ConfigMap/Secret/PVCが全て参照できているか |
| 12-7 | データ永続化テスト | Pod削除後もデータが残るか |
🍽️ 設定メモ(ConfigMap)→ 金庫の暗証番号(Secret)→ 食材倉庫(PVC)→ 全部使って店舗オープン(Deployment)→ 店舗を建て替えても食材は残る(永続化テスト)
12-1. 作業ディレクトリ
# ホームディレクトリで実行
cd ~
mkdir -p k8s-practice/config-secret-pv
cd k8s-practice/config-secret-pv
12-2. この記事で作成するファイル
~/k8s-practice/
└── config-secret-pv/ ← 今回はここ
├── configmap.yaml
├── secret.yaml
├── pvc.yaml
└── deployment-db.yaml
12-3. ConfigMapの作成と確認
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
RAILS_ENV: "production"
DATABASE_HOST: "db-service"
LOG_LEVEL: "info"
🔰 ConfigMap = 環境変数の設定ファイル。 Podに注入すると、アプリから
process.env.DATABASE_HOSTのように使える。
# ConfigMap作成
kubectl apply -f configmap.yaml
# 確認
kubectl get configmap app-config
# 中身を確認
kubectl describe configmap app-config
期待される出力:
Name: app-config
Data
====
DATABASE_HOST: db-service
LOG_LEVEL: info
RAILS_ENV: production
12-4. Secretの作成と確認
# secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: db-secret
type: Opaque
stringData:
POSTGRES_PASSWORD: "secretpass123"
🔰
stringDataで平文のまま書ける。 apply後は自動でBase64エンコードされ、dataフィールドに格納される。
# Secret作成
kubectl apply -f secret.yaml
# 確認(値はBase64で表示される)
kubectl get secret db-secret -o yaml
期待される出力(抜粋):
data:
POSTGRES_PASSWORD: c2VjcmV0cGFzczEyMw==
# Base64デコードで元の値を確認
kubectl get secret db-secret -o jsonpath='{.data.POSTGRES_PASSWORD}' | base64 -d
# → secretpass123
12-5. PVCの作成と確認
# pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: db-data-pvc
spec:
accessModes:
- ReadWriteOnce
storageClassName: standard
resources:
requests:
storage: 1Gi
🔰 PVCを作成すると、StorageClassが自動でディスク(PV)を確保してくれる。 Pod削除後もデータは残る。
# PVC作成
kubectl apply -f pvc.yaml
# 確認
kubectl get pvc db-data-pvc
期待される出力:
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
db-data-pvc Bound pvc-xxx 1Gi RWO standard 10s
🔰 STATUS が Bound になればOK。PVが自動作成されてバインドされています。
12-6. 全てを組み合わせたDeployment
# deployment-db.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: db
spec:
replicas: 1
selector:
matchLabels:
app: db
template:
metadata:
labels:
app: db
spec:
containers:
- name: postgres
image: postgres:14
ports:
- containerPort: 5432
envFrom:
- configMapRef:
name: app-config
env:
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: POSTGRES_PASSWORD
volumeMounts:
- name: db-storage
mountPath: /var/lib/postgresql/data
subPath: pgdata
volumes:
- name: db-storage
persistentVolumeClaim:
claimName: db-data-pvc
# Deployment作成
kubectl apply -f deployment-db.yaml
# Pod確認
kubectl get pods
# Pod内の環境変数を確認
kubectl exec -it $(kubectl get pods -l app=db -o jsonpath='{.items[0].metadata.name}') -- env | grep -E "RAILS_ENV|DATABASE_HOST|POSTGRES_PASSWORD"
期待される出力:
RAILS_ENV=production
DATABASE_HOST=db-service
POSTGRES_PASSWORD=secretpass123
12-7. データの永続化を確認
# Pod内にテストデータを作成
kubectl exec -it $(kubectl get pods -l app=db -o jsonpath='{.items[0].metadata.name}') -- psql -U postgres -c "CREATE TABLE test (id int, name text);"
kubectl exec -it $(kubectl get pods -l app=db -o jsonpath='{.items[0].metadata.name}') -- psql -U postgres -c "INSERT INTO test VALUES (1, 'hello');"
# Podを削除(Deploymentが自動再作成)
kubectl delete pod -l app=db
# 新しいPodが起動するのを待つ
kubectl get pods --watch
# データが残っているか確認
kubectl exec -it $(kubectl get pods -l app=db -o jsonpath='{.items[0].metadata.name}') -- psql -U postgres -c "SELECT * FROM test;"
期待される出力:
id | name
----+-------
1 | hello
🔰 Podを削除してもPVCがデータを保持しているため、新しいPodでデータが復元されます。
12-8. お片付け
# 全リソースを削除
kubectl delete deployment db
kubectl delete pvc db-data-pvc
kubectl delete secret db-secret
kubectl delete configmap app-config
# 確認
kubectl get all
kubectl get pvc
kubectl get configmap
kubectl get secret
13. まとめ
13-1. 本記事で学んだこと
| # | 学んだこと | キーポイント |
|---|---|---|
| ① | 分離の必要性 | Docker Composeの全部入りから、k8sの責務分離(ConfigMap/Secret/PV)へ |
| ② | ConfigMap | 一般設定の外部化。環境変数注入 or ファイルマウント。共有パターン |
| ③ | Secret | 機密情報版ConfigMap。Base64は暗号化ではない。保護レイヤーの理解 |
| ④ | Secret運用 | etcd暗号化、Sealed Secrets、External Secrets Operator |
| ⑤ | PV/PVC | ストレージの実体(PV)と要求(PVC)の分離。ReclaimPolicy |
| ⑥ | StorageClass | 動的プロビジョニング。CSI。volumeBindingMode |
| ⑦ | StatefulSet | DB向け。連番Pod名、順序付き起動、各Podに専用PVC、Headless Service |
13-2. ゴールの達成確認
| # | ゴール | 達成状況 |
|---|---|---|
| ① | なぜ分離するかを説明できる | ✅ セクション4で解説 |
| ② | ConfigMapの2つの使い方を実践できる | ✅ セクション5, 11で解説 |
| ③ | SecretとConfigMapの違いを説明できる | ✅ セクション6-7で解説 |
| ④ | PV/PVCとStorageClassを説明できる | ✅ セクション8-9で解説 |
| ⑤ | DeploymentとStatefulSetの使い分け | ✅ セクション10で解説 |
13-3. 次回予告
第5回:模擬プロジェクト — 全概念を組み合わせて実践
次回は、第1回〜第4回で学んだ全概念(Deployment, Service, Ingress, ConfigMap, Secret, PVC)を組み合わせて、実践的なアプリケーションをk8s上に構築します。Docker Composeで作れるレベルのアプリを、k8sで一から構築する体験を通じて、各リソースの関係性と設計判断を体感します。