🛠️

外部 IP を持たない Cloud Build のプライベートプールをインターネットに通信させる方法

2025/01/14に公開

クラウドエースの北野です。
外部 IP を割り当てていない Cloud Build のプライベートプールをインターネットにアクセスさせる方法を紹介します。

要約

Cloud Build のプライベートプールから GitHub などインターネットにアクセスする構成は以下の通りです。

  • プライベートサービスアクセスによる VPC ネットワークとプライベートプールのネットワーク接続
    • カスタムルートのエクスポートの有効
  • Compute Engine の NAT 化
    • IP 転送の有効化
    • OS 内の NAT 設定
  • VPC ネットワーク上のプライベートプールの通信を NAT 化した Compute Engine へフォワーディング
    • ルートによる通信を Compute Engine へのルーティング
    • Firewall によるプライベートサービスアクセスから Compute Engine への通信許可

はじめに

セキュリティの観点から Compute Engine などのコンピュートリソースに外部 IP アドレスを持たせないセキュリティポリシーがあるかと思います。
CI/CD の実行環境にもこのセキュリティポリシーを適用した場合、SaaS の GitHub Actions を使えなくなる可能性があります。

そういったとき、Cloud Build のプライベートプールを使うと簡単にセキュリティ要件を満たせることもあります。
プライベートプールは作成時に外部 IP アドレスの割り当てを選択することができ、割り当てないと外部 IP も持たない環境にすることができます。しかし、外部 IP を持たないと、以下のように GitHub のリポジトリの内容をクローンすることができず、ビルドに失敗します。

本記事では、外部 IP アドレスを持たないプライベートプールをインターネットにアクセスさせる方法を紹介します。

設計

外部 IP アドレスを持たないプライベートプールがインターネットにアクセスさせるために以下のように NAT 化した Compute Engine (以下 GCE と呼ぶ)を構築し、プライベートプールからの通信をインターネットに転送します。

上記の内容を実現するための各 Google Cloud のコンポーネントの設定は以下の通りです。

  • Cloud Build プライベートプールの設定
    • プライベートネットワークによる VPC ネットワークに接続
    • 外部 IP の割り当てをしない
  • VPC ネットワークの設定
    • プライベートサービスアクセスによる接続サービスプロデューサー Google Cloud Platform とのプライベート接続の作成
    • ルートによるすべての通信を NAT 化した GCE の へのフォワーディング
    • Firewall によるプライベートプールのから NAT 化した GCE へのアクセス許可
  • GCE の設定
    • IP 転送の有効化
    • OS への NAT 化設定

構築

Terraform を使って、設計内容のシステムを構築していきます。記事の内容を試す場合、紹介する Terraform コードの <> の部分は、実環境に沿うように書き替えてください。

VPCネットワークの構築

プライベートプールを接続する VPC ネットワークを作成します。
本記事では、以下のアドレス範囲でシステムを構築します。

  • NAT 化した GCE を構築するサブネットワークのアドレス範囲: 192.168.0.0/24
  • プライベートプールのアドレス範囲: 192.168.1.0/24
resource "google_compute_network" "main" {
  name = <VPC Network Name>

  project                 = var.project
  auto_create_subnetworks = false
}

resource "google_compute_subnetwork" "main" {
  name = <Subnetwork Name>

  project       = var.project
  ip_cidr_range = "192.168.0.0/24"
  region        = "asia-northeast1"
  network       = google_compute_network.main.id
}

作成した上記の VPC ネットワークに以下の設定をします。

  • プライベートサービスアクセスによる接続サービスプロデューサー Google Cloud Platform とのプライベート接続の作成
  • NAT 化した GCE へフォワーディングするルートの作成
  • NAT 化した GCE への通信を許可する Firewall の作成

プライベートサービスアクセスの設定

Cloud Build のプライベートプールのネットワークをプライベートネットワークで構築するために、プライベートサービスアクセスを使って接続サービスプロデューサー Google Cloud Platform とのプライベート接続を作成します。このとき、カスタムルートのエクスポートを有効化します。

resource "google_compute_global_address" "main" {
  name = <IPAddress Name>

  project       = var.project
  purpose       = "VPC_PEERING"
  address_type  = "INTERNAL"
  prefix_length = "24"          # プライベートプールの IP アドレス範囲 (192.168.1.0/24)
  address       = "192.168.1.0" # プライベートプールの IP アドレス範囲 (192.168.1.0/24)
  network       = google_compute_network.main.id
}

resource "google_service_networking_connection" "main" {
  network = google_compute_network.main.id

  service = "servicenetworking.googleapis.com"
  reserved_peering_ranges = [
    google_compute_global_address.main.name
  ]
}

resource "google_compute_network_peering_routes_config" "main" {
  peering = google_service_networking_connection.main.peering

  project              = var.project
  network              = google_compute_network.main.name
  export_custom_routes = true
  import_custom_routes = false
}

ルートの作成

プライベートプールからの通信を NAT 化した GCE にフォワーディングする以下のルートを作成します。

ターゲット 宛先アドレス ネクストホップ プライオリティ
VPC ネットワーク上のすべてのトラフィック (指定なし) 0.0.0.0/1 NAT の GCE インスタンス 1000
VPC ネットワーク上のすべてのトラフィック (指定なし) 128.0.0.0/1 NAT の GCE インスタンス 1000
NAT 化した GCE インスタンス (GCE のネットワークタグ) 0.0.0.0/1 default-internet-gateway 10
NAT 化した GCE インスタンス (GCE のネットワークタグ) 128.0.0.0/1 default-internet-gateway 10
resource "google_compute_route" "all-traffic-to-nat-1" {
  name = <NAT Name1>

  project           = var.project
  dest_range        = "0.0.0.0/1"
  network           = google_compute_network.main.name
  priority          = 1000
  next_hop_instance = google_compute_instance.main.id
}

resource "google_compute_route" "all-traffic-to-nat-2" {
  name = <NAT Name2>

  project           = var.project
  dest_range        = "0.0.0.0/1"
  network           = google_compute_network.main.name
  priority          = 1000
  next_hop_instance = google_compute_instance.main.id
}

resource "google_compute_route" "nat-to-default-gw-1" {
  name = <NAT Name3>

  project          = var.project
  dest_range       = "0.0.0.0/1"
  network          = google_compute_network.main.name
  priority         = 10
  next_hop_gateway = "default-internet-gateway"
  tags             = ["nat"]
}

resource "google_compute_route" "nat-to-default-gw-2" {
  name = <NAT Name4>

  project          = var.project
  dest_range       = "128.0.0.0/1"
  network          = google_compute_network.main.name
  priority         = 10
  next_hop_gateway = "default-internet-gateway"
  tags             = ["nat"]
}

Firewallの作成

プライベートプール から NAT 化した GCE への通信を許可する Firewall を作成します。

resource "google_compute_network_firewall_policy" "main" {
  name = <Firewall Policy Name>

  project = var.project
}

resource "google_compute_network_firewall_policy_association" "main" {
  name = <Firewall Policy Association Name>

  project           = var.project
  attachment_target = google_compute_network.main.id
  firewall_policy   = google_compute_network_firewall_policy.main.name
}

resource "google_compute_network_firewall_policy_rule" "main" {
  firewall_policy = google_compute_network_firewall_policy.main.name

  rule_name = <Firewall Policy Rule Name>
  project   = var.project
  action    = "allow"
  direction = "INGRESS"
  priority  = 1000
  target_service_accounts = [
    google_service_account.gce-nat-gcb.email
  ]

  match {
    src_ip_ranges = [
      "192.168.1.0/24" # プライベートプールの IP アドレス範囲 (192.168.1.0/24)
    ]

    layer4_configs {
      ip_protocol = "all"
      ports       = []
    }
  }
}

GCE の作成

NAT 化する GCE インスタンスを作成します。GCE を NAT 化するために以下のように GCE を作成します。

  • IP 転送の有効化
  • OS 内への NAT 化設定
    • IP フォーワードの有効化
    • iptables の設定

NAT 化するための GCE の OS 内の設定は、スタートアップスクリプトでおこないます。

resource "google_compute_instance" "main" {
  name = <Compute Engine Name>

  project        = var.project
  can_ip_forward = true
  machine_type   = "e2-micro"
  zone           = "asia-northeast1-b"
  tags           = ["nat"]

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

  network_interface {
    subnetwork = google_compute_subnetwork.main.id
    access_config {
    }
  }

  service_account {
    email  = google_service_account.gce-nat-gcb.email
    scopes = ["cloud-platform"]
  }

  metadata = {
    startup-script = <<EOT
      sysctl -w net.ipv4.ip_forward=1
      iptables -t nat -A POSTROUTING -o $(ip addr show scope global | head -1 | awk -F: '{print $2}') -j MASQUERADE
      EOT
  }
}

resource "google_service_account" "gce-nat-gcb" {
  account_id = "gce-nat-gcb"
  project    = var.project
}

Cloud Build のプライベートプールの作成

プライベートネットワークに上記で作成した VPC ネットワークを指定して、外部 IP アドレスを持たないプライベートプールを作成します。

resource "google_cloudbuild_worker_pool" "main" {
  name = <Worker Pool Name>

  project  = var.project
  location = "asia-northeast1"
  worker_config {
    disk_size_gb   = 100
    machine_type   = "e2-medium"
    no_external_ip = true
  }
  network_config {
    peered_network = google_compute_network.main.id
  }

  depends_on = [
    google_service_networking_connection.main
  ]
}

プライベートプールでのビルド実行

最後に構築したプライベートプールを使って Cloud Build トリガーを実行させてみます。Cloud Build トリガーは以下のコードで作成される terraform plan を実行するトリガーです。

resource "google_cloudbuild_trigger" "main" {
  name = <Cloud Build Trigger Name>

  project         = var.project
  location        = "asia-northeast1"
  service_account = google_service_account.gcb-tf.id

  source_to_build {
    repository = <Repository ID>
    ref       = "refs/heads/main"
    repo_type = "GITHUB"
  }

  build {
    logs_bucket = <GCS Bucket URL>

    step {
      name       = "hashicorp/terraform"
      id         = "terraform-init"
      entrypoint = "/bin/sh"
      args = [
        "-c",
        "terraform init",
      ]
    }

    step {
      name       = "hashicorp/terraform"
      id         = "terraform-plan"
      entrypoint = "/bin/sh"
      env = [
        format("TF_VAR_project=%s", var.project)
      ]
      args = [
        "-c",
        "terraform plan"
      ]
    }

    options {
      worker_pool = google_cloudbuild_worker_pool.main.id
    }
  }
}

resource "google_service_account" "gcb-tf" {
  account_id = "gcb-tf-pr"
  project    = var.project
}

resource "google_project_iam_member" "project" {
  project = var.project

  role   = "roles/editor"
  member = google_service_account.gcb-tf.member
}

作成したトリガーを実行すると、以下のようにプライベートプールは GitHub にアクセスし、Terraform を実行します。


さいごに

外部 IP アドレスを持たない Cloud Build のプライベートプールを VPC ネットワークに接続してインターネットにアクセスさせる方法を紹介しました。
今回、外部 IP を持つ GCE インスタンスを NAT として構築しましたが、Cloud NAT を使うとこの GCE も外部 IP アドレスを持たせることなく構築できます。また、EGRESS の通信を制御する Firewall を作成すると、プライベートプールからインターネットへの通信も制限できます。
みなさんも Cloud Build のプライベートプールを使ってセキュリティポリシーに沿った CI/CD 環境を構築してみてください。

Discussion