Начало работы с Docker. Часть пятая

25.03.2020

Теги: ApacheCLIDockerLinuxMySQLPHPВиртуализацияКомандаНастройкаПроцессУстановка

Взаимодействие служб apache и mysql

Хорошо, наши две службы запускаются, но пока непонятно, могут ли они общаться между собой. И установились ли расширения mysqli и pdo_mysql для работы из PHP с базой данных MySQL. Давайте для начала заглянем внутрь контейнера apache, чтобы проверить расширения для работы с базой данных. Для этого запустим службы в работу и подключимся к контейнеру apache.

Сначала выясним из вывода phpinfo(), где расположены ini-файлы:

Configuration File (php.ini) Path /usr/local/etc/php
Loaded Configuration File /usr/local/etc/php/php.ini
Scan this dir for additional .ini files /usr/local/etc/php/conf.d
Additional .ini files parsed /usr/local/etc/php/conf.d/docker-php-ext-mysqli.ini,
/usr/local/etc/php/conf.d/docker-php-ext-pdo_mysql.ini,
/usr/local/etc/php/conf.d/docker-php-ext-sodium.ini

Теперь запускаем наши службы

$ docker-compose up -d
Creating network "www_default" with the default driver
Creating www_apache_1 ... done
Creating www_mysql_1  ... done

И смотрим ini-файлы внутри контейнера

$ docker-compose exec apache /bin/bash
# cd /usr/local/etc/php/conf.d/
# ls
docker-php-ext-mysqli.ini  docker-php-ext-pdo_mysql.ini  docker-php-ext-sodium.ini
# cat docker-php-ext-mysqli.ini
extension=mysqli.so
# cat docker-php-ext-pdo_mysql.ini 
extension=pdo_mysql.so
# exit

Вроде все в порядке, так что можем поработать из php-скрипта с базой данныx:

$ nano ~/www/apache/html/index.php
<?php
// открываем новое соединение
$mysqli = new mysqli(
    'mysql',  // хост в сети
    'root',   // пользователь
    'qwerty', // пароль
    'mysql'   // база данных
);
// если произошла ошибка
if ($mysqli->connect_error) {
    die('Error : ('. $mysqli->connect_errno .') '. $mysqli->connect_error);
}

// выполняем запрос к БД
$results = $mysqli->query("SELECT `User`, `Host` FROM `user` WHERE 1");
// выводим результат запроса
echo '<table border="1">';
while($row = $results->fetch_assoc()) {
    echo '<tr>';
    echo '<td>'.$row['User'].'</td>';
    echo '<td>'.$row['Host'].'</td>';
    echo '</tr>';
}
echo '</table>';

// освобождаем память
$results->free();
// закрываем соединение
$mysqli->close();

Обратите внимание, что в качестве хоста для соединения с сервером БД мы указываем mysql — это имя службы в файле docker-compose.yml.

Три службы: Apache+PHP, MySQL и phpMyAdmin

Давайте отредактируем файл ~/www/docker-compose.yml и добавим еще один контейнер:

$ nano ~/www/docker-compose.yml
version: '3'
services:
  apache:
    build: # инструкции для создания образа
      # контекст (путь к каталогу, содержащему Dockerfile)
      context: ./apache/
      # это необязательно, потому что контекст уже задан
      dockerfile: Dockerfile
    ports:
      # контейнер будет доступен на порту 80 основной системы
      - 80:80
    volumes: # путь указывается от директории, где расположен docker-compose.yml
      # монтируем директорию с php-скриптом внутрь контейнера
      - ./apache/html/:/var/www/html/
      # монтируем файл конфигурации php.ini внутрь контейнера
      - ./apache/php.ini:/usr/local/etc/php/php.ini
      # монтируем файл конфигурации Apache2 внутрь контейнера
      - ./apache/httpd.conf:/etc/apache2/apache2.conf
      # монтируем файл логов доступа Apache2 внутрь контейнера
      - ./apache/logs/access.log:/var/log/apache2/access.log
      # монтируем файл логов ошибок Apache2 внутрь контейнера
      - ./apache/logs/error.log:/var/log/apache2/error.log
  mysql:
    build: # инструкции для создания образа
      # контекст (путь к каталогу, содержащему Dockerfile)
      context: ./mysql/
      # это необязательно, потому что контекст уже задан
      dockerfile: Dockerfile
    environment:
      # пароль пользователя root
      MYSQL_ROOT_PASSWORD: qwerty
    ports:
      - 3306:3306
    volumes: # путь указывается от директории, где расположен docker-compose.yml
      # монтируем файл конфигурации MySQL внутрь контейнера
      - ./mysql/mysql.cnf:/etc/mysql/my.cnf
      # монтируем директорию с базами данных внутрь контейнера
      - ./mysql/data/:/var/lib/mysql/
      # монтируем директорию с логами MySQL внутрь контейнера
      - ./mysql/logs/:/var/log/mysql/
  pma:
    # используем готовый образ phpmyadmin
    image: phpmyadmin/phpmyadmin
    ports:
      - 8080:80
    environment:
      # название хоста в сети www_default
      PMA_HOST: mysql
      MYSQL_USERNAME: root
      MYSQL_ROOT_PASSWORD: qwerty
$ docker-compose up -d
Creating network "www_default" with the default driver
Creating www_mysql_1 ... done
Creating www_apache_1 ... done
Creating www_pma_1    ... done

Открываем браузер и набираем в адресной строке http://localhost:8080:

$ docker-compose down
Stopping www_apache_1 ... done
Stopping www_mysql_1  ... done
Stopping www_pma_1    ... done
Removing www_apache_1 ... done
Removing www_mysql_1  ... done
Removing www_pma_1    ... done
Removing network www_default

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

Сеть Docker построена на Container Network Model (CNM), которая позволяет любому желающему создать свой собственный сетевой драйвер. Таким образом, у контейнеров есть доступ к разным типам сетей и они могут подключаться к нескольким сетям одновременно. Помимо различных сторонних сетевых драйверов, у самого Docker-а есть 4 встроенных:

  • bridge: в этой сети контейнеры запускаются по умолчанию. Связь устанавливается через bridge-интерфейс на хосте. У контейнеров, которые используют одинаковую сеть, есть своя собственная подсеть, и они могут передавать данные друг другу по умолчанию.
  • host: этот драйвер дает контейнеру доступ к собственному пространству хоста (контейнер будет видеть и использовать тот же интерфейс, что и хост).
  • overlay: этот драйвер позволяет строить сети на нескольких хостах с Docker (обычно на Docker Swarm кластере). У контейнеров также есть свои адреса сети и подсети, и они могут напрямую обмениваться данными, даже если они располагаются физически на разных хостах.
  • none: это сетевой драйвер, который умеет отключать всю сеть для контейнеров. Обычно используется в сочетании с пользовательским сетевым драйвером.

Сети типа мост (bridge)

По умолчанию для контейнеров используется bridge. При первом запуске контейнера Docker создает дефолтную bridge-сеть. Эту сеть можно увидеть в общем списке по команде

$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
4db4885e345c        bridge              bridge              local
1a425e4362b4        host                host                local
9246f826508b        none                null                local

Чтобы проинспектировать ее свойства, запустим команду

$ docker network inspect bridge
[
    {
        "Name": "bridge",
        "Id": "4db4885e345cbad5662da7cc0fe753e2f323a84fd2ba01947067cb1cca2b70c5",
        "Created": "2020-03-30T08:56:17.5621013+03:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.17.0.0/16",
                    "Gateway": "172.17.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {},
        "Options": {
            "com.docker.network.bridge.default_bridge": "true",
            "com.docker.network.bridge.enable_icc": "true",
            "com.docker.network.bridge.enable_ip_masquerade": "true",
            "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
            "com.docker.network.bridge.name": "docker0",
            "com.docker.network.driver.mtu": "1500"
        },
        "Labels": {}
    }
]

Также можно создать свои собственные bridge-сети при помощи команды

$ docker network create --driver bridge --subnet 192.168.100.0/24 --ip-range 192.168.100.0/24 custom-bridge-network

Bridge-интерфейсы хоста

Каждая bridge-сеть имеет свое представление в виде интерфейса на хосте. С сетью bridge, которая существует по умолчанию, обычно ассоциируется интерфейс docker0. Для каждой новой сети, которая создается при помощи команды docker network create, будет ассоциироваться свой собственный новый интерфейс.

$ ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default 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
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: enp2s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 00:e0:4c:36:01:db brd ff:ff:ff:ff:ff:ff
    inet 192.168.110.14/24 brd 192.168.110.255 scope global dynamic enp2s0
       valid_lft 15789sec preferred_lft 15789sec
    inet6 fe80::2e0:4cff:fe36:1db/64 scope link 
       valid_lft forever preferred_lft forever
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default 
    link/ether 02:42:3f:50:59:fd brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:3fff:fe50:59fd/64 scope link 
       valid_lft forever preferred_lft forever

Можно получить больше данных о статусе моста при помощи утилиты brctl (должен быть установлен пакет bridge-utils):

$ brctl show docker0
bridge name    bridge id            STP enabled    interfaces
docker0        8000.02423f5059fd    no

Как только мы запустим контейнеры и привяжем их к этой сети, интерфейс каждого из этих контейнеров будет выведен в списке в отдельной колонке. А если включить захват трафика в bridge-интерфейсе, то можно увидеть, как передаются данные между контейнерами в одной подсети.

Виртуальные интерфейсы Linux

Container Networking Model дает каждому контейнеру свое собственное сетевое пространство. Если запустить команду ip addr внутри контейнера, то можно увидеть его интерфейсы такими, какими их видит сам контейнер:

$ docker run -it ubuntu:latest
# apt update
# apt install iproute2 -y
# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default 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
48: eth0@if49: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever

Интерфейс eth0, который представлен в этом примере, можно увидеть только изнутри контейнера, а снаружи, на хосте, Docker создает соответствующую копию виртуального интерфейса if49, которая служит связью с внешним миром. Запустим еще один терминал на хосте и выполним команду

$ ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default 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
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: enp2s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 00:e0:4c:36:01:db brd ff:ff:ff:ff:ff:ff
    inet 192.168.110.14/24 brd 192.168.110.255 scope global dynamic enp2s0
       valid_lft 13634sec preferred_lft 13634sec
    inet6 fe80::2e0:4cff:fe36:1db/64 scope link 
       valid_lft forever preferred_lft forever
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:3f:50:59:fd brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:3fff:fe50:59fd/64 scope link 
       valid_lft forever preferred_lft forever
49: veth59134c1@if48: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default 
    link/ether ba:52:36:4d:82:41 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::b852:36ff:fe4d:8241/64 scope link 
       valid_lft forever preferred_lft forever
$ brctl show docker0
bridge name    bridge id            STP enabled    interfaces
docker0        8000.02423f5059fd    no             veth59134c1

Откроем еще один терминал на хосте и запустим контейнер:

$ docker run -it ubuntu:latest
# apt update
# apt install iproute2 -y
# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default 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
50: eth0@if51: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever

Теперь опять выполним на хосте команды

$ ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default 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
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: enp2s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 00:e0:4c:36:01:db brd ff:ff:ff:ff:ff:ff
    inet 192.168.110.14/24 brd 192.168.110.255 scope global dynamic enp2s0
       valid_lft 13136sec preferred_lft 13136sec
    inet6 fe80::2e0:4cff:fe36:1db/64 scope link 
       valid_lft forever preferred_lft forever
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:3f:50:59:fd brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:3fff:fe50:59fd/64 scope link 
       valid_lft forever preferred_lft forever
49: veth59134c1@if48: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default 
    link/ether ba:52:36:4d:82:41 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::b852:36ff:fe4d:8241/64 scope link 
       valid_lft forever preferred_lft forever
51: veth48f087e@if50: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default 
    link/ether fa:62:a1:e8:46:fc brd ff:ff:ff:ff:ff:ff link-netnsid 1
    inet6 fe80::f862:a1ff:fee8:46fc/64 scope link 
       valid_lft forever preferred_lft forever
$ brctl show docker0
bridge name    bridge id            STP enabled    interfaces
docker0        8000.02423f5059fd    no             veth48f087e
                                                   veth59134c1

Сразу стало видно, что два интерфейса присоединены к bridge-интерфейсу docker0 (по одному на каждый контейнер).

                        | Интефейс внутри контейнера | Интерфейс снаружи контейнера
-----------------------------------------------------------------------------------
Первый контейнер Ubuntu | Имя eth0, номер 48         | Имя veth59134c1, номер 49
Второй контейнер Ubuntu | Имя eth0, номер 50         | Имя veth48f087e, номер 51

Дополнительно

Поиск: Apache • CLI • Docker • MySQL • PHP • Виртуализация • Команда • Настройка • Процесс • Установка • Linux

Каталог оборудования
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Производители
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Функциональные группы
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.