
Cześć, Habr! Przedstawiam wam tłumaczenie artykułu autora .
Dziś porozmawiamy o tym, jak Docker wykorzystuje przestrzeń dyskową hosta, a także dowiemy się, jak zwolnić tę przestrzeń z nieużywanych obrazów i kontenerów.

Całkowite zużycie
Docker to niesamowite narzędzie, o którym pewnie niewielu wątpi dzisiaj. Zaledwie kilka lat temu ten produkt zapewnił nam zupełnie nowy sposób na budowanie, dostarczanie i uruchamianie dowolnego środowiska, znacząco oszczędzając zasoby procesora i pamięci RAM. Oprócz tego (a dla niektórych to może być nawet najważniejsze) Docker niezwykle uprościł i zunifikował zarządzanie cyklem życia używanych środowisk roboczych.
Jednak za wszystkie te zalety nowoczesnego życia trzeba płacić. Gdy uruchamiamy kontenery, pobieramy lub tworzymy własne obrazy, wdrażamy skomplikowane ekosystemy, musimy płacić. I płacimy, między innymi, przestrzenią dyskową.
Jeśli nigdy się nie zastanawiałeś, ile miejsca naprawdę zajmuje twój Docker, możesz być nieprzyjemnie zaskoczony wynikiem tego polecenia:
$ docker system df 
Tutaj pokazane jest użycie dysku przez Dockera w różnych aspektach:
- obrazy (images) – całkowity rozmiar obrazów pobranych z repozytoriów obrazów oraz zbudowanych w twoim systemie;
- kontenery (containers) – całkowita ilość przestrzeni dyskowej używanej przez uruchomione kontenery (mając na myśli całkowitą ilość warstw zapisu dla wszystkich kontenerów);
- lokalne wolumeny (local volumes) – objętość lokalnych magazynów zamontowanych w kontenerach;
- cache budowy (build cache) – pliki tymczasowe, generowane podczas budowy obrazów (przy użyciu narzędzia BuildKit, dostępnego od wersji Dockera 18.09).
Mogę się założyć, że już po tym prostym wyliczeniu masz ochotę na wyczyszczenie dysku z niepotrzebnych plików i odzyskanie drogocennych gigabajtów (przyp. red.: szczególnie, jeśli co miesiąc płacisz za te gigabajty).
Zużycie dysku przez kontenery
Za każdym razem, gdy tworzysz kontener na hoście, w katalogu /var/lib/docker tworzone są różne pliki i katalogi, wśród których warto wyróżnić następujące:
- Katalog /var/lib/docker/containers/ID_kontenera – przy użyciu standardowego sterownika logowania, to tutaj zapisywane są dzienniki zdarzeń w formacie JSON. Zbyt szczegółowe logi, a także logi, które nikt nie czyta i nie przetwarza w żaden inny sposób, często prowadzą do przepełnienia dysków.
- Katalog /var/lib/docker/overlay2 – zawiera warstwy odczytu-zapisu kontenerów (overlay2 to preferowany sterownik w większości dystrybucji Linuksa). Jeśli kontener zapisuje dane w swoim systemie plików, to właśnie w tym katalogu będą one przechowywane.
Wyobraźmy sobie system, na którym zainstalowany jest nieskalany Docker, który nigdy nie uruchamiał kontenerów ani nie budował obrazów. Jego raport o wykorzystaniu przestrzeni dyskowej będzie wyglądał następująco:
$ 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 0BUruchommy jakiś kontener, na przykład NGINX:
$ docker container run --name www -d -p 8000:80 nginx:1.16Co się dzieje z dyskiem:
- obrazy (images) zajmują 126 MB, to ten sam NGINX, który uruchomiliśmy w kontenerze;
- kontenery (containers) zajmują śmieszne 2 bajty.
$ 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 0BZ wyjścia wynika, że nie mamy jeszcze miejsca, które moglibyśmy zwolnić. Ponieważ 2 bajty to zupełnie niewielka wartość, wyobraźmy sobie, że nasz NGINX niespodziewanie napisał gdzieś 100 Megabajtów danych i stworzył wewnętrznie plik test.img o dokładnie takim rozmiarze.
$ docker exec -ti www
dd if=/dev/zero of=test.img bs=1024 count=0 seek=$[1024*100]Ponownie sprawdźmy zużycie przestrzeni dyskowej na hoście. Zobaczymy, że kontener (containers) zajmuje tam 100 Megabajtów.
$ 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 0BMyślę, że twój dociekliwy umysł już zadaje sobie pytanie, gdzie znajduje się nasz plik test.img. Poszukajmy go:
$ 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.imgNie wdając się w szczegóły, można zauważyć, że plik test.img jest wygodnie umiejscowiony na poziomie odczytu-zapisu, zarządzanym przez sterownik overlay2. Jeśli jednak zatrzymamy nasz kontener, host zasugeruje nam, że to miejsce w zasadzie można zwolnić:
# 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 0BJak możemy to zrobić? Usuwając kontener, co spowoduje oczyszczenie odpowiedniej przestrzeni na poziomie odczytu-zapisu.
Za pomocą następującego polecenia możesz usunąć wszystkie zainstalowane kontenery za jednym razem i oczyścić swój dysk z wszystkich utworzonych przez nie plików na poziomie odczytu-zapisu:
$ docker container prune
OSTRZEŻENIE! To usunie wszystkie zatrzymane kontenery.
Czy na pewno chcesz kontynuować? [y/N] y
Usunięte kontenery:
5e7f8e5097ace9ef5518ebf0c6fc2062ff024efb495f11ccc89df21ec9b4dcc2
Całkowita odzyskana przestrzeń: 104.9MBZatem uwolniliśmy 104,9 Megabajta, usuwając kontener. Ale ponieważ już nie używamy wcześniej pobranego obrazu, staje się on również kandydatem do usunięcia i uwolnienia naszych zasobów:
$ docker system df
TYPU CAŁKOWITE AKTYWNE ROZMIAR MOŻLIWE DO ODZYSKANIA
Obrazy 1 0 126M 126M (100%)
Kontenery 0 0 0B 0B
Lokalne Wolumeny 0 0 0B 0B
Cache Budowy 0 0 0B 0BUwaga: dopóki obraz jest używany przez chociaż jeden kontener, nie będziesz mógł zastosować tego triku.
Podkomenda prune, którą użyliśmy powyżej, działa tylko na zatrzymanych kontenerach. Jeśli chcemy usunąć nie tylko zatrzymane, ale także uruchomione kontenery, należy użyć jednego z tych poleceń:
# Historical command
$ docker rm -f $(docker ps –aq)
# More recent command
$ docker container rm -f $(docker container ls -aq)Notatki na marginesie: jeżeli podczas uruchamiania kontenera używasz parametru —rm, to przy jego zatrzymaniu zostanie zwolniona cała przestrzeń dyskowa, którą zajmował.
Użycie dysku przez obrazy
Kilka lat temu rozmiar obrazu wynoszący kilka set megabajtów był całkowicie normalny: obraz Ubuntu ważył 600 Megabajtów, a obraz Microsoft .Net – kilka Gigabajtów. W tamtych zamierzchłych czasach pobranie jednego tylko obrazu mogło znacząco zaszkodzić twojej wolnej przestrzeni na dysku, nawet jeśli dzieliłeś poziomy między obrazami. Dziś – chwała wielkim – obrazy ważą znacznie mniej, ale nawet w tym przypadku można szybko zapełnić dostępne zasoby, jeśli nie podejmujesz pewnych środków ostrożności.
Istnieje kilka typów obrazów, które nie są bezpośrednio widoczne dla końcowego użytkownika:
- obraz pośredni, na podstawie którego zbudowane są inne obrazy – nie mogą być usunięte, jeśli używasz kontenerów opartych na tych "innych" obrazach.
- obrazom wiszącym – to pośrednie obrazy, na które nie wskazuje żaden działający kontener – mogą być usunięte.
- Za pomocą następującej komendy możesz sprawdzić, czy w twoim systemie znajdują się obrazy wiszące:
$ docker image ls -f dangling=true
REPOSITORY TAG IMAGE ID CREATED SIZE
none none 21e658fe5351 12 minut temu 71.3MBMożna je usunąć w następujący sposób:
$ docker image rm $(docker image ls -f dangling=true -q)Możemy również użyć subkomendy prune:
$ docker image prune
OSTRZEŻENIE! To usunie wszystkie obrazy wiszące.
Czy na pewno chcesz kontynuować? [y/N] y
Usunięte obrazy:
usunięto: sha256:143407a3cb7efa6e95761b8cd6cea25e3f41455be6d5e7cda
usunięto: sha256:738010bda9dd34896bac9bbc77b2d60addd7738ad1a95e5cc
usunięto: sha256:fa4f0194a1eb829523ecf3bad04b4a7bdce089c8361e2c347
usunięto: sha256:c5041938bcb46f78bf2f2a7f0a0df0eea74c4555097cc9197
usunięto: sha256:5945bb6e12888cf320828e0fd00728947104da82e3eb4452f
Całkowita odzyskana przestrzeń: 12.9kBJeśli nagle chcemy usunąć wszystkie obrazy (a nie tylko wiszące) jednym poleceniem, możemy to zrobić tak:
$ docker image rm $(docker image ls -q)Użycie dysku przez wolumeny
Wolumeny (volumes) są używane do przechowywania danych poza systemem plików kontenera. Na przykład, jeśli chcemy zachować wyniki działania jakiejś aplikacji, aby można je było wykorzystywać w inny sposób. Częstym przykładem są bazy danych.
Uruchommy kontener MongoDB, zamontujmy do niego zewnętrzny wolumen i przywróćmy z niego kopię zapasową bazy danych (mamy ją 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
--jsonArrayDane będą znajdować się na maszynie gospodarza w katalogu /var/lib/docker/volumes. Ale dlaczego nie w poziomie odczytu-zapisu kontenera? Ponieważ w Dockerfile obrazu MongoDB katalog /data/db (w którym MongoDB domyślnie przechowuje swoje dane) został zdefiniowany jako wolumen (volume).

Notatki na marginesie: wiele obrazów, w wyniku działania których powinny być tworzone dane, używa wolumenów (volumes) do przechowywania tych danych.
Kiedy znudzeni MongoDB zatrzymamy (a może nawet usuniemy) kontener, wolumen nie zostanie usunięty. Będzie nadal zajmował naszą cenną przestrzeń dyskową, dopóki nie usuniemy go jawnie taką komendą:
$ docker volume rm $(docker volume ls -q)Albo możemy użyć już znanej nam subkomendy prune:
$ docker volume prune
OSTRZEŻENIE! To spowoduje usunięcie wszystkich lokalnych wolumenów, które nie są używane przez co najmniej jeden kontener.
Czy na pewno chcesz kontynuować? [y/N] y
Usunięte wolumeny:
d50b6402eb75d09ec17a5f57df4ed7b520c448429f70725fc5707334e5ded4d5
8f7a16e1cf117cdfddb6a38d1f4f02b18d21a485b49037e2670753fa34d115fc
599c3dd48d529b2e105eec38537cd16dac1ae6f899a123e2a62ffac6168b2f5f
...
732e610e435c24f6acae827cd340a60ce4132387cfc512452994bc0728dd66df
9a3f39cc8bd0f9ce54dea3421193f752bda4b8846841b6d36f8ee24358a85bae
045a9b534259ec6c0318cb162b7b4fca75b553d4e86fc93faafd0e7c77c79799
c6283fe9f8d2ca105d30ecaad31868410e809aba0909b3e60d68a26e92a094da
Łącznie odzyskana przestrzeń: 25,82GB
luc@saturn:~$Wykorzystanie dysku dla pamięci podręcznej budowy obrazów
W Docker 18.09 proces tworzenia obrazów przeszedł kilka zmian dzięki narzędziu BuildKit. Dzięki temu narzędziu zwiększa się prędkość procesu, optymalizuje zarządzanie pamięcią i bezpieczeństwem. Tutaj nie będziemy omawiać szczegółowo wszystkich funkcji tego znakomitego narzędzia, skupimy się jedynie na tym, jak dotyka kwestii wykorzystywania przestrzeni dyskowej.
Załóżmy, że mamy bardzo prostą aplikację Node.Js:
- plik index.js uruchamia prosty serwer HTTP, który odpowiada tekstem na każde przyjęte zapytanie:
- plik package.json określa zależności, z których używana jest tylko expressjs do uruchomienia serwera 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(), 'Otrzymano żądanie'));
});
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 do budowy obrazu wygląda tak:
FROM node:13-alpine
COPY package.json /app/package.json
RUN cd /app && npm install
COPY . /app/
WORKDIR /app
EXPOSE 80
CMD ["npm", "start"]Zbudujemy obraz w zwykły sposób, bez użycia BuildKit:
$ docker build -t app:1.0 .Jeżeli sprawdzimy wykorzystanie przestrzeni dyskowej, zobaczymy, że miejsce zajmują tylko obraz bazowy (node:13-alpine) i końcowy obraz (app:1.0):
TYP CAŁKOWITA AKTYWNE ROZMIAR MOŻLIWE DO ODDANIA
Obrazy 2 0 109.3MB 109.3MB (100%)
Kontenery 0 0 0B 0B
Lokalne Wolumeny 0 0 0B 0B
Cache Budowy 0 0 0B 0BZbudujemy drugą wersję naszej aplikacji, już z wykorzystaniem BuildKit. W tym celu musimy jedynie ustawić zmienną DOCKER_BUILDKIT na wartość 1:
$ DOCKER_BUILDKIT=1 docker build -t app:2.0 .Jeśli teraz sprawdzimy użycie dysku, zobaczymy, że teraz angażuje się pamięć podręczna budowy (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.949kBAby go oczyścić, użyjemy następującego polecenia:
$ docker builder prune
WARNING! To usunie wszystkie wiszące pliki cache z budowy.
Czy na pewno chcesz kontynuować? [y/N] y
Usunięte obiekty cache:
rffq7b06h9t09xe584rn4f91e
ztexgsz949ci8mx8p5tzgdzhe
3z9jeoqbbmj3eftltawvkiayi
Całkowita odzyskana przestrzeń: 8.949kBWyczyść wszystko!
Tak więc omówiliśmy oczyszczanie przestrzeni dyskowej zajmowanej przez kontenery, obrazy i wolumeny. W tym pomaga nam subkomenda prune. Można ją jednak zastosować również na poziomie systemu docker, a oczyści wszystko, co tylko możliwe:
$ docker system prune
WARNING! To usunie:
- wszystkie zatrzymane kontenery
- wszystkie sieci, które nie są używane przez przynajmniej jeden kontener
- wszystkie wiszące obrazy
- wszystkie wiszące pliki cache z budowy
Czy na pewno chcesz kontynuować? [y/N]Jeśli z jakiegoś powodu oszczędzasz przestrzeń dyskową na maszynie z Dockerem, warto wprowadzić zwyczaj okresowego uruchamiania tego polecenia.
Źródło: habr.com
