Деплой на PaaS (Heroku, Render) дает нам представление того, как организовать его самостоятельно, на свой сервер. В общем случае, процесс выглядит так:
- Клонирование репозитория
- Сборка проекта. В нашем случае установка зависимостей
- Доставка на сервера. У нас пока один сервер
- Остановка старой версии и запуск новой
С точки зрения 12 факторов, важно разделять процесс сборки и релиза. Представьте что у нас 10 машин. Если мы начнем клонировать репозиторий на каждую из машин и выполнять там сборку, то получим множество неудобств:
- Если сборка пройдет неуспешно, то мы просто потратим ресурсы серверов впустую.
- Скачивание зависимостей это трафик, который стоит денег. Умножаем вес зависимостей на количество серверов.
- В случае отката на предыдущую версию, придется долго ждать пока заново выполнится сборка. Проблема даже с одним сервером.
Сборка, обычно, выполняется отдельно от релиза. Чаще всего ей занимается CI, который, после выполнения всех проверок, формирует какой-то артефакт: пакет под операционную систему (например deb), архив, Docker-образ. Последний стал стандартом де-факто. Все так или иначе переехали на Docker.
Разберем, как организовать сборку через CI, для любого проекта на примере devops-example-app
Сборка
Первым делом, нужно создать Dockerfile и добавить туда все шаги для подготовки приложения. Обычно это делается так, гуглится статья (официальные примеры), в которой упаковывается приложение на том же фреймворке и дальше методом проб и ошибок этот процесс повторяется, до тех пор, пока локально получится собрать образ, запустить его и увидеть готовое приложение. Для нашего приложения Dockerfile выглядит так:
FROM node:17
# Куда складываем файлы проекта
WORKDIR /app
# Копирует package.json и package-lock.json
COPY package*.json ./
# Установка зависимостей происходит до копирования файлов проекта
# Так как это позволяет реже сбрасывать этот слой (только при изменении файлов package*)
RUN npm ci
COPY . .
# Старт сервера описывается в scripts внутри package.json
CMD ["npm", "start"]
Когда Dockerfile готов, а образ собирается и запускается, пора создать аккаунт на Docker Hub. Внутри добавляется репозиторий для хранения нашего Docker-образа, и, наконец, выполняется docker push
. Так мы получаем образ, готовый для деплоя.
Остается последний шаг - автоматизация сборки с помощью Github Actions. Причем она будет практически идентичная для всех проектов, которые собираются с помощью Docker независимо от стека.
# Отличается от docker-example-app, так как он собирается через Docker Compose
# https://github.com/docker/build-push-action
name: main
on:
push:
branches:
- 'main'
env: # тег, под которым будет храниться временный образ-кеш
TEST_TAG: hexletcomponents/devops-example-app:test
jobs:
docker:
runs-on: ubuntu-latest
steps:
# Клонируем репозиторий
- uses: actions/checkout@v2
# Ниже список шагов из документации build-push-action
- uses: docker/setup-qemu-action@v1
- uses: docker/setup-buildx-action@v1
- uses: docker/login-action@v1
# Эти секреты нужно добавить самостоятельно
# Получить https://docs.docker.com/docker-hub/access-tokens/
# Добавить https://docs.github.com/en/actions/security-guides/encrypted-secrets
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
# Сборка и сразу кеширование, чтобы повторный запуск был быстрее
- uses: docker/build-push-action@v2
with:
context: .
load: true
tags: ${{ env.TEST_TAG }}
# Запуск тестов, команда запуска зависит от стека
# Лучше ее спрятать за make
- run: docker run --rm ${{ env.TEST_TAG }} npm test
# Здесь же нужно настроить запуск линтера
# Заливаем протестированный образ на Docker Hub
# https://github.com/docker/build-push-action/blob/master/docs/advanced/test-before-push.md
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
push: true
tags: hexletcomponents/devops-example-app:latest
В этом workflow собирается образ с тегом latest, который обновляется после каждого коммита и успешного прохождения тестов. При таком подходе будет невозможно узнать какая прямо сейчас версия на продакшене и, что совсем плохо, будет невозможно откатиться на другую версию, если что-то пойдет не так. Поэтому помимо образа latest, который полезен для постоянного тестирования процесса сборки, нужен отдельный workflow, в котором готовятся образы с тегами под каждую версию.
Такой workflow запускается не на коммиты, а на создание тега в git. Этот же тег затем используется и для Docker-образа. Ниже пример обновленного workflow, который нам подходит:
name: release
on:
create:
tags:
- v* # только теги начинающиеся с v: v1, v2, v5
env:
APP_IMAGE_NAME: hexletcomponents/devops-example-app
jobs:
docker:
runs-on: ubuntu-latest
steps:
# Скачиваем образ
- run: docker pull ${{ env.APP_IMAGE_NAME }}:latest
# Тегируем, тег в образе совпадает с тегом git-репозитория
# github.ref_name - в данном случае имя тега
- run: docker tag ${{ env.APP_IMAGE_NAME }}:latest ${{ env.APP_IMAGE_NAME }}:${{ github.ref_name }}
- uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
# Заливаем новый тег
- run: docker push ${{ env.APP_IMAGE_NAME }}:${{ github.ref_name }}
Запуск проверок для latest не нужен, так как он уже был проверен во время сборки по коммиту. Это сэкономит время на сборку.
В этой системе есть один момент, который нужно учитывать. Создавать тег можно только тогда, когда выполнится сборка latest. Иначе этот воркфлоу сделает тег из старого образа. Обойти это ограничение можно автоматическим созданием тега во время пуша в git-репозиторий.
Самостоятельная работа
-
Зарегистрируйтесь на Docker Hub
-
Склонируйте репозиторий devops-example-app, выполните сборку и залейте образ в ваш аккаунт на Docker Hub
Дополнительные материалы
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.