GCP Cloud NAT で固定IPからインターネットに出られるようにする (VM)

## 内容

GCPにVM Instanceなどを作成して、固定のIPからアクセスできるようにCloud NATを設定する

https://cloud.google.com/nat/docs/public-nat

# VPC

[terraform-google-modules/network/google](https://registry.terraform.io/modules/terraform-google-modules/network/google/latest) ←このモジュールを使って簡単に作成することができる。

今回は、Subnet一つのシンプルなVPCを作成。firewall_rulesはOptionalですが、今回はIAP TunnelからのSSHのみ許可する場合は以下のように設定

```hcl
module "simple_vpc" {
  source  = "terraform-google-modules/network/google"

  project_id   = var.project
  network_name = "analysis-vpc-simple"
  routing_mode = "GLOBAL"

  subnets = [
    {
      subnet_name           = "test-subnet"
      subnet_ip             = "10.10.20.0/24"
      subnet_region         = var.region
      subnet_private_access = "true"
      subnet_flow_logs      = "true"
      description           = "Test subnet"
    },
  ]

  firewall_rules = [
    {
      name        = "allow-ssh-ingress"
      description = null
      direction   = "INGRESS"
      priority    = null
      ranges = [
        "35.235.240.0/20", # Identity-Aware Proxy (IAP) source ip
      ]
      source_tags             = null
      source_service_accounts = null
      target_tags             = null
      target_service_accounts = null
      allow = [{
        protocol = "tcp"
        ports    = ["22"]
      }]
      deny = []
      log_config = {
        metadata = "INCLUDE_ALL_METADATA"
      }
    }
  ]
}
```

## Cloud Router

> Cloud Router is only used to place NAT information onto the VMs.

NATの情報をVMに連携するために使われる (VMが不要の場合はCloud Routerも不要かも)

```hcl
resource "google_compute_router" "router" {
  project = var.project
  name    = "nat-router"
  network = module.simple_vpc.network_name
  region  = var.region
}
```

## Cloud Nat

Cloud Natも[terraform-google-modules/cloud-nat/google](https://registry.terraform.io/modules/terraform-google-modules/cloud-nat/google/latest) という便利なModuleがあるのでこちらを利用。

Defaultではnat_ipsは自動的にstatic ip addressが作成されて決まる (`AUTO_ONLY`)

```hcl
module "cloud-nat" {
  source  = "terraform-google-modules/cloud-nat/google"
  version = "~> 5.0"

  project_id                         = var.project
  region                             = var.region
  router                             = google_compute_router.router.name
  name                               = "nat-config"
  source_subnetwork_ip_ranges_to_nat = "ALL_SUBNETWORKS_ALL_IP_RANGES" # default: ALL_SUBNETWORKS_ALL_IP_RANGES
}
```

`nat_ips`を自分で指定したい場合は、以下のように `google_compute_address.static_address.self_link` などを設定する。(`MANUAL_ONLY`)

```hcl
resource "google_compute_address" "static_address" {
  name         = "static-address"
  description  = "static ip address"
  address_type = "EXTERNAL" # default: EXTERNAL
}
module "cloud-nat" {
  ...
  nat_ips = [
    google_compute_address.static_address.self_link,
  ]
  ...
}
```

## VM Instance

- `network_interface`でVPCで定義したNetworkとSubnetworkを指定
- `boot_disk`や`lifecycle`は適宜調整


```hcl
resource "google_compute_instance" "vm_instance" {
  name                      = "vm-instance"
  machine_type              = "e2-medium"
  zone                      = "asia-northeast1-c"
  allow_stopping_for_update = true
  desired_status            = "RUNNING" # "RUNNING" or "TERMINATED"

  boot_disk {
    initialize_params {
      image = "debian-cloud/debian-11"
    }
  }

  network_interface {
    network    = module.simple_vpc.network_name
    subnetwork = module.simple_vpc.subnets["${var.region}/test-subnet"].self_link
  }

  lifecycle {
    ignore_changes = [
      boot_disk[0].initialize_params, # to prevent from recreating an instance due to new image
      metadata["ssh-keys"],
    ]
  }
}
```

## SSH permission

Identity-Aware Proxy (IAP) tunnelでVMに接続する場合は以下の権限を付与する

```hcl
resource "google_project_iam_member" "project" {
  project = var.project
  role    = "roles/iap.tunnelResourceAccessor"
  member  = "user:nakamasato@gmail.com"
}
```

## SSHで接続

```
gcloud auth login
```

```
gcloud compute ssh vm-instance \
    --zone asia-northeast1-c \
    --tunnel-through-iap
```

## Ref

- https://cloud.google.com/nat/docs/public-nat