Контейнеризация — способ запускать приложение со всеми зависимостями в определённой среде, которая не влияет на операционную систему. Таким образом ни одно приложение не зависит ни от другого приложения, ни от глобального окружения.
Если на вашем сервере, например, установлен php версии 8.1, но по каким-то причинам нужно запустить старый проект, вы можете пойти двумя путями. Первое, что приходит в голову — настроить ещё один сервер, «развернуть» на нём веб-окружение с php версии 5.6, обновить все пакеты и зависимости, где нужно — переписать код. Но есть способ проще: достаточно создать контейнер, окружение которого не будет влиять на всю остальную систему.
Приведём ещё один пример. Допустим, вы разрабатываете веб-сайт, который посещают люди. Но ваш сервер постепенно выходит из строя и нуждается в обновлении. Вам нужно перенести проект на другую машину, при этом сохранив бесперебойность работы сайта. Решение «в лоб» — построить другой сервер и скопировать файлы на него. При этом нужно обеспечить целостность базы данных, настройки серверного окружения и так далее. Непосредственно переход вы запланируете на какую-нибудь ночь, а после миграции будете чинить проект до самого утра. Однако, если бы ваш проект представлял собой контейнер, вы бы без проблем запустили его на любом сервере той же ОС.
Плюсы контейнеризации очевидны — помимо изолированности от основной системы, вы также обретаете мобильность, приложения можно переносить с одной машины на другую и запускать в разных системах, не беспокоясь, что ваш проект не запустится или сломает соседние.
Однако не стоит путать контейнеризацию с виртуализацией. Виртуализация — по сути отдельное устройство со своими оборудованием и операционной системой. Среда контейнера же запускается из ядра основной операционной системы, значит она может работать только в основной ОС. Контейнеры потребляют меньше ресурсов.
Для разработки и запуска таких контейнерных приложений используют Docker. Он позволяет автоматизировать процессы создания и развёртывания, упрощая задачи программистам и администраторам. Подробнее о Docker и контейнеризации можно почитать в нашей статье «Что такое Docker».
В этой статье мы рассмотрим контейнеризацию на примере стека LEMP: Linux, Nginx, MySQL и PHP. Для демонстрации работы с зависимостями веб-приложений установим Laravel.
Нам понадобится сервер с установленной ОС Ubuntu, для этого мы заказали облачное решение на timeweb.cloud и настроили пользователю привилегии sudo. Кроме того, на сервере должны быть установлены Docker и Docker Compose.
Laravel — бесплатный фреймворк с открытым исходным кодом. Его используют для создания архитектурно сложных веб-приложений. Загрузим в домашнюю директорию из GitHub актуальную версию:
cd ~
git clone https://github.com/laravel/laravel.git
Смонтируем образ composer
в каталог проекта Laravel, поскольку крайне не рекомендуется устанавливать Composer глобально и запускать из-под суперпользователя:
cd laravel
docker run -v $PWD:/app composer install
Этой командой мы создаём виртуальный контейнер, который будет привязан к текущему каталогу; всё, что находится внутри каталога laravel
, скопировано в контейнер. Каталог vendor
, который создал менеджер зависимостей Composer в рамках команды install
, также скопирован в текущий каталог, то есть в laravel
.
Затем изменим права на каталог так, чтобы к нему был доступ у обычного пользователя, поскольку нам понадобится запускать процессы в контейнере без root-привилегий.
sudo chown -R username:username ~/laravel
Теперь можно приступать к работе со службами с помощью Docker Compose.
Для решения задач, связанных с развёртыванием проектов, которые используют несколько сервисов, разработан инструмент Docker Compose. Если Docker применяется для управления отдельными контейнерами, то Docker Compose позволяет работать с приложениями со сложной архитектурой и множеством зависимостей.
Для настройки приложения Laravel создадим файл docker-compose.yml
и определим необходимые службы — веб-сервер и базу данных.
nano ~/laravel/docker-compose.yml
version: '3'
services:
# PHP Service
app:
build:
context: .
dockerfile: Dockerfile
image: php:8.1
container_name: app
restart: unless-stopped
tty: true
environment:
SERVICE_NAME: app
SERVICE_TAGS: dev
working_dir: /var/www
volumes:
- ./laravel/php/local.ini:/usr/local/etc/php/conf.d/local.ini
networks:
- app-network
# Nginx Service
webserver:
image: nginx:alpine
container_name: webserver
restart: unless-stopped
tty: true
ports:
- "80:80"
- "443:443"
volumes:
- /home/username/laravel:/var/www
- ./nginx/conf.d:/etc/nginx/conf.d
networks:
- app-network
# MySQL Service
db:
image: mysql:5.7.22
volumes:
- ./mysql:/var/lib/mysql
container_name: db
restart: unless-stopped
tty: true
ports:
- "3306:3306"
environment:
SERVICE_TAGS: dev
SERVICE_NAME: mysql
MYSQL_ROOT_PASSWORD: p@$$w0rd
networks:
- app-network
# Docker Networks
networks:
app-network:
driver: bridge
Подробно разберём некоторые ключевые моменты в этом файле:
/var/www
.laravel
, пароль (в нашем случае — p@$$w0rd
) и порт.Если Docker Hub недоступен, можно использовать наш бесплатный прокси, который возобновляет этот доступ.
Обратите внимание, что если вы не укажете имя контейнера container_name
, Docker сделает это за вас. При этом он присвоит контейнерам имя исторической личности и случайного слова — это значительно усложнит администрирование вашего приложения.
Для того чтобы организовать взаимодействие между контейнерами, подключим службы к соединительной сети app-network
. Так приложения из разных сетей не смогут взаимодействовать друг с другом — это значительно повысит безопасность проекта.
Далее приступим к настройке основных служб.
kube
С помощью специального файла Dockerfile можно указать, как собирать образы, на основе которых создаётся контейнер. Этот файл содержит инструкции и аргументы, которые при работе с Docker обрабатываются сверху вниз.
Наш Dockerfile расположим в корне проекта:
nano ~/laravel/Dockerfile
FROM php:8.1-fpm
# Устанавливаем рабочую директорию
WORKDIR /var/www
# Копируем composer.lock и composer.json
COPY composer.lock composer.json /var/www/
# Устанавливаем зависимости
RUN apt-get update && apt-get install -y \
build-essential \
libpng-dev \
libjpeg62-turbo-dev \
libfreetype6-dev \
locales \
zip \
jpegoptim optipng pngquant gifsicle \
vim \
unzip \
git \
curl \
libpq-dev \
libonig-dev \
libzip-dev && \
docker-php-ext-configure gd --with-freetype --with-jpeg && \
docker-php-ext-install pdo_mysql mbstring zip exif pcntl gd && \
apt-get clean && rm -rf /var/lib/apt/lists/*
# Загружаем актуальную версию Composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
# Создаём пользователя и группу www для приложения Laravel
RUN groupadd -g 1000 www
RUN useradd -u 1000 -ms /bin/bash -g www www
# Копируем содержимое текущего каталога в рабочую директорию
COPY . /var/www
COPY --chown=www:www . /var/www
# Меняем пользователя на www
USER www
# В контейнере открываем 9000 порт и запускаем сервер php-fpm
EXPOSE 9000CMD ["php-fpm"]
В первой строке мы указываем, что нужно создать образ PHP-FPM версии 8.1. Кроме того, установим нужные для работы приложения Laravel пакеты: pdo_mysql
, mbstring
, imagick
и composer
.
В качестве аргументов директивы RUN
укажем команды для установки и обновления зависимостей. А также создадим пользователя и группу www
для работы с приложением Laravel. Так мы снизим риски при запуске контейнера Docker, поскольку иначе они бы запускались с правами суперпользователя.
С помощью Workdir
укажем рабочий каталог /var/www
и зададим необходимые права для нового пользователя www
, воспользовавшись командой COPY
. Директивой EXPOSE
откроем 9000 порт для сервера php-fpm
.
После создания контейнера нужно запустить php-fpm
сервер, для этого используем команду CMD
.
Чаще всего для веб-приложений достаточно основных файлов конфигурации служб. В рамках нашего примера мы всё же переопределим базовые настройки для демонстрации возможностей контейнеризации.
После того, как мы определили конфигурацию контейнера, нужно настроить PHP для обработки входящих запросов NGINX. Для начала нужно создать файл local.ini
, путь к нему мы указывали в файле docker-compose.yml
— /usr/local/etc/php/conf.d/local.ini
. Если мы не создадим этот файл, контейнер будет использовать стандартный файл конфигурации PHP: php.ini
.
В директории приложения laravel
создаём каталог php
и файл local.ini
в нём:
mkdir ~/laravel/php
nano ~/laravel/php/local.ini
В этот файл вставим директиву для отображения всех ошибок:
error_reporting = E_ALL
Затем файл можно сохранить и закрыть. Когда мы приступим к активной работе над приложением, сможем добавить любые параметры конфигурации для переопределения стандартных.
В нашем примере мы используем NGINX в качестве прокси-сервера. Это значит, что все входящие запросы будут асинхронно передаваться на бэкенд (в нашем случае — PHP-FPM).
По аналогии с настройкой PHP мы также создадим в каталоге laravel
файл /nginx/conf.d/app.conf
:
mkdir -p ~/laravel/nginx/conf.d
nano ~/laravel/nginx/conf.d/app.conf
Внутри файла определим конфигурацию прокси-сервера:
server {
listen 80;
index index.php index.html;
error_log /var/log/nginx/error.log;
access_log /var/log/nginx/access.log;
root /var/www/public;
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass app:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
location / {
try_files $uri $uri/ /index.php?$query_string;
gzip_static on;
}
}
Внутри блока server
используем следующие директивы:
Listen
— порт, на котором сервер будет отлавливать запросы. Index
— индексные файлы веб-приложения. Error_log
, access_log
— файлы для записи журналов ошибок и запросов к серверу. root
— корневой каталог веб-приложения.Внутри первого блока location
определим настройки PHP. В частности, укажем, что служба app
прослушивает 9000 порт. Директива fastcgi_pass
указывает, что сервер PHP-FPM должен выполнять прослушивание через сокет TCP. Второй блок location
задаёт правила перенаправления всех запросов на индексный файл index.php
с сохранением параметров.
Настройка MySQL осуществляется аналогично конфигурации веб-сервера и PHP. Сначала мы создадим файл в каталоге, который указали внутри файла docker-compose.yml
:
mkdir ~/laravel/mysql
nano ~/laravel/mysql/my.cnf
В этом файле определим общие правила журналирования:
[mysqld]
general_log = 1
general_log_file = /var/lib/mysql/general.log
После того, как мы определили конфигурацию для основных служб, можно запускать контейнеры. Но перед этим укажем настройки приложения Laravel в специальном файле .env
. Внутри каталога Laravel уже есть пример такого файла, который поставляется в дистрибутиве — .env.example
. Копируем его и указываем свои настройки там, где это нужно:
cd ~/laravel
cp .env.example .env
nano .env
Основная настройка для приложения — доступ для подключения к базе данных и адрес сервера. В файле найдём строку APP_URL
и укажем домен или IP-адрес нашего сайта, далее в блоке DB_CONNECTION
укажем наши данные:
APP_URL=http://domain.com
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=p@$$w0rd
MYSQL_ROOT_PASSWORD=p@$$w0rd
Когда все настройки будут определены, нужно выполнить всего одну команду для запуска контейнеров, служб и подключений:
docker-compose up -d
При первом вызове Docker будет загружать в систему образы, поэтому запуск может занять некоторое время. После сохранения образов на локальной машине Docker Compose создаст наши контейнеры. При этом, чтобы обеспечить их работу в фоновом режиме, мы должны использовать флаг -d
.
Вы можете проверить корректность запуска всех процессов с помощью команды docker ps
. Она отобразит список контейнеров, их уникальные идентификаторы, имена, образы и статусы.
Для корректной работы приложения Laravel мы должны сгенерировать ключ APP_KEY
в .env-файл. Для этого воспользуемся docker-compose exec
, эта утилита позволяет выполнять команды в конкретных контейнерах:
docker-compose exec app php artisan key:generate
… и сохраним настройки в кэш для ускорения загрузки:
docker-compose exec app php artisan config:cache
При кэшировании настройки будут загружены в файл /var/www/bootstrap/cache/config.php
.
Теперь, если ввести IP-адрес вашего сервера в адресную строку браузера, вы увидите стартовую страницу Laravel.
Только после того, как мы запустили контейнеры, можно приступить к созданию пользователя и базы данных для приложения Laravel.
По умолчанию MySQL создаётся только одна учётная запись — root. Но использовать её в веб-среде небезопасно, поскольку root-пользователь обладает неограниченными привилегиями.
Воспользуемся командой docker-compose exec
для того, чтобы перейти в интерактивную оболочку bash
в контейнере db
:
docker-compose exec db bash
Далее — уже внутри оболочки — нужно войти в учётную запись root:
mysql -u root -p
Поскольку при создании файла docker-compose.yml
мы определили базу данных laravel
, она уже создана. Убедимся в этом:
show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| laravel |
| mysql |
| performance_schema |
| sys |
+--------------------+
5 rows in set (0.01 sec)
Создаём учётную запись laravel
с доступом только к этой базе данных. Пароль нужно указать такой же, как и в файле .env
:
GRANT ALL ON laravel.* TO 'laravel'@'%' IDENTIFIED BY 'p@$$w0rd';
Обновляем привилегии, закрываем MySQL и выходим из контейнера:
mysql> FLUSH PRIVILEGES;
mysql> EXIT;
# exit
В этой статье мы рассмотрели преимущества использования контейнеризации в работе с веб-приложениями. Среди основных плюсов — независимость приложений друг от друга и от сервера, на котором они находятся.
Для упрощения работы с контейнерами использовали Docker Compose, который позволяет создавать группу контейнеров Docker, а не запускать каждый по отдельности. В качестве демонстрации простоты работы с контейнерами создали простое Laravel-приложение, используя стек LEMP.
В официальном канале Timeweb Cloud собрали комьюнити из специалистов, которые говорят про IT-тренды, делятся полезными инструкциями и даже приглашают к себе работать.
docker-compose up -d Building app Step 1/4 : FROM php:8.1-fpm ---> 17af27b15bfd Step 2/4 : WORKDIR /var/www ---> Using cache ---> 9ac982377b12 Step 3/4 : COPY composer.lock composer.json /var/www ERROR: Service 'app' failed to build: When using COPY with more than one source file, the destination must be a directory and end with a / . Поправьте блок где Копируем composer.lock и composer.json. Видимо в нем проблема. В чем моя ошибка? делал как нужно
Добрый день!
Спасибо за замечание! Верстку поправили. Ошибка могла возникать из-за отсутствия слэша в конце строки «COPY composer.lock composer.json /var/www/»
Здравствуйте.
Нет никаких упоменаний в Вашем примере файла docker-compose.yml о файле local.ini.
Спасибо за внимательность! Добавили блок про
local.ini
вdocker-compose.yml
.Здравствуйте!
Добрый день! Инструкция не подойдет для сервера Apache — она написана под стек LEMP, в котором в качестве веб-сервера используется Nginx.
Указанный путь
WORKDIR /var/www
задействован в качествеDOCUMENT_ROOT
для приложения Laravel, которое размещается в контейнере.Директива
FROM
при использовании Dockerfile задаёт родительский образ, из которого будет создан контейнер. Конкретно в данном примере директиваFROM
задействует образ Docker с PHP 8.1, которое работает в режиме FPM.Порт для Docker-контейнера может быть переопределён в зависимости от необходимой задачи и используемого приложения. В документации Докера описан запуск Docker-контейнера с использованием порта 8089. В нашей же инструкции используются порты 80, 443, 3306 и 9000.
На этапе "Ключ приложения Laravel" получаю ошибку:
Что я делаю не так? Под каким пользователем надо это делать?
Добрый день! Нам не удалось воспроизвести ошибку 🤔
даже после долгой настройки этот код не работает
Спасибо за сигнал, перепроверили инструкцию и доработали её 💙
Какой-то не юзабельный код. А нельзя было указать и пути к конфигам в docker-compose.yml?
Спасибо, что подметили, перепроверили статью и исправили недочёты 😉