Перейти к содержимому

Как развернуть докер контейнер

  • автор:

Краткое руководство. Развертывание экземпляра контейнера в Azure с помощью Docker CLI

Служба «Экземпляры контейнеров Azure» позволяет легко и быстро запускать бессерверные контейнеры Docker в Azure. Выполняйте развертывание в экземпляр контейнера по запросу, если разрабатываете облачные приложения и хотите легко переходить с локальной разработки на облачное развертывание.

В этом кратком руководстве вы используете собственные команды Docker CLI, чтобы развернуть контейнер Docker и сделать его приложение доступным в Экземплярах контейнеров. Эта возможность предоставляется благодаря интеграции Docker и Azure. Через несколько секунд после выполнения команды docker run вы можете перейти к приложению, которое выполняется в контейнере:

Приложение, развернутое с помощью службы

Если у вас еще нет подписки Azure, создайте бесплатную учетную запись, прежде чем начинать работу.

Для целей этого краткого руководства вам понадобится Docker Desktop версии 2.3.0.5 или более поздней для Windows или macOS. Или же установите интерфейс командной строки интеграции Docker ACI для Linux.

Поддерживаются не все функции Экземпляров контейнеров Azure. Предоставьте отзыв об интеграции Docker-Azure, создав вопрос в репозитории GitHub для aci-integration-beta.

Создание контекста Azure

Чтобы использовать команды Docker для запуска контейнеров в службе «Экземпляры контейнеров Azure», сначала войдите в Azure:

docker login azure 

При появлении запроса введите или выберите свои учетные данные Azure.

Создайте контекст ACI, выполнив docker context create aci . Этот контекст связывает Docker с подпиской Azure и группой ресурсов, чтобы вы могли создавать экземпляры контейнеров и управлять ими. Например, чтобы создать контекст с именем myacicontext:

docker context create aci myacicontext 

При появлении запроса выберите идентификатор подписки Azure, а затем существующую группу ресурсов или создайте новую. Если вы выбираете создание новой группы ресурсов, ее имя будет задано системой. Экземпляры контейнеров Azure, как и все ресурсы Azure, должны быть развернуты в группе ресурсов. Группы ресурсов позволяют организовать соответствующие ресурсы Azure и управлять ими.

Запустите docker context ls , чтобы убедиться, что вы добавили контекст ACI в контексты Docker:

docker context ls 

Создание контейнера

Создав контекст Docker, можно приступить к созданию контейнера в Azure. В этом кратком руководстве используется общедоступный образ mcr.microsoft.com/azuredocs/aci-helloworld . Этот образ содержит небольшое веб-приложение, написанное на Node.js, которое обслуживает статические HTML-страницы.

Сначала перейдите в контекст ACI. В этом контексте выполняются все последующие команды Docker.

docker context use myacicontext 

Выполните команду docker run , чтобы создать экземпляр контейнера Azure с портом 80, доступным в Интернете.

docker run -p 80:80 mcr.microsoft.com/azuredocs/aci-helloworld 

Пример выходных данных при успешном развертывании:

[+] Running 2/2 ⠿ hungry-kirch Created 5.1s ⠿ single--container--aci Done 11.3s hungry-kirch 

Запустите docker ps , чтобы получить сведения о выполняющемся контейнере и общедоступном IP-адресе:

docker ps 

В примере выходных данных показан общедоступный IP-адрес, в данном случае 52.230.225.232:

CONTAINER ID IMAGE COMMAND STATUS PORTS hungry-kirch mcr.microsoft.com/azuredocs/aci-helloworld Running 52.230.225.232:80->80/tcp 

Затем перейдите по IP-адресу в браузере. Если появится примерно такая веб-страница, поздравляем! Вы успешно развернули приложение, выполняющееся в контейнере Docker в Azure.

Приложение, развернутое с помощью службы

Извлечение журналов контейнера

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

Например, выполните команду docker logs , чтобы просмотреть журналы контейнера hungry-kirch в контексте ACI:

docker logs hungry-kirch 

В выходных данных будут содержаться журналы для контейнера, а также должны отобразиться запросы HTTP GET, созданные при просмотре приложения в браузере.

listening on port 80 ::ffff:10.240.255.55 - - [07/Jul/2020:17:43:53 +0000] "GET / HTTP/1.1" 304 - "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36" ::ffff:10.240.255.55 - - [07/Jul/2020:17:44:36 +0000] "GET / HTTP/1.1" 304 - "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36" ::ffff:10.240.255.55 - - [07/Jul/2020:17:44:36 +0000] "GET / HTTP/1.1" 304 - "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36" 

Очистка ресурсов

Завершив работу с контейнером, запустите docker rm , чтобы удалить его. Эта команда останавливает и удаляет экземпляр контейнера Azure.

docker rm hungry-kirch 

Дальнейшие действия

В этом кратком руководстве вы создали экземпляр контейнера Azure из общедоступного образа, используя интеграцию между Docker и Azure. Дополнительные сведения о сценариях интеграции см. в документации по Docker.

Кроме того, вы можете использовать расширение Docker для Visual Studio Code, чтобы получить доступ к интегрированному интерфейсу для разработки, запуска контейнеров, образов и контекстов, а также управления ими.

Чтобы использовать средства Azure для создания экземпляров контейнеров и управления ими, ознакомьтесь с другими краткими руководствами об использовании Azure CLI, Azure PowerShell, портала Azure и шаблонов Azure Resource Manager.

Если вы хотите использовать Docker Compose, чтобы определить и запустить многоконтейнерное приложение локально, а затем переключиться на Экземпляры контейнеров Azure, перейдите к следующему учебнику.

Гайд по Docker: что это такое, зачем его использовать и как с ним работать

Гайд по Docker: что это такое, зачем его использовать и как с ним работать главное изображение

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

Разработчик узнаёт, что сайт компании работает с помощью веб-сервера Nginx, менеджера процессов PHP-FPM и системы управления базами данных Postgres. Теперь программист ищет нужную страницу. Поиск выглядит так:

  1. Разработчик вводит в браузере адрес сайта
  2. Браузер запрашивает HTML-страницу с котиками по указанному адресу
  3. HTTP-сервер Nginx принимает запрос и делегирует создание страницы PHP-FPM
  4. PHP-FPM запрашивает данные о котиках из базы Postgres, строит HTML-страницу и отдает обратно его серверу Nginx, а тот — клиенту-браузеру
  5. Разработчик видит страницу с котиками.

Свое первое задание разработчик выполняет на компьютере тимлида, где уже установлен Nginx, PHP-FPM и Postgres. На следующий день ему выдают новый компьютер, на котором этих программ нет.

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

  • Устанавливает Nginx
  • Устанавливает PHP-FPM и все нужные расширения
  • Настраивает совместную работу Nginx и PHP-FPM
  • Устанавливает Postgres, создает пользователей, нужные базы и схемы.

Установка идет долго: приходится ждать, пока сначала установится одна программа, потом другая. Сложности добавляет и то, что вся его команда работает над проектом на разных операционных системах: одни на macOS, а другие на Ubuntu или Windows.

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

Бесплатные курсы по программированию в Хекслете

  • Освойте азы современных языков программирования
  • Изучите работу с Git и командной строкой
  • Выберите себе профессию или улучшите навыки

Что такое Docker

Docker — это популярная программа, в основе которой лежит технология контейнеризации. Docker позволяет запускать Docker-контейнеры с приложениями из заранее заготовленных шаблонов — Docker-образов (или по-другому Docker images).

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

Простыми словами контейнер — это некая изолированная песочница для запуска ваших приложений.

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

Что еще может делать Docker:

  • Управлять изолированными приложениями
  • Ускорять и автоматизировать развертывание приложений
  • Доставлять приложения до серверов
  • Масштабировать приложения
  • Запускать на одном компьютере разные версии одной программы.

Как работает Docker

Концепцию программы легче понять на практике. Сначала установим на компьютер Docker и запустим HTTP-сервер Nginx. Для этого введем следующую команду:

docker run -p 8080:80 nginx:latest

Далее откроем браузер и забьем в адресную строку: 127.0.0.1:8080 . Откроется страница приветствия Nginx.

Теперь разберемся подробнее, что происходит, когда мы вводим команду docker run -p 8080:80 nginx:latest . Она выполняет следующее:

  1. Скачивает docker-образ — шаблон для создания Docker-контейнера — nginx:latest из публичного репозитория Docker Hub (если его не скачивали ранее). Docker-образ содержит все необходимое для запуска приложения: код, среду выполнения, библиотеки, переменные окружения и файлы конфигурации. На странице Nginx в Docker Hub можно найти Docker-образ nginx:latest , где latest — это тег (метка, снимок), который ссылается на самый свежий docker-образ и описывает его.
  2. Запускает Docker-контейнер с помощью Docker-образа
  3. Пробрасывает порт. Ранее мы объясняли, что процессы в Docker-контейнерах запускаются в изоляции от ОС, то есть все порты между ОС и Docker-контейнером закрыты. Для того, чтобы мы смогли обратиться к Nginx, нужно пробросить порт, что и делает опция -p 8080:80 , где 80 — это порт Nginx внутри контейнера, а 8080 — порт в локальной сети ОС.

