Terraform Multi-Cloud Deployment: จัดการ Infrastructure ข้าม Cloud Provider

การ deploy โครงสร้างพื้นฐานข้ามหลาย cloud provider พร้อมกันในโปรเจกต์เดียวเป็นสิ่งที่ Terraform ออกแบบมาให้ทำได้ดีที่สุด เพราะใช้ภาษา HCL เดียวกันควบคุมได้ทั้ง AWS, Azure, Google Cloud, DigitalOcean, Cloudflare และอื่น ๆ ผ่าน provider ที่แยกกัน ทำให้วางสถาปัตยกรรมแบบ multi-cloud หรือ hybrid-cloud ได้โดยไม่ต้องเรียนรู้ tooling ใหม่

บทความนี้อธิบายรูปแบบการเขียน config สำหรับ multi-cloud, การจัดการ credential แยกแต่ละ provider, การออกแบบ module ที่ใช้ซ้ำข้าม cloud, การจัดการ state file, และ use case ที่พบบ่อย พร้อมข้อควรระวังสำหรับทีมที่เพิ่งเริ่มออกแบบ multi-cloud ด้วย IaC

ทำไมต้อง Multi-Cloud

เหตุผลที่ทำให้องค์กรเลือกใช้หลาย cloud พร้อมกัน

  • ลด vendor lock-in เพื่อไม่ต้องผูกขาดกับ provider รายเดียว
  • ใช้ประโยชน์จาก service ที่โดดเด่นของแต่ละ provider (เช่น BigQuery ของ GCP, Azure AD, S3 ของ AWS)
  • กระจายความเสี่ยงเมื่อ cloud ใด cloud หนึ่งล่ม ระบบยังทำงานต่อได้
  • ปฏิบัติตามข้อกำหนด data sovereignty ที่บังคับให้ข้อมูลบางประเภทต้องอยู่ในผู้ให้บริการเฉพาะ
  • ใช้ edge network ของ Cloudflare คู่กับ origin server บน cloud อื่น

ประกาศหลาย Provider ในโปรเจกต์เดียว

Terraform รองรับการประกาศ provider หลายตัวใน root module เดียว ตัวอย่างการใช้ AWS ร่วมกับ Cloudflare

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
    cloudflare = {
      source  = "cloudflare/cloudflare"
      version = "~> 4.0"
    }
  }
}

provider "aws" {
  region = "ap-southeast-1"
}

provider "cloudflare" {
  api_token = var.cloudflare_api_token
}

เมื่อรัน terraform init ระบบจะดาวน์โหลด provider binary ของทั้งสองตัว และ terraform plan/apply จะประสาน resource ข้าม provider ได้ใน run เดียวกัน

Provider Alias สำหรับหลาย Region หรือหลาย Account

หากต้องใช้ provider เดียวกันคนละ account หรือคนละ region ให้ใช้ alias

provider "aws" {
  alias  = "primary"
  region = "ap-southeast-1"
}

provider "aws" {
  alias  = "dr"
  region = "ap-southeast-2"
}

resource "aws_s3_bucket" "primary_log" {
  provider = aws.primary
  bucket   = "myapp-log-sg"
}

resource "aws_s3_bucket" "dr_log" {
  provider = aws.dr
  bucket   = "myapp-log-sydney"
}

แนวทางนี้เหมาะกับการทำ disaster recovery แบบ cross-region หรือการแยก billing account สำหรับแต่ละ business unit

การจัดการ Credential

ห้ามเก็บ API key ของ cloud provider ไว้ใน code หรือ state file โดยตรง แนวทางที่ปลอดภัย

  • AWS: ใช้ IAM role + AWS SSO หรือ OIDC federation จาก CI/CD
  • Azure: ใช้ Service Principal + client_secret เก็บใน Key Vault หรือใช้ Managed Identity
  • GCP: ใช้ Service Account key หรือ Workload Identity Federation สำหรับ CI/CD
  • Cloudflare: ใช้ API Token ที่จำกัดสิทธิ์ (ไม่ใช่ Global API Key)
  • DigitalOcean: ใช้ Personal Access Token เก็บใน secret manager

ใน CI/CD pipeline แนะนำ OIDC federation ที่สุด เพราะไม่ต้องเก็บ long-lived credential ไว้เลย token จะหมดอายุภายในไม่กี่นาที

Pattern การออกแบบ Multi-Cloud Module

Pattern A — แยก root module ต่อ provider

เขียน root module แยกเป็น envs/aws/, envs/gcp/, envs/cloudflare/ แต่ละ directory มี state file แยก เหมาะเมื่อแต่ละ cloud มีวงจร lifecycle ต่างกัน เช่น AWS ปล่อยใหม่ทุกวัน แต่ Cloudflare แก้ไขนาน ๆ ครั้ง

ข้อดี: blast radius เล็ก, apply พร้อมกันไม่ชนกัน

ข้อเสีย: ส่งค่าระหว่าง cloud ต้องผ่าน remote state data source หรือ export/import ด้วยมือ

Pattern B — root module เดียวรวม provider ทั้งหมด

เหมาะเมื่อ resource ระหว่าง cloud มีการอ้างอิงกันตรง ๆ เช่น Cloudflare DNS record ต้องชี้ไปยัง AWS ALB DNS name ที่เพิ่งสร้าง สามารถใช้ output ของ aws resource ป้อนเข้า cloudflare_record ได้ทันที

resource "aws_lb" "web" {
  name               = "myapp-alb"
  load_balancer_type = "application"
  subnets            = aws_subnet.public[*].id
}

resource "cloudflare_record" "web" {
  zone_id = var.cloudflare_zone_id
  name    = "app"
  type    = "CNAME"
  value   = aws_lb.web.dns_name
  proxied = true
}

ข้อดี: reference ข้าม cloud ตรง ๆ ไม่ต้องผ่าน remote state

ข้อเสีย: state file ใหญ่ขึ้น, plan/apply นานขึ้น, เสี่ยง lock กัน

การส่งค่าข้าม State File (Remote State Data)

ถ้าใช้ Pattern A ให้ root module หนึ่งอ่าน output จากอีก state file ผ่าน terraform_remote_state data source

data "terraform_remote_state" "aws_network" {
  backend = "s3"
  config = {
    bucket = "myapp-tfstate"
    key    = "aws/network.tfstate"
    region = "ap-southeast-1"
  }
}

resource "cloudflare_record" "api" {
  zone_id = var.cloudflare_zone_id
  name    = "api"
  type    = "A"
  value   = data.terraform_remote_state.aws_network.outputs.public_ip
}

การใช้ remote state data ทำให้แต่ละโปรเจกต์ทำงานแยกได้แต่ยังเชื่อมโยงค่ากันได้อัตโนมัติ

Use Case ที่พบบ่อย

Web application บน AWS + CDN Cloudflare

Origin server รันบน EC2 หรือ ECS, ใช้ Cloudflare เป็น CDN + WAF + DDoS protection — เขียน resource ALB + Cloudflare CNAME ใน project เดียว

Data Warehouse บน GCP + Application บน AWS

Application layer รันบน AWS EKS ส่งข้อมูลไปเก็บใน BigQuery ผ่าน Pub/Sub — เขียน Terraform สร้าง IAM role ทั้งสองฝั่งให้ cross-account access ถูกต้อง

Disaster Recovery ข้าม Cloud

Production อยู่บน AWS, DR site อยู่บน Azure — ใช้ Terraform กำหนด infra ทั้งสองให้ schema เหมือนกัน พร้อม failover script

Hybrid Cloud กับ on-premise

On-premise VMware หรือ Nutanix มี provider Terraform ของตัวเอง สามารถ deploy infra ทั้งใน data center และ public cloud ในโปรเจกต์เดียวได้

การจัดการ State File สำหรับ Multi-Cloud

แนวทางเก็บ state file ที่แนะนำ

  • ใช้ backend เดียวทั้งโปรเจกต์ (เช่น S3 + DynamoDB lock) แม้จะ deploy ไปหลาย cloud
  • หรือใช้ Terraform Cloud workspace เป็น backend กลาง บริหารง่ายและมี audit log ในตัว
  • แยก key/path ใน backend ตาม environment และ scope เช่น prod/aws-network, prod/gcp-data
  • เปิด encryption at rest และ versioning เพื่อ rollback ได้ในกรณี state corrupt

CI/CD สำหรับ Multi-Cloud

Pipeline ต้องรองรับ credential หลายชนิด ที่นิยมคือใช้ matrix job ให้แต่ละ cloud มี step apply ของตัวเอง ตัวอย่าง GitHub Actions

jobs:
  terraform-apply:
    strategy:
      matrix:
        stack: [aws, gcp, cloudflare]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: hashicorp/setup-terraform@v3
      - name: Apply ${{ matrix.stack }}
        working-directory: envs/${{ matrix.stack }}
        run: |
          terraform init
          terraform apply -auto-approve

แต่ละ job ใช้ OIDC federation แยกตาม cloud ทำให้ไม่ต้องเก็บ key ใด ๆ ใน secret store ของ CI/CD เลย

ข้อควรระวัง

  • อย่าพยายามสร้าง abstraction layer ที่ปิด native feature ของแต่ละ cloud เพราะจะสูญเสียข้อดีของ cloud นั้น
  • Dependency ข้าม cloud ทำให้ blast radius กว้าง ถ้า AWS ล่มแล้ว Cloudflare record ชี้ไม่ถูกต้องก็แก้ลำบาก
  • ราคาต่อ cloud ต่างกัน ต้องมี cost monitoring แยก เช่น AWS Cost Explorer, GCP Billing, Azure Cost Management
  • การทำ network peering ข้าม cloud มีค่า egress สูงและ latency สูง ควรออกแบบให้ traffic หลักอยู่ใน cloud เดียว
  • Provider version แต่ละตัว release ไม่พร้อมกัน ต้องจัดการ version pinning อย่างระวัง
  • Multi-cloud ไม่ใช่คำตอบของทุกปัญหา ถ้าองค์กรไม่มี platform team พร้อม ให้เลือก cloud หลักก่อน

สรุป

Terraform ทำให้การเขียน infra แบบ multi-cloud เป็นเรื่องที่จัดการได้ผ่านภาษา HCL เดียวกัน โดยใช้ provider หลายตัว, alias สำหรับ account/region, และ pattern root-module ที่เลือกตามความแนบแน่นของ dependency ข้าม cloud หัวใจคือการจัดการ credential อย่างปลอดภัยผ่าน OIDC federation และการวางแผน state file ให้เหมาะกับขนาดทีม

ก่อนจะไป multi-cloud เต็มตัว แนะนำเริ่มจาก pattern ง่าย ๆ เช่น AWS + Cloudflare หรือ GCP + Cloudflare ก่อน เมื่อทีมคุ้นกับการจัดการ provider หลายตัวแล้วค่อยขยายไป cloud ที่สาม — การ over-engineer ตั้งแต่ต้นมักสร้าง overhead มากกว่าประโยชน์ที่ได้