Container dirancang untuk terisolasi — punya filesystem sendiri, proses sendiri, dunia kecilnya sendiri. Justru isolasi itulah yang bikin container begitu berguna. Tapi begitu kamu menjalankan lebih dari satu container, atau kamu ingin orang dari luar benar-benar bisa memakai apa yang ada di dalamnya, satu pertanyaan langsung muncul: gimana caranya kotak-kotak yang tersegel rapat ini saling bicara, bicara ke mesin yang menjalankannya, dan bicara ke internet yang lebih luas?
Di situlah peran jaringan container atau container networking. Kedengarannya bikin keder, dan memang punya reputasi sebagai bagian dari dunia container yang sering bikin orang mentok. Kabar baiknya, di balik semua istilahnya cuma ada segelintir ide, dan begitu itu nyantol, hampir semua kejutan jaringan yang bakal kamu temui jadi masuk akal. Yuk kita bangun pelan-pelan dari bawah.
Kenapa container butuh jaringan sama sekali
Bayangkan susunan yang umum: aplikasi web di satu container, database di container lain, mungkin cache di container ketiga. Aplikasi web perlu mengirim query ke database. Pengunjung dari internet perlu menjangkau aplikasi web. Database, idealnya, justru tidak boleh bisa dijangkau dari internet sama sekali — cuma aplikasi web yang boleh melihatnya.
Tidak ada satu pun dari itu yang jalan kalau tiap container adalah kotak tersegel sempurna. Maka platform container memberi tiap container sebuah koneksi jaringan, persis seperti kalau kamu menancapkan kabel jaringan ke komputer sungguhan. Bedanya, semua ini bersifat virtual — tidak ada kabel fisik, tidak ada switch fisik. Platform membuat versi software dari benda-benda itu lalu menyambungkan container-mu ke dalamnya.
Kalau kamu sudah baca soal bagaimana sebuah container mengisolasi sebuah proses, jaringan adalah lubang yang sengaja kamu buat secara terkendali di isolasi itu supaya container-nya jadi berguna. Seluruh seni jaringan container intinya cuma membuka lubang secukupnya — dan tidak lebih.
Tiap container punya identitas jaringannya sendiri
Saat sebuah container dijalankan, platform biasanya memberinya virtual network interface sendiri dan sebuah alamat IP privat sendiri. Ini ide pertama yang harus kamu resapi: sebuah container biasanya punya IP yang berbeda dari mesin host tempat dia berjalan.
Jadi kalau host-mu punya IP seperti 192.168.1.50, sebuah container yang berjalan di atasnya bisa saja mendapat sesuatu seperti 172.17.0.2 di sebuah jaringan privat internal yang cuma ada di dalam host itu. Dari sudut pandang si container, dia terlihat seperti komputer kecil biasa dengan koneksi jaringan biasa.
MESIN HOST (IP 192.168.1.50)
┌──────────────────────────────────────────┐
│ │
│ container A container B │
│ 172.17.0.2 172.17.0.3 │
│ │ │ │
│ └──────── bridge ──────┘ │
│ (172.17.0.1) │
│ │ │
└──────────────────┼───────────────────────┘
│
internet
Alamat-alamat privat tadi hidup di sebuah virtual network yang disiapkan platform. Container yang berada di virtual network yang sama bisa saling menjangkau langsung lewat alamat-alamat tersebut. Container di jaringan yang berbeda tidak bisa — dan pemisahan itu adalah fitur, bukan cacat. Begitulah caramu menjaga database tetap tak terlihat oleh apa pun kecuali service yang memang seharusnya melihatnya.
IP privat itu sementara dan internal
IP privat sebuah container ditetapkan oleh platform dan bisa berubah setiap kali container-nya restart. IP itu juga cuma berarti di dalam virtual network host tersebut — kamu tidak bisa mengetik 172.17.0.2 di browser ponselmu lalu menjangkau apa pun. Itulah kenapa, seperti yang nanti kamu lihat, kita hampir tidak pernah menulis IP container secara permanen. Kita pakai nama sebagai gantinya.
Bridge: switch virtual di tengah
Lihat lagi diagram di atas dan perhatikan benda di tengah yang berlabel bridge. Ini bagian paling penting untuk dipahami, dan dari sinilah istilah “bridge network” berasal.
Bridge itu sebuah switch virtual. Di dunia fisik, network switch adalah kotak tempat kamu menancapkan beberapa komputer supaya bisa saling bicara. Bridge melakukan persis hal itu, tapi dalam bentuk software. Ketika platform dijalankan, dia membuat sebuah bridge default, memberinya sebuah alamat (sering kali seperti 172.17.0.1), dan tiap container yang kamu jalankan otomatis tersambung ke situ kecuali kamu mengatur lain.
Karena semua container itu berbagi satu bridge, mereka semua duduk di jaringan privat kecil yang sama dan bisa saling menjangkau. Bridge juga berperan sebagai gateway mereka — ketika sebuah container ingin bicara ke sesuatu di luar host (misalnya mengunduh update dari internet), traffic-nya menuju bridge, lalu bridge meneruskannya ke koneksi jaringan asli milik host.
Jadi bridge memainkan dua peran sekaligus:
- Sebuah switch yang menyambungkan container satu sama lain.
- Sebuah gateway yang menyambungkan container ke luar, ke sisa dunia.
Ini adalah perilaku default di kebanyakan platform container, dan untuk banyak kasus sederhana kamu tidak pernah perlu memikirkannya. Semuanya jalan begitu saja. Repotnya biasanya mulai muncul di tepi — saat memasukkan traffic dari luar, dan saat membuat container saling menemukan dengan andal. Dua hal itulah yang dibahas di dua bagian berikutnya.
Memasukkan traffic dari luar: publish port
Ini kenyataan yang awalnya membingungkan: secara default, sebuah container bisa dijangkau oleh container lain di jaringannya, tapi tidak bisa dijangkau dari dunia luar — bahkan tidak dari mesin host dengan cara yang biasa kamu bayangkan. Aplikasi web-mu mungkin asyik mendengarkan di port 80 di dalam container-nya, tapi mengetik alamat host di browser malah tidak memberi apa-apa.
Kenapa? Karena port 80 milik container itu hidup di virtual network privat tadi. Dunia luar tidak punya jalur ke sana. Untuk mengatasinya, kamu mem-publish port itu (kadang disebut “expose” atau “mapping”) — kamu memberitahu platform untuk menyambungkan sebuah port di host ke sebuah port di dalam container.
Browser di internet
│
▼
host:8080 ──publish──► container:80
(bisa dijangkau) (aplikasi mendengarkan di sini)
Dalam praktiknya, ini ditulis sebagai pemetaan berformat PORT_HOST:PORT_CONTAINER. Flag yang umum di command line adalah -p:
# Petakan port 8080 di host ke port 80 di dalam container
run -p 8080:80 my-web-app
Sekarang sebuah permintaan yang tiba di host pada port 8080 akan diteruskan masuk ke port 80 milik container. Container-nya sendiri tidak berubah sama sekali — dia tetap mendengarkan di port 80 secara internal. Kamu cuma membangun sebuah pintu dari port host menuju ke dalamnya.
Beberapa hal yang perlu kamu tahu soal publish port:
- Dua angkanya tidak harus sama.
8080:80berarti “host 8080 menuju container 80.” Kamu bisa menjalankan tiga container yang semuanya mendengarkan di port 80 secara internal, lalu mem-publish-nya ke port host 8080, 8081, dan 8082 supaya tidak bentrok. - Satu port host cuma bisa dipakai sekali. Kalau dua container sama-sama mencoba mem-publish ke port host 8080, yang kedua bakal gagal. Port internal boleh diulang sebebasnya; port host tidak boleh.
- Publish hanya yang memang perlu publik. Database-mu juga mendengarkan di sebuah port, tapi kamu hampir tidak pernah mem-publish-nya. Membiarkannya tidak di-publish berarti dia bisa dijangkau oleh container aplikasimu (jaringan yang sama) tapi tak terlihat dari luar — persis seperti yang kamu mau.
Publish secukupnya, jangan royal
Setiap port yang di-publish adalah sebuah pintu masuk ke sistemmu. Database, cache, panel admin internal — biasanya benda-benda ini sama sekali tidak ada urusannya untuk bisa dijangkau dari internet. Biarkan mereka di jaringan internal dan izinkan hanya service yang menghadap publik (aplikasi web-mu) yang bicara dengannya. Kalau kamu mendapati dirimu mem-publish port database “cuma buat ngetes sesuatu,” ingat untuk menutupnya lagi. Ini cara pikir least-privilege yang sama, yang menjaga server sungguhan tetap aman.
Kalau kamu sudah baca soal port dan protokol, ini bakal terasa akrab — port yang di-publish cuma port biasa di host yang diteruskan ke dalam. Tidak ada yang aneh; ini konsep port yang sama yang sudah kamu kenal, diterapkan melewati batas container.
Cara container saling menemukan: nama, bukan IP
Tadi kita bilang IP container berubah saat restart dan tidak boleh ditulis permanen. Lalu gimana caranya container aplikasi web benar-benar menjangkau container database kalau dia tidak boleh mengandalkan 172.17.0.3?
Jawabannya adalah DNS antar-container. Platform container modern menjalankan sebuah layanan DNS bawaan yang kecil di tiap jaringan buatan pengguna. Ketika kamu menaruh beberapa container di satu jaringan dan memberi mereka nama, mereka bisa saling menjangkau lewat nama, dan platform menerjemahkan nama itu ke IP apa pun yang sedang dimiliki container tersebut.
container aplikasi web container database
"shop-web" "shop-db"
│ ▲
│ sambung ke "shop-db:5432" │
└────────────────────────────────┘
(DNS menerjemahkan nama
ke IP yang sedang aktif)
Jadi alih-alih mengonfigurasi aplikasimu dengan alamat IP yang rapuh, kamu mengonfigurasinya dengan nama yang stabil seperti shop-db. Container database boleh restart, mendapat IP baru sama sekali, dan aplikasimu tetap jalan — karena dia menanyakan nama-nya, dan platform mengembalikan alamat yang benar setiap saat.
Bridge default tidak melakukan resolusi nama
Ada jebakan pemula klasik di sini. Jaringan bridge default milik platform biasanya tidak memberimu penemuan berbasis nama — container di sana cuma bisa saling menjangkau lewat IP. Resolusi nama bekerja di jaringan buatan pengguna (jaringan yang kamu buat sendiri). Maka saran standarnya: buat jaringanmu sendiri, sambungkan container-container yang berkaitan ke situ, dan biarkan mereka saling menemukan lewat nama. Kalau dua container “tidak bisa saling melihat” padahal berjalan berdampingan, hal pertama yang harus dicek adalah apakah mereka berada di satu jaringan buatan pengguna bersama, atau malah terdampar di bridge default.
Inilah kenapa perkakas container menganjurkanmu untuk mengelompokkan container-container yang berkaitan ke sebuah jaringan bersama yang diberi nama. Satu keputusan itu memberimu sekaligus konektivitas (mereka bisa saling menjangkau) dan penemuan (mereka bisa saling menemukan lewat nama).
Isolasi jaringan sebagai alat keamanan
Di sinilah jaringan berhenti jadi sekadar urusan pipa dan berubah jadi desain. Karena kamu mengendalikan container mana yang berbagi sebuah jaringan, kamu mengendalikan siapa boleh bicara dengan siapa — dan itu salah satu batas keamanan paling bersih yang kamu punya.
Sebuah pola yang umum dan masuk akal terlihat seperti ini:
- Sebuah jaringan frontend tempat web server yang menghadap publik berada. Port-nya di-publish, jadi internet bisa menjangkaunya.
- Sebuah jaringan backend yang menampung database dan service internal lainnya. Tidak ada satu pun di sini yang di-publish.
- Aplikasi web disambungkan ke dua-duanya, jadi dia bisa menerima permintaan dari sisi publik dan meneruskan query ke sisi privat.
internet
│
▼ (port di-publish)
┌─────────────┐ ┌──────────────────────┐
│ jaringan │ │ jaringan │
│ frontend │ │ backend │
│ │ │ │
│ aplikasi ───┼────────┼──► database │
│ web │ │ cache │
└─────────────┘ └──────────────────────┘
(tidak ada port di-publish —
tak terjangkau dari internet)
Database benar-benar tidak punya jalur dari internet. Bahkan kalau seorang penyerang menemukan port-nya, tidak ada rute menuju ke sana — dia hidup di jaringan privat tanpa pintu yang di-publish. Satu-satunya jalan masuk adalah lewat aplikasi web, yang berarti permukaan seranganmu jadi kecil dan disengaja. Ini prinsip yang sama yang kamu pakai saat menempatkan sebuah reverse proxy di depan service internal: buka satu pintu masuk yang dikendalikan dengan hati-hati, dan jaga semua yang lain tetap privat di belakangnya.
Daftar cek mental yang cepat
Saat ada yang tidak nyambung, telusuri hal-hal ini secara urut — daftar ini menangkap sebagian besar masalah jaringan container:
- Apakah kedua container berada di jaringan buatan pengguna yang sama? Kalau tidak, mereka tidak bisa saling melihat (dan di bridge default, tidak bisa me-resolve nama).
- Apakah aplikasi di dalamnya benar-benar mendengarkan di port yang kamu kira? Container yang mendengarkan di
127.0.0.1alih-alih0.0.0.0tidak akan menerima koneksi dari luar dirinya sendiri, bahkan dengan port yang sudah di-publish. - Sudahkah kamu mem-publish port-nya ke host? Kalau kamu bisa menjangkau sebuah service dari container lain tapi tidak dari browser, kemungkinan besar kamu lupa pemetaan
HOST:CONTAINER. - Apakah port host-nya sudah dipakai? Dua container tidak bisa mem-publish ke port host yang sama, dan program lain di host bisa saja sudah memegangnya.
- Apakah kamu memakai nama padahal IP-nya mungkin sudah berubah? IP yang ditulis permanen rusak saat restart; nama tidak.
Daftar singkat itu menyelesaikan sebagian besar momen “tapi mereka kan bersebelahan, kok tidak bisa bicara?”.
Penutup
Jaringan container tidak semisterius reputasinya. Kalau diringkas, kamu dapat:
- Container terisolasi secara default, jadi platform memberi tiap container sebuah virtual network interface dan sebuah IP privat agar dia bisa berkomunikasi sama sekali.
- Bridge adalah switch dan gateway virtual di tengah: dia menyambungkan container satu sama lain dan meneruskan traffic mereka keluar ke dunia.
- Dunia luar tidak bisa menjangkau sebuah container sampai kamu mem-publish sebuah port, memetakan
PORT_HOSTkePORT_CONTAINER. Publish hanya yang memang perlu publik. - Container saling menemukan lewat nama via DNS bawaan — tapi hanya di jaringan buatan pengguna, bukan bridge default. Nama bertahan melewati restart; IP tidak.
- Memilih container mana yang berbagi sebuah jaringan adalah batas keamanan yang nyata: jauhkan database dan service internal dari jaringan mana pun yang di-publish, supaya internet tidak bisa menjangkaunya.
Begitu kamu bisa membayangkan bridge, port yang di-publish, dan jaringan bernama, sisanya sebagian besar cuma variasi dari segelintir gerakan ini. Berikutnya, ada baiknya kita mundur sejenak untuk membandingkan container dengan menjalankan langsung di atas mesin — apa yang kamu korbankan, dan apa yang kamu dapat, ketika memilih container ketimbang bare metal.