Nasza strona używa cookies. Korzystając ze strony, wyrażasz zgodę na używanie cookies, zgodnie z aktualnymi ustawieniami przeglądarki. Rozumiem

Jak odzyskać przestrzeń dyskową zajętą przez Dockera

Luc Juggery Freelance Docker & Kubernetes trainer
Sprawdź, w jaki sposób możesz odzyskać przestrzeń dyskową, którą Docker zajął podczas swojej pracy.
Jak odzyskać przestrzeń dyskową zajętą przez Dockera

W tym artykule wrócimy do podstaw Dockera. Przyjrzymy się temu, jak wykorzystuje on miejsce na dysku komputera hosta i jak je ewentualnie odzyskać, gdy Docker nie jest już używany.


Ogólne zużycie

Docker jest świetny, nie mam co do tego wątpliwości. Kilka lat temu dał nam nowy sposób tworzenia, wysyłania i obsługiwania dowolnych zadań poprzez demokratyzację użytkowania kontenerów i o wiele prostsze zarządzanie ich cyklem życia. Daje to też developerom możliwość uruchamiania dowolnych aplikacji bez zanieczyszczania lokalnej maszyny.

Kiedy jednak uruchamiamy kontenery, ściągamy obrazy, uruchamiamy złożone stosy aplikacji, czy tworzymy własne obrazy, to będzie się to wiązać z użyciem sporej ilości miejsca w systemie plików hosta. Jeśli przez jakiś czas nie czyściliśmy naszego komputera, to możemy się spotkać z czymś takim:

$ docker system df




Polecenie to pokazuje zużycie dysku w kilku kategoriach:

  • Obrazy: rozmiar obrazów pobranych z rejestru i utworzonych lokalnie.
  • Kontenery: przestrzeń dyskowa używana przez kontenery działające w systemie, co oznacza przestrzeń zarezerwowaną na odczyt-zapis dla każdego kontenera
  • Wolumeny lokalne: pamięć zapisana na hoście, ale poza systemem plików kontenera.
  • Build Cache: pamięć podręczna generowana przez proces budowania obrazu (tylko w przypadku korzystania z BuildKit, dostępnego od Docker 18.09).


Z powyższego widać, że można odzyskać sporo miejsca na dysku - ponieważ nie jest ono używane przez Dockera, to można je zwrócić maszynie hosta.


Zużycie dysku przez kontenery

Za każdym razem, gdy tworzony jest kontener, w katalogu /var/lib/docker na hoście powstaje kilka folderów i plików. Między innymi:

  • folder /var/lib/docker/container/ID (ID - unikalny identyfikator kontenera). Jeśli kontener używa domyślnego sterownika logowania, wszystkie jego logi zostaną zapisane w pliku JSON w tym właśnie folderze. Generowanie zbyt wielu logów może mieć zatem wpływ na zajęte miejsce na maszynie hosta.
  • folder wewnątrz /var/lib/docker/overlay2, który zawiera warstwę do odczytu i zapisu kontenera (overlay2 jest preferowanym sterownikiem przechowywania w większości dystrybucji Linuksa). Jeśli kontener będzie przechowywał dane we własnym systemie plików, to będą one magazynowane w katalogu /var/lib/docker/overlay2 na hoście.


Wyobraźmy sobie, że mamy zupełnie nowy system, w którym właśnie został zainstalowany 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


Najpierw uruchamiamy kontener NGINX:

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


Uruchamiając ponownie polecenie df, widzimy teraz:

  • jeden obraz o rozmiarze 126 MB. Jest to NGINX:1.16 zaciągnięty, kiedy uruchamialiśmy kontener.
  • jeden kontener - kontener www uruchamiany z obrazu NGINX.

$ 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


Nie mamy jeszcze miejsca do odzyskania, ponieważ kontener jest uruchomiony, a obraz obecnie używany. Ponieważ rozmiar kontenera (2B) jest mały, a przez to niełatwy do śledzenia w systemie plików, utwórzmy pusty plik o rozmiarze 100 MB w systemie plików kontenera. W tym celu korzystamy z komendy dd z kontenera www.

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


Ten plik jest tworzony w warstwie read-write połączonej z tym kontenerem. Jeśli ponownie sprawdzimy, co wraca polecenie df, to zobaczymy, że kontener zajmuje teraz dodatkowe miejsce na dysku.

$ 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


Gdzie się ten plik znajduje na hoście? Spójrzmy:

$ 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


Nie zagłębiając się w szczegóły, plik ten został utworzony w warstwie read-write kontenera, którą zarządza sterownik overlay2. Jeśli zatrzymamy kontener, miejsce na dysku używane przez kontener będzie można odzyskać. Spójrzmy:

# 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


Jak można to zrobić? Przez usunięcie kontenera, co spowoduje usunięcie warstwy read-write. Poniższe polecenia pozwalają nam usunąć wszystkie zatrzymane kontenery na raz i odzyskać miejsce na dysku, z którego korzystają:

$ 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


Z tego, co zostało wypisane widać, że odzyskane zostało miejsce zajmowane przez kontenery, a to dlatego, że obraz już nie jest używany (kontener już nie działa), więc zajęta pamięć może wrócić do hosta:

$ 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


Uwaga:
jeśli obraz zostanie wykorzystany przez co najmniej jeden kontener, nie będzie można odzyskać zajmowanego przez niego miejsca na dysku.

Podkomenda prune, której użyliśmy powyżej, usuwa zatrzymane kontenery. Jeśli musimy usunąć wszystkie kontenery, zarówno działające, jak i zatrzymane, to możemy użyć jednego z następujących poleceń (oba oznaczają to samo):

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

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


Uwaga: często przydaje się użycie flagi --rm podczas uruchamiania kontenera, aby był on automatycznie usuwany po zatrzymaniu procesu PID 1. Zwalnia to tym samym przestrzeń dyskową.


Zużycie dysku przez obrazy

Kilka lat temu normalną rzeczą było posiadanie kilkuset MB na obraz. Obrazy Ubuntu miały około 600 MB, a obrazy Microsoft .Net ważyły kilka GB (tak, kilka GB). W tym czasie pobranie tylko kilku obrazów mogło szybko wpłynąć na miejsce na dysku komputera głównego, nawet jeśli warstwy są współdzielone między obrazami.

Dziś nie jest to już takie adekwatne - obrazy podstawowe są znacznie lżejsze - ale po pewnym czasie gromadzenie się obrazów z pewnością może nas przytłoczyć, jeśli nie będziemy ostrożni.

Istnieje kilka rodzajów obrazów, które nie są bezpośrednio widoczne dla użytkownika końcowego:

  • Obrazy pośrednie, do których są odniesienia z innych obrazów (obrazy podrzędne), przez co nie można ich usunąć.
  • Wiszące obrazy, czyli takie, do których nie ma już referencji. Zajmują one trochę miejsca na dysku, więc można je usunąć.


Poniższe polecenia wyświetlają listę wiszących obrazów w systemie:

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


Aby usunąć wiszący obraz, możemy wybrać dłuższą drogę:

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


Lub użyć podkomendy 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


W przypadku, gdy musimy usunąć wszystkie obrazy naraz (nie tylko te wiszące), możemy uruchomić następujące polecenie. Nie spowoduje ono jednak usunięcia obrazów aktualnie używanych przez kontener:

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


Zużycie dysku przez wolumeny

Wolumeny służą do przechowywania danych poza systemem plików kontenera. Na przykład, gdy kontener uruchamia aplikację przechowującą stan, chcemy, aby dane były utrwalane poza kontenerem, tak żeby oddzielić je od jego cyklu życia. Wolumeny są również używane, ponieważ ciężkie operacje systemu plików wewnątrz kontenera mają negatywny wpływ na wydajność.

Załóżmy, że uruchamiamy kontener oparty na MongoDB, a następnie używamy go do testowania wykonanej wcześniej kopii zapasowej (dostępnej lokalnie w pliku 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


Dane w pliku kopii zapasowej będą przechowywane na hoście w folderze /var/lib/docker/volumes. Dlaczego te dane nie są zapisywane w warstwie kontenera? Ponieważ w pliku Dockerfile obrazu mongo lokalizacja /data/db (gdzie mongo domyślnie przechowuje swoje dane) jest definiowana jako volume.


Kawałek pliku Dockerfile używanego do tworzenia obrazu MongoDB


Uwaga: wiele obrazów, często związanych z aplikacjami stateful, każe wolumenom zarządzać danymi poza warstwą kontenera. Kiedy skończyliśmy testować tworzenie kopii zapasowej, zatrzymujemy lub usuwamy kontener. Wolumen nie jest jednak usuwany - pozostaje tam, zajmując miejsce na dysku, chyba że wprost go usuniemy. Aby to zrobić, możemy wybrać trudniejszą opcję:

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


Lub użyć komendy 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:~$


Zużycie dysku przez Build Cache

Docker 18.09 wprowadza ulepszenia dla procesu budowania dzięki BuildKit. Korzystanie z tego narzędzia może usprawnić wydajność, zarządzanie miejscem, działanie funkcji oraz bezpieczeństwo. Nie będziemy tutaj szczegółowo omawiać BuildKit. Zamiast tego zobaczymy, jak go włączyć oraz jak wpływa on na zużycie dysku.

Przyjrzyjmy się testowej aplikacji Node.js oraz powiązanych z nią plików Dockerfile. Plik index.js definiuje prosty serwer HTTP, który eksponuje punkt końcowy ‘/’ i odpowiada ciągiem znaków na każde otrzymane żądanie. 

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);


package.json definiuje zależności. Do ustawienia serwera HTTP potrzebne nam jedynie expressjs.

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


Dockerfile mówi nam, jak zbudować obraz z powyższego kodu:

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


Zbudujmy obraz tak jak zwykle, bez włączonego BuildKit:

$ docker build -t app:1.0 .


Jeśli sprawdzimy zużycie dysku, zobaczymy tylko obraz podstawowy (node:13-alpine ściągnięty na początku kompilacji) i ostateczny obraz kompilacji (app:1.0):

$ 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 0 0 0B 0B


Stwórzmy teraz wersję 2.0 obrazu za pomocą BuildKit. Musimy tylko ustawić DOCKER_BUILDKIT na 1:

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


Jeśli ponownie sprawdzimy zużycie dysku, to zobaczymy, że utworzono build-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


Aby usunąć build-cache, użyjemy następującej komendy:

$ 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


Sprzątamy wszystko na raz

Jak widzieliśmy w powyższych przykładach, każde z poleceń kontenera, obrazu i wolumenu udostępnia podkomendę prune w celu odzyskania miejsca na dysku. Podkomenda prune jest dostępna z poziomu samego Dockera, więc odzyskuje całe nieużywane miejsce na dysku na raz:

$ 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]


Uruchamianie tego polecenia raz na jakiś czas w celu wyczyszczenia dysku jest zdecydowanie dobrym nawykiem.

Oryginał tekstu w języku angielskim możesz przeczytać tutaj.


Jeśli masz jakieś przemyślenia dotyczące tego artykułu, podziel się nimi w komentarzu
👇

Rozpocznij dyskusję

Lubisz dzielić się wiedzą i chcesz zostać autorem?

Podziel się wiedzą z 160 tysiącami naszych czytelników

Dowiedz się więcej