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
Выше пример 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
- Node - это наши виртуальные машины, на которых установлен docker. Есть
manager
иworkers
ноды. Manager нода управляет workers нодами. Она отвечает за создание/обновление/удаление сервисов на workers, а также за их масштабирование и поддержку в требуемом состоянии. Workers ноды используются только для выполнения поставленных задач и не могут управлять кластером - Stack - это набор сервисов, которые логически связаны между собой. По сути это набор сервисов, которые мы описываем в обычном compose файле. Части stack (services) могут располагаться как на одной ноде, так и на разных
- Service - это как раз то, из чего состоит
stack
. Service является описанием того, какие контейнеры будут создаваться. Если вы пользовалисьdocker-compose.yaml
, то уже знакомы с этой сущностью. Кроме стандартных полей docker в режимеswarm
поддерживает ряд дополнительных, большинство из которых находятся внутри секцииdeploy
- Task - это непосредственно созданный контейнер, который docker создал на основе той информации, которую мы указали при описании
service
. Swarm будет следить за состоянием контейнера и при необходимости его перезапускать или перемещать на другую ноду
Деплоим стэк с 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