CI/CD из GitHub в Яндекс Облако через Docker

от автора

Привет, Habr!

Heroku меньше чем через месяц станет недоступен для бесплатного использования. У многих моих знакомых, и у меня в том числе, на нем хостились несколько проектов. Поэтому встал вопрос миграции в другое облако. Остановился на Яндекс Облаке:

  • Относительно невысокие цены.

  • Возможность управлять как из панели, так и через CLI.

  • Интеграция с GitHub Actions.

  • Пробный период.

Задача

Есть 2 приложения: frontend и backend. Нужно выложить их на хостинг вместе. А чтобы работали вместе, склеить их с помощью nginx.

Общий вид пайплайна

  1. Пуш изменений в main.

  2. Создание Docker образов.

  3. Пуш образов в Docker Hub.

  4. Уведомление 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *