【入門】Terraformで構築したGCEにSSHしたい【gcloud/VPC/FW/GCE】in2024年4月

GCE

はじめに

今回は、前回に引き続きGCP入門ということで、Terraformで構築したGCEにSSHするところまで進めていきたいと思います。

GCPとTerraformの初期設定は以下を参考に完了しておきます。

Terraformコードの作成

以下を参考にTerraformコードを作成します。

Terraform Registry
Terraform を使用して基本的な Flask ウェブサーバーをデプロイする  |  Google Cloud

任意のテキストエディタでmain.tfのようなファイルを作成しておきます。

mkdir test-project && cd test-project
touch main.tf

作成したファイルに以下のコードを記述します。

(サブネットは後続の記事で冗長構成を構築したく4つにしています)

provider "google" {
  project = "your-project-id
  region  = "asia-northeast1" # 東京リージョン
}

# VPC作成
resource "google_compute_network" "vpc_network" {
  name = "my-vpc-network"
  # auto_create_subnetworks = false
}

# パブリックサブネット1作成
resource "google_compute_subnetwork" "public_subnet_1" {
  name          = "public-subnet-1"
  ip_cidr_range = "10.0.1.0/24"
  network       = google_compute_network.vpc_network.self_link
  region        = "asia-northeast1"
}

# パブリックサブネット2作成
resource "google_compute_subnetwork" "public_subnet_2" {
  name          = "public-subnet-2"
  ip_cidr_range = "10.0.2.0/24"
  network       = google_compute_network.vpc_network.self_link
  region        = "asia-northeast1"
}

# プライベートサブネット1作成
resource "google_compute_subnetwork" "private_subnet_1" {
  name          = "private-subnet-1"
  ip_cidr_range = "10.0.3.0/24"
  network       = google_compute_network.vpc_network.self_link
  region        = "asia-northeast1"
}

# プライベートサブネット2作成
resource "google_compute_subnetwork" "private_subnet_2" {
  name          = "private-subnet-2"
  ip_cidr_range = "10.0.4.0/24"
  network       = google_compute_network.vpc_network.self_link
  region        = "asia-northeast1"
}

# GCEインスタンス作成
resource "google_compute_instance" "example_instance" {
  name         = "example-instance"
  machine_type = "f1-micro"
  zone         = "asia-northeast1-a"

  boot_disk {
    initialize_params {
      image = "ubuntu-os-cloud/ubuntu-2004-lts"
    }
  }

  network_interface {
    subnetwork = google_compute_subnetwork.public_subnet_1.self_link
    access_config {
      // Ephemeral IP
    }
  }
}

# ファイアウォールルール作成
resource "google_compute_firewall" "ssh_http_firewall" {
  name    = "ssh-http-firewall"
  network = google_compute_network.vpc_network.self_link

  allow {
    protocol = "tcp"
    ports    = ["22", "80","443"]
  }

  source_ranges = ["0.0.0.0/0"]
  # target_tags   = ["example-instance"]
}

リソースの構築

terraform planを実行してドライランを確認してterraform applyを実行します。

$ terraform plan

Terraform used the selected providers to generate the
following execution plan. Resource actions are
indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # google_compute_firewall.ssh_http_firewall will be created
  + resource "google_compute_firewall" "ssh_http_firewall" {
      + creation_timestamp = (known after apply)
      + destination_ranges = (known after apply)
      + direction          = (known after apply)
      + enable_logging     = (known after apply)
      + id                 = (known after apply)
      + name               = "ssh-http-firewall"
      + network            = (known after apply)
      + priority           = 1000
      + project            = "your-project-id"
      + self_link          = (known after apply)
      + source_ranges      = [
          + "0.0.0.0/0",
        ]
      + target_tags        = [
          + "example-instance",
        ]

      + allow {
          + ports    = [
              + "22",
              + "80",
              + "443",
            ]
          + protocol = "tcp"
        }
    }

  # google_compute_instance.example_instance will be created
  + resource "google_compute_instance" "example_instance" {
      + can_ip_forward       = false
      + cpu_platform         = (known after apply)
      + current_status       = (known after apply)
      + deletion_protection  = false
      + effective_labels     = (known after apply)
      + guest_accelerator    = (known after apply)
      + id                   = (known after apply)
      + instance_id          = (known after apply)
      + label_fingerprint    = (known after apply)
      + machine_type         = "f1-micro"
      + metadata_fingerprint = (known after apply)
      + min_cpu_platform     = (known after apply)
      + name                 = "example-instance"
      + project              = "your-project-id"
      + self_link            = (known after apply)
      + tags_fingerprint     = (known after apply)
      + terraform_labels     = (known after apply)
      + zone                 = "asia-northeast1-a"

      + boot_disk {
          + auto_delete                = true
          + device_name                = (known after apply)
          + disk_encryption_key_sha256 = (known after apply)
          + kms_key_self_link          = (known after apply)
          + mode                       = "READ_WRITE"
          + source                     = (known after apply)

          + initialize_params {
              + image                  = "ubuntu-os-cloud/ubuntu-2004-lts"
              + labels                 = (known after apply)
              + provisioned_iops       = (known after apply)
              + provisioned_throughput = (known after apply)
              + size                   = (known after apply)
              + type                   = (known after apply)
            }
        }

      + confidential_instance_config {
          + enable_confidential_compute = (known after apply)
        }

      + network_interface {
          + internal_ipv6_prefix_length = (known after apply)
          + ipv6_access_type            = (known after apply)
          + ipv6_address                = (known after apply)
          + name                        = (known after apply)
          + network                     = (known after apply)
          + network_ip                  = (known after apply)
          + stack_type                  = (known after apply)
          + subnetwork                  = (known after apply)
          + subnetwork_project          = (known after apply)

          + access_config {
              + nat_ip       = (known after apply)
              + network_tier = (known after apply)
            }
        }

      + reservation_affinity {
          + type = (known after apply)

          + specific_reservation {
              + key    = (known after apply)
              + values = (known after apply)
            }
        }

      + scheduling {
          + automatic_restart           = (known after apply)
          + instance_termination_action = (known after apply)
          + min_node_cpus               = (known after apply)
          + on_host_maintenance         = (known after apply)
          + preemptible                 = (known after apply)
          + provisioning_model          = (known after apply)

          + local_ssd_recovery_timeout {
              + nanos   = (known after apply)
              + seconds = (known after apply)
            }

          + node_affinities {
              + key      = (known after apply)
              + operator = (known after apply)
              + values   = (known after apply)
            }
        }
    }

  # google_compute_network.vpc_network will be created
  + resource "google_compute_network" "vpc_network" {
      + auto_create_subnetworks                   = true
      + delete_default_routes_on_create           = false
      + gateway_ipv4                              = (known after apply)
      + id                                        = (known after apply)
      + internal_ipv6_range                       = (known after apply)
      + mtu                                       = (known after apply)
      + name                                      = "my-vpc-network"
      + network_firewall_policy_enforcement_order = "AFTER_CLASSIC_FIREWALL"
      + numeric_id                                = (known after apply)
      + project                                   = "your-project-id"
      + routing_mode                              = (known after apply)
      + self_link                                 = (known after apply)
    }

  # google_compute_subnetwork.private_subnet_1 will be created
  + resource "google_compute_subnetwork" "private_subnet_1" {
      + creation_timestamp         = (known after apply)
      + external_ipv6_prefix       = (known after apply)
      + fingerprint                = (known after apply)
      + gateway_address            = (known after apply)
      + id                         = (known after apply)
      + internal_ipv6_prefix       = (known after apply)
      + ip_cidr_range              = "10.0.3.0/24"
      + ipv6_cidr_range            = (known after apply)
      + name                       = "private-subnet-1"
      + network                    = (known after apply)
      + private_ip_google_access   = (known after apply)
      + private_ipv6_google_access = (known after apply)
      + project                    = "your-project-id"
      + purpose                    = (known after apply)
      + region                     = "asia-northeast1"
      + secondary_ip_range         = (known after apply)
      + self_link                  = (known after apply)
      + stack_type                 = (known after apply)
    }

  # google_compute_subnetwork.private_subnet_2 will be created
  + resource "google_compute_subnetwork" "private_subnet_2" {
      + creation_timestamp         = (known after apply)
      + external_ipv6_prefix       = (known after apply)
      + fingerprint                = (known after apply)
      + gateway_address            = (known after apply)
      + id                         = (known after apply)
      + internal_ipv6_prefix       = (known after apply)
      + ip_cidr_range              = "10.0.4.0/24"
      + ipv6_cidr_range            = (known after apply)
      + name                       = "private-subnet-2"
      + network                    = (known after apply)
      + private_ip_google_access   = (known after apply)
      + private_ipv6_google_access = (known after apply)
      + project                    = "your-project-id"
      + purpose                    = (known after apply)
      + region                     = "asia-northeast1"
      + secondary_ip_range         = (known after apply)
      + self_link                  = (known after apply)
      + stack_type                 = (known after apply)
    }

  # google_compute_subnetwork.public_subnet_1 will be created
  + resource "google_compute_subnetwork" "public_subnet_1" {
      + creation_timestamp         = (known after apply)
      + external_ipv6_prefix       = (known after apply)
      + fingerprint                = (known after apply)
      + gateway_address            = (known after apply)
      + id                         = (known after apply)
      + internal_ipv6_prefix       = (known after apply)
      + ip_cidr_range              = "10.0.1.0/24"
      + ipv6_cidr_range            = (known after apply)
      + name                       = "public-subnet-1"
      + network                    = (known after apply)
      + private_ip_google_access   = (known after apply)
      + private_ipv6_google_access = (known after apply)
      + project                    = "your-project-id"
      + purpose                    = (known after apply)
      + region                     = "asia-northeast1"
      + secondary_ip_range         = (known after apply)
      + self_link                  = (known after apply)
      + stack_type                 = (known after apply)
    }

  # google_compute_subnetwork.public_subnet_2 will be created
  + resource "google_compute_subnetwork" "public_subnet_2" {
      + creation_timestamp         = (known after apply)
      + external_ipv6_prefix       = (known after apply)
      + fingerprint                = (known after apply)
      + gateway_address            = (known after apply)
      + id                         = (known after apply)
      + internal_ipv6_prefix       = (known after apply)
      + ip_cidr_range              = "10.0.2.0/24"
      + ipv6_cidr_range            = (known after apply)
      + name                       = "public-subnet-2"
      + network                    = (known after apply)
      + private_ip_google_access   = (known after apply)
      + private_ipv6_google_access = (known after apply)
      + project                    = "your-project-id"
      + purpose                    = (known after apply)
      + region                     = "asia-northeast1"
      + secondary_ip_range         = (known after apply)
      + self_link                  = (known after apply)
      + stack_type                 = (known after apply)
    }

Plan: 7 to add, 0 to change, 0 to destroy.

──────────────────────────────────────────────────────

Note: You didn't use the -out option to save this
plan, so Terraform can't guarantee to take exactly
these actions if you run "terraform apply" now.

f1-microがasia-northeast1-aで使用できなかったため、e2-microに変更して再度terraform planを実行しました。

$ terraform plan
google_compute_network.vpc_network: Refreshing state... [id=projects/your-project-id/global/networks/my-vpc-network]
google_compute_subnetwork.private_subnet_2: Refreshing state... [id=projects/your-project-id/regions/asia-northeast1/subnetworks/private-subnet-2]
google_compute_subnetwork.public_subnet_1: Refreshing state... [id=projects/your-project-id/regions/asia-northeast1/subnetworks/public-subnet-1]
google_compute_subnetwork.public_subnet_2: Refreshing state... [id=projects/your-project-id/regions/asia-northeast1/subnetworks/public-subnet-2]
google_compute_subnetwork.private_subnet_1: Refreshing state... [id=projects/your-project-id/regions/asia-northeast1/subnetworks/private-subnet-1]
google_compute_firewall.ssh_http_firewall: Refreshing state... [id=projects/your-project-id/global/firewalls/ssh-http-firewall]

Terraform used the selected providers to generate the
following execution plan. Resource actions are
indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # google_compute_instance.example_instance will be created
  + resource "google_compute_instance" "example_instance" {
      + can_ip_forward       = false
      + cpu_platform         = (known after apply)
      + current_status       = (known after apply)
      + deletion_protection  = false
      + effective_labels     = (known after apply)
      + guest_accelerator    = (known after apply)
      + id                   = (known after apply)
      + instance_id          = (known after apply)
      + label_fingerprint    = (known after apply)
      + machine_type         = "e2-micro"
      + metadata_fingerprint = (known after apply)
      + min_cpu_platform     = (known after apply)
      + name                 = "example-instance"
      + project              = "your-project-id"
      + self_link            = (known after apply)
      + tags_fingerprint     = (known after apply)
      + terraform_labels     = (known after apply)
      + zone                 = "asia-northeast1-a"

      + boot_disk {
          + auto_delete                = true
          + device_name                = (known after apply)
          + disk_encryption_key_sha256 = (known after apply)
          + kms_key_self_link          = (known after apply)
          + mode                       = "READ_WRITE"
          + source                     = (known after apply)

          + initialize_params {
              + image                  = "ubuntu-os-cloud/ubuntu-2004-lts"
              + labels                 = (known after apply)
              + provisioned_iops       = (known after apply)
              + provisioned_throughput = (known after apply)
              + size                   = (known after apply)
              + type                   = (known after apply)
            }
        }

      + confidential_instance_config {
          + enable_confidential_compute = (known after apply)
        }

      + network_interface {
          + internal_ipv6_prefix_length = (known after apply)
          + ipv6_access_type            = (known after apply)
          + ipv6_address                = (known after apply)
          + name                        = (known after apply)
          + network                     = (known after apply)
          + network_ip                  = (known after apply)
          + stack_type                  = (known after apply)
          + subnetwork                  = "<https://www.googleapis.com/compute/v1/projects/your-project-id/regions/asia-northeast1/subnetworks/public-subnet-1>"
          + subnetwork_project          = (known after apply)

          + access_config {
              + nat_ip       = (known after apply)
              + network_tier = (known after apply)
            }
        }

      + reservation_affinity {
          + type = (known after apply)

          + specific_reservation {
              + key    = (known after apply)
              + values = (known after apply)
            }
        }

      + scheduling {
          + automatic_restart           = (known after apply)
          + instance_termination_action = (known after apply)
          + min_node_cpus               = (known after apply)
          + on_host_maintenance         = (known after apply)
          + preemptible                 = (known after apply)
          + provisioning_model          = (known after apply)

          + local_ssd_recovery_timeout {
              + nanos   = (known after apply)
              + seconds = (known after apply)
            }

          + node_affinities {
              + key      = (known after apply)
              + operator = (known after apply)
              + values   = (known after apply)
            }
        }
    }

Plan: 1 to add, 0 to change, 0 to destroy.

──────────────────────────────────────────────────────

Note: You didn't use the -out option to save this
plan, so Terraform can't guarantee to take exactly
these actions if you run "terraform apply" now.

terraform applyを実行します。

$ terraform apply
google_compute_network.vpc_network: Refreshing state... [id=projects/your-project-id/global/networks/my-vpc-network]
google_compute_subnetwork.private_subnet_1: Refreshing state... [id=projects/your-project-id/regions/asia-northeast1/subnetworks/private-subnet-1]
google_compute_subnetwork.public_subnet_2: Refreshing state... [id=projects/your-project-id/regions/asia-northeast1/subnetworks/public-subnet-2]
google_compute_subnetwork.public_subnet_1: Refreshing state... [id=projects/your-project-id/regions/asia-northeast1/subnetworks/public-subnet-1]
google_compute_subnetwork.private_subnet_2: Refreshing state... [id=projects/your-project-id/regions/asia-northeast1/subnetworks/private-subnet-2]
google_compute_firewall.ssh_http_firewall: Refreshing state... [id=projects/your-project-id/global/firewalls/ssh-http-firewall]

Terraform used the selected providers to generate the
following execution plan. Resource actions are
indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # google_compute_instance.example_instance will be created
  + resource "google_compute_instance" "example_instance" {
      + can_ip_forward       = false
      + cpu_platform         = (known after apply)
      + current_status       = (known after apply)
      + deletion_protection  = false
      + effective_labels     = (known after apply)
      + guest_accelerator    = (known after apply)
      + id                   = (known after apply)
      + instance_id          = (known after apply)
      + label_fingerprint    = (known after apply)
      + machine_type         = "e2-micro"
      + metadata_fingerprint = (known after apply)
      + min_cpu_platform     = (known after apply)
      + name                 = "example-instance"
      + project              = "your-project-id"
      + self_link            = (known after apply)
      + tags_fingerprint     = (known after apply)
      + terraform_labels     = (known after apply)
      + zone                 = "asia-northeast1-a"

      + boot_disk {
          + auto_delete                = true
          + device_name                = (known after apply)
          + disk_encryption_key_sha256 = (known after apply)
          + kms_key_self_link          = (known after apply)
          + mode                       = "READ_WRITE"
          + source                     = (known after apply)

          + initialize_params {
              + image                  = "ubuntu-os-cloud/ubuntu-2004-lts"
              + labels                 = (known after apply)
              + provisioned_iops       = (known after apply)
              + provisioned_throughput = (known after apply)
              + size                   = (known after apply)
              + type                   = (known after apply)
            }
        }

      + confidential_instance_config {
          + enable_confidential_compute = (known after apply)
        }

      + network_interface {
          + internal_ipv6_prefix_length = (known after apply)
          + ipv6_access_type            = (known after apply)
          + ipv6_address                = (known after apply)
          + name                        = (known after apply)
          + network                     = (known after apply)
          + network_ip                  = (known after apply)
          + stack_type                  = (known after apply)
          + subnetwork                  = "<https://www.googleapis.com/compute/v1/projects/your-project-id/regions/asia-northeast1/subnetworks/public-subnet-1>"
          + subnetwork_project          = (known after apply)

          + access_config {
              + nat_ip       = (known after apply)
              + network_tier = (known after apply)
            }
        }

      + reservation_affinity {
          + type = (known after apply)

          + specific_reservation {
              + key    = (known after apply)
              + values = (known after apply)
            }
        }

      + scheduling {
          + automatic_restart           = (known after apply)
          + instance_termination_action = (known after apply)
          + min_node_cpus               = (known after apply)
          + on_host_maintenance         = (known after apply)
          + preemptible                 = (known after apply)
          + provisioning_model          = (known after apply)

          + local_ssd_recovery_timeout {
              + nanos   = (known after apply)
              + seconds = (known after apply)
            }

          + node_affinities {
              + key      = (known after apply)
              + operator = (known after apply)
              + values   = (known after apply)
            }
        }
    }

Plan: 1 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

google_compute_instance.example_instance: Creating...
google_compute_instance.example_instance: Still creating... [10s elapsed]
google_compute_instance.example_instance: Creation complete after 16s [id=projects/your-project-id/zones/asia-northeast1-a/instances/example-instance]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

VPC,FW,インスタンスが作成されました!

SSH接続

インスタンスにCLIコンソール接続する方法がいくつか用意されていますので、今回は2パターン試していきます。

ブラウザからのSSH接続

GCEコンソール上から接続する方法です。

  • SSH>ブラウザウィンドウで開く

非常に手軽に実行できる点が魅力ですね。

以下のようなウィンドウが開かれてブラウザからコンソール接続できました。

ただし、VPCのファイアウォールで適切にアクセス許可を行う必要があります。

ブラウザでの SSH を使用するには、環境が次の要件を満たしている必要があります。

  • Google Cloud コンソールは、サポート対象のウェブブラウザで実行されている必要があります。
  • 接続先の VM には、ゲスト環境インストールされ、実行されている必要があります。ゲスト環境は、Google 提供の公開イメージから作成した VM にプリインストールされています。
  • ネットワークは以下の要件を満たしている必要があります。
    • HTTPS プロキシとセキュリティ デバイスは、TLS インスペクションなどのために、独自の TLS 証明書を使用してトラフィックを復号、再暗号化してはなりません。
    • ネットワークは、google.comgstatic.com、または googleapis.com で終わるホスト名との間のトラフィックを許可する必要があります。
    • ネットワークは、デフォルト ドメインの IP アドレスへのパケット送信を許可する必要があります。
    • 外部 IP アドレスを使用して VM に接続するには、Virtual Private Cloud(VPC)で IP 範囲 0.0.0.0/0 の TCP 上り(内向き)トラフィックを許可する必要があります。
    • 内部 IP アドレスを使用して VM に接続するには、VPC で IAP を使用する必要があります。
ブラウザでの SSH  |  Compute Engine ドキュメント  |  Google Cloud

外部IPでアクセスしたい場合は、0.0.0.0/0からのsshアクセスを許可します。

ただし、セキュリティ的によろしくないので、内部IPでアクセスしたいと思います。その場合は、35.235.240.0/20からのsshアクセスを許可することでコンソールアクセス可能です。これは、httpsでGCPのフルマネージドのリバースプロキシであるCloudIAP(35.235.240.0/20)を経由してSSHするためです。

TCP 転送での IAP の使用  |  Identity-Aware Proxy  |  Google Cloud

ローカルのターミナルからgcloudSSH接続

ローカル端末で任意のターミナルを開き、以下のコマンドを実行します。

パスワードを2回聞かれるので、任意のパスワードを入力します。

/Users/username/.ssh/配下に秘密鍵が作成されるので、今後、パスワード認証でSSHアクセスできるようになります。

$ gcloud compute ssh --zone "asia-northeast1-a" "example-instance" --project "your-project-id"
WARNING: The private SSH key file for gcloud does not exist.
WARNING: The public SSH key file for gcloud does not exist.
WARNING: You do not have an SSH key for gcloud.
WARNING: SSH keygen will be executed to generate a key.
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:

以下の条件を満たしている必要があります。

  • インスタンスが外部IPを持っていること
  • VPCのファイアウォールでローカル端末のIPアドレスからのsshを許可していること

内部IPのみのインスタンスにローカル端末からSSH接続

ロールを追加した上で、IAP経由する方法があるようです。

今回は実施しませんでしたが、今後はこちらも試していきたいと思います。

TCP 転送での IAP の使用  |  Identity-Aware Proxy  |  Google Cloud

作成したリソースを削除

忘れずにリソースを削除しておきましょう。

$ terraform destroy
Destroy complete! Resources: 7 destroyed.

まとめ

今回は、TerraformでGCEインスタンスを作成してSSHしてみました。

次回以降で、Webサーバー構築なども試していきたと思います。

最後までご覧いただきありがとうございました

コメント

タイトルとURLをコピーしました