アプリ配備の骨格
第21章で立ち上げた空の EKS クラスタの上に、サンプルサービス myshop-api を一つのまとまりのマニフェストで配備します。Namespace · ServiceAccount · ConfigMap · Secret · Deployment · Service · Ingress · HPA · PodDisruptionBudget の9つのオブジェクトを一連の流れで整理し、AWS Load Balancer Controller で ALB を自動プロビジョニングします。そのまとまりを Helm チャートで抽象化して dev / prod に異なる values で適用する方法までを一度にたどります。
第21章 EKS クラスタセットアップ で空の EKS クラスタ一台が準備できました。ノードは立ち上がっていてシステム Pod (CoreDNS, kube-proxy, VPC CNI, EBS CSI) は生きていますが、私たちのワークロードは一行もその上に載っていない状態です。本章はその空いた場所を埋めます。サンプルのバックエンドサービス myshop-api を9つのマニフェストに整理し、AWS Load Balancer Controller で ALB を自動プロビジョニングして外部の入り口を作り、そのすべてのマニフェストを Helm チャートで束ねて dev / prod の二環境に異なる values で配備されるようにします。
本章の終わりでは myshop-api が外部から HTTPS でアクセス可能な状態になります。データストアがないので /health/ready だけが 200 を返す空の殻ですが、その空いた部分は次の 第23章 で RDS · External Secrets で埋めます。
myshop-api の標準マニフェストのまとまり #
運用ワークロード一つを K8s に載せる標準のまとまりを一つの表に整理します。
| オブジェクト | 役割 | 本書の出所 |
|---|---|---|
Namespace | ワークロードを束ねる隔離単位 | 第7章 |
ServiceAccount | Pod の ID + IRSA の付着点 | 第16章 |
ConfigMap | 環境設定値 (ログレベル、feature flag) | 第6章 |
Secret | DB パスワードなど | 第6章 |
Deployment | 実際のワークロード (Pod replicas) | 第4章 |
Service | クラスタ内部の仮想 IP | 第5章 |
Ingress | 外部の入り口 (ALB に解決される) | 第10章 |
HorizontalPodAutoscaler | CPU ベースの Pod 自動調整 | 第13章 |
PodDisruptionBudget | 自発的 disruption の可用性の下限線 | 第13章 |
1 ~ 3部でオブジェクトの次元で分けて扱ったモデルが、この一つの表の中に集まります。本章はそのモデルを 一つのまとまりの運用マニフェストへ組み立てる段階 です。各オブジェクトの単独の使い方よりも、まとまりとして組み立てたときの環境変数の注入方式に集中します。
Namespace と ServiceAccount #
apiVersion: v1
kind: Namespace
metadata:
name: myshop
labels:
name: myshop
team: platform
env: prod
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: myshop-api
namespace: myshop
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/myshop-prod-apiNamespace のラベルは 第7章 Namespace とラベル で扱った標準です。team / env / cost-center のようなラベルを一貫して付けておくと、第25章 モニタリング · アラート の Prometheus がワークロードをグルーピングするときにそのまま活用され、第28章 コスト最適化 のコスト追跡でも同じラベルをキーに使います。
ServiceAccount の IRSA annotation は 第16章 RBAC / ServiceAccount 深掘り のその annotation です。書かれた IAM Role の権限を、myshop-api Pod がこの ServiceAccount で動作しながら自動で受け取ります。Role 自体の定義は第21章で整理した Terraform パターンで管理します。
module "myshop_api_irsa" {
source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"
version = "~> 5.0"
role_name = "myshop-prod-api"
oidc_providers = {
main = {
provider_arn = var.oidc_provider_arn
namespace_service_accounts = ["myshop:myshop-api"]
}
}
role_policy_arns = {
secrets = aws_iam_policy.myshop_secrets_read.arn
}
}この Terraform が作った Role の ARN を ServiceAccount の annotation に書きます。以降、Pod 内の AWS SDK はこの Role の権限で Secrets Manager · S3 · CloudWatch を呼べます。namespace_service_accounts の trust 制約が、myshop ネームスペースの myshop-api ServiceAccount だけがこの Role を取得できるように束ねるセキュリティ境界です。
ConfigMap と Secret #
apiVersion: v1
kind: ConfigMap
metadata:
name: myshop-api
namespace: myshop
data:
LOG_LEVEL: "info"
FEATURE_NEW_CHECKOUT: "true"
CACHE_TTL_SECONDS: "300"
AWS_REGION: "ap-northeast-2"
---
apiVersion: v1
kind: Secret
metadata:
name: myshop-api
namespace: myshop
type: Opaque
stringData:
DATABASE_URL: "postgresql://will-be-replaced-by-external-secrets"Secret の実際の値はマニフェストに置きません。第23章 DB 連携 で External Secrets Operator が AWS Secrets Manager の秘密を K8s Secret へ自動同期する方式を扱います。本章では骨格だけを固めておき、第20章 GitOps の秘密管理モデルと併せて 第24章 CI / CD パイプライン で改めて組み合わせます。
Deployment — Pod replicas #
apiVersion: apps/v1
kind: Deployment
metadata:
name: myshop-api
namespace: myshop
labels:
app.kubernetes.io/name: myshop-api
app.kubernetes.io/component: backend
spec:
replicas: 3
selector:
matchLabels:
app.kubernetes.io/name: myshop-api
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app.kubernetes.io/name: myshop-api
app.kubernetes.io/component: backend
spec:
serviceAccountName: myshop-api
containers:
- name: api
image: 123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/myshop-api:1.4.2
ports:
- containerPort: 8000
name: http
envFrom:
- configMapRef:
name: myshop-api
- secretRef:
name: myshop-api
resources:
requests:
cpu: "200m"
memory: "256Mi"
limits:
cpu: "1"
memory: "512Mi"
readinessProbe:
httpGet:
path: /health/ready
port: http
initialDelaySeconds: 5
periodSeconds: 5
livenessProbe:
httpGet:
path: /health/live
port: http
initialDelaySeconds: 30
periodSeconds: 10
startupProbe:
httpGet:
path: /health/live
port: http
failureThreshold: 30
periodSeconds: 10
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app.kubernetes.io/name: myshop-api運用マニフェストの標準要素が集まっています。それぞれが本書のどの章で解かれたかを押さえると次のとおりです。
strategy.rollingUpdate— 第4章 Deployment / ReplicaSet のローリングアップデート。maxUnavailable: 0で新バージョンが十分に立ち上がってからでないと旧バージョンを下げない、無停止の基本的な保護です。envFrom— 第6章 ConfigMap · Secret のまとまった注入。ConfigMap と Secret のすべてのキーを一度に環境変数として展開してくれます。- 三つの probe — 第12章 ヘルスチェック のその三つ。readiness は即時 (5秒後に開始)、liveness は保守的に (30秒後に開始)、startup は初期化が長い場合の猶予です。
resources— 第11章 リソース要求と上限 の requests / limits。今回のワークロードは requests < limits の Burstable QoS クラスに属します。topologySpreadConstraints— Pod を複数の AZ に分散。AZ 一つが落ちても別の AZ の Pod が生き残ります。app.kubernetes.io/標準ラベル — 第7章 のその標準。selector · モニタリング · ロギングがすべてこのラベルでグルーピングされます。
Service — クラスタ内部の固定の入り口 #
apiVersion: v1
kind: Service
metadata:
name: myshop-api
namespace: myshop
labels:
app.kubernetes.io/name: myshop-api
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: myshop-api
ports:
- name: http
port: 80
targetPort: http
protocol: TCPtype: ClusterIP が核心です。Service 自体はクラスタ内部からのみアクセス可能な仮想 IP であり、外部公開は次に作る Ingress が担当します。第5章 Service で扱ったそのモデルそのままです。アプリの外部公開は Service ではなく Ingress で決める — この分離が運用クラスタの標準パターンで、内部通信と外部公開の質感を別のレイヤーに束ねるセキュリティ · 運用の出発点です。
AWS Load Balancer Controller — Ingress の ALB マッピング #
EKS の Ingress は K8s 標準の Ingress オブジェクトですが、そのオブジェクトを実際の ALB (AWS Application Load Balancer) に解決するには AWS Load Balancer Controller というコンポーネントがクラスタにインストールされている必要があります。第10章 Ingress の §「クラウド別 Ingress Controller」で触れたそのコンポーネントが、本格的なセットアップ段階で扱われます。
helm repo add eks https://aws.github.io/eks-charts
helm repo update
helm install aws-load-balancer-controller eks/aws-load-balancer-controller \
-n kube-system \
--set clusterName=myshop-prod \
--set serviceAccount.create=false \
--set serviceAccount.name=aws-load-balancer-controllerServiceAccount はあらかじめ IRSA で作っておきます (第21章 の EBS CSI IRSA マニフェストと同じパターン)。この controller がクラスタの中で Ingress オブジェクトを watch していて、Ingress が作られるとそれに合う ALB を AWS API でプロビジョニングします。Controller は K8s と AWS の二つの質感をつなぐブリッジ という位置を覚えておくとよいです — 同じパターンが 第23章 の External Secrets Operator、第25章 の CloudWatch Container Insights でも繰り返されます。
Ingress マニフェスト #
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myshop-api
namespace: myshop
annotations:
kubernetes.io/ingress.class: alb
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}]'
alb.ingress.kubernetes.io/ssl-redirect: '443'
alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:ap-northeast-2:123456789012:certificate/abc-123
alb.ingress.kubernetes.io/healthcheck-path: /health/ready
external-dns.alpha.kubernetes.io/hostname: api.myshop.example.com
spec:
rules:
- host: api.myshop.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myshop-api
port:
number: 80annotation 一つずつの意味を整理します。
scheme: internet-facing— インターネット公開の ALB です。内部用はinternalにします。target-type: ip— Pod IP を ALB のターゲットグループに直接登録します。ALB が Pod IP の変動を watch する流れです。listen-ports/ssl-redirect— HTTPS だけを受けて HTTP は自動で redirect します。certificate-arn— ACM 証明書。Route 53 と関連するドメインの証明書を ACM で発行しておきます。healthcheck-path— ALB が各 Pod に送るヘルスチェックのパス。第12章 の readiness probe と同じパスに合わせるのが一般的です。external-dns.alpha.kubernetes.io/hostname— external-dns コンポーネントがこの ALB の DNS を Route 53 の A レコードとして自動登録します。
このマニフェストを適用した時点から約 1 ~ 2分後に、AWS コンソールで新しい ALB が立ち上がっていて、api.myshop.example.com ドメインがその ALB へつながっている形が出来上がります。
HPA — トラフィックに応じた Pod 自動調整 #
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: myshop-api
namespace: myshop
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: myshop-api
minReplicas: 3
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60
behavior:
scaleUp:
stabilizationWindowSeconds: 30
scaleDown:
stabilizationWindowSeconds: 300CPU 使用率が平均 60 % を超えると Pod 数を増やし、下がれば減らします。第13章 オートスケーリング のそのモデルそのままです。behavior の stabilization ウィンドウが運用の要 — scale-up は30秒で速く、scale-down は5分で保守的に 置く非対称が核心です。トラフィックスパイクには素早く対応しながらも、一時的な dip で Pod が上下に揺れて減らないようにする質感です。
PodDisruptionBudget — 可用性の下限線 #
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: myshop-api
namespace: myshop
spec:
minAvailable: 2
selector:
matchLabels:
app.kubernetes.io/name: myshop-apiこの一枚が何を防ぐのか — ノードアップグレード / ノードドレイン / Cluster Autoscaler のノード回収のような 自発的 disruption 時に、myshop-api Pod が同時に2個未満に落ちないことを保証します。K8s 運用の定番の事故 — 「EKS アップグレード中に Pod が一度に全部下がってダウンタイムが発生」を防ぐツールです。第26章 運用チェックリスト のクラスタアップグレード手順で、PDB がなぜ必須かが本格的に解かれます。
PDB の核心は 自発的 (voluntary) という限定です。ノードの突然の障害のような 非自発的 disruption は PDB では防げません — その質感は topologySpreadConstraints と multi-AZ ノードグループが担当します。
Helm チャートで束ねる #
ここまで書いた9枚のマニフェストはそれ自体でも動作します。しかし dev / prod に同じワークロードを異なる値で配備するには、マニフェストを環境ごとに複製する負担が生じます。この地点で Helm が入ってきます。第18章 CRD と Operator の §「Helm チャートで束ねる」で触れた Helm の位置が、本格的な運用チャートとして扱われる段階です。
Chart 構造 #
charts/myshop-api/
├── Chart.yaml
├── values.yaml # デフォルト値
├── values-dev.yaml # dev override
├── values-prod.yaml # prod override
└── templates/
├── _helpers.tpl
├── namespace.yaml
├── serviceaccount.yaml
├── configmap.yaml
├── secret.yaml
├── deployment.yaml
├── service.yaml
├── ingress.yaml
├── hpa.yaml
└── pdb.yamlChart.yaml #
apiVersion: v2
name: myshop-api
description: myshop API service
type: application
version: 0.1.0
appVersion: "1.4.2"
maintainers:
- name: platform-team
email: platform@myshop.example.comversion がチャート自体のバージョン、appVersion がそのチャートが配備するアプリケーションのバージョンです。二つが分離される理由 — チャートマニフェストはそのままでイメージタグだけを上げる流れが多いためです。
values.yaml — デフォルト値 #
image:
repository: 123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/myshop-api
tag: "1.4.2"
pullPolicy: IfNotPresent
replicaCount: 3
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: 1
memory: 512Mi
serviceAccount:
create: true
name: myshop-api
irsaRoleArn: ""
config:
LOG_LEVEL: info
FEATURE_NEW_CHECKOUT: "true"
CACHE_TTL_SECONDS: "300"
ingress:
enabled: true
host: api.myshop.example.com
certificateArn: ""
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 20
targetCPUUtilizationPercentage: 60
pdb:
enabled: true
minAvailable: 2values-prod.yaml — 環境別 override #
replicaCount: 5
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: 2
memory: 1Gi
serviceAccount:
irsaRoleArn: arn:aws:iam::123456789012:role/myshop-prod-api
config:
LOG_LEVEL: warn
ingress:
host: api.myshop.example.com
certificateArn: arn:aws:acm:ap-northeast-2:123456789012:certificate/abc-123
autoscaling:
minReplicas: 5
maxReplicas: 50Template — Deployment 一枚の例 #
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "myshop-api.fullname" . }}
labels:
{{- include "myshop-api.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "myshop-api.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "myshop-api.selectorLabels" . | nindent 8 }}
spec:
serviceAccountName: {{ .Values.serviceAccount.name }}
containers:
- name: api
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
envFrom:
- configMapRef:
name: {{ include "myshop-api.fullname" . }}
- secretRef:
name: {{ include "myshop-api.fullname" . }}
resources:
{{- toYaml .Values.resources | nindent 12 }}{{ include "myshop-api.fullname" . }} のような Helper は _helpers.tpl に定義しておきます。Helm チャートの標準パターンで、helm create で骨格を作ると自動で入ってきます。
配備 — 同じチャート、異なる環境 #
helm upgrade --install myshop-api charts/myshop-api \
-n myshop --create-namespace \
-f charts/myshop-api/values.yaml \
-f charts/myshop-api/values-dev.yamlhelm upgrade --install myshop-api charts/myshop-api \
-n myshop --create-namespace \
-f charts/myshop-api/values.yaml \
-f charts/myshop-api/values-prod.yaml同じチャートが環境別に異なって適用されます。この一つのコマンドは 第24章 CI / CD パイプライン の ArgoCD Application sync に置き換えられますが、開発段階の手動配備にはそのまま有効です。第20章 GitOps の git 単一ソースモデルが、この Helm チャートのディレクトリへ自然につながります。
cert-manager と external-dns — 付加コンポーネント #
ACM 証明書を直接発行 · 更新する代わりに、クラスタの中で Let’s Encrypt 証明書を自動で受け取りたいなら cert-manager、Route 53 の DNS レコードをマニフェストで自動管理したいなら external-dns を併せて導入します。
helm repo add jetstack https://charts.jetstack.io
helm install cert-manager jetstack/cert-manager \
-n cert-manager --create-namespace \
--set installCRDs=truehelm install external-dns external-dns/external-dns \
-n external-dns --create-namespace \
--set provider=aws \
--set aws.region=ap-northeast-2 \
--set "domainFilters[0]=myshop.example.com"運用クラスタの標準セットアップです。ドメインが追加されるたびに Route 53 コンソールを開かず、マニフェストの annotation 一行で終わる流れが出来上がります。ACM 証明書を使うときは cert-manager は必要ありませんが、マルチクラスタ · マルチクラウド環境では cert-manager の方が一貫します。二つのコンポーネントの ServiceAccount も 第21章 の EBS CSI IRSA と同じパターンで IAM Role を受け取らないと、Route 53 のレコードを変更できません。
最初の配備後の点検 #
配備直後に点検する項目です。
kubectl get all -n myshop
kubectl get hpa -n myshop
kubectl get pdb -n myshop
kubectl get ingress -n myshopkubectl describe ingress myshop-api -n myshop | grep "Address"curl -i https://api.myshop.example.com/health/readyこの四段階で、Pod · HPA · Ingress · 外部からの入り口までが動作する状態かが確認されます。200 応答が返れば、クラスタの最初のワークロードが運用に入った時点です。もし ALB の Address が空だったり 503 が返ったりしたら、第27章 kubectl デバッグパターン の Ingress デバッグ節を先に見ておくと役に立ちます。
練習問題 #
- 本章の9つのマニフェストを一つのディレクトリに展開しておき、第21章 で立ち上げた dev EKS クラスタに順に適用します。
kubectl get all -n myshopの出力で、Deployment が 3 / 3、ReplicaSet が同一、Pod がすべて Running、Service の ClusterIP が割り当てられたところまで確認します。Ingress の ALB Address が立ち上がるまで約 1 ~ 2分かかりますが、この時間のあいだに AWS コンソールで ALB · TargetGroup · Route 53 の A レコードが順に作られる様子を併せて観察します。 - 本章の9つのマニフェストを Helm チャート一つのまとまりに変換します。
helm create myshop-apiで骨格を作ったあと、values.yaml·values-dev.yaml·values-prod.yamlの三つのファイルがどう分かれるかを整理し、dev の replicaCount と prod の replicaCount が異なって適用されるかをhelm templateで事前検証します。この事前検証が 第24章 CI / CD の ArgoCD diff モデルとどうつながるかを一段落でメモします。 topologySpreadConstraintsのwhenUnsatisfiableをScheduleAnywayとDoNotScheduleの二つに変えながら、Pod の分布がどう変わるかを観察します。AZ が二つだけなのに replicaCount が3の場合、どのオプションがどんなトレードオフを作るかを、ご自身の運用シナリオに照らして一段落で比較します。第21章 の VPC モジュールでの単一 AZ vs マルチ AZ の決定が、このオプションの結果をどう揺らすかも併せて押さえます。
一行まとめ: 運用ワークロード一つの標準は Namespace · ServiceAccount · ConfigMap · Secret · Deployment · Service · Ingress · HPA · PodDisruptionBudget の9つのオブジェクトであり、このまとまりを Helm チャートで抽象化すれば、同じチャートが dev / prod に異なる values で展開される。EKS の Ingress は AWS Load Balancer Controller が ALB として実際に解決し、ACM 証明書 + Route 53 + external-dns がドメイン · TLS · DNS の三つの質感をマニフェストの annotation 一行で束ねる。
次の章 #
この時点で myshop-api は外部から HTTPS でアクセス可能な状態ですが、データストアがなく /health/ready だけが 200 を返す空の殻です。次の章ではその空いた部分を埋めます。
第23章 DB 連携 では、RDS PostgreSQL との接続、Secrets Manager で DB パスワードを安全に注入する道、External Secrets Operator で K8s Secret とクラウドの秘密ストアを同期する流れ、そしてコネクションプールの運用原則までを一つのサイクルで扱います。