Як зменшити розмір образу Docker: 6 методів оптимізації

Як зменшити розмір образу Docker: 6 методів оптимізації
Переклад 18 хв. читання

Якщо ви хочете зменшити розмір докер-образу, використовуйте найкращі стандартні практики створення докер-образів.

У цій статті ми розповімо про різні методи оптимізації, які ви можете швидко застосувати, щоб створити якомога менший образ докера. Ми також розглянемо деякі з найкращих інструментів для оптимізації.

Docker як контейнерний рушій дозволяє легко взяти шматок коду і запустити його всередині контейнера. Це допомагає інженерам зібрати всі залежності коду та файли в одному місці, звідки їх можна запустити будь-де, досить швидко та легко.

Вся концепція образів, які можна запускати будь-де, починається з простого конфігураційного файлу, який називається Dockerfile. Спочатку ми додаємо всі інструкції зі збирання, такі як залежності коду, команди та деталі базового образу, у Dockerfile.

Потреба в оптимізації образів Docker

Незважаючи на те, що процес збирання Docker є простим, багато організацій припускаються помилки, створюючи роздуті та не оптимізовані образи.

При типовій розробці програмного забезпечення кожен сервіс має декілька версій/випусків, і кожна версія вимагає більше залежностей, команд та конфігурацій. Це створює проблеми при створенні образів, оскільки той самий код потребує більше часу та ресурсів для створення, перш ніж його можна буде відправити у вигляді контейнера.

Я бачив випадки, коли початковий образ програми починався з 350 МБ, а з часом він збільшувався до більш ніж 1,5 ГБ.

Крім того, встановлюючи небажані бібліотеки, ми збільшуємо ймовірність потенційного ризику для безпеки, збільшуючи простір для атак.

Тому інженери DevOps повинні оптимізувати образи, щоб запобігти роздуванню докерів після збірок програми або майбутніх випусків. Не тільки для робочих середовищ, але й на кожному етапі процесу CI/CD ви повинні здійснювати оптимізацію.

Крім того, з використанням інструментів оркестрування контейнерів, таких як Kubernetes, краще мати образи невеликого розміру, щоб скоротити час на передачу та розгортання.

Як зменшити розмір образу докера?

Якщо ми візьмемо образ контейнера типової програми, він містить базовий образ, залежності/файли/конфіги та сміття (небажане програмне забезпечення).

Як зменшити розмір образу Docker: 6 методів оптимізації

Отже, все зводиться до того, наскільки ефективно ми можемо керувати цими ресурсами всередині образу контейнера.

Розгляньмо різні усталені методи оптимізації образів Docker. Крім того, ми навели практичні приклади для розуміння оптимізації образів докера в реальному часі.

Ви можете скористатися наведеними в статті прикладами або спробувати методи оптимізації на існуючих Docker-файлах.

Нижче наведено методи, використовуючи які ми можемо оптимізувати образи.

  1. Використання бездистрибутивних / мінімальних базових образів
  2. Багатоетапні збірки
  3. Мінімізація кількості шарів
  4. Розуміння кешування
  5. Використання Dockerignore
  6. Зберігання даних програми поза межами контейнера
Docker-файли для вправ: Весь код програми, Docker-файли та конфіги, використані в цій статті, розміщені в цьому репозиторії Github. Ви можете клонувати його і слідувати інструкціям.

Спосіб 1: Використовуйте мінімальні базові образи

Перш за все, необхідно зосередитися на виборі правильного базового образу з мінімальним розміром.

Одним з таких прикладів є альпійські базові образи alpine. Ці образи можуть мати розмір всього 5.59 МБ. Це означає, що він не лише невеликий, але й максимально безпечний.

alpine       latest    c059bfaa849c     5.59MB

Базовий образ Nginx alpine займає лише 22 МБ.

За замовчуванням, він постачається з командним рядком sh, що дозволяє під'єднатися до контейнера для налагодження.

Ви можете ще більше зменшити розмір базового образу за допомогою бездистрибутивних образів. Це урізана версія операційної системи. Бездистрибутивні базові образи доступні для java, nodejs, python, Rust тощо.

Бездистрибутивні образи настільки мінімізовані, що в них навіть немає оболонки. Ви можете запитати, як же тоді налагоджувати програми? Для налагодження є версія того ж самого образу, яка постачається з busybox.

Крім того, зараз більшість дистрибутивів мають мінімальні базові образи.

Примітка: Базові образи, що є у відкритому доступі, не можна безпосередньо використовувати у проєктних середовищах. Вам потрібно отримати дозвіл від служби безпеки підприємства на використання базового образу. У деяких організаціях служба безпеки сама публікує базові збірки щомісяця після тестування і сканування на безпеку. Ці образи будуть доступні у загальному приватному сховищі організації.

Спосіб 2: Використання багатоетапних збірок Docker

Шаблон багатоетапної збірки розвинувся з концепції шаблону білдера, де ми використовуємо різні Docker-файли для збірки та пакування коду програми. Хоча цей патерн допомагає зменшити розмір образу, він створює невеликі накладні витрати, коли справа доходить до побудови конвеєрів.

При багатоетапній збірці ми отримуємо ті ж переваги, що і при використанні патерну білдера. У цьому підході ми використовуємо проміжні образи (етапи збірки) для компіляції коду, встановлення залежностей та пакувальних файлів. Ідея полягає в тому, щоб усунути непотрібні шари в образі.

Після цього на інший образ копіюються лише необхідні для запуску програми файли, які містять лише необхідні бібліотеки, тобто полегшують запуск програми. Розглянемо це на практичному прикладі, де ми створимо простий застосунок на Nodejs та оптимізуємо його Docker-файл.

Спочатку створимо код. У нас буде наступна структура каталогів.

├── Dockerfile1
├── Dockerfile2
├── env
├── index.js
└── package.json

Збережіть наступний код як index.js.

const dotenv=require('dotenv'); 
dotenv.config({ path: './env' });

dotenv.config();

const express=require("express");
const app=express();

app.get('/',(req,res)=>{
  res.send(`Learning to Optimize Docker Images with DevOpsCube!`);
});


app.listen(process.env.PORT,(err)=>{
    if(err){
        console.log(`Error: ${err.message}`);
    }else{
        console.log(`Listening on port ${process.env.PORT}`);
    }
  }
)

Збережіть наступне як package.json.

{
  "name": "nodejs",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "dotenv": "^10.0.0",
    "express": "^4.17.2"
  }
}

Збережіть наступну змінну з портом у файлі з назвою env.

PORT=8080

Простий Dockerfile для цієї програми буде виглядати так - Збережіть його як Dockerfile1.

FROM node:16
COPY . .
RUN npm install
EXPOSE 3000
CMD [ "node", "index.js" ]

Перевіримо скільки необхідно місця для його збірки.

docker build -t devopscube/node-app:1.0 --no-cache -f Dockerfile1 .

Після завершення збірки. Перевіримо його розмір за допомогою команди:

docker image ls

Ось що ми отримуємо.

devopscube/node-app   1.0       b15397d01cca   22 seconds ago   910MB

Отже, розмір складає 910 МБ.

Тепер використаймо цей метод для створення багатоетапної збірки.

Ми використаємо node:16 як базовий образ, тобто образ для встановлення усіх залежностей та модулів, після чого перемістимо його вміст до мінімального та легшого "alpine" образу. "Alpine" образ має мінімальний набір утиліт, а отже є дуже легким.

Ось графічне зображення багатоетапної збірки Docker.

Як зменшити розмір образу Docker: 6 методів оптимізації

Крім того, в одному Dockerfile ви можете мати декілька етапів з різними базовими образами. Наприклад, ви можете мати різні етапи для збірки, тестування, статичного аналізу та па кування з різними базовими образами.

Розглянемо, як може виглядати новий Docker-файл. Ми просто скопіюємо необхідні файли з базового образу в основний.

Збережемо наступний файл як Dockerfile2.

FROM node:16 as build

WORKDIR /app
COPY package.json index.js env ./
RUN npm install

FROM node:alpine as main

COPY --from=build /app /
EXPOSE 8080
CMD ["index.js"]

