За последний год программное обеспечение для автоматизации развертывания в среде виртуализации на уровне операционной системы набирает большие обороты. Эта статья послужит новичкам в этой сфере примером, как нужно упаковывать свое приложение в Docker контейнеры.
В классическом виде, PHP приложение представляет из себя следующие составляющие:
В нашем примере мы будем использовать Nginx, PostgreSQL и PHP-FPM.
Для начала работы, нам потребуется Docker. Скачать его можно на официальном сайте Docker.
Docker создает образы на основе DockerFile файлов, в котором описывается функционал. Мы создадим 3 образа для наших составляющих.
В данном DockerFile мы создаем пользователя www-data с группой 82 и устанавливаем Nginx. Последняя строка COPY предполагает, что у вас конфигурация приложения лежит в папке config/website.conf. Она скопируется в /etc/nginx/conf.d/website.conf.
В этом образе, мы будем отталкиваться от образа postgres:9.5.2 и выполним команду для определения локали и языка.
Этот образ послужит нам основным образом для нашего приложения. Сначала мы устанавливаем все необходимое для PHP и PHP-FPM. Далее, мы копируем текущую папку приложения в /usr/src/app, где будет распологаться наше приложение. В самом конце мы запускаем PHP-FPM.
И так, у нас есть есть DockerFile'ы, на основе которых мы должны создать образы. Образы создаются очень просто. Достаточно выполнить следующие команды:
В дальнейшем советую добавить к этим командам --no-cache, чтобы каждый раз не компилировать составляющие.
Мы создаем образы, прикрепляем их к нашему аккаунту на Docker Hub. Теперь, нам нужно отправить наши образы на репозиторий в Docker Hub. Выполняем следующие команды:
Мы почти у цели! Нам осталось загрузить образы из репозитория и запустить их. Загружаем их с помощью следующих команд:
Осталось их запустить. Делается это так же просто.
Вуаля! Наше приложение запущено на Docker контейнерах. И тем не менее, всем читателям-новичкам я бы обязательно ознакомиться с документацией Docker.
Всем желаю успехов в освоении новых технологий!
В классическом виде, PHP приложение представляет из себя следующие составляющие:
- Веб-сервер
- СУБД
- PHP приложение
В нашем примере мы будем использовать Nginx, PostgreSQL и PHP-FPM.
1. Установка Docker
Для начала работы, нам потребуется Docker. Скачать его можно на официальном сайте Docker.
2. Создание образов
Docker создает образы на основе DockerFile файлов, в котором описывается функционал. Мы создадим 3 образа для наших составляющих.
DockerFileNginx
FROM nginx:mainline-alpine
RUN set -ex && addgroup -g 82 -S www-data && adduser -u 82 -D -S -G www-data www-data && mkdir -p /etc/pki/nginx/ && apk update && apk --no-cache add --update openssl && openssl dhparam -out /etc/pki/nginx/dhparams.pem 4096 && sed -i -e 's/user\s*nginx;/user www-data www-data;/g' /etc/nginx/nginx.conf && sed -i -e 's/worker_processes\s*1;/worker_processes auto;/g' /etc/nginx/nginx.conf && rm -rf /var/cache/apk/*
COPY config/website.conf /etc/nginx/conf.d/website.conf
В данном DockerFile мы создаем пользователя www-data с группой 82 и устанавливаем Nginx. Последняя строка COPY предполагает, что у вас конфигурация приложения лежит в папке config/website.conf. Она скопируется в /etc/nginx/conf.d/website.conf.
DockerFilePostgresql
FROM postgres:9.5.2
RUN localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8
ENV LANG en_US.utf8
В этом образе, мы будем отталкиваться от образа postgres:9.5.2 и выполним команду для определения локали и языка.
DockerFile
FROM alpine:edge
# Timezone
ENV TIMEZONE Europe/Moscow
ENV PHP_MEMORY_LIMIT 1024M
ENV MAX_UPLOAD 128M
ENV PHP_MAX_FILE_UPLOAD 128
ENV PHP_MAX_POST 128M</blockquote>
RUN set -ex && addgroup -g 82 -S www-data && adduser -u 82 -D -S -G www-data www-data && echo "@testing http://dl-4.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories && apk update && apk upgrade && apk add --update tzdata && cp /usr/share/zoneinfo/${TIMEZONE} /etc/localtime && echo "${TIMEZONE}" > /etc/timezone && apk --update add --no-cache php7-fpm@testing php7-mcrypt@testing php7-openssl@testing php7-json@testing php7-mysqli@testing php7-session@testing php7-gd@testing php7-xmlreader@testing php7-xmlrpc@testing php7-zip@testing php7-iconv@testing php7-curl@testing php7-zlib@testing php7@testing php7-ctype@testing php7-pgsql@testing php7-pdo_pgsql@testing bash rsync && sed -i -e "s/;daemonize\s*=\s*yes/daemonize = no/g" /etc/php7/php-fpm.conf && sed -i -e "s/listen\s*=\s*127.0.0.1:9000/listen = [::]:9000/g" /etc/php7/php-fpm.d/www.conf && sed -i -e "s/;chdir\s*=\s*\/var\/www/chdir = \/usr\/src\/app/g" /etc/php7/php-fpm.d/www.conf && sed -i -e "s/user\s*=\s*nobody/user = www-data/g" /etc/php7/php-fpm.d/www.conf && sed -i -e "s/group\s*=\s*nobody/group = www-data/g" /etc/php7/php-fpm.d/www.conf && sed -i -e "s/;clear_env\s*=\s*no/clear_env = no/g" /etc/php7/php-fpm.d/www.conf && sed -i -e "s/;catch_workers_output\s*=\s*yes/catch_workers_output = yes/g" /etc/php7/php-fpm.d/www.conf && sed -i "s|;date.timezone =.*|date.timezone = ${TIMEZONE}|" /etc/php7/php.ini && sed -i "s|memory_limit =.*|memory_limit = ${PHP_MEMORY_LIMIT}|" /etc/php7/php.ini && sed -i "s|upload_max_filesize =.*|upload_max_filesize = ${MAX_UPLOAD}|" /etc/php7/php.ini && sed -i "s|max_file_uploads =.*|max_file_uploads = ${PHP_MAX_FILE_UPLOAD}|" /etc/php7/php.ini && sed -i "s|post_max_size =.*|post_max_size = ${PHP_MAX_POST}|" /etc/php7/php.ini && sed -i "s/;cgi.fix_pathinfo=1/cgi.fix_pathinfo=0/" /etc/php7/php.ini && apk del tzdata && rm -rf /var/cache/apk/*
COPY . /usr/src/app
RUN chown -R www-data:www-data /usr/src/app
EXPOSE 9000
CMD ["php-fpm7"]
Этот образ послужит нам основным образом для нашего приложения. Сначала мы устанавливаем все необходимое для PHP и PHP-FPM. Далее, мы копируем текущую папку приложения в /usr/src/app, где будет распологаться наше приложение. В самом конце мы запускаем PHP-FPM.
Создание образов на основе DockerFile'ов
И так, у нас есть есть DockerFile'ы, на основе которых мы должны создать образы. Образы создаются очень просто. Достаточно выполнить следующие команды:
docker build -t myusername/myproject-nginx:latest -f DockerfileNginx .
docker build -t myusername/myproject-postgresql:latest -f DockerfilePostgreSql .
docker build -t myusername/myproject:latest .
В дальнейшем советую добавить к этим командам --no-cache, чтобы каждый раз не компилировать составляющие.
Мы создаем образы, прикрепляем их к нашему аккаунту на Docker Hub. Теперь, нам нужно отправить наши образы на репозиторий в Docker Hub. Выполняем следующие команды:
docker push myusername/myproject-nginx:latest
docker push myusername/myproject-postgresql:latest
docker push myusername/myproject:latest
Запуск образов на сервере
Мы почти у цели! Нам осталось загрузить образы из репозитория и запустить их. Загружаем их с помощью следующих команд:
docker pull myusername/myproject-nginx:latest
docker pull myusername/myproject-postgresql
docker pull myusername/myproject
Осталось их запустить. Делается это так же просто.
docker run —name myproject-nginx -d -p 80:80 myusername/myproject-nginx:latest
docker run —name myproject-postgresql9.5.2 -d -p 5432:5432 myusername/myproject-postgresql9.5.2:latest
docker run —name myproject -d -p 9000:9000 myusername/myproject:latest
Вуаля! Наше приложение запущено на Docker контейнерах. И тем не менее, всем читателям-новичкам я бы обязательно ознакомиться с документацией Docker.
Всем желаю успехов в освоении новых технологий!
Поделиться с друзьями
kricha
Кстати, если вы работаете над несколькими взаимодействующими веб-приложениями, то для связки нужно будет использовать docker-compose, дам мой пример:
Fedcomp
в docker-compose version 1 все гораздо проще.
ErickSkrauch
Указывать явные IP необязательно, docker-compose автоматически перелинкует контейнеры и они по умолчанию будут доступны по имени своего service. В примере выше, вероятно, было важно иметь фиксированные IP.
aig
Я бы вообще ничего без docker-compose не запускал, иначе потом черт ногу сломит с этими контейнерами, а compose делает имена вида folder_service_n, делает сам отдельную сетку с таким же названием папки.
Кстати, а зачем вообще указывать extra_hosts и IP адреса? Docker же позволяет внутри одной сети работать просто по имени сервиса, да и порты все наружу открывать не нужно, достаточно только для nginx.
EmotionTigran
Docker-compose хороший инструмент и конечно же его стоит использовать. Но все же, я бы исключил его для новичков, которые только-только начинают ознакамливаться с Docker'ом.
hudson
Ну видимо каждому своё, я наоборот несказанно счастлив, что параллельно учился пользоваться docker и docker-compose.
hudson
Я даже больше скажу, я никогда не использую чистый образ (типа image: mysql:latest), а делаю примерно так:
Таким образом я получаю фиксированные имена образа и контейнера (если использовать версионирование, то имя образа лучше писать в виде myproject-app:vesion, как принято в dockerhub).
При этом Dockerfile может содержать просто FROM mysql:latest, или же дополнительные инструкции.
amberovsky
Зачем в данном примере явно создавать сеть и прописывать ip?
docker-compose всё сделает за вас
kricha
я очень сомневаюсь, что вы из php-контейнера сможете достучаться до какого-нибудь из хостов.
amberovsky
Что вы имеете ввиду? Используя docker-compose можно обращаться к «своим» хостам по имени сервиса
kricha
Обьясняю. У вас есть nginx-контейнер и php-контейнер, они оба дают возможность работе двум web-сервисам site.local и api.local, если из nginx-контейнера вы можете получить доступ к обоим хостам, то в php-контейнере вам ни один не будет доступен, и из кода site.local нельзя будет сделать обращение к api.local.
amberovsky
Я понял, вам нужны алиасы на nginx сервис.
Вы пробовали networks — aliases?
kiaplayer
Можете мне пояснить, как человеку незнакомому с Docker: если ли смысл использовать описанный подход вместо связки Vagrant + VirtualBox?
kricha
вагрант — очень тугой инструмент, докер же быстрее, да и удобнее, как по мне.
A1MaZ
Если все устраивает, то переходить резко на докер я бы не стал. Для нового приложения имеет смысл попробовать.
ppa
Все от задач зависит. Лично я использую докер в нескольких виртуалках, развернутых варгантом (к слову, вагрант не показался мне удачным решением для разворачивания и уж тем более управления большим количеством виртуалок). Опять же, если требуется запустить зоопарк сервисов на одной машине, то докер отличное решение. Если же нужно давать доступ к машине 3-м лицам, то однозначно виртуалки.
renskiy
Если ваша связка Vagrant + Virtualbox работает стабильно, и у вас нет необходимости периодически добавлять новые сервисы в проект, то данную связку вполне удобно использовать, особенно если у вас уже все настроено и отлажено, включая процессы.
Но если вы захотите сделать апгрейд вашей системы, то наверно уже стоит подумать об уходе от зависимости от Vagrant.
Я как раз имел такой опыт перехода с Vagrant на Docker. И при этом столкнулся с рядом трудностей. Например, Docker из коробки не предлагает никакой автоматизации. Есть конечно docker-compose, но его возможности по сравнению с Vagrant весьма скудные (YAML все-таки не сравнить с полноценным ЯП). В итоге пришлось разрабатывать свое собственное решение.
Если вы все же захотите попробовать Docker без отказа от Vagrant, то последний предоставляет такую возможность, но разобраться в настройке не так то просто. Мне очень помогло в свое время вот это описание процесса настройки Vagrant + Docker.
BAG_Art
https://github.com/laradock/laradock
этот проект реализует подход vagtant + homestead
там же можно полистать docker-compose.yml
kesn
Куча sed'ов выглядит ужасно. Я бы просто создал конфиг-файл и переписал бы им дефолтный файл конфига
ls1
Простые решения увы не всегда подходят, иногда надо сохранить имеющийся конфиг. А со временем приходит понимание, что sed вовсе не ужасен, а совсем даже наоборот
OnYourLips
Гораздо лучше: допишите эти измененные строки в новом ini-файле в mods-available директории.
Не надо править конфиги: либо переписываете своим, либо дополняете (самый чистый вариант).
Он не просто ужасен, а отвратителен, и уровень отвратительности пропорционален величине проекта.
Например, вариант с шаблонизатором (подход популярен в средствах управления инфраструктурой) гораздо чище.
ls1
В данном случае вместо sed что именно предлагаете?
darken99
Dockerize
KlimovDm
foxmuldercp
Наверное автор имел в виду то, что некоторые вещи, вроде небольших однострочников могут перерасти в полтора килобайтный однострочник, например на перле, и превратятся в малопонятное и малосопровождаемое нечто, если наследник не сможет понять что этот однострочник в девичестве должен был выполнять.
Я например, не один раз в работе получал в наследство такие вот "однострочники", в которых сам автор через полгода после написания разобраться не мог :(
В этом плане мне нравятся шаблонизаторы, вроде встроенного в ruby ERB, с помощью которого можно генерить вполне приятные вещи, и не только конфиги
nskforward
А почему вы для PHP не используете готовый официальный образ?
nskforward
Также не вижу в PHP образе перенаправление логов в потоки вывода. Вы как логи с контейнера считываете?
EmotionTigran
Это форк официального образа для PHP. Для моего проекта там немного по-другому настраивается PHP, в этом же случае, оставил все по дефолту, но показал людям настройки, чтобы было понятно. На счет логов, действительно их нет. Не учел, добавлю как буду у компьютера. Спасибо!
vopper
Как только, появился докер я помню задавался вопросом, по поводу, контейнерезации, в том числе и бд.и ячитал, что контейнеры, придуманны не для хранения бд, это так? сейчас ситуация изменилась
jok40
EmotionTigran
В статье написано «Docker создает образы на основе DockerFile файлов, в котором описывается функционал. Мы создадим 3 образа для наших составляющих.».
jok40
Я это сказал к тому, что если Вы позиционируете подаваемый материал как информацию для новичков, то желательно всё-таки хотя-бы маленько разжёвывать содержимое примеров. А то смотришь в этот кусок кода и медленно офигеваешь от него.
pluseg
Я правильно понимаю, что после pull'a контейнеров и их запуска, нам еще нужно запустить миграции базы?
EmotionTigran
Запуск миграций можно написать в конце DockerFile'а в команде CMD.
CMD(«php bin/console doctrine:migrations run»);
hudson
При сборке контейнера контейнер базы данных не обязательно будет запущен. Я пока этот момент путём не решил. Ниже есть комментарий про использование /docker-entrypoint-initdb.d, но это не совсем миграции. Хотя дамп туда можно загрузить, но выполняться он будет только при первом запуске контейнера сразу после его создания.
nskforward
При создании контейнера из образа, вы можете поместить любое количество файлов с расширением .sql в специальную папку (смотреть в документации к образу вашей БД), которые автоматически исполнятся
amberovsky
Я вам предложу вместо длинной портянки chain-вызовов — сделать несколько .sh-файлов.
И я думаю не стоит делать -p на posgresql/project порты, так как они не должны быть доступны снаружи (как минимум сделать ограничение на 127.0.0.1). У вас есть expose в project, всё, что осталось — использовать link при создании контейнера
ALexhha
Если меня не подводит память, то опция --no-cache как раз наоборот будет каждый раз пересобирать все слои.
Dominant
Может пропустил, каков процесс деплоя с докером без доунтайма?
nskforward
Лично я только 2 способа знаю (если кто знает ещё, поделитесь):
1) Самый простой. Вы загружаете новый образ своего приложения в docker-репозиторий. Затем на сервере в папке вашего приложения выполняете «docker-compose up -d». После чего докер сперва скачает новый образ, грохнет старый контейнер и создаст новый. В этот момент я без остановки делаю рефреш страницы в браузере. При очередном рефреше страницы вижу уже новую версию в браузере, никаких ошибок или зависания не обнаружил.
2) Более сложный. Нужно использовать прокси с несколькими бэкендами. Поднимаете новый бэкенд с новой версией приложения, если всё ок, тушите бэкенд со старой версией приложения. В этом случае можно протестировать работу на небольшом количестве пользователей как ведёт себя новая версия прежде чем полностью переключаться.
nskforward
В первом варианте сперва нужно сделать docker pull образа перед вызовом «docker-compose up -d»
letchik
А как вы статику обслуживаете? Через php? Я не вижу приложения в nginx контейнере.
EmotionTigran
Статика у меня лежит в volumes, который подключается к nginx контейнеру. Думаю, следующая статья затронет больше тонкостей.