Основы Docker

Навроцкий Артем

Основы Docker

Навроцкий Артем

Основы Docker

Что такое Docker?

Docker
Программное обеспечение для автоматизации развёртывания и управления приложениями в средах с поддержкой контейнеризации. Позволяет «упаковать» приложение со всем его окружением и зависимостями в контейнер.
https://ru.wikipedia.org/wiki/Docker

Какие проблемы он решает?

Разворачивание приложений в production
На одном хосте могут жить сервисы на разных библиотеках и дистрибутивах Linux. Например ElasitcSearch базируется на CentOS, а другие сервисы базируются на Ubuntu.
Окружение для сборки на CI
Все зависимости для сборки можно упаковать в контейнер и хранить вместе с кодом. Для установки нового ПО не нужно обращаться к админам.
Тестовое окружение
Можно скачать всё окружение в виде одного образа и запустить одной командой.

Контейнеры VS Виртуальные машины

Контейнеризация
Метод виртуализации, при котором ядро операционной системы поддерживает несколько изолированных экземпляров пространства пользователя вместо одного.
Виртуальная машина
Это окружение, которое представляется для «гостевой» операционной системы, как аппаратное. Однако на самом деле это программное окружение, которое эмулируется программным обеспечением хостовой системы.

Контейнеры

Плюсы

Минусы

Виртуальные машины

Плюсы

Минусы

Linux vs MacOS vs Windows

Linux

Docker использует встроенную поддержку контейнеров.

Настройка памяти в MacOS

Что занимает место на диске?

  1. Образы (images)
  2. Контейнеры (containers):
    • изменённые файлы
    • логи
  3. Разделы (volumes)
  4. Кэш сборки

Что занимает место на диске?

$ docker system df
TYPE           TOTAL  ACTIVE  SIZE     RECLAIMABLE
Images         46     3       18.59GB  17.33GB (93%)
Containers     3      2       1.169GB  2.198kB (0%)
Local Volumes  1      1       1.049GB  0B (0%)
Build Cache    6      0       0B       0B
        

Удаление всего лишнего

$ 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 container ls -a
$ docker container ps -a
$ docker container prune
        

Образы

$ docker image ls
$ docker images
$ docker image prune
        

Пример запуска Docker-образа

$ docker run --rm -it alpine:latest sh
# ls
bin    home   mnt    root   srv    usr
dev    lib    opt    run    sys    var
etc    media  proc   sbin   tmp
# exit

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

Передача переменных окружения

$ docker run --rm \
    --hostname=foobar \
    --env FOO=bar \
    --env BAR=baz \
    alpine:latest \
    env
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin
HOSTNAME=foobar
FOO=bar
BAR=baz
HOME=/root

Сетевое взаимодействие

Сеть Docker Server-а

$ docker run --rm -it --net host nginx

$ docker run --rm -it --net host ubuntu hostname -I
172.16.1.150 172.17.0.1

Сетевое взаимодействие

Изолированная сеть

$ docker run --rm -it \
    --publish 8080:80 \
    --publish :8081:80/tcp \
    --publish 127.0.0.1:8082:80/tcp \
    nginx

$ docker run --rm -it ubuntu hostname -I
172.17.0.3

Если порт занят, то под MacOS контейнер запустится, но трафик пойдет в локальный сервис, а не в контейнер.

Общая файловая система

$ docker run --rm --volume $(pwd):/workspace:ro \
    alpine \
    ls /workspace
Jenkinsfile
config.yaml
content
hugo.sh
public
resources
start.sh
static
themes
     

Аккуратнее с правами доступа

$ docker run --rm ubuntu:latest id
uid=0(root) gid=0(root) groups=0(root)

$ docker run --rm --user 1000:100 ubuntu:latest id
uid=1000 gid=100(users) groups=100(users)

$ docker run --rm bitnami/elasticsearch:latest id
uid=1001 gid=0(root) groups=0(root)

Контейнер пишет с теми правами, от которых запущен его процесс.

Права на docker это почти sudo

$ id
uid=1000(bozaro) gid=1000(bozaro)

$ docker run --rm --volume $(pwd):/workspace alpine \
    touch /workspace/file.txt

$ ls -al file.txt
-rw-r--r-- 1 root root 0 июл  9 14:33 file.txt

Docker volumes

$ vid=$(docker volume create)

$ echo ${vid}
f122409a44bd69485a02f1887b76fbc37936cd8bc387443194a1c9903057d448

$ docker run --rm --volume ${vid}:/workspace alpine \
    ls /workspace

$ docker run --rm --volume ${vid}:/workspace alpine \
    touch /workspace/file.txt

$ docker run --rm --volume ${vid}:/workspace alpine \
    ls /workspace
file.txt

$ docker volume rm ${vid}
f122409a44bd69485a02f1887b76fbc37936cd8bc387443194a1c9903057d448
    

Docker volumes

Создание VOLUME может быть прописано в метаданных Docker-образа, например:

FROM ubuntu:18.04
VOLUME /workspace

Обычно, такие VOLUME удаляются при удалении контейнера. Но иногда этого не происходит и они "подвисают".

Копирование файлов

Не подключаясь к контейнеру, можно скопировать всё его содержимое в TAR-архив:

$ container=$(docker run --rm --detach \
    alpine:latest sleep 600)
$ docker export ${container} > alpine.tar

Копирование файлов

Так же можно копировать отдельные файлы из контейнера:

$ docker cp ${container}:/etc/ etc/
$ docker cp ${container}:/etc/hostname hostname
$ docker cp ${container}:/etc/ - > etc.tar

И в контейнер:

$ docker cp etc/ ${container}:/etc/
$ docker cp hostname ${container}:/etc/hostname
$ docker cp - ${container}:/etc/ < etc.tar

Выполнение процессов в контейнере

Ничто не мешает подключиться к контейнеру:

$ docker exec ${container} ps
PID   USER     TIME  COMMAND
    1 root      0:00 sleep 600
    6 root      0:00 ps

$ docker exec -it ${container} /bin/sh
/ # ps aux
PID   USER     TIME  COMMAND
    1 root      0:00 sleep 600
   11 root      0:00 /bin/sh
   16 root      0:00 ps aux
/ # exit

Какую команду выполнит Docker?

docker run IMAGE [COMMAND] [ARG...]

$ docker run alpine/helm version

Какую команду выполнит Docker?

Если COMMAND ARG... не указан, то он считается равным Cmd.

Выполяется команда, полученная конкатенацией Entrypoint и COMMAND ARG....

image Entrypoint Cmd
alpine
null
["/bin/sh"]
alpine/helm
["helm"]
["--help"]
$ docker inspect alpine/helm | jq -c ".[0].Config.Entrypoint"
["helm"]

$ docker inspect alpine/helm | jq -c ".[0].Config.Cmd"
["--help"]

Какую команду выполнит Docker?

docker run --entrypoint=COMMAND IMAGE [ARG...]

Выполнится команда COMMAND ARG... с учетом поиска в PATH

$ # /bin/sh -c whoami
$ docker run --entrypoint=/bin/sh alpine/helm -c whoami
root

$ docker run --entrypoint=/bin/ps alpine/helm
PID   USER     TIME  COMMAND
    1 root      0:00 /bin/ps

$ docker run --entrypoint=ps alpine/helm
PID   USER     TIME  COMMAND
    1 root      0:00 /bin/ps

Какую команду выполнит Docker?

docker run --entrypoint="" IMAGE COMMAND [ARG...]

Выполнится команда COMMAND ARG... с учетом поиска в PATH

$ docker run --entrypoint="" alpine/helm whoami
root

$ docker run --entrypoint="" alpine/helm /bin/ps
PID   USER     TIME  COMMAND
    1 root      0:00 /bin/ps

$ docker run --entrypoint="" alpine/helm ps
PID   USER     TIME  COMMAND
    1 root      0:00 /bin/ps

Метки Docker-образа

Контейнер может содержать метки (LABEL).

Пример Dockerfile:

FROM ubuntu:18.04
LABEL "com.example.vendor"="ACME Incorporated"
LABEL com.example.label-with-value="foo"
LABEL version="1.0"

Метки Docker-образа

Посмотреть метаданные контейнера/образа можно командой вида:

$ docker build -t test .

$ docker inspect test | jq ".[0].Config.Labels"
{
  "com.example.label-with-value": "foo",
  "com.example.vendor": "ACME Incorporated",
  "version": "1.0"
}

Метки Docker-образа

Или через UI от Docker Registry

jfrog.joom.it/docker-images/mongos:latest
https://jfrog.joom.it/ui/repos/tree/Properties/docker-images%2Fmongos%2Flatest%2Fmanifest.json

Docker сохраняет stdout и stderr

$ id=$(docker run --detach docker/whalesay cowsay boo)

$ sleep 5

$ docker inspect ${id} | jq ".[0].State.Status"
"exited"

Docker сохраняет stdout и stderr

$ docker logs ${id}
_____ < boo > ----- \ \ \ ## . ## ## ## == ## ## ## ## === /""""""""""""""""___/ === ~~~ {~~ ~~~~ ~~~ ~~~~ ~~ ~ / ===- ~~~ \______ o __/ \ \ __/ \____\______/

Docker сохраняет stdout и stderr

