
Ketika membuat wadah Docker, yang terbaik adalah selalu berusaha untuk meminimalkan ukuran gambar. Gambar yang menggunakan lapisan yang sama dan lebih ringan - lebih cepat ditransfer dan digunakan.
Tetapi bagaimana cara mengontrol ukuran ketika setiap eksekusi dari pernyataan RUN
membuat layer baru? Selain itu, Anda masih memerlukan artefak perantara sebelum membuat gambar itu sendiri ...
Anda mungkin tahu bahwa sebagian besar file Docker memiliki fitur yang agak aneh, misalnya:
FROM ubuntu RUN apt-get update && apt-get install vim
Nah, mengapa &&
sini? Bukankah lebih mudah menjalankan dua pernyataan RUN
, seperti di sini?
FROM ubuntu RUN apt-get update RUN apt-get install vim
Dimulai dengan Docker versi 1.10, operator COPY
, ADD
dan RUN
menambahkan layer baru pada gambar. Dalam contoh sebelumnya, dua lapisan dibuat bukan satu.

Layers sebagai git melakukan.
Lapisan Docker mempertahankan perbedaan antara versi gambar sebelumnya dan saat ini. Dan seperti halnya git commit, mereka berguna jika Anda membaginya dengan repositori atau gambar lain. Bahkan, ketika meminta gambar dari registri, hanya lapisan yang hilang dimuat, yang menyederhanakan pemisahan gambar di antara kontainer.
Tetapi pada saat yang sama, setiap lapisan terjadi, dan semakin banyak dari mereka, semakin berat gambar akhir. Repositori Git serupa dalam hal ini: ukuran repositori bertambah dengan jumlah layer, karena ia harus menyimpan semua perubahan di antara commit. Ini digunakan sebagai praktik yang baik untuk menggabungkan beberapa pernyataan RUN
pada baris yang sama, seperti pada contoh pertama. Tapi sekarang, sayangnya, tidak.
1. Gabungkan beberapa layer menjadi satu menggunakan rakitan Docker-gambar
Ketika repositori Git bertambah, Anda bisa meringkas seluruh histori perubahan menjadi satu komit dan melupakannya. Ternyata sesuatu yang serupa dapat diimplementasikan di Docker - melalui perakitan bertahap.
Mari kita membuat wadah Node.js.
Mari kita mulai dengan index.js
:
const express = require('express') const app = express() app.get('/', (req, res) => res.send('Hello World!')) app.listen(3000, () => { console.log(`Example app listening on port 3000!`) })
dan package.json
:
{ "name": "hello-world", "version": "1.0.0", "main": "index.js", "dependencies": { "express": "^4.16.2" }, "scripts": { "start": "node index.js" } }
Kemas aplikasi dengan Dockerfile
berikut:
FROM node:8 EXPOSE 3000 WORKDIR /app COPY package.json index.js ./ RUN npm install CMD ["npm", "start"]
Buat gambar:
$ docker build -t node-vanilla .
Periksa apakah semuanya berfungsi:
$ docker run -p 3000:3000 -ti --rm --init node-vanilla
Sekarang Anda dapat mengikuti tautan: http: // localhost: 3000 dan lihat "Hello World!"
Di Dockerfile
sekarang memiliki operator COPY
dan RUN
, jadi kami memperbaiki kenaikan setidaknya dua lapisan, dibandingkan dengan gambar asli:
$ docker history node-vanilla IMAGE CREATED BY SIZE 075d229d3f48 /bin/sh -c #(nop) CMD ["npm" "start"] 0B bc8c3cc813ae /bin/sh -c npm install 2.91MB bac31afb6f42 /bin/sh -c #(nop) COPY multi:3071ddd474429e1⦠364B 500a9fbef90e /bin/sh -c #(nop) WORKDIR /app 0B 78b28027dfbf /bin/sh -c #(nop) EXPOSE 3000 0B b87c2ad8344d /bin/sh -c #(nop) CMD ["node"] 0B <missing> /bin/sh -c set -ex && for key in 6A010⦠4.17MB <missing> /bin/sh -c #(nop) ENV YARN_VERSION=1.3.2 0B <missing> /bin/sh -c ARCH= && dpkgArch="$(dpkg --print⦠56.9MB <missing> /bin/sh -c #(nop) ENV NODE_VERSION=8.9.4 0B <missing> /bin/sh -c set -ex && for key in 94AE3⦠129kB <missing> /bin/sh -c groupadd --gid 1000 node && use⦠335kB <missing> /bin/sh -c set -ex; apt-get update; apt-ge⦠324MB <missing> /bin/sh -c apt-get update && apt-get install⦠123MB <missing> /bin/sh -c set -ex; if ! command -v gpg > /⦠0B <missing> /bin/sh -c apt-get update && apt-get install⦠44.6MB <missing> /bin/sh -c #(nop) CMD ["bash"] 0B <missing> /bin/sh -c #(nop) ADD file:1dd78a123212328bd⦠123MB
Seperti yang Anda lihat, gambar akhir telah meningkat lima lapisan baru: satu untuk setiap operator di Dockerfile
kami. Sekarang mari kita coba membangun Docker bertahap. Kami menggunakan Dockerfile
sama, terdiri dari dua bagian:
FROM node:8 as build WORKDIR /app COPY package.json index.js ./ RUN npm install FROM node:8 COPY --from=build /app / EXPOSE 3000 CMD ["index.js"]
Bagian pertama Dockerfile
membuat tiga lapisan. Kemudian layer digabungkan dan disalin ke tahap kedua dan terakhir. Dua lapisan lagi ditambahkan ke gambar di atas. Hasilnya, kami memiliki tiga lapisan.

