単一のクラウドプロバイダに依存するリスクを避けるため、マルチクラウド構成を検討する企業が増えている。しかし「マルチクラウドをやりたい」という声は多くても、実際にInfrastructure as Code(IaC)で管理している事例はまだ少ない。この記事では、TerraformでAWSとGCPのハイブリッド構成を管理する実践的な方法を、コード例を交えて解説する。
なぜマルチクラウドなのか
マルチクラウドを採用する動機はさまざまだが、主な理由は以下の通りだ。
- ベンダーロックインの回避。単一プロバイダの障害時に事業継続できる体制を作る
- 各クラウドの強みを活かす。機械学習はGCP、エンタープライズ統合はAWSなど
- コスト最適化。ワークロードに応じて最もコスト効率の良いプロバイダを選択する
- 規制対応。データの所在地や主権に関する要件を満たす
一方で、マルチクラウドには複雑性の増大、運用コストの上昇、チームのスキルセット拡大といった課題もある。Terraformはこれらの課題を緩和するための強力なツールだ。
Terraformプロジェクト構成
マルチクラウド環境でのTerraformプロジェクトは、モジュール設計が鍵になる。以下に推奨するディレクトリ構成を示す。
terraform/
├── environments/
│ ├── production/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── outputs.tf
│ │ ├── terraform.tfvars
│ │ └── backend.tf
│ └── staging/
│ ├── main.tf
│ └── ...
├── modules/
│ ├── aws/
│ │ ├── vpc/
│ │ ├── eks/
│ │ ├── rds/
│ │ └── s3/
│ ├── gcp/
│ │ ├── vpc/
│ │ ├── gke/
│ │ ├── cloudsql/
│ │ └── gcs/
│ └── shared/
│ ├── dns/
│ ├── monitoring/
│ └── vpn/
└── global/
├── iam/
└── state-backend/
この構成のポイントは、各クラウドのリソースをモジュールとして分離しつつ、共通のインフラ(VPN接続、DNS、モニタリング)をsharedモジュールで管理することだ。
プロバイダ設定
Terraformの強みは、複数のプロバイダを1つの設定ファイルで扱える点にある。以下は本番環境のプロバイダ設定例だ。
# environments/production/main.tf
terraform {
required_version = ">= 1.7.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.40"
}
google = {
source = "hashicorp/google"
version = "~> 5.20"
}
google-beta = {
source = "hashicorp/google-beta"
version = "~> 5.20"
}
}
backend "s3" {
bucket = "mycompany-terraform-state"
key = "production/terraform.tfstate"
region = "ap-northeast-1"
encrypt = true
dynamodb_table = "terraform-lock"
}
}
provider "aws" {
region = var.aws_region
default_tags {
tags = {
Environment = "production"
ManagedBy = "terraform"
Project = "multicloud"
}
}
}
provider "google" {
project = var.gcp_project_id
region = var.gcp_region
}
provider "google-beta" {
project = var.gcp_project_id
region = var.gcp_region
}
AWS側: VPC + EKSの構築
AWS側ではVPCを作成し、EKSクラスタをデプロイする。本番環境ではマルチAZ構成にし、プライベートサブネットにワーカーノードを配置する。
# modules/aws/vpc/main.tf
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "${var.project}-${var.environment}-vpc"
}
}
resource "aws_subnet" "private" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 4, count.index)
availability_zone = var.availability_zones[count.index]
tags = {
Name = "${var.project}-${var.environment}-private-${count.index}"
"kubernetes.io/role/internal-elb" = "1"
}
}
resource "aws_subnet" "public" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 4, count.index + length(var.availability_zones))
availability_zone = var.availability_zones[count.index]
map_public_ip_on_launch = true
tags = {
Name = "${var.project}-${var.environment}-public-${count.index}"
"kubernetes.io/role/elb" = "1"
}
}
EKSクラスタモジュール
# modules/aws/eks/main.tf
resource "aws_eks_cluster" "main" {
name = "${var.project}-${var.environment}"
role_arn = aws_iam_role.cluster.arn
version = var.kubernetes_version
vpc_config {
subnet_ids = var.subnet_ids
endpoint_private_access = true
endpoint_public_access = var.public_access
security_group_ids = [aws_security_group.cluster.id]
}
encryption_config {
resources = ["secrets"]
provider {
key_arn = var.kms_key_arn
}
}
enabled_cluster_log_types = [
"api", "audit", "authenticator",
"controllerManager", "scheduler"
]
}
resource "aws_eks_node_group" "main" {
cluster_name = aws_eks_cluster.main.name
node_group_name = "${var.project}-${var.environment}-workers"
node_role_arn = aws_iam_role.node.arn
subnet_ids = var.subnet_ids
scaling_config {
desired_size = var.desired_size
max_size = var.max_size
min_size = var.min_size
}
instance_types = var.instance_types
update_config {
max_unavailable = 1
}
}
GCP側: VPC + GKEの構築
GCP側でも同等の構成をTerraformで管理する。GKEはAutopilotモードを採用し、ノード管理の運用負荷を軽減する。
# modules/gcp/vpc/main.tf
resource "google_compute_network" "main" {
name = "${var.project}-${var.environment}-vpc"
auto_create_subnetworks = false
routing_mode = "GLOBAL"
}
resource "google_compute_subnetwork" "main" {
name = "${var.project}-${var.environment}-subnet"
ip_cidr_range = var.subnet_cidr
region = var.region
network = google_compute_network.main.id
secondary_ip_range {
range_name = "pods"
ip_cidr_range = var.pods_cidr
}
secondary_ip_range {
range_name = "services"
ip_cidr_range = var.services_cidr
}
private_ip_google_access = true
}
GKE Autopilotクラスタ
# modules/gcp/gke/main.tf
resource "google_container_cluster" "main" {
provider = google-beta
name = "${var.project}-${var.environment}"
location = var.region
enable_autopilot = true
network = var.network_id
subnetwork = var.subnetwork_id
ip_allocation_policy {
cluster_secondary_range_name = "pods"
services_secondary_range_name = "services"
}
private_cluster_config {
enable_private_nodes = true
enable_private_endpoint = false
master_ipv4_cidr_block = var.master_cidr
}
release_channel {
channel = "REGULAR"
}
workload_identity_config {
workload_pool = "${var.gcp_project_id}.svc.id.goog"
}
binary_authorization {
evaluation_mode = "PROJECT_SINGLETON_POLICY_ENFORCE"
}
}
クラウド間VPN接続
マルチクラウドの要はクラウド間のネットワーク接続だ。AWSとGCPをVPNで接続し、プライベートネットワーク経由で通信できるようにする。
# modules/shared/vpn/main.tf
# GCP側: VPNゲートウェイ
resource "google_compute_ha_vpn_gateway" "to_aws" {
name = "vpn-to-aws"
network = var.gcp_network_id
region = var.gcp_region
}
# GCP側: External VPN Gateway (AWS側のエンドポイント)
resource "google_compute_external_vpn_gateway" "aws" {
name = "aws-vpn-gateway"
redundancy_type = "TWO_IPS_REDUNDANCY"
interface {
id = 0
ip_address = var.aws_vpn_tunnel1_address
}
interface {
id = 1
ip_address = var.aws_vpn_tunnel2_address
}
}
# GCP側: VPNトンネル
resource "google_compute_vpn_tunnel" "to_aws_1" {
name = "vpn-to-aws-tunnel-1"
vpn_gateway = google_compute_ha_vpn_gateway.to_aws.id
peer_external_gateway = google_compute_external_vpn_gateway.aws.id
peer_external_gateway_interface = 0
shared_secret = var.vpn_shared_secret
router = google_compute_router.vpn.id
vpn_gateway_interface = 0
}
Stateの分割とリモート参照
マルチクラウド環境では、Terraform Stateの管理戦略が重要になる。一つの巨大なStateファイルですべてを管理するのは危険だ。以下の方針でStateを分割することを推奨する。
- クラウドプロバイダごとにStateを分離する
- ネットワーク、コンピュート、データベースなどのレイヤーごとにさらに分割する
- terraform_remote_state データソースで他のStateの出力を参照する
- Terraform CloudやSpaceliftなどのプラットフォームでState管理を一元化する
# GCP側からAWS VPNの情報を参照
data "terraform_remote_state" "aws_network" {
backend = "s3"
config = {
bucket = "mycompany-terraform-state"
key = "production/aws/network/terraform.tfstate"
region = "ap-northeast-1"
}
}
locals {
aws_vpn_tunnel1_address = data.terraform_remote_state.aws_network.outputs.vpn_tunnel1_address
aws_vpn_tunnel2_address = data.terraform_remote_state.aws_network.outputs.vpn_tunnel2_address
}
CI/CDとの統合
Terraformの変更をGitHub Actionsで自動化する。PRで plan を実行し、mainマージ時に apply する。
# .github/workflows/terraform.yml
name: Terraform
on:
pull_request:
paths: ['terraform/**']
push:
branches: [main]
paths: ['terraform/**']
jobs:
plan:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- name: Terraform Init
run: terraform init
working-directory: terraform/environments/production
- name: Terraform Plan
run: terraform plan -out=plan.tfplan
working-directory: terraform/environments/production
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
GOOGLE_CREDENTIALS: ${{ secrets.GCP_SA_KEY }}
apply:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
environment: production
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- name: Terraform Apply
run: |
terraform init
terraform apply -auto-approve
working-directory: terraform/environments/production
コスト管理の自動化
マルチクラウド環境ではコスト管理が複雑になる。InfracostをCIパイプラインに組み込み、PR時点でコスト影響を可視化する。
# CIパイプラインでの実行例
$ infracost breakdown --path=terraform/environments/production
Project: terraform/environments/production
Name Monthly Qty Unit Monthly Cost
─────────────────────────────────────────────────────────────────────
aws_eks_cluster.main
└─ EKS cluster 730 hours $73.00
aws_eks_node_group.main
└─ 3 x m5.xlarge (on-demand) 2,190 hours $630.72
google_container_cluster.main
└─ Autopilot (per vCPU hour) 730 hours $116.80
└─ Autopilot (per GB hour) 730 hours $12.78
OVERALL TOTAL $833.30
まとめ: マルチクラウドは手段であって目的ではない
Terraformによるマルチクラウド管理は強力だが、すべてのチームに必要というわけではない。マルチクラウドを採用する前に、以下の点を検討すべきだ。
- 本当にマルチクラウドが必要なのか、単一クラウドのマルチリージョンで十分ではないか
- チームにマルチクラウドを管理するスキルセットがあるか
- 運用コストの増大を許容できるか
- 各クラウドのマネージドサービスを使い分けるのか、それともクラウドアグノスティックな構成にするのか
マルチクラウドは銀の弾丸ではない。しかし、適切に設計・運用すれば、ビジネスの回復力とコスト効率を大幅に向上させることができる。Terraformはその実現を支える最も実用的なツールの一つだ。
次回は、この構成にObservabilityレイヤー(Prometheus, Grafana, OpenTelemetry)を追加する方法を解説する予定だ。








# comment