$ docker logs --timestamps ${id}
2020-07-09T12:35:19.788330077Z _____ 2020-07-09T12:35:19.788348669Z < boo > 2020-07-09T12:35:19.788354027Z ----- 2020-07-09T12:35:19.788356697Z \ 2020-07-09T12:35:19.788358973Z \ 2020-07-09T12:35:19.788361128Z \ 2020-07-09T12:35:19.788363290Z ## . 2020-07-09T12:35:19.788365599Z ## ## ## == 2020-07-09T12:35:19.788367782Z ## ## ## ## === 2020-07-09T12:35:19.788369934Z /""""""""""""""""___/ === 2020-07-09T12:35:19.788372305Z ~~~ {~~ ~~~~ ~~~ ~~~~ ~~ ~ / ===- ~~~ 2020-07-09T12:35:19.788374442Z \______ o __/ 2020-07-09T12:35:19.788376591Z \ \ __/ 2020-07-09T12:35:19.788378853Z \____\______/

Docker сохраняет stdout и stderr

$ sudo cat /var/lib/docker/containers/${id}/${id}-json.log
{"log":" _____ \n","stream":"stdout","time":"2020-07-09T12:35:19.788330077Z"} {"log":"\u003c boo \u003e\n","stream":"stdout","time":"2020-07-09T12:35:19.788348669Z"} {"log":" ----- \n","stream":"stdout","time":"2020-07-09T12:35:19.788354027Z"} {"log":" \\\n","stream":"stdout","time":"2020-07-09T12:35:19.788356697Z"} {"log":" \\\n","stream":"stdout","time":"2020-07-09T12:35:19.788358973Z"} {"log":" \\ \n","stream":"stdout","time":"2020-07-09T12:35:19.788361128Z"} {"log":" ## . \n","stream":"stdout","time":"2020-07-09T12:35:19.78836329Z"} {"log":" ## ## ## == \n","stream":"stdout","time":"2020-07-09T12:35:19.788365599Z"} {"log":" ## ## ## ## === \n","stream":"stdout","time":"2020-07-09T12:35:19.788367782Z"} {"log":" /\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"___/ === \n","stream":"stdout","time":"2020-07-09T12:35:19.788369934Z"} {"log":" ~~~ {~~ ~~~~ ~~~ ~~~~ ~~ ~ / ===- ~~~ \n","stream":"stdout","time":"2020-07-09T12:35:19.788372305Z"} {"log":" \\______ o __/ \n","stream":"stdout","time":"2020-07-09T12:35:19.788374442Z"} {"log":" \\ \\ __/ \n","stream":"stdout","time":"2020-07-09T12:35:19.788376591Z"} {"log":" \\____\\______/ \n","stream":"stdout","time":"2020-07-09T12:35:19.788378853Z"}
$ docker rm ${id}

По-умолчанию Docker не ротирует логи.

Из-за этого логи могут занять всё доступное место на диске Docker Server-а.

Пример Dockerfile

# Use the official image as a parent image.
FROM node:current-slim
# Set the working directory.
WORKDIR /usr/src/app
# Copy the file from your host to your current location.
COPY package.json .
# Run the command inside your image filesystem.
RUN npm install
# Inform Docker that the container is listening on the specified
# port at runtime.
EXPOSE 8080
# Run the specified command within the container.
CMD [ "npm", "start" ]
# Copy the rest of your app's source code from your host to your
# image filesystem.
COPY . .

Docker хранит образ слоями

При каждом изменении внутренней файловой системы во время сборки контейнера будет создаваться новый слой (LAYER).

Слой содержит внутри изменения файловой системы:

Сколько занимает Docker образ?

Рассмотрим на примере Dockerfile

FROM ubuntu:18.04
LABEL com.joom.retention.maxDays=-1
ARG KUBE_VERSION=1.17.2
RUN apt -y update && apt -y install curl nginx
RUN curl -f -s -o /usr/local/bin/kubectl ... && \
chmod +x /usr/local/bin/kubectl && \
kubectl version --client
ADD default /etc/nginx/sites-available/default
ADD log-read.sh /opt/
RUN chmod +x /opt/log-read.sh
CMD ["/bin/bash", "-xc", "/opt/log-read.sh"]

Сколько занимает Docker образ?

docker image ls test
REPOSITORY  TAG     IMAGE ID      CREATED        SIZE
test        latest  1c393d55d51c  4 minutes ago  206MB

При этом в docker registry размер того же образа будет 86.81MB.

Это вызвано тем, что docker image ls и docker save не используют сжание слоёв. При этом внутри Docker Registry слои хранятся пожатые gzip-ом.

Это имеет забавное следствие: слои сжимаются на этапе docker push, из-за этого заливка образов может упираться в процессор, а не в сеть.

Так же важно заметить, что зачастую сумма размеров образов на равна общему размеру образов за счет переиспользования слоёв.

Полезные ссылки

Спасибо за внимание!