Published on

ハンズオンメモ Google Cloud Kubernetes Engine (GKE)

はじめに

この記事は以下書籍の勉強メモです。

知ったことメモ

自動モードVPCネットワークの有効化設定

resource "google_compute_network" "vpc" {
    name = "gke-network"
    auto_create_subnetworks = false # これ
}
  • この設定により、サブネットを自動で作るか手動で作るかを決定する。
  • trueにした時(=自動で作る)の挙動
    • 全リージョンにサブネットを自動作成する。
    • 各サブネットにはGoogleがあらかじめ定義した10.128.0.0/9 ブロック内のIPは荷が自動的に割り当てられる。
  • 自動モード→カスタムモード(falseにした時)へ変更できるが、逆はできない。
  • これ勉強以外で自動モードにする場面ってあるのか?

プライベートGoogleアクセス

resource "google_compute_subnetwork" "subnet" {
  name                     = "gke-subnet"
  region                   = "asia-northeast1"
  network                  = google_compute_network.vpc.id
  ip_cidr_range            = "10.0.0.0/24"
  private_ip_google_access = true # これ
}
  • プライベートGoogleアクセスは、外部IPアドレスを持たないVM・GKEノードが、Googleのパブリックサービス(Cloud Storage, Big Query, Artifact Registryなど)のAPIエンドポイントにアクセスできるようにするために必要な設定。
    • コンテナイメージのプル、ロギング、モニタリングなどにつかう
  • NATはインターネット全般の出口を提供するが、プライベートGoogleアクセスはGoogle APIへの専用ルートを提供する。

GKSコントロールプレーン関連の定義まとめ

後述するソースのうちresource "google_container_cluster" "primary"ブロックの中身がわからなかったので、理解のために分解して整理してみる。
ブロックの中で気になったところのみ触れる。
    name     = "cookbook-cluster"
    location = "asia-northeast1"
locationにasia-northeast1を指定しており、Regional Clusterとなる。
コントロールプレーンが3つのゾーンに分散され、高い可用性が確保される。
    # デフォルトノードプールを削除し、カスタムノードプールを使用する(推奨)
    remove_default_node_pool = true
    initial_node_count = 1
GKEは、クラスター作成時に自動的にデフォルトのノードプールを作成するが、remove_default_node_poolでこれを即座に削除する。
(感想) VPCの自動モードもそうだが、GCPはデフォルトで何かしら作る思想のリソースがあるのはなぜなのだろうか。
また、削除する場合も初期ノード数の定義が必要なためここでは1を設定している。
このハンズオンではgoogle_container_node_poolで別途ノードを定義している。
    # VPC-native traffic routing (Alias IP)
    networking_mode = "VPC_NATIVE"
    ip_allocation_policy {
        cluster_ipv4_cidr_block  = "/16"
        services_ipv4_cidr_block = "/22"
    }
networking_mode
Pod(コンテナ)に、VPCネットワーク内の正規のIPアドレスを直接割り当てる方式を指定する。
静的ルートテーブルを用いるルートベース方式よりも拡張性が高い。
ip_allocation_policy
Pod(/16)と Service(/22)に割り当てる IP アドレスの範囲(CIDR)を定義する。
これにより、PodがVPC内の正規のIPをもつことができ、他のVPCリソースとの通信がスムーズになる。
    # Workload Identity (Azure AD Pod Identity / Workload Identity)
    # GSAとKSAを連携させるための設定
    workload_identity_config {
        workload_pool = "cookbook-251226.svc.id.goog"
    }
k8s上のサービスアカウント(KSA)にGoogle Cloudのサービスアカウント(GSA)の権限を安全に借用させるための機能。

基本情報と

やったこと

※書籍の該当章に書かれている内容を1つのハンズオンで確認できるようにGeminiでまとめ直したものです。

Chapter 6: Google Cloud Kubernetes Engine (GKE) Hands-on

このハンズオンでは、Google Cloud Kubernetes Engine (GKE) を使用して、スケーラブルでセキュアなコンテナ基盤を構築し、アプリケーションをデプロイする手順を学習します。
書籍にある「Zonal/Regional Cluster」「Autoscaling」「Application Deployment」のエッセンスを統合し、本番環境を意識した構成(Regional Cluster, Private Nodes, Autoscaling)を Terraform で構築します。

構成図

本ハンズオンで構築するリソース構成です。
組織ポリシー(外部 IP 禁止)に準拠するため、GKE ノードはプライベート IP のみを持ち、外部への通信は Cloud NAT を経由します。

前提条件

  • Region: asia-northeast1
  • Tools: Windows Terminal (PowerShell), Cursor, Terraform, gcloud SDK
  • Policy: constraints/compute.vmExternalIpAccess (ノードへの外部 IP 付与禁止) が有効

作業ステップ

Step 0: 認証と初期設定

作業を開始する前に、Google Cloud への認証を行います。
  1. PowerShell を開き、以下のコマンドを実行します。
# ユーザー認証
gcloud auth login

# アプリケーションのデフォルト認証情報(ADC)の取得
# TerraformがGoogle Cloud APIを操作するために必要です
gcloud auth application-default login
  1. 作業用ディレクトリを作成します。
# 作業ディレクトリの作成
mkdir terraform
mkdir app

Step 1: Terraform ファイルの作成 (Network & GKE)

概要:
GKE クラスタとそれを支えるネットワークリソースを定義します。
書籍では Zonal と Regional を別々に扱っていますが、本番環境での可用性を考慮し、Regional Cluster (Azure AKS の Availability Zones 構成に相当) を採用します。また、組織ポリシーに従い、ノードに外部 IP を持たせないPrivate Cluster構成とします。
作成ファイル: terraform/main.tf
terraform {
  required_version = ">= 1.0"
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 5.0"
    }
  }
}

provider "google" {
  project = "cookbook-251226"
  region  = "asia-northeast1"
}

# ------------------------------------------------------------------------------
# Network Resources
# ------------------------------------------------------------------------------

# VPC Network (Azure VNet)
# default VPCは削除済みのため新規作成
resource "google_compute_network" "vpc" {
  name                    = "gke-network"
  auto_create_subnetworks = false
}

# Subnet
# Private Google Accessを有効にし、NATなしでもGoogle API (GCR等) へアクセス可能にする
resource "google_compute_subnetwork" "subnet" {
  name                     = "gke-subnet"
  region                   = "asia-northeast1"
  network                  = google_compute_network.vpc.id
  ip_cidr_range            = "10.0.0.0/24"
  private_ip_google_access = true
}

# Cloud Router & NAT (Azure NAT Gateway)
# Private Nodeがインターネット(Docker Hub等)へアクセスするために必要
resource "google_compute_router" "router" {
  name    = "gke-router"
  region  = "asia-northeast1"
  network = google_compute_network.vpc.id
}

resource "google_compute_router_nat" "nat" {
  name                               = "gke-nat"
  router                             = google_compute_router.router.name
  region                             = "asia-northeast1"
  nat_ip_allocate_option             = "AUTO_ONLY"
  source_subnetwork_ip_ranges_to_nat = "ALL_SUBNETWORKS_ALL_IP_RANGES"

  log_config {
    enable = true
    filter = "ERRORS_ONLY"
  }
}

# ------------------------------------------------------------------------------
# GKE Cluster Resources (Azure Kubernetes Service)
# ------------------------------------------------------------------------------

# Service Account for Nodes
# 最小権限の原則に従い、デフォルトのCompute Engine SAではなく専用SAを使用
resource "google_service_account" "gke_sa" {
  account_id   = "gke-node-sa"
  display_name = "GKE Node Service Account"
}

# 必要な権限付与 (Monitoring, Logging, Artifact Registry Reader)
resource "google_project_iam_member" "gke_sa_roles" {
  for_each = toset([
    "roles/logging.logWriter",
    "roles/monitoring.metricWriter",
    "roles/monitoring.viewer",
    "roles/artifactregistry.reader"
  ])
  project = "cookbook-251226"
  role    = each.key
  member  = "serviceAccount:${google_service_account.gke_sa.email}"
}

# GKE Regional Cluster
# 書籍 6.2 Regional Cluster に相当
resource "google_container_cluster" "primary" {
  name     = "cookbook-cluster"
  location = "asia-northeast1" # Regional Cluster (高可用性)

  # 削除保護の無効化(ハンズオン環境のためfalseに設定)
  deletion_protection = false

  # デフォルトノードプールを削除し、カスタムノードプールを使用する(推奨)
  remove_default_node_pool = true
  initial_node_count       = 1

  network    = google_compute_network.vpc.id
  subnetwork = google_compute_subnetwork.subnet.id

  # VPC-native traffic routing (Alias IP)
  networking_mode = "VPC_NATIVE"
  ip_allocation_policy {
    cluster_ipv4_cidr_block  = "/16"
    services_ipv4_cidr_block = "/22"
  }

  # Private Cluster Config (組織ポリシー対応)
  # ノードに外部IPを付与しない
  private_cluster_config {
    enable_private_nodes    = true
    enable_private_endpoint = false # コントロールプレーンにはパブリックアクセス可能とする(学習用)
    master_ipv4_cidr_block  = "172.16.0.0/28"
  }

  # Workload Identity (Azure AD Pod Identity / Workload Identity)
  # GSAとKSAを連携させるための設定
  workload_identity_config {
    workload_pool = "cookbook-251226.svc.id.goog"
  }
}

# Node Pool
# 書籍 6.3 Resizing a Cluster (Autoscaling) に相当
resource "google_container_node_pool" "primary_nodes" {
  name       = "primary-node-pool"
  location   = "asia-northeast1"
  cluster    = google_container_cluster.primary.name

  # Autoscaling 設定
  autoscaling {
    min_node_count = 1
    max_node_count = 3
  }

  # 初期ノード数(Autoscaling有効時は無視される場合があるが定義推奨)
  node_count = 1

  node_config {
    # コスト最適化のため e2-medium (2 vCPU, 4GB) を使用
    # 本番Javaアプリでは e2-standard-2 以上を推奨
    machine_type = "e2-medium"

    # 組織ポリシー constraints/compute.requireShieldedVm 対応
    shielded_instance_config {
      enable_secure_boot          = true
      enable_integrity_monitoring = true
    }

    # Google APIへのアクセススコープ
    oauth_scopes = [
      "https://www.googleapis.com/auth/cloud-platform"
    ]

    service_account = google_service_account.gke_sa.email

    # メタデータ保護
    workload_metadata_config {
      mode = "GKE_METADATA"
    }
  }
}
Tips:
  • Pod と Service の IP 帯域:
    • Pod: アプリが動く最小単位。GKE では各 Pod に VPC 内で有効な IP が付与されるため、大量の Pod 作成に備えて広い帯域(/16 など)を割り当てます。
    • Service: 複数の Pod を束ねる仮想 IP(ロードバランサ)。Pod の IP が変わっても、Service の IP は固定されるため、安定した通信先として機能します。
    • VPC ネイティブ: Pod の IP が VPC の正規の IP として扱われるため、VPC 内の他のリソース(VM や DB)から Pod へ直接ルーティングが可能になり、パフォーマンスと可視性が向上します。
  • Regional vs Zonal: Regional クラスタはコントロールプレーンとノードが複数ゾーンに分散されるため、単一ゾーン障害に耐えられます(SLA も向上)。ただし、ゾーン間通信コストが発生する可能性があります。
  • Standard vs Autopilot: 今回は書籍の内容(ノード管理、リサイズ)を学ぶため Standard モードを使用していますが、ノード管理不要な Autopilot (書籍 6.7) が現在のデフォルト推奨です。
  • Best Practice: GKE のセキュリティの概要

Step 2: リソースの作成

概要:
Terraform を使用して Google Cloud 上にリソースを展開します。API の有効化(Kubernetes Engine API)も Terraform の構成に含まれており、依存関係を考慮して順次作成されます。
  1. Terraform ディレクトリに移動し、初期化と適用を行います。
# APIの有効化を確実にするため、事前にgcloudでも実行しておくとスムーズです
gcloud services enable container.googleapis.com

cd terraform
terraform init
terraform apply -auto-approve
Note: GKE クラスタの作成には 10 分〜15 分程度かかります。

Step 3: アプリケーションのデプロイ準備

概要:
クラスタ構築後、kubectl コマンドを使用してクラスタへの接続設定を行います。
  1. クラスタの認証情報を取得します。
# クラスタ認証情報の取得 (kubeconfigの更新)
gcloud container clusters get-credentials cookbook-cluster --region asia-northeast1
  1. ノードの状態を確認します。Regional クラスタのため、3 つのゾーンにノードが分散されている(または Autoscaler により調整されている)ことを確認します。
kubectl get nodes

Step 4: Java アプリケーションのデプロイ

概要:
書籍 6.5「Deploying a Spring Boot Java Application」に基づき、Java アプリケーションをデプロイします。
書籍では Jib を使ってソースからビルドしていますが、ここではインフラ構築に焦点を当てるため、Google が提供するサンプルコンテナイメージを使用します。
作成ファイル: app/deployment.yaml
# app/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-java
  labels:
    app: hello-java
spec:
  replicas: 3
  selector:
    matchLabels:
      app: hello-java
  template:
    metadata:
      labels:
        app: hello-java
    spec:
      containers:
        - name: hello-java
          # 書籍のSpring Bootアプリの代わりに軽量なサンプルを使用
          image: us-docker.pkg.dev/google-samples/containers/gke/hello-app:1.0
          ports:
            - containerPort: 8080
          resources:
            requests:
              cpu: "100m"
              memory: "128Mi"
---
apiVersion: v1
kind: Service
metadata:
  name: hello-java-lb
spec:
  type: LoadBalancer # 外部ロードバランサを作成 (Azure Load Balancer相当)
  selector:
    app: hello-java
  ports:
    - port: 80
      targetPort: 8080
      protocol: TCP
  1. マニフェストを適用します。
cd ../app
kubectl apply -f deployment.yaml
CE 向け Tips:
  • LoadBalancer Service: GKE で type: LoadBalancer を指定すると、Google Cloud Load Balancer (L4) が自動的にプロビジョニングされます。
  • Multi-Cluster Ingress (MCI): 書籍 6.4 で紹介されている MCI は、複数のクラスタ(リージョン跨ぎなど)に対して単一の IP でルーティングを行う高度な機能です(Azure Front Door + AKS に近い構成)。これには GKE Enterprise (Anthos) の有効化が必要です。

Step 5: 動作確認

概要:
デプロイしたアプリケーションが正常に稼働し、外部からアクセスできることを確認します。
  1. Service の外部 IP アドレスが割り当てられるまで待ちます(数分かかる場合があります)。
# External-IP が表示されるまで監視
kubectl get service hello-java-lb --watch
  1. IP アドレス(例: 34.x.x.x)が表示されたら、Ctrl+C で抜け、ブラウザまたは curl でアクセスします。
$serviceIp = kubectl get service hello-java-lb -o jsonpath='{.status.loadBalancer.ingress[0].ip}'
Write-Host "Accessing http://$serviceIp"
Invoke-WebRequest -Uri "http://$serviceIp" -UseBasicParsing
期待される出力:
Hello, world! やホスト名などが含まれる HTML レスポンスが返ってくれば成功です。
  1. Autoscaling の動作確認(オプション):
    負荷をかけるか、Deployment のレプリカ数を増やしてノードが増えるか観察できますが、今回はリソース削除を優先します。

Step 6: リソースの削除

概要:
課金を防ぐため、作成したすべてのリソースを削除します。
  1. Kubernetes リソース(Service/LoadBalancer)を削除します。
    Terraform destroy だけでも消えますが、K8s 側で作られた LB リソースが残留するリスクを防ぐため、明示的に削除することを推奨します。
kubectl delete -f deployment.yaml
  1. Terraform でインフラを削除します。
Set-Location "d:\30.開発\20251229_GCP_Cookbook\Chapter6\terraform"
terraform destroy -auto-approve

エラーメモ

terraform apply

 Error: Error waiting for creating GKE cluster: 
│       - Not all instances running in IGM after 10.122220596s. Expected 1, running 0, transitioning 1. Current errors: [CONDITION_NOT_MET]: Instance 'gke-cookbook-cluster-default-pool-fafffcff-h5kv' creation failed: Constraint constraints/compute.requireShieldedVm violated for project projects/cookbook-251226. Secure Boot is not enabled in the 'shielded_instance_config' field. See https://cloud.google.com/resource-manager/docs/organization-policy/org-policy-constraints for more information
│       - Not all instances running in IGM after 14.501550931s. Expected 1, running 0, transitioning 1. Current errors: [CONDITION_NOT_MET]: Instance 'gke-cookbook-cluster-default-pool-d2cddbcc-mlfk' creation failed: Constraint constraints/compute.requireShieldedVm violated for project projects/cookbook-251226. Secure Boot is not enabled in the 'shielded_instance_config' field. See https://cloud.google.com/resource-manager/docs/organization-policy/org-policy-constraints for more information
│       - Not all instances running in IGM after 18.293067562s. Expected 1, running 0, transitioning 1. Current errors: [CONDITION_NOT_MET]: Instance 'gke-cookbook-cluster-default-pool-5fa00056-6qc2' creation failed: Constraint constraints/compute.requireShieldedVm violated for project projects/cookbook-251226. Secure Boot is not enabled in the 'shielded_instance_config' field. See https://cloud.google.com/resource-manager/docs/organization-policy/org-policy-constraints for more information.
│   with google_container_cluster.primary,
│   on main.tf line 90, in resource "google_container_cluster" "primary":
│   90: resource "google_container_cluster" "primary" {
組織ポリシー constraints/compute.requireShieldedVm が有効な環境において、GKE クラスタ作成時に**一時的に作成されるデフォルトのノードプールが Secure Boot 設定を満たしていないために発生している。
google_container_cluster 内に以下を追加して解消。
  node_config {
    shielded_instance_config {
      enable_secure_boot = true
    }
  }