Provisioning infrastruktur cloud secara reproducible: EC2, VPC, Security Group, S3, dan IAM di AWS; Compute Engine, GCS, dan firewall di Google Cloud; hingga Virtual Machine dan Virtual Network di Azure, lengkap dengan pola multi-cloud.

Setelah di episode 25 sebelumnya kita membangun monitoring & observability stack — Prometheus, Grafana, dan logging — pada episode kali ini kita akan pindah ke lingkungan tempat sebagian besar infrastruktur modern hidup: cloud.
Mari kita ingat kembali perjalanan kita. Di episode 3 dan 21 kita sudah berkenalan dengan dynamic inventory untuk cloud: plugin aws_ec2, gcp_compute, dan azure_rm yang membuat daftar server terisi otomatis mengikuti keadaan cloud. Di episode 19 kita melihat kolaborasi Ansible dan Terraform. Sekarang saatnya kita membahas sisi provisioning itu sendiri: membuat server, jaringan, storage, dan akses di tiga cloud provider terbesar — AWS, Google Cloud, dan Azure — menggunakan Ansible.
Kenapa ini penting? Bayangkan seorang engineer yang membuka AWS Console, klik "Launch Instance", lalu memilih opsi-opsi dengan mouse untuk membuat satu server. Sekarang kalikan dengan 20 server, 3 environment, dan 2 tim. Semua klik itu tidak tercatat, tidak bisa di-review, dan hampir pasti hasilnya berbeda-beda satu sama lain — inilah cikal bakal configuration drift dan tagihan cloud yang tidak terkendali. Di sisi lain, jika provisioning ditulis sebagai playbook Ansible, seluruh proses bisa di-review di Pull Request, direproduksi kapan saja, dan diverifikasi idempotent.
Perlu ditegaskan juga peran yang tepat: Terraform lebih unggul untuk lifecycle dan state infrastruktur, sedangkan Ansible unggul untuk konfigurasi dan deployment aplikasi. Namun banyak tim memakai Ansible untuk provisioning juga — terutama saat butuh satu bahasa yang sama untuk seluruh stack, atau saat volume perubahan infrastruktur masih kecil. Kita akan membahas pola ini secara praktis, termasuk bagaimana Ansible menangani multi-cloud secara portabel.
Setiap cloud provider punya collection resmi yang dikelola komunitas atau vendor:
| Cloud | Collection | Plugin Inventory | Modul Provisioning Utama |
|---|---|---|---|
| AWS | amazon.aws, community.aws | aws_ec2 | ec2_instance, ec2_security_group, ec2_vpc_net, s3_bucket, iam_user, rds_instance |
| Google Cloud | google.gcp | gcp_compute | gcp_compute_instance, gcp_compute_network, gcp_compute_firewall, gcp_storage_bucket, gcp_sql_instance |
| Azure | azure.azcollection | azure_rm | azure_rm_virtualmachine, azure_rm_virtualnetwork, azure_rm_networksecuritygroup, azure_rm_storageaccount, azure_rm_sqlserver |
Install ketiganya:
ansible-galaxy collection install amazon.aws community.aws google.gcp azure.azcollectionNote
Collection azure.azcollection memiliki persyaratan tambahan: sejumlah library Python (Azure SDK) yang harus diinstall lewat pip menggunakan file requirements-azure.txt yang ada di dalam collection tersebut. Tanpa itu, modul Azure akan gagal dengan error ModuleNotFoundError. Cek dokumentasi collection saat menginstall.
Setiap provider punya cara autentikasinya sendiri, dan semuanya tidak boleh disimpan plaintext di playbook atau inventory:
AWS_ACCESS_KEY_ID dan AWS_SECRET_ACCESS_KEY, atau profile dari ~/.aws/credentials. Di playbook bisa lewat parameter access_key/secret_key yang diambil dari Vault.service_account_file) atau Application Default Credentials (auth_kind: application).AZURE_SUBSCRIPTION_ID, AZURE_CLIENT_ID, AZURE_SECRET, AZURE_TENANT (service principal), atau kombinasi ad_user + password.Warning
Kredensial cloud adalah kunci ke seluruh akun, bukan hanya satu server. Jangan pernah menaruh secret_key AWS atau file service account GCP di repository. Simpan di Ansible Vault (episode 14) atau di secret store CI/CD (episode 19), dan gunakan akun dengan IAM policy least privilege — bukan root account.
Semua playbook di episode ini diasumsikan dijalankan dari localhost (control node) — provisioning dilakukan via API cloud, bukan SSH:
---
# group_vars/aws.yml
cloud_provider: aws
aws_region: ap-southeast-1
aws_ami: ami-0abcdef1234567890
instance_name: app-01
instance_size: t3.microamazon.aws.ec2_instanceMari mulai dari AWS. Modul ec2_instance adalah penerus modul lama ec2 yang deprecated — ia lebih idempotent dan mendukung tags dengan baik. Playbook berikut membuat dua instance sekaligus:
- name: Provision instance EC2
hosts: localhost
gather_facts: false
vars:
aws_region: ap-southeast-1
ami_id: ami-0abcdef1234567890
tasks:
- name: Launch instance EC2
amazon.aws.ec2_instance:
name: "{{ item }}"
key_name: arman-keypair
image_id: "{{ ami_id }}"
instance_type: t3.micro
region: "{{ aws_region }}"
security_groups:
- app-sg
vpc_subnet_id: subnet-0abc1234
associate_public_ip_address: true
tags:
Environment: production
Role: app
ManagedBy: ansible
state: running
wait: true
loop:
- app-01
- app-02Yang perlu diperhatikan:
image_id (AMI) bersifat region-specific. AMI yang valid di us-east-1 belum tentu ada di ap-southeast-1. Selalu parameterisasi dan pin ke versi — jangan pakai "latest" yang bisa berubah diam-diam.tags bukan sekadar pelengkap — tag adalah cara kalian melacak biaya, mengelompokkan resource, dan (krusial!) cara plugin dynamic inventory aws_ec2 memilih host. Instance tanpa tag Environment akan hilang dari radar biaya dan inventory.wait: true membuat playbook menunggu instance benar-benar running (dan IP tersedia) sebelum melanjutkan ke task berikutnya — misalnya langsung menjalankan playbook konfigurasi ke instance tersebut.Security Group adalah firewall virtual di level instance. Dengan Ansible, aturannya menjadi deklaratif dan bisa di-review:
- name: Kelola security group
hosts: localhost
gather_facts: false
vars:
aws_region: ap-southeast-1
vpc_id: vpc-0123456789abcdef0
tasks:
- name: Buat security group aplikasi
amazon.aws.ec2_security_group:
name: app-sg
description: "Security group untuk aplikasi web"
region: "{{ aws_region }}"
vpc_id: "{{ vpc_id }}"
rules:
- proto: tcp
ports: 22
cidr_ip: "{{ admin_cidr }}"
- proto: tcp
ports: 80
cidr_ip: 0.0.0.0/0
- proto: tcp
ports: 443
cidr_ip: 0.0.0.0/0
rules_egress:
- proto: all
cidr_ip: 0.0.0.0/0Perhatikan penggunaan {{ admin_cidr }} untuk akses SSH — membatasi SSH ke IP kantor/tim (misal 203.0.113.0/24) adalah salah satu praktik keamanan termurah dengan dampak terbesar. VPC dan subnet sendiri dikelola modul ec2_vpc_net dan ec2_vpc_subnet dengan pola idempotent yang sama: tentukan cidr_block, state: present, dan Ansible akan menjaga state-nya.
S3 bucket dikelola modul s3_bucket — termasuk versi, enkripsi, dan tags:
- name: Kelola S3 bucket
hosts: localhost
gather_facts: false
tasks:
- name: Buat bucket penyimpanan backup
amazon.aws.s3_bucket:
name: "{{ backup_bucket }}"
region: "{{ aws_region }}"
state: present
versioning: enabled
encryption: "AES256"
tags:
Environment: production
Purpose: database-backup
- name: Buat user IAM untuk backup service
amazon.aws.iam_user:
name: svc-backup
state: presentIAM (iam_user, iam_role, iam_policy, iam_managed_policy) adalah fondasi keamanan AWS. Pola yang direkomendasikan di dunia nyata: buat role khusus (misal role-backup) dengan policy sesempit mungkin yang hanya mengizinkan s3:PutObject pada bucket backup, lalu attach role tersebut ke instance — alih-alih menyimpan access key panjang-panjang di dalam instance. Untuk RDS, ELB, dan Auto Scaling Group, modul yang relevan adalah rds_instance, elb_classic_lb/elb_application_lb, dan autoscaling_group (semua di community.aws) — polanya identik: deklaratif, idempotent, berbasis state.
Untuk GCP, collection google.gcp menyediakan modul dengan pola resource-based (satu modul per resource). Contoh membuat instance Compute Engine:
- name: Provision instance Compute Engine
hosts: localhost
gather_facts: false
vars:
gcp_project: my-company-project
gcp_zone: asia-southeast1-a
gcp_sa_file: /opt/keys/gcp-sa.json
tasks:
- name: Buat instance app-01
google.gcp.gcp_compute_instance:
name: app-01
project: "{{ gcp_project }}"
zone: "{{ gcp_zone }}"
machine_type: e2-small
auth_kind: serviceaccount
service_account_file: "{{ gcp_sa_file }}"
disks:
- auto_delete: true
boot: true
initialize_params:
source_image: "projects/ubuntu-os-cloud/global/images/family/ubuntu-2404-lts"
network_interfaces:
- network: "projects/{{ gcp_project }}/global/networks/default"
access_configs:
- name: External NAT
type: ONE_TO_ONE_NAT
tags:
items: ["http-server"]
metadata:
ssh-keys: "arman:{{ vault_ssh_pubkey }}"
state: presentBeberapa hal khas GCP:
asia-southeast1-a), sementara resource regional (VPC, firewall) hidup di region. Mencampur keduanya adalah sumber kebingungan paling umum.source_image menggunakan image family — mirip konsep "latest", tapi distabilkan per family (misal ubuntu-2404-lts), sehingga dapat update OS terbaru tanpa mengubah playbook.auth_kind: serviceaccount + service_account_file — autentikasi resmi untuk automation. File JSON service account wajib di-Vault.VPC network (gcp_compute_network) dan firewall rules (gcp_compute_firewall) mengikuti pola yang sama, sementara GCS buckets memakai gcp_storage_bucket dan Cloud SQL memakai gcp_sql_instance. Plugin dynamic inventory gcp_compute (episode 21) kemudian membaca semua instance ini otomatis untuk playbook konfigurasi.
Azure memakai collection azure.azcollection dengan modul berprefiks azure_rm_. Contoh membuat Virtual Machine:
- name: Provision Virtual Machine di Azure
hosts: localhost
gather_facts: false
vars:
azure_rg: rg-app
azure_location: southeastasia
tasks:
- name: Buat VM app-01
azure.azcollection.azure_rm_virtualmachine:
resource_group: "{{ azure_rg }}"
name: app-01
vm_size: Standard_B2s
admin_username: arman
ssh_public_key_file: ~/.ssh/id_ed25519.pub
image:
offer: UbuntuServer
publisher: Canonical
sku: "22.04-LTS"
version: latest
network_interfaces:
- name: app-01-nic
virtual_network:
name: vnet-app
subnet:
name: snet-app
security_group:
name: nsg-app
state: presentPerhatikan bahwa Azure VM memerlukan infrastruktur jaringan yang eksplisit sejak awal: virtual_network, subnet, dan security_group semuanya dirujuk (dan bisa dibuat otomatis) oleh modul VM. Resource jaringan itu sendiri dikelola modul azure_rm_virtualnetwork, azure_rm_subnet, dan azure_rm_networksecuritygroup. Storage accounts memakai azure_rm_storageaccount dan Azure SQL memakai azure_rm_sqlserver. Pola "resource dikelola satu modul, dirujuk oleh modul lain" ini konsisten di seluruh collection Azure.
Tip
Karena kredensial Azure sering diisi lewat variabel environment (AZURE_SUBSCRIPTION_ID, AZURE_CLIENT_ID, AZURE_SECRET, AZURE_TENANT), di CI/CD kalian bisa meletakkannya di secret store pipeline. Di lokal, export di .env yang tidak di-commit — jangan di-hardcode di playbook.
Sekarang pertanyaan besarnya: bagaimana jika perusahaan kalian memakai lebih dari satu cloud — atau ingin bisa berpindah? Ansible menawarkan dua pola pelengkap:
1. Variabel abstraksi + conditional when. Definisikan satu variabel cloud_provider, lalu jalankan task cloud-spesifik secara kondisional. Ini pola paling sederhana dan paling jelas:
- name: Provision instance sesuai cloud provider
hosts: localhost
gather_facts: false
vars:
cloud_provider: aws
instance_name: app-01
instance_size:
aws: t3.micro
gcp: e2-small
azure: Standard_B2s
tasks:
- name: Launch instance di AWS
amazon.aws.ec2_instance:
name: "{{ instance_name }}"
image_id: "{{ aws_ami_id }}"
instance_type: "{{ instance_size[cloud_provider] }}"
region: "{{ aws_region }}"
when: cloud_provider == "aws"
- name: Launch instance di Google Cloud
google.gcp.gcp_compute_instance:
name: "{{ instance_name }}"
project: "{{ gcp_project }}"
zone: "{{ gcp_zone }}"
machine_type: "{{ instance_size[cloud_provider] }}"
when: cloud_provider == "gcp"
- name: Launch VM di Azure
azure.azcollection.azure_rm_virtualmachine:
resource_group: "{{ azure_rg }}"
name: "{{ instance_name }}"
vm_size: "{{ instance_size[cloud_provider] }}"
when: cloud_provider == "azure"2. Dictionary lookup. Nilai yang bersifat "mapping per provider" (ukuran instance, AMI, zona) disimpan sebagai dictionary, dan dipilih dengan {{ instance_size[cloud_provider] }}. Ini menjaga playbook tetap DRY untuk nilai-nilai umum, sementara task spesifik provider tetap eksplisit dengan when.
Tapi ada peringatan penting. Abstraksi penuh antar cloud (satu playbook untuk semua provider) memang menggiurkan, namun ada trade-off:
| Pendekatan | Kelebihan | Kekurangan |
|---|---|---|
| Satu playbook multi-cloud | Portabilitas maksimal, konsistensi visibilitas | Fitur spesifik cloud tidak tersedia (AMI/AMR, autoscaling AWS, dsb); kode jadi kompleks |
| Playbook per provider | Mendalam, memanfaatkan fitur unik tiap cloud | Duplikasi; tidak ada "satu sumber" untuk pola yang sama |
Important
Realita industri: hampir tidak ada tim yang benar-benar "portable antar cloud". Sebagian besar justru memakai dua cloud secara bersamaan untuk alasan strategis (redundancy, harga, kompliansi data) — bukan untuk migrasi cepat. Pola yang sehat adalah: simpan hal yang memang sama (nama aplikasi, region, tags, alur provisioning) dalam variabel bersama, dan biarkan task yang beda antar provider hidup terpisah. Jangan paksakan abstraksi total — itu biasanya berakhir dengan playbook yang membingungkan semua orang.
Pola multi-cloud yang paling umum di dunia nyata sebenarnya bukan abstraksi, melainkan kombinasi dynamic inventory: jalankan playbook yang sama terhadap grup host dari aws_ec2, gcp_compute, dan azure_rm sekaligus, dengan group_vars per provider membawa perbedaan konfigurasi (paket manager, daemon, path). Ini memanfaatkan kekuatan asli Ansible — satu playbook, banyak tipe host — tanpa memaksakan keseragaman modul provisioning.
1. Hardcode AMI / image ID
AMI berbeda per region; image GCP per family; SKU Azure per publikasi. Nilai hardcode = playbook yang rusak saat region diganti. Parameterisasi dan versioning.
2. Kredensial cloud ter-commit ke Git
Access key AWS atau service account GCP di repository adalah bom waktu. Selalu Vault atau secret store — dan ingat: Git tidak pernah lupa (episode 14).
3. Provisioning tanpa wait
Tanpa wait: true, playbook meneruskan ke task konfigurasi (SSH) sebelum instance siap — hasilnya Connection refused. Untuk S3/bucket yang butuh propagasi, gunakan modul retry (until + retries dari episode 9).
4. Lupa tags untuk billing & inventory
Instance tanpa tags sulit dilacak biayanya dan sulit dipilih plugin dynamic inventory. Tetapkan standar tag (Environment, Team, CostCenter) sejak provisioning pertama.
5. Mencampur region/zone
VPC dibuat di region A, instance di zona region B — jaringan tidak ketemu. Selalu deklarasikan region/zone/location secara eksplisit dan konsisten.
6. Mengabaikan dependensi Python collection
Terutama Azure: lupa menjalankan pip install -r requirements-azure.txt membuat semua modul azure_rm_* gagal. Dokumentasikan persyaratan ini di README proyek.
7. Abstraksi berlebihan
Satu playbook yang mencoba menyatukan semua cloud sering menghasilkan kondisi when bertumpuk yang sulit dibaca dan fitur terpotong. Abstraksi secukupnya; manfaatkan perbedaan.
Pada episode ini kita telah membahas provisioning cloud dengan Ansible: collection amazon.aws/community.aws untuk EC2, Security Group, VPC, S3, dan IAM; google.gcp untuk Compute Engine, jaringan, dan firewall; azure.azcollection untuk Virtual Machine, Virtual Network, dan Network Security Group; serta pola multi-cloud berupa variabel abstraksi dan conditional when — lengkap dengan trade-off yang harus dipahami sebelum memutuskan sejauh mana abstraksi diterapkan.
Kuncinya: provisioning cloud yang ditulis sebagai kode membuat infrastruktur teraudit, tereproduksi, dan idempotent — tidak lagi bergantung pada klik-klik di console dan ingatan manusia. Ditambah dynamic inventory dari episode 21, kalian kini memiliki siklus lengkap: provisioning → konfigurasi → monitoring.
Di episode 27 selanjutnya, kita akan mengamankan semua yang telah kita bangun: Security Hardening & Compliance Automation — implementasi CIS benchmarks, hardening SSH dan firewall, patch management otomatis, hingga compliance as code dengan STIG dan tools seperti OpenSCAP dan Lynis. Pastikan tetap semangat!