Tiga trik sederhana untuk mengurangi gambar buruh pelabuhan

gambar

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.


gambar


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.


gambar


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

Source: https://habr.com/ru/post/id437372/


All Articles