Layer

Sistem berkas berlapis pada image, mekanisme copy-on-write, dan pembagian layer antar image.

Daftar isi

Image Docker tidak tersusun sebagai satu blok besar, melainkan sebagai tumpukan layer, satuan penyusun image berupa kumpulan perubahan sistem berkas. Setiap layer dihasilkan satu instruksi Dockerfile, resep teks yang mendefinisikan cara image dibangun. Topik ini membahas cara layer tersusun, mekanisme copy-on-write, yaitu penyalinan berkas ke lapisan atas sebelum berkas itu diubah, serta pembagian layer antar image yang menghemat disk dan jaringan. Di akhir topik, sebuah simulasi kecil berbahasa Python memperagakan aturan baca dan tulisnya tanpa memerlukan Docker.

Tumpukan layer pada image#

Setiap instruksi pada Dockerfile menghasilkan satu layer baru yang hanya berisi perbedaan terhadap layer di bawahnya, misalnya berkas yang baru disalin atau paket yang baru dipasang. Docker menumpuk layer-layer itu menjadi satu sistem berkas utuh saat image dipakai. Seluruh layer image bersifat read-only, sehingga tidak ada satu layer pun yang pernah ditulis ulang setelah image selesai dibangun. Saat container dibuat, Docker menambahkan satu lapisan tulis tipis di puncak tumpukan sebagai tempat seluruh perubahan container ditulis.

Diagram berikut memperlihatkan dua image yang tersusun dari tumpukan layer serupa.

layer berkas catatanlayer 3, pustaka aplikasilayer 2, paket sistemlayer 1, alpine 3.20image aplikasi catatandibagilayer berkas statistiklayer 3, pustaka aplikasilayer 2, paket sistemlayer 1, alpine 3.20image aplikasi statistik
Dua image berbagi tiga layer dasar yang sama dan hanya berbeda pada layer teratasnya

Urutan tumpukan pada diagram bersifat tetap, dari layer alpine di dasar hingga layer berkas aplikasi di puncak. Karena isinya tidak berubah, setiap layer dapat dihitung dan dibandingkan antar image. Docker memakai sifat ini untuk berbagi layer sebagaimana dijelaskan pada bagian selanjutnya.

Layer dibagi antar image#

Layer yang isinya sama disimpan sekali saja dan dibagi antar image yang tersimpan pada satu host, komputer tempat Docker berjalan. Docker menyimpan setiap layer secara terpisah, lalu menyusun tiap image dari layer-layer yang dirujuknya. Dua image yang dibangun dari dasar yang sama karenanya hanya menambahkan biaya penyimpanan pada layer pembedanya, sedangkan layer dasar yang sama tidak diduplikasi di disk.

Manfaat pembagian ini terlihat saat pull, proses mengunduh image dari registry, gudang yang melayani penyimpanan dan pendistribusian image. Daemon, proses latar belakang Docker yang mengerjakan pekerjaan container, memeriksa layer mana yang sudah ada di host, lalu hanya mengunduh layer yang belum tersimpan. Layer yang sudah ada dilewati dengan pesan singkat bahwa layer tersebut sudah ada, sedangkan layer yang baru diunduh ditampilkan beserta progresnya. Blok berikut memperlihatkan penarikan image yang sama dua kali.

bash
# tidak-dijalankan: daemon Docker tidak tersedia di sandbox
# penarikan kedua menemukan seluruh layer sudah tersimpan
docker pull alpine:3.20
docker pull alpine:3.20

Penarikan pertama mengunduh setiap layer image sekaligus memverifikasinya. Penarikan kedua menemukan bahwa seluruh layer sudah ada, sehingga daemon melewatkannya dan hanya memeriksa manifest. Manifest adalah berkas metadata dari registry yang mencantumkan daftar layer beserta penanda keabsahannya. Perilaku yang sama berlaku untuk dua image berbeda yang berbagi layer dasar, sehingga semakin banyak image pada host yang berbagi dasar yang sama, semakin sedikit data yang harus melintasi jaringan.

Copy-on-write#

Copy-on-write (salin saat menulis) adalah aturan yang mengatur pembacaan dan penulisan pada sistem berkas berlapis. Saat container membaca sebuah berkas, Docker mencarinya mulai dari lapisan paling atas ke bawah dan memakai salinan pertama yang ditemukan. Saat container mengubah berkas yang hanya ada di layer image, Docker menyalin berkas itu ke lapisan tulis terlebih dahulu, lalu perubahan ditulis pada salinannya. Layer di bawah tidak pernah disentuh, sehingga container lain yang membaca layer yang sama tidak melihat perubahan apa pun. Penghapusan pun bekerja di lapisan atas, yaitu dengan menandai berkas agar tidak lagi terlihat.

