Рубріки: Решения

Как писать деплой-скрипты: гайд от релиз-менеджера Luxoft

Виталий Корж

Я не отношу себя к борцам с несуществующими проблемами, а больше нацелен на доработку того, что может быть улучшено. Одно из таких направлений — релиз-менеджмент.

По своим проектам я заметил, что часть рутинных задач по конфигурации пайплайна делается методом Сtrl+C/Сtrl+V. Получившиеся манускрипты своей массивностью пугают неискушенные умы программистов, что может приводить к неприятным последствиям.

Языки разметки — невероятно универсальный инструмент, овладев которым можно избавить себя от ежедневной рутины. Не стоит забывать, что сами по себе, без дополнительного слоя компиляции, они ничего не могут, но в связке с другими программами превращаются в мощный инструмент.

Думаю, что моя статья может быть интересна разработчикам и всем, кому интересно, как живет код-поле коммита; возможно, и DevOps почерпнут что-то в свой арсенал.

Языки разметки

Современную разработку невозможно представить без использования языков разметки. Форматы XML, YAML и JSON появились приблизительно в одно и то же время и вполне способны друг друга заменить. Разнообразие вариантов применения в описании и конфигурации различных систем выделяет их из числа языков разметки.

У каждого из них своя специфика. Мы не будем рассматривать семантику и изначальную документ-ориентированность форматов, а сконцентрируемся на сценариях использования. 

XML остается языком конфигурации в различных платформах и в различных своих спецификациях позволяет генерировать XHTML из XSLT и XML. YAML и JSON же занимают совершенно другое место в повседневной жизни каждого разработчика. 

Применение YAML

Чаще всего с этими языками мы сталкиваемся при использовании CI/CD-инструментов. Рассмотреть и сравнить различные решения можно по ссылке.

Я на своих проектах последние пару лет использую GitLab. Приложения деплоятся в Kubernetes, а инфраструктура может конфигурироваться с помощью CloudFormation. Эти продукты объединяет то, что для их конфигурации используется YAML/JSON. И если конфигурации Kubernetes и CloudFormation описывают систему и ничем не примечательны, то GilLab позволяет играть в некое подобие программирования. 

Знакомство с GitLab CI/CD

GitLab CI/CD позволяет управлять деливери-процессом через файл конфигурации. На первый взгляд файл не представляет собой ничего интересного, позволяя описать интересующие нас шаги и задать необходимые переменные, не забыв о исполняемых скриптах.

Для переменных применимо наследование, помимо файла конфигурации их можно указать на уровне проекта или группы. Список переменных расширяется новыми на каждом уровне. Переменные группы переопределяются проектными, а те в свою очередь — указанными непосредственно в конфигурации. Помимо пользовательских переменных, доступен внушительный список предопределенных на все случаи жизни.

Базовый пайплайн может выглядеть следующим образом:

--- main.yml
stages:
 - test
 - build
 - deploy

variables:
 foo: bar
 hello: world

app-test:
 stage: test
 script:
   - application test

app-build:
 stage: build
 script:
   - application build
   - container push

app-deploy:
 stage: deploy
 variables:
   foo: baz
 script:
   - container deploy

GitLab/YAML-программирование

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

nested-block: &some-ref
 foo: bar
 hello: world

nested-list: &list-ref
 - command
 - command param

app-block:
 stage: test
 <<: *some-ref
 script:
   - shell command
   - *list-ref

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

Наследование по умолчанию позволяет дополнять и переопределять определенные блоки. Extends, в свою очередь, позволяет наследовать уже существующие блоки, создавая новую логику на их основании, и работает с импортированными сущностями. Блоки с точкой в начале имени скрыты от системы и не отображаются на графическом конвейере. В то же время их можно использовать как основу или дополнение к существующей конфигурации.

--- example.yml
include:
 - local: main.yml


.app-build:
 variables:
   foo: bar


app-build:
 variables:
   fiz: baz

app-redeploy:
 extends:
   - .app-deploy
   - app-deploy
 when: manual

Каждая из этих техник позволяет навести порядок в наших проектах, но наибольший эффект достигается, если использовать их вместе. 

Чтобы не мусорить в своих проектах и максимально упростить процедуру апгрейда, я использую отдельный проект с пайплайнами, которые стандартизуют процедуру. Самая простая структура такого проекта может разниться, но в общих чертах выглядит так:

/ci-project
    /services
        java.yml
        node.yml
    basic.yml

Для использования в проекте нам будет достаточно сослаться на нужный файл:

include:
 - project: '/ci-project'
   ref: 'master'
   file: 'services/java.yml'

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

Но что будет, если мы захотим регламентировать не только языки, но и различные реализации build-test-deploy? Для этого пригодится ключевое слово rules. Rules позволяет настраивать различные условия для наших стейджей, тем самым изменив порядок наследования:

.node-apps:
 rules:
   - if: $LANGUAGE == "node"

В результате мы получаем более гибкую структуру с единой точкой входа main.yml:

/ci-project
    /services
        java.yml
        node.yml
    /deploy
        k8s.yml
        ec2.yml
    /build
        java.yml
        node.yml
    main.yml

Использование версионирования добавит стабильности пайплайнов. А файл с проектными особенностями позволит отделить логику от импортов:

include:
 - project: 'ci-project'
   ref: 'tag-version'
   file: 'main.yml'
 - local: '/.local-ci.yml'

Шаблонизация деплоя

В то время как тестирование и сборка артефактов регулируются командами, процесс деплоя для распределенных систем описывается документом, в котором от сервиса к сервису изменяется лишь пара строк.

В качестве примера рассмотрим деплой сервиса на Kubernetes. В зависимости от типа сервиса количество различных компонентов будет отличаться, но значащими параметрами будут: образ приложения в реестре, переменные окружения, иногда сертификаты, публичные адреса, параметры авторизации…

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

Реализовать шаблонизацию можно по-разному. Я выделю два варианта:

  • использование «настоящего» программирования (Python + Jinja), что требует дополнительных манипуляций с образом сборщика;
  • использование консольных команд (jq), требующее установки нескольких пакетов.

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

Вариант с jq наименее требователен к кастомизации исходного образа и способен решать главную задачу: он упрощает работу разработчика. По сути, он представляет собой не что иное, как JSON-программирование.

Вот вариант с кастомизацией CronJob:

--- main.yml
variables:
 TEMPLATE: > #сворачивает текст в строку
   {
     "apiVersion": "batch/v1beta1",
     "kind": "CronJob",
     "metadata": {
       "name": .name
     },
     "spec": {
       "schedule": .schedule,
       "concurrencyPolicy": "Forbid",
       "startingDeadlineSeconds": 1800,
       "successfulJobsHistoryLimit": 1,
       "failedJobsHistoryLimit": 1,
       "jobTemplate": {
         "spec": {
           "template": {
             "spec": {
               "containers": [
                 {
                   "name": "container-name",
                 }
               ],
               "restartPolicy": "Never"
             }
           }
         }
       }
     }
   }

.template: &template-ref
 - SECRETS_VAL=$(kubectl get secret $SECRET -o json | jq -c --arg secret $SECRET '.data | keys | map({"name":.,"valueFrom":{"secretKeyRef":{"name":$secret,"key":.}}})')
 - JOB=$(echo $JOBS | jq -c "[.[] | map_values(.) | $TEMPLATE]")
 - JOB=$(echo $JOB | jq -c --arg namespace $NAMESPACE --arg app $PROJECT_NAME '.items[].metadata += {"namespace":$namespace,"labels":{"group":$app}} | .items[].spec += {successfulJobsHistoryLimit:1}')
 - JOB=$(echo $JOB | jq -c --arg image $IMAGE --argjson env $SECRETS_VAL '.items[].spec.jobTemplate.spec.template.spec.containers[] +=  {"image":$image,"env":$env}')
 - echo $JOB | kubectl apply -f -

deploy:
 script:
   - *template-ref

--- .local-ci.yml
deploy:
 variables:
   NAMESPACE: space
   IMAGE: image.registry/image
   JOBS: >
     [
         {"name":"a", "schedule":"* * * * *"},
         {"name":"b", "schedule":"0 0 * * *"}
     ]

Все, что требуется от разработчика в этом примере, — определить переменные JOB, которые он хочет создать.

If you have found a spelling error, please, notify us by selecting that text and pressing Ctrl+Enter.

Останні статті

Токсичные коллеги. Как не стать одним из них и прекратить ныть

В благословенные офисные времена, когда не было большой войны и коронавируса, люди гораздо больше общались…

07.12.2023

Делать что-то впервые всегда очень трудно. Две истории о начале карьеры PM

Вот две истории из собственного опыта, с тех пор, когда только начинал делать свою карьеру…

04.12.2023

«Тыжпрограммист». Как люди не из ІТ-отрасли обесценивают профессию

«Ты же программист». За свою жизнь я много раз слышал эту фразу. От всех. Кто…

15.11.2023

Почему чат GitHub Copilot лучше для разработчиков, чем ChatGPT

Отличные новости! Если вы пропустили, GitHub Copilot — это уже не отдельный продукт, а набор…

13.11.2023

Как мы используем ИИ и Low-Code технологии для разработки IT-продукта

Несколько месяцев назад мы с командой Promodo (агентство инвестировало в продукт более $100 000) запустили…

07.11.2023

Университет или курсы. Что лучше для получения IT-образования

Пару дней назад прочитал сообщение о том, что хорошие курсы могут стать альтернативой классическому образованию.…

19.10.2023