Статья о том, когда есть данные продукты (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.