Hari 3 — Docker Compose: Membedah docker-compose.yml yang Sudah Dipakai
Rabu, 2 jam. Compose sudah dipakai sejak Minggu 3 (Airflow) dan Minggu 4 (+Kafka) — sesi ini membedah tiap bagiannya.
Tujuan Belajar
- Menjelaskan fungsi Docker Compose: mendefinisikan & menjalankan banyak container sebagai 1 kesatuan
- Membaca & menjelaskan tiap bagian
docker-compose.ymlyang sudah dipakai:services,depends_on,environment,volumes,ports,build - Menjelaskan network default Compose dan bagaimana service saling terhubung lewat nama service sebagai hostname
- Menjelaskan kenapa
host.docker.internaldibutuhkan untukpg-belajar(Minggu 3) — sesuatu yang tidak akan dibutuhkan kalaupg-belajarada di file Compose yang sama
Untuk Instruktur
Ini kesempatan bagus untuk peserta membuka file docker-compose.yml mereka sendiri (bukan contoh instruktur) dan menjelaskan tiap barisnya sendiri, dipandu pertanyaan. Kalau ada baris yang tidak bisa dijelaskan, itu sinyal bagian yang perlu didalami — jauh lebih efektif daripada instruktur menjelaskan file yang tidak familiar.
Konsep & Sintaks
Kenapa Compose Dibutuhkan
docker run cocok untuk 1 container. Begitu kebutuhan naik jadi banyak container yang saling terhubung (Airflow scheduler + webserver + Postgres metadata + Redis + Kafka, seperti setup sejak Minggu 3-4), menjalankan & mengelola tiap docker run manual satu-satu jadi tidak praktis — perlu diingat urutan start, nama container, port, environment variable, network, dst tiap kali. Docker Compose menyelesaikan ini: 1 file YAML mendeklarasikan semua service dan hubungan antar mereka, dijalankan dengan 1 perintah (docker compose up).
Anatomi docker-compose.yml yang Sudah Dipakai
Potongan relevan dari setup Airflow (Minggu 3) + Kafka (Minggu 4):
services:
airflow-webserver:
build: . # <- pakai Dockerfile custom (hari_2_dockerfile.md), bukan image resmi apa adanya
depends_on:
- postgres
environment:
AIRFLOW__CORE__EXECUTOR: LocalExecutor
volumes:
- ./dags:/opt/airflow/dags
- ./spark_jobs:/opt/airflow/spark_jobs
- ./data:/opt/airflow/data
ports:
- "8080:8080"
postgres:
image: postgres:13 # <- Postgres INTERNAL milik Airflow (metadata DB), BEDA dari pg-belajar
environment:
POSTGRES_USER: airflow
POSTGRES_PASSWORD: airflow
kafka:
image: bitnami/kafka:3.7
ports:
- "9092:9092"
environment:
- KAFKA_CFG_NODE_ID=0
- KAFKA_CFG_PROCESS_ROLES=controller,broker
| Field | Fungsi |
|---|---|
services | Daftar semua container yang dikelola Compose ini — tiap key (airflow-webserver, postgres, kafka) jadi 1 service |
build: . | Build image dari Dockerfile di direktori ini, bukan pakai image jadi dari registry |
image: postgres:13 | Alternatif build — langsung pakai image dari registry apa adanya |
depends_on | Urutan start container (bukan urutan “siap dipakai” — lihat Kesalahan Umum #1) |
environment | Environment variable yang di-inject ke dalam container saat jalan |
volumes | Bind mount (kode/data dari host) atau named volume (dikelola Docker) — detail penuh di hari_4_networking_volume.md |
ports | Format "HOST:CONTAINER" — memetakan port di container supaya bisa diakses dari mesin host (8080:8080 artinya localhost:8080 di laptop kamu terhubung ke port 8080 di dalam container) |
Poin penting yang sering terlewat: postgres di file Compose Airflow ini adalah database metadata internal Airflow sendiri (menyimpan status DAG run, task instance, dst) — beda total dari pg-belajar (Postgres warehouse tempat fact_sales/dim_* di-load, Minggu 1-3). Dua Postgres yang berbeda kepentingan, kebetulan sama-sama bernama mirip.
Network: Kenapa Service Bisa Saling Panggil Pakai Nama
Compose otomatis membuat 1 network bersama untuk semua service dalam file yang sama — di dalam network itu, tiap service bisa saling terhubung memakai nama service-nya sebagai hostname (Docker menyediakan DNS internal otomatis), tanpa perlu tahu IP container.
Network Compose "ecommerce-etl-pipeline_default"
┌─────────────────────────────────────────────────┐
│ airflow-webserver <──────> postgres │
│ │ │ │
│ └──────────> kafka <─────┘ │
└─────────────────────────────────────────────────┘
Ini sebabnya Airflow bisa terhubung ke Postgres metadata-nya lewat host postgres (bukan localhost atau IP) — nama service itu langsung “resolve” ke container yang benar, selama keduanya didefinisikan di file Compose yang sama.
Kenapa pg-belajar Butuh host.docker.internal (Sambungan dari Minggu 3)
pg-belajar (warehouse) dijalankan terpisah lewat docker run biasa (Minggu 1) — bukan bagian dari docker-compose.yml Airflow. Karena itu, pg-belajar tidak ada di network Compose yang sama dengan airflow-webserver, sehingga tidak bisa dipanggil cukup pakai nama container-nya. host.docker.internal adalah hostname khusus yang disediakan Docker Desktop untuk mengakses mesin host dari dalam container — karena port 5432 pg-belajar sudah di-publish ke host, ini jadi jalan pintas untuk menjangkaunya.
Kalau pg-belajar didefinisikan sebagai service di file Compose yang sama (alternatif desain yang valid, tidak dilakukan sengaja di Minggu 3 untuk memisahkan konsep “warehouse yang dipakai sejak Minggu 1” dari “infrastruktur orkestrasi Minggu 3”), maka koneksinya bisa cukup pakai postgresql://...@pg-belajar:5432/... — tanpa host.docker.internal sama sekali, persis seperti postgres (metadata Airflow) di atas.
Kesalahan Umum
- Mengira
depends_onmenunggu service “siap dipakai”.depends_oncuma menjamin urutan container mulai berjalan (start), bukan menjamin aplikasi di dalamnya sudah siap menerima koneksi. Postgres container bisa “started” tapi proses database-nya masih inisialisasi beberapa detik — service lain yangdepends_on: [postgres]bisa saja mencoba konek terlalu cepat dan gagal. Solusi produksi:healthcheck+depends_on: condition: service_healthy(di luar cakupan wajib minggu ini, tapi baik diketahui ada). - Menaruh port yang sama di beberapa service.
ports: "8080:8080"di 2 service berbeda akan bentrok saatdocker compose up— port di sisi host harus unik per service yang publish ke port yang sama itu. - Lupa
docker compose downsetelah selesai, membiarkan resource menyala terus. Beda daridocker compose stop(cuma stop, container & network masih ada),docker compose downbenar-benar membersihkan container & network yang dibuat Compose — kebiasaan baik untuk laptop yang resource-nya terbatas. - Mengira semua service di 1 file Compose otomatis “satu kesatuan” dengan container yang dijalankan
docker runterpisah. Seperti kasuspg-belajardi atas — container daridocker runbiasa tidak otomatis masuk ke network Compose manapun, kecuali sengaja dihubungkan (docker network connect) atau diakses lewathost.docker.internal.
Latihan
- Buka
docker-compose.ymldi repoecommerce-etl-pipelinekamu sendiri. Untuk tiap service di dalamnya, jelaskan: pakaiimageataubuild? Port apa yang di-publish ke host, dan kenapa perlu? - Kalau kamu tambahkan service baru
rediske file Compose yang sama denganairflow-webserver, host apa yang harus dipakaiairflow-webserveruntuk konek ke Redis itu — nama service, IP tertentu, atauhost.docker.internal? Jelaskan alasannya. - Jelaskan kenapa
depends_on: [postgres]diairflow-webservertidak cukup untuk menjamin Airflow tidak akan pernah gagal konek ke Postgres saat startup — skenario konkret apa yang bisa membuatnya tetap gagal walaudepends_onsudah dipasang? kafka(Minggu 4) danpg-belajar(Minggu 1) sama-sama dibutuhkanstreaming-demo/producer.py/consumer.py(yang jalan dari host, bukan dari dalam container Compose manapun, sesuaimateri/minggu_4/latihan_dq_streaming_mini_project.md). Kenapaproducer.pybisa konek ke Kafka cukup pakailocalhost:9092(bukanhost.docker.internalatau nama servicekafka)?
Kunci Jawaban & Pembahasan
1. Jawaban bervariasi tergantung isi file masing-masing peserta, tapi pola yang diharapkan: airflow-webserver/airflow-scheduler pakai build: . (custom Dockerfile dengan Java+PySpark+GE), postgres (metadata) dan kafka pakai image langsung (tidak butuh modifikasi). Port yang di-publish ke host: 8080 (Airflow UI, diakses dari browser di mesin host) dan 9092 (Kafka broker, supaya producer.py/consumer.py yang jalan dari host venv bisa terhubung).
2. Pakai nama service (redis), bukan host.docker.internal atau IP — karena redis dan airflow-webserver didefinisikan di file Compose yang sama, keduanya otomatis berada di network Compose bersama, dan DNS internal Docker akan meresolusi redis ke container yang benar. host.docker.internal cuma dibutuhkan untuk kasus seperti pg-belajar, yang berada di luar file Compose ini (dijalankan docker run terpisah).
3. Skenario konkret: container postgres sudah berstatus “running” (proses container-nya jalan), tapi proses PostgreSQL di dalamnya masih dalam tahap inisialisasi database (beberapa detik pertama setelah start, terutama kalau ini pertama kali volume-nya dibuat). depends_on cuma menjamin Docker memulai container postgres lebih dulu sebelum airflow-webserver — begitu container postgres “started” (bukan berarti “siap menerima koneksi”), Docker langsung lanjut start airflow-webserver, yang mungkin langsung mencoba konek dalam hitungan milidetik, lebih cepat dari waktu Postgres benar-benar siap menerima koneksi. Ini kenapa healthcheck + condition: service_healthy (disinggung di Kesalahan Umum #1) jadi solusi yang lebih tepat untuk production, dibanding depends_on polos.
4. Karena producer.py/consumer.py dijalankan dari host (venv Python di laptop, bukan dari dalam container manapun) — dari sudut pandang host, port 9092 Kafka sudah di-publish langsung ke localhost:9092 (lewat ports: "9092:9092" di Compose). Konsep hostname-service-sebagai-DNS (kafka sebagai nama) cuma berlaku di dalam network Docker, antar container — begitu kamu berada di luar Docker (di host), akses ke container manapun yang portnya di-publish selalu lewat localhost:<port_host>, sama sekali tidak relevan pakai host.docker.internal (itu untuk arah sebaliknya: dari dalam container mengakses host) atau nama service (itu untuk antar container).