【Kubernetes実践編 #03】詳細設計 — マニフェストの全パラメータを「意図を持って」決める
3.1 はじめに
3.1.1 前回の振り返り — 基本設計書が手元にある状態
前回(第2回)で、TaskBoardの基本設計書を作成しました。5つの設計領域について「何を使うか」と「なぜそれを選ぶか」を文書化しています。
- ワークロード設計: Nginx → Deployment、TaskBoard API → Deployment、MySQL → StatefulSet、DB初期化 → Job、バックアップ → CronJob、ログ収集 → DaemonSet
- 可用性設計: フロントとAPIは2レプリカ、MySQLは1レプリカ、PDBで最低1Podを維持
- データ永続化設計: MySQL用PVC(1Gi、ReadWriteOnce)、CronJobによる定期バックアップ
- ネットワーク設計: Service(ClusterIP)+ Gateway API(パスベースルーティング)
- セキュリティ設計方針: Namespace分離(app / db / monitoring)、RBAC、NetworkPolicy(デフォルト拒否)、SecurityContext(非root必須)
基本設計書は「方針」を決める文書です。「MySQLにはStatefulSetを使用する(理由:データの永続化と安定したネットワークIDが必要)」——ここまでが基本設計の範囲でした。
3.1.2 本回の問題提起 — 「YAMLの各フィールドの値を根拠を持って決められるか」
基本設計書があっても、いざマニフェストを書こうとすると手が止まる瞬間があります。
「requests.memoryは256Miでいい?384Mi?根拠は?」
「startupProbeのfailureThresholdは何回にする?periodSecondsは?」
「readOnlyRootFilesystemをtrueにしたいけど、どのパスにemptyDirを設定すればいい?」
これらの問いに答えるのが「詳細設計」です。基本設計の「方針」を、YAMLマニフェストの「具体的なパラメータ値」に変換する作業です。そして、すべてのパラメータに「なぜこの値か」の根拠を添える。これが本回の仕事です。
3.1.3 本回のゴールと成果物
本回のゴールは、マニフェストの全フィールドについて「なぜこの値か」を説明できる状態になることです。
成果物は以下の2つです。
- パラメータ設計書: 各マニフェストのフィールドごとに「値」と「設計根拠」を対にした文書。記事内で段階的に構築します
- 全マニフェスト: 第4回〜第6回でkubectl applyする完成版マニフェスト一式。主要マニフェスト(Deployment、StatefulSet、Service、HPA、PDB)は全文掲載し、補助マニフェスト(Namespace、RBAC、NetworkPolicy等)は応用編からの変更点のみ記載します
本回は設計フェーズの最終回です。次回(第4回)からは構築フェーズに入り、ここで作成したマニフェストを実際にクラスタに適用します。本回の成果物の品質が、構築フェーズの成否を左右します。
3.2 VMのパラメータシートとK8sのパラメータ設計書
3.2.1 VMの世界でのパラメータシート — 何を記録するか
VMwareの世界で「詳細設計書」や「パラメータシート」を書いた経験がある方は、その構造を思い出してください。基本設計で「Webサーバー2台、DBサーバー1台」と決めた後、詳細設計ではVM1台1台のパラメータを詰めていきます。
- VMスペック: vCPU数、メモリ容量、ディスク容量(各値に設計根拠を記載)
- OS設定: タイムゾーン、NTP設定、ファイアウォールルール
- ミドルウェア設定: Apacheのworker数、MySQLのinnodb_buffer_pool_size
- 監視設定: Zabbixのポーリング間隔、リトライ回数、アラート閾値
- バックアップ設定: スケジュール、保持世代数、取得方式
共通しているのは、すべてのパラメータに「なぜこの値か」の根拠が求められる点です。設計レビューで「このVMのメモリが4GBなのはなぜ?」と聞かれて「なんとなく」では通りません。「JVMヒープ2GB + OS領域1GB + バッファ1GB = 4GB」のように、積み上げの根拠が必要です。
3.2.2 K8sのパラメータ設計書 — 何を記録するか
K8sの詳細設計でも同じことをします。ただし、対象がVMの設定値からマニフェストのフィールド値に変わります。
| VMのパラメータシート | K8sのパラメータ設計書 |
|---|---|
| vCPU数 / メモリ容量 | resources(requests / limits) |
| Zabbixポーリング間隔 / リトライ回数 | Probe(periodSeconds / failureThreshold) |
| サービスアカウント / SELinux設定 | SecurityContext(runAsUser / capabilities) |
| 設定ファイルの外出し(Ansibleの変数) | ConfigMap / Secret |
| デプロイ方式(ローリング / Blue-Green) | strategy(RollingUpdate: maxSurge / maxUnavailable) |
| HA構成の同居制限(アンチアフィニティ) | PDB(minAvailable / maxUnavailable) |
技術は変わりましたが、「パラメータの値を決め、根拠を添える」というプロセスはまったく同じです。
3.2.3 「なぜこの値か」に答える設計文化
本回で作成するパラメータ設計書は、以下の構造を持ちます。各セクション(3.3〜3.7)の末尾で、その設計領域のパラメータ設計書パートを完成させていきます。
┌────────────────┬──────────────┬─────────────────────────────┐
│ パラメータ │ 設定値 │ 設計根拠 │
├────────────────┼──────────────┼─────────────────────────────┤
│ replicas │ 2 │ 基本設計: 可用性確保(PDB考慮) │
│ requests.cpu │ 200m │ Payara Micro定常時のCPU使用量 │
│ limits.memory │ 512Mi │ JVMヒープ256Mi+メタ+OS領域 │
│ startupProbe │ fT:30, pS:2 │ 起動15-20秒、最大60秒待ち │
└────────────────┴──────────────┴─────────────────────────────┘
この「値 + 根拠」のペアが、設計レビューでの「なぜこの値か」への回答になります。VMの設計レビューと同じ文化を、K8sの世界でも維持しましょう。
3.3 resources設計 — CPU/メモリの割り当て根拠
3.3.1 コンポーネント別の特性を整理する
resources(requests / limits)の値を決めるには、まず各コンポーネントの「性格」を理解する必要があります。応用編第1回でkubectl top podsを実行した際、NginxとPayara Microのリソース消費量に大きな差があったことを覚えているでしょうか。あの実測値が、resources設計の出発点になります。
| コンポーネント | CPU特性 | メモリ特性 | 定常時の実測傾向 |
|---|---|---|---|
| Nginx | 静的ファイル配信のみ。CPU消費は極めて小さい | 軽量。ワーカープロセスが少量のメモリを使用 | CPU: 1〜5m、メモリ: 20〜40Mi |
| TaskBoard API(Payara Micro) | 起動時に高負荷(クラスローディング)。定常時は中程度 | JVMヒープ + Metaspace + OS領域。起動後は比較的安定 | CPU: 10〜50m、メモリ: 280〜350Mi |
| MySQL | クエリ処理に依存。TaskBoardの規模では低〜中程度 | innodb_buffer_pool_sizeが支配的。起動後は安定 | CPU: 10〜30m、メモリ: 150〜250Mi |
requestsは「このPodを配置するためにNodeに確保してほしいリソース量」であり、Schedulerの配置判断に使われます。limitsは「このPodが使用できるリソースの上限」であり、これを超えるとOOMKill(メモリ)やスロットリング(CPU)が発生します。
3.3.2 TaskBoard API(Payara Micro)のresources設計
Payara MicroはJVMベースのアプリケーションサーバーです。メモリ設計にはJVM固有の考慮が必要です。
JVMのメモリは、大きく3つの領域で構成されます。ヒープ(オブジェクトの格納先)、Metaspace(クラス情報の格納先)、そしてネイティブメモリ(スレッドスタック、NIOバッファ等)です。Payara Micro 7.2026.1のデフォルト設定では、JVMヒープの最大サイズはコンテナのメモリ制限の約25%〜50%に自動調整されます(JVMのエルゴノミクス機能)。
limits.memoryを512Miに設定した場合、JVMは概ね以下のように領域を使います。
limits.memory: 512Mi の内訳(概算)
├── JVMヒープ : 約200〜256Mi(エルゴノミクスによる自動調整)
├── Metaspace : 約50〜80Mi(ロードするクラス数に依存)
├── スレッドスタック : 約30〜50Mi(スレッド数 × 1MBスタック)
├── NIOバッファ等 : 約20〜30Mi
└── OS領域 + 余裕 : 残り(OOMKill防止のバッファ)
応用編第1回でkubectl topを実行した際、Payara Microは定常時に280〜350Mi程度のメモリを消費していました。limits.memoryを512Miに設定することで、定常時の消費量に対して約150〜230Miの余裕が確保されます。この余裕は、リクエスト増加時のヒープ拡張やGCの一時的なメモリ使用増に対応するバッファです。
requests.memoryは384Miとします。定常時の消費量(280〜350Mi)をカバーしつつ、Schedulerが適切にNodeへ配置できるだけの「予約量」です。requestsをlimitsと同じ512Miにすると、過剰な予約でNodeのリソースが無駄になります。逆に256Miまで下げると、実際の消費量がrequestsを常時上回り、Nodeのリソースが逼迫したときに真っ先に退避される対象になります。
CPUについては、requests: 200mが起動後の定常時に十分な値です。起動時にはクラスローディングで一時的にCPUが跳ね上がりますが、limitsの500mでキャップされるため、他のPodへの影響は限定的です。
3.3.3 Nginx のresources設計
Nginxは静的ファイル配信のみを担当する軽量コンポーネントです。CPU消費は極めて小さく、メモリも少量で済みます。
requests.cpuを50m、requests.memoryを64Miとします。これは応用編で観察した定常時のリソース消費(CPU: 1〜5m、メモリ: 20〜40Mi)に十分な余裕を持たせた値です。limits.cpuは200m、limits.memoryは128Miとし、トラフィック急増時にも対応できるようにします。
Nginxのresourcesは意図的に小さく設定しています。HPAでスケールアウトする際、1Pod追加あたりのリソース消費が小さいため、ResourceQuota(app: requests.cpu合計2コア)の中でより多くのPodを起動できます。応用編第8回で設定したHPA(min: 2、max: 6)でフルスケールアウトしても、Nginx 6 Pod × requests 50m = 300mで、Quotaには十分な余裕があります。
3.3.4 MySQL のresources設計
MySQLはデータベースという性質上、メモリの安定的な確保がパフォーマンスに直結します。特にinnodb_buffer_pool_sizeはMySQLのパフォーマンスを左右する最重要パラメータであり、利用可能なメモリの50〜80%を割り当てるのがベストプラクティスとされています。
limits.memoryを512Miに設定した場合、MySQLが実際に使えるメモリは512MiからOS領域やスレッド管理領域を差し引いた量になります。innodb_buffer_pool_sizeはデフォルトの128MBのままとし、TaskBoardの学習環境では十分なサイズです。本番環境でデータ量が増える場合は、limits.memoryの増加とともにinnodb_buffer_pool_sizeも調整します。
requests.memoryは256Miとします。MySQLは起動後にメモリ使用量が安定するため、requestsとlimitsの間に差を設けても問題ありません。ただし、limitsとの差が大きすぎると、Nodeのメモリが逼迫した際にOOMKillされるリスクが高まります。StatefulSetのPodは再起動時にデータの整合性チェックが走るため、不用意な再起動は避けたいところです。
3.3.5 resources設計のパラメータ設計書
| コンポーネント | パラメータ | 設定値 | 設計根拠 |
|---|---|---|---|
| Nginx | requests.cpu | 50m | 定常時CPU消費1〜5m。余裕を持たせつつ、HPA時のQuota効率を考慮 |
| requests.memory | 64Mi | 定常時メモリ消費20〜40Mi。ワーカープロセスの安定動作に十分 | |
| limits.cpu | 200m | トラフィック急増時の処理能力確保 | |
| limits.memory | 128Mi | 静的ファイル配信のみのため128Miで十分。OOMKill防止のバッファ含む | |
| TaskBoard API | requests.cpu | 200m | 定常時CPU消費10〜50m。JVMのGC処理やリクエスト処理の安定確保 |
| requests.memory | 384Mi | 定常時メモリ消費280〜350Mi。Schedulerへの適切な予約量 | |
| limits.cpu | 500m | 起動時のクラスローディングおよびリクエストスパイク時に対応 | |
| limits.memory | 512Mi | JVMヒープ約256Mi + Metaspace約80Mi + スレッド/NIO約50Mi + OS/バッファ約126Mi | |
| MySQL | requests.cpu | 200m | クエリ処理の安定性確保。TaskBoardの規模では十分 |
| requests.memory | 256Mi | innodb_buffer_pool_size 128MB + OS領域 + バッファ | |
| limits.cpu | 500m | 複雑なクエリやテーブルスキャン時のCPU確保 | |
| limits.memory | 512Mi | innodb_buffer_pool_size 128MB + 接続スレッド + 一時テーブル + OS領域 |
3.4 Probe設計 — 「応用編の体験」を「設計判断」に変換する
3.4.1 3つのProbeの復習と設計判断の観点
応用編第8回で、3つのProbeの役割と各パラメータの効果を体験しました。ここでは、その体験を「TaskBoardの各コンポーネントにどのProbeをどの値で設定するか」という設計判断に変換します。
| Probe | 判定結果 | 失敗時のアクション | 設計判断の観点 |
|---|---|---|---|
| startupProbe | 起動が完了したか | 完了前にliveness/readinessを抑制 | 起動に時間がかかるコンポーネントに必須 |
| livenessProbe | プロセスが生きているか | コンテナを再起動 | failureThresholdを小さくしすぎると不安定化 |
| readinessProbe | リクエストを受け入れ可能か | Serviceのエンドポイントから除外 | DB接続確認等の依存チェックを含められる |
応用編第8回で体験した重要な教訓を振り返ります。startupProbeなしでPayara Microをデプロイしたとき、起動完了前にlivenessProbeが「応答しない → 異常 → 再起動」と判定し、CrashLoopBackOffに陥りました。また、failureThresholdを1にしたとき、一時的な高負荷でPodが不必要に再起動される事態も体験しました。これらの「体験」が、パラメータ値の「根拠」になります。
3.4.2 TaskBoard API(Payara Micro)のProbe設計
Payara MicroはMicroProfile Health標準のエンドポイントを持っています。本回では、応用編第8回で設定したカスタムHealthCheck(HealthCheckReady.java:DB接続確認を含むReadinessチェック)が追加済みの状態を前提とします。
startupProbe — Payara Microの起動時間は15〜20秒です。Node負荷が高い状況では25秒以上かかる可能性もあります。応用編第8回でfailureThreshold: 30, periodSeconds: 2(最大60秒待ち)を設定し、安定した起動を確認しました。この値をそのまま採用します。エンドポイントは/health/startedで、Payara Microの起動完了を正確に判定できます。
livenessProbe — プロセスの生存確認です。エンドポイントは/health/live。応用編第8回ではperiodSeconds: 10, failureThreshold: 3(30秒間の無応答で再起動)を設定しました。この設定は「一時的な高負荷で誤って再起動されない」と「本当にハングした場合に適切な時間で検知する」のバランスを取っています。failureThresholdを1にした場合の不安定さは応用編で体験済みです。
readinessProbe — リクエスト受け入れ可否の判定です。エンドポイントは/health/readyで、応用編第8回で追加したカスタムHealthCheckReady(DB接続確認を含む)が動作します。periodSeconds: 5でlivenessより高頻度に設定します。readinessProbeの失敗はServiceのエンドポイントからの除外のみで再起動は行われないため、頻繁にチェックしても副作用は小さいです。DB接続が切れた場合に素早くトラフィックから除外し、復旧したら素早くトラフィックに復帰させるためです。
3.4.3 Nginx のProbe設計
Nginxは起動が非常に速く(通常1秒以内)、MicroProfile Healthのようなヘルスチェック専用エンドポイントは持ちません。HTTP GETでルートパス(/)にアクセスし、応答を確認します。ポートは応用編第7回で非root化した際に80から8080に変更済みです。
startupProbeは設定しません。 Nginxの起動は1秒以内で完了するため、startupProbeを設ける必要はありません。livenessProbeのinitialDelaySecondsも不要(デフォルトの0秒)です。
livenessProbe — HTTP GET /(port 8080)で応答を確認します。periodSeconds: 10, failureThreshold: 3で、30秒間の無応答で再起動します。Nginxがハングすることは稀ですが、万が一のワーカープロセス異常に備えた保険です。
readinessProbe — livenessと同じHTTP GET /(port 8080)を使用しますが、periodSeconds: 5, failureThreshold: 3で、15秒間応答しなければServiceから除外します。設定ファイルの誤りなどで応答不能になった場合に、トラフィックを正常なPodに寄せるための設定です。
3.4.4 MySQL のProbe設計
MySQLのProbeにはHTTPエンドポイントがないため、TCP接続チェック(port 3306)を使用します。MySQLにはmysqladmin pingというヘルスチェックコマンドもありますが、exec Probeはプロセス起動のオーバーヘッドがあります。TCP接続チェックは軽量かつ「MySQLプロセスがポートをlistenしているか」を十分に判定できます。
startupProbeは設定しません。 MySQLの起動は通常5〜10秒程度で完了します。Payara Microほど長くはないため、livenessProbeのinitialDelaySeconds: 10で対応できます。ただし、初回起動時(データディレクトリの初期化を含む場合)は20秒以上かかることがあるため、余裕を持たせます。
livenessProbe — tcpSocket port: 3306でMySQLプロセスの生存を確認します。initialDelaySeconds: 15, periodSeconds: 10, failureThreshold: 3です。データベースの再起動はデータ整合性に影響しうるため、failureThresholdは慎重に設定します。応用編第8回の教訓をここでも活かし、安易に再起動されない設定にします。
readinessProbe — 同じくtcpSocket port: 3306を使用します。periodSeconds: 5, failureThreshold: 3で、MySQLがクエリを受け付けられない状態を素早く検知し、Headless Serviceのエンドポイントから除外します。
3.4.5 Probe設計のパラメータ設計書
| コンポーネント | Probe | 方式 | パラメータ | 設計根拠 |
|---|---|---|---|---|
| TaskBoard API | startupProbe | HTTP GET /health/started :8080 | initialDelaySeconds: 0, periodSeconds: 2, failureThreshold: 30, timeoutSeconds: 3 | 起動15-20秒。最大60秒待ち。応用編第8回で検証済み |
| livenessProbe | HTTP GET /health/live :8080 | periodSeconds: 10, failureThreshold: 3, timeoutSeconds: 3 | 30秒間の無応答で再起動。一時的高負荷での誤再起動を防止 | |
| readinessProbe | HTTP GET /health/ready :8080 | periodSeconds: 5, failureThreshold: 3, successThreshold: 1, timeoutSeconds: 3 | DB接続確認含む。15秒で除外、1回成功で復帰 | |
| Nginx | livenessProbe | HTTP GET / :8080 | periodSeconds: 10, failureThreshold: 3, timeoutSeconds: 3 | 30秒間の無応答で再起動。起動が速いためstartupProbe不要 |
| readinessProbe | HTTP GET / :8080 | periodSeconds: 5, failureThreshold: 3, successThreshold: 1, timeoutSeconds: 3 | 15秒で除外。設定ミスによる応答不能に備える | |
| MySQL | livenessProbe | tcpSocket :3306 | initialDelaySeconds: 15, periodSeconds: 10, failureThreshold: 3, timeoutSeconds: 3 | 初期化含む起動時間を考慮。DB再起動の影響が大きいため慎重に設定 |
| readinessProbe | tcpSocket :3306 | periodSeconds: 5, failureThreshold: 3, successThreshold: 1, timeoutSeconds: 3 | クエリ受付不可を素早く検知。Headless Serviceエンドポイント制御 |
3.5 SecurityContext設計 — 各Podのセキュリティ設定値を決める
3.5.1 応用編第7回の知識を設計に落とし込む
応用編第7回では、SecurityContextの4つの設定項目を優先順位とともに学びました。「非rootで動かす → ファイルシステムを保護する → 権限昇格を防ぐ → ケーパビリティを最小化する」という順序です。この4項目をTaskBoardの各コンポーネントに適用する際の具体的な値を決定します。
応用編第7回で確認した各コンポーネントの実行ユーザーは以下の通りです。
| コンポーネント | 実行ユーザー | UID | 応用編第7回での対応 |
|---|---|---|---|
| Nginx | nginx | 101 | ポート80→8080変更、nginx.confをConfigMap化、emptyDirマウント |
| TaskBoard API(Payara Micro) | payara | 1000 | 元から非root。SecurityContext宣言のみ追加 |
| MySQL | mysql | 999 | 元から非root。readOnlyRootFilesystemは非適用 |
3.5.2 コンポーネント別のSecurityContext設計
全コンポーネント共通の設定:
runAsNonRoot: true— rootでの実行を禁止allowPrivilegeEscalation: false— 権限昇格を禁止capabilities.drop: ALL— 全ケーパビリティを剥奪
Nginx: runAsUser: 101(nginx:1.27イメージのnginxユーザー)。readOnlyRootFilesystem: trueを適用し、/tmp(PID、一時ファイル、ログ)と/var/cache/nginx(キャッシュ)をemptyDirでマウントします。nginx.confはConfigMapで/etc/nginx/nginx.confにsubPathマウントします。応用編第7回で非root対応済みのnginx.conf(listen 8080、PID/一時ファイル/ログを/tmp配下に変更)を使用します。
TaskBoard API(Payara Micro): runAsUser: 1000(payaraユーザー)。readOnlyRootFilesystem: trueを適用し、/tmp(Java一時ファイル領域)と/opt/payara/config(Payara実行時設定)をemptyDirでマウントします。Payara Microは元から非rootで動作するため、SecurityContextの追加で動作に影響はありません。
MySQL: runAsUser: 999(mysqlユーザー)。readOnlyRootFilesystemは適用しません。応用編第7回で判断した通り、MySQLは起動時に/var/lib/mysql以外にも複数のディレクトリ(/var/lib/mysql-files、/var/lib/mysql-keyring等)への書き込みを必要とし、これらすべてをemptyDirで対応する費用対効果が低いためです。代わりに/tmpと/var/run/mysqld(ソケットファイル)をemptyDirでマウントします。データディレクトリ/var/lib/mysqlはvolumeClaimTemplatesによるPVCマウントです。
3.5.3 SecurityContext設計のパラメータ設計書
| コンポーネント | パラメータ | 設定値 | 設計根拠 |
|---|---|---|---|
| Nginx | runAsNonRoot | true | 非root実行を強制 |
| runAsUser | 101 | nginx:1.27のnginxユーザー(uid=101) | |
| readOnlyRootFilesystem | true | 書き込みはemptyDir(/tmp, /var/cache/nginx)に限定 | |
| allowPrivilegeEscalation | false | 権限昇格を禁止 | |
| capabilities.drop | ALL | 全ケーパビリティ剥奪 | |
| emptyDirマウント | /tmp, /var/cache/nginx | Nginx動作に必要な書き込みパス | |
| TaskBoard API | runAsNonRoot | true | 非root実行を強制 |
| runAsUser | 1000 | Payara Microのpayaraユーザー(uid=1000) | |
| readOnlyRootFilesystem | true | 書き込みはemptyDir(/tmp, /opt/payara/config)に限定 | |
| allowPrivilegeEscalation | false | 権限昇格を禁止 | |
| capabilities.drop | ALL | 全ケーパビリティ剥奪 | |
| emptyDirマウント | /tmp, /opt/payara/config | JVM一時ファイルとPayara設定ファイル用 | |
| MySQL | runAsNonRoot | true | 非root実行を強制 |
| runAsUser | 999 | mysql:8.0のmysqlユーザー(uid=999) | |
| readOnlyRootFilesystem | false(非適用) | MySQL初期化プロセスが複数パスに書き込むため費用対効果が低い | |
| allowPrivilegeEscalation | false | 権限昇格を禁止 | |
| capabilities.drop | ALL | 全ケーパビリティ剥奪 | |
| emptyDirマウント | /tmp, /var/run/mysqld | 一時ファイルとソケットファイル用。データは PVC |
3.6 ConfigMap / Secret設計 — 何を外出しにするか
3.6.1 ConfigMapに格納するもの
ConfigMapには、マニフェストから分離すべき「設定情報」を格納します。TaskBoardで必要なConfigMapは以下の1つです。
nginx-config: 応用編第7回で作成した非root対応版のnginx.confを格納します。listen 8080、PID/一時ファイル/ログを/tmp配下に配置する設定です。Nginx DeploymentからsubPathマウントで/etc/nginx/nginx.confに配置します。subPathを使う理由は、/etc/nginxディレクトリ内の他のファイル(mime.types等)を上書きしないためです。
TaskBoard API(Payara Micro)の設定は、現状ではDockerイメージのビルド時に組み込まれています。DB接続情報はSecretとenv経由で渡します。ConfigMapとして外出しにする設定は現時点ではありません。実践編第8回でHelm化する際に、values.yamlに切り出す項目として改めて検討します。
3.6.2 Secretに格納するもの
Secretには、平文で保管すべきでない「機密情報」を格納します。
mysql-secret(db Namespace): MySQL認証情報を格納します。応用編第3回で作成したSecretと同じ構造です。
MYSQL_ROOT_PASSWORD— MySQLのrootパスワードMYSQL_DATABASE— 作成するデータベース名(taskboard)MYSQL_USER— アプリケーション用ユーザー名MYSQL_PASSWORD— アプリケーション用パスワード
MySQL StatefulSetからはenvFrom.secretRefで全キーを環境変数として注入します。TaskBoard API Deploymentからはenv.valueFrom.secretKeyRefでMYSQL_USERとMYSQL_PASSWORDを個別に参照します。Secretはdb Namespaceに配置しますが、TaskBoard APIはapp Namespaceにあるため、クロスNamespace参照はできません。TaskBoard APIではDB接続情報を環境変数(DB_HOST, DB_NAME, DB_USER, DB_PASSWORD)として設定し、DB_USERとDB_PASSWORDはdb NamespaceのSecretではなく、app Namespaceにもmysql-secretを作成して参照します。
3.6.3 変更時の反映方法
ConfigMapやSecretを変更した場合、Podへの反映方法はマウント方式によって異なります。
| マウント方式 | 自動反映 | 反映タイミング | TaskBoardでの該当 |
|---|---|---|---|
| volumeMount(subPathなし) | あり | kubeletの同期間隔(デフォルト約60秒) | — |
| volumeMount + subPath | なし | Pod再起動が必要 | nginx-config(subPath使用) |
| env / envFrom | なし | Pod再起動が必要 | mysql-secret(envFrom使用)、API環境変数 |
TaskBoardでは、nginx-configのsubPathマウントとSecretのenv参照、いずれも変更時にはPodの再起動が必要です。実践編第8回で変更管理の手順を設計する際に、kubectl rollout restartを使った安全な再起動手順を整備します。
3.7 Deployment / StatefulSetの詳細パラメータ
3.7.1 strategy(ローリングアップデート戦略)
Deployment(Nginx、TaskBoard API)のアップデート戦略を決めます。
Nginx: RollingUpdateでmaxSurge: 1, maxUnavailable: 0を設定します。maxUnavailableを0にすることで、アップデート中に利用不可のPodが生じないことを保証します。maxSurge: 1により、古いPodが停止する前に新しいPodが1つ起動し、容量を維持します。Nginxは起動が速いため、この設定でアップデートはスムーズに完了します。
TaskBoard API: 同じくRollingUpdateでmaxSurge: 1, maxUnavailable: 0を設定します。Payara Microは起動に15〜20秒かかりますが、startupProbeが起動完了を監視するため、新しいPodが「Ready」になるまで古いPodは停止されません。maxUnavailable: 0により、アップデート中もAPIの可用性を維持します。
MySQL(StatefulSet): StatefulSetのデフォルト戦略はRollingUpdateです。replicasが1のため、strategyの設定による差はありません。アップデート時はmysql-0が停止し、新しいmysql-0が起動します。この間DBは一時的に停止しますが、TaskBoard APIのreadinessProbe(DB接続確認)がDBの復旧を検知し、トラフィックを自動で復元します。
3.7.2 ラベル体系の設計
マニフェスト間の整合性を確保するため、ラベル体系を統一します。応用編を通じて使用してきたラベル体系をそのまま踏襲します。
| ラベルキー | 用途 | 値の例 |
|---|---|---|
app | アプリケーション全体の識別 | taskboard(全リソース共通) |
component | コンポーネントの識別 | frontend / api / db / log-collector |
この2つのラベルを組み合わせることで、Serviceのselector、NetworkPolicyのpodSelector、HPAのscaleTargetRefが正確にPodを選択できます。全マニフェストでこのラベル体系を一貫して使用することが、マニフェスト間の整合性の基盤です。
ラベルの配置場所も統一します。
metadata:
labels: # (1) リソース自体のラベル(検索・フィルタ用)
app: taskboard
component: api
spec:
selector:
matchLabels: # (2) Deployment/StatefulSetがPodを選択するラベル
app: taskboard
component: api
template:
metadata:
labels: # (3) Pod templateのラベル — (2)と一致必須
app: taskboard
component: api
(2)と(3)が一致しないとDeploymentが自分のPodを管理できません。全マニフェストでこの一致を確認します。
3.7.3 PodDisruptionBudgetの値を決める
PDB(PodDisruptionBudget)は、Node drainやクラスタメンテナンス時に「最低何Podを維持するか」を保証するリソースです。基本設計で「フロントとAPIにPDBを設定する」と方針を決めました。ここで具体的な値を決めます。
Nginx: replicas: 2のうち、minAvailable: 1を設定します。これにより、Node drain時に2Podのうち少なくとも1Podは常に稼働していることが保証されます。2Podとも同一Nodeに配置されていた場合でも、1Podが別Nodeに移動してReadyになるまで、もう1Podの停止は待機されます。
TaskBoard API: 同じくminAvailable: 1を設定します。Payara Microは起動に15〜20秒かかるため、PDBの保護がなければdrain時にAPIが一時的に全停止する可能性があります。startupProbeとPDBの組み合わせにより、新しいPodが起動完了するまで古いPodが保護されます。
MySQL: replicas: 1のためPDBは設定しません。replicas: 1でminAvailable: 1のPDBを設定すると、Node drainが永遠にブロックされる可能性があるためです。MySQLのNode退避はサービス断を伴いますが、これはシングルレプリカ構成の制約として基本設計で受容済みです。
3.8 完成マニフェストの全体確認
3.8.1 主要マニフェストの掲載
ここまでの設計を反映した主要マニフェストを掲載します。パラメータ設計書と対照しながら確認してください。補助マニフェスト(Namespace、ResourceQuota、LimitRange、RBAC、NetworkPolicy、Gateway API関連)は応用編で作成済みのものを使用し、変更はありません。第4回〜第6回でそれらも含めて適用します。
■ TaskBoard API Deployment
# taskboard-api-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: taskboard-api
namespace: app
labels:
app: taskboard
component: api
spec:
replicas: 2 # 基本設計: 可用性確保
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # 新Pod 1つを先に起動
maxUnavailable: 0 # 利用不可Podをゼロに維持
selector:
matchLabels:
app: taskboard
component: api
template:
metadata:
labels:
app: taskboard
component: api
spec:
containers:
- name: taskboard-api
image: taskboard-api:2.0.0
imagePullPolicy: Never # kindクラスタ用
ports:
- containerPort: 8080
env:
- name: DB_HOST
value: "mysql-0.mysql-headless.db.svc.cluster.local"
- name: DB_NAME
value: "taskboard"
- name: DB_USER
valueFrom:
secretKeyRef:
name: mysql-secret
key: MYSQL_USER
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: MYSQL_PASSWORD
resources:
requests:
cpu: "200m" # JVM定常時のCPU
memory: "384Mi" # 定常時メモリ280-350Mi + 余裕
limits:
cpu: "500m" # 起動時/スパイク時のCPU
memory: "512Mi" # JVMヒープ+Meta+スレッド+OS
# Probe設計
startupProbe:
httpGet:
path: /health/started
port: 8080
initialDelaySeconds: 0 # 即座にチェック開始
periodSeconds: 2 # 2秒ごとにチェック
timeoutSeconds: 3
failureThreshold: 30 # 最大60秒待ち(2秒×30回)
livenessProbe:
httpGet:
path: /health/live
port: 8080
periodSeconds: 10 # 10秒ごとにチェック
timeoutSeconds: 3
failureThreshold: 3 # 30秒間の無応答で再起動
readinessProbe:
httpGet:
path: /health/ready # DB接続確認含む
port: 8080
periodSeconds: 5 # livenessより高頻度
timeoutSeconds: 3
failureThreshold: 3 # 15秒で除外
successThreshold: 1 # 1回成功で復帰
# SecurityContext
securityContext:
runAsNonRoot: true
runAsUser: 1000 # payaraユーザー
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
volumeMounts:
- name: tmp
mountPath: /tmp
- name: payara-config
mountPath: /opt/payara/config
volumes:
- name: tmp
emptyDir: {}
- name: payara-config
emptyDir: {}
■ Nginx Deployment
# nginx-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
namespace: app
labels:
app: taskboard
component: frontend
spec:
replicas: 2 # 基本設計: 可用性確保
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
app: taskboard
component: frontend
template:
metadata:
labels:
app: taskboard
component: frontend
spec:
containers:
- name: nginx
image: nginx:1.27
ports:
- containerPort: 8080 # 非root対応で80→8080
resources:
requests:
cpu: "50m"
memory: "64Mi"
limits:
cpu: "200m"
memory: "128Mi"
livenessProbe:
httpGet:
path: /
port: 8080
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
readinessProbe:
httpGet:
path: /
port: 8080
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
successThreshold: 1
securityContext:
runAsNonRoot: true
runAsUser: 101 # nginxユーザー
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
volumeMounts:
- name: nginx-config
mountPath: /etc/nginx/nginx.conf
subPath: nginx.conf
readOnly: true
- name: tmp
mountPath: /tmp
- name: cache
mountPath: /var/cache/nginx
volumes:
- name: nginx-config
configMap:
name: nginx-config
- name: tmp
emptyDir: {}
- name: cache
emptyDir: {}
■ MySQL StatefulSet
# mysql-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
namespace: db
labels:
app: taskboard
component: db
spec:
serviceName: mysql-headless # Headless Serviceと紐付け
replicas: 1 # 基本設計: シングルレプリカ
selector:
matchLabels:
app: taskboard
component: db
template:
metadata:
labels:
app: taskboard
component: db
spec:
containers:
- name: mysql
image: mysql:8.0
envFrom:
- secretRef:
name: mysql-secret # 全キーを環境変数として注入
ports:
- containerPort: 3306
name: mysql
resources:
requests:
cpu: "200m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"
livenessProbe:
tcpSocket:
port: 3306
initialDelaySeconds: 15 # 初期化を含む起動時間を考慮
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
readinessProbe:
tcpSocket:
port: 3306
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
successThreshold: 1
securityContext:
runAsNonRoot: true
runAsUser: 999 # mysqlユーザー
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
# readOnlyRootFilesystem: 非適用(設計根拠は3.5.2参照)
volumeMounts:
- name: mysql-data
mountPath: /var/lib/mysql
- name: tmp
mountPath: /tmp
- name: run-mysqld
mountPath: /var/run/mysqld
volumes:
- name: tmp
emptyDir: {}
- name: run-mysqld
emptyDir: {}
volumeClaimTemplates:
- metadata:
name: mysql-data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi # 学習環境のため1GiBで十分
■ Service定義
# taskboard-api-service.yaml
apiVersion: v1
kind: Service
metadata:
name: taskboard-api
namespace: app
labels:
app: taskboard
component: api
spec:
selector:
app: taskboard
component: api
ports:
- port: 8080
targetPort: 8080
protocol: TCP
---
# nginx-service.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx
namespace: app
labels:
app: taskboard
component: frontend
spec:
selector:
app: taskboard
component: frontend
ports:
- port: 8080
targetPort: 8080
protocol: TCP
---
# mysql-headless.yaml(Headless Service)
apiVersion: v1
kind: Service
metadata:
name: mysql-headless
namespace: db
labels:
app: taskboard
component: db
spec:
clusterIP: None # Headless Service
selector:
app: taskboard
component: db
ports:
- port: 3306
targetPort: 3306
name: mysql
■ HPA
# hpa-nginx.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: nginx-hpa
namespace: app
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: nginx
minReplicas: 2 # Deployment.replicasと一致
maxReplicas: 6
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70 # CPU requests(50m)の70%=35m超過でスケール
---
# hpa-taskboard-api.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: taskboard-api-hpa
namespace: app
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: taskboard-api
minReplicas: 2
maxReplicas: 4 # JVMのリソース消費が大きいため控えめ
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
behavior:
scaleUp:
policies:
- type: Pods
value: 1 # 1 Podずつ慎重にスケールアウト
periodSeconds: 60
■ PDB
# pdb-nginx.yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: nginx-pdb
namespace: app
spec:
minAvailable: 1 # 常に最低1 Podを維持
selector:
matchLabels:
app: taskboard
component: frontend
---
# pdb-taskboard-api.yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: taskboard-api-pdb
namespace: app
spec:
minAvailable: 1
selector:
matchLabels:
app: taskboard
component: api
3.8.2 マニフェスト間の整合性チェック
全マニフェストを書き上げたら、以下の観点でクロスチェックを行います。構築フェーズで「applyしたら動かない」を防ぐための最終確認です。
| チェック観点 | 確認内容 | TaskBoardでの確認結果 |
|---|---|---|
| ラベル一致 | Deployment/StatefulSetのselector ↔ Pod templateのlabels | 全リソースで app: taskboard + component: xxx が一致 ✓ |
| Service selector | Serviceのselector ↔ 対象PodのラベルServiceのport ↔ Podのport | taskboard-api Service → component: api、port: 8080 ✓ nginx Service → component: frontend、port: 8080 ✓ mysql-headless → component: db、port: 3306 ✓ |
| Secret参照 | envFrom/env.secretKeyRef → Secret名とキーの存在 | MySQL → mysql-secret(envFrom)✓ TaskBoard API → mysql-secret(DB_USER, DB_PASSWORD)✓ |
| ConfigMap参照 | volumeMount → ConfigMap名とキーの存在 | Nginx → nginx-config ConfigMap、キー: nginx.conf ✓ |
| Namespace | 全リソースが正しいNamespaceに配置されているか | app: Nginx, TaskBoard API, Service, HPA, PDB ✓ db: MySQL, mysql-headless, mysql-secret ✓ |
| ポート番号 | containerPort ↔ Service port ↔ Probeのport | TaskBoard API: 全て8080 ✓ Nginx: 全て8080 ✓ MySQL: 全て3306 ✓ |
| HPA対象 | scaleTargetRef ↔ Deployment名 | nginx-hpa → Deployment/nginx ✓ taskboard-api-hpa → Deployment/taskboard-api ✓ |
| PDB対象 | selector ↔ Podラベル | nginx-pdb → frontend ✓ taskboard-api-pdb → api ✓ |
この整合性チェック表は、構築フェーズ(第4回〜第6回)でも活用します。
3.8.3 パラメータ設計書の最終版
3.3〜3.7で段階的に構築してきたパラメータ設計書を、最終版として統合します。以下は主要パラメータの一覧です。
💬 あなた → AI(Claude):
TaskBoard APIのDeploymentマニフェストを作ってほしい。要件は以下の通り。
– replicas: 2
– イメージ: taskboard-api:2.0.0
– DB接続情報はSecretから取得
– MicroProfile Healthのエンドポイントでヘルスチェック
– 非root実行必須
🤖 Claude の回答:
# AIが生成したドラフト
apiVersion: apps/v1
kind: Deployment
metadata:
name: taskboard-api
namespace: app
labels:
app: taskboard
component: api
spec:
replicas: 2
selector:
matchLabels:
app: taskboard
component: api
template:
metadata:
labels:
app: taskboard
component: api
spec:
containers:
- name: taskboard-api
image: taskboard-api:2.0.0
ports:
- containerPort: 8080
env:
- name: DB_HOST
value: "mysql-0.mysql-headless.db.svc.cluster.local"
- name: DB_NAME
value: "taskboard"
- name: DB_USER
valueFrom:
secretKeyRef:
name: mysql-secret
key: MYSQL_USER
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: MYSQL_PASSWORD
resources:
requests:
cpu: "200m"
memory: "384Mi"
limits:
cpu: "500m"
memory: "512Mi"
startupProbe:
httpGet:
path: /health/started
port: 8080
failureThreshold: 10
periodSeconds: 5
livenessProbe:
httpGet:
path: /health/live
port: 8080
periodSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /health/ready
port: 8080
periodSeconds: 10
failureThreshold: 3
securityContext:
runAsNonRoot: true
runAsUser: 1000
🔍 ここで立ち止まって確認しましょう
AIの出力をそのまま適用する前に、応用編で得た知識を基に以下をチェックしてください。☐ startupProbeの待ち時間は十分か? —
failureThreshold: 10 × periodSeconds: 5 = 最大50秒。一見十分に見えますが、応用編第8回でfailureThreshold: 30, periodSeconds: 2 = 最大60秒を採用した理由を思い出してください。periodSecondsが5秒だと、起動完了の検知が最大5秒遅れます。periodSeconds: 2なら最大2秒で検知できます。起動時間に変動がある環境では、チェック頻度を上げるほうがスムーズです
☐ readinessProbeのperiodSecondsはlivenessと同じでいいか? — AIは両方をperiodSeconds: 10にしています。しかし、readinessProbeはServiceのルーティング制御に使われるため、livenessより高頻度(periodSeconds: 5)にするのが実践的です。DB接続が復旧した際に、素早くトラフィックに復帰させたいためです
☐ SecurityContextは完全か? —runAsNonRootとrunAsUserは設定されていますが、readOnlyRootFilesystem、allowPrivilegeEscalation、capabilitiesが欠落しています。応用編第7回で学んだ4つの設定項目をすべて含めるべきです
☐ emptyDirのマウントは設定されているか? —readOnlyRootFilesystem: trueにする場合、/tmpと/opt/payara/configのemptyDirマウントが必要です。AIのドラフトにはvolumeMounts/volumesセクションがありません
☐ strategyは指定されているか? — ローリングアップデート戦略が未指定です。デフォルトでRollingUpdateになりますが、maxSurge/maxUnavailableを明示的に設定しておくと設計意図が明確になります
☐ imagePullPolicyは設定されているか? — kindクラスタではimagePullPolicy: Neverが必要です
AIの出力は「おおよそ正しい」ですが、応用編で培った知識があれば改善点を見つけられます。特にstartupProbeのパラメータとSecurityContextの完全性は、応用編第7回・第8回を体験していないと気づけない項目です。AIはドラフト生成に有用ですが、最終的な設計判断はあなた自身の経験と知識で行ってください。
補助マニフェスト(Namespace、ResourceQuota、LimitRange、RBAC、NetworkPolicy、Gateway API、CronJob、Job、DaemonSet)は応用編で作成したものをそのまま使用します。変更が必要な項目はありません。第4回〜第6回で適用する際にこれらのマニフェストも含めた全量を掲載します。
3.9 この回のまとめ
3.9.1 成果物の確認 — 全マニフェスト + パラメータ設計書
本回で以下の成果物が完成しました。
- パラメータ設計書: resources(3.3節)、Probe(3.4節)、SecurityContext(3.5節)、ConfigMap/Secret(3.6節)、strategy/ラベル/PDB(3.7節)の各設計領域について、全パラメータの値と設計根拠を文書化
- 主要マニフェスト: TaskBoard API Deployment、Nginx Deployment、MySQL StatefulSet、Service 3種、HPA 2種、PDB 2種
- マニフェスト間整合性チェック表: ラベル、ポート、Secret参照、Namespace、HPA/PDB対象の整合性を確認
全マニフェストのすべてのフィールドに「なぜこの値か」の根拠があります。これが詳細設計の目的でした。
3.9.2 設計フェーズの振り返り(第1回〜第3回)
設計フェーズ全体を振り返ります。3回を通じて、TaskBoardの設計は以下のように具体化されてきました。
| 回 | 成果物 | 抽象度 | 答える問い |
|---|---|---|---|
| 第1回(構成図) | 3階層の構成図 | 高い | 「何があるか」「どうつながっているか」 |
| 第2回(基本設計) | 5領域の基本設計書 | 中程度 | 「何を使うか」「なぜそれを選ぶか」 |
| 第3回(詳細設計) | 全マニフェスト + パラメータ設計書 | 低い(具体的) | 「具体的にどの値を設定するか」「なぜその値か」 |
構成図 → 方針 → パラメータ値。この3段階の具体化プロセスは、VMの世界でも、K8sの世界でも、あるいはこれから出会う新しい技術の世界でも、変わりません。技術は変わっても、設計のプロセスは持ち運べるスキルです。
3.9.3 次回予告 — 設計フェーズから構築フェーズへ
設計フェーズはこれで完了です。手元には構成図、基本設計書、詳細設計書(全マニフェスト + パラメータ設計書)が揃っています。
次回(第4回)からは構築フェーズに入ります。応用編で作成したTaskBoardリソースを一度すべて削除し、設計書に基づいて「白紙から」再構築します。まずは基盤整備——Namespace、ResourceQuota、LimitRange、RBAC、Metrics Server、Gateway APIコントローラーの導入です。今回作成したマニフェストを実際にkubectl applyするのは第5回からですが、基盤が正しく整備されていなければマニフェストは機能しません。
設計を「絵に描いた餅」で終わらせず、動くシステムとして実現する。次回からはその工程です。
AI活用 — マニフェストのドラフト生成と批判的レビュー:対話ログ
本回のAI活用セッションは、3.8.3節に組み込みました。AIにTaskBoard APIのDeploymentマニフェストのドラフト生成を依頼し、応用編で得た知識を基に批判的レビューを行う体験です。AIの出力は「おおよそ正しいが完全ではない」ことが多く、以下の改善点を発見できました。
- startupProbeの
periodSecondsが大きすぎる(5秒 → 2秒に修正)— 応用編第8回の知見 - readinessProbeの
periodSecondsがlivenessと同じ — トラフィック制御にはより高頻度が望ましい - SecurityContextが不完全 —
readOnlyRootFilesystem、allowPrivilegeEscalation、capabilitiesが欠落 — 応用編第7回の知見 - emptyDirマウントが未設定 —
readOnlyRootFilesystem: trueとの組み合わせに必須 imagePullPolicy: Neverが未設定 — kind環境固有の設定
AIは設計のたたき台を素早く作るのに適しています。しかし、パラメータの「最適値」と「設計根拠」は、自分の経験と知識で判断する必要があります。応用編で手を動かして得た体験こそが、AIの出力を検証するための最強の武器です。
