Docker

Разное про докер

Getting Started

Getting started

Что такое контейнер? Контейнер это процесс на твоей машине, который изолирован от остальных процессов
Изоляция достигается с помощью kernel namespaces и cgroups, которые существуют в линуксе уже давно

Что такое образ контейнера (image)? Это всего лишь образ файловой системы контейнера, он содержит все что нужно для запуска приложения внутри контейнера: зависимости, конфиги, скрипты, бинари итд
Также в образе содержится "конфигурация контейнера" (переменные окружения, дефолтная команда для запуска и прочее)

Если ты хорошо знаком с chroot, то можешь воспринимать докер как прокаченный chroot


Namespaces

Пространство имён Что изолирует
PID PID процессов
NETWORK Сетевые устройства, стеки, порты и т.п.
USER ID пользователей и групп
MOUNT Точки монтирования
IPC SystemV IPC, очереди сообщений POSIX
UTS Имя хоста и доменное имя NIS

Например, Network namespace включает в себя системные ресурсы, связанные с сетью, такие как сетевые интерфейсы (например, wlan0, eth0), таблицы маршрутизации и т.д., Mount namespace включает файлы и каталоги в системе, PID содержит ID процессов и так далее

Нэймспейсы нужны для изоляции процессов друг от друга, например если один процесс "сошел с ума" и выполнил 'rm -rf /', то это на зааффектит другие процессы, потому что он изолирован и сломает только свое окружение

По умолчанию ядро создает процессы в "нэймспейсах по умолчанию"
Это видно ниже

root@three:~# ls -l /proc/1/ns/*t*
lrwxrwxrwx 1 root root 0 Apr  2 06:34 /proc/1/ns/mnt -> 'mnt:[4026531840]'
lrwxrwxrwx 1 root root 0 Apr  2 06:34 /proc/1/ns/net -> 'net:[4026531992]'
lrwxrwxrwx 1 root root 0 Apr  2 06:34 /proc/1/ns/uts -> 'uts:[4026531838]'
root@three:~# ls -l /proc/$$/ns/*t*
lrwxrwxrwx 1 root root 0 Apr  2 06:35 /proc/1169/ns/mnt -> 'mnt:[4026531840]'
lrwxrwxrwx 1 root root 0 Apr  2 06:35 /proc/1169/ns/net -> 'net:[4026531992]'
lrwxrwxrwx 1 root root 0 Apr  2 06:35 /proc/1169/ns/uts -> 'uts:[4026531838]'

Команда unshare позволяет запустить процесс в новом нэймспейсе

root@three:~# unshare -u bash ### -u значит uts
root@three:~# ls -l /proc/$$/ns/*t*
lrwxrwxrwx 1 root root 0 Apr  2 06:49 /proc/1331/ns/mnt -> 'mnt:[4026531840]'
lrwxrwxrwx 1 root root 0 Apr  2 06:49 /proc/1331/ns/net -> 'net:[4026531992]'
lrwxrwxrwx 1 root root 0 Apr  2 06:49 /proc/1331/ns/uts -> 'uts:[4026532179]' # этот отличается от тех что мы видели выше

Еще пример

root@three:~# hostname # проверяем хостнэйм в текущей оболочке
three
root@three:~# bash # заходим в подоболочку
root@three:~# hostname test # меняем хостнэйм
root@three:~# hostname # проверяем что поменялся
test
root@three:~# exit # выходим из подоболочки в которой поменян хостнэйм
root@three:~# hostname # проверяем 
test # видим что он поменялся и в продительской оболочке

А теперь используем unshare

root@three:~# hostname
three
root@three:~# unshare -u bash
root@three:~# hostname test
root@three:~# hostname
test
root@three:~# exit
root@three:~# hostname
three # все то же самое что и выше, но в родительской оболочке хостнэйм не изменился

Еще пример
Смотрим сколько интерфейсов и процессов видит наша оболочка

root@two:~# ip a | wc -l
24
root@two:~# ps aux | wc -l
73

Запускаем баш в новых неймспейсах

root@two:~# unshare --pid --net --fork --mount-proc /bin/bash

Смотрим снова и видим что теперь баш видит совсем чуть-чуть

root@two:~# ip a
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
root@two:~# ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.1   6996  3556 pts/0    S    05:49   0:00 /bin/bash
root         3  0.0  0.1  10636  3068 pts/0    R+   05:49   0:00 ps aux

Docker делает то же самое
Вот например контейнер который что-то пингует

root@two:~# docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
fe6374a8f57b        test                "ping 8.8.8.8"      56 seconds ago      Up 55 seconds                           test
root@two:~# lsns
        NS TYPE   NPROCS   PID USER COMMAND
4026531835 cgroup     75     1 root /sbin/init
4026531836 pid        74     1 root /sbin/init
4026531837 user       75     1 root /sbin/init
4026531838 uts        74     1 root /sbin/init
4026531839 ipc        74     1 root /sbin/init
4026531840 mnt        71     1 root /sbin/init
4026531860 mnt         1    20 root kdevtmpfs
4026531992 net        74     1 root /sbin/init
4026532172 mnt         1   186 root /lib/systemd/systemd-udevd
4026532179 mnt         1   261 ntp  /usr/sbin/ntpd -p /var/run/ntpd.pid -g -u 105:112
4026532187 mnt         1   989 root ping 8.8.8.8
4026532188 uts         1   989 root ping 8.8.8.8
4026532189 ipc         1   989 root ping 8.8.8.8
4026532190 pid         1   989 root ping 8.8.8.8
4026532192 net         1   989 root ping 8.8.8.8

Как видно он находится в 5 нэймспейсах


lsns позволяет посмотреть нэймспейсы
А nsenter позволяет запустить какую-нибудь программу внутри какого-нибудь нэймспейса

root@two:~# lsns
        NS TYPE   NPROCS   PID USER COMMAND
4026531835 cgroup     73     1 root /sbin/init
4026531836 pid        72     1 root /sbin/init
4026531837 user       73     1 root /sbin/init
4026531838 uts        72     1 root /sbin/init
4026531839 ipc        72     1 root /sbin/init
4026531840 mnt        69     1 root /sbin/init
4026531860 mnt         1    20 root kdevtmpfs
4026531992 net        72     1 root /sbin/init
4026532172 mnt         1   186 root /lib/systemd/systemd-udevd
4026532179 mnt         1   261 ntp  /usr/sbin/ntpd -p /var/run/ntpd.pid -g -u 105:112
4026532187 mnt         1  2779 root ping 8.8.8.8
4026532188 uts         1  2779 root ping 8.8.8.8
4026532189 ipc         1  2779 root ping 8.8.8.8
4026532190 pid         1  2779 root ping 8.8.8.8
4026532192 net         1  2779 root ping 8.8.8.8
root@two:~# nsenter -a -t 2779 /bin/sh
/ # ps aux
PID   USER     TIME  COMMAND
    1 root      0:00 ping 8.8.8.8
   17 root      0:00 /bin/sh
   18 root      0:00 ps aux
/ #

То есть эта программа позволяет зайти внутрь какого-нибудь контейнера без использования docker exec -it name bash


В /sys/fs... есть место где можно смотреть и править параметры cgroups

root@two:/sys/fs/cgroup/memory/docker/dba66fa2b64f380f6643d35e81c765e4f4254c45ffc9bd8864b502ab8814e506# cat tasks
2936
root@two:/sys/fs/cgroup/memory/docker/dba66fa2b64f380f6643d35e81c765e4f4254c45ffc9bd8864b502ab8814e506# ps aux | grep 2936 | grep -v grep
root      2936  0.0  0.0   1580     4 ?        Ss   06:14   0:00 sleep 3700

Хэш связан с конкретным контейнером

Внутри файлики с различными ограничениями сигруппы

Cache

При сборке образа полезно использовать кэш
С его помощью можно значительно ускорить процесс сборки

Одна команда - один слой

Пример правильного Dockerfile'a

FROM alpine
RUN apk add --no-cache nginx && mkdir -p /run/nginx
EXPOSE 80
COPY custom.conf /etc/nginx/conf.d/
COPY . /opt/
CMD ["nginx", "-g", "daemon off;"]

Используется alpine потому что он легковесный (меньше размер - быстрее сборка)

Также видно что команды записано в порядке увеличения вероятности изменения
Потому что изменение в слое из середины или начала повлечет за собой пересборку всех последующих слоев
А мы понимаем что слой с установкой nginx мы вряд ли будем менять, а слой с копированием кода будет меняться при каждой сборке (иначе зачем еще пересобирать)
Учитывая это ставим слои в соотв. порядке

root@three:~/test# docker build -t test_size5 .
Sending build context to Docker daemon  5.632kB
Step 1/6 : FROM alpine
 ---> 49f356fa4513
Step 2/6 : RUN apk add --no-cache nginx && mkdir -p /run/nginx
 ---> Using cache
 ---> 55dac18b6a46
Step 3/6 : EXPOSE 80
 ---> Using cache
 ---> 813853058580
Step 4/6 : COPY custom.conf /etc/nginx/conf.d/
 ---> Using cache
 ---> 930e206c4054
Step 5/6 : COPY . /opt/
 ---> 890c5cc41dd3
Step 6/6 : CMD ["nginx", "-g", "daemon off;"]
 ---> Running in 42c07646cb68
Removing intermediate container 42c07646cb68
 ---> 70d894801010
Successfully built 70d894801010
Successfully tagged test_size5:latest

Выше пункты 2-4 были взяты из кэша, что здорово ускорило сборку

Multi-stage

Firefox_2021-04-02-10-22-41.png

Выше пример Dockerfile'a в котором используется multi-stage

Первая часть занимается сборкой бинаря из кода

А во второй части готовый бинарь кладется в scratch (пустой образ) и указываются entrypoint и опции запуска
scratch полезен когда приложению не требуется окружение (когда это самостоятельный бинарь)
Получается что весь наш образ это одно единственное приложение

Размер образа становится минимальным

Swarm

С помощью команды

root@idudin-swarm-01:~# docker system info | grep Swarm
 Swarm: inactive

Мы можем увидеть включен ли сворм на сервере с докером

Включаем если не включен

root@idudin-swarm-01:~# docker swarm init
Swarm initialized: current node (gnf236u9n3gnor2926ro2retb) is now a manager.

To add a worker to this swarm, run the following command:

    docker swarm join --token SWMTKN-1-0d3es2bdhqlaozr8qzi7w9gu91bcr2m97pdr81e9n0mfeb29sw-4haan920y2de2z45n4jkyzr5f 172.28.103.198:2377

To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.

Подключаем вторую ноду

root@idudin-swarm-02:~# docker swarm join --token SWMTKN-1-0d3es2bdhqlaozr8qzi7w9gu91bcr2m97pdr81e9n0mfeb29sw-4haan920y2de2z45n4jkyzr5f 172.28.103.198:2377
This node joined a swarm as a worker.

Видим что все круто

root@idudin-swarm-01:~# docker node ls
ID                            HOSTNAME          STATUS    AVAILABILITY   MANAGER STATUS   ENGINE VERSION
gnf236u9n3gnor2926ro2retb *   idudin-swarm-01   Ready     Active         Leader           20.10.12
uwj0b9w0ussmfhkjmdgev9j19     idudin-swarm-02   Ready     Active                          20.10.12


Деплоим стэк с wordpress'ом

root@idudin-swarm-01:~# cat wordpress-stack.yaml
version: '3.1'

services:

  wordpress:
    image: wordpress
    restart: always
    ports:
      - 8080:80
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_USER: exampleuser
      WORDPRESS_DB_PASSWORD: examplepass
      WORDPRESS_DB_NAME: exampledb
    volumes:
      - wordpress:/var/www/html

  db:
    image: mysql:5.7
    restart: always
    environment:
      MYSQL_DATABASE: exampledb
      MYSQL_USER: exampleuser
      MYSQL_PASSWORD: examplepass
      MYSQL_RANDOM_ROOT_PASSWORD: '1'
    volumes:
      - db:/var/lib/mysql

volumes:
  wordpress:
  db:
root@idudin-swarm-01:~# docker stack deploy -c wordpress-stack.yaml wordpress
Ignoring unsupported options: restart

Creating network wordpress_default
Creating service wordpress_db
Creating service wordpress_wordpress

Проверяем

root@idudin-swarm-01:~# docker stack ls
NAME        SERVICES   ORCHESTRATOR
wordpress   2          Swarm
root@idudin-swarm-01:~# docker stack services wordpress
ID             NAME                  MODE         REPLICAS   IMAGE              PORTS
4arvtnm8p1t6   wordpress_db          replicated   1/1        mysql:5.7
pbgau50cuvj8   wordpress_wordpress   replicated   1/1        wordpress:latest   *:8080->80/tcp

Dockerd повесился на порт 8080 на обеих нодах кластера

root@idudin-swarm-01:~# ss -tlpn | grep 8080
LISTEN    0         4096                     *:8080                   *:*        users:(("dockerd",pid=421243,fd=46))
...
root@idudin-swarm-02:~# ss -tlpn | grep 8080
LISTEN    0         4096                     *:8080                   *:*        users:(("dockerd",pid=1894,fd=35))

То есть можно ставить балансер перед кластером и пулять трафик на любую ноду кластера

🚀 curl -I 172.28.103.198:8080/wp-admin/install.php
HTTP/1.1 200 OK
Date: Mon, 27 Feb 2023 07:38:30 GMT
Server: Apache/2.4.54 (Debian)
X-Powered-By: PHP/8.0.28
Expires: Wed, 11 Jan 1984 05:00:00 GMT
Cache-Control: no-cache, must-revalidate, max-age=0
Content-Type: text/html; charset=utf-8

🚀 curl -I 172.28.103.98:8080/wp-admin/install.php
HTTP/1.1 200 OK
Date: Mon, 27 Feb 2023 07:38:34 GMT
Server: Apache/2.4.54 (Debian)
X-Powered-By: PHP/8.0.28
Expires: Wed, 11 Jan 1984 05:00:00 GMT
Cache-Control: no-cache, must-revalidate, max-age=0
Content-Type: text/html; charset=utf-8