Как создать свой Docker-образ

Теперь попробуем создать свой Docker-образ, взяв за основу nginx:latest . Docker умеет создавать Docker-образ, читая текстовые команды, которые записаны в файл Dockerfile.

Вот пример простейшего Dockerfile:

FROM nginx:latest RUN echo 'Hi, we are building a custom docker image from nginx:latest!' COPY nginx-custom-welcome-page.html /usr/share/nginx/html/index.html 

Команда FROM задает базовый (родительский) Docker-образ и всегда вызывается в первую очередь. Команда COPY копирует файлы в Docker-контейнер.

С помощью COPY можно заменить стандартную велком-страницу Nginx на такую страницу:

    Welcome to custom Nginx page!   

Узнать подробнее об этих и других командах Docker можно в официальной документации .

Теперь, когда мы разобрались, за что отвечают команды, создадим Docker-образ из Dockerfile:

$ docker build -t nginx_custom:latest -f /opt/src/docker-for-kids/dockerFiles/Nginx-custom/Dockerfile /opt/src/docker-for-kids Sending build context to Docker daemon 139.3kB Step 1/3 : FROM nginx:latest latest: Pulling from library/nginx 31b3f1ad4ce1: Pull complete fd42b079d0f8: Pull complete 30585fbbebc6: Pull complete 18f4ffdd25f4: Pull complete 9dc932c8fba2: Pull complete 600c24b8ba39: Pull complete Digest: sha256:0b970013351304af46f322da1263516b188318682b2ab1091862497591189ff1 Status: Downloaded newer image **for** nginx:latest ---**>** 2d389e545974 Step 2/3 : RUN echo 'Hi, we are building a custom docker image from nginx:latest!' ---**>** Running **in** 05ffd060061f Hi, we are building a custom docker image from nginx:latest! Removing intermediate container 05ffd060061f ---**>** 9ac62be4252a Step 3/3 : COPY nginx-custom-welcome-page.html /usr/share/nginx/html/index.html ---**>** 704121601a45 Successfully built 704121601a45 Successfully tagged nginx_custom:latest 

Поясним, какие команды мы использовали в этом коде:

  • -t nginx_custom:latest — это имя будущего Docker-образа, latest — это тег
  • -f /opt/src/docker-for-kids/dockerFiles/Nginx-custom/Dockerfile — путь до Dockerfile
  • /opt/src/docker-for-kids — директория, в контексте которой будет создан Docker-образ. Контекст — это все то, что доступно для команд из Dockerfile при сборке (билде) образа. Процесс создания Docker-образа может ссылаться на любой из файлов в контексте.

Теперь запускаем команду:

$ docker run -p 8080:80 Nginx_custom:latest

Что такое Docker Compose и как он работает

С ростом количества Docker-контейнеров их становится труднее поддерживать. Конфигурация каждого контейнера описывается в своем Dockerfile, и их нужно запускать отдельной командой. Это же касается сборки или пересборки контейнеров.

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

Для описания приложений используется YAML-файл.

version: '3' services: nginx: container_name: nginx-test # имя Docker-контейнера build: # создать Docker-образ из DockerFile context: . # путь, в контексте которого будет создан Docker-образ dockerfile: ./dockerFiles/nginx/Dockerfile # путь до Dockerfile, из которого будет собран Docker-образ ports: # проброс портов - "80:80" networks: # имя сети, к которой будет подключен Docker-контейнер - test-network depends_on: # эта программа будет запущена только после того, как запустится сервис под именем php-fpm - php-fpm volumes: # монтирование директорий, директория-на-хост-машине: директория-в-докере - ./:/var/www/hello.dev/ php-fpm: container_name: php-fpm-test build: context: . dockerfile: ./dockerFiles/php-fpm/Dockerfile networks: - test-network volumes: - ./:/var/www/hello.dev/ postgres: container_name: postgres-test image: postgres:14.1-alpine # тег Docker-образа из https://hub.docker.com/ environment: postgres_PASSWORD: mysecretpass # переменные окружения, которые использует Docker-контейнер networks: - test-network networks: # явно объявленные сети test-network: driver: bridge 

Если изображать этот код схематично, то описание приложения выглядит так:

Каждый сервис находится внутри Docker-контейнера. Точкой входа в приложение, как и в случае с тем разработчиком и веб-сайтом компании, является Nginx. Пользователи веб-сайта делают запросы к Nginx, у которого проброшен порт 80.

Разберем еще несколько команд, которые реализует Docker:

  1. network . Как мы объяснили ранее, каждое приложение в Docker-контейнере находится в изоляции. networks объединяет все Docker-контейнеры в одну сеть с именем test-network, и это позволяет обращаться к нужному контейнеру по его имени.
  2. volumes — это механизм для хранения данных вне Docker-контейнера, то есть в файловой системе нашей ОС. volumes решает проблему совместного использования файлов.

Все примеры, а также исходники Dockerfile можно взять из репозитория на GitHub.

Как создать простое веб-приложение с помощью Docker

Создадим простое веб-приложение, которое покажет нам сообщение об успешном подключении к базе данных. Вместо адреса базы данных используем host=postgres , такое же имя cервиса, как и в YAML-файле. Напомню, что эта возможность появилась благодаря общей сети test-network.

 try  $pdo = new \PDO("pgsql:host=postgres;dbname=postgres", 'postgres', 'mysecretpass'); echo "Подключение к базе данных установлено! 
"
; return; > catch (PDOException $exception) echo "Ошибка при подключении к базе данных
$exception->getMessage()>
"
; >

PDO — это интерфейс для доступа к базам данных в PHP. Подробнее об этом можно узнать в официальной документации .

Теперь, чтобы создать все Docker-образы и запустить Docker-контейнеры нужно выполнить:

Выполняем index.php и видим успешное соединение с базой данных.

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

Итог

Освоив Docker, разработчики могут разворачивать все необходимые им сервисы на каком угодно компьютере. Также эта программа — отличный инструмент для быстрой доставки до серверов, тестирования. Изучить Docker не так тяжело, как может показаться новичкам, но зато это умение значительно сэкономит их время на ручной установке софта. Почитать про Docker подробнее можно на официальном сайте .

Бесплатные курсы по программированию в Хекслете

  • Освойте азы современных языков программирования
  • Изучите работу с Git и командной строкой
  • Выберите себе профессию или улучшите навыки

Docker: как развернуть фуллстек-приложение и не поседеть

«Нам нужен DevOps!»
(самая популярная фраза в конце любого хакатона)

Сначала немного лирики.

Когда разработчик является отличным девопсом, умеющим развернуть своё детище на любой машине под любой OC, это плюс. Однако, если он вообще ничего не смыслит дальше своей IDE, это не минус — в конце концов, деньги ему платят за код, а не за умение его разворачивать. Узкий глубокий специалист на рынке ценится выше, чем средней квалификации «мастер на все руки». Для таких, как мы, «пользователей IDE», хорошие люди придумали Docker.

Принцип Docker следующий: «работает у меня — работает везде». Единственная программа, необходимая для деплоя копии Вашего приложения где угодно — это Docker. Если Вы запустили своё приложение в докере у себя на машине, оно гарантированно с тем же успехом запустится в любом другом докере. И ничего, кроме докера, устанавливать не нужно. У меня, к примеру, на виртуальном сервере даже Java не стоит.

Как работает Docker?

Docker создаёт образ виртуальной машины с установленными в ней приложениями. Дальше этот образ разворачивается как абсолютно автономная виртуальная машина. Запущенная копия образа называется «контейнер». Вы можете запустить на сервере любое количество образов, и каждый из них будет отдельной виртуальной машиной со своим окружением.

Что такое виртуальная машина? Это инкапсулированное место на сервере с операционкой, в которой установлены приложения. В любой операционке обычно крутится большое количество приложений, в нашей же находится одно.

Схему развёртывания контейнеров можно представить так:

Для каждого приложения мы создаём свой образ, а потом разворачиваем каждый контейнер отдельно. Также, можно положить все приложения в один образ и развернуть как один контейнер. Причём, чтобы не разворачивать каждый контейнер отдельно, мы можем использовать отдельную утилиту docker-compose, которая конфигурирует контейнеры и взаимосвязь между ними через отдельный файл. Тогда структура всего приложения может выглядеть так:

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

Впрочем, довольно лирики, давайте писать код. Мы напишем простейшее приложение на спринге и реакте, которое будет записывать наши обращения к фронту в базу данных, и поднимем всё это через Docker. Структура нашего приложения будет выглядеть так:

Реализовать такую структуру можно разными путями. Мы реализуем один из них. Мы создадим два образа, запустим из них два контейнера, причём, бэкенд будет подключаться к базе данных, которая установлена на конкретном сервере где-то в интернете (да, такие запросы к базе будут ходить не быстро, но нами движет не жажда оптимизации, а научный интерес).

Пост будет разбит на части:

0. Устанавливаем Docker.
1. Пишем приложения.
2. Собираем образы и запускаем контейнеры.
3. Собираем образы и запускаем контейнеры на удалённом сервере.
4. Решаем проблемы с сетью.

0. Устанавливаем Docker

Для того, чтобы установить Docker, нужно зайти на сайт и следовать тому, что там написано. При установка Docker на удалённом сервере будьте готовы к тому, что с серверами на OpenVZ Docker может не работать. Равно как могут быть проблемы, если у Вас не включён iptables. Желательно заводить сервер на KVM с iptables. Но это мои рекомендации. Если у Вас всё заработает и так, я буду рад, что Вы не потратили кучу времени на выяснение, почему не работает, как это пришлось сделать мне.

1. Пишем приложения

Напишем простое приложение с самым примитивным бэкендом на Spring Boot, очень простым фронтендом на ReactJS и базой данных MySQL. Приложение будет иметь Single-Page с одной-единственной кнопкой, которая будет записывать время нажатия по ней в базу данных.

Я рассчитываю на то, что Вы уже умеете писать приложения на буте, но если нет, Вы можете клонировать готовый проект. Все ссылки в конце статьи.

Backend на Spring Boot

plugins < id 'org.springframework.boot' version '2.1.4.RELEASE' id 'java' >apply plugin: 'io.spring.dependency-management' group = 'ru.xpendence' version = '0.0.2' sourceCompatibility = '1.8' repositories < mavenCentral() >dependencies
package ru.xpendence.rebounder.entity; import com.fasterxml.jackson.annotation.JsonFormat; import javax.persistence.*; import java.io.Serializable; import java.time.LocalDateTime; import java.util.Objects; /** * Author: Vyacheslav Chernyshov * Date: 14.04.19 * Time: 21:20 * e-mail: 2262288@gmail.com */ @Entity @Table(name = "request_logs") public class Log implements Serializable < private Long id; private LocalDateTime created; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) public Long getId() < return id; >@Column @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss.SSS") public LocalDateTime getCreated() < return created; >@PrePersist public void prePersist() < this.created = LocalDateTime.now(); >//setters, toString, equals, hashcode, constructors

LogController, который будет работать по упрощённой логике и сразу писать в базу данных. Сервис мы опускаем.

package ru.xpendence.rebounder.controller; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import ru.xpendence.rebounder.entity.Log; import ru.xpendence.rebounder.repository.LogRepository; import java.util.logging.Logger; /** * Author: Vyacheslav Chernyshov * Date: 14.04.19 * Time: 22:24 * e-mail: 2262288@gmail.com */ @RestController @RequestMapping("/log") public class LogController < private final static Logger LOG = Logger.getLogger(LogController.class.getName()); private final LogRepository repository; @Autowired public LogController(LogRepository repository) < this.repository = repository; >@GetMapping public ResponseEntity log() < Log log = repository.save(new Log()); LOG.info("saved new log: " + log.toString()); return ResponseEntity.ok(log); >> 

Всё, как мы видим, очень просто. По GET-запросу мы делаем запись в базу и возвращаем результат.

Файл настроек приложения обсудим отдельно. Их два.

spring: profiles: active: remote
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://my-remote-server-database:3306/rebounder_database?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC username: admin password: 12345 jpa: hibernate: ddl-auto: update show-sql: true properties: hibernate.dialect: org.hibernate.dialect.MySQL5Dialect server: port: 8099

Как это работает, Вы, наверняка, знаете, сначала Spring сканирует файл application.properties или application.yml — какой найдёт. В нём мы указываем одну-единственную настройку — какой профиль будем использовать. Обычно во время разработки у меня накапливается несколько профилей, и очень удобно их переключать при помощи дефолтного профиля. Далее Spring находит application.yml с нужным суффиксом и пользует его.

Мы указали датасорс, настройки JPA и, что важно, внешний порт нашего бэкенда.

Фронтенд на ReactJS

Фронтенд тоже можно посмотреть в проекте на git, а можно даже не смотреть, а клонировать и запустить.

Отдельную работу фронтенда можно проверить, скачав проект, перейдя в терминале в корневую папку проекта (туда, где лежит файл package.json) и выполнив последовательно две команды:

npm install // устанавливает в проект все необходимые зависимости, аналог maven npm start // запускает проект

Конечно, для этого Вам нужен установленный Node Package Manager (npm), и это тот самый трудный путь, которого мы избегаем при помощи Docker. Если Вы всё-таки запустили проект, Вы увидите следующее окошко:

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

export default class Api < _apiPath = 'http://localhost:8099'; _logUrl = '/log'; getResource = async () =>< const res = await fetch(`$$`); if (!res.ok) < throw new Error(`Could not fetch $` + `, received $`) > return await res.json(); >; >;

