Docker Tips: Очистіть свою машину від мотлоху

Docker Tips: Очистіть свою машину від мотлоху

Привіт, Хабре! Представляю вашій увазі переклад статті "Docker Tips: Clean Up Your Local Machine" автора Luc Juggery.

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


Docker Tips: Очистіть свою машину від мотлоху

Загальне споживання

Docker - крута штука, напевно, сьогодні мало хто в цьому сумнівається. Лише кілька років тому цей продукт надав нам абсолютно новий спосіб побудови, доставки та запуску будь-якого оточення, дозволяючи значно заощадити ресурси процесора та оперативної пам'яті. На додаток до цього (а для когось це буде навіть найважливішим) Docker дозволив нам неймовірно спростити та уніфікувати управління життєвим циклом використовуваних робочих середовищ.

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

Якщо ви ніколи не замислювалися про те, скільки ж місця реально зайнято на вашій машині Docker'ом, то можете бути неприємно здивовані висновком цієї команди:

$ docker system df

Docker Tips: Очистіть свою машину від мотлоху

Тут відображено використання диска Docker в різних розрізах:

  • образи (images) – загальний розмір образів, які були завантажені зі сховищ образів та побудовані у вашій системі;
  • контейнери (containers) – загальний обсяг дискового простору, використовуваний запущеними контейнерами (мається на увазі загальний обсяг верств читання-запису всіх контейнерів);
  • локальні томи (local volumes) – обсяг локальних сховищ, примонтованих до контейнерів;
  • кеш складання (build cache) – тимчасові файли, згенеровані процесом побудови образів (при використанні інструменту BuildKit, доступного починаючи з Docker версії 18.09).

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

Використання диска контейнерами

Щоразу при створенні контейнера на хостовій машині в каталозі /var/lib/docker створюється кілька файлів та каталогів, серед яких варто зазначити такі:

  • Каталог /var/lib/docker/containers/ID_контейнера – під час використання стандартного драйвера логування саме сюди зберігаються журнали подій у JSON-форматі. Занадто докладні логи, а також логи, які ніхто не читає та не обробляє іншими способами, часто спричиняють переповнення дисків.
  • Каталог /var/lib/docker/overlay2 – містить шари читання-запису контейнерів (overlay2 – віддані перевагу драйверу більшості дистрибутивів Linux). Якщо контейнер зберігає дані у своїй файловій системі, то саме в цьому каталозі вони будуть розміщені.

Давайте уявімо собі систему, на якій встановлений незайманий Docker, який жодного разу не брав участі в запуску контейнерів і збиранні образів. Його звіт про використання дискового простору виглядатиме так:

$ docker system df
TYPE           TOTAL      ACTIVE     SIZE       RECLAIMABLE
Images         0          0          0B         0B
Containers     0          0          0B         0B
Local Volumes  0          0          0B         0B
Build Cache    0          0          0B         0B

Запустимо якийсь контейнер, наприклад, NGINX:

$ docker container run --name www -d -p 8000:80 nginx:1.16

Що відбувається з диском:

  • образи займають 126 Мб, це той самий NGINX, який ми запустили в контейнері;
  • контейнери (containers) займають смішні 2 байти.

$ docker system df
TYPE           TOTAL      ACTIVE     SIZE       RECLAIMABLE
Images         1          1          126M       0B (0%)
Containers     1          1          2B         0B (0%)
Local Volumes  0          0          0B         0B
Build Cache    0          0          0B         0B

Судячи з висновку, ми ще не маємо простору, який ми могли б вивільнити. Так як 2 байти це абсолютно несерйозно, давайте уявимо, що наш NGINX несподівано для всіх написав кудись 100 мегабайт даних і створив у собі файл test.img саме такого розміру.

$ docker exec -ti www 
  dd if=/dev/zero of=test.img bs=1024 count=0 seek=$[1024*100]

Знову досліджуємо використання дискового простору на хості. Ми побачимо, що контейнер (containers) займає там 100 мегабайт.

$ docker system df
TYPE           TOTAL      ACTIVE     SIZE       RECLAIMABLE
Images         1          1          126M       0B (0%)
Containers     1          1          104.9MB    0B (0%)
Local Volumes  0          0          0B         0B
Build Cache    0          0          0B         0B

Думаю, ваш допитливий мозок вже ставить питання, де ж знаходиться наш файл test.img. Давайте його пошукаємо:

$ find /var/lib/docker -type f -name test.img
/var/lib/docker/overlay2/83f177...630078/merged/test.img
/var/lib/docker/overlay2/83f177...630078/diff/test.img

Не вдаючись до подробиць, можна відзначити, що файл test.img зручно розташувався на рівні читання-запису, керованому драйвером overlay2. Якщо ми зупинимо наш контейнер, то хост підкаже нам, що це місце, в принципі, можна звільнити:

# Stopping the www container
$ docker stop www

