Google Cloud/Terraform シンプルなWebサイト用構成を作ってみた

20212 ワード

TL;DR

シンプルなWebサイト用構成をTerraformを使用して、Google Cloud上で作ってみようと思います。
まずは構成を考える上で検討した内容と、VPC及びGCEインスタンスを作っていこうと思います。

考えた構成

構成図

構成する上で検討した内容

  1. テスト用VPCを1つ構成し、「asia-northeast1」リージョンでサブネットを2つ作成する。
    ※構成図で「VPC1」と書いた方のVPCとなります。「VPC2」については、Cloud SQLについて書く時に改めて説明します。
  2. 作成したサブネットの内、1つにGCEインスタンス1台を配置する。
  3. GCEインスタンスには外部IPアドレスを持たせず、外部(インターネット)にアクセスする際は、Cloud NAT経由でアクセスするようにする。
  4. インタネットからのアクセスは、Cloud Load Balancing(HTTP)が受けて、GCEインスタンスにリクエストを転送できるようにする。
    ※今回はGCEインスタンス1台で構成しておりますが、本番環境ではGCEインスタンス2台以上の冗長構成を採用するケースがほとんどと思うので、ロードバランサ配下にGCEインスタンスを配置します。
  5. テスト用VPCのリソースからプライベートIPアドレスでアクセス可能なCLoud SQLインスタンス(エンジン:PostgreSQL)を配置する。

Terraformでコードを書く方針

Terraformでコードを作成する方針は、以下とします。

  1. 作成するリソースの名前の接頭辞に自分が作成したリソースとして判別できるように適当なプロジェクト名を入れる。
  2. 各リソース定義の記載前に何のリソースを作成するかコメントする。

リソースの作成~その1~

VPC

確認した内容

  1. サブネットはリージョナルリソースとなる。[1]
    ゾーンの選択は、コンピュートインスタンスを作成する際に選択する。
  2. Cloud NATはリージョナルリソースなので、一つのVPCに異なるリージョンのサブネットを配置する場合は、それぞれのサブネットにCloud NATを構成する必要がある。[2]

構築ポイント

  1. 一つのVPCを作成し、「asia-northeast1」リージョンでサブネットを2つ(an1-private1:192.168.1.0/24、an1-private2:192.168.2.0/24)を作成する。
    Google CloudのVPCは、一つのVPCに異なるアベイラビリティゾーンのサブネットを所属させることができるが、まずは同じリージョンのサブネットとして構成してみる。
  2. 「asia-northeast1」リージョンにCloud NATを作成し、外部IPを持たせないGCEインスタンスが、インターネットに接続できるようにする。
    Cloud NAT単体だけではなく、ルーティングに必要なCloud Routerも同時に作成する。
  3. Cloud NATには静的なグローバルIPアドレスを手動でアサインする。

コード

# VPC
resource "google_compute_network" "vpc_network" {
  name                    = "btc4043-vpc"
  auto_create_subnetworks = false
  routing_mode            = "REGIONAL"
}

# Subnet1(asia-northeast1)
resource "google_compute_subnetwork" "an1_private1" {
  name          = "btc4043-an1-private1"
  ip_cidr_range = "192.168.1.0/24"
  region        = "asia-northeast1"
  network       = google_compute_network.vpc_network.id
}

# Subnet2(asia-northeast1)
resource "google_compute_subnetwork" "an1_private2" {
  name          = "btc4043-an1-private2"
  ip_cidr_range = "192.168.2.0/24"
  region        = "asia-northeast1"
  network       = google_compute_network.vpc_network.id
}

# Cloud Router (for asia-northeast1)
resource "google_compute_router" "an1_router" {
  name    = "btc4043-an1-router"
  region  = google_compute_subnetwork.an1_private1.region
  network = google_compute_network.vpc_network.id
}

# External ip for Cloud NAT
resource "google_compute_address" "nat_external_ipaddress" {
  name = "btc4043-nat-external-ip"
}

# Cloud NAT
resource "google_compute_router_nat" "an1_nat" {
  name                               = "btc4043-an1-nat"
  router                             = google_compute_router.an1_router.name
  region                             = google_compute_router.an1_router.region
  nat_ip_allocate_option             = "MANUAL_ONLY"
  nat_ips                            = google_compute_address.nat_external_ipaddress.*.self_link
  source_subnetwork_ip_ranges_to_nat = "LIST_OF_SUBNETWORKS"

  subnetwork {
    name                    = google_compute_subnetwork.an1_private1.id
    source_ip_ranges_to_nat = ["ALL_IP_RANGES"]
  }

  subnetwork {
    name                    = google_compute_subnetwork.an1_private2.id
    source_ip_ranges_to_nat = ["ALL_IP_RANGES"]
  }
}

GCEインスタンス

確認した内容

  1. サービスアカウントとアクセススコープの違いについて把握し、適切な権限を付与する必要がある。[3]
  2. IAP接続に必要なアクセス開放設定をファイアウォールで設定する必要があり、ソースIPアドレスは指定がある。[4]