Фронтенд работает предсказуемо. Переходим по ссылке, дожидаемся ответа и выводим его на экран.

Стоит акцентировать внимание на следующих пунктах:

  1. Фронт открыт внешнему миру через порт 3000. Это порт по умолчанию для React.
  2. Бэк открыт по порту 8099. Мы его задали в настройках приложения.
  3. Бэк стучится к БД через внешний интернет.

2. Собираем образы и запускаем контейнеры

Структура нашей сборки будет следующая. Мы создадим два образа — фронтенд и бэкенд, которые будут общаться друг с другом через внешние порты. Для базы мы не будем создавать образ, мы установим её отдельно. Почему так? Почему для базы мы не создаём образ? У нас есть два приложения, которые постоянно изменяются и не хранят в себе данные. База данных хранит в себе данные, и это может быть результат нескольких месяцев работы приложения. Более того, к данной базе данных может обращаться не только наше бэкенд-приложение, но и многие другие — на то она и база данных, и мы не будем её постоянно пересобирать. Опять же, это возможность поработать с внешним API, чем, безусловно, является подключение к нашей БД.

Сборка front-end

Для запуска каждого приложения (будь то фронт или бэк) потребуется определённая последовательность действий. Для запуска приложения на React нам потребуется сделать следующее (при условии, что у нас уже есть Linux):

  1. Установить NodeJS.
  2. Скопировать приложение в определённую папку.
  3. Проинсталлировать необходимые пакеты (команда npm install).
  4. Запустить приложение командой npm start.
FROM node:alpine WORKDIR /usr/app/front EXPOSE 3000 COPY ./ ./ RUN npm install CMD ["npm", "start"]

Разберём, что означает каждая строчка.

FROM node:alpine

Этой строчкой мы даём понять докеру, что при запуске контейнера первым делом нужно будет скачать из репозитория Docker и установить NodeJS, причём, самую лёгкую (все самые лёгкие версии популярных фреймворков и библиотек в докере принято называть alpine).

WORKDIR /usr/app/front

В линуксе контейнера будут созданы те же стандартные папки, что и в других линуксах — /opt, /home, /etc, /usr и проч. Мы задаём рабочую директорию, с которой будем работать — /usr/app/front.

EXPOSE 3000

Открываем порт 3000. Дальнейшая связь с приложением, запущенным в контейнере, будет происходить через этот порт.

COPY ./ ./

Копируем содержимое исходного проекта в рабочую папку контейнера.

RUN npm install

Устанавливаем все пакеты, необходимые для запуска приложения.

CMD ["npm", "start"]

Запускаем приложение командой npm start.

Именно этот сценарий будет выполнен в нашем приложении при запуске контейнера.

Давайте сразу соберём фронт. Для этого нужно в терминале, находясь в корневой папке проекта (там, где находится Dockerfile), выполнить команду:

docker build -t rebounder-chain-frontend .

docker — вызов приложения docker, ну, это вы знаете.
build — сборка образа из целевых материалов.
-t — в дальнейшем, приложение будет доступно по тегу, указанному здесь. Можно не указывать, тогда Docker сгенерирует собственный тег, но отличить его от других будет невозможно.
. — указывает, что собирать проект нужно именно из текущей папки.

В результате, сборка должна закончиться текстом:

Step 7/7 : CMD ["npm", "start"] ---> Running in ee0e8a9066dc Removing intermediate container ee0e8a9066dc ---> b208c4184766 Successfully built b208c4184766 Successfully tagged rebounder-chain-frontend:latest

Если мы видим, что последний шаг выполнен и всё Successfull, значит, образ у нас есть. Мы можем проверить это, запустив его:

docker run -p 8080:3000 rebounder-chain-frontend

Смысл этой команды, я думаю, в целом понятен, за исключением записи -p 8080:3000.
docker run rebounder-chain-frontend — означает, что мы запускаем такой-то докер-образ, который мы обозвали rebounder-chain-frontend. Но такой контейнер не будет иметь выхода наружу, ему нужно задать порт. Именно команда ниже его задаёт. Мы помним, что наше React-приложение запускается на порте 3000. Команда -p 8080:3000 указывает докеру, что нужно взять порт 3000 и пробросить его на порт 8080 (который будет открыт). Таким образом, приложение, которое работает по порту 3000, будет открыто по порту 8080, и на локальной машине будет доступно именно по этому порту.

Итак, что мы видим при запуске команды выше: Mac-mini-Vaceslav:rebounder-chain-frontend xpendence$ docker run -p 8080:3000 rebounder-chain-frontend > rebounder-chain-frontend@0.1.0 start /usr/app/front > react-scripts start Starting the development server. Compiled successfully! You can now view rebounder-chain-frontend in the browser. Local: http://localhost:3000/ On Your Network: http://172.17.0.2:3000/ Note that the development build is not optimized. To create a production build, use npm run build.

Пусть вас не смущает запись

 Local: http://localhost:3000/ On Your Network: http://172.17.0.2:3000/

Так думает React. Он действительно доступен в пределах контейнера по порту 3000, но мы пробросили этот порт на порт 8080, и из контейнера приложение работает по порту 8080. Можете запустить приложение локально и проверить это.

Итак, у нас есть готовый контейнер с фронтенд-приложением, теперь давайте соберём бэкенд.

Сборка back-end.

Сценарий запуска приложения на Java существенно отличается от предыдущей сборки. Он состоит из следующих пунктов:

  1. Устанавливаем JVM.
  2. Собираем jar-архив.
  3. Запускаем его.
# back # устанавливаем самую лёгкую версию JVM FROM openjdk:8-jdk-alpine # указываем ярлык. Например, разработчика образа и проч. Необязательный пункт. LABEL maintainer="2262288@gmail.com" # указываем точку монтирования для внешних данных внутри контейнера (как мы помним, это Линукс) VOLUME /tmp # внешний порт, по которому наше приложение будет доступно извне EXPOSE 8099 # указываем, где в нашем приложении лежит джарник ARG JAR_FILE=build/libs/rebounder-chain-backend-0.0.2.jar # добавляем джарник в образ под именем rebounder-chain-backend.jar ADD $ rebounder-chain-backend.jar # команда запуска джарника ENTRYPOINT ["java","-jar","/rebounder-chain-backend.jar"]

Процесс сборки образа с включением джарника по некоторым пунктам напоминает таковой для нашего фронта.

Процесс сборки и запуска второго образа принципиально ничем не отличается от сборки и запуска первого.

docker build -t rebounder-chain-backend . docker run -p 8099:8099 rebounder-chain-backend

Теперь, если у Вас запущены оба контейнера, а бэкенд подключён к БД, всё заработает. Напоминаю, что подключение к БД из бэкенда Вы должны прописать сами, и оно должно работать через внешнюю сеть.

3. Собираем образы и запускаем контейнеры на удалённом сервере

Для того, чтобы всё заработало на удалённом сервере, нам необходимо, чтобы на нём был уже установлен Docker, после чего, достаточно запустить образы. Мы пойдём правильным путём и закоммитим наши образы в свой аккаунт в облаке Docker, после чего, они станут доступны из любой точки мира. Конечно, альтернатив данному подходу, как и всему, что описано в посте, предостаточно, но давайте ещё немного поднажмём и сделаем свою работу хорошо. Плохо, как говорил Андрей Миронов, мы всегда успеем сделать.

Создание аккаунта на хабе Docker

Первое, что Вам предстоит сделать — это обзавестись аккаунтом на хабе Docker. Для этого надо перейти на хаб и зарегаться. Это несложно.

Далее, нам нужно зайти в терминал и авторизоваться в Docker.

docker login

Вас попросят ввести логин и пароль. Если всё ок, в терминале появится уведомление, что Login Succeeded.

Коммит образов на Docker Hub

Далее, мы должны пометить наши образы тэгами и закоммитить их в хаб. Делается это командой по следующей схеме:

docker tag имя образа логин/имя_образа:версия

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

В моём случае, это выглядело так:

Мы можем проверить наличие данного образа в локальном репозитории при помощи команды:

Mac-mini-Vaceslav:rebounder-chain-backend xpendence$ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE xpendence/rebounder-chain-backend 0.0.2 c8f5b99e15a1 About an hour ago 141MB

Наш образ готов к коммиту. Коммитим:

docker push xpendence/rebounder-chain-backend:0.0.2

Должна появиться запись об успешном коммите.
Делаем то же самое с фронтендом:

docker tag rebounder-chain-frontend xpendence/rebounder-chain-frontend:0.0.1 docker push xpendence/rebounder-chain-frontend:0.0.1

Теперь, если мы зайдём на hub.docker.com, мы увидим два закоммиченных образа. Которые доступны откуда угодно.

Поздравляю. Нам осталось перейди к заключительной части нашей работы — запустить образы на удалённом сервере.

Запускаем образы на удалённом сервере

Теперь мы можем запустить наш образ на любой машине с Docker, выполнив всего одну строчку в терминале (в нашем случае, нам надо последовательно выполнить две строчки в разных терминалах — по одной на каждый образ).