# Visualizing the impact on the disk usage
$ docker system df
TYPE           TOTAL      ACTIVE     SIZE       RECLAIMABLE
Images         1          1          126M       0B (0%)
Containers     1          0          104.9MB    104.9MB (100%)
Local Volumes  0          0          0B         0B
Build Cache    0          0          0B         0B

Як ми можемо це зробити? Видаленням контейнера, що спричинить очищення відповідного простору на рівні читання-запису.

За допомогою наступної команди ви можете видалити всі встановлені контейнери одним махом і очистити диск від усіх створених ними на рівні читання-запису файлів:

$ docker container prune
WARNING! This will remove all stopped containers.
Are you sure you want to continue? [y/N] y
Deleted Containers:
5e7f8e5097ace9ef5518ebf0c6fc2062ff024efb495f11ccc89df21ec9b4dcc2

Total reclaimed space: 104.9MB

Отже, ми вивільнили 104,9 мегабайта видаленням контейнера. Але оскільки ми вже не використовуємо завантажений раніше образ, він теж стає кандидатом на видалення і вивільнення наших ресурсів:

$ docker system df
TYPE           TOTAL      ACTIVE     SIZE       RECLAIMABLE
Images         1          0          126M       126M (100%)
Containers     0          0          0B         0B
Local Volumes  0          0          0B         0B
Build Cache    0          0          0B         0B

Увага: доки образ використовується хоча б одним контейнером, ви не зможете використовувати цей трюк.

Субкоманда prune, яку ми використовували вище, дає ефект лише на зупинених контейнерах. Якщо ми хочемо видалити не лише зупинені, а й запущені контейнери, слід використовувати одну з цих команд:

# Historical command
$ docker rm -f $(docker ps –aq)

# More recent command
$ docker container rm -f $(docker container ls -aq)

Нотатки на полях: якщо при запуску контейнера використовувати параметр -rm, то при його зупинці буде вивільнено весь дисковий простір, який він займав.

Використання диска образами

Кілька років тому розмір образу кілька сотень мегабайт був цілком нормальним: образ Ubuntu важив 600 Мегабайт, а образ Microsoft .Net – кілька Гігабайт. У ті кудлаті часи скачування одного лише образу могло завдати великої шкоди вашому вільному місцю на диску, навіть якщо ви розшарували рівні між образами. Сьогодні – хвала великим – образи важать набагато менше, але навіть у цьому випадку можна швидко забити наявні ресурси, якщо не вживати деяких запобіжних заходів.

Є кілька типів образів, які безпосередньо не видно кінцевому користувачеві:

  • intermediate образи, на основі яких зібрані інші образи – вони не можуть бути видалені, якщо ви використовуєте контейнери на базі цих самих «інших» образів;
  • dangling образи – це такі intermediate образи, куди не посилається жоден із запущених контейнерів – можуть бути видалені.
  • За допомогою наступної команди ви можете перевірити наявність у вашій системі dangling образів:

$ docker image ls -f dangling=true
REPOSITORY  TAG      IMAGE ID         CREATED             SIZE
none      none   21e658fe5351     12 minutes ago      71.3MB

Видалити їх можна в такий спосіб:

$ docker image rm $(docker image ls -f dangling=true -q)

Ми також можемо використовувати субкоманду prune:

$ docker image prune
WARNING! This will remove all dangling images.
Are you sure you want to continue? [y/N] y
Deleted Images:
deleted: sha256:143407a3cb7efa6e95761b8cd6cea25e3f41455be6d5e7cda
deleted: sha256:738010bda9dd34896bac9bbc77b2d60addd7738ad1a95e5cc
deleted: sha256:fa4f0194a1eb829523ecf3bad04b4a7bdce089c8361e2c347
deleted: sha256:c5041938bcb46f78bf2f2a7f0a0df0eea74c4555097cc9197
deleted: sha256:5945bb6e12888cf320828e0fd00728947104da82e3eb4452f

Total reclaimed space: 12.9kB

Якщо ми раптом захочемо видалити взагалі всі образи (а не тільки dangling) однією командою, можна зробити так:

$ docker image rm $(docker image ls -q)

Використання диска томами

Тому (volumes) застосовуються для зберігання даних за межами файлової системи контейнера. Наприклад, якщо ми хочемо зберегти результати роботи будь-якої програми, щоб використовувати їх якось ще. Найчастішим прикладом є бази даних.

Давайте запустимо контейнер MongoDB, примонтуємо до нього зовнішній по відношенню до контейнера том, і відновимо з нього бекап бази даних (у нас він доступний у файлі bck.json):

# Running a mongo container
$ docker run --name db -v $PWD:/tmp -p 27017:27017 -d mongo:4.0

# Importing an existing backup (from a huge bck.json file)
$ docker exec -ti db mongoimport 
  --db 'test' 
  --collection 'demo' 
  --file /tmp/bck.json 
  --jsonArray

Дані будуть перебувати на хостовій машині в каталозі /var/lib/docker/volumes. Але чому не на рівні читання-запису контейнера? Тому що в Dockerfile образу MongoDB каталог /data/db (у якому MongoDB за умовчанням зберігає свої дані) визначений як том (volume).

Docker Tips: Очистіть свою машину від мотлоху

Нотатки на полях: багато образів, в результаті роботи яких повинні створюватися дані, використовують томи (volumes) для збереження цих даних.

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

$ docker volume rm $(docker volume ls -q)

Ну чи ми можемо використати вже знайому нам субкоманду prune:

$ docker volume prune
WARNING! This will remove all local volumes not used by at least one container.
Are you sure you want to continue? [y/N] y
Deleted Volumes:
d50b6402eb75d09ec17a5f57df4ed7b520c448429f70725fc5707334e5ded4d5
8f7a16e1cf117cdfddb6a38d1f4f02b18d21a485b49037e2670753fa34d115fc
599c3dd48d529b2e105eec38537cd16dac1ae6f899a123e2a62ffac6168b2f5f
...
732e610e435c24f6acae827cd340a60ce4132387cfc512452994bc0728dd66df
9a3f39cc8bd0f9ce54dea3421193f752bda4b8846841b6d36f8ee24358a85bae
045a9b534259ec6c0318cb162b7b4fca75b553d4e86fc93faafd0e7c77c79799
c6283fe9f8d2ca105d30ecaad31868410e809aba0909b3e60d68a26e92a094da

Total reclaimed space: 25.82GB
luc@saturn:~$

Використання диска для кешу збирання образів

У Docker 18.09 процес створення образів зазнав деяких змін завдяки інструменту BuildKit. За допомогою цієї штуки збільшується швидкість процесу, оптимізується керування зберіганням даних та безпекою. Тут ми не розглядатимемо всі деталі цього чудового інструменту, зупинимося лише нам, як він торкається питань використання дискового простору.

Припустимо, що у нас є абсолютно простий додаток Node.Js:

  • файл index.js запускає простий HTTP сервер, який відповідає рядком на кожен отриманий запит:
  • файл package.json визначає залежності, з яких використовується тільки expressjs для запуску сервера HTTP:

$ cat index.js
var express = require('express');
var util    = require('util');
var app = express();
app.get('/', function(req, res) {
  res.setHeader('Content-Type', 'text/plain');
  res.end(util.format("%s - %s", new Date(), 'Got Request'));
});
app.listen(process.env.PORT || 80);

$ cat package.json
    {
      "name": "testnode",
      "version": "0.0.1",
      "main": "index.js",
      "scripts": {
        "start": "node index.js"
      },
      "dependencies": {
        "express": "^4.14.0"
      }
    }

Dockerfile для складання образу виглядає так:

FROM node:13-alpine
COPY package.json /app/package.json
RUN cd /app && npm install
COPY . /app/
WORKDIR /app
EXPOSE 80
CMD ["npm", "start"]

Зберемо образ звичайним способом, без використання BuildKit:

$ docker build -t app:1.0 .

Якщо ми перевіримо використання дискового простору, то побачимо, що місце займають лише базовий образ (node:13-alpine) та кінцевий образ (app:1.0):

TYPE           TOTAL      ACTIVE     SIZE       RECLAIMABLE
Images         2          0          109.3MB    109.3MB (100%)
Containers     0          0          0B         0B
Local Volumes  0          0          0B         0B
Build Cache    0          0          0B         0B

Зберемо другу версію нашої програми, вже з використанням BuildKit. Для цього нам необхідно встановити змінну DOCKER_BUILDKIT в значення 1:

$ DOCKER_BUILDKIT=1 docker build -t app:2.0 .

Якщо ми зараз перевіримо використання диска, то побачимо, що тепер там бере участь кеш збирання (buid-cache):

$ docker system df
TYPE           TOTAL      ACTIVE     SIZE       RECLAIMABLE
Images         2          0          109.3MB    109.3MB (100%)
Containers     0          0          0B         0B
Local Volumes  0          0          0B         0B
Build Cache    11         0          8.949kB    8.949kB

Для його очищення скористаємося наступною командою:

$ docker builder prune
WARNING! This will remove all dangling build cache.
Are you sure you want to continue? [y/N] y
Deleted build cache objects:
rffq7b06h9t09xe584rn4f91e
ztexgsz949ci8mx8p5tzgdzhe
3z9jeoqbbmj3eftltawvkiayi

Total reclaimed space: 8.949kB

Очистити все!

Отже, ми розглянули очищення дискового простору, зайнятого контейнерами, образами та томами. У цьому нам допомагає субкоманда prune. Але її можна використовувати і на системному рівні docker, і вона очистить все, що тільки зможе:

$ docker system prune
WARNING! This will remove:
  - all stopped containers
  - all networks not used by at least one container
  - all dangling images
  - all dangling build cache

Are you sure you want to continue? [y/N]

Якщо ви з якихось причин заощаджуєте дисковий простір на машині з Docker, періодичний запуск цієї команди варто ввести в звичку.

Джерело: habr.com

Додати коментар або відгук