構築ポイント

  1. GCEインスタンスに付与するサービスアカウントを明示的に作成する。
    ※インスタンス上のアプリケーションで、Google Cloud上の他リソースに対してアクセスが必要な場合は、適切なロールを付与する。
  2. デプロイしたGCEインスタンスから後述するCloud SQLインスタンスにDB接続できるように、アクセススコープとして「cloud-platform」以外に「sql-admin」も付与しておく。
  3. 今回の構成では、踏み台用インスタンスは配置しないので、外部IPアドレスを持たないGCEインスタンスに対しては、IAP経由でSSH接続できるようにする。
  4. インスタンスタイプは「e2-micro」、OSのイメージは「centos-stream-8」を選択する。
    GECインスタンスをデプロイした後、apacheのインストール、index.htmlなど配置する。

コード

# Service account for GCE instance
resource "google_service_account" "sc_gce1" {
  account_id   = "btc4043-sc-gce1"
  display_name = "Service Account for GCE1"
}

# Firewall1 for GCE instance
resource "google_compute_firewall" "fw_gce1" {
  name    = "btc4043-fw-gce1"
  network = google_compute_network.vpc_network.name

  direction = "INGRESS"

  allow {
    protocol = "tcp"
    ports    = ["22"]
  }

  source_ranges = [
    "35.235.240.0/20"
  ]

}

# GCE instance
resource "google_compute_instance" "gce1" {
  name         = "btc4043-gce1"
  machine_type = "e2-micro"
  zone         = "asia-northeast1-b"

  boot_disk {
    initialize_params {
      size  = 20
      image = "centos-stream-8"
    }
  }

  network_interface {
    subnetwork = google_compute_subnetwork.an1_private1.self_link
  }

  metadata = {
    Name = "btc4043-fw-gce1"
  }

  service_account {
    email  = google_service_account.sc_gce1.email
    scopes = ["cloud-platform", "sql-admin"]
  }

  tags = ["btc4043-fw-gce1"]

}

インスタンスグループ

確認した内容

  1. Cloud Load BalacingのバックエンドサービスとしてGCEインスタンスを構成する場合は、対象GECインスタンスのディスクイメージからインスタンステンプレートを作成し、そのテンプレートからインスタンスグループを構成する必要がある。[5]
  2. GCEインスタンスでは、Cloud Load Balacingからのヘルスチェックを受け入れる必要があるので、ヘルスチェックを受け入れるため、指定グローバルIPアドレスレンジからのファイアウォール許可設定を追加する必要がある。[6]
  3. マネージドインスタンスグループでは、名前付きポートの割り当てを行い、Cloud Load Balancingのバックエンドサービスの構成内で登録しておく必要があり、この名前付きポートにリクエストが到達する。[7]

構築ポイント

  1. 前回のGCEインスタンス用コードを使ってデプロイしたインスタンスにIAP接続し、httpd、postgresqlのパッケージインストールを実施後、httpdの自動起動設定とindex.htmlの配置などの初期設定を行う。
  2. Google Cloud Consoleから初期設定が完了したGCEインスタンスのストレージのイメージを手動で作成する。
  3. インスタンステンプレートは、項番2で作成したディスクイメージを基とし、IAP接続以外に、Cloud Load Balancingからのヘルスチェックを受け入れられるように、ファイアウォール許可設定を追加する。
  4. インスタンスグループは、マネジメントインスタンスグループとして構成し、シングルゾーンでインスタンス1台のみ起動させて、自動スケーリングは設定なしという構成にする。
    また、GCEインスタンス上で茶華道しているHTTPサービス(TCP/80番)を名前付きポートとして構成する。

コード

# Instanece disk image (made by console)
data "google_compute_image" "gce1_image" {
  name = "btc4043-gce1-image"
}

# Firewall2 for GCE instance
resource "google_compute_firewall" "fw_gce2" {
  name    = "btc4043-fw-gce2"
  network = google_compute_network.vpc_network.name

  direction = "INGRESS"

  allow {
    protocol = "tcp"
    ports    = ["80"]
  }

  source_ranges = [
    "35.191.0.0/16",
    "130.211.0.0/22"
  ]

}

# Instance template
resource "google_compute_instance_template" "gce1_template" {
  name        = "btc4043-gce1-template"
  description = "gce1 instance template"

  tags = ["btc4043-fw-gce1", "btc4043-fw-gce2"]

  machine_type = "e2-micro"

  scheduling {
    automatic_restart = true
  }

  disk {
    source_image = data.google_compute_image.gce1_image.self_link
    auto_delete  = true
    boot         = true
  }

  network_interface {
    subnetwork = google_compute_subnetwork.an1_private1.self_link
  }

  service_account {
    email  = google_service_account.sc_gce1.email
    scopes = ["cloud-platform", "sql-admin"]
  }
}

# Managed instance group
resource "google_compute_instance_group_manager" "gce1-mig" {
  name = "btc4043-gce1-mig"

  base_instance_name = "btc4043-gce1"
  zone               = "asia-northeast1-b"

  target_size = 1

  named_port {
    name = "http"
    port = 80
  }

  version {
    instance_template = google_compute_instance_template.gce1_template.id
  }

}

Cloud Load Balacing

確認した内容

  1. 従来型の外部HTTPロードバランサ用のTerraformサンプルコードがあったので、参考にした。[8]

Cloud Load Balacingを構成しようと試みた時、どういったリソースを順次組み立てていく必要があるのかが掴みにくかった。
そんな時に、以下のドキュメントの「単純なホストとパスのルール」を一読し、サンプルコードを参考することで、Cloud Load Balacingをどのように構成したら良いか(リクエストがどのようなフローでバックエンドサービスに到達するか)理解できた。