Optimasi Layer

Penggabungan RUN, pembersihan cache dalam satu layer, dan pemanfaatan cache mount.

Daftar isi

Setiap instruksi pada Dockerfile menyisakan satu layer, lapisan penyusun image, dan layer itu bersifat permanen, termasuk berkas sementara yang dibuat lalu dihapus di instruksi lain. Topik ini membahas konsekuensi sifat itu terhadap ukuran image, pola pembersihan yang benar dalam satu instruksi RUN, urutan COPY yang menghormati cache build, serta cache mount BuildKit yang menjaga cache apt dan pip agar tidak masuk layer sama sekali. Contohnya kembali memakai Dockerfile aplikasi catatan berbasis python:3.12-slim.

Layer RUN bersifat permanen#

Setiap instruksi RUN mengeksekusi perintahnya, menyimpan hasil akhir sistem berkasnya sebagai layer baru, lalu layer itu tidak pernah berubah lagi. Apabila berkas dihapus pada RUN berikutnya, penghapusan itu menjadi layer tersendiri yang hanya menandai berkas sebagai tersembunyi, sedangkan layer sebelumnya tetap menyimpan berkas aslinya. Ukuran image dihitung dari penjumlahan seluruh layer, sehingga rm pada RUN terpisah tidak pernah mengecilkan image, malah menambah satu layer baru. Dockerfile berikut memperagakan pola yang keliru.

dockerfile Contoh: pembersihan pada RUN terpisah yang tidak mengecilkan image
FROM python:3.12-slim
WORKDIR /aplikasi
RUN apt-get update && apt-get install -y --no-install-recommends gcc
RUN rm -rf /var/lib/apt/lists/*
COPY app.py .
CMD ["python3", "app.py"]

Pada Dockerfile tersebut, apt-get update mengunduh daftar paket ke /var/lib/apt/lists/, dan daftar itu terkunci di layer hasil RUN pertama. RUN rm di bawahnya hanya menyembunyikan daftar itu pada layer berikutnya, sehingga ukuran image tetap memuatnya. Pembersihan yang benar harus terjadi sebelum layer ditutup, yaitu di instruksi yang sama dengan pemasangannya.

Pembersihan dalam satu instruksi RUN#

Rangkaian && menjadikan pemasangan dan pembersihan satu instruksi, sehingga berkas sementara tidak pernah tersimpan di layer mana pun. Dockerfile berikut memperbaiki pola yang keliru di atas.

dockerfile Contoh: pemasangan dan pembersihan dirangkai dalam satu RUN
FROM python:3.12-slim
WORKDIR /aplikasi
RUN apt-get update \
    && apt-get install -y --no-install-recommends gcc \
    && rm -rf /var/lib/apt/lists/*
COPY app.py .
CMD ["python3", "app.py"]

Dengan rangkaian itu, image hanya memuat gcc yang benar-benar dipasang, tanpa daftar paket, tanpa arsip unduhan, dan tanpa layer pembersihan. Opsi --no-install-recommends ikut mengecilkan hasilnya dengan melewatkan paket yang hanya direkomendasikan. Pola yang sama berlaku untuk pip melalui opsi --no-cache-dir, misalnya pip install --no-cache-dir -r requirements.txt. Opsi itu membuat arsip wheel, paket biner siap pasang, yang diunduh tidak tertinggal di /root/.cache/pip.

Ukuran yang diharapkan cukup dinyatakan secara kualitatif, karena nilainya bergantung pada paket yang dipasang. Image sebelum perbaikan memuat seluruh cache apt sebagai isi yang tersembunyi di dalam layer, sedangkan image sesudahnya mendekati jumlah ukuran paket yang benar-benar dipasang. Perbedaannya dapat diaudit dengan docker history sebagaimana dibahas pada topik audit ukuran image.

bash
# tidak-dijalankan: daemon Docker tidak tersedia di sandbox
# membandingkan SIZE image sebelum dan sesudah pola pembersihan diterapkan
docker images aplikasi-catatan

Urutan COPY yang menghormati cache#

Ukuran dan kecepatan build berjalan berpasangan, karena pembersihan yang bagus menjadi sia-sia apabila cache build selalu kedaluwarsa. Prinsipnya, instruksi yang jarang berubah ditulis di atas, sedangkan kode aplikasi yang sering berubah ditulis paling akhir. Dockerfile di atas sudah menerapkannya sebagian, yaitu pemasangan gcc berlangsung sebelum COPY app.py ., sehingga suntingan kode aplikasi tidak memicu pemasangan ulang paket. Kesalahan pengurutan yang sering terjadi adalah COPY . . di awal, sehingga setiap perubahan berkas apa pun menginvalidasi layer di bawahnya, termasuk layer dependensi yang paling mahal dibangun ulang.

Untuk aplikasi yang berdependensi pada requirements.txt, urutan yang benar menyalin requirements.txt lebih dahulu, memasang dependensinya, baru menyalin app.py. Dengan urutan itu, layer dependensi tetap terpakai dari cache meskipun kode aplikasi berubah setiap hari. Aturan lengkapnya, termasuk syarat cache berlaku per instruksi, dibahas pada topik Build Caching.

Cache mount BuildKit#

Pembersihan satu RUN menyelesaikan ukuran layer, tetapi cache yang dibuang harus diunduh lagi pada build berikutnya. Cache mount BuildKit menutup celah itu, yaitu fitur BuildKit yang menempelkan direktori cache hanya selama instruksi berjalan, sehingga isinya dipakai ulang antar build tanpa pernah menjadi bagian layer. Docker Engine modern memakai BuildKit sebagai builder bawaannya, komponen yang mengeksekusi build, sehingga sintaks ini langsung dapat dipakai.

dockerfile Contoh: cache mount untuk cache apt dan pip
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
    --mount=type=cache,target=/var/lib/apt,sharing=locked \
    apt-get update && apt-get install -y --no-install-recommends gcc

RUN --mount=type=cache,target=/root/.cache/pip \
    pip install -r requirements.txt

Pada instruksi pertama, cache apt berada di dalam mount, sehingga rm -rf /var/lib/apt/lists/* tidak diperlukan lagi dan daftar paket yang diunduh tetap terpakai pada build berikutnya. Pada instruksi kedua, opsi --no-cache-dir sengaja dilepas agar pip menyimpan arsip unduhannya ke /root/.cache/pip yang ditempelkan sebagai cache mount. Hasilnya ada dua, yaitu image yang tidak memuat cache apa pun sekaligus cache yang dipakai ulang tanpa memperbesar image. Perbandingannya sederhana, yaitu pembersihan satu RUN cocok untuk cache yang tidak dipakai ulang, sedangkan cache mount cocok untuk cache yang mahal diunduh.

Kesalahan umum#

  • Menulis rm -rf /var/lib/apt/lists/* pada RUN terpisah. Layer sebelumnya tetap memuat daftar paketnya. Perbaikannya, rangkai pemasangan dan pembersihan dalam satu instruksi.
  • Memisahkan apt-get update dan apt-get install ke dua RUN berbeda. Setiap RUN menutup layer, sehingga daftar paket tersimpan permanen dan isinya dapat kedaluwarsa. Perbaikannya, rangkaikan keduanya beserta pembersihannya dalam satu instruksi.
  • Mempertahankan --no-cache-dir bersama cache mount pip. Opsi itu mengosongkan cache, sehingga mount yang ditempelkan tidak pernah terisi. Perbaikannya, lepas opsi tersebut pada instruksi yang memakai cache mount.

Ringkasan#

  • rm pada RUN berikutnya tidak mengecilkan image, karena layer sebelumnya tetap menyimpan berkas yang dihapus.
  • Pemasangan dan pembersihan dirangkai dalam satu RUN dengan &&, sehingga berkas sementara tidak pernah masuk layer mana pun.
  • Urutan instruksi menempatkan bagian yang jarang berubah di atas agar layer dependensi bertahan di cache build.
  • Cache mount BuildKit menempelkan cache apt dan pip hanya selama build, sehingga cache dipakai ulang tanpa memperbesar image.

Referensi#