Ayo kita coba. Pertama, buat wadah:
$ docker build -t node-multi-stage .
Memeriksa sejarah:
$ docker history node-multi-stage IMAGE CREATED BY SIZE 331b81a245b1 /bin/sh -c #(nop) CMD ["index.js"] 0B bdfc932314af /bin/sh -c #(nop) EXPOSE 3000 0B f8992f6c62a6 /bin/sh -c #(nop) COPY dir:e2b57dff89be62f77⦠1.62MB b87c2ad8344d /bin/sh -c #(nop) CMD ["node"] 0B <missing> /bin/sh -c set -ex && for key in 6A010⦠4.17MB <missing> /bin/sh -c #(nop) ENV YARN_VERSION=1.3.2 0B <missing> /bin/sh -c ARCH= && dpkgArch="$(dpkg --print⦠56.9MB <missing> /bin/sh -c #(nop) ENV NODE_VERSION=8.9.4 0B <missing> /bin/sh -c set -ex && for key in 94AE3⦠129kB <missing> /bin/sh -c groupadd --gid 1000 node && use⦠335kB <missing> /bin/sh -c set -ex; apt-get update; apt-ge⦠324MB <missing> /bin/sh -c apt-get update && apt-get install⦠123MB <missing> /bin/sh -c set -ex; if ! command -v gpg > /⦠0B <missing> /bin/sh -c apt-get update && apt-get install⦠44.6MB <missing> /bin/sh -c #(nop) CMD ["bash"] 0B <missing> /bin/sh -c #(nop) ADD file:1dd78a123212328bd⦠123MB
Lihat apakah ukuran file telah berubah:
$ docker images | grep node- node-multi-stage 331b81a245b1 678MB node-vanilla 075d229d3f48 679MB
Ya, sudah menjadi lebih kecil, tetapi belum signifikan.
2. Kami menghapus semua yang tidak perlu dari wadah menggunakan distroless
Gambar saat ini memberi kami Node.js, yarn
, npm
, bash
, dan banyak binari berguna lainnya. Juga, ini didasarkan pada Ubuntu. Dengan demikian, dengan menyebarkannya, kami mendapatkan sistem operasi lengkap dengan banyak biner dan utilitas yang bermanfaat.
Namun, kami tidak membutuhkan mereka untuk menjalankan wadah. Satu-satunya ketergantungan yang diperlukan adalah Node.js.
Kontainer Docker harus mendukung operasi satu proses dan berisi set alat minimum yang diperlukan untuk menjalankannya. Seluruh sistem operasi tidak diperlukan untuk ini.
Jadi kita bisa mendapatkan semuanya kecuali Node.js.
Tapi bagaimana caranya?
Google telah menghasilkan solusi serupa - GoogleCloudPlatform / distroless .
Deskripsi untuk repositori berbunyi:
Gambar distroless hanya berisi aplikasi dan dependensinya. Tidak ada manajer paket, shell, atau program lain yang biasanya ditemukan dalam distribusi Linux standar.
Ini yang kamu butuhkan!
Jalankan Dockerfile
untuk mendapatkan gambar baru:
FROM node:8 as build WORKDIR /app COPY package.json index.js ./ RUN npm install FROM gcr.io/distroless/nodejs COPY --from=build /app / EXPOSE 3000 CMD ["index.js"]
Kami mengumpulkan gambar seperti biasa:
$ docker build -t node-distroless .
Aplikasi harus bekerja dengan baik. Untuk memeriksa, jalankan wadah:
$ docker run -p 3000:3000 -ti --rm --init node-distroless
Dan buka http: // localhost: 3000 . Apakah gambar menjadi lebih mudah tanpa binari tambahan?
$ docker images | grep node-distroless node-distroless 7b4db3b7f1e5 76.7MB
Seperti itu saja! Sekarang beratnya hanya 76,7 MB, sebanyak 600 MB lebih sedikit!
Semuanya keren, tapi ada satu poin penting. Saat wadah berjalan, dan Anda perlu memeriksanya, Anda dapat terhubung menggunakan:
$ docker exec -ti <insert_docker_id> bash
Menyambung ke wadah yang sedang berjalan dan memulai bash
sangat mirip dengan membuat sesi SSH.
Tetapi karena distroless adalah versi yang dilucuti dari sistem operasi asli, tidak ada binari tambahan, atau, sebenarnya, sebuah shell!
Bagaimana cara terhubung ke wadah yang sedang berjalan jika tidak ada shell?
Yang paling menarik adalah tidak ada apa-apa.
Ini tidak terlalu baik, karena hanya binari yang dapat dieksekusi dalam sebuah wadah. Dan satu-satunya yang dapat diluncurkan adalah Node.js:
$ docker exec -ti <insert_docker_id> node
Bahkan, ada nilai tambah dalam hal ini, karena jika beberapa penyerang dapat memperoleh akses ke wadah, itu akan jauh lebih sedikit ruginya daripada jika memiliki akses ke shell. Dengan kata lain, lebih sedikit binari - bobot lebih sedikit dan keamanan yang lebih tinggi. Tapi, dengan biaya debugging yang lebih kompleks.
Di sini harus dicatat bahwa tidak layak menghubungkan dan men-debug kontainer di lingkungan prod. Lebih baik mengandalkan sistem logging dan pemantauan yang dikonfigurasi dengan benar.
Tetapi bagaimana jika kita masih perlu debugging, namun kita ingin gambar buruh pelabuhan menjadi yang terkecil?
3. Kurangi gambar dasar dengan Alpine
Anda dapat mengganti distroless dengan gambar Alpine.
Alpine Linux adalah distribusi yang berorientasi keamanan, ringan berdasarkan musl libc dan busybox . Tapi kami tidak akan mengambil kata, melainkan memeriksanya.
Jalankan Dockerfile
menggunakan node:8-alpine
:
FROM node:8 as build WORKDIR /app COPY package.json index.js ./ RUN npm install FROM node:8-alpine COPY --from=build /app / EXPOSE 3000 CMD ["npm", "start"]
Buat gambar:
$ docker build -t node-alpine .
Periksa ukuran:
$ docker images | grep node-alpine node-alpine aa1f85f8e724 69.7MB
Pada output, kami memiliki 69,7MB - ini bahkan kurang dari gambar tanpa distro.
Mari kita periksa apakah mungkin untuk terhubung ke wadah yang berfungsi (dalam hal gambar distrolles, kita tidak bisa melakukan ini).
Luncurkan wadah:
$ docker run -p 3000:3000 -ti --rm --init node-alpine Example app listening on port 3000!
Dan hubungkan:
$ docker exec -ti 9d8e97e307d7 bash OCI runtime exec failed: exec failed: container_linux.go:296: starting container process caused "exec: \"bash\": executable file not found in $PATH": unknown
Tidak berhasil Tapi mungkin wadahnya sudah ...:
$ docker exec -ti 9d8e97e307d7 sh / #
Hebat! Kami berhasil terhubung ke wadah, dan pada saat yang sama citranya juga lebih kecil. Namun di sini ada beberapa nuansa.
Gambar Alpine didasarkan pada muslc, pustaka standar alternatif untuk C. Sementara sebagian besar distribusi Linux, seperti Ubuntu, Debian, dan CentOS, didasarkan pada glibc. Dipercaya bahwa kedua perpustakaan ini menyediakan antarmuka yang sama untuk bekerja dengan kernel.
Namun, mereka memiliki tujuan yang berbeda: glibc adalah yang paling umum dan cepat, sementara muslc mengambil lebih sedikit ruang dan ditulis dengan bias keamanan. Ketika suatu aplikasi mengkompilasi, sebagai suatu peraturan, ia mengkompilasi ke pustaka C. tertentu. Jika Anda perlu menggunakannya dengan pustaka lain, Anda harus melakukan kompilasi ulang.
Dengan kata lain, membuat wadah pada gambar Alpine dapat menyebabkan kejadian yang tidak terduga, karena pustaka C standar yang digunakan di dalamnya berbeda. Perbedaannya akan terlihat ketika bekerja dengan binari yang dikompilasi, seperti ekstensi Node.js untuk C ++.
Misalnya, paket PhantomJS yang sudah jadi tidak berfungsi di Alpine.
Jadi apa gambar dasar untuk dipilih?
Tampilan alpen, Alpine, atau vanilla - tentu saja, lebih baik memutuskan berdasarkan situasinya.
Jika Anda berurusan dengan prod dan keamanan itu penting, mungkin ketidaktahuan akan lebih tepat.
Setiap biner yang ditambahkan ke gambar Docker menambah risiko tertentu pada stabilitas seluruh aplikasi. Risiko ini dapat dikurangi dengan hanya memasang satu biner di wadah.
Misalnya, jika penyerang dapat menemukan kerentanan dalam aplikasi yang berjalan berdasarkan gambar yang tidak jelas, ia tidak dapat menjalankan shell dalam wadah karena tidak ada!
Jika karena alasan tertentu ukuran gambar buruh pelabuhan sangat penting bagi Anda, sudah pasti ada baiknya melihat lebih dekat pada gambar berbasis Alpine.
Mereka benar-benar kecil, tetapi, dengan biaya kompatibilitas. Alpine menggunakan pustaka C standar yang sedikit berbeda, muslc, jadi terkadang masalah akan muncul. Contohnya tersedia di tautan berikut: https://github.com/grpc/grpc/issues/8528 dan https://github.com/grpc/grpc/issues/6126 .
Gambar vanila sangat ideal untuk pengujian dan pengembangan.
Ya, mereka besar, tetapi mereka terlihat seperti mesin lengkap dengan Ubuntu diinstal. Selain itu, semua binari di OS tersedia.
Ringkas ukuran gambar Docker yang diterima:
node:8
681MB
node:8
dengan build incremental 678MB
gcr.io/distroless/nodejs
76.7MB
node:8-alpine
69,7MB
Kata perpisahan dari penerjemah
Baca artikel lain di blog kami:
Cadangan stateful di Kubernetes
Mencadangkan sejumlah besar proyek web heterogen
Bot Telegram untuk Redmine. Cara menyederhanakan hidup untuk diri sendiri dan orang