AWX & Tower

AWX & Tower #

Menjalankan Ansible dari command line bekerja baik untuk satu orang. Tapi saat tim tumbuh, muncul tantangan baru: siapa yang boleh menjalankan playbook apa ke lingkungan mana? Bagaimana history semua eksekusi dicatat? Bagaimana non-engineer bisa memicu deployment tanpa akses ke terminal? AWX (versi open source) dan Ansible Tower (sekarang bernama Red Hat Ansible Automation Platform / Automation Controller) menjawab semua ini — mereka adalah platform web untuk mengelola, menjadwalkan, dan mengontrol akses ke Ansible. Artikel ini membahas kapan kita membutuhkan platform ini, arsitekturnya, konsep intinya, dan pola yang harus kita hindari saat mengimplementasikannya.

AWX vs Ansible Tower — Perbandingan Fitur #

AWX adalah upstream open source dari Ansible Tower. Kode yang sama, dua distribusi yang berbeda — keputusan memilih salah satu biasanya ditentukan oleh kebutuhan support, model lisensi, dan skala organisasi. Tabel berikut merangkum perbedaan yang paling relevan untuk keputusan produksi:

Aspek AWX (Open Source) Ansible Tower / Automation Controller (Red Hat)
Lisensi Apache 2.0 (gratis) Berbayar — subscription per node atau per Automation Platform
Dukungan resmi Komunitas (GitHub, forum, Matrix) Red Hat (SLA, patch keamanan, support 24/7)
Siklus rilis Sering (tiap ~2-3 minggu); bisa breaking Predictable (2-3 release mayor per tahun); backward compatible
Kompatibilitas OS Kubernetes (Operator), Docker Compose (dev) RHEL, OpenShift, Kubernetes via Operator
Update Otomatis Bisa, tapi tidak otomatis Subscription entitlement untuk stable channel
Integrasi ekosistem Red Hat Tidak ada Ansible Automation Hub (private Galaxy), Insights, Satellite
Cocok untuk Lab, homelab, tim kecil-menengah, eksplorasi fitur terbaru Perusahaan dengan tim besar, compliance requirement, kebutuhan support
Source code github.com/ansible/awx Proprietary (subset AWX + patches Red Hat)
Biaya tersembunyi Waktu engineer untuk maintain, troubleshoot upgrade Biaya subscription di awal, tapi predictable

Poin penting: AWX dan Tower berbagi codebase inti. Fitur yang kita lihat di AWX biasanya muncul di Tower dalam 6-12 bulan berikutnya dengan hardening tambahan. Tapi stability dan support adalah apa yang kita bayar di Tower — bukan fitur.

Decision Tree Memilih AWX atau Tower #

flowchart TD
    A{"Butuh support<br/>resmi Red Hat?"} -- "Ya" --> B{"Anggaran subscription<br/>tersedia?"}
    A -- "Tidak" --> C{"Tim kita mampu<br/>maintain upgrade<br/>AWX sendiri?"}
    B -- "Ya" --> D["Tower / Automation Controller"]
    B -- "Tidak" --> E["Evaluasi trade-off:<br/>support vs budget"]
    C -- "Ya" --> F{"Jumlah node<br/>> 100?"}
    C -- "Tidak" --> G["Pertimbangkan managed<br/>service atau konsultan"]
    F -- "Tidak" --> H["AWX"]
    F -- "Ya" --> I{"Butuh private<br/>Automation Hub?"}
    I -- "Ya" --> D
    I -- "Tidak" --> J{"Compliance requirement<br/>ketat?"}
    J -- "Ya" --> D
    J -- "Tidak" --> H

Gunakan decision tree ini sebagai starting point, bukan aturan absolut. Banyak tim besar tetap memilih AWX karena mereka punya engineer yang maintenance-nya masuk ke rutinitas tim. Dan banyak tim kecil justru memilih Tower karena mereka tidak ingin pusing dengan upgrade compatibility.

AWX versi 24.x (per awal 2026) sudah stabil untuk kebutuhan production ringan-menengah. Versi mayor baru biasanya membawa peningkatan ke UI, performance, dan integrasi Kubernetes — tapi migrasi antar versi mayor kadang butuh perhatian khusus karena ada perubahan API. Selalu baca release notes sebelum upgrade.

Arsitektur AWX: Control Plane dan Execution Plane #

AWX dipisah jadi dua plane yang punya tanggung jawab berbeda. Memahami pemisahan ini penting saat kita melakukan scaling AWX ke ribuan node atau saat troubleshoot kenapa job tertentu lambat.