Konsekuensinya, mengedit satu berkas besar berarti menyalin seluruh isinya ke lapisan tulis lebih dahulu, walaupun yang berubah hanya satu baris. Untuk data yang banyak ditulis, penyimpanan di luar lapisan tulis lebih tepat, dan topik tentang data persisten pada jalur ini membahasnya lebih lanjut.

Skrip berikut mensimulasikan overlay dua layer dengan dua kamus Python. Fungsi baca memeriksa layer atas dahulu, lalu layer bawah, sesuai urutan pencarian pada sistem berkas berlapis. Fungsi tulis menyalin nilai dari layer bawah ke layer atas sebelum menimpanya, persis seperti copy-on-write.

python simulasi-layer.py
"""Simulasi overlay dua layer dan copy-on-write, tanpa Docker."""

layer_bawah = {  # layer image, bersifat read-only
    "app.conf": "debug=mati",
    "sapaan.txt": "halo",
    "versi": "1.0",
}
layer_atas = {}  # lapisan tulis milik container


def baca(nama):
    """Membaca berkas: layer atas dulu, lalu layer bawah."""
    if nama in layer_atas:
        print(f"baca {nama}: {layer_atas[nama]!r} (layer atas)")
    elif nama in layer_bawah:
        print(f"baca {nama}: {layer_bawah[nama]!r} (layer bawah)")
    else:
        print(f"baca {nama}: tidak ditemukan")


def tulis(nama, isi):
    """Menulis berkas: salin dulu dari layer bawah bila perlu (copy-on-write)."""
    if nama in layer_bawah and nama not in layer_atas:
        print(f"copy-on-write: {nama} disalin ke layer atas sebelum diubah")
    layer_atas[nama] = isi
    print(f"tulis {nama}: {isi!r} (layer atas)")


def tampilkan():
    print(f"layer atas : {layer_atas}")
    print(f"layer bawah: {layer_bawah}")


tampilkan()
baca("sapaan.txt")
tulis("sapaan.txt", "selamat pagi")
baca("sapaan.txt")
baca("app.conf")
tulis("catatan.log", "baris pertama")
tampilkan()
text Keluaran
layer atas : {}
layer bawah: {'app.conf': 'debug=mati', 'sapaan.txt': 'halo', 'versi': '1.0'}
baca sapaan.txt: 'halo' (layer bawah)
copy-on-write: sapaan.txt disalin ke layer atas sebelum diubah
tulis sapaan.txt: 'selamat pagi' (layer atas)
baca sapaan.txt: 'selamat pagi' (layer atas)
baca app.conf: 'debug=mati' (layer bawah)
tulis catatan.log: 'baris pertama' (layer atas)
layer atas : {'sapaan.txt': 'selamat pagi', 'catatan.log': 'baris pertama'}
layer bawah: {'app.conf': 'debug=mati', 'sapaan.txt': 'halo', 'versi': '1.0'}

Pembacaan pertama menemukan sapaan.txt di layer bawah tanpa penyalinan apa pun. Penulisan berikutnya memicu penyalinan karena nilai itu ada di layer bawah, dan pembacaan setelahnya kini mengambil nilai dari layer atas. Pembuatan catatan.log tidak menyalin apa pun karena berkas itu belum ada di layer bawah, sedangkan isi layer bawah tetap tidak berubah sampai baris terakhir.

Kesalahan umum#

  • Mengira image ikut berubah saat container menulis berkas. Container hanya menulis pada lapisan tulisnya, sedangkan layer image tetap read-only. Perbaikannya, bangun image baru apabila isi image memang perlu berubah.
  • Mengira perubahan di satu container terlihat oleh container lain. Yang dibagi antar container hanyalah layer read-only, sedangkan lapisan tulis bersifat privat untuk tiap container. Perbaikannya, data yang perlu dibagi diletakkan di luar container.
  • Menulis data kerja yang besar langsung di lapisan tulis. Copy-on-write menyalin seluruh berkas ke lapisan atas sebelum berkas itu dapat diedit, sehingga penulisan berkas besar menjadi lambat. Perbaikannya, pindahkan data semacam itu ke penyimpanan di luar container.
  • Mengira ukuran pada daftar image sama dengan ruang disk yang benar-benar dipakainya. Ukuran image dihitung sebagai jumlah seluruh layer, sedangkan layer yang dibagi antar image disimpan sekali saja di disk. Perbaikannya, baca ukuran image sebagai ukuran logis, dan gunakan docker system df untuk melihat pemakaian disk yang sebenarnya.

Ringkasan#

  • Image tersusun dari layer read-only yang dihasilkan per instruksi Dockerfile, dan container menambahkan satu lapisan tulis di puncak tumpukan.
  • Layer yang isinya sama disimpan sekali dan dibagi antar image, sehingga pull hanya mengunduh layer yang belum ada di host.
  • Saat membaca, container mencari berkas dari lapisan atas ke bawah, sedangkan saat mengubah, berkas dari layer bawah disalin dahulu ke lapisan tulis sesuai aturan copy-on-write.

Referensi#