Google Kubernetes Engine(GKE)の限定公開クラスタをTerraformで作成する

記事タイトルとURLをコピーする

G-gen の佐々木です。当記事では Google Cloud ( 旧称 GCP ) の代表的サービスである Google Kubernetes Engine ( GKE ) の限定公開クラスタを、Terraform を使用して作成していきます。

前提知識

Google Kubernetes Engine とは

Google Kubernetes Engine (以下、GKE ) は、Google Cloud のインフラストラクチャ上に構築された マネージドな Kubernetes クラスタ を利用することができるサービスです。
サービスの詳細は以下の記事で解説しています。

blog.g-gen.co.jp

Terraform とは

Terraform は Infrastructure as Code (IaC) を実現するオープンソースのツールです。
Terraform の概要は以下の記事で解説しています。

blog.g-gen.co.jp

なぜ 検証用の GKE クラスタを Terraform で作成するのか

GKE クラスタを作成する際、設定できるパラメータの項目数は非常に多く、検証のたびに 同じ構成でクラスタを作成するのは非常に煩雑な作業 になります。

クラスタのパラメータ以外にも、クラスタを配置する VPC とサブネットの作成が必要となり、限定公開クラスタを使用したい場合は、以下のリソースも追加で必要となる場合があります。

  • コントロールプレーンにプライベート IP で接続するための 踏み台 VM
  • クラスタからインターネットにアクセスするための Cloud NAT

このように GKE クラスタ以外のリソースが増えてくると、環境の構築手順が煩雑になり、またクラスタが不要になった際に、リソースの削除漏れ が起こりやすくなります。

リソースの管理に Terraform を使用することによって、コマンド 1 つで すべてのリソースを一括で作成/削除する ことができます。

また、GKE では、Standard モードのクラスタであれば、ノードとして起動される Commpute Engine インスタンスの料金が常に発生します。
そして Standard モード、Autopilot モードのどちらであっても、クラスタ管理手数料 として $74.40 / 月 の料金が発生します (最初の 1 クラスタのみ条件を満たせば無料枠あり)。

簡単な検証目的の利用であれば、料金節約のためにこまめにクラスタを削除することが望ましいでしょう。

構成図

当記事では、限定公開の GKE クラスタを Autopilot モードで作成していきます。
GKE ノードが配置される VPC に踏み台 VM を作成し、踏み台から Kubernetes の管理操作を行えるようにします。
また、クラスタからインターネットにアクセスできるように、Cloud NAT を作成します。

構成図

Terraform のディレクトリ構成

ファイルを environments/ ディレクトリと modules/ ディレクトリに分けています。

root/
├ environments/
│ └ test/
│   ├ main.tf
│   ├ terraform.tfvars
│   └ variables.tf
└ modules/
  ├ bastion.tf
  ├ gke.tf
  ├ nat.tf
  ├ outputs.tf
  ├ variables.tf
  └ vpc.tf

各 Google Cloud リソースを定義するモジュールを modules/ ディレクトリに集約し、environments/ ディレクトリ配下にある test/main.tf から各モジュールを呼び出します。
同一構成の環境を異なる設定値で作成したい場合は、environments/ 配下の test/ ディレクトリを複製し、terraform.tfvars に記述されている設定値を書き換えます。

Terraform 詳細

environments/test/main.tf

terraform.tfvars から変数を受け取り、modules/ 配下にあるモジュールを呼び出して各リソースを作成します。
リソースの作成後、modules/outputs.tf から受け取った出力を元に、作成した GKE クラスタに接続するためのコマンドをターミナルに出力します。

terraform {
    required_providers {
        google = {
            source  = "hashicorp/google"
            version = ">= 4.0.0"
        }
    }
    required_version = ">= 1.3.0"
  
    backend "gcs" {
        # tfstate ファイルの保存先となる GCS バケット
        bucket = "tfstate"
        prefix = "gke-test"
    }
}
  
provider "google" {}
  
module "modules" {
    source = "../../modules"
  
    # 変数を modules に渡す
    common  = var.common
    vpc     = var.vpc
    gke     = var.gke
    bastion = var.bastion
  
}
  
# 全リソース作成後、GKE クラスタの接続コマンドを出力
output "command_to_connect_cluster" {
    value = "\n$ gcloud container clusters get-credentials ${module.modules.cluster_name} --region ${module.modules.cluster_location} --project ${module.modules.cluster_project}\n"
}

environments/test/variables.tf および modules/variables.tf

変数を宣言するファイルで、変数の受け渡しのために environments/ 側と modules/ 側で同一のファイルを配置します。
変数をオブジェクト型で設定することで、リソースの種類ごとにパラメータをグループ分けすることができ、どのリソースに関するパラメータなのかが明瞭になります。

variable "common" {
    type = object ({
        prefix  = string  # 固有のプレフィクス (任意の文字列)
        env     = string  # 環境名 ( dev、prod など)
        project = string  # プロジェクト
        region  = string  # リージョン
        zone    = string  # ゾーン
    })
    description = "リソース共通の設定値"
}
  
variable "vpc" {
    type = object ({
        subnet_cidr = string  # サブネットの CIDR 範囲 ( GKE ノード、踏み台ホストが使用する IP 範囲)
    })
    description = "VPC の設定値"
}
  
variable "gke" {
    type = object ({
        cluster_cidr = string  # GKE Pod が使用する IP 範囲
        service_cidr = string  # GKE Service が使用する IP 範囲
        master_cidr  = string  # コントロールプレーンが使用する IP 範囲
    })
    description = "GKE の設定値"
}
  
variable "bastion" {
    type = object ({
        machine_type    = string  # 踏み台 VM のマシンタイプ
        ssh_sourcerange = string  # 踏み台 VM に SSH アクセスできるソース IP 範囲
    })
    description = "踏み台 VM の設定値"
}

environments/test/terraform.tfvars

variables.tf で宣言した変数にパラメータを代入します。
同一の構成で別のクラスタを作成したい場合は、test/ ディレクトリを複製し、このファイルのパラメータを修正するだけで実現できるようにします。

common = {
    prefix  = "ggen"
    env     = "test"
    project = "myproject"
    region  = "asia-northeast1"
    zone    = "asia-northeast1-b"
}
  
vpc = {
    subnet_cidr = "192.168.100.0/24"
}
  
gke = {
    cluster_cidr = "172.16.0.0/16"
    service_cidr = "172.31.0.0/16"
    master_cidr  = "192.168.200.0/28"
}
  
bastion = {
    machine_type    = "e2-small"
    ssh_sourcerange = "35.235.240.0/20"  # IAP の IP 範囲
}

modules/vpc.tf

GKE クラスタを配置する VPC リソースを作成します。

# GKE クラスタを作成する VPC
resource "google_compute_network" "vpc_network" {
    project = var.common.project
  
    name                    = "vpc-${var.common.prefix}-${var.common.env}"
    auto_create_subnetworks = false    
}
  
# GKE クラスタを作成するサブネット
resource "google_compute_subnetwork" "subnet_asia_ne1" {
    project = var.common.project
  
    name          = "subnet-${var.common.prefix}-${var.common.env}"
    ip_cidr_range = var.vpc.subnet_cidr
    region        = var.common.region
    network       = google_compute_network.vpc_network.id
    
    private_ip_google_access = true
}

modules/gke.tf

限定公開の GKE クラスタを Autopilot モードで作成します。

resource "google_container_cluster" "primary" {
    project = var.common.project
  
    name             = "cluster-${var.common.prefix}-${var.common.env}-autopilot"
    enable_autopilot = true  # Autopilot モードでクラスタを作成
    location         = var.common.region
    network          = google_compute_network.vpc_network.id
    subnetwork       = google_compute_subnetwork.subnet_asia_ne1.id
  
    ip_allocation_policy {
        cluster_ipv4_cidr_block  = var.gke.cluster_cidr
        services_ipv4_cidr_block = var.gke.service_cidr
    }
    
    # 限定公開クラスタの設定
    private_cluster_config {
        enable_private_nodes    = true
        enable_private_endpoint = true
        master_ipv4_cidr_block  = var.gke.master_cidr
    }
  
    master_authorized_networks_config {
        # コントロールプレーンへのアクセスを許可する IP 範囲
        cidr_blocks {
            cidr_block = var.vpc.subnet_cidr  # ノードと踏み台が作られるサブネットからのアクセスを許可
        }
    }
}

modules/bastion.tf

コントロールプレーンに接続する踏み台 VM を作成します。
起動スクリプトを使用して、Kubernetes の管理操作に必要なコマンドと GKE のプラグインをインストールしています。

# 踏み台 VM が使用するサービスアカウント
resource "google_service_account" "bastion" {
    project = var.common.project
  
    account_id   = "sa-${var.common.prefix}-${var.common.env}"
    display_name = "Service Account for bastion-${var.common.prefix}-${var.common.env}"
}
  
# サービスアカウントにロールを付与
resource "google_project_iam_member" "bastion" {
    project = var.common.project
  
    role    = "roles/container.developer"  # Kubernetes Engine デベロッパー ロール
    member  = "serviceAccount:${google_service_account.bastion.email}"
}
  
# 踏み台 VM
resource "google_compute_instance" "bastion" {
    project = var.common.project
  
    name         = "bastion-${var.common.prefix}-${var.common.env}"
    machine_type = var.bastion.machine_type
    zone         = var.common.zone
  
    tags = ["ssh"]  # ネットワークタグ
  
    boot_disk {
        initialize_params {
            image = "debian-cloud/debian-11"  # OS イメージ
        }
    }
  
    network_interface {
        subnetwork_project = var.common.project
  
        network    = google_compute_network.vpc_network.name         # VPC
        subnetwork = google_compute_subnetwork.subnet_asia_ne1.name  # サブネット
  
        access_config {}  # パブリック IP を付与
    }
  
    metadata = {
        enable-oslogin = "true"  # OS Login を有効化
    }
  
    # 起動スクリプトで kubectl と GKE のプラグインをインストール
    metadata_startup_script = <<EOF
#!/bin/bash
sudo apt update
sudo apt install kubectl
sudo apt install google-cloud-sdk-gke-gcloud-auth-plugin
EOF
  
    service_account {
        email  = google_service_account.bastion.email
        scopes = ["cloud-platform"]
    }
}
  
resource "google_compute_firewall" "ssh" {
    project = var.common.project
  
    name    = "vpc-${var.common.prefix}-${var.common.env}-ssh-allow"
    network = google_compute_network.vpc_network.name
  
    allow {
        protocol = "tcp"
        ports    = ["22"]
    }
  
    source_ranges = [
        var.bastion.ssh_sourcerange
    ]
    target_tags   = ["ssh"]
}

modules/nat.tf

クラスタからインターネットアクセスするための Cloud NAT を作成します。
Cloud NAT を使用するためには Cloud Router が必要になるため、このモジュールで一緒に作成します、

# Cloud Router
resource "google_compute_router" "router" {
    project = var.common.project
  
    name    = "router-${var.common.prefix}-${var.common.env}"
    region  = var.common.region
    network = google_compute_network.vpc_network.id
}
  
# Cloud NAT
resource "google_compute_router_nat" "nat" {
    project = var.common.project
  
    name = "nat-${var.common.prefix}-${var.common.env}"
    router                 = google_compute_router.router.name
    region                 = google_compute_router.router.region
    nat_ip_allocate_option = "AUTO_ONLY"
  
    source_subnetwork_ip_ranges_to_nat = "ALL_SUBNETWORKS_ALL_IP_RANGES"
  
    log_config {
        enable = true
        filter = "ERRORS_ONLY"
    }
}

modules/outputs.tf

GKE クラスタの接続コマンドを出力するために必要なリソース情報を main.tf に返します。

output "cluster_name" {
  value = google_container_cluster.primary.name
}
  
output "cluster_location" {
    value = google_container_cluster.primary.location
}
  
output "cluster_project" {
    value = google_container_cluster.primary.project
}

Terraform の実行

terraform apply コマンドは environments/test/ ディレクトリで実行します。 実行が完了すると、ターミナルに以下のような文字列が出力されます。

Apply complete! Resources: 9 added, 0 changed, 0 destroyed.
  
Outputs:
  
command_to_connect_cluster = <<EOT
  
$ gcloud container clusters get-credentials cluster-ggen-test-autopilot --region asia-northeast1 --project myproject
  
EOT

ここで出力された gcloud container clusters get-credentials コマンドを踏み台 VM で実行することで、作成した GKE クラスタに接続することができます。

佐々木 駿太 (記事一覧)

G-gen最北端、北海道在住のクラウドソリューション部エンジニア

2022年6月にG-genにジョイン。Google Cloud Partner Top Engineer 2024に選出。好きなGoogle CloudプロダクトはCloud Run。

趣味はコーヒー、小説(SF、ミステリ)、カラオケなど。