Deploy Container

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_container untuk menjamin proses deployment yang idempoten.
  • Pasang Python SDK di Target Host — Pastikan pustaka Python docker terinstal di server tujuan agar modul Ansible Docker dapat berinteraksi dengan API socket Docker daemon.
  • Kencangkan Keamanan Runtime — Terapkan opsi read_only: true pada kontainer yang tidak memerlukan akses tulis pada sistem berkas root, lalu gunakan mount tmpfs untuk 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: true pada task deployment.
  • Deployment Minim Downtime — Lakukan pull image baru terlebih dahulu sebelum memperbarui kontainer, lalu pasang task verifikasi endpoint HTTP (uri module) untuk memastikan aplikasi siap melayani lalu lintas.
  • Manfaatkan Health Check Native — Konfigurasikan opsi pemeriksaan kesehatan bawaan Docker (healthcheck) pada modul docker_container untuk mengotomasi pengawasan kestabilan aplikasi langsung oleh daemon Docker.

← Sebelumnya: Provision Host   Berikutnya: Ansible vs Docker Compose →

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