GCP Shared VPCを利用した全社共通ネットワークの運用におけるDedicated Interconnect利用設定の最適化手法

f:id:vasilyjp:20210319200947j:plain

はじめに

こんにちは。気がつけば4月でZOZOTOWNに関わって9年目を迎えるSRE部の横田です。普段はSREとしてZOZOTOWNのリプレイスや運用に携わっています。

本記事ではGoogle Cloud PlatformでShared VPCを採用し全社共通ネットワークを構築した背景とその運用方法について説明します。

ZOZOTOWNとパブリッククラウド専用線

まずはZOZOTOWNとパブリッククラウドを接続する専用線について説明します。

数年前まではZOZOTOWNを支える基盤は、ほぼ全てがオンプレミス環境で稼働しており、以下の課題がありました。

  • システムが密結合であること
  • アジリティの低さ

これらを解決するためにパブリッククラウドを活用したマイクロサービス化が日々進んでいます。

現在パブリッククラウドはAmazon Web Services(以下、AWS)とGoogle Cloud Platform(以下、GCP)を主に利用しています。一方でオンプレミス環境でも様々な重要なシステムが稼働しています。それらの異なる環境で稼働するシステムが安定して相互通信を行えるネットワークはZOZOTOWNにとって重要な存在です。

そこで現在は各パブリッククラウドで以下の専用線接続サービスを利用しています。ここではバックアップ回線を含む利用状況を示しています。

どちらもオンプレミスとパブリッククラウドのネットワークを直接物理的に接続するサービスです。プライベート通信のためにインターネットを介したVPNでのネットワーク経路という選択肢もありますが、膨大なトラフィックに対する品質保証のためにAWSとGCPどちらも直接接続が可能なサービスを弊社では選択しています。オンプレミスとパブリッククラウド間の通信だけではなく、AWSとGCPなどパブリッククラウド同士の通信にも、これらのサービスとデータセンターを経由し、LANで実現させているケースが多々存在しています。

直面していた課題

ここでは、実際に直面していた2種類の課題を紹介します。

BGP設定に対する課題

上述した専用線接続サービスは対向側のオンプレミス環境で要件を満たすルーターにBGP設定を投入することで疎通可能になります。弊社ではBGP設定において以下の課題が存在していました。

  • BGP設定自体の複雑さ
  • 複数チームで作業するため細かな連携が必要であること

AWSの場合は導入当初こそ各VPC毎にVirtual Interfaceを払い出していましたが、現在はDirect Connect Gatewayを活用しているケースが多いです。そのため、一度Direct Connect Gatewayを所有するVPCに対してVirtual Interfaceの作成とオンプレミス側でBGP設定を行えば以降は相乗りするVPCにはBGP設定は不要になります。

GCPの場合はこれまでのやり方だと新規プロジェクト作成の度にBGP設定を実施していました。さらに弊社の場合はプロダクション環境とステージング環境、開発環境など複数の環境が異なるGCPのプロジェクト上で稼働しています。そのため何か新しいプロジェクトを立ち上げる場合には複数の環境に対して設定する必要があります。

またオンプレミス環境の設定とクラウド環境の設定は異なるチームで連携して互いに作業する運用だったため、連携コストや作業ミスが発生した場合の切り分けや再設定にどうしても時間を要する課題が潜在的に存在していました。

Dedicated InterconnectのVLANアタッチ上限の課題

ある日、GCPのDedicated Interconnectで致命的な問題に直面しました。

GCPを利用する複数チームから同時期に複数の新規プロジェクトでDedicated Interconnectを利用する要望があり、設定パラメーターなどを連携して各チームが作業した際にある新規プロジェクトで以下のエラーが発生したのです。

Error: Error waiting to create InterconnectAttachment: Error waiting for Creating InterconnectAttachment: Quota 'Interconnect_ATTACHMENTS_ALL_REGIONS' exceeded.  Limit: 16.0 globally

1つのDedicated Interconnectに関連付けることができるVLANアタッチメントの最大数の上限である「16」を超過しようとしたため発生したエラーでした。

公式ドキュメントでも上限の引き上げが不可能な項目となっています。

この上限に関して完全に盲点だったのは反省点です。この問題を解決しない限り新規のプロジェクトがDedicated Interconnectを利用できない状況となりました。

対応方針を模索

社内の有識者を集め対応方針を決めていきました。以下に検討した案とそれぞれのメリット、デメリットを紹介します。

Dedicated Interconnect自体を追加する案

シンプルに専用線を追加する案ですがデメリットの要素が多く選択肢からは早々に外れました。

  • メリット
    • これまで通りの運用で追加が行える
    • VPCのフルコントロール権を各プロジェクトに与えられる
  • デメリット
    • 敷設に様々なコストがかかる
    • 帯域の利用状況としては余裕ある状況でのDedicated Interconnect追加はもったいない
    • 1本追加すれば上限数が16増えるがプロジェクトの増加傾向を考えるとまた同じ悩みに直面する日は近い

VPC Network Peeringを使った構成にする案

GCPのVPC Network Peeringを使ってDedicated Interconnect設定済のVPCとVPC Network Peeringを行うことで専用線を利用する方法です。

こちらの構成の場合は以下の設定が必要になります。

  • HubとなるVPCからオンプレミス側へ各プロジェクトのVPC CIDRをアドバタイズ
  • VPC Network Peeringを使いハブとなるVPCと各プロジェクトのVPCを接続
  • custom route設定でオンプレミス環境のネットワーク経路を各プロジェクトのVPCへ伝播
    • ハブとなるVPCではカスタム経路をexport
    • 各プロジェクトのVPCではカスタム経路をimport

vpc-peering

  • メリット
    • 既存のVPCをこの接続方法に切り替える場合もVPCをそのまま残せる
    • VPCのフルコントロール権を各プロジェクトに与えることができる
  • デメリット
    • 多数のプロジェクトとVPC Network Peeringを行うようになると管理が煩雑になる
    • 接続可能なVPCの上限が25

こちらはDedicated Interconnectを追加する案よりも魅力的でしたが、接続可能なVPC上限を考慮すると心許ない値だったため、この構成は見送りました。

Shared VPCを使った構成にする案

Shared VPCは組織内の複数のプロジェクトのリソースを共通のVPCに接続する方法です。

shared-vpc

  • メリット
    • Dedicated Interconnect利用に関するBGP設定はホストプロジェクトのみ行えば良い
    • Firewallなどネットワークポリシーの一元管理が可能
    • 接続可能なサービスプロジェクト数が初期値で1000と弊社には十分な値
  • デメリット
    • サービスプロジェクト側で任意のタイミングでVPCに紐づくリソースの変更ができない
    • 既存プロジェクトをShared VPCに移行する場合はサブネットの作り直しになる
      • システム停止など移行方法を検討する必要がある

VPCのフルコントロール権限などがなくなってしまう課題はありましたが、ガバナンスを効かせる意味でも今の環境に適しているのはShared VPCという結論となり採用することになりました。

Shared VPCの管理と運用方法

ここからはShared VPCの管理と運用を解説していきます。

Shared VPCの利用が決定した際に以下の方針を立てました。

  • 今後Dedicated Interconnectを利用する新規プロジェクトはShared VPCのサービスプロジェクトとする
    • 既存プロジェクトの移行を現段階では強制しない
  • Shared VPCの管理チームはチーム横断型
  • 1つのネットワーク管理プロジェクトとしてShared VPCは専用のGitHubリポジトリで管理
    • Terraformを使った構成管理とGitHub ActionsでのCI/CDを行う
    • サービスプロジェクト追加時はサービスプロジェクトのメンバーがPull Request作成

管理チーム

現在、4名の管理チームで運用しています。管理チームの主なタスクはネットワークの採番(予め払い出した巨大なCIDRから細分化して払い出し)とPull Request作成のコードレビューです。

ネットワークの採番ではGoogle Kubernetes Engine(以下、GKE)を使うか使わないかで提供するIPレンジを調整します。基本的にGKEの利用が無い場合はプロダクション環境やステージング環境には/20のCIDRを割当てます。開発環境やQA環境に関してはプロダクション同等のサイジングが必要無いケースも多いため、半分の/21のCIDRを割り当てます。

GKEを利用するプロジェクトの場合は必要となるIPが多くなるため/18のCIDRを割り当てるようにしています。GKEのアドレス管理に関しては公式ドキュメントにも記載されており参考にしました。

運用方法

Shared VPCの利用依頼からネットワークリソースの作成までを図示します。

operation

Shared VPCの管理リポジトリは各プロジェクトのリポジトリとは完全に分離されており、Shared VPCのためのCI/CDで追加したネットワークリソースを各サービスプロジェクトで指定してCompute Engineなどのリソースを作成します。ネットワークの払い出し以外はコードで完結できるようになっています。

TerraformでShared VPC環境を定義

ここからはShared VPCを構築するTerraformのtfファイルで定義される内容について解説していきます。プロダクション環境やステージング環境など複数の環境に対してCDできるリポジトリ構成としています。

.
├── .github
│   └── workflows
└──terraform
   └── gcp
       ├── dev
       │   ├── backend.tf
       │   ├── locals.tf
       │   ├── main.tf -> ../main.tf
       │   ├── service1.tf -> ../service1.tf
       │   └── service2.tf -> ../service2.tf
       ├── prd           # dev同様のファイル構成
       ├── qa            # dev同様のファイル構成
       ├── stg           # dev同様のファイル構成
       ├── main.tf
       ├── service1.tf
       └── service2.tf

それぞれのファイルの設定内容を解説していきます。

locals.tf

プロダクションやステージングなどの複数環境に対してCDを行うために変数を定義しています。サービスプロジェクトを追加する場合は変数の追加が必要になりますが、そちらについては後述します。なお、値は仮のものですが以下の変数を定義しました。

locals {
  env          = "dev" # 各環境の名称
  host_project = "poject-dev" # ホストプロジェクトの名称

  # Dedicated Interconnectに関する各種変数を指定
  interconnect_region                            = "asia-northeast1" # Dedicated Interconnectのregion
  interconnect_url_main                          = "https://www.googleapis.com/dev-main" # メイン回線のURL
  interconnect_url_bkup                          = "https://www.googleapis.com/dev-backup" # バックアップ回線のURL
  interconnect_attachment_bandwidth_capacity     = "BPS_10G" # アタッチする帯域
  router_google_asn                              = 64512 # GCP側のASN
  router_peer_asn                                = 65000 # オンプレミス側のASN
  interconnect_attachment_candidate_subnets_main = ["192.168.0.0/24"] # メイン回線のBGP IPで利用するCIDR
  interconnect_attachment_candidate_subnets_bkup = ["192.168.1.0/24"] # バックアップ回線のBGP IPで利用するCIDR
  interconnect_attachment_vlan_id                = 100 # Cloud RouterにアタッチするVLAN

  # Cloud Routerを利用するregionを定義
  nat_router_regions = [
    "asia-northeast1",
    "asia-east1",
  ]

  # マネージドサービスやServerless環境など共通の環境で利用するサブネットを定義
  cidr_google_managed_services         = "192.168.10.0/24" # マネージドサービスに割り当てるサブネット
  cidr_vpc_serverless_access_connector = "192.168.11.0/24" # Serverless環境からVPCへのアクセスを中継するコネクタ用のサブネット
  cidr_proxy_only_subnet = {
    "asia-northeast1" = "192.168.12.0/24" # 内部Load Balancer利用のためのプロキシ専用サブネット①
    "asia-east1"      = "192.168.13.0/24" # 内部Load Balancer利用のためのプロキシ専用サブネット②
  }

main.tf

main.tfではホストプロジェクト側で管理すべき以下の内容を定義しています。

  • Shared VPCのホストプロジェクト、VPC定義
  • Dedicated Interconnectを利用するための定義
  • networkViewerの権限を付与するサービスプロジェクトのメンバーの定義
  • マネージドサービスやServerless環境利用のための定義
  • Firewall定義
  • プライベートIPでLoad Balancerを利用するためのプロキシ専用サブネットの定義
  • Cloud NATコントロールプレーンの定義

またCDのために各環境のディレクトリ配下に(prd/stg/dev/qa)にシンボリックリンクを作成します。

サンプルコードと共に各定義の内容を解説していきます。

Shared VPCのホストプロジェクト、VPC定義

Shared VPCで利用するプロジェクトやVPCを定義します。複数regionのサービスを利用するケースがあるためルーティングモードはグローバルで設定します。

provider "google" {
  project = local.host_project
}

provider "google-beta" {
  project = local.host_project
}

resource "google_compute_network" "shared_vpc" {
  name                    = "shared-vpc-${local.env}"
  auto_create_subnetworks = false
  routing_mode            = "GLOBAL"
}

resource "google_compute_shared_vpc_host_project" "host" {
  project = local.host_project
}

Dedicated Interconnectを利用するための定義

メイン回線とバックアップ回線にそれぞれ定義します。

BGP IPが再作成される問題に対して社内ナレッジがあったためlifecycleを使って例外設定をします。ignore_changesを利用することでTerraform上の管理しているリソースと実際のリソースに差分がある状況の変更を無視できます。

# Cloud Routerを定義
resource "google_compute_router" "shared_vpc_main" {
  name    = "shared-vpc-main-${local.env}"
  network = google_compute_network.shared_vpc.id
  bgp {
    asn               = local.router_google_asn
    advertise_mode    = "CUSTOM"
    advertised_groups = ["ALL_SUBNETS"]
    advertised_ip_ranges {
      range = local.cidr_google_managed_services
    }
  }
  region = local.interconnect_region
}

# Dedicated Interconnectの定義とVLANアタッチメント
# 既存のDedicated Interconnectを指定する
resource "google_compute_interconnect_attachment" "shared_vpc_main" {
  admin_enabled     = true
  name              = "shared-vpc-main-${local.env}"
  interconnect      = local.interconnect_url_main
  router            = google_compute_router.shared_vpc_main.id
  bandwidth         = local.interconnect_attachment_bandwidth_capacity
  candidate_subnets = local.interconnect_attachment_candidate_subnets_main
  region            = local.interconnect_region
  vlan_tag8021q     = local.interconnect_attachment_vlan_id
  # Avoid force replacement
  lifecycle {
    ignore_changes = [
      candidate_subnets,
    ]
  }
}

# BGP IPを定義
resource "google_compute_router_interface" "shared_vpc_main" {
  name                    = "router-interface-shared-vpc-main-${local.env}"
  router                  = google_compute_router.shared_vpc_main.name
  ip_range                = google_compute_interconnect_attachment.shared_vpc_main.cloud_router_ip_address
  interconnect_attachment = google_compute_interconnect_attachment.shared_vpc_main.id
  region                  = local.interconnect_region
  lifecycle {
    ignore_changes = [
      ip_range,
    ]
  }
}

# オンプレミス側ルーターとのBGPセッションを定義
resource "google_compute_router_peer" "shared_vpc_main" {
  name                      = "shared-vpc-${local.env}"
  router                    = google_compute_router.shared_vpc_main.name
  region                    = local.interconnect_region
  advertised_groups         = []
  advertised_route_priority = 0
  peer_ip_address           = replace(google_compute_interconnect_attachment.shared_vpc_main.customer_router_ip_address, "/29", "")
  peer_asn                  = local.router_peer_asn
  interface                 = google_compute_router_interface.shared_vpc_main.name
}

networkViewerの権限を付与するサービスプロジェクトのメンバーの定義

サービスプロジェクトのメンバーにShared VPCのnetworkViewer権限を付与するための定義です。この権限を付与しないとShared VPCで作成したサブネットの情報を各サービスプロジェクトのメンバーが参照できません。

resource "google_project_iam_member" "network_viewer" {
  count   = length(local.all_service_projects_members)
  project = local.host_project
  role    = "roles/compute.networkViewer"
  member  = element(local.all_service_projects_members, count.index)
}

マネージドサービスやServerless環境利用のための定義

Cloud SQLなどのマネージドサービスやServerless環境に対してプライベートIPでアクセスするための定義します。設定詳細については公式ドキュメントもご参照ください。

# マネージドサービスとのVPC Network Peering設定
resource "google_compute_global_address" "private_ip_alloc_google_managed_service" {
  name          = "google-managed-services-${google_compute_network.shared_vpc.name}"
  purpose       = "VPC_PEERING"
  address_type  = "INTERNAL"
  prefix_length = tonumber(element(split("/", local.cidr_google_managed_services), 1))
  network       = google_compute_network.shared_vpc.id
  address       = element(split("/", local.cidr_google_managed_services), 0)
}

resource "google_service_networking_connection" "private_service_connection_google_managed_service" {
  network                 = google_compute_network.shared_vpc.id
  service                 = "servicenetworking.googleapis.com"
  reserved_peering_ranges = [google_compute_global_address.private_ip_alloc_google_managed_service.name]
}

resource "google_compute_network_peering_routes_config" "private_service_access_mysql" {
  peering              = "cloudsql-mysql-googleapis-com"
  network              = google_compute_network.shared_vpc.name
  import_custom_routes = false
  export_custom_routes = true
}

# Serverless環境とVPCを接続するためのコネクタ設定
resource "google_vpc_access_connector" "connector" {
  name          = "vpc-access-connector"
  region        = local.interconnect_region
  ip_cidr_range = local.cidr_vpc_serverless_access_connector
  network       = google_compute_network.shared_vpc.name
}

Firewall定義

Shared VPCに対するFirewallを定義します。設定する際のポイントは以下の点です。

  • Identity-Aware Proxyのように全サービスプロジェクトが利用する仕組みに関しては予め許可設定にする
  • Shared VPCからマネージドサービスに対する通信(Egress)は予め拒否設定にする

先ほども触れましたがCloud SQLなどのマネージドサービスとプライベートIPでの通信を行う要件がありPrivate Service Accessを利用しています。Private Service Accessは作成時にPrivate Service Access用のサブネットにCIDRを指定しますが、ユーザー管理下のVPCではなくGCP管理下のVPCに作成されます。作成されたPrivate Service AccessとShared VPCをVPC Network PeeringすることでプライベートIPでの接続が可能になります。

Cloud SQLのプライベートIPでの利用時には1つのリージョンと1つのデータベースタイプにつき最小/24のサブネットが指定したCIDR内から割り当てられる要件が存在します。Cloud SQL側のFirewallで承認済みネットワークにプライベートサブネットを指定することができないため、何も制御を行わない場合はShared VPC内のどのようなサービスプロジェクトのリソースでもCloud SQLにインスタンスレベルでのアクセスが可能になってしまいます。

この問題を回避するために送信元側で通信を制御します。Firewallのコンポーネントのデフォルト設定ではEgressは全て許可設定のため、上述したようにShared VPC内からマネージドサービスのサブネットに対するDeny設定を投入しています。Shared VPC内各サービスプロジェクト毎に通信要件のあるCloud SQLに対しTagまたはService AccountへEgressの許可ルールをDenyよりも高い優先順位で作成することで制御を行うことにしました。

resource "google_compute_firewall" "allow_ssh_from_iap" {
  name     = "allow-ssh-from-iap"
  network  = google_compute_network.shared_vpc.name
  priority = 65534

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

  source_ranges = ["35.235.240.0/20"]
}

resource "google_compute_firewall" "deny_all_to_private_service_access" {
  name     = "deny-all-to-private-service-access"
  network  = google_compute_network.shared_vpc.name
  priority = 65532

  direction = "EGRESS"

  deny {
    protocol = "tcp"
    ports    = ["0-65535"]
  }

  destination_ranges = [local.cidr_google_managed_services]
}

プライベートIPでLoad Balancerを利用するためのプロキシ専用サブネットの定義

プライベートIPでLoad Balancerを利用するシーンも多いためプロキシ専用のサブネットをShared VPC内に定義します。プロキシ専用のサブネットはregion毎に1つしか作成できないためmain.tfで管理します。

resource "google_compute_subnetwork" "proxy_only_subnet" {
  provider = google-beta

  for_each = local.cidr_proxy_only_subnet

  name          = "proxy-only-subnet-${each.key}"
  ip_cidr_range = each.value
  region        = each.key
  network       = google_compute_network.shared_vpc.self_link

  purpose = "INTERNAL_HTTPS_LOAD_BALANCER"
  role    = "ACTIVE"
}

Cloud NATコントロールプレーンの定義

プライベートサブネットからインターネットに接続するためにはCloud NATを利用します。

Cloud NATはSDNな分散マネージドサービスのため以下2つの要素から定義されます。

  • Cloud NATコントロールプレーン
  • Cloud NATゲートウェイ

以下の理由からmain.tfでコントロールプレーンを定義します。

resource "google_compute_router" "nat-router" {
  for_each = toset(local.nat_router_regions)

  name    = "nat-router-${each.value}"
  region  = each.value
  network = google_compute_network.shared_vpc.self_link

  bgp {
    asn = local.router_google_asn
  }
}

ここまでホストプロジェクト側で予め準備してきたtfファイルを解説してきました。次は各サービスプロジェクトがShared VPCを利用する場合に作成、変更するファイルについて解説していきます。

サービスプロジェクトのメンバーにより作成・変更するファイル

新規にサービスプロジェクト側でShared VPCのサブネットを利用する場合は以下のtfファイルを作成、追加変更をします。

  • 各サービスプロジェクトの設定項目を記載するサービスプロジェクト名.tf(新規作成)
    • サンプルとしてservice1を記載
  • locals.tf(追記)

順に詳細を解説していきます。

サービスプロジェクト名.tf

それぞれのサービスプロジェクトで管理するネットワークリソースを定義したtfファイルを作成します。CDのため各環境のディレクトリ(prd・stg・dev・qa)配下にシンボリックリンクを作成します。

# サービスプロジェクトを定義
resource "google_compute_shared_vpc_service_project" "service1" {
  host_project    = google_compute_shared_vpc_host_project.host.project
  service_project = local.service1["service_project"]
}

# サービスプロジェクト(service1)で利用するサブネットを定義
resource "google_compute_subnetwork" "service1_subnet" {
  name          = "${local.service1.service_project}-subnet"
  region        = local.service1["region"]
  network       = google_compute_network.shared_vpc.id
  ip_cidr_range = local.service1["primary_cidr"]

  private_ip_google_access = true
}

# 作成したサブネットに対して利用するサービスプロジェクトのメンバーへ操作権限を付与
resource "google_compute_subnetwork_iam_member" "service1" {
  for_each   = toset(local.service1.service_project_members)
  project    = google_compute_shared_vpc_host_project.host.project
  region     = google_compute_subnetwork.service1_subnet.region
  subnetwork = google_compute_subnetwork.service1_subnet.name
  role       = "roles/compute.networkUser"
  member     = each.value
}

# 組織ポリシーでサブネットとサービスプロジェクトを紐づける
resource "google_project_organization_policy" "service1" {
  project    = local.service1["service_project"]
  constraint = "compute.restrictSharedVpcSubnetworks"

  list_policy {
    inherit_from_parent = false
    allow {
      values = [
        "projects/${local.host_project}/regions/${google_compute_subnetwork.service1_subnet.region}/subnetworks/${google_compute_subnetwork.service1_subnet.name}"
      ]
    }
  }
}

# Cloud NATゲートウェイに割り当てるIPを定義

resource "google_compute_address" "service1_nat_ip" {
  name   = "${local.service1.service_project}-nat-ip"
  region = local.service1["region"]
}

# Cloud NATゲートウェイを定義
resource "google_compute_router_nat" "service1_nat_gateway" {
  name                   = "${local.service1.service_project}-nat-gateway"
  router                 = google_compute_router.nat-router[google_compute_subnetwork.service1_subnet.region].name
  region                 = local.service1["region"]
  nat_ip_allocate_option = "MANUAL_ONLY"
  min_ports_per_vm       = 64 # 状況に応じて変更する
  nat_ips                = [google_compute_address.service1_nat_ip.self_link]

  source_subnetwork_ip_ranges_to_nat = "LIST_OF_SUBNETWORKS"
  subnetwork {
    name                    = google_compute_subnetwork.service1_subnet.self_link
    source_ip_ranges_to_nat = ["ALL_IP_RANGES"]
  }

  log_config {
    enable = true
    filter = "ALL"
  }
}

ポイントは組織ポリシーの制約です。サブネットとサービスプロジェクトを紐づけることができます。この組織ポリシーで複数のサービスプロジェクトに対して権限を持つユーザーが誤って意図しないサービスプロジェクトにリソースを作成してしまうことを制御できます。

locals.tfへの追記

先ほど解説したlocals.tfサービスプロジェクト.tfファイルで利用する変数を追記していきます。

  # 各サービスプロジェクトで利用する変数をlocals.tfに追記して定義する
  service1 = {
    service_project    = "service1-${local.env}"
    service_project_id = "service1-${local.env}"
    region             = "asia-northeast1"

    primary_cidr = "192.168.100.0/24"

    service_project_members = [
      "group:service1@example.com"
    ]
  }

  # 全サービスプロジェクトのメンバーを追記
  all_service_projects_members = distinct(concat(
    local.service1.service_project_members
  ))
}

状況によってはサービスプロジェクトのメンバーでmain.tfを編集してPull Requestを作成することもあります。

Shared VPC環境のCI/CD

最後にShared VPCリポジトリのGitHub Actionsを利用したCI/CDについても簡単に解説していきます。

ci-cd

各ブランチでCI/CDが行われ、異なるGCP環境に対して処理が実行されるようになっています。各ブランチで段階的にCI/CDすることで誤った設定をした場合も開発環境やステージング環境への反映後に気がつき修正が可能なため、安全なリリースができる仕組みとなっています。

ZOZOTOWNのCI/CD戦略については弊社川崎の書いた記事で詳しく紹介されておりますので是非ご覧ください。

techblog.zozo.com

Shared VPC管理リポジトリでのCI/CDにより作成されたサブネット上にリソースを構築することで各サービスプロジェクトはDedicated Interconnectが利用可能な状態になります。

まとめ

Shared VPCを導入したことにより直面したVLANアタッチメントの上限数の問題を回避できました。またクラウド環境、オンプレミス環境と複数のチームでの設定が必要なことから潜在的に抱えていた課題もオンプレミス側のBGPルーターから広報するネットワークが増えない限りは基本的にはGCP側の作業のみで完結できるようになりました。

一方でGCP外の内部リソース(AWSなど)との通信制御については課題もあります。マネージドサービスなどは全サービスプロジェクトが共通のサブネットを利用していますが、IPレベルでの通信制御ができないため現状はどうしても制御が必要なシーンでは送信元でアクセス先を絞ることになります。Cloud ArmorがプライベートIPでの通信に向けて適用できるようになることを期待せずにはいられません。

謝辞

本プロジェクトの進行と環境構築、そして本ブログの執筆にあたり多大なる協力をいただいた弊社shiozakicivitaspo、そしてsonotsへこの場を借りてお礼を申し上げさせていただきます。

最後に

ZOZOテクノロジーズでは一緒にサービスを作り上げてくれる仲間を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください!

tech.zozo.com

カテゴリー