docker run -p 8099:8099 xpendence/rebounder-chain-backend:0.0.2 docker run -p 8080:3000 xpendence/rebounder-chain-frontend:0.0.1

У такого запуска есть, правда, один минус. При закрытии терминала процесс завершится и приложение прекратит работу. Чтобы этого избежать, мы можем запустить приложение в «откреплённом» режиме:

docker run -d -p 8099:8099 xpendence/rebounder-chain-backend:0.0.2 docker run -d -p 8080:3000 xpendence/rebounder-chain-frontend:0.0.1

Теперь приложение не будет выдавать лог в терминал (это можно, опять же, настроить отдельно), но и при закрытии терминала оно не прекратит свою работу.

4. Решаем проблемы с сетью

Если Вы всё сделали правильно, Вас, возможно, ожидает самое большое разочарование на всём пути следования этому посту — вполне может так получиться, что ничего не работает. Например, у Вас всё великолепно собралось и заработало на локальной машине (как, например, у меня на маке), но при развёртывании на удалённом сервере контейнеры перестали друг друга видеть (как, например, у меня на удалённом сервере на Linux). В чём проблема? А проблема вот в чём, и я в начале о ней намекал. Как уже было сказано раньше, при запуске контейнера Docker создаёт отдельную виртуальную машину, накатывает туда Linux, и потом в этот Linux устанавливает приложение. Это значит, что условный localhost для запущенного контейнера ограничивается самим контейнером, и о существовании других сетей приложение не подозревает. Но нам нужно, чтобы:

а) контейнеры видели друг друга.
б) бэкенд видел базу данных.

Решения проблемы два.

1. Создание внутренней сети.
2. Вывод контейнеров на уровень хоста.

1. На уровне Docker можно создавать сети, причём, три из них в нём есть по умолчанию — bridge, none и host.

Bridge — это внутренняя сеть Docker, изолированная от сети хоста. Вы можете иметь доступ к контейнерам только через те порты, которые открываете при запуске контейнера командой -p. Можно создавать любое количество сетей типа bridge.

None — это отдельная сеть для конкретного контейнера.

Host — это сеть хоста. При выборе этой сети, Ваш контейнер полностью доступен через хост — здесь попросту не работает команда -p, и если Вы развернули контейнер в этой сети, то Вам незачем задавать внешний порт — контейнер доступен по своему внутреннему порту. Например, если в Dockerfile EXPOSE задан как 8090, именно через этот порт будет доступно приложение.

Поскольку нам нужно иметь доступ к базе данных сервера, мы воспользуемся последним способом и выложим контейнеры в сеть удалённого сервера.

Делается это очень просто, мы убираем из команды запуска контейнера упоминание о портах и и указываем сеть host:

docker run --net=host xpendence/rebounder-chain-frontend:0.0.8

Подключение к базе я указал

localhost:3306

Подключение фронта к бэку пришлось указать целиком, внешнее:

http://

Если вы пробрасываете внутренний порт на внешний, что часто бывает для удалённых серверов, то для базы нужно указывать внутренний порт, а для контейнера — внешний порт.

Если Вы хотите поэкспериментировать с подключениями, вы можете скачать и собрать проект, который я специально написал для тестирования соединения между контейнерами. Просто вводите необходимый адрес, жмёте Send и в режиме отладки смотрите, что прилетело обратно.

Заключение

Есть масса способов собрать и запустить образ Docker. Интересующимся советую изучить docker-compose. Здесь мы разобрали лишь один из способов работы с докером. Конечно, такой подход поначалу кажется не таким уж и простым. Но вот пример — в ходе написания поста у меня возникли с исходящими подключениями на удалённом сервере. И в процессе дебага мне пришлось несколько раз менять настройки подключения к БД. Вся сборка и деплой умещались у меня в набор 4 строчек, после ввода которых я видел результат на удалённом сервере. В режиме экстремального программирования Docker окажется незаменим.

Как и обещал, выкладываю исходники приложений:

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

Докер везде. Когда-то на него смотрели, как на очередную забаву неугомонных программистов, но докер оказался не таков. Если раньше в любой вакансии писали 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-compose

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

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

Рекомендую читать всем, но особенно тем, кто уже разбирался с 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-й винде тоже, но не пробовал. Я не знаю, какая у вас система, поэтому смотрите инструкции здесь.

Выбирайте версию 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

  • © Webdevkin
  • 2015 — пока не надоест
  • ВКонтакте
  • webdevkin@gmail.com
  • RSS-лента

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *