Docker и docker-compose для начинающих. Докеризуем интернет-магазин

ноябрь 3 , 2019
Исходники

Докер везде. Когда-то на него смотрели, как на очередную забаву неугомонных программистов, но докер оказался не таков. Если раньше в любой вакансии писали jquery и zend framework, то сейчас - git и docker. Не удивлюсь, если докер станет таким же стандартом, как и гит в свое время. Или уже стал, это я только из пещеры вылез.

Зачем мне понадобился докер? Ну не знал и не знал, сидел не умничал, сейчас-то что началось?

Разбираться с ним меня сподвигли вы, дорогие читатели. Да ладно? Серьезно. Четверть вопросов по интернет-магазину звучит примерно так: не работает, что делать? Почти всегда причина в том, что нужно настроить окружение. Поставить веб-сервер, завести php и mysql, развернуть базу и прочие рутинные штуки.

Вопрос разворачивания рабочего окружения для веб-проектов волновал меня давно.

Когда я начал заниматься первыми веб-проектами, то ухитрился поднять apache, php и mysql на windows 7. Было это лет 8 назад. История из серии "один раз получилось, но повторить не смогу". Это медаль, которую получаешь раз в жизни.

Вторая попытка осмыслить вопрос рабочего окружения случилась в 2015 году. Я тогда написал адовую статью, как развернуть окружение для веб-разработчика. Это было страшно, но я сам так работал. Схема предполагала винду как основную ОС, в винде ставилась виртуальная машина VirtualBox, в которой поднимался debian. В debian уже nginx, php, mysql и nodejs. Также ssh-сервер на виртуалке и ssh-клиент на винде. Все это волшебным образом соединялось вместе и реально работало! Я не жалею, что творил эту дичь, но вам не советую проделывать такой же путь. Просто не надо. Мир стал намного проще.

Затем я пересел на linux и сразу стало легче. nginx, php и mysql ставились несколькими командами. Раз поставил, нашел типовую конфигурацию для nginx и фигачишь. Создать виртуальный хост для нового проекта - дело двух минут. По сей день так работаю.

Но вопрос читателей меня не оставлял. Ведь все мы разные. Одни только начинают разбираться в веб-разработке. Другие работают на винде и им так удобнее. Третьи занимаются фронтендом и им вообще до фонаря эти php и базы данных. Они хотят потрогать javascript-код, а как его потрогаешь, если магазин просто не заводится?

Часто меня выручали системы вроде denwer и open server. Я сам ими не пользовался, но видел со стороны, как это работает, и поэтому советовал. В том же денвере apache, php и mysql поднимаются довольно просто, на мой взгляд. Судя по отзывам, некоторых читателей это выручало.

Но пора было делать следующий шаг.


Докер

Докер - это такая штука для разработки и разворачивания приложений. В случае веб-проектов докер позволяет развернуть nginx, php, mysql, phpmyadmin и вообще любые приложения. Причем все это добро не нужно ставить руками и засорять систему. Все сервисы запускаются в виртуальных контейнерах и никак не влияют на основную ОС. То есть у вас в системе будут только докер, редактор кода, браузер и командная строка. Звучит круто, но пока бесполезно, да? Но смотрим дальше.

Если приложение завернуто в докер, то оно запускается одной командой. По ходу статьи мы увидим, как это делается. Представьте, чтобы посмотреть, как работает авторизация или админка в интернет-магазине, вам нужно лишь скачать исходники и выполнить команду


    docker-compose up --build

Да-да, это все. Забудьте об nginx, php и mysql, о том, что под капотом крутится вебпак и нужно не забывать делать npm install - всю эту работу возьмет на себя докер. Больше не нужно гадать, заведется ли код на вашей версии php или какую базу данных нужно поставить. Все это докер делает под капотом. Нам остается работать над кодом и не думать об окружении.

Конечно, это все звучит здорово, но прежде чем так легко обращаться с докером, нужно разобраться как и что.


Основы докера

Разбираясь в теме, я прочитал десятка три статей по докеру. Но в голове все уложилось только когда я нашел вот эту

Docker самый простой и понятный туториал

Рекомендую статью всем, кто хочет не просто скопипастить примеры из моей статьи, а понять, что такое докер, образы, контейнеры, как монтировать папки и файлы, как запускать команды и еще много полезных вещей. Статья большая, но написано по-человечески, простым языком, с примерами и аналогиями из жизни.


Докер на практике, docker-compose

docker-compose - это система, которая позволяет управлять набором из докер-контейнеров. То есть работают отдельные контейнеры nginx, php, mysql, но все это еще нужно подружить между собой. Любой туториал по докеру приводит в пример, как запустить nginx, но нам этого мало. Как завести сразу все, что нужно? Иными словами, LEMP - linux, nginx, php и mysql.

Здесь случилось то же самое, что и с теорией по основам докера. Я перепробовал много разных вариантов, но то php не заводился, то mysql не подключался. Удалось все сделать только по примеру из этой статьи

Установка LEMP с помощью Docker'а

Рекомендую читать всем, но особенно тем, кто уже разбирался с docker и docker-compose. Если вы знаете общие принципы или просто хотите завести свой проект, то лучше прочитать эту статью. Автор расписал коротко и по делу. Это не как у меня, статьи только с литром пива читать.

А еще мне понравилась структура проекта из поста. С разрешения автора в своей статье я использую его структуру и конфиги проекта практически без изменений, разве что слегка сократив.


docker и docker-compose. Аналогия для фронтендщиков

Процесс докеризации проекта напоминает мне сборку фронтенда.

В обоих случаях нам нужно решить набор задач. На фронте это собрать весь javascript в один файл и сжать его, препроцессить стили и тоже их сжать, оптимизировать картинки, запустить watcher. А с докером это поднять nginx, php, mysql и развернуть базу.

Для сборки фронта мы используем npm-пакеты, готовые библиотеки для сжатия и склеивания. В докере это готовые образы nginx, php, mysql.

Все это добро на фронте собирается в один конфиг gulpfile.js или webpack.config.js, а в докере - в docker-compose.yml. И все задачи запускаются одной командой, например, gulp build или docker-compose up --build

Вот такая у меня аналогия. С общими делами разобрались, переходим к практике


Ставим docker и docker-compose

Докер отлично работает на линуксе и маке. Говорят, на 10-й винде тоже, но не пробовал. Я не знаю, какая у вас система, поэтому смотрите инструкции здесь.

Установить docker - ссылка для ubuntu, но там рядом и другие ОС
Установить docker-compose

Выбирайте версию CE - Community Edition, она бесплатная. EE - enterprise, нам ни к чему.

После установки докера перезагрузитесь и проверьте, все ли в порядке


    docker -v && docker-compose -v

Если что-то не заработает, то наберите такие команды


    sudo groupadd docker
    sudo usermod -aG docker $USER
    
    sudo chmod +x /usr/local/bin/docker-compose

Структура проекта


    - hosts/
    - images/
        - php/
    - logs/
    - mysql/
    - www/
        - default.test/
    docker-compose.yml
  1. В папке hosts мы будем хранить конфиги nginx
  2. В images - образы докера, пока только php
  3. Папка logs нужна для хранения логов nginx
  4. mysql будет содержать файлы базы данных
  5. В www будем складывать непосредственно проекты. Каждый проект в отдельной папке. Для начала - default.test, который будем использовать для примера.
  6. docker-compose.yml - файл конфига, который соберет все в кучу

Тестовый проект default.test

Он будет очень простой. Закинем в папку www/default.test файлик index.php с содержимым


    phpinfo();

Вот и весь проект. На первом этапе мы всего лишь убедимся, что правильно настроили nginx и php. Давайте же посмотрим, как это сделать


Конфиг nginx

Это файл hosts/default.conf такого содержимого


    server {
        index index.php index.html;
        server_name default.test;
        error_log  /var/log/nginx/default.error.log;
        access_log /var/log/nginx/default.access.log;
        root /var/www/default.test;
    
        location ~ \.php$ {
            try_files $uri =404;
            fastcgi_split_path_info ^(.+\.php)(/.+)$;
            fastcgi_pass php:9000;
            fastcgi_index index.php;
            include fastcgi_params;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            fastcgi_param PATH_INFO $fastcgi_path_info;
        }
    }

Если вам приходилось настраивать nginx, то отличие найдете только одно. Раньше в локейшене \.php$ в fastcgi_pass вы писали что-то вроде


    fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;

Теперь же php:9000. Это докеровская тема, php - это название хоста, по которому доступен php-контейнер, а 9000 - порт, по которому до него можно достучаться.

Такой конфиг более менее универсальный и подходит для локальной разработки несложных проектов. В статье мы рассмотрим 3 проекта и конфиги в них будут различаться только 4 строками.

  1. index index.php index.html; - это путь к корневому файлу проекта. Можно оставить оба, и php, и html. Если в папке будет index.php, то исполльзуется он, если нет - то index.html
  2. server_name default.test; - имя хоста, которое мы будем вбивать в браузере
  3. error_log /var/log/nginx/default.error.log; - куда складывать error логи nginx. Вот и пригодилась папка logs. Для каждого проекта будем заводить отдельные логи, хотя никто не запрещает складывать все в просто error.log и у вас будет один файл на все проекты. Для разработки годится
  4. access_log /var/log/nginx/default.access.log; - аналогично, только для access логов
  5. root /var/www/default.test; - папка проекта. Хост и название папки указываем одинаково, с точкой, чтобы не путаться

Настраиваем php и Dockerfile для него

По идее можно не делать отдельный Dockerfile для php, а взять готовый образ. Именно так мы сделаем с nginx и mysql, когда будем настраивать docker-compose.yml. Но проблема в том, что nginx и mysql прекрасно работают из коробки, а с php немного иначе. Он тоже работает, но в официальный образ не включены никакие расширения, которые могут понадобиться при работе. Именно поэтому мы будем собирать php хитрее, через Dockerfile.

Давайте посмотрим на конфиг. Это файл images/php/Dockerfile


    FROM php:7.2-fpm
    
    RUN apt-get update && apt-get install -y \
            curl \
            wget \
            libfreetype6-dev \
            libjpeg62-turbo-dev \
            libmcrypt-dev \
        && pecl install mcrypt-1.0.1 \
        && docker-php-ext-install -j$(nproc) iconv mbstring mysqli pdo_mysql zip \
        && docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \
        && docker-php-ext-install -j$(nproc) gd \
        && docker-php-ext-enable mcrypt
    
    ADD php.ini /usr/local/etc/php/conf.d/40-custom.ini
    
    WORKDIR /var/www
    
    CMD ["php-fpm"]

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

  1. FROM php:7.2-fpm - указываем официальный образ. Напоминаю, он пустой, без всяких расширений
  2. Блок RUN - куча линукс-команд, которые ставят curl, wget и различные расширения вроде mysqli и mcrypt. Без mysqli, например, наш интернет-магазин просто не заработает
  3. ADD php.ini /usr/local/etc/php/conf.d/40-custom.ini - это возможность использовать кастомный php.ini. Кстати, добавьте его в папку images/php, пусть будет пустой
  4. WORKDIR /var/www - рабочая директория для php
  5. CMD ["php-fpm"] - команда запуска контейнера

Да, Dockerfile для php посложнее, чем стандартный nginx-конфиг. nginx мы просто будем копипастить, меняя хост и пути. Здесь же, чтобы самому написать такой файл, нужно представлять, как работает php, где хранится php.ini, что такое рабочая директория и как ставить расширения. Но с другой стороны, базовые настройки меняться не будет, главное, разобраться со страшной портянкой в RUN.

Блок RUN - это набор команд, которые мы нагуглили "как установить php в linux" и вбили в консоли. Разве что расширения ставятся через docker-php-ext-install. Например, в линуксе мы бы поставили mysqli примерно так


    sudo apt-get install php7.2-mysqli

А в докере нужно указать его в строке


    docker-php-ext-install -j$(nproc) mysqli

Если честно, мне пока сложно понять, где и какие команды используются в тех или иных случаях. Поэтому не вижу других способов, чтобы для каждого конкретного случая копать документацию и гуглить. Но повторюсь, для наших проектов большего не понадобится. В Dockerfile мы больше заглядывать не будем.

Пора собирать nginx и php в единое целое - переходим к docker-compose.yml


Конфиг docker-compose.yml

Файл будет такого содержания


    version: '2'
    services:
      nginx:
        image: nginx:latest
        ports:
          - "8000:80"
        volumes:
          - ./hosts:/etc/nginx/conf.d
          - ./www:/var/www
          - ./logs:/var/log/nginx
        links:
          - php
      php:
        build: ./images/php
        volumes:
          - ./www:/var/www

Разберем, что здесь написано.

version: '2' - версия docker-compose. Сейчас есть и 3-я, но там какие-то совсем хитрые штуки, нам хватит и 2
services - здесь перечисляем контейнеры, которые будут у нас работать в связке. Пока что nginx и php, позже добавим mysql
image: nginx:latest - используем последнюю версию образа nginx с официального хранилища dockerhub
ports: - "8000:80" - здесь прокидываем порты. nginx в контейнере работает на дефолтном 80, а мы возьмем 8000. Это значит, в браузере будем открывать не default.test, a default.test:8000

volumes: - здесь монтируем файлы и папки. Или прокидываем их из локальной системы в контейнер. Посмотрим первый пример ./hosts:/etc/nginx/conf.d. В папке hosts у нас лежат конфиги nginx, но контейнер-то об этом не знает. Контейнер изолирован от нашей основной системы, а раздел volumes как раз и позволяет "общаться" контейнеру с нашей ОС. Можно считать эту команду копированием содержимого локальной папки ./hosts в "удаленную папку" контейнера /etc/nginx/conf.d. Или еще лучше настройкой ссылки (симлинки). Аналогично ./www:/var/www указывает рабочую директорию для nginx, а ./logs:/var/log/nginx - расположение логов

links: - php указывает, что контейнер nginx имеет зависимость от php

Дальше раздел php. Здесь короче, потому что основное мы указали в Dockerfile

build: ./images/php - папка, где располагается Dockerfile
volumes: - ./www:/var/www - прокидываем рабочую директорию точно так же, как и для nginx

С конфигом docker-compose.yml пока все. Идем дальше


Правим файл hosts

Чтобы наш пробный сайт default.test заработал в браузере, нужно не забыть добавить его в файл hosts. Открываем /etc/hosts с sudo (на unix-системах) и добавляем в него


    127.0.0.1 default.test

Точно так же, как и при работе с локальным веб-сервером. Сохраняем файл, закрываем


Запускаем проект

Наконец-то самое интересное, проверим, как все это работает. В консоли из корневой папки проекта запускаем команду


    docker-compose up 

И ждем... Первый запуск будет проходить не быстро. В консоль будет сыпаться до фига всего разного, но можно будет разглядеть, как скачиваются образы ngnix и php, как выполняются команды RUN из php-шного Dockerfile, как запускаются контейнеры.

Если мы все сделали правильно, то увидим в консоли примерно такую картину


    Starting docker_php_1 ... done
    Starting docker_nginx_1 ... done
    Attaching to docker_php_1, docker_nginx_1
    php_1    | [01-Nov-2019 16:21:18] NOTICE: fpm is running, pid 1
    php_1    | [01-Nov-2019 16:21:18] NOTICE: ready to handle connections

done напротив названий контейнеров говорит, что они успешно запущены. Идем в браузер, переходим на http://default.test:8000/ и видим полный расклад phpinfo. Работает!

Мы запустили наш первый проект, завернутый в докер. Не знаю, как вы, а я испытал какой-то священный трепет, когда все это завелось. С ума сойти, я могу просто взять этот проект, перетащить на другой компьютер и запустить его одной командой в консоли. Да, весь проект всего лишь выводит phpinfo, но эта простенькая штука станет базой для разворачивания более сложных проектов, связанных с базой данных да и вообще с чем угодно. Ниже мы научимся подключать в докере mysql и заведем интернет-магазин.

Но сначала немного отвлечемся и посмотрим, какие команды нам пригодятся при работе с docker-compose и вообще с проектами


Как работать с docker-compose

Первой командой, которую мы запустили, был docker-compose up. Команда анализирует конфиг docker-compose.yml, скачивает нужные образы, монтирует файлы и папки и запускает контейнеры. При этом процесс висит в консоли, а чтобы остановить его, нужно нажать ctrl+C, стандартно. А еще в консоли будут выводиться логи docker-compose.

Можно запустить контейнеры с опцией --build, вот так


    docker-compose up --build

При этом принудительно пересоберутся все образы. Это может быть полезно, когда мы что-то поменяли в конфигах и пока еще не понимаем, требует это пересборки или нет. Принудительный билд занимает больше времени, но я пока предпочитаю всегда его использовать. Все равно после самого первого сбора запуск проходит очень быстро, а потерпеть лишние 2 секунды не проблема.

Если вы не хотите, чтобы в консоли висел открытый docker-compose, запускайте его как процесс, с опцией -d


    docker-compose up --build -d

Тогда докер запустит все контейнеры, но в фоне. Все будет работать точно так же, но чтобы остановить контейнеры, нужно будет запускать


    docker-compose down 

А логи смотреть, запуская отдельно


    docker-compose logs -f

Если мы разворачиваем приложение в продакшене, то конечно, запускать его стоит как фоновый процесс. Но в режиме разработки мне больше нравится держать консоль открытой и сразу смотреть логи. И не думать, нужна ли пересборка всех образов или нет. То есть запускать


    docker-compose up --build

Еще при разработке бывает полезно заглянуть в логи nginx. Отслеживать их будем через обычный tail -f


    tail -f ./logs/default.error.log

Пока это самое базовое, что может нам потребоваться. Для более тонкой работы с отдельными образами и контейнерами читайте вышеупомянутую статью - Docker самый простой и понятный туториал


Запускаем второй проект. Мое старое портфолио

Предлагаю немного передохнуть и закрепить информацию. Каким образом? Мы создадим второй проект. Он тоже php-ный, но чуть сложнее одного файла. Там уже подключаются шрифты, стили и javascript. Технически это не будет отличаться от первого default.test, но фишка в другом. Мы увидим, как просто нам теперь создавать новые проекты, а заодно и посмотрим на мое старое портфолио, которое я создал лет 6-7 назад. Это простой сайт на php, где рассказывается, какой я замечательный человек и разработчик.

Создаем новую папку www/w-portfolio.test. Кладем туда файлы проекта. Расписывать их нет смысла, все найдете в исходниках.

Дальше добавляем nginx конфиг в hosts - w-portfolio.conf с содержимым


    server {
        index index.php index.html;
        server_name w-portfolio.test;
        error_log  /var/log/nginx/portfolio.error.log;
        access_log /var/log/nginx/portfolio.access.log;
        root /var/www/w-portfolio.test;
    
        location ~ \.php$ {
            try_files $uri =404;
            fastcgi_split_path_info ^(.+\.php)(/.+)$;
            fastcgi_pass php:9000;
            fastcgi_index index.php;
            include fastcgi_params;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            fastcgi_param PATH_INFO $fastcgi_path_info;
        }
    }

То есть мы скопировали конфиг из дефолтного default.conf и поменяли default на w-portfolio. Что может быть проще?

Осталось добавить в файл hosts строку


    127.0.0.1 w-portfolio.test

И перезапустить docker-compose


    docker-compose up --build

И открыть в браузере http://w-portfolio.test:8000

Итак, второй сайт разобрали, идем дальше.


Создаем проект интернет-магазина

Все то же самое, что и с проектом w-portfolio.test.

Создаем папку w-shop.test, копируем туда файлы магазина. Если вы читали статьи по магазину или админке, то в курсе, что это за магазин. Можете просто взять свои локальные файлы. А если нет, то взгляните, как этот самый магазин выглядит shop.webdevkin.ru и берите файлы из исходников

Дальше заводим nginx-конфиг w-shop.conf в папке hosts


    server {
        index index.php index.html;
        server_name w-shop.test;
        error_log  /var/log/nginx/shop.error.log;
        access_log /var/log/nginx/shop.access.log;
        root /var/www/w-shop.test;
    
        location ~ \.php$ {
            try_files $uri =404;
            fastcgi_split_path_info ^(.+\.php)(/.+)$;
            fastcgi_pass php:9000;
            fastcgi_index index.php;
            include fastcgi_params;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            fastcgi_param PATH_INFO $fastcgi_path_info;
        }
    }

И прописываем в /etc/hosts строку


    127.0.0.1 w-shop.test

Если вы прямо сейчас перезапустите docker-compose, то у вас откроется первая страница http://w-shop.test:8000/ и корзина http://w-shop.test:8000/cart.html. Будет работать даже добавление в корзину. А вот каталог с фильтрами, каталог с пагинацией и отправка заказов не заведутся, потому что там уже включается база. Давайте разбираться, как работать с mysql


Подключаем mysql

Добрались до самого интересного. Идем сразу в конфиг docker-compose.yml и добавим в services новый раздел mysql, вот так


  mysql:
    image: mysql
    ports:
      - "3307:3306"
    volumes:
      - ./mysql:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: root

А в раздел php добавим зависимость от нового контейнера


  php:
    build: ./images/php
    links:
      - mysql
    volumes:
      - ./www:/var/www

Разбираемся по порядку.

image: mysql - образ mysql из dockerhub-хранилища
ports: - "3307:3306" - справа дефолтный порт, на котором mysql работает в контейнере. Слева - порт, который займет mysql в локальной ОС. Почему я не оставил тоже дефолтный 3306? Потому что у меня уже установлен локальный mysql. У вас, скорее всего, тоже. И когда я попытался запустить докер, то он мне сказал, извини, чувак, порт 3306 уже занят, не шмогла. Проверяем в консоли


    $ sudo netstat -nlpt | grep 3306
    tcp        0      0 127.0.0.1:3306          0.0.0.0:*               LISTEN      1121/mysqld     

Действительно занят. Можно остановить локальный mysql через sudo service mysql stop, а можно просто повесить докеровский mysql на другой порт. Например, на 3307 или любой другой свободный.

Идем дальше.
volumes: - ./mysql:/var/lib/mysql - монтируем папки, ./mysql - это папка в нашем проекте, куда будем складывать файлы базы, а /var/lib/mysql - это папка, где лежит база по дефолту. По идее, можно держать базу и внутри контейнера, но удобнее вытащить в "свою" ОС. Например, так можно будет делать бэкапы и использовать одну и ту же базу в разных проектах

И последнее
environment: MYSQL_ROOT_PASSWORD: root - переменные среды. Здесь указываем только пароль для root-пользователя. В интернет-магазине у нас использовался root/root, пусть и здесь так же

В php мы добавили блок-зависимость от нового контейнера mysql
links: - mysql

Все, база mysql у нас будет подниматься и будет работать. Но есть одно но. Как с ней работать руками? Чтобы прямо войти и посмотреть.

Конечно, можно подключаться к ней в консоли и фигачить create table и прочие хакерские заклинания, но хочется чего-то попроще. Люди давно изобрели PhpMyAdmin, который стоит на каждом хостинге и нам привычен. Давайте и его заведем в докер.


Устанавливаем PhpMyAdmin

В docker-compose.yml добавляем еще один контейнер


    pma:
        image: phpmyadmin/phpmyadmin
        restart: always
        links:
          - mysql:mysql
        ports:
          - 8001:80
        environment:
          PMA_HOST: mysql
          MYSQL_USERNAME: root
          MYSQL_ROOT_PASSWORD: root

Разбираем

  • image: phpmyadmin/phpmyadmin - берем официальный образ
  • restart: always - вот этой магии, честно, не понял. Всегда перезапускать контейнер, но почему только этот? Как узнаю, расскажу, а пока оставим так
  • links: - mysql:mysql - зависимость от mysql
  • ports: - 8001:80 - справа дефолтный порт, на котором phpmyadmin запущен в контейнере, слева - любой свободный, я взял 8001 - следующий за 8000, по которому открываем сам сайт
  • И переменные среды: хост, пользователь и пароль. В реальном приложении, конечно, не будем заходить под рутом, но для примера годится
    environment: PMA_HOST: mysql, MYSQL_USERNAME: root, MYSQL_ROOT_PASSWORD: root

Готово, скоро будем пробовать. Но пока еще раз отвлечемся


Зависимости в docker-compose

Обратите внимание, как мы проставляли зависимости контейнеров друг от друга.


    nginx:
        links:
            - php
    php:
        links:
            - mysql
    mysql:
        ...
    pma:
        links:
            - mysql:mysql

nginx зависит от php, то есть сначала стартует php, а потом nginx. php от mysql, phpmyadmin тоже от mysql и лишь mysql ни от кого не зависит. Она база, она сама по себе работает.

Когда мы запустим docker-compose снова, то увидим в консоли такую картину


    Starting docker_mysql_1 ... done
    Starting docker_php_1   ... done
    Recreating docker_pma_1 ... done
    Starting docker_nginx_1 ... done

Это как раз демонстрирует порядок запуска контейнеров. А останавливаются они в обратном порядке


    Killing docker_pma_1    ... done
    Killing docker_nginx_1  ... done
    Killing docker_php_1    ... done
    Killing docker_mysql_1  ... done

Сначала те, от которых никто не зависит, в конце - самые "важные".

Но это было отступление, погнали дальше, проверять mysql


Подключаемся к базе через PhpMyAdmin

Запускаем docker-compose up --build. Снова придется подождать, потому что докеру нужно выкачать 2 новых образа. Ждем, дожидаемся успеха и открываем http://w-shop.test:8001. Видим привычную форму входа в PhpMyAdmin. Вбиваем логин/пароль root/root и входим в интерфейс базы. Но здесь нас может ждать сюрприз.

У меня получилось так - я успешно зашел в PhpMyAdmin один раз, поковырялся там, создал базу и вышел. А вот второй раз зайти уже не смог. Вбиваю root/root, а в ответ mysql not connect или что-то такое.

Полез гуглить. Нашел, что в этом случае нужно из под рута выполнить такой запрос


    ALTER USER root IDENTIFIED WITH mysql_native_password BY 'PASSWORD';

PASSWORD в смысле root - наш пароль рута. Это понятно, а как войти-то в базу, раз PhpMyAdmin не пускает? Нужно лезть руками, в консоли, по хакерски. mysql -uroot -proot и вот это вот. Тонкость в том, что нужно сначала зайти в контейнер mysql, а уже потом в саму базу mysql. Давайте вспомним статью по основам докера, которую я рекомендовал в самом начале.

Чтобы попасть в контейнер, нужно узнать его id. Набиваем docker ps и видим примерно такой список

webdevkin. docker-ps

Id контейнера mysql - 3825c5051ee9. Давайте заглянем в него


    docker exec -it 3825c5051ee9 bash

Мы зашли в контейнер и запустили в нем команду bash. Ключ -it нужен, чтобы создать интерактивный терминал. Проще говоря, мы попали в консоль контейнера. Здесь уже привычнее. Подключаемся к базе рутом и выполняем нагугленный запрос


    mysql -uroot -proot
    ALTER USER root IDENTIFIED WITH mysql_native_password BY 'root';
    exit // выходим из базы данных
    exit // выходим из контейнера mysql

После этого все заработало и PhpMyAdmin стал пускать без ограничений.


Создаем базу данных интернет-магазина

По фэншую мы должны были создать базу и накатить sql-дамп через докер. Запустить команду mysql -uroot -proot < dump.sql. Но предварительно проверив, существуют ли нужные таблицы, чтобы не перезатереть созданные ранее. Уверен, что докер это умеет, но я не осилил, простите дурака. Как осилю, расскажу, скорее всего, можно создать Dockerfile по аналогии с php и добавить в блок RUN такую команду


    mysql -uroot -proot < ./www/w-shop.test/sql/dump.sql. 

Но пока давайте заведем базу руками.

Заходим в PhpMyAdmin, создаем там базу webdevkin и импортируем данные. Если у вас магазин уже развернут, то можете снять дамп со своей базы. Если же нет, то берите из исходников - файл www/w-shop.test/sql/dump.sql. После это у вас получится такая структура

webdevkin. docker, таблицы mysql

Там уже забиты товары, бренды и категории. Вроде бы все, можно открывать магазин. Открываем страницу каталога с фильтрами - http://w-shop.test:8000/catalog.html, ан нет - крутится спиннер и товары не подгружаются. Разбираемся, в чем дело. С бекенда приходит страшный ворнинг


    Warning: mysqli::query(): Couldn't fetch mysqli in /var/www/w-shop.test/scripts/catalog.php on line 16

Дело в том, что мы не можем подключиться к mysql. Посмотрим на файл www/w-shop.test/scripts/catalog.php, самое начало, где объявляем константы с настройками базы. Вот строка


    define('DB_HOST', 'localhost');

Фишка в том, что нам нужно подключаться не к localhost, а к mysql - название контейнера в docker-compose. Поменяем в константе DB_HOST значение localhost на mysql и все заработает. Благодаря качественному проектированию у нас подключение к базе разбросано по всем скриптам, поэтому придется это делать в четырех местах: catalog.php, common.php, compare.php и order.php.

Если вы работаете с магазином, то надеюсь, вместо дублирования настроек уже подключаете common.php и используете функцию connectDB оттуда. Если нет, то самое время это сделать :-)

Вот теперь наш магазин работает. Надеюсь, мы все сделали правильно по ходу урока, а я ничего не пропустил. Ну и вы всегда можете скачать исходники.


Выводы. Преимущества докера

Давайте сведем воедино найденные плюсы докера.

1. Не нужно ставить тонну всего
В рабочей системе нет nginx, php, mysql, nodejs. Мы можем поставить любые базы и приложения: монгу, редис, postgresql, sphinx, nodejs и черта лысого, но все это будет закрыто в контейнерах и не засорит основую ОС.

2. Изолированность
Ничего из того, что мы ставим, не поломает нам систему. Если что-то криво настроено, контейнер просто не запустится. Если поломается песочница, то можно всегда ее снести и ставить заново за считанные секунды.

3. Кроссплатформенность и универсальность
Больше нет нужды переживать, в какой ОС вы работаете. Можно днем работать за системником с линуксом, а вечерами за ноутбуком с macOS или виндой.

4. Гибкость, легко менять версии и технологии
Легко проверить приложение на разных версиях php, mysql и других приложений.

5. Удобство разворачивания
Эту тему я не затрагивал, но докер хорош не только в локальной разработке, но при разворачивании сервиса на продакшене. Я даже слышал мнение, что докер - это в первую очередь поставка, а не разработка. Но оставим этот срач экспертам. Нас же, программистов, больше интересует удобство разработки. Например, мне совершенно не нравится ковыряться в десятке микросервисов, чтобы запустить какой-то проект. И докер в этом плане отличное решение.

6. И наконец, докер везде
Про него говорят, пишут, ставят в вакансии между аджайлом и гитом. Такое чувство, что скоро без докера не возьмут даже в сантехники. Я уже упоминал, что считаю, докер скоро станет таким же стандартом, как и гит. Это только мои догадки, но пока все к этому идет.


Минусы докера

В нем нужно разобраться. Да, вот так банально. Докер - это не самая простая тема, с которой мы сталкивались, и некоторые вещи приходится прямо вкуривать. Я прочитал десятки статей, попробовал несколько туториалов и на работе поюзал два проекта с докером. И только тогда начал хоть немного соображать, что в нем происходит. А это ведь только самое простое, сколько еще предстоит изучить и понять! Сложно. Но интересно.


Зачем изучать докер? Мне и без него хорошо

У каждого своя ситуация, я расскажу про свою. Недавно думал насчет изучения новых технологий и вывел для себя 5 стадий работы с ними.

1. не знаю и знать не хочу
2. чот все знают, а я нет, ну и фиг с ними
3. может все-таки посмотреть, вроде прикольно
4. нормальная рабочая тема, надо пользоваться
5. как я без этого жил раньше

Пока что я застрял между 3 и 4 стадией, но надеюсь, дойду до конца. Все 5 стадий мы проходим, если технология действительно хороша и полезна. Если же это очередная распиаренная хрень, то хорошо если доходим до третьей стадии "может, посмотреть?". А то и вообще до второй или даже первой. Почему-то кажется, что это не про докер, и он стоит того, чтобы с ним заморочиться.

Ровно так у меня получилось с гитом - как я перестал бояться и полюбил гит


Что дальше будет с докером в блоге

У меня нет в планах строить серию постов по докеру по примеру уроков vue. Тем более, сначала нужно получше разобраться. Скорее всего, я буду понемножку осваивать докер, заверну админку магазина, проект на vuejs и дальше буду пробовать еще что-то. Когда соберусь написать о полнотекстовом поиске sphinx или о работе с монго в php, то пожалуй, буду разворачивать это все в докере. Но постараюсь без фанатизма. Поживем - увидим.


Ссылки

Docker - самый простой и понятный туториал
Установка LEMP с помощью Docker'а
Демо интернет-магазина
Исходники всех проектов из статьи

На этом все. Всем удачи и до встречи.

Исходники
Заходите в группу в контакте - https://vk.com/webdevkin
Анонсы статей, обсуждения интернет-магазинов, vue, фронтенда, php, гита.
Истории из жизни айти и обсуждение кода.
Как Вам статья? Оцените!