Пример деплоя в Bamboo через Saltstack

Статья о том, когда есть данные продукты (https://www.saltstack.com/ и https://www.atlassian.com/ru/software/bamboo) в текущей инфраструктуре и захотелось их как-то связать между собой (запускать стейты солта из бамбу). Когда начинаешь гуглить подобный кейс, то каких-то примеров не находишь, а при создании своей реализации хочется сначала изучить чужой опыт, может быть взять что-то за основу (особенно всё печально, если перед этим не было опыта в других CI/CD). В общем, чтобы восполнить данный пробел, опишу как их вместе можно относительно "удобно" состыковать.

Рассмотрим обычный случай: программисты билдят свои docker образы, нам их надо задеплоить на сервер.  Как известно, в bamboo есть 2 сущности - Build  (CI) и Deploy  (CD). Программисты написали свой Build Plan, на выходе получаем получаем тегированный образ  где-то в registry. Теперь хотим, чтобы данный образ запустился на сервере с новым тегом + пробросить в контейнер некоторые labels ( т.е. стоит задача удобного проброса переменных из bamboo (https://confluence.atlassian.com/bamboo/bamboo-variables-289277087.html) в стейт солта).

 

1. Структура стейта в salt

my_app/map.jinja:
{% set my_app = {
    'bamboo': {
        'deploy': {
            'environment': 'unknown',
            'project': 'unknown',
            'release': 'unknown',
            'release_previous': 'unknown',
            'rollback': 'unknown',
        },
    },
        'docker': {
            'container_name': 'my_app',
            'image': 'some_image',
            'version': 'latest',
            'branch': 'master',
...

 

my_app/docker.sls
{% from 'my_app/map.jinja' import my_app %}
...
my_app-start-container:
  docker_container.running:
    - name: my_app
    - image: some-registry:{{ my_app.docker.version }}
...
    - labels:
      - bamboo.deploy.environment: {{ my_app.bamboo.deploy.environment }}
      - bamboo.deploy.project: {{ my_app.bamboo.deploy.project }}
      - bamboo.deploy.release: '{{ my_app.bamboo.deploy.release }}'
      - bamboo.deploy.release_previous: '{{ my_app.bamboo.deploy.release_previous }}'
      - bamboo.deploy.rollback: '{{ my_app.bamboo.deploy.rollback }}'
...

 

У нас обычно принята следующая структура написания стейтов (упрощенно):  

tree 
.
├── docker.sls
├── init.sls
└── map.jinja

cat init.sls 
include:
  - .docker

В map.jinja определяем дефолтные параметры, которые,  в случае чего, переопределяем (стандартный подход во всех IaC)

 

2. В репозитории, в котором происходит сборка, можно положить такой файл (можно его подправить под свои нужды):

CI_CD/deploy.py
 
 
#!/usr/bin/env python3
 
import os
import json
import subprocess
import sys
import argparse
 
parser = argparse.ArgumentParser(description="Deploy container")
parser.add_argument("--group", metavar="group-my_app", required=True, help="group to deploy")
parser.add_argument("--state", metavar="my_app", required=True, help="state to deploy")
parser.add_argument("--server", metavar="my-server.test.ru", help="server to deploy")
args = parser.parse_args()
 
target = "G@groups:" + args.group
if args.server:
    target += " and " + args.server
 
pillar = {
    "my_app": {
        "bamboo": {
            "deploy": {
                "environment": os.getenv("bamboo_deploy_environment"),
                "project": os.getenv("bamboo_deploy_project"),
                "release": os.getenv("bamboo_deploy_release"),
                "release_previous": os.getenv("bamboo_deploy_release_previous"),
                "rollback": os.getenv("bamboo_deploy_rollback"),
            },
        },
        "docker": {
          "version": os.getenv("bamboo_buildNumber"),
        },
    },
}
 
def run_checked(cmd):
    print("Executing", " ".join(cmd), flush=True)
    result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    print(result.stdout.decode(), flush=True)
    if result.returncode != 0:
        sys.exit(result.returncode)
    if result.stdout.find(b"Minion did not return. [No response]") != -1:
        sys.exit(1)
    if result.stdout.find(b"Salt request timed out. The master is not responding.") != -1:
        sys.exit(1)
 
run_checked(["sudo", "salt", "-C", target, "saltutil.refresh_pillar"])
run_checked(["sudo", "salt", "-C", target, "saltutil.sync_all", "refresh=True"])
run_checked(["sudo", "salt", "-C", target, "state.sls", state, "pillar={}".format(json.dumps(pillar))])

 

Скрипт нужен, чтобы в bamboo task самому не формировать для стейта солта pillar с динамически переменнными, выставленных в bamboo, т.е. в таске bamboo (c типом Script) в deployment project не писать строчки вида (см. п.4): 

/usr/bin/salt -G groups:my_group state.sls my_app pillar='{"my_app": {"docker": {"branch": "${bamboo.planRepository.branch}", "version": ${bamboo.buildNumber}"}}}'

 Скрипт запускает стейт опираясь на grain groups (в вашей инфраструктуре вместо грейна можеть быть что-то свое -  список хостов, pcre и т.п.)

 

3. В Build Plan надо добавить новый артефакт (Configure Plan → Your stage → вкладка Artifacts)

 

4. Создаем новый Deployment Project,  слинкованный с указанным Build Plan, и environment в нём. В env создаем следующие task:

В итоге, когда Bamboo deployment скачает артефакт и запустит этот скрипт с указанными аргументами, на бамбу агенте будет пытаться выполниться данная команда:

CI_CD/deploy.py --group=group-my_app --state=my_app
 
Executing sudo salt -C G@groups:group-my_app state.sls my_app pillar={"my_app": {"bamboo": {"deploy": {"environment": value1, "project": value2, "release": value3, "release_previous": value4, "rollback": value5}}}}

 

Я здесь опускаю настройки агента, чтобы он мог запускать стейт солта (вполне достаточно будет агента с типом Local, работающего от юзера bamboo, в sudoers которого прописано возможность запускать указанные команды). 

 При выполнении деплоймента на агенте, бамбу в environment ОС выставляет свои переменные окружения (можно их посмотреть через команду env):

bamboo_planRepository_branchName=master
bamboo_deploy_release_previous=release-4
bamboo_deploy_version=release-5
...

 

Python скрипт берет данные env и подставляет в пиллары стейта salt. Далее в стейте salt данные пиллары переопределяют дефолтные значения, которые уже выставляются в labels и tag контейнера. 

В конечно результате получим контейнер развернутого из образа с тегом, равным последнему билду и некоторыми labels, которые помогут, в случае чего, понять из какого деплоймента его запустили. В общем ничего нового, что делается в других вариантах CI/CD и Iac.

Please publish modules in offcanvas position.