Deploy Container #
Setelah host Docker berhasil disiapkan dengan runtime dan konfigurasi daemon yang optimal, langkah berikutnya yang harus kita lakukan adalah menjalankan container di atas host tersebut. Mengelola siklus hidup (lifecycle) container secara manual menggunakan CLI seperti docker run atau docker stop sangat berisiko di lingkungan production. Kita tidak memiliki catatan perubahan, tidak ada kepastian status kontainer yang konsisten, dan prosesnya sulit diulang secara identik di berbagai server.
Ansible menawarkan pendekatan yang berbeda melalui ekosistem modul community.docker. Menggunakan modul ini, kita dapat mendeklarasikan kondisi akhir (desired state) dari kontainer kita—mulai dari image yang digunakan, port yang diekspos, volume yang di-mount, variabel lingkungan, kebijakan restart, hingga mekanisme pemeriksaan kesehatan (health check). Ansible akan secara otomatis mendeteksi status kontainer yang sedang berjalan, membandingkannya dengan definisi kita, dan melakukan tindakan korektif jika terdapat perbedaan. Pendekatan deklaratif ini menjamin proses deployment yang idempoten, konsisten, dan dapat diaudit secara penuh.
flowchart TD
Start["Playbook Dijalankan"] --> CheckContainer{"Apakah container ada?"}
CheckContainer -. "Tidak" .-> CreateNew["Buat Container Baru (State: started)"]
CheckContainer -. "Ya" .-> CheckConfig{"Apakah ada perubahan image/ports/env?"}
CheckConfig -- "Ya" --> Recreate["Recreate Container (Hapus & Buat Baru)"]
CheckConfig -- "Tidak" --> CheckState{"Apakah container sedang berjalan?"}
CheckState -- "Tidak" --> StartExisting["Jalankan Container (State: started)"]
CheckState -- "Ya" --> Skip["Lewati Task (Idempoten - Status OK)"]
CreateNew --> Verify["Jalankan Health Check"]
Recreate --> Verify
StartExisting --> Verify
Verify --> End["Deployment Sukses"]
Paradigma Imperatif vs Deklaratif dalam Deployment #
Untuk memahami kekuatan Ansible dalam mengelola kontainer, kita harus memahami perbedaan mendasar antara model imperatif (perintah langkah-demi-langkah) dan model deklaratif (pernyataan kondisi akhir).
Saat menggunakan model imperatif dengan perintah bash, kita memberi tahu server bagaimana cara menjalankan kontainer. Jika kontainer sudah ada, perintah akan gagal. Jika kita ingin mengubah port, kita harus menghentikan kontainer lama terlebih dahulu lalu membuat yang baru secara manual. Sebaliknya, model deklaratif Ansible memfokuskan kita untuk menulis apa kondisi akhir kontainer yang kita inginkan, sementara Ansible menangani detail langkah eksekusinya.
Berikut adalah tabel perbandingan komparatif yang memperlihatkan perbedaan kedua pendekatan ini:
| Karakteristik | Pendekatan Imperatif (docker run CLI) |
Pendekatan Deklaratif (Ansible docker_container) |
|---|---|---|
| Definisi Status | Dinyatakan sebagai perintah aksi instan. | Dinyatakan sebagai deklarasi status akhir yang diinginkan. |
| Idempotensi | Tidak idempoten. Menjalankan perintah yang sama dua kali akan menghasilkan error port konflik atau nama kontainer ganda. | Mutlak idempoten. Jika status kontainer di server sudah sesuai, Ansible tidak akan melakukan tindakan apapun. |
| Penanganan Perubahan | Harus menghapus kontainer lama secara manual sebelum mengganti konfigurasi/port. | Mendeteksi perbedaan konfigurasi secara otomatis dan me-recreate kontainer dengan konfigurasi baru. |
| Integrasi Secret | Secret sering kali ditulis langsung sebagai teks polos di terminal history. | Terintegrasi erat dengan Ansible Vault untuk enkripsi variabel sensitif secara aman. |
| Kemudahan Rollback | Membutuhkan penulisan ulang perintah manual dengan tag versi lama. | Cukup mengganti nilai variabel tag image pada playbook lalu menjalankannya kembali. |
Instalasi Dependensi Ansible Docker #
Modul-modul Docker di Ansible tidak dibundel di dalam paket inti Ansible Core. Kita harus menginstal koleksi (collection) community.docker terlebih dahulu melalui Ansible Galaxy. Selain itu, modul-modul ini bergantung pada library Python resmi dari Docker yang disebut docker-py atau docker SDK pada host target agar dapat berkomunikasi dengan socket Docker daemon /var/run/docker.sock.
1. Instalasi Koleksi Terpusat (Ansible Control Node) #
Koleksi community.docker diinstal pada mesin kontrol kita (control node) dengan menjalankan perintah berikut:
ansible-galaxy collection install community.docker
2. Penyiapan Python SDK di Host Target (Target Host) #
Agar playbook kita dapat mengeksekusi modul Docker di server tujuan, kita harus memastikan pustaka Python docker terinstal di server tersebut. Kita dapat mengotomasinya melalui task Ansible berikut:
# playbooks/tasks/prepare_docker_sdk.yml
---
- name: Pastikan pip3 terinstal di sistem
apt:
name: python3-pip
state: present
when: ansible_os_family == "Debian"
- name: Pastikan pip3 terinstal di CentOS/RHEL
dnf:
name: python3-pip
state: present
when: ansible_os_family == "RedHat"
- name: Instal Docker SDK untuk Python menggunakan pip
pip:
name: docker
state: present
Menggunakan Modul community.docker.docker_container #
Modul community.docker.docker_container adalah jantung utama pengelolaan siklus hidup kontainer. Modul ini mendukung berbagai status target melalui parameter state.
Berikut adalah opsi state yang paling umum digunakan:
started: (Default) Menjamin kontainer dibuat dan dalam kondisi berjalan. Jika kontainer sudah berjalan tetapi konfigurasinya berubah, Ansible akan otomatis me-recreate kontainer tersebut.stopped: Menjamin kontainer dihentikan sementara, tetapi tidak menghapus definisinya dari host.present: Membuat kontainer di host tetapi tidak langsung menjalankannya.absent: Menjamin kontainer dihentikan dan dihapus sepenuhnya dari host, termasuk membersihkan volume anonim terkait jika diinginkan.
Konfigurasi Runtime Container #
Dalam merancang kontainer tingkat production, ada banyak aspek runtime yang harus dikonfigurasi secara hati-hati, terutama penanganan port, mounting volume untuk data persisten, pengaturan variabel lingkungan, dan penentuan kebijakan restart (restart policy).
1. Port Mapping dan Volume Mounting #
Saat mendeploy aplikasi web atau database, kita harus memetakan port internal kontainer ke port host, serta memetakan direktori host ke dalam kontainer untuk penyimpanan data yang tidak hilang saat kontainer dihapus.
# ANTI-PATTERN: Menuliskan port dan volume tanpa pertimbangan keamanan atau struktur direktori
- name: Jalankan DB Postgresql salah
community.docker.docker_container:
name: database_salah
image: postgres:15
ports:
- "5432:5432" # Mengekspos database langsung ke publik internet
volumes:
- "/tmp/db-data:/var/lib/postgresql/data" # Menyimpan data di folder /tmp yang bisa terhapus otomatis oleh sistem
# BENAR: Membatasi eksposur port dan menggunakan jalur direktori persisten yang aman
- name: Jalankan DB Postgresql benar
community.docker.docker_container:
name: database_benar
image: postgres:15-alpine
state: started
ports:
- "127.0.0.1:5432:5432" # Database hanya bisa diakses lokal (localhost) atau via VPN
volumes:
- "/var/lib/postgresql/prod_data:/var/lib/postgresql/data:rw" # Jalur persisten dengan izin write aktif
restart_policy: unless-stopped
2. Keamanan Runtime Tambahan #
Untuk meningkatkan keamanan kontainer kita, disarankan untuk membatasi hak akses root di dalam kontainer. Kita dapat menyetel sistem berkas kontainer menjadi read-only menggunakan opsi read_only, dan hanya memperbolehkan penulisan data pada folder spesifik melalui tmpfs atau volume yang ditentukan.
- name: Deploy container web statis yang dikeraskan (hardened)
community.docker.docker_container:
name: static-web
image: nginx:alpine
state: started
read_only: true # Membuat seluruh root filesystem container menjadi read-only
tmpfs:
- /var/cache/nginx:uid=101,gid=101,mode=0755
- /var/run:uid=101,gid=101,mode=0755
ports:
- "8080:80"
Manajemen Variabel Lingkungan Sensitif #
Aplikasi production selalu membutuhkan variabel lingkungan (environment variables) seperti password database, API key, atau token enkripsi. Menuliskan rahasia-rahasia ini dalam file playbook biasa dalam teks polos (plaintext) adalah pelanggaran keamanan fatal yang sangat berbahaya.
Langkah Pengamanan: Enkripsi dengan Ansible Vault #
Kita harus mengenkripsi seluruh data rahasia menggunakan Ansible Vault, lalu memanggil variabel terenkripsi tersebut di dalam task dengan tambahan opsi no_log: true. Opsi no_log: true sangat penting agar Ansible tidak mencetak nilai dari variabel sensitif tersebut ke dalam file log eksekusi terminal kita.
1. Berkas Rahasia Terenkripsi (vars/secrets.yml) #
Kita mengunci variabel sensitif kita menggunakan perintah ansible-vault encrypt vars/secrets.yml. Isi file yang terenkripsi akan terlihat seperti ini:
# vars/secrets.yml (Setelah didekripsi dengan vault password)
---
db_prod_password: "SuperSecretSecureDatabasePassword2026!"
api_key_third_party: "live_abcd1234efgh5678"
2. Task Playbook Menggunakan Vault (tasks/deploy_app.yml) #
Saat menjalankan task deployment kontainer, kita memanggil variabel tersebut dan memastikan sistem mencatat status tanpa menampilkan rahasianya:
# playbooks/tasks/deploy_app.yml
---
- name: Muat variabel rahasia dari vault
include_vars:
file: vars/secrets.yml
- name: Deploy aplikasi web dengan credential sensitif
community.docker.docker_container:
name: web-app
image: registry.company.com/webapp:v1.2.0
state: started
env:
DATABASE_URL: "postgresql://postgres:{{ db_prod_password }}@db.local:5432/webapp"
API_TOKEN: "{{ api_key_third_party }}"
APP_ENV: "production"
# no_log menyembunyikan parameter task dari output stdout dan log server Ansible
no_log: true
Pola Deployment Idempoten dan Zero-Downtime #
Saat kita melakukan pembaruan kode aplikasi, kita biasanya mengubah versi tag image di playbook (misalnya dari v1.1.0 ke v1.2.0). Ansible secara cerdas akan mendeteksi perbedaan image ini, menghentikan kontainer lama, menghapusnya, dan menjalankan kontainer baru.
Namun, proses pergantian ini menyebabkan aplikasi kita mengalami downtime selama beberapa detik. Untuk mencegah hal tersebut di lingkungan production, kita dapat menerapkan pola rolling update sederhana dengan langkah-langkah: pull image baru terlebih dahulu, lalu lakukan verifikasi port sebelum mengalihkan lalu lintas jaringan.
Berikut adalah contoh playbook lengkap untuk deployment idempoten yang meminimalkan downtime:
# playbooks/deploy_webapp_rolling.yml
---
- name: Deployment Web Application Rolling Update
hosts: webservers
vars:
app_image: "nginx"
app_version: "1.25.3-alpine"
app_port: 8080
container_name: "prod-web-app"
tasks:
- name: 1. Tarik (pull) image baru terlebih dahulu sebelum mematikan container lama
community.docker.docker_image:
name: "{{ app_image }}"
tag: "{{ app_version }}"
source: pull
register: pull_result
- name: 2. Kumpulkan status kontainer yang sedang berjalan
community.docker.docker_container_info:
name: "{{ container_name }}"
register: current_container
- name: 3. Tampilkan pesan jika kontainer perlu di-update
debug:
msg: "Kontainer akan di-update dari versi lama ke {{ app_version }}"
when:
- current_container.exists
- current_container.container.Config.Image != (app_image + ":" + app_version)
- name: 4. Deploy kontainer baru (menggantikan kontainer lama jika berbeda)
community.docker.docker_container:
name: "{{ container_name }}"
image: "{{ app_image }}:{{ app_version }}"
state: started
restart_policy: unless-stopped
ports:
- "{{ app_port }}:80"
env:
TZ: "Asia/Jakarta"
APP_STATUS: "active"
- name: 5. Tunggu kontainer baru siap menerima request (Health Check Verification)
uri:
url: "http://localhost:{{ app_port }}/"
status_code: 200
register: health_check
# Coba hubungi endpoint hingga 10 kali dengan jeda 3 detik tiap percobaan
until: health_check.status == 200
retries: 10
delay: 3
Penggunaan Health Check Native Docker #
Docker memiliki fitur pemeriksaan kesehatan bawaan (native health check) yang memungkinkan daemon memeriksa kondisi aplikasi di dalam kontainer secara berkala. Jika aplikasi hang atau mengalami kegagalan internal, status kontainer akan berubah menjadi unhealthy. Informasi status kesehatan ini sangat berguna agar orkestrator atau proxy jaringan mengetahui kapan harus menghentikan pengiriman trafik ke kontainer tersebut.
Kita dapat mengonfigurasi parameter pemeriksaan kesehatan ini langsung dari task docker_container Ansible:
- name: Deploy container Node.js dengan healthcheck native Docker
community.docker.docker_container:
name: api-service
image: node:18-alpine
state: started
ports:
- "3000:3000"
healthcheck:
# Perintah CLI yang dijalankan di dalam kontainer untuk memeriksa kesehatan
test: ["CMD", "curl", "-f", "http://localhost:3000/healthz"]
# Jalankan pemeriksaan setiap 30 detik
interval: 30s
# Jika perintah memakan waktu lebih dari 5 detik, anggap gagal
timeout: 5s
# Toleransi kegagalan berturut-turut sebelum kontainer ditandai unhealthy
retries: 3
# Masa tenggang awal saat kontainer baru start sebelum pemeriksaan dimulai
start_period: 10s
Dengan mengaktifkan konfigurasi pemeriksaan kesehatan bawaan ini, kita memindahkan tanggung jawab pengawasan ke level daemon Docker. Kita juga dapat memadukannya dengan parameter restart_policy agar daemon otomatis me-restart kontainer jika mendeteksi status unhealthy dalam jangka waktu tertentu.
Mengelola Jaringan dan Volume Terisolasi #
Selain menjalankan kontainer itu sendiri, arsitektur microservices tingkat lanjut mewajibkan kita untuk mengatur isolasi jaringan dan manajemen media penyimpanan persisten secara terpisah. Mengandalkan pembuatan jaringan bridge bawaan Docker secara otomatis sering kali membuat kontainer kita berada dalam satu segmen jaringan yang sama, yang melanggar prinsip keamanan hak akses minimal (principle of least privilege).
Ansible menyediakan modul community.docker.docker_network dan community.docker.docker_volume yang memungkinkan kita membuat infrastruktur pendukung kontainer secara terisolasi dan aman sebelum kontainer itu sendiri dijalankan.
# playbooks/tasks/setup_networks_volumes.yml
---
- name: Buat jaringan bridge terisolasi untuk backend aplikasi
community.docker.docker_network:
name: app-backend-net
driver: bridge
internal: true # Kontainer di jaringan ini tidak dapat mengakses internet luar secara langsung
ipam_config:
- subnet: "172.22.0.0/16"
gateway: "172.22.0.1"
- name: Buat jaringan bridge untuk web public-facing
community.docker.docker_network:
name: app-public-net
driver: bridge
internal: false # Diperbolehkan akses internet luar
- name: Buat volume persisten dengan driver kustom
community.docker.docker_volume:
name: app-db-volume
state: present
driver: local
driver_options:
type: none
device: /var/lib/postgresql/data
o: bind
Dengan mengaitkan kontainer ke jaringan yang tepat, misalnya menghubungkan database hanya ke app-backend-net dan proxy web nginx ke kedua jaringan (app-public-net dan app-backend-net), kita menciptakan arsitektur multi-tier yang sangat aman, mirip dengan konsep DMZ (Demilitarized Zone) di jaringan tradisional.
Monitoring dan Inspeksi Kontainer via Ansible #
Setelah kontainer berjalan, terkadang playbook kita perlu mengambil data dinamis dari kontainer tersebut—seperti alamat IP internal, status kesehatan saat ini, atau direktori mount yang aktif—untuk digunakan oleh task berikutnya (misalnya untuk mengonfigurasi load balancer nginx di host lain).
Kita dapat menggunakan modul community.docker.docker_container_info untuk melakukan query detail runtime kontainer secara real-time:
# playbooks/tasks/inspect_container.yml
---
- name: Kumpulkan detail informasi kontainer database
community.docker.docker_container_info:
name: prod-database
register: db_info
- name: Tampilkan status kesehatan database
debug:
msg: "Database saat ini berstatus: {{ db_info.container.State.Health.Status }}"
when: db_info.exists
- name: Ambil alamat IP internal database
set_fact:
db_internal_ip: "{{ db_info.container.NetworkSettings.Networks['app-backend-net'].IPAddress }}"
when:
- db_info.exists
- "'app-backend-net' in db_info.container.NetworkSettings.Networks"
- name: Gunakan IP database untuk konfigurasi aplikasi web
debug:
msg: "Aplikasi akan dihubungkan ke IP database: {{ db_internal_ip }}"
when: db_internal_ip is defined
Otomatisasi inspeksi ini membebaskan kita dari keharusan menulis script parsing string hasil perintah docker inspect secara manual, yang sering kali rapuh dan tidak konsisten.
Ringkasan #
- Gunakan Pola Deklaratif — Hindari menjalankan perintah imperatif docker run secara manual di CLI. Selalu gunakan modul
docker_containeruntuk menjamin proses deployment yang idempoten.- Pasang Python SDK di Target Host — Pastikan pustaka Python
dockerterinstal di server tujuan agar modul Ansible Docker dapat berinteraksi dengan API socket Docker daemon.- Kencangkan Keamanan Runtime — Terapkan opsi
read_only: truepada kontainer yang tidak memerlukan akses tulis pada sistem berkas root, lalu gunakan mounttmpfsuntuk menulis data sementara secara aman.- Enkripsi dengan Ansible Vault — Lindungi seluruh variabel sensitif (seperti database credentials atau API keys) menggunakan Ansible Vault dan tambahkan opsi
no_log: truepada task deployment.- Deployment Minim Downtime — Lakukan pull image baru terlebih dahulu sebelum memperbarui kontainer, lalu pasang task verifikasi endpoint HTTP (
urimodule) untuk memastikan aplikasi siap melayani lalu lintas.- Manfaatkan Health Check Native — Konfigurasikan opsi pemeriksaan kesehatan bawaan Docker (
healthcheck) pada moduldocker_containeruntuk mengotomasi pengawasan kestabilan aplikasi langsung oleh daemon Docker.
← Sebelumnya: Provision Host Berikutnya: Ansible vs Docker Compose →