flowchart LR
    subgraph CP["Control Plane — mengatur, tidak mengeksekusi"]
        API["REST API<br/>:8043 / :443"]
        WEB["Web UI<br/>nginx + Django"]
        TASKD[("Task System<br/>awx-manage")]
        SCHED[("Scheduler<br/>awx-manage schedule")]
        PG[("PostgreSQL<br/>state + audit log")]
    end

    subgraph EP["Execution Plane — menjalankan playbook"]
        EX1["Execution Node 1<br/>awx-execution-env"]
        EX2["Execution Node 2<br/>awx-execution-env"]
        EX3["Execution Node 3<br/>awx-execution-env"]
    end

    subgraph EXT["Sistem Eksternal"]
        GIT[("Git Repository<br/>playbooks + roles")]
        CMDB[("Inventory Source<br/>AWS / GCP / Azure")]
        CRED[("Credential Store<br/>Vault / cloud KMS")]
        SLACK["Slack / Email<br/>callback notifier"]
    end

    USER(["User / Engineer"]) --> WEB
    CI(["CI/CD System"]) --> API
    GIT -->|"scm update"| TASKD
    CMDB -->|"sync inventory"| TASKD
    CRED -->|"fetch secret saat runtime"| EX1
    CRED -->|"fetch secret saat runtime"| EX2

    WEB --> API
    API --> TASKD
    TASKD --> PG
    SCHED --> TASKD
    TASKD -->|"distribute job"| EX1
    TASKD -->|"distribute job"| EX2
    TASKD -->|"distribute job"| EX3

    EX1 -->|"callback event"| TASKD
    TASKD --> SLACK

Control plane berisi API server, web UI, task system yang menjadwalkan job, scheduler, dan database PostgreSQL. Semua state — inventory, project, job template, history eksekusi — disimpan di database. Control plane tidak pernah mengeksekusi playbook langsung; ia hanya mendistribusikan job ke execution plane.

Execution plane berisi execution node yang menjalankan awx-execution-env (container image berisi Ansible + collections + dependencies). Setiap job berjalan di execution node, lalu hasilnya (stdout, return code, artifact) dikembalikan ke control plane untuk dicatat di database.

Pemisahan ini punya konsekuensi penting. Pertama, scaling horizontal: tambah execution node saat job queue panjang, tanpa harus membesarkan control plane. Kedua, isolasi environment: execution plane bisa berada di jaringan yang punya akses ke managed node, sementara control plane cukup diakses user. Ketiga, observability lebih mudah: semua event tercatat di control plane, sehingga kita bisa membuat dashboard di observability/dashboard yang membaca dari API AWX.

Jangan pernah menjalankan execution plane di node yang sama dengan managed node production. Job AWX butuh privilege (root atau sudo ke managed node) dan akses ke credential — mengkonsolidasikan semuanya di satu host berarti satu compromised credential bisa langsung menyentuh seluruh fleet.

Konsep Inti AWX #

Sebelum menulis kode, kita perlu memahami hierarki resource di AWX. Semua objek di AWX berada di bawah Organization — sebuah container logis untuk mengelompokkan resource berdasarkan tim, bisnis unit, atau environment.

flowchart TD
    ORG["Organization"]
    ORG --> INV["Inventory"]
    ORG --> PROJ["Project"]
    ORG --> CRED["Credential"]
    ORG --> JT["Job Template"]
    ORG --> WF["Workflow Template"]
    ORG --> ROLE["Role / Team"]

    INV --> HOST["Hosts + Groups"]
    PROJ --> SCMDET[("Git Repo<br/>playbooks")]
    CRED --> CREDT["Credential Type<br/>SSH, Vault, AWS, dll"]

    JT -. "kombinasi" .-> INV
    JT -. "kombinasi" .-> PROJ
    JT -. "kombinasi" .-> CRED
    JT -. "kombinasi" .-> CREDT
    JT --> SURVEY["Survey<br/>form input"]

    WF --> JTA["Job Template A"]
    WF --> JTB["Job Template B"]
    WF --> JTC["Job Template C"]
    WF -->|"success/failure link"| JTA
    WF -->|"success/failure link"| JTB
    WF -->|"success/failure link"| JTC

Inventory adalah kumpulan host yang menjadi target playbook. Bisa static (file YAML) atau dynamic (diambil dari AWS EC2, GCP, Azure, VMware, atau CMDB). Inventory bisa punya groups yang berfungsi seperti pattern hosts: webservers di playbook.

Project adalah pointer ke repository Git (GitHub, GitLab, Bitbucket) yang berisi playbook. AWX akan git pull repository ini setiap kali job dijalankan (jika scm_update_on_launch: true), sehingga kita selalu menggunakan versi playbook terbaru.

Credential adalah objek yang menyimpan informasi otentikasi — SSH key, vault password, AWS access key, dan lain-lain. Credential tidak pernah disimpan di database sebagai plaintext; AWX mengenkripsinya dengan key dari file SECRET_KEY, dan hanya di-decrypt saat job dijalankan.

Job Template adalah unit eksekusi utama. Ia adalah kombinasi: Inventory + Project + Playbook + Credential + (opsional) Survey. Setiap kali Job Template dijalankan, AWX membuat objek Job yang mencatat input, output, return code, dan durasi.

Workflow Template menghubungkan beberapa Job Template dengan logika percabangan. Workflow adalah cara AWX mengimplementasikan pipeline — kita bisa mendefinisikan “jalankan Job A; jika sukses jalankan Job B; jika gagal jalankan Job C” tanpa menulis satu baris kode pun.


ANTI-PATTERN: Menjalankan Playbook Langsung dari CLI untuk Production #

Pola yang sering muncul di tim yang baru migrasi ke AWX: developer tetap menjalankan ansible-playbook dari laptop untuk deploy ke production karena “lebih cepat” dan “sudah hafal”. Ini adalah anti-pattern serius yang harus kita hilangkan sejak hari pertama.

# ANTI-PATTERN: production deploy dari laptop engineer
# Tidak ada audit trail, tidak ada approval, tidak ada rollback otomatis
ssh ops@jump-host "cd /opt/ansible && ansible-playbook -i production deploy.yml --extra-vars 'version=2.1.0'"

# Masalah:
# 1. Siapa yang deploy? -- hanya "yang punya akses ssh"
# 2. Kapan di-deploy? -- tidak tercatat
# 3. Apa yang berubah? -- diff dihitung manual dari Git
# 4. Kalau gagal? -- engineer harus SSH manual untuk rollback
# 5. Compliance? -- auditor tidak bisa jawab "siapa deploy apa ke mana"
# BENAR: production deploy lewat AWX Job Template dengan approval
# playbooks/configure-awx.yml — definisikan Job Template dengan ask_scm_diff dan ask_limit_on_launch
---
- name: Konfigurasi Job Template production dengan approval
  hosts: localhost
  vars:
    awx_host: "https://awx.company.internal"
    awx_oauthtoken: "{{ vault_awx_token }}"

  tasks:
    - name: Buat Job Template production dengan approval workflow
      awx.awx.job_template:
        controller_host: "{{ awx_host }}"
        controller_oauthtoken: "{{ awx_oauthtoken }}"
        name: "Deploy — Production"
        organization: "Platform Engineering"
        job_type: run
        inventory: "Production Inventory"
        project: "Infrastructure Playbooks"
        playbook: "playbooks/deploy.yml"
        credentials:
          - "Production SSH Key"
          - "Ansible Vault Password"
        # Fitur yang hilang kalau pakai CLI langsung:
        ask_scm_diff_on_launch: true     # Tampilkan diff Git sebelum run
        ask_limit_on_launch: true        # Bisa batasi ke subset host
        ask_variables_on_launch: true    # Bisa override variabel
        ask_inventory_on_launch: false   # Production inventory tetap fixed
        survey_enabled: true
        become_enabled: true
        # Schedule untuk deployment window
        verbosity: 1                     # Default verbosity, bisa dinaikkan saat run
      no_log: true

Dengan Job Template seperti di atas, setiap eksekusi ke production:

  1. Record di audit log AWX — siapa yang trigger, kapan, dengan parameter apa.
  2. Menampilkan Git diff sebelum run — engineer bisa review perubahan apa yang akan di-deploy.
  3. Bisa dibatasi ke subset host (--limit webservers-01,webservers-02) — berguna untuk canary deploy.
  4. Menjadi bagian dari Workflow yang punya approval gate — tidak ada deploy ke production tanpa reviewer menyetujui.

Lifecycle Job: Dari Klik ke Hasil #

Memahami state yang dilalui sebuah job membantu kita melakukan debug saat ada job yang “stuck” atau tidak pernah selesai. Diagram state berikut menunjukkan transisi yang mungkin:

stateDiagram-v2
    [*] --> New: "launch"
    New --> Pending: "masuk queue"
    Pending --> Waiting: "butuh input<br/>(survey/approval)"
    Waiting --> Pending: "input diberikan"
    Pending --> Running: "execution node<br/>tersedia"
    Running --> Successful: "exit code 0"
    Running --> Failed: "exit code non-zero"
    Running --> Error: "execution error<br/>(connection lost, dll)"
    Running --> Canceled: "user cancel"
    Waiting --> Canceled: "user cancel"
    Pending --> Canceled: "user cancel"
    Successful --> [*]
    Failed --> [*]
    Error --> [*]
    Canceled --> [*]

State Pending adalah yang paling sering membuat bingung: job sudah di-trigger tapi belum jalan. Penyebab umumnya: tidak ada execution node yang available (semua sibuk), node dalam maintenance, atau control plane kehabisan resource. Cek InstancesJobs di AWX UI untuk lihat job mana yang sedang berjalan dan execution node mana yang handle.

State Waiting berarti job butuh input lebih lanjut — biasanya karena Workflow Template punya approval node, atau karena Job Template punya survey yang belum diisi.

State Error berbeda dari Failed. Failed berarti playbook exit dengan kode non-zero (task ada yang gagal). Error berarti masalah di level eksekusi — connection ke managed node terputus, execution environment crash, atau OOM di execution node.


Konfigurasi AWX dengan Ansible: As Code untuk Platform Automation #

AWX sendiri bisa dikonfigurasi dengan Ansible menggunakan collection awx.awx dan module tower_* di belakangnya. Ini bukan recursive irony — justru ini best practice. Platform automation harus di-deklarasi di Git, di-review via PR, dan di-rollback jika ada masalah, sama persis seperti managed node yang dikonfigurasi AWX.

# Install dependencies
ansible-galaxy collection install awx.awx
pip install awxkit
# playbooks/configure-awx.yml
---
- name: Konfigurasi AWX untuk project infrastruktur
  hosts: localhost
  vars:
    awx_host: "https://awx.company.internal"
    awx_oauthtoken: "{{ vault_awx_token }}"

  tasks:
    # 1. Buat Organization
    - name: Buat organization
      awx.awx.organization:
        controller_host: "{{ awx_host }}"
        controller_oauthtoken: "{{ awx_oauthtoken }}"
        name: "Platform Engineering"
        description: "Team Platform Engineering — owns production infrastructure"
        state: present

    # 2. Buat Credential untuk SSH
    - name: Buat credential SSH untuk production
      awx.awx.credential:
        controller_host: "{{ awx_host }}"
        controller_oauthtoken: "{{ awx_oauthtoken }}"
        name: "Production SSH Key"
        organization: "Platform Engineering"
        credential_type: "Machine"
        inputs:
          ssh_key_data: "{{ vault_production_ssh_key }}"
          username: ansible-deploy
          become_method: sudo
          become_username: root
        state: present
      no_log: true
      # no_log: true mencegah private key bocor ke log Ansible

    # 3. Buat Credential untuk Vault
    - name: Buat credential Ansible Vault
      awx.awx.credential:
        controller_host: "{{ awx_host }}"
        controller_oauthtoken: "{{ awx_oauthtoken }}"
        name: "Ansible Vault Password"
        organization: "Platform Engineering"
        credential_type: "Vault"
        inputs:
          vault_password: "{{ vault_ansible_vault_password }}"
        state: present
      no_log: true

    # 4. Buat Credential untuk AWS (untuk dynamic inventory)
    - name: Buat credential AWS untuk inventory sync
      awx.awx.credential:
        controller_host: "{{ awx_host }}"
        controller_oauthtoken: "{{ awx_oauthtoken }}"
        name: "AWS Read-Only for Inventory"
        organization: "Platform Engineering"
        credential_type: "Amazon Web Services"
        inputs:
          username: "{{ vault_aws_access_key }}"
          password: "{{ vault_aws_secret_key }}"
        state: present
      no_log: true

    # 5. Buat Project (link ke Git repository)
    - name: Buat project dari Git repository
      awx.awx.project:
        controller_host: "{{ awx_host }}"
        controller_oauthtoken: "{{ awx_oauthtoken }}"
        name: "Infrastructure Playbooks"
        organization: "Platform Engineering"
        scm_type: git
        scm_url: "https://github.com/company/ansible-infra.git"
        scm_branch: main
        scm_update_on_launch: true    # Selalu pull terbaru sebelum run
        scm_clean: true              # Hapus perubahan lokal sebelum update
        scm_delete_on_update: false  # Jaga credential di working copy
        timeout: 60
        state: present

    # 6. Buat Inventory source dinamis dari AWS EC2
    - name: Buat inventory production
      awx.awx.inventory:
        controller_host: "{{ awx_host }}"
        controller_oauthtoken: "{{ awx_oauthtoken }}"
        name: "Production Inventory"
        organization: "Platform Engineering"
        state: present

    - name: Buat inventory source dari AWS EC2
      awx.awx.inventory_source:
        controller_host: "{{ awx_host }}"
        controller_oauthtoken: "{{ awx_oauthtoken }}"
        name: "AWS EC2 Production"
        inventory: "Production Inventory"
        source: ec2
        credential: "AWS Read-Only for Inventory"
        region: "ap-southeast-1"
        # Cache inventory 15 menit untuk mengurangi API call
        cache_timeout: 900
        # Group otomatis dari tag EC2
        keyed_groups:
          - prefix: role
            key: tags.Role
          - prefix: env
            key: tags.Environment
        update_on_launch: true
        state: present

    # 7. Buat Job Template untuk deployment
    - name: Buat Job Template untuk deployment
      awx.awx.job_template:
        controller_host: "{{ awx_host }}"
        controller_oauthtoken: "{{ awx_oauthtoken }}"
        name: "Deploy — Production"
        organization: "Platform Engineering"
        job_type: run
        inventory: "Production Inventory"
        project: "Infrastructure Playbooks"
        playbook: "playbooks/deploy.yml"
        credentials:
          - "Production SSH Key"
          - "Ansible Vault Password"
        # Survey: form input sebelum run
        survey_enabled: true
        survey_spec:
          description: "Parameter deployment ke production"
          name: "Deploy Parameters"
          spec:
            - variable: version
              question_name: "Versi yang akan di-deploy"
              question_description: "Contoh: 2.1.0 — pastikan tag Git sudah dibuat"
              required: true
              type: text
              min: 1
              max: 32
            - variable: confirm
              question_name: "Konfirmasi deploy ke production"
              question_description: "Production deploy mempengaruhi user — pastikan on-call aware"
              required: true
              type: multiplechoice
              choices: "yes\nno"
              default: "no"
        # Approval
        ask_scm_diff_on_launch: true     # Tampilkan Git diff
        ask_limit_on_launch: true        # Bisa batasi subset host
        ask_variables_on_launch: true    # Bisa override variabel
        state: present

Perhatikan beberapa hal penting dari playbook di atas. Pertama, semua task yang menangani secret punya no_log: true — ini mencegah private key dan password bocor ke output Ansible (yang mungkin masuk ke log AWX dan jadi searchable). Kedua, scm_clean: true memastikan working copy di AWX bersih sebelum pull, tapi scm_delete_on_update: false mencegah credential di working copy terhapus. Ketiga, inventory source dikonfigurasi dengan cache_timeout: 900 (15 menit) — mencegah API throttling ke AWS dan mempercepat launch job.


ANTI-PATTERN: Menyimpan Credential sebagai Plaintext #

Salah satu kesalahan paling berbahaya yang sering muncul di deployment AWX: developer copy-paste private key atau AWS secret langsung ke field input di UI AWX, atau bahkan menulisnya di survey spec. AWX mengenkripsi credential di database, tetapi input dari user di survey atau extra variables tidak selalu dilindungi.

# ANTI-PATTERN: credential plaintext di Job Template survey
- name: Job Template dengan credential di survey
  awx.awx.job_template:
    name: "Deploy — Production"
    survey_enabled: true
    survey_spec:
      spec:
        - variable: ssh_private_key
          question_name: "SSH Private Key"
          type: textarea        # ANTI-PATTERN: ini jadi tersimpan di launch history
        - variable: db_password
          question_name: "Database Password"
          type: password        # ANTI-PATTERN: tetap tersimpan di launch history
# BENAR: gunakan credential type eksternal (Vault, cloud secret manager)
---
- name: Buat credential type untuk HashiCorp Vault
  awx.awx.credential_type:
    controller_host: "{{ awx_host }}"
    controller_oauthtoken: "{{ awx_oauthtoken }}"
    name: "HashiCorp Vault SSH Key"
    kind: cloud
    inputs:
      fields:
        - id: vault_url
          type: string
          label: Vault URL
        - id: vault_token
          type: string
          label: Vault Token
          secret: true
        - id: ssh_key_path
          type: string
          label: Path ke SSH Key di Vault
      required:
        - vault_url
        - vault_token
        - ssh_key_path
    injectors:
      ssh_private_key: "{{ lookup('hashi_vault', 'secret=' + ssh_key_path, token=vault_token, url=vault_url)['data']['private_key'] }}"
    state: present

- name: Buat credential eksternal yang reference ke Vault
  awx.awx.credential:
    controller_host: "{{ awx_host }}"
    controller_oauthtoken: "{{ awx_oauthtoken }}"
    name: "Production SSH Key (from Vault)"
    organization: "Platform Engineering"
    credential_type: "HashiCorp Vault SSH Key"
    inputs:
      vault_url: "https://vault.company.internal:8200"
      vault_token: "{{ vault_operator_token }}"
      ssh_key_path: "secret/data/infrastructure/prod-ssh-key"
    state: present
  no_log: true
  # Token di-decrypt saat job jalan, lalu di-inject sebagai ssh_private_key
  # yang dipakai oleh module 'Machine' credential di runtime

Tabel Credential Type yang Tersedia #

AWX menyediakan banyak credential type bawaan, dan kita bisa membuat custom credential type untuk sistem internal:

Credential Type Use Case Secret yang Disimpan
Machine SSH ke Linux/Unix managed node SSH private key, username, become password
Source Control Pull playbook dari Git Token PAT, SSH key, atau basic auth
Ansible Vault Decrypt vault file di playbook Vault password
Amazon Web Services AWS API untuk inventory + module Access key + secret key
Google Compute Platform GCP API Service account JSON
Microsoft Azure Azure API Client ID, secret, tenant, subscription
HashiCorp Vault Ambil secret dari Vault saat runtime Vault token + path (token bisa di-renew otomatis)
CyberArk AIM Ambil secret dari CyberArk Query string + AIM credentials
OpenStack OpenStack API Username, password, project, auth URL
Custom (kind: cloud) Integrasi sistem internal Sesuai definisi inputs.fields

Prinsip yang harus kita pegang: credential tidak pernah ditulis di playbook, survey, atau extra variables. Selalu lewat credential type eksternal yang fetch secret saat runtime dari Vault, cloud KMS, atau sistem secret management lain. Audit log AWX mencatat siapa yang menggunakan credential apa, tetapi tidak pernah mencatat isi credential itu sendiri.

Jika kita melihat playbook AWX yang punya extra_vars berisi string yang terlihat seperti -----BEGIN RSA PRIVATE KEY-----, berhenti. Itu private key, dan sekarang sudah bocor ke mana-mana: Git history, AWX launch history, log Ansible, dan mungkin log monitoring. Rotate key-nya, audit access-nya, dan refactor ke credential type eksternal.

ANTI-PATTERN: Inventory Hardcoded di Project Repository #

Pola yang sering muncul di tim yang baru adopsi AWX: simpan file inventory/production.yml di Git repository playbook, dan pakai inventory itu langsung dari Job Template. Ini bekerja, tetapi mengingkari salah satu kekuatan utama AWX — dynamic inventory.

# ANTI-PATTERN: inventory hardcoded di repository playbook
# inventory/production.yml — disimpan di Git, manual update saat ada server baru
---
all:
  hosts:
    web-01:
      ansible_host: 10.0.1.10
    web-02:
      ansible_host: 10.0.1.11
    db-01:
      ansible_host: 10.0.2.10
  children:
    webservers:
      hosts:
        web-01:
        web-02:
    databases:
      hosts:
        db-01:
# BENAR: inventory source dinamis dari AWX
---
- name: Buat inventory source dari AWS EC2
  awx.awx.inventory_source:
    controller_host: "{{ awx_host }}"
    controller_oauthtoken: "{{ awx_oauthtoken }}"
    name: "AWS EC2 Production"
    inventory: "Production Inventory"
    source: ec2
    credential: "AWS Read-Only for Inventory"
    region: "ap-southeast-1"
    # Filter: hanya instance yang running dan punya tag tertentu
    instance_filters:
      tag:Environment: production
      instance-state-name: running
    # Group otomatis dari tag EC2
    keyed_groups:
      - prefix: role
        key: tags.Role
      - prefix: env
        key: tags.Environment
    # Override variabel per host
    hostvars:
      ansible_user: ansible-deploy
      ansible_python_interpreter: /usr/bin/python3
    # Cache 15 menit, refresh saat launch job
    cache_timeout: 900
    update_on_launch: true
    state: present

Dengan dynamic inventory, server baru yang di-tag Environment=production and Role=webserver di AWS otomatis masuk ke grup role_webserver dan env_production di AWX — tidak perlu commit apa-apa ke Git. Server yang di-terminate otomatis hilang dari inventory saat cache expired.

Tabel Sumber Inventory yang Didukung AWX #

Source Plugin / Script Use Case Cocok untuk
Static File YAML/INI di Project Server yang jarang berubah On-premise legacy, lab
AWS EC2 amazon.aws.aws_ec2 Instance EC2 AWS-centric infrastructure
AWS RDS Custom plugin Database RDS Inventory DB untuk konfigurasi
GCP Compute google.cloud.gcp_compute Instance GCP GCP-centric infrastructure
Azure VM azure.azcollection.azure_rm VM di Azure Azure-centric infrastructure
VMware vCenter community.vmware.vmware_vm_inventory VM di vSphere Hybrid cloud dengan on-prem
OpenStack openstack.cloud.os_server_info Instance OpenStack Private cloud OpenStack
Satellite Custom script via Foreman Server yang dimanage Red Hat Satellite RHEL fleet management
Custom script Script Python/Shell CMDB internal, sumber custom Organisasi dengan CMDB sendiri

Cara paling sederhana bermigrasi dari static ke dynamic: buat inventory baru dengan source yang sama dengan cloud provider kita, lalu point Job Template ke inventory baru itu. Inventory lama bisa di-archive, dan dynamic inventory jadi sumber utama.


Workflow Template: Pipeline Deployment dengan Approval #

Workflow Template adalah cara AWX mengimplementasikan pipeline. Alih-alih menulis script shell atau Python yang memanggil beberapa ansible-playbook secara berurutan, kita definisikan graph Job Template di UI AWX — lebih visual, lebih mudah di-review, dan terintegrasi dengan RBAC.

flowchart TD
    START(["Start"]) --> A["JT: Lint & Validate"]
    A -->|"success"| B{"JT: Deploy Staging"}
    A -->|"failure"| NOTIFY1["End: Notify Failure"]
    B -->|"success"| C{"JT: Integration Test"}
    B -->|"failure"| NOTIFY2["End: Notify Failure"]
    C -->|"success"| APPROVAL{"Gate: Approval"}
    C -->|"failure"| NOTIFY3["End: Notify Failure"]
    APPROVAL -->|"approved"| D["JT: Deploy Production"]
    APPROVAL -->|"rejected"| HOLD["End: Hold"]
    D -->|"success"| E["JT: Verify Health"]
    D -->|"failure"| ROLLBACK["JT: Rollback Production"]
    ROLLBACK --> NOTIFY4["End: Notify Failure"]
    E -->|"healthy"| END(["End: Success"])
    E -->|"unhealthy"| ROLLBACK

Workflow ini mengimplementasikan pipeline deployment production yang lengkap: lint → deploy staging → integration test → approval gate → deploy production → verify health. Kalau ada step yang gagal, jalur failure mengarah ke notification atau rollback.

# playbooks/configure-workflow.yml
---
- name: Buat workflow deployment lengkap
  hosts: localhost
  vars:
    awx_host: "https://awx.company.internal"
    awx_oauthtoken: "{{ vault_awx_token }}"

  tasks:
    - name: Buat Workflow Job Template
      awx.awx.workflow_job_template:
        controller_host: "{{ awx_host }}"
        controller_oauthtoken: "{{ awx_oauthtoken }}"
        name: "Full Deployment Pipeline"
        organization: "Platform Engineering"
        description: "Pipeline deployment lengkap: validate, staging, integration, approval, production, verify"
        # Survey di level workflow
        survey_enabled: true
        survey_spec:
          description: "Parameter deployment pipeline"
          name: "Pipeline Parameters"
          spec:
            - variable: version
              question_name: "Versi yang akan di-deploy"
              required: true
              type: text
        state: present
      register: workflow

    - name: Tambahkan node validate ke workflow
      awx.awx.workflow_job_template_node:
        controller_host: "{{ awx_host }}"
        controller_oauthtoken: "{{ awx_oauthtoken }}"
        workflow_job_template: "Full Deployment Pipeline"
        identifier: "validate"
        unified_job_template: "Validate Playbook"
        state: present

    - name: Tambahkan approval node setelah integration test
      awx.awx.workflow_job_template_node:
        controller_host: "{{ awx_host }}"
        controller_oauthtoken: "{{ awx_oauthtoken }}"
        workflow_job_template: "Full Deployment Pipeline"
        identifier: "production_approval"
        all_parents_must_converge: false
        # Approval node di-pause sampai reviewer setujui
        state: present
      register: approval_node

    - name: Set approval ke 'always' — workflow pause di node ini
      awx.awx.workflow_job_template_node_approval:
        controller_host: "{{ awx_host }}"
        controller_oauthtoken: "{{ awx_oauthtoken }}"
        workflow_job_template: "Full Deployment Pipeline"
        node_identifier: "production_approval"
        timeout: 3600  # Auto-cancel setelah 1 jam jika tidak ada approval
        state: present

    - name: Hubungkan node dengan success/failure links
      awx.awx.workflow_job_template_node:
        controller_host: "{{ awx_host }}"
        controller_oauthtoken: "{{ awx_oauthtoken }}"
        workflow_job_template: "Full Deployment Pipeline"
        identifier: "validate"
        related:
          success_nodes:
            - identifier: "deploy_staging"
          failure_nodes:
            - identifier: "notify_failure"
        state: present

    - name: Sambungkan semua node ke production deploy dan rollback path
      awx.awx.workflow_job_template_node:
        controller_host: "{{ awx_host }}"
        controller_oauthtoken: "{{ awx_oauthtoken }}"
        workflow_job_template: "Full Deployment Pipeline"
        identifier: "deploy_production"
        related:
          success_nodes:
            - identifier: "verify_health"
          failure_nodes:
            - identifier: "rollback_production"
        state: present

Approval node adalah fitur yang sering di-underestimate. Saat workflow sampai di node ini, ia berhenti dan menunggu — tidak ada eksekusi lebih lanjut sampai reviewer menyetujui. Reviewer menerima notifikasi (bisa dikonfigurasi per workflow) dan bisa approve atau reject dari UI AWX atau via REST API. Ini mengimplementasikan four-eyes principle untuk production deployment tanpa perlu script tambahan.

Workflow ini bisa di-trigger dari banyak sumber: manual launch dari UI, REST API dari CI/CD, webhook dari GitHub/GitLab, atau schedule. Untuk integrasi dengan pipeline CI/CD yang lebih kompleks, lihat cicd/pipeline-design — artikel itu membahas bagaimana CI menghasilkan artifact dan CD (termasuk AWX workflow) mendistribusikannya.


RBAC: Kontrol Akses Berbasis Peran #

AWX punya sistem RBAC yang detail. Kita bisa memberikan permission ke user atau team di level Organization, Project, Inventory, Job Template, atau Workflow Template. Pattern yang umum: beri akses yang diperlukan saja, jangan lebih.

# playbooks/configure-rbac.yml
---
- name: Konfigurasi RBAC untuk tim development dan SRE
  hosts: localhost
  vars:
    awx_host: "https://awx.company.internal"
    awx_oauthtoken: "{{ vault_awx_token }}"

  tasks:
    # Tim developer: bisa execute staging, tapi BUKAN production
    - name: Beri role execute ke tim developer untuk staging
      awx.awx.role:
        controller_host: "{{ awx_host }}"
        controller_oauthtoken: "{{ awx_oauthtoken }}"
        team: "Developers"
        role: execute
        job_templates:
          - "Deploy — Staging"
          - "Restart App — Staging"
        state: present

    - name: Beri role read-only ke tim developer untuk production inventory
      awx.awx.role:
        controller_host: "{{ awx_host }}"
        controller_oauthtoken: "{{ awx_oauthtoken }}"
        team: "Developers"
        role: read
        inventories:
          - "Production Inventory"
        state: present
        # Developer bisa lihat isi production inventory (untuk debugging)
        # tapi TIDAK bisa execute Job Template yang target production

    # Tim SRE: admin penuh
    - name: Beri role admin ke tim SRE
      awx.awx.role:
        controller_host: "{{ awx_host }}"
        controller_oauthtoken: "{{ awx_oauthtoken }}"
        team: "SRE"
        role: admin
        organizations:
          - "Platform Engineering"
        state: present
        # SRE bisa modify Job Template, manage credential, dll.

    # Auditor: read-only di semua level
    - name: Beri role read-only ke auditor
      awx.awx.role:
        controller_host: "{{ awx_host }}"
        controller_oauthtoken: "{{ awx_oauthtoken }}"
        team: "Auditors"
        role: read
        organizations:
          - "Platform Engineering"
        state: present
        # Auditor bisa lihat history job, konfigurasi, log
        # tapi tidak bisa trigger atau modify apapun

Pattern RBAC yang baik: least privilege. Developer tidak perlu akses ke production credential; mereka hanya perlu Job Template staging. SRE perlu akses penuh karena mereka yang on-call. Auditor perlu read-only untuk compliance check. Setiap role punya alasan yang jelas.


Webhook: Trigger Job dari Sistem Eksternal #

AWX mendukung webhook untuk GitHub, GitLab, dan generic HTTP POST. Ini memungkinkan job dipicu otomatis saat ada push ke repository, atau saat pipeline CI selesai:

# Aktifkan webhook di Job Template
- name: Aktifkan webhook di Job Template
  awx.awx.job_template:
    controller_host: "{{ awx_host }}"
    controller_oauthtoken: "{{ awx_oauthtoken }}"
    name: "Deploy — Staging"
    webhook_service: github     # atau gitlab, atau 'none' untuk generic
    webhook_credential: "GitHub Webhook Token"  # opsional, untuk validasi HMAC
    state: present
# Di GitHub Actions — trigger AWX saat ada push ke main
- name: Trigger AWX deployment
  if: github.ref == 'refs/heads/main'
  run: |
    curl -X POST \
      -H "Content-Type: application/json" \
      -H "X-GitHub-Event: push" \
      -H "X-GitHub-Delivery: ${{ github.event_id }}" \
      "https://awx.company.internal/api/v2/job_templates/42/github/" \
      -d '{"ref": "refs/heads/main", "version": "${{ github.sha }}"}'    
# Di GitLab CI — trigger AWX via webhook
trigger-awx:
  stage: deploy
  script:
    - |
      curl -X POST \
        -H "X-Gitlab-Event: Push Hook" \
        -H "X-Gitlab-Token: $AWX_WEBHOOK_SECRET" \
        "https://awx.company.internal/api/v2/job_templates/42/gitlab/" \
        -d '{"ref": "refs/heads/main"}'      
  only:
    - main

Saat menggunakan webhook dengan HMAC credential, AWX akan memverifikasi signature dari GitHub/GitLab sebelum menjalankan job. Ini mencegah orang yang tidak berwenang memicu job hanya dengan mengetahui URL webhook. Untuk generic webhook (tanpa GitHub/GitLab), kita bisa mengimplementasikan custom authentication via header token.

Saat mengintegrasikan AWX dengan CI/CD pipeline, pemisahan peran penting: CI (build, test, scan) tidak perlu akses ke AWX production credential. CI cukup trigger AWX workflow via webhook; AWX sendiri yang handle deployment dengan credential-nya sendiri. Kalau CI di-compromise, attacker tidak langsung punya akses ke production — mereka hanya bisa trigger job yang tetap melalui approval gate AWX.


Observabilitas: Dashboard dan Alert dari AWX #

AWX mengekspos REST API yang bisa di-scrape untuk monitoring. Endpoint yang paling berguna untuk observabilitas:

# Total job yang berjalan (untuk gauge di dashboard)
GET /api/v2/unified_job_templates/?page_size=1

# Job yang gagal dalam 1 jam terakhir
GET /api/v2/jobs/?status=failed&started__gte=2026-06-08T04:00:00Z

# Job yang masih pending (antrian panjang = masalah)
GET /api/v2/jobs/?status=pending

# Inventory source yang gagal sync
GET /api/v2/inventory_sources/?last_job_failed=true

Data ini bisa di-scrape ke Prometheus via json_exporter, lalu divisualisasikan di Grafana. Artikel observability/dashboard membahas pola membuat dashboard dari REST API seperti ini. Untuk alerting, kita bisa menetapkan alert Prometheus berdasarkan metric: awx_jobs_failed_total > 5 artinya ada 5 job gagal dalam 5 menit terakhir, kemungkinan ada masalah di environment.


Ringkasan #

  • AWX adalah open source (Apache 2.0), cocok untuk lab, homelab, dan tim yang mampu maintain upgrade sendiri. Tower / Automation Controller berbayar dengan support Red Hat, cocok untuk perusahaan dengan compliance requirement. Keduanya berbagi codebase inti.
  • AWX dipisah jadi control plane (API, UI, database, scheduler) dan execution plane (execution node yang jalankan playbook). Scaling horizontal dengan tambah execution node; control plane cukup untuk traffic UI dan API.
  • Konsep inti: OrganizationProject (Git repo) + Inventory (host target) + Credential (secret) + Job Template (kombinasi ketiganya) + Workflow Template (graph beberapa Job Template dengan approval).
  • ANTI-PATTERN: jalankan playbook langsung dari CLI untuk production — tidak ada audit, approval, atau rollback. Gunakan Job Template dengan ask_scm_diff_on_launch dan approval gate.
  • ANTI-PATTERN: credential plaintext di survey atau extra variables — selalu pakai credential type eksternal (Vault, cloud KMS). Audit log mencatat siapa yang akses credential, bukan isinya.
  • ANTI-PATTERN: inventory hardcoded di repository — gunakan inventory source dinamis (AWS EC2, GCP, Azure) dengan keyed_groups untuk auto-grouping dari tag/label cloud.
  • Workflow Template mengimplementasikan pipeline deployment (lint → staging → integration → approval → production → verify) tanpa kode. Approval node enforces four-eyes principle.
  • RBAC harus pakai least privilege: developer punya execute di staging tapi tidak di production; SRE punya admin di production; auditor punya read-only.
  • Webhook memungkinkan CI/CD trigger job AWX via HTTP POST. Gunakan HMAC credential untuk validasi signature, dan pisahkan secret CI dengan credential AWX.
  • Observabilitas lewat REST API AWX — scrape ke Prometheus, visualisasikan di Grafana, set alert untuk job gagal, antrian panjang, dan inventory sync failure.

← Sebelumnya: Strategy & Serial   Berikutnya: Pipeline Design →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact