GitHub Actions для построения CD в Yandex Cloud

Nikolay Matrosov
5 min readNov 8, 2021

Надеюсь вы уже знакомы с тем, что такое GitHub Actions и для чего они вам могут быть полезны. Если нет, то могу посоветовать хороший доклад и его текстовую расшифровку на эту тему.

Тут же я расскажу про экшены, которые помогут вам интегрироваться с Yandex Cloud.

Если у вас есть идея, какого экшена не хватает, то заведите тему в этом репозитории, расскажите подробно, что он должен делать и возможно его реализуют.

В предыдущем посте я рассказал, как залогиниться в Docker в GitHub Action и запушить собранный image в Container Registry в Яндекс Облаке.

Теперь расскажу как из этого образа развернуть контейнер. В Yandex Cloud для этого есть как минимум 3 опции:
• Виртуальная машина, созданная из Container Optimized Image;
• Serverless Container;
• Managed K8s кластер.

Ниже я расскажу про первые два варианта.

Так же вам может захотеться деплоить новые версии serverless функций непосредственно из GitHub репозитория. Для этого тоже есть экшен.

Ну и напоследок рассмотрим еще два простых экшена: для получения секретов из Lockbox и сброса кэша в Yandex Cloud CDN.

Деплой в COI VM

Для этого нам понадобится в GitHub Workflow добавить следующий кусок.

- name: Deploy COI VM
id: deploy-coi
uses: yc-actions/yc-coi-deploy@v1
env:
IMAGE_URL: cr.yandex/crpk28lsfu91rns28316/yc-coi-github-action:${{ github.sha }}
SSH_KEY: ${{ secrets.SSH_KEY }}
with:
yc-sa-json-credentials: ${{ secrets.YC_SA_JSON_CREDENTIALS }}
folder-id: b1gbmkj63********
vm-name: yc-action-demo
vm-service-account-id: aje9nl4jk3********
vm-cores: 2
vm-memory: 2 GB
vm-core-fraction: 100
vm-zone-id: ru-central1-a
vm-subnet-id: e9bobc1ueq********
user-data-path: './user-data.yaml'
docker-compose-path: './docker-compose.yaml'

Разберем подробнее.

Для авторизации в API нам понадобится авторизованный ключ сервисного аккаунта. Как его создать подробно можно прочитать в документации IAM. Далее его нужно положить в секреты на GitHub.

Так же необходимо указать folder-id каталога в котором будет развернута ВМ. В параметр vm-name следует передать имя виртуальной машины, на которой разворачивать контейнер. Если такой машины нет, то она будет создана в ходе выполнения экшена. Т.к. я постарался упростить этап конфигурирования создаваемой ВМ, может так случиться, что вам не хватит каких-либо параметров. В таком случае вам нужно создать ВМ любым другим способом (UI, CLI, API, Terraform), а этому экшену останется лишь подкладывать новый docker-compose файл на ВМ через апдейт метадаты.

Вот мы и подошли к тому как будет разворачиваться контейнер на ВМ. Для этого в репозитории должен быть файл docker-compose.yaml в котором нужно описать конфигурацию разворачиваемого одного или нескольких контейнеров. Для того чтобы на этом этапе передать в файл некоторые переменные можно пользоваться mustache-синтакисисом. Например:

version: '3.7'
services:
logs:
container_name: logs-app
image: {{ env.IMAGE_URL }}
restart: always

Также нам понадобится файл user-data.yaml примерно такого содержания:

#cloud-config
users:
- name: username
groups: sudo
shell: /bin/bash
sudo: [ 'ALL=(ALL) NOPASSWD:ALL' ]
ssh-authorized-keys:
- {{ env.SSH_KEY }}

Здесь тоже можно использовать подстановку значений. Логиниться на созданную ВМ по ssh нужно будет от имени пользователя username или того которого вы укажете в user-data.

Полный код примера репозитория с этим workflow можно найти тут.

Деплой в Serverless Containers

Подход тут аналогичный, отличия лишь в используемом action.

- name: Deploy Serverless Container
id: deploy-sls-container
uses: yc-actions/yc-sls-container-deploy@v1
with:
yc-sa-json-credentials: ${{ secrets.YC_SA_JSON_CREDENTIALS }}
container-name: yc-action-demo
folder-id: bbajn5q2d74c********
revision-service-account-id: ajeqnasj95o7********
revision-cores: 1
revision-memory: 512Mb
revision-core-fraction: 100
revision-concurrency: 8
revision-image-url: cr.yandex/crp00000000000000000/my-cr-repo:${{ github.sha }}
revision-execution-timeout: 10

Так же как и в предыдущем примере мы указываем folder-id, где искать, и на этот раз container-name, который искать.

Затем будет получен id этого контейнера, с которым и будут производиться манипуляции с контейнером. Имя кажется более человекопонятным идентификатором, поэтому в инпутах экшенов используется именно оно.

Далее перечислены параметры с которыми будет развернута ревизия контейнера. Так же можно переда переменные окружения и другие параметры. Полный список можно найти в файле-описании action.yaml.

Деплой Serverless Function

Тут в отличие от предыдущих экшенов нам не нужно собирать docker image.

Если функция написана на языке для которого есть нативный рантайм, то можно просто при помощи этого экшена сразу создать новую версию функции.

Но тут я хотел бы разобрать чуть более сложный workflow, в котором мы сначала соберем js файлы из TypeScript исходников, а только потом создадим из них версию.

name: YC Function Deploy

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:

jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- name: Use Node.js
uses: actions/setup-node@v1
with:
node-version: '12.x'
- name: Install dependencies
run: npm ci
- run: npm run build --if-present
# Опционально можно запустить прогон тестов, если они есть
# - run: npm run test
- name: Deploy Function
id: sls-func
uses: yc-actions/yc-sls-function@v1.0.1
with:
yc-sa-json-credentials:${{secrets.YC_SA_JSON_CREDENTIALS}}
folder-id: 'b1gbmkj6*******'
function-name: 'github-deploy'
runtime: 'nodejs16'
memory: '256Mb'
entrypoint: 'dist/index.handler'
environment: |
DEBUG=True
FOO=bar

include: |
./dist
package.json
exclude: |
**/*.ts

И так по шагам.

actions/checkout@v2 — Скачивает исходный код из репозитория.

actions/setup-node@v1 — Делает доступным в нашем окружении NodeJs. И как видно из переданных параметров, в этом случае мы используем 12 версию.

run: npm ci — Ставит зависимости опираясь на package-lock.json. Убедитесь, что он есть в вашем репозитории.

run: npm run build --if-present — Билдим наши исходники на TypeScript в js.

И вот мы подошли к нашему экшену — yc-actions/yc-sls-function@v1.0.1. Как видно на вход он принимает всё тот же folder-id, а также имя функции, где мы будем создавать новую версию. Это обязательные параметры. Если такой функции нет в каталоге, то она будет создана.

Далее идут параметры версии функции. runtime принимает значения описанные в документации.entrypoint указывает на входную точку — функцию в коде, которая будет вызвана для обработки входящего сообщения. Оба эти параметра также обязательны.

Обратите внимание, что после environment указана вертикальная черта | — это способ передать многострочные значения в YAML. Именно в таком формате ожидаются:
— переменные окружения;
— списки путей, которые включить в архив при сборке iclude;
— а также шаблоны путей, которые исключить из нее exclude.

Получение секретов из Lockbox

Этот экшен прост относительно предыдущих и делает ровно то что указано в заголовке: получает секреты и складывает их в output, чтобы их можно было использовать в следующих шагах.

- name: Fetch secret
id: lockbox-secret
uses: yc-actions/yc-lockbox@v1
with:
yc-sa-json-credentials: ${{ secrets.YC_SA_JSON_CREDENTIALS }}
secret-id: e6q************

Обратиться к полученному секрету, лежащему по ключу foo из другого шага можно так: ${{ steps.lockbox-secret.outputs.foo }}.

Сброс кэша в YC CDN

Тоже довольно простой экшен. Он может пригодиться вам, когда вы собрали и разложили новую статику и хотите принудительно сбросить кэши в CDN.

- name: Purge CDN cache
id: purge-cache
uses: yc-actions/yc-cdn-cache@v1
with:
yc-sa-json-credentials: ${{ secrets.YC_SA_JSON_CREDENTIALS }}
cdn-resource-id: bc8********

--

--