Привет, Habr!
Heroku меньше чем через месяц станет недоступен для бесплатного использования. У многих моих знакомых, и у меня в том числе, на нем хостились несколько проектов. Поэтому встал вопрос миграции в другое облако. Остановился на Яндекс Облаке:
-
Относительно невысокие цены.
-
Возможность управлять как из панели, так и через CLI.
-
Интеграция с GitHub Actions.
-
Пробный период.
Задача
Есть 2 приложения: frontend и backend. Нужно выложить их на хостинг вместе. А чтобы работали вместе, склеить их с помощью nginx.
Общий вид пайплайна
-
Пуш изменений в main.
-
Создание Docker образов.
-
Пуш образов в Docker Hub.
-
Уведомление VM об обновлениях.
Создание Docker образов
Используем GitHub Actions для запуска нашего пайплайна при пуше в main.
on: push: branches: [ "main" ]
Теперь настроим билд Docker образов.
Т.к. у меня 2 отдельных приложения (React, FastAPI) + связующее звено (nginx), то билдить лучше параллельно.
env: FRONTEND_IMAGE: ${{ secrets.DOCKER_HUB_USERNAME }}/text-to-image-frontend:${{ github.sha }} BACKEND_IMAGE: ${{ secrets.DOCKER_HUB_USERNAME }}/text-to-image-api:${{ github.sha }} NGINX_IMAGE: ${{ secrets.DOCKER_HUB_USERNAME }}/text-to-image-nginx:${{ github.sha }} jobs: build-backend: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 - name: Login to Dockerhub uses: docker/login-action@v2 with: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} - name: Setup Buildx uses: docker/setup-buildx-action@v2 - name: Build Backend uses: docker/build-push-action@v3 with: context: ./src/backend file: ./src/backend/Dockerfile push: true tags: ${{ env.BACKEND_IMAGE }} build-frontend: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 - name: Login to Dockerhub uses: docker/login-action@v2 with: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} - name: Setup Buildx uses: docker/setup-buildx-action@v2 - name: Build Frontend uses: docker/build-push-action@v3 with: context: src/frontend file: ./src/frontend/Dockerfile push: true build-args: SERVER_URL=${{ secrets.API_SERVER_URL }} tags: ${{ env.FRONTEND_IMAGE }} build-nginx: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 - name: Login to Dockerhub uses: docker/login-action@v2 with: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} - name: Setup Buildx uses: docker/setup-buildx-action@v2 - name: Build Nginx for together composing uses: docker/build-push-action@v3 with: context: ./nginx file: ./nginx/Dockerfile push: true tags: ${{ env.NGINX_IMAGE }}
* Названия создаваемых образов содержат хеш коммита вместо ‘latest’. Причина в том, что обновление контейнера не будет происходить, если тег не обновился. Поэтому не стоит использовать неизменные теги.
Уведомление VM
Чтобы контейнеры в VM обновились, ее нужно уведомить. Будем использовать готовый Action для деплоя.
О создаваемой VM
Это действие создает (или обновляет существующую) виртуальную машину на базе COI (Container Optimized Image).
Для нас это означает, что это Ubuntu с предустановленными Docker и специальным демоном для его запуска.
В экшене пока доступен только docker-compose, хотя есть вариант COI спецификации
На этом шаге нам нужно передать:
-
Ключ для сервисного аккаунта.
-
Метаданные виртуальной машины.
-
Docker-compose спецификацию.
-
Информацию о пользователях.
Ключ сервисного аккаунта
Виртуальные машины создает сервисный аккаунт. Чтобы действовать от лица этого сервисного аккаунта нужно передать его ключ.
Создать этот ключ можно через CLI
yc iam access-key create --service-account-name sample-sa-name
Передавать ключ через параметр yc-sa-json-credentials
* Для сервисного аккаунта необходима роль compute.admin, чтобы он мог создавать и обновлять виртуальные машины
Метаданные VM
Для создания виртуальной машины нужно передать ее описание через параметры:
-
folder-id— ID каталога, где будет располагаться VM (обязательно); -
vm-name— название виртуальной машины (обязательно); -
vm-subnet-id— ID подсети, в которой будет располагаться VM (обязательно); -
vm-service-account-id— ID сервисного аккаунта, ассоциированного с VM; -
vm-service-account-name— название сервисного аккаунта, если ID не передан; -
vm-cores— количество ядер процессора VM; -
vm-memory— размер RAM VM; -
vm-core-fraction— для vCPU в процентах; -
vm-disk-size— размер диска; -
vm-zone-id— ID зоны, в которой будет располагаться VM; -
vm-platform-id— ID платформы (процессора, грубо говоря).
Первые 3 — обязательные, для остальных есть значения по-умолчанию
Чтобы найти подходящие конфигурации можно собрать свою в конфигураторе цен, а затем найти требуемые ID в документации. Например, для Intel Broadwell -vm-platform-id: ‘standard-v1’
Передача docker-compose спецификации
Контейнеры будем создавать с помощью docker-compose. Путь к файлу спецификации передается через параметр docker-compose-path.
К этому файлу будет применяться шаблонизатор Mustache. Т.е. мы можем передавать ему параметры через переменные окружения, а для доступа к ним использовать синтаксис: {{ env.VARIABLE_NAME }} .
Например, так передаются названия создаваемых Docker образов и другие переменные
update-yc: - name: Deploy COI VM uses: yc-actions/yc-coi-deploy@v1.0.1 env: BACKEND_IMAGE: ${{ env.BACKEND_IMAGE }} FRONTEND_IMAGE: ${{ env.FRONTEND_IMAGE }} NGINX_IMAGE: ${{ env.NGINX_IMAGE }} FRONTEND_ORIGINS: ${{ secrets.FRONTEND_ORIGINS }} NGINX_CERT: ${{ secrets.NGINX_CERT }} NGINX_CERT_KEY: ${{ secrets.NGINX_CERT_KEY }} with: docker-compose-path: './yandex-cloud/docker-compose.yc.yaml'
version: '3.7' services: nginx: image: {{ env.NGINX_IMAGE }} ports: - '80:80' - '443:443' restart: always environment: NGINX_CERT: {{ env.NGINX_CERT }} NGINX_CERT_KEY: {{ env.NGINX_CERT_KEY }} depends_on: - frontend - backend frontend: image: {{ env.FRONTEND_IMAGE }} restart: always backend: image: {{ env.BACKEND_IMAGE }} environment: ORIGINS: {{ env.FRONTEND_ORIGINS }} restart: always
Передача информации о пользователях
Для инициализации VM используется cloud-init. Пользователи создаются на основании конфигурации cloud-config. Например, вот конфигурация для создания единственного админа:
#cloud-config users: - name: {{ env.YC_VM_USERNAME }} groups: sudo shell: /bin/bash sudo: [ 'ALL=(ALL) NOPASSWD:ALL' ] ssh_authorized_keys: - {{ env.YC_VM_SSH }}
Для небольшого приложения этого достаточно.
* Заметьте #cloud-config на первой строчке — он информирует cloud-init об использовании именно cloud-config. Его необходимо указать.
Итоговый step деплоя
update-yc: runs-on: ubuntu-latest needs: [build-backend, build-frontend, build-nginx] steps: - name: Checkout uses: actions/checkout@v3 - name: Deploy COI VM id: deploy-coi uses: yc-actions/yc-coi-deploy@v1.0.1 env: BACKEND_IMAGE: ${{ env.BACKEND_IMAGE }} FRONTEND_IMAGE: ${{ env.FRONTEND_IMAGE }} NGINX_IMAGE: ${{ env.NGINX_IMAGE }} FRONTEND_ORIGINS: ${{ secrets.FRONTEND_ORIGINS }} YC_VM_SSH: ${{ secrets.YC_VM_SSH }} YC_VM_USERNAME: ${{ secrets.YC_VM_USERNAME }} NGINX_CERT: ${{ secrets.NGINX_CERT }} NGINX_CERT_KEY: ${{ secrets.NGINX_CERT_KEY }} with: yc-sa-json-credentials: ${{ secrets.YC_SA_JSON_CREDENTIALS }} folder-id: ${{ secrets.YC_FOLDER_ID }} VM-name: ${{ secrets.YC_VM_NAME }} vm-service-account-id: ${{ secrets.YC_SERVICE_ACCOUNT_ID }} vm-cores: 2 vm-platform-id: 'standard-v2' vm-memory: 512Mb vm-disk-size: 30Gb vm-core-fraction: 5 vm-subnet-id: ${{ secrets.YC_SUBNET_ID }} docker-compose-path: './yandex-cloud/docker-compose.yc.yaml' user-data-path: './yandex-cloud/user-data.yaml'
Идея с сертификатом
NGINX работает на 80 порту, но хотелось бы сделать работу через HTTPS.
Как вариант, можно запускать docker-compose с 2 приложениями, а NGINX будет работать вне докера и иметь сертификат устанавливаемый вручную. Но для этого нужно подключиться по ssh и скопировать его вручную.
Я выбрал другой способ: передавать сертификат и ключ через переменные окружения при старте Docker контейнера с NGINX.
Т.к. ключ создается для сертификата с переносами строк, то и передавать NGINX`у нужно тоже файл сертификата с переносами строк. При подстановке значений в docker-compose при помощи Mustache создается невалидный yaml файл, т.к. переносы строк все ломают.
Решение костыльное — заменить все переносы строк специальным символом и передавать полученные сертификат/ключ уже в новом виде. При получении сделать обратную замену.
Заменить я решил на ;.
Скрипт для старта NGINX:
#!/usr/bin/env sh if [ -z "${NGINX_CERT_KEY}" ]; then echo "NGINX_CERT_KEY env variable is not provided. Provide private key for encrypted certificate in it" exit 1 fi if [ -z "${NGINX_CERT}" ]; then echo "NGINX_CERT env variable is not provided. Provided encrypted certificate in it" fi mkdir -p /etc/nginx/certs echo "$NGINX_CERT" | tr ';' '\n' > /etc/nginx/certs/certificate.crt echo "$NGINX_CERT_KEY" | tr ';' '\n' > /etc/nginx/certs/certificate.key nginx -g 'daemon off;'
Итог
Настройка заняла примерно 2 дня. Большая часть времени ушла на фикс косяков. Например, не добавил #cloud-config в user-data.yaml или не дал права сервисному аккаунту.
Полезные сслыки:
В последней ссылке описаны экшены для деплоя Serverless Containers (деплой единственного образа) и Serverless Function.
Код проекта можно посмотреть здесь.
Готовое приложение: https://text-to-image.online
ссылка на оригинал статьи https://habr.com/ru/post/697206/
Добавить комментарий