Обзор задачи
Довольно часто, приложение не является монолитным, а состоит из нескольких микросервисов. Все они могут использовать как один стек технологий, так и разный: речь идёт как о языках программирования и фреймворках, так и об окружениях и операционных системах. Сложившийся подход в таких случаях — положить Dockerfile для сборки образа каждого микросервиса в отдельную папку, т.к. в одном Dockerfile вы не можете описать все компоненты вашего приложения. И, т.к. вам нужно описывать сборку каждого образа микросервиса в отдельном файле, вы не можете использовать, например, какие-либо общие части конфигурации сборки.
werf позволяет описать все образы проекта в одном конфигурационном файле и это действительно удобно.
В данной статье рассматривается сборка тестового приложения AtSea Shop и демонстрируется описание сборки нескольких компонентов приложения в одном конфигурационном файле.
Требования
- Установленные зависимости werf.
- Установленный multiwerf.
Выбор версии werf
Перед началом работы необходимо выбрать версию werf. Для выбора актуальной версии werf в канале stable, релиза 1.0, выполним следующую команду:
. $(multiwerf use 1.1 stable --as-file)
Сборка приложения
В качестве тестового приложения будет рассматриваться приложение AtSea Shop из официального репозитория примеров Docker. Это приложение — прототип небольшого интернет-магазина, оно состоит из нескольких компонентов — frontend (написан на ReactJS) и backend (Java Spring Boot). Так же для большей правдоподобности в проект добавлены reverse-прокси на базе nginx и платежный шлюз.
Компоненты приложения
Backend
Образ с именем app
. Контейнер с backend принимает HTTP-запросы от контейнера frontend.
Исходный код приложения находится в папке /app
и состоит из приложения на Java и приложения на ReactJS.
Для сборки образа backend будем использовать два артефакта (подробней об артефактах можно прочитать здесь) — storefront
и appserver
.
Образ самого backend основан на официальном образе Java. Он использует файлы из артефактов и не требует дополнительных шагов по скачиванию и сборке чего-либо.
image: app
from: java:8-jdk-alpine
docker:
ENTRYPOINT: ["java", "-jar", "/app/AtSea-0.0.1-SNAPSHOT.jar"]
CMD: ["--spring.profiles.active=postgres"]
shell:
beforeInstall:
- mkdir /app
- adduser -Dh /home/gordon gordon
import:
- artifact: storefront
add: /usr/src/atsea/app/react-app/build
to: /static
after: install
- artifact: appserver
add: /usr/src/atsea/target/AtSea-0.0.1-SNAPSHOT.jar
to: /app
after: install
Артефакт Storefront
В артефакте выполняется сборка asset’ов, после чего они импортируются в папку /static
в backend-образ app
.
Для эффективности сборка образа storefront
разделена на две стадии — install и setup.
artifact: storefront
from: node:12.10-alpine
git:
- add: /app/react-app
to: /usr/src/atsea/app/react-app
stageDependencies:
install:
- package.json
setup:
- src
- public
shell:
install:
- cd /usr/src/atsea/app/react-app
- npm install
setup:
- cd /usr/src/atsea/app/react-app
- npm run build
Артефакт Appserver
В артефакте выполняется сборка Java-кода, после чего результат, jar-файл AtSea-0.0.1-SNAPSHOT.jar
, импортируется в папку /app
в backend-образ app
.
Для эффективности сборка образа appserver
разделена на две стадии — install и setup.
Папка /usr/share/maven/ref/repository
монтируется с помощью инструкции build_dir
, чтобы заработало кэширование (подробнее об инструкциях монтирования можно прочитать здесь).
artifact: appserver
from: maven:3.6.2-jdk-8
mount:
- from: build_dir
to: /usr/share/maven/ref/repository
git:
- add: /app
to: /usr/src/atsea
stageDependencies:
install:
- pom.xml
setup:
- src
shell:
install:
- cd /usr/src/atsea
- mvn -B -f pom.xml -s /usr/share/maven/ref/settings-docker.xml dependency:go-offline
setup:
- cd /usr/src/atsea
- mvn -B -s /usr/share/maven/ref/settings-docker.xml package -DskipTests
Frontend
Образ с именем reverse_proxy
, базирующийся на официальном образе сервера NGINX, выступает в качестве точки приема входящего трафика в приложение (frontend) и настроен как реверсный прокси.
Т.е. его роль — прием внешнего трафика, кэширование и передача соответствующего трафика на backend-контейнер.
image: reverse_proxy
from: nginx:1.17-alpine
ansible:
install:
- name: "Copy nginx.conf"
copy:
content: |
{{ .Files.Get "reverse_proxy/nginx.conf" | indent 8 }}
dest: /etc/nginx/nginx.conf
- name: "Copy SSL certificates"
file:
path: /run/secrets
state: directory
owner: nginx
- copy:
content: |
{{ .Files.Get "reverse_proxy/certs/revprox_cert" | indent 8 }}
dest: /run/secrets/revprox_cert
- copy:
content: |
{{ .Files.Get "reverse_proxy/certs/revprox_key" | indent 8 }}
dest: /run/secrets/revprox_key
Database
Образ базы данных с именем database
основывается на официальном образе СУБД PostgreSQL.
В образ добавлены инструкции и SQL-файлы для конфигурации сервера PostgreSQL.
БД нужна, т.к. backend-контейнер использует её для хранения данных.
image: database
from: postgres:11
docker:
ENV:
POSTGRES_USER: gordonuser
POSTGRES_DB: atsea
ansible:
install:
- raw: mkdir -p /images/
- name: "Copy DB configs"
copy:
content: |
{{ .Files.Get "database/pg_hba.conf" | indent 8 }}
dest: /usr/share/postgresql/11/pg_hba.conf
- copy:
content: |
{{ .Files.Get "database/postgresql.conf" | indent 8 }}
dest: /usr/share/postgresql/11/postgresql.conf
git:
- add: /database/docker-entrypoint-initdb.d/
to: /docker-entrypoint-initdb.d/
Payment gateway
Образ с именем payment_gw
— образ демонстрационного приложения платежного шлюза.
По сути он не делает ничего, кроме того что пишет в stdout сообщения.
Его роль в настоящем примере — быть еще одним компонентом (микросервисом) приложения.
image: payment_gw
from: alpine:3.9
docker:
CMD: ["/home/payment/process.sh"]
ansible:
beforeInstall:
- name: "Create payment user"
user:
name: payment
comment: "Payment user"
shell: /bin/sh
home: /home/payment
- file:
path: /run/secrets
state: directory
owner: payment
- copy:
content: |
production
dest: /run/secrets/payment_token
git:
- add: /payment_gateway/process.sh
to: /home/payment/process.sh
owner: payment
Шаг 1: Код приложения
Склонируем репозиторий с кодом приложения AtSea Shop:
git clone https://github.com/dockersamples/atsea-sample-shop-app.git
Шаг 2: Конфигурация werf.yaml
Для сборки приложения включая все его компоненты, в корневой папке репозитория создадим файл werf.yaml
со следующим содержимым:
project: atsea-shop
configVersion: 1
---
artifact: storefront
from: node:12.10-alpine
git:
- add: /app/react-app
to: /usr/src/atsea/app/react-app
stageDependencies:
install:
- package.json
setup:
- src
- public
shell:
install:
- cd /usr/src/atsea/app/react-app
- npm install
setup:
- cd /usr/src/atsea/app/react-app
- npm run build
---
artifact: appserver
from: maven:3.6.2-jdk-8
mount:
- from: build_dir
to: /usr/share/maven/ref/repository
git:
- add: /app
to: /usr/src/atsea
stageDependencies:
install:
- pom.xml
setup:
- src
shell:
install:
- cd /usr/src/atsea
- mvn -B -f pom.xml -s /usr/share/maven/ref/settings-docker.xml dependency:go-offline
setup:
- cd /usr/src/atsea
- mvn -B -s /usr/share/maven/ref/settings-docker.xml package -DskipTests
---
image: app
from: java:8-jdk-alpine
docker:
ENTRYPOINT: ["java", "-jar", "/app/AtSea-0.0.1-SNAPSHOT.jar"]
CMD: ["--spring.profiles.active=postgres"]
shell:
beforeInstall:
- mkdir /app
- adduser -Dh /home/gordon gordon
import:
- artifact: storefront
add: /usr/src/atsea/app/react-app/build
to: /static
after: install
- artifact: appserver
add: /usr/src/atsea/target/AtSea-0.0.1-SNAPSHOT.jar
to: /app
after: install
---
image: reverse_proxy
from: nginx:1.17-alpine
ansible:
install:
- name: "Copy nginx.conf"
copy:
content: |
{{ .Files.Get "reverse_proxy/nginx.conf" | indent 8 }}
dest: /etc/nginx/nginx.conf
- name: "Copy SSL certificates"
file:
path: /run/secrets
state: directory
owner: nginx
- copy:
content: |
{{ .Files.Get "reverse_proxy/certs/revprox_cert" | indent 8 }}
dest: /run/secrets/revprox_cert
- copy:
content: |
{{ .Files.Get "reverse_proxy/certs/revprox_key" | indent 8 }}
dest: /run/secrets/revprox_key
---
image: database
from: postgres:11
docker:
ENV:
POSTGRES_USER: gordonuser
POSTGRES_DB: atsea
ansible:
install:
- raw: mkdir -p /images/
- name: "Copy DB configs"
copy:
content: |
{{ .Files.Get "database/pg_hba.conf" | indent 8 }}
dest: /usr/share/postgresql/11/pg_hba.conf
- copy:
content: |
{{ .Files.Get "database/postgresql.conf" | indent 8 }}
dest: /usr/share/postgresql/11/postgresql.conf
git:
- add: /database/docker-entrypoint-initdb.d/
to: /docker-entrypoint-initdb.d/
---
image: payment_gw
from: alpine:3.9
docker:
CMD: ["/home/payment/process.sh"]
ansible:
beforeInstall:
- name: "Install shadow utils"
package:
name: shadow
state: present
- name: "Create payment user"
user:
name: payment
comment: "Payment user"
shell: /bin/sh
home: /home/payment
- file:
path: /run/secrets
state: directory
owner: payment
- copy:
content: |
production
dest: /run/secrets/payment_token
git:
- add: /payment_gateway/process.sh
to: /home/payment/process.sh
owner: payment
Шаг 3: Создание SSL-сертификата
NGINX в образе reverse_proxy
принимает запросы по SSL и ему требуется соответствующий ключ и сертификат.
Для создания ключа и сертификата выполним следующую команду в корневой папке проекта:
mkdir -p reverse_proxy/certs && openssl req -newkey rsa:4096 -nodes -subj "/CN=atseashop.com;" -sha256 -keyout reverse_proxy/certs/revprox_key -x509 -days 365 -out reverse_proxy/certs/revprox_cert
Шаг 4: Сборка образов
Для сборки всех образов проекта, выполним следующую команду в корневой папке проекта:
werf build --stages-storage :local
Шаг 5: Добавление информации в файл /etc/hosts
Чтобы пример открывался в браузере по имени http://atseashop.com
, добавьте в файл etc/hosts
строку для atseashop.com
с адресом локального интерфейса. Например вот так:
sudo sed -ri 's/^(127.0.0.1)(\s)+/\1\2atseashop.com /' /etc/hosts
Шаг 6: Запуск приложения
Для запуска приложения, выполним следующие команды в корневой папке проекта:
werf run --stages-storage :local --docker-options="-d --name payment_gw" payment_gw &&
werf run --stages-storage :local --docker-options="-d --name database -p 5432:5432" database &&
werf run --stages-storage :local --docker-options="-d --name app -p 8080:8080 --link database:database" app &&
werf run --stages-storage :local --docker-options="-d --name reverse_proxy -p 80:80 -p 443:443 --link app:appserver" reverse_proxy
Проверьте что все контейнеры запустились, выполнив:
docker ps
В выводе команды должны присутствовать запущенные контейнеры с именами: reverse_proxy
, app
, database
, payment_gw
и registry
.
Подождите около 30 секунд, чтобы все контейнеры успели после старта перейти в режим готовности.
Затем откройте в браузере адрес atseashop.com. Произойдет перенаправление на адрес https://atseashop.com
и вы получите предупреждение безопасности от браузера — следствие использования самоподписанного SSL-сертификата. Добавьте в браузере исключение для страницы https://atseashop.com
.
Остановка приложения
Для остановки контейнеров приложения выполним следующую команду:
docker stop reverse_proxy app database payment_gw
Выводы
Мы описали инструкции по сборке всех образов приложения в одном файле. Приведенный пример иллюстрирует следующие возможности: