Docker Volume/Network
В данном практическом занятии рассматривается работа с томами(volume), а также различные конфигурации сети(network).
Volume
Local
Для работы с локальными томами используем следующий Vagrantfile
:
Vagrant.configure("2") do |config|
config.vm.define "storage" do |c|
c.vm.box = "ubuntu/lunar64"
c.vm.hostname = "storage"
c.vm.network "private_network", type: "dhcp"
c.vm.provision "shell", inline: <<-SHELL
mkdir /data;chmod 777 /data
echo '/data *(rw)' > /etc/exports
export DEBIAN_FRONTEND=noninteractive
apt-get update -q
apt-get install -yq libnss-mdns nfs-server docker.io
usermod -a -G docker vagrant
SHELL
end
end
Для простого монтирования локальной директории внутрь контейнера можно указать путь в
опции -v
команды docker run
:
vagrant@storage:~$ docker run -d -v /data:/usr/share/nginx/html -p 8888:80 --name nginx nginx
Unable to find image 'nginx:latest' locally
latest: Pulling from library/nginx
a803e7c4b030: Pull complete
8b625c47d697: Pull complete
4d3239651a63: Pull complete
0f816efa513d: Pull complete
01d159b8db2f: Pull complete
5fb9a81470f3: Pull complete
9b1e1e7164db: Pull complete
Digest: sha256:32da30332506740a2f7c34d5dc70467b7f14ec67d912703568daff790ab3f755
Status: Downloaded newer image for nginx:latest
95dcf19937c35d8525c5ccf1ca527cb89903df1e4c5f80c175243da00dcb5d8b
vagrant@storage:~$ echo data > /data/index.html
vagrant@storage:~$ curl localhost:8888
data
vagrant@storage:~$ docker rm -f nginx
nginx
Чтобы использовать том вместо монтирования существующей директории можно создать его
явно командой docker volume create
:
vagrant@storage:~$ docker volume create empty
empty
При создании можно указать набор меток в метаданных, которые можно потом использовать для фильтрации:
vagrant@storage:~$ docker volume create new --label test=true
new
vagrant@storage:~$ docker volume create new1 --label test=true
new1
vagrant@storage:~$ docker volume ls
DRIVER VOLUME NAME
local empty
local new
local new1
vagrant@storage:~$ docker volume ls -f label=test=true
DRIVER VOLUME NAME
local new
local new1
vagrant@storage:~$ docker volume prune --filter label=test=true -af
Deleted Volumes:
new
new1
Total reclaimed space: 0B
Также можно указать имя несуществующего тома в опции -v
команды docker run
:
vagrant@storage:~$ docker run -d -v html:/usr/share/nginx/html -p 8888:80 --name nginx nginx
1c9c70a664504e1e05973e11933ffadda0e7d0ac34cbc712fe2c3db5790b5abc
vagrant@storage:~$ docker volume ls
DRIVER VOLUME NAME
local empty
local html
vagrant@storage:~$ curl localhost:8888
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
Примечание
Если мы используем пустой том и монтируем его по пути в контейнере, где уже имеются какие-либо файлы, то данные файлы будут скопированы в этот том.
Для получения информации о томе можно воспользоваться командой docker volume inspect
:
vagrant@storage:~$ docker volume inspect html
[
{
"CreatedAt": "2023-10-04T12:40:45Z",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/html/_data",
"Name": "html",
"Options": null,
"Scope": "local"
}
]
vagrant@storage:~$ sudo ls /var/lib/docker/volumes/html/_data
50x.html index.html
С содержимым в томе можно взаимодействовать также по указанному пути:
vagrant@storage:~$ sudo sh -c 'echo html > /var/lib/docker/volumes/html/_data/index.html'
vagrant@storage:~$ curl localhost:8888
html
Можно комбинировать монтирование томов и директорий внутрь контейнера, также можно использовать вложенность:
vagrant@storage:~$ docker run -d -v html:/usr/share/nginx/html -v /data:/usr/share/nginx/html/data -p 8888:80 --name nginx nginx
1afbbeee97533280a87480f9f051029c310548c1a33a4599449d6824227f78c2
vagrant@storage:~$ curl localhost:8888
html
vagrant@storage:~$ curl localhost:8888/data/
data
Содержимое томов не будет стираться после удаления контейнера, а также может использоваться одновременно несколькими контейнерами:
vagrant@storage:~$ docker rm -f nginx
nginx
vagrant@storage:~$ docker run -d -v html:/usr/share/nginx/html -p 8888:80 --name nginx1 nginx
a603c5e903d8dd4ed894d7dd5cced89e0a8018efbbaef29b19f2183ce7f0933b
vagrant@storage:~$ docker run -d -v html:/usr/share/nginx/html -p 8889:80 --name nginx2 nginx
e5c41a798a90ee298204ff38431972cf6be3ada9fc140331458bd0580ff7f5d9
vagrant@storage:~$ curl localhost:8888
html
vagrant@storage:~$ curl localhost:8889
html
vagrant@storage:~$ docker rm -f nginx1 nginx2
nginx1
nginx2
Remote
Хоть по-умолчанию в качестве драйвера для создания тома можно использовать только local
,
в linux
использование этого драйвера подразумевает возможность использования любой
файловой системы известной ядру и монтируемой командой mount
. Таким образом можно
использовать сетевые файловые системы, например NFS
.
Дополним наш Vagrantfile
двумя машинами для демонстрации и запустим их:
Vagrant.configure("2") do |config|
config.vm.define "storage" do |c|
c.vm.box = "ubuntu/lunar64"
c.vm.hostname = "storage"
c.vm.network "private_network", type: "dhcp"
c.vm.provision "shell", inline: <<-SHELL
mkdir /data;chmod 777 /data
echo '/data *(rw)' > /etc/exports
export DEBIAN_FRONTEND=noninteractive
apt-get update -q
apt-get install -yq libnss-mdns nfs-server docker.io
usermod -a -G docker vagrant
SHELL
end
config.vm.define "docker1" do |c|
c.vm.box = "ubuntu/lunar64"
c.vm.hostname = "docker1"
c.vm.network "private_network", type: "dhcp"
c.vm.provision "shell", inline: <<-SHELL
apt-get update -q
apt-get install -yq libnss-mdns nfs-common docker.io
usermod -a -G docker vagrant
SHELL
end
config.vm.define "docker2" do |c|
c.vm.box = "ubuntu/lunar64"
c.vm.hostname = "docker2"
c.vm.network "private_network", type: "dhcp"
c.vm.provision "shell", inline: <<-SHELL
apt-get update -q
apt-get install -yq libnss-mdns nfs-common docker.io
usermod -a -G docker vagrant
SHELL
end
end
Создадим на машинах docker1
и docker2
тома, которые будут использовать сетевую
файловую систему с машины storage
и запустим с ними контейнер nginx
на каждой машине:
$ vagrant ssh docker1
vagrant@docker1:~$ docker volume create -o type=nfs -o device=:/data -o o=addr=storage.local storage
storage
vagrant@docker1:~$ docker run -d -v storage:/usr/share/nginx/html -p 8888:80 --name nginx nginx
7d9068e47ad0e6ab70f84ec528a756f2aab14db6b55c62356923d5a0697a26c7
$ vagrant ssh docker2
vagrant@docker2:~$ docker volume create -o type=nfs -o device=:/data -o o=addr=storage.local storage
storage
vagrant@docker2:~$ docker run -d -v storage:/usr/share/nginx/html -p 8888:80 --name nginx nginx
348b13f53d1f18498265c1dcb57329ba404e43ee0ccc91b4d020638699d8434a
Зайдем на машину storage
и убедимся, что контейнеры на машинах docker1
и docker2
используют сетевую файловую систему:
$ vagrant ssh storage
vagrant@storage:~$ curl docker1.local:8888
data
vagrant@storage:~$ curl docker2.local:8888
data
vagrant@storage:~$ echo nfs-data > /data/index.html
vagrant@storage:~$ curl docker1.local:8888
nfs-data
vagrant@storage:~$ curl docker2.local:8888
nfs-data
Network
Для управления сетевой конфигурацией существует команда docker network
,
чтобы отобразить список доступный сетей можно выполнить команду docker network ls
:
vagrant@storage:~$ docker network ls
NETWORK ID NAME DRIVER SCOPE
ebe779724911 bridge bridge local
100b3a82985d host host local
9676646e1660 none null local
Bridge
По-умолчанию для запущенных контейнеров используется сеть bridge
,
для создание новой сети с собственной конфигурацией можно воспользоваться командой
docker network create
. Если при создании не указать параметр --driver
, то новая
сеть также будет типа bridge
. Создадим новую сеть и посмотрим ее конфигурацию
командой docker network inspect
:
vagrant@storage:~$ docker network create br --subnet 10.0.0.0/24
feb2d1e6a7ef18fef4b3edf81aaac94b70410c4f7b6c1a7a60fcda8a3fd48b67
vagrant@storage:~$ docker network inspect br
[
{
"Name": "br",
"Id": "feb2d1e6a7ef18fef4b3edf81aaac94b70410c4f7b6c1a7a60fcda8a3fd48b67",
"Created": "2023-10-04T20:05:54.017090666Z",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "10.0.0.0/24"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {},
"Options": {},
"Labels": {}
}
]
При использовании пользовательской сети типа bridge
, в отличии от сети используемой
по-умолчанию, также добавляется функционал разрешения имен контейнеров внутри этой сети.
Для указания сети при запуске контейнера необходимо указать опцию --network
в команде
docker run
, сравним запуск контейнеров в сети по-умолчанию и в нашей созданной:
vagrant@storage:~$ docker run -d --name first alpine sleep inf
495e509f4f9c62a68b3501d7eabd0bd1989a8feb0736cf0cb9c415bb6c83bdd6
vagrant@storage:~$ docker run -d --name second alpine sleep inf
d7960ea8f4ed5f4006a333ca24c830fdd062beefa10d56b9e13dbc8c39fe6f3c
vagrant@storage:~$ docker exec -it first sh
# ip addr show dev eth0
56: eth0@if57: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
# ping -c1 first
ping: bad address 'first'
#
vagrant@storage:~$ docker exec -it second sh
# ip addr show dev eth0
58: eth0@if59: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
link/ether 02:42:ac:11:00:04 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.4/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
# ping -c1 second
ping: bad address 'second'
#
vagrant@storage:~$ docker rm -f first second
first
second
vagrant@storage:~$ docker run -d --network br --name first alpine sleep inf
2479516cf26e2af9df4e158df691695ca20d8ca92c02c35ba438ef89eb18d976
vagrant@storage:~$ docker run -d --network br --name second alpine sleep inf
a6eeefe1997e114e563a92e5930858815f147ce64f2c08b3f7dd1fabd33532d5
vagrant@storage:~$ docker exec -it first sh
# ip addr show dev eth0
60: eth0@if61: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
link/ether 02:42:0a:00:00:02 brd ff:ff:ff:ff:ff:ff
inet 10.0.0.2/24 brd 10.0.0.255 scope global eth0
valid_lft forever preferred_lft forever
# ping -c1 first
PING first (10.0.0.2): 56 data bytes
64 bytes from 10.0.0.2: seq=0 ttl=64 time=0.029 ms
--- first ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max = 0.029/0.029/0.029 ms
# ping -c1 second
PING second (10.0.0.3): 56 data bytes
64 bytes from 10.0.0.3: seq=0 ttl=64 time=0.091 ms
--- second ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max = 0.091/0.091/0.091 ms
#
vagrant@storage:~$ docker rm -f first second
first
second
Как видно из вывода - при использовании созданной нами сети используется заданная нами адресация и работает разрешение имен контейнеров.
Контейнер может быть подключен одновременно к нескольким сетям, для этого после запуска
можно воспользоваться командой docker network connect
, а для отключения от сети
командой docker network disconnect
:
vagrant@storage:~$ docker run -d --name first alpine sleep inf
4a9da4cf9b0b959298b29da8b59a920531a9fa53769525a99d22bac58eda12bb
vagrant@storage:~$ docker exec -it first ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
68: eth0@if69: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
vagrant@storage:~$ docker network connect br first
vagrant@storage:~$ docker exec -it first ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
68: eth0@if69: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
70: eth1@if71: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
link/ether 02:42:0a:00:00:02 brd ff:ff:ff:ff:ff:ff
inet 10.0.0.2/24 brd 10.0.0.255 scope global eth1
valid_lft forever preferred_lft forever
vagrant@storage:~$ docker network disconnect bridge first
vagrant@storage:~$ docker exec -it first ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
70: eth1@if71: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
link/ether 02:42:0a:00:00:02 brd ff:ff:ff:ff:ff:ff
inet 10.0.0.2/24 brd 10.0.0.255 scope global eth1
valid_lft forever preferred_lft forever
Host
Контейнер также можно запустить в сети хоста, так что процессы внутри контейнера будут прослушивать порты непосредственно на адресе хоста:
vagrant@storage:~$ docker run -d -v /data:/usr/share/nginx/html --network host --name nginx nginx
88dec80591b12144824b739177b1133af526ccd4074b3d752044d1a7fe9e347b
vagrant@storage:~$ curl localhost
nfs-data
vagrant@storage:~$ docker rm -f nginx
nginx
IPVLAN
Попробуем объединить сети докера из нескольких виртуальных машин воспользовавшись
драйвером ipvlan
. Данный драйвер позволяет разделить один сетевой
интерфейс хоста между несколькими контейнерами, так что при создании сети нам нужно
указать parent
интерфейс из внутренней сети, которую создает vagrant
, для того
чтобы контейнеры могли общаться в ней между собой. При использовании по-умолчанию
virtualbox provider в vagrant для внутренней сети используется сеть 192.168.56.0/24,
имя сетевого интерфейса можно получить командой:
vagrant@storage:~$ ip -br a | awk '/192.168.56/{print $1}'
enp0s8
Создадим на каждой машине сеть типа ipvlan
указав для каждой машины свой ip-range
:
$ vagrant ssh storage
vagrant@storage:~$ docker network create -d ipvlan --subnet=10.1.1.0/24 --ip-range=10.1.1.0/28 -o parent=enp0s8 internal
91a0d8b2235c7851fe1f90eb7cd1924d5be3b9f631f5dc6b95407523e6d8397c
$ vagrant ssh docker1
vagrant@docker1:~$ docker network create -d ipvlan --subnet=10.1.1.0/24 --ip-range=10.1.1.16/28 -o parent=enp0s8 internal
c618b6d42b63ce1f0af54de147e2ed4c28e62f1ed9bed0e9c7101aae160e1e04
$ vagrant ssh docker2
vagrant@docker2:~$ docker network create -d ipvlan --subnet=10.1.1.0/24 --ip-range=10.1.1.32/28 -o parent=enp0s8 internal
28e646c7404ff3fc6341c19fd6c9a57512d07f8c9ca5050ce370180f8954274a
Запустим на машинах docker1
и docker2
контейнеры с nginx
и убедимся, что
полученные контейнерами адреса находятся в заданных сетях:
$ vagrant ssh docker1
vagrant@docker1:~$ docker run -d --restart=always -v storage:/usr/share/nginx/html --network internal --name nginx nginx
455186759d7c6ea8374034b7aad68067c570807a61d315c31e897b3783803f18
vagrant@docker1:~$ docker inspect nginx -f '{{json .NetworkSettings.Networks.internal.IPAddress}}'
"10.1.1.17"
$ vagrant ssh docker2
vagrant@docker2:~$ docker run -d --restart=always -v storage:/usr/share/nginx/html --network internal --name nginx nginx
1460ce30d5a8ed1267e261c29097c8970e9327088a3ba831066ffe38bffa57c8
vagrant@docker2:~$ docker inspect nginx -f '{{json .NetworkSettings.Networks.internal.IPAddress}}'
"10.1.1.33"
Также запустим nginx
на машине storage
создав конфигурацию, которая будет проксировать
запросы балансируя между контейнерами на машинах docker1
и docker2
:
vagrant@storage:~$ cat <<EOF>>default.conf
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://docker;
}
}
upstream docker {
server 10.1.1.17 fail_timeout=1s;
server 10.1.1.33 fail_timeout=1s;
}
EOF
Запустим контейнер с данной конфигурацией и подключим к нашей сети:
vagrant@storage:~$ docker run -d -v ./default.conf:/etc/nginx/conf.d/default.conf -p 8888:80 --name nginx nginx
vagrant@storage:~$ docker network connect internal nginx
Теперь мы можем проверить связность из данного контейнера до контейнеров на машинах
docker1
и docker2
:
vagrant@storage:~$ curl localhost:8888
nfs-data
Даже если мы остановим машину docker1
, то nginx
на машине storage
будет
направлять запросы на оставшуюся машину docker2
:
$ vagrant halt docker1
==> docker1: Attempting graceful shutdown of VM...
$ vagrant ssh storage
vagrant@storage:~$ curl localhost:8888
nfs-data
$ vagrant halt docker2
==> docker2: Attempting graceful shutdown of VM...
$ vagrant ssh storage
vagrant@storage:~$ curl localhost:8888
<html>
<head><title>502 Bad Gateway</title></head>
<body>
<center><h1>502 Bad Gateway</h1></center>
<hr><center>nginx/1.25.2</center>
</body>
</html>
$ vagrant up docker1
Bringing machine 'docker1' up with 'virtualbox' provider...
==> docker1: Machine booted and ready!
$ vagrant ssh storage
vagrant@storage:~$ curl localhost:8888
nfs-data