Розглянемо, скільки місця для зберігання він потребує. Збираємо командою:

docker build -t devopscube/node-app:2.0 --no-cache -f Dockerfile2 .

Після завершення збірки. Перевіримо його розмір за допомогою

docker image ls

Ось що ми отримуємо.

devopscube/node-app   2.0       fa6ae75da252   32 seconds ago   171MB

Таким чином, новий зменшений розмір образу становить 171 МБ у порівнянні з образом з усіма залежностями.

Тобто оптимізація становить понад 80%!

Втім, якби ми використали той самий базовий образ, що і на етапі збирання, то не побачили б великої різниці.

Ви можете ще сильніше зменшити розмір образу, використовуючи бездистрибутивні образи. Ось той самий Docker-файл з багатоетапною збіркою, в якому замість alpine використовується бездистрибутивний образ Google для nodeJS.

FROM node:16 as build

WORKDIR /app
COPY package.json index.js env ./
RUN npm install

FROM gcr.io/distroless/nodejs

COPY --from=build /app /
EXPOSE 3000
CMD ["index.js"]

Якщо ви зберете наведений вище Docker-файл, розмір вашого образу становитиме 118 МБ,

devopscube/distroless-node   1.0       302990bc5e76     118MB

Спосіб 3: Мінімізуйте кількість шарів

Образи докера працюють наступним чином - кожна команда RUN, COPY, FROM в докер-файлі додає новий шар, і кожен шар збільшує час виконання збірки та збільшує вимоги до місця для зберігання образу.

Розгляньмо це на практичному прикладі: створимо образ ubuntu з оновленими та модернізованими бібліотеками, а також деякими необхідними пакетами, такими як vim, net-tools, dnsutils.

Docker-файл для цього буде наступним - Збережіть його як Dockerfile3.

FROM ubuntu:latest
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update -y
RUN apt-get upgrade -y
RUN apt-get install vim -y
RUN apt-get install net-tools -y
RUN apt-get install dnsutils -y

Ми також хочемо дізнатися час збирання цього образу.

Демон Docker має вбудовану можливість показувати загальний час виконання Docker-файлу.

Щоб увімкнути цю функцію, виконайте наступні кроки

  1. Створіть файл daemon.json з таким вмістом у каталозі /etc/docker/
{
  "experimental": true
}
  1. Щоб увімкнути цю функцію, виконайте наступну команду.
export DOCKER_BUILDKIT=1
  1. Створюємо його та перевіряємо розмір та час збирання
time docker build -t devopscube/optimize:3.0 --no-cache -f Dockerfile3 .
  1. Команда time виведе час виконання
time docker build -t devopscube/optimize:3.0 --no-cache -f Dockerfile3 .


[+] Building 117.1s (10/10) FINISHED                                                                   
 => [internal] load build definition from Dockerfile                                              
.
.
.
.                                                                       
 => => writing image sha256:9601bcac010062c656dacacbc7c554b8ba552c7174f32fdcbd24ff9c7482a805      0.0s 
 => => naming to docker.io/devopscube/optimize:3.0                                                0.0s 
                                                                                                       
real    1m57.219s                                                                                      
user	0m1.062s
sys	0m0.911

Після завершення збірки час виконання складає 117.1 секунди.

Перевіримо його розмір за допомогою команди

docker image ls

Ось що ми отримали.

devopscube/optimize  3.0   9601bcac0100   About a minute ago   227MB

Отже, розмір становить 227 МБ.

Об'єднаємо команди RUN в один шар і збережемо його як Dockerfile4.

FROM ubuntu:latest
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update -y && apt-get upgrade -y && apt-get install --no-install-recommends vim net-tools dnsutils -y

У наведеній вище команді RUN ми використали прапорець --no-install-recommends для вимкнення рекомендованих пакунків. Це рекомендується робити, коли ви використовуєте install у ваших Docker-файлах.

Тепер подивимося, який обсяг пам'яті та час потрібно для збірки.

time docker build -t devopscube/optimize:4.0 --no-cache -f Dockerfile4 .

Він буде відображати час виконання в терміналі.

time docker build -t devopscube/optimize:0.4 --no-cache -f Dockerfile4 .


[+] Building 91.7s (6/6) FINISHED                                                                      
 => [internal] load build definition from Dockerfile2                                             0.4s
.
.
.  
 => => naming to docker.io/devopscube/optimize:4.0                                                0.0s 
                                                                                                       
real    1m31.874s                                                                                      
user	0m0.884s
sys	0m0.679s

Після завершення збірки - час виконання становить 91.7 секунд.

Перевіримо його розмір за допомогою

docker image ls

Ось що ми отримуємо.

devopscube/optimize  4.0   37d746b976e3   42 seconds ago      216MB

Таким чином, розмір файлу становить 216 МБ.

Використовуючи цю техніку оптимізації, час виконання зменшився з 117,1 с до 91,7 с, а загальний розмір файлу зменшився з 227 МБ до 216 МБ.

Спосіб 4: Розуміння кешування

Часто один і той самий образ доводиться збирати знову і знову з невеликими змінами у коді.

У таких випадках Docker може допомогти, зберігаючи кеш кожного шару збірки, сподіваючись, що він може знадобитися у майбутньому.

Згідно з цією концепцією, рекомендується додавати рядки, які використовуються для встановлення залежностей та пакунків, на початку Docker-файлу - перед командами COPY.

Причиною цього є те, що докер зможе кешувати образ з необхідними залежностями, і цей кеш можна буде використовувати у наступних збірках, коли код буде змінено.

Для прикладу, розглянемо наступні два докер-файли.

Dockerfile 5

FROM ubuntu:latest
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update -y && \
    apt-get upgrade -y && \
    apt-get install -y vim net-tools dnsutils
COPY . .

Dockerfile 6

FROM ubuntu:latest
COPY . .
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update -y && \
    apt-get upgrade -y && \
    apt-get install -y vim net-tools dnsutils

Docker зможе краще використовувати функціонал кешу у Dockerfile5, ніж у Dockerfile6, завдяки кращому розташуванню команди COPY.

Спосіб 5: Використання Dockerignore

Як правило, до образу докера потрібно копіювати лише необхідні файли.

Docker може ігнорувати файли, наявні у робочому каталозі, якщо це налаштовано у файлі .dockerignore.

Про цю особливість слід пам'ятати під час оптимізації образу докера.

Спосіб 6: Зберігайте дані програми в іншому місці

Зберігання даних програми в образі призведе до надмірного збільшення розміру образів.

Наполегливо рекомендується використовувати функцію volume, щоб відокремити дані від образу контейнера.

Інструменти оптимізації образів Docker

Нижче наведено деякі інструменти з відкритим вихідним кодом, які допоможуть вам оптимізувати образи Docker. Ви можете вибрати інструмент і зробити його частиною конвеєра образів Docker, щоб забезпечити створення лише оптимізованих образів для розгортання програм.

  1. Dive: Це інструмент для аналізу образів, який допоможе вам виявити шари в образах контейнерів Docker і OCI. Використовуючи Dive, ви можете знайти способи оптимізації ваших образів Docker. Перегляньте репозиторій Dive на Github для більш детальної інформації.
  2. Docker Slim: допомагає оптимізувати ваші образи Docker для безпеки та розміру. Дізнайтеся більше про Docker Slim у репозиторії Github. За допомогою Slim ви можете зменшити розмір образу докера до 30 разів.
  3. Docker Squash: Ця утиліта допомагає зменшити розмір образу завдяки стисненню шарів. Функція стиснення також доступна у Docker CLI за допомогою прапорця squash.

Підсумок

Наведені вище методи допоможуть вам створювати оптимізовані образи докерів і писати кращі докер-файли.

Крім того, якщо ви будете дотримуватися всіх стандартних практик роботи з контейнерами, ви зможете зменшити розмір докер-образу, що дасть змогу розгортати легші образи.

Крім того, з погляду DevSecOps, ви можете використовувати інструменти сканування вразливостей з відкритим вихідним кодом, такі як Trivy, для сканування образів Docker

Джерело: How to Reduce Docker Image Size: 6 Optimization Methods
Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація