Di episode ini kita akan merancang stack multi-cloud lengkap dengan OpenTofu: AWS VPC dan EKS cluster, GCP Cloud Storage bucket dan Service Account, Cloudflare DNS, hingga Helm releases dan workloads di Kubernetes. Kita juga melihat bagaimana dynamic provider iteration dan reusable modules menyederhanakan konfigurasi lintas cloud.

Di episode 16 sebelumnya kita membahas Policy as Code dengan OPA, di mana setiap plan OpenTofu diuji terhadap aturan Rego sebelum boleh di-apply. Sekarang pertanyaannya beranjak dari cara mengamankan menjadi cara membangun: bagaimana satu basis kode yang sama bisa mengelola layanan di beberapa cloud sekaligus?
Di episode 17 kali ini, kita akan merancang infrastruktur multi-cloud lengkap dengan OpenTofu. Kita akan membangun (1) AWS VPC dan EKS cluster, (2) GCP Cloud Storage bucket dan Service Account, (3) Cloudflare DNS records dan SSL rules, serta (4) Helm releases dan workloads di Kubernetes — semuanya dideklarasikan dalam satu bahasa HCL yang sama.
Multi-cloud bukan sekadar tren. Ada alasan bisnis yang kuat di baliknya: menghindari vendor lock-in, memanfaatkan layanan terbaik di tiap cloud, serta memenuhi persyaratan geografis dan regulasi tertentu. Namun multi-cloud juga membawa kompleksitas besar — apalagi jika dikelola manual lewat konsol yang berbeda-beda.
OpenTofu menjadikan seluruh cloud tampak seperti satu bahasa yang seragam. VPC di AWS, bucket di GCP, dan DNS di Cloudflare hanyalah blok HCL yang bisa dideklarasikan bersebelahan dalam satu repository, di-review dengan satu proses yang sama, dan diaudit dengan satu jejak yang jelas.
Setiap cloud membutuhkan provider yang dideklarasikan di required_providers:
terraform {
required_version = ">= 1.8"
required_providers {
aws = { source = "hashicorp/aws", version = "~> 5.0" }
google = { source = "hashicorp/google", version = "~> 6.0" }
cloudflare = { source = "cloudflare/cloudflare", version = "~> 4.0" }
kubernetes = { source = "hashicorp/kubernetes", version = "~> 2.0" }
helm = { source = "hashicorp/helm", version = "~> 2.0" }
}
}Selanjutnya kita konfigurasikan instansi provider untuk masing-masing cloud. Ketiga blok ini bisa digabung dalam satu file providers.tf:
provider "aws" {
region = var.aws_region
}Tip
Untuk kredensial, jangan hardcode di kode. AWS memakai chain credential standar (environment variable atau OIDC), GCP memakai Application Default Credentials, dan Cloudflare memakai API token yang disimpan di secret store — seperti yang kita bahas di episode 15.
Bagian pertama dari stack kita adalah jaringan dan kontrol plane Kubernetes di AWS. Kita gunakan modul community agar tidak menulis ratusan baris resource dasar:
module "vpc" {
source = "registry.opentofu.org/terraform-aws-modules/vpc/aws"
version = "~> 5.0"
name = "${var.env}-vpc"
cidr = "10.0.0.0/16"
}
module "eks" {
source = "registry.opentofu.org/terraform-aws-modules/eks/aws"
version = "~> 20.0"
cluster_name = "${var.env}-eks"
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnets
cluster_version = "1.31"
}Modul vpc membuat jaringan lengkap dengan subnet publik dan privat. Modul eks membangun control plane EKS dan menghubungkannya ke subnet privat. Ketika selesai, kita bisa mengambil kubeconfig dari output modul dan mengarahkan provider Kubernetes ke cluster ini.
Di sisi GCP, kita siapkan bucket untuk aset statis beserta versi (versioning) dan Service Account untuk workload:
resource "google_storage_bucket" "assets" {
name = "${var.env}-static-assets"
location = "ASIA-SOUTHEAST2"
force_destroy = false
versioning {
enabled = true
}
}
resource "google_service_account" "app" {
account_id = "app-svc"
display_name = "Application Service Account"
}Bucket dibuat dengan location di Asia Tenggara dan versi aktif agar objek yang terhapus masih bisa dipulihkan. Service Account app-svc bisa dihubungkan ke workload Kubernetes melalui Workload Identity Federation, sehingga Pod di EKS bisa mengakses bucket GCP tanpa menyimpan kunci JSON.
Selanjutnya kita arahkan traffic publik ke aplikasi. Rekaman DNS menunjuk nama domain ke endpoint cluster, dan aturan SSL memaksa semua traffic memakai HTTPS:
resource "cloudflare_record" "app" {
zone_id = var.cloudflare_zone_id
name = "app"
content = module.eks.cluster_endpoint
type = "CNAME"
proxied = true
}
resource "cloudflare_ruleset" "https" {
zone_id = var.cloudflare_zone_id
name = "redirect-https"
kind = "zone"
phase = "http_request_dynamic_redirect"
rules {
action = "redirect"
description = "Alihkan HTTP ke HTTPS"
expression = "starts_with(http.request.uri, \"http:\")"
enabled = true
}
}Dengan proxied = true, traffic mengalir melalui jaringan Cloudflare sehingga bisa dimanfaatkan untuk caching, WAF, dan SSL. Aturan cloudflare_ruleset memastikan tidak ada pengunjung yang mengakses layanan lewat HTTP polos.
Terakhir, kita deploy aplikasi ke cluster EKS. Provider kubernetes dan helm membaca kubeconfig dari output modul EKS, lalu kita pasang ingress controller sebagai Helm release dan aplikasi sebagai workload:
resource "helm_release" "nginx_ingress" {
name = "ingress-nginx"
repository = "https://kubernetes.github.io/ingress-nginx"
chart = "ingress-nginx"
version = "4.10.0"
namespace = "ingress"
create_namespace = true
set {
name = "controller.publishService.enabled"
value = "true"
}
}resource "kubernetes_deployment" "app" {
metadata {
name = "myapp"
namespace = "default"
}
spec {
replicas = 3
selector {
match_labels = {
app = "myapp"
}
}
template {
metadata {
labels = { app = "myapp" }
}
spec {
container {
image = "ghcr.io/armandwipangestu/myapp:1.0.0"
name = "myapp"
}
}
}
}
}helm_release mengelola lifecycle chart secara penuh, sementara kubernetes_deployment mendefinisikan workload. Kombinasi keduanya membuat seluruh aplikasi, dari control plane hingga deployment, dikelola oleh satu tool.
Fitur eksklusif OpenTofu dari episode 7 — dynamic provider iteration — sangat berguna di arsitektur multi-cloud. Jika kita ingin EKS berjalan di dua region sekaligus, kita tidak perlu menyalin blok provider:
locals {
regions = {
singapore = { region = "ap-southeast-1" }
jakarta = { region = "ap-southeast-3" }
}
}
provider "aws" {
region = each.value.region
for_each = local.regions
}Satu blok provider yang sama menghasilkan dua instansi sekaligus, sesuatu yang mustahil dilakukan di Terraform. Gabungkan dengan for_each pada resource dan kalian bisa menyebar stack ke banyak region dengan kode yang sangat ringkas.
Warning
Menaruh semua cloud dalam satu root module memang menarik, tapi blasting radius-nya besar: satu tofu apply bisa menyentuh banyak cloud sekaligus. Praktik umum adalah memisahkan setiap cloud ke root module sendiri, misalnya infra/aws, infra/gcp, infra/cloudflare, dan infra/k8s, dengan state yang terpisah dan dihubungkan lewat tofu output atau data source remote state.
Pada episode 17 ini, kita telah merancang infrastruktur multi-cloud lengkap: VPC dan EKS di AWS, bucket dan Service Account di GCP, DNS dan aturan SSL di Cloudflare, serta Helm releases dan workloads di Kubernetes. Kita juga melihat bagaimana dynamic provider iteration menangani multi-region dengan elegan.
Key takeaway:
required_providers mendeklarasikan provider, blok provider mengonfigurasinya.google_storage_bucket dan google_service_account membangun fondasi layanan GCP.cloudflare_record mengarahkan DNS, cloudflare_ruleset memaksa HTTPS.helm_release mengelola chart, kubernetes_deployment mendefinisikan workload.Kemampuan memprovisioning multi-cloud adalah kekuatan besar, tapi membawa tanggung jawab baru: memastikan realita cloud selalu sama dengan yang kita deklarasikan. Di episode 18 nanti, kita akan membahas Infrastructure Drift Detection & Auto-Remediation — bagaimana mendeteksi dan menangani penyimpangan yang terjadi di luar kendali kode. Sampai jumpa!