plan-4ging

2026/04/06

はじめに

ざっくりイメージだけまとめると下記の通り

Terraform:インフラを管理する
 → 「どんなサーバー・ネットワークを用意するか」
Kubernetes:コンテナを管理する
 → 「どのようにアプリを動かし続けるか」

Terraform とは

概要

IaC(Infrastructure as Code)ツール
AWS/GCP/Azure などのクラウドリソースをHCL(HashiCorp Configuration Language)というコードで定義・管理する

手動によるインフラ構築

  • GUI でポチポチ作った設定は再現性がない
  • 「どんな設定で作ったか」が記録に残らない
  • 本番と同じ環境を開発・ステージングに再現するのが難しい
  • チームでの変更管理ができない

Terraform によるインフラ構築

  • インフラの定義をコードとして Git で管理できる
  • applyコマンド一発で同じ環境を何度でも再現できる
  • 変更の差分がplanコマンドで事前に確認できる
  • チームでレビュー・承認フローを組み込める

基本構文(HCL)

下記一例

# main.tf

# プロバイダー設定(AWS を使用する場合)
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }

  # 状態ファイル(tfstate)をリモートで管理する
  backend "s3" {
    bucket = "my-terraform-state"
    key    = "production/terraform.tfstate"
    region = "ap-northeast-1"
  }
}

provider "aws" {
  region = "ap-northeast-1"
}

# 変数定義
variable "environment" {
  type        = string
  description = "デプロイ環境"
  default     = "production"
}
variable "app_name" {
  type    = string
  default = "myapp"
}

# ローカル変数
locals {
  common_tags = {
    Environment = var.environment
    ManagedBy   = "Terraform"
    App         = var.app_name
  }
}

# リソース定義(VPC)
resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = merge(local.common_tags, {
    Name = "${var.app_name}-vpc"
  })
}

# サブネット定義
resource "aws_subnet" "public" {
  count             = 2  # 2つ作成(AZ 冗長化)
  vpc_id            = aws_vpc.main.id  # 上で作った VPC を参照
  cidr_block        = "10.0.${count.index + 1}.0/24"
  availability_zone = data.aws_availability_zones.available.names[count.index]

  map_public_ip_on_launch = true

  tags = merge(local.common_tags, {
    Name = "${var.app_name}-public-${count.index + 1}"
  })
}

# データソース
data "aws_availability_zones" "available" {
  state = "available"
}

# アウトプット(他の設定から参照できる値)
output "vpc_id" {
  value       = aws_vpc.main.id
  description = "作成した VPC の ID"
}

output "public_subnet_ids" {
  value = aws_subnet.public[*].id
}

基本コマンド

terraformコマンドを使用する

# 初期化
terraform init

# 構文チェック・フォーマット
terraform fmt        # コードを整形する
terraform validate   # 構文を検証する

# 変更のプレビュー(何が変わるか事前確認)
terraform plan
terraform plan -out=tfplan  # プランをファイルに保存する

# 変更の適用
terraform apply
terraform apply tfplan  # 保存したプランを適用する

# 現在の状態を確認
terraform show
terraform state list   # 管理しているリソース一覧

# リソースの削除
terraform destroy      # 全削除
terraform destroy -target=aws_db_instance.main  # 特定のリソースのみ削除

# State の操作
terraform state mv    # リソースの移動・名前変更
terraform state rm    # State からリソースを除外
terraform import      # 既存リソースを Terraform 管理下に取り込む

ディレクトリ構成

下記一例

infrastructure/
├── modules/              ← 再利用可能なモジュール
│   ├── vpc/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   └── outputs.tf
│   ├── ecs/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   └── outputs.tf
│   └── rds/
│       ├── main.tf
│       ├── variables.tf
│       └── outputs.tf

├── environments/         ← 環境ごとの設定
│   ├── production/
│   │   ├── main.tf       ← モジュールを呼び出す
│   │   ├── variables.tf
│   │   ├── terraform.tfvars  ← 環境ごとの変数値
│   │   └── backend.tf
│   └── staging/
│       ├── main.tf
│       └── terraform.tfvars
# environments/production/main.tf
# モジュールを呼び出す

module "vpc" {
  source      = "../../modules/vpc"
  environment = var.environment
  app_name    = var.app_name
  cidr_block  = "10.0.0.0/16"
}

module "rds" {
  source      = "../../modules/rds"
  environment = var.environment
  vpc_id      = module.vpc.vpc_id       # VPC モジュールのアウトプットを渡す
  subnet_ids  = module.vpc.private_subnet_ids
  db_password = var.db_password
}

Kubernetes(k8s)とは

概要

Google が開発し OSS 化したコンテナオーケストレーションシステム
複数のコンテナを自動でデプロイ・スケール・復旧・管理する

コンテナを手動で管理する場合

  • コンテナが落ちたとき手動で再起動が必要
  • トラフィックが増えたとき手動でコンテナを増やす必要がある
  • 複数サーバーへのコンテナ配置を手動で管理するのが困難
  • デプロイ時のダウンタイムを避けるのが難しい

コンテナを Kubernetes で管理する場合

  • コンテナが落ちると自動で再起動する(自己修復)
  • CPU 使用率などに応じて自動でスケールアウト/イン
  • 複数ノードへのコンテナ配置を自動で最適化
  • ローリングアップデートでダウンタイムなしデプロイ

クラスター構成

コントロールプレーン(管理層、Control Plane)
├── API Server    ← kubectl などからの操作を受け付ける
├── Scheduler     ← Pod をどのノードに配置するか決定する
├── Controller Manager ← 状態の監視・自己修復
└── etcd          ← クラスターの全状態を保存する KV ストア

ワーカーノード(実行層、Worker Node)× N 台
├── kubelet       ← API Server と通信・コンテナの管理
├── kube-proxy    ← ネットワークルールの管理
└── Container Runtime(containerd / Docker)
    └── Pod(コンテナの実行単位)

主要コンポーネント

Pod

コンテナの最小デプロイ単位
通常は直接 Pod を作らず Deployment 経由で管理する

apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod
  labels:
    app: myapp
spec:
  containers:
    - name: myapp
      image: myapp:1.0.0
      ports:
        - containerPort: 3000
      env:
        - name: DATABASE_URL
          valueFrom:
            secretKeyRef:
              name: myapp-secrets    # Secret から参照
              key: database_url
      resources:
        requests:
          memory: "128Mi"
          cpu:    "100m"   # 0.1 vCPU
        limits:
          memory: "256Mi"
          cpu:    "500m"   # 0.5 vCPU
      livenessProbe:       # 生存確認(失敗したら再起動)
        httpGet:
          path: /health
          port: 3000
        initialDelaySeconds: 10
        periodSeconds:       10
      readinessProbe:      # 準備完了確認(失敗したらトラフィックを送らない)
        httpGet:
          path: /ready
          port: 3000
        initialDelaySeconds: 5
        periodSeconds:       5

Deployment

Pod の宣言的な管理、ローリングアップデート

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-deployment
  namespace: production
spec:
  replicas: 3           # 常に 3つの Pod を維持する
  selector:
    matchLabels:
      app: myapp
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge:       1   # 更新中に最大 1つ余分に起動してよい
      maxUnavailable: 0   # 更新中に停止する Pod は 0(ダウンタイムなし)
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
        - name: myapp
          image: myapp:1.2.0
          ports:
            - containerPort: 3000
          resources:
            requests:
              memory: "256Mi"
              cpu:    "250m"
            limits:
              memory: "512Mi"
              cpu:    "1000m"
          livenessProbe:
            httpGet:
              path: /health
              port: 3000
            failureThreshold: 3
            periodSeconds:    10
          readinessProbe:
            httpGet:
              path: /ready
              port: 3000
            failureThreshold: 3
            periodSeconds:    5

Service

Pod へのネットワークアクセスを提供する
Pod は再起動のたびに IP が変わるため Service を経由してアクセスする

# ClusterIP(クラスター内部からのみアクセス)
apiVersion: v1
kind: Service
metadata:
  name: myapp-service
  namespace: production
spec:
  type: ClusterIP
  selector:
    app: myapp       # このラベルを持つ Pod に転送する
  ports:
    - port:       80   # Service のポート
      targetPort: 3000 # Pod のポート

---
# LoadBalancer(外部からアクセス・クラウドの LB を自動作成)
apiVersion: v1
kind: Service
metadata:
  name: myapp-lb
spec:
  type: LoadBalancer
  selector:
    app: myapp
  ports:
    - port:       80
      targetPort: 3000

Ingress

HTTP/HTTPS のルーティングを定義する
ホスト名・パスに応じて複数の Service に振り分ける

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: myapp-ingress
  annotations:
    kubernetes.io/ingress.class: nginx
    cert-manager.io/cluster-issuer: letsencrypt-prod  # TLS 自動発行
    nginx.ingress.kubernetes.io/proxy-body-size: "10m"
spec:
  tls:
    - hosts:
        - app.example.com
      secretName: myapp-tls
  rules:
    - host: app.example.com
      http:
        paths:
          - path: /api
            pathType: Prefix
            backend:
              service:
                name: api-service
                port:
                  number: 80
          - path: /
            pathType: Prefix
            backend:
              service:
                name: frontend-service
                port:
                  number: 80

ConfigMap

機密でない設定値を管理する

apiVersion: v1
kind: ConfigMap
metadata:
  name: myapp-config
data:
  APP_ENV: "production"
  LOG_LEVEL: "info"
  REDIS_HOST: "redis-service"

Secret

機密情報を管理する(Base64 エンコード)

apiVersion: v1
kind: Secret
metadata:
  name: myapp-secrets
type: Opaque
stringData: # stringData は自動で Base64 エンコードされる
  DATABASE_URL: "postgresql://user:pass@db-host:5432/myapp"
  JWT_SECRET:   "your-secret-key"
  # ※ 実際には外部シークレット管理(AWS Secrets Manager 等)との連携を推奨

HPA(Horizontal Pod Autoscaler)

負荷に応じて Pod 数を自動でスケールする

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: myapp-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: myapp-deployment
  minReplicas: 2    # 最小 Pod 数
  maxReplicas: 20   # 最大 Pod 数
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: AverageUtilization
          averageUtilization: 70   # CPU 使用率が 70% を超えたらスケールアウト
    - type: Resource
      resource:
        name: memory
        target:
          type: AverageValue
          averageValue: 400Mi   # メモリ使用量が 400Mi を超えたらスケールアウト

基本コマンド

kubectlコマンドを使用する

# リソースの確認
kubectl get pods -n production          # Pod 一覧
kubectl get deployments -n production   # Deployment 一覧
kubectl get services -n production      # Service 一覧
kubectl get all -n production           # 全リソース

# 詳細確認
kubectl describe pod myapp-pod-xxxxx -n production  # Pod の詳細
kubectl logs myapp-pod-xxxxx -n production          # ログの確認
kubectl logs -f myapp-pod-xxxxx                     # ログをリアルタイムで追う

# デプロイ操作
kubectl apply -f deployment.yaml          # マニフェストを適用する
kubectl rollout status deployment/myapp   # デプロイの進捗確認
kubectl rollout history deployment/myapp  # デプロイ履歴
kubectl rollout undo deployment/myapp     # 1つ前のバージョンに戻す

# スケール操作
kubectl scale deployment myapp --replicas=5  # Pod 数を 5 に変更する

# デバッグ
kubectl exec -it myapp-pod-xxxxx -- /bin/sh  # Pod の中に入る

2つの違い・役割分担

Terraform の役割

「インフラの箱」を作る

- VPC・サブネット・セキュリティグループ
- EKS クラスター本体(ノードグループ・IAM)
- RDS・ElastiCache・S3
- Route53・ACM(TLS 証明書)
- CloudFront・WAF
- ECR(コンテナイメージレジストリ)

Kubernetes の役割

箱の中でアプリを”安定/持続的”に動かす

- アプリのデプロイ(Deployment)
- ネットワーク(Service・Ingress)
- 設定・シークレット(ConfigMap・Secret)
- 自動スケール(HPA)
- バッチ処理(Job・CronJob)
- Pod のヘルスチェック・自己修復

併用例

① Terraform で EKS クラスターを作成する
  → VPC・サブネット・EKS・ノードグループ・IAM を定義

② Terraform で周辺インフラを作成する
  → RDS・ElastiCache・S3・ALB・Route53

③ Terraform でアプリが使う IAM ロールを作成する
  → IRSA(IAM Roles for Service Accounts)

④ Kubernetes でアプリをデプロイする
  → Deployment・Service・Ingress・HPA を apply

⑤ CI/CD で自動化する
  → インフラ変更:terraform plan → apply
  → アプリデプロイ:docker build → push → kubectl apply

選定基準

Terraform を使うタイミング

  • 新しいクラウドインフラを構築するとき
  • 既存の手動構築環境を IaC に移行するとき
  • 本番と同じ構成のステージング環境を作るとき
  • マルチクラウド・マルチアカウント管理が必要なとき
  • インフラの変更履歴を Git で管理したいとき

Kubernetes を使うタイミング

  • 複数のマイクロサービスを運用するとき
  • 自動スケールアウトが必要なとき
  • ダウンタイムなしのデプロイが必要なとき
  • 複数チームが独立したデプロイを行うとき
  • バッチ処理をコンテナで定期実行したいとき

注意点

Terraform

tfstateの破損・競合

  • 複数人が同時にapplyするとステートが競合する
  • DynamoDB でのステートロックを設定する
  • CI/CD 経由でのみapplyを実行するルールにする

本番環境への誤destroy

  • destroyは全リソースを削除する
  • 本番環境ではdeletion_protectionを有効化する
  • CI/CD の権限でdestroyを実行できないようにする
  • planの確認を必須化する

シークレットの管理

  • AWS Secrets Manager / Vault から動的に取得する
  • tfstateのバケットを暗号化・アクセス制限する

ドリフト(実態とコードのズレ)

  • 手動でコンソールからリソースを変更するとtfstateと実態がズレる
  • コンソールでの手動変更を禁止するルールを作る
  • planを定期実行してドリフトを検知する

Kubernetes

リソース制限の未設定

  • resources.requests/limitsを設定しないと、1つの Pod がノードのリソースを使い切ってしまう
  • 必ずrequests/limitsを設定する
  • LimitRangeでネームスペース単位のデフォルト値を設定する

ヘルスチェックの未設定

  • livenessProbe/readinessProbeを設定しないと、起動中の Pod にトラフィックが流れてエラーになる
  • 必ず両方設定する
  • /health(生存確認)と/ready(準備確認)は別エンドポイントにする

Secrets の管理

  • applyでシークレットを YAML ファイルで管理すると、Base64 エンコードされただけで Git に上がってしまう
  • External Secrets Operator + AWS Secrets Manager を使う
  • Sealed Secrets で暗号化して Git に保存する

1つの Namespace に全リソースを詰め込む

  • default namespace に全リソースを入れると管理が困難になる
  • サービス・環境ごとに namespace を分ける
    • 例:production / staging / monitoring

PodDisruptionBudget の未設定

まとめ

TerraformKubernetes
役割インフラ管理コンテナ管理
言語HCLYAML
操作対象クラウドリソース全般コンテナ・Pod・Service 等
状態管理tfstateetcd

参考