Русский
Русский
English
Статистика
Реклама

Cicd

Из песочницы Способы и примеры внедрения утилит для проверки безопасности Docker

21.10.2020 18:15:40 | Автор: admin

Привет, Хабр!

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

В этой статье я поделюсь готовыми скриптами для внедрения нескольких утилит обеспечения безопасности Docker и инструкцией, как развернуть небольшой демо-стенд для проверки этого процесса. Материалами можно воспользоваться, чтобы поэкспериментировать с тем, как организовать процесс тестирования безопасности образов и инструкций Dockerfile. Понятно, что инфраструктура разработки и внедрения у всех разные, поэтому ниже я приведу несколько возможных вариантов.

Утилиты проверки безопасности


Существует большое количество различных вспомогательных приложений и скриптов, которые выполняют проверки разнообразных аспектов Docker-инфраструктуры. Часть из них уже была описана в предыдущей статье (http://personeltest.ru/aways/habr.com/ru/company/swordfish_security/blog/518758/#docker-security), а в данном материале я бы хотел остановиться на трех из них, которые покрывают основную часть требований к безопасности Docker-образов, строящихся в процессе разработки. Помимо этого я также покажу пример как можно эти три утилиты соединить в один pipeline для осуществления проверок безопасности.

Hadolint
https://github.com/hadolint/hadolint

Довольно простая консольная утилита, которая помогает в первом приближении оценить корректность и безопасность инструкций Dockerfile-ов (например использование только разрешенных реестров образов или использование sudo).

Вывод утилиты Hadolint

Dockle
https://github.com/goodwithtech/dockle

Консольная утилита, работающая с образом (или с сохраненным tar-архивом образа), которая проверяет корректность и безопасность конкретного образа как такового, анализируя его слои и конфигурацию какие пользователи созданы, какие инструкции используются, какие тома подключены, присутствие пустого пароля и т. д. Пока количество проверок не очень большое и базируется на нескольких собственных проверках и рекомендациях CIS (Center for Internet Security) Benchmark для Docker.


Trivy
https://github.com/aquasecurity/trivy

Эта утилита нацелена на нахождение уязвимостей двух типов проблемы сборок ОС (поддерживаются Alpine, RedHat (EL), CentOS, Debian GNU, Ubuntu) и проблемы в зависимостях (Gemfile.lock, Pipfile.lock, composer.lock, package-lock.json, yarn.lock, Cargo.lock). Trivy умеет сканировать как образ в репозитории, так и локальный образ, а также проводить сканирование на основании переданного .tar файла с Docker-образом.



Варианты внедрения утилит


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

Основная идея состоит в том, чтобы продемонстрировать, как можно внедрить автоматическую проверку содержимого Dockerfile и Docker-образов, которые создаются в процессе разработки.

Сама проверка состоит из следующих шагов:
  1. Проверка корректности и безопасности инструкций Dockerfile утилитой линтером Hadolint
  2. Проверка корректности и безопасности конечного и промежуточных образов утилитой Dockle
  3. Проверка наличия общеизвестных уязвимостей (CVE) в базовом образе и ряде зависимостей утилитой Trivy

Дальше в статье я приведу три варианта внедрения этих шагов:
Первый путём конфигурации CI/CD pipeline на примере GitLab (с описанием процесса поднятия тестового инстанса).
Второй с использованием shell-скрипта.
Третий с построением Docker-образа для сканирования Docker-образов.
Вы можете выбрать вариант который больше вам подходит, перенести его на свою инфраструктуру и адаптировать под свои нужды.

Все необходимые файлы и дополнительные инструкции также находятся в репозитории: https://github.com/Swordfish-Security/docker_cicd

Интеграция в GitLab CI/CD


В первом варианте мы рассмотрим, как можно внедрить проверки безопасности на примере системы репозиториев GitLab. Здесь мы пройдем по шагам и разберем как установить с нуля тестовое окружение с GitLab, составить процесс сканирования и осуществить запуск утилит для проверки тестового Dockerfile и случайного образа приложения JuiceShop.

Установка GitLab
1. Ставим Docker:
sudo apt-get update && sudo apt-get install docker.io

2. Добавляем текущего пользователя в группу docker, чтобы можно было работать с докером не через sudo:
sudo addgroup <username> docker

3. Находим свой IP:
ip addr

4. Ставим и запускаем GitLab в контейнере, заменяя IP адрес в hostname на свой:
docker run --detach \--hostname 192.168.1.112 \--publish 443:443 --publish 80:80 \--name gitlab \--restart always \--volume /srv/gitlab/config:/etc/gitlab \--volume /srv/gitlab/logs:/var/log/gitlab \--volume /srv/gitlab/data:/var/opt/gitlab \gitlab/gitlab-ce:latest

Ждём, пока GitLab выполнит все необходимые процедуры по установке (можно следить за процессом через вывод лог-файла: docker logs -f gitlab).

5. Открываем в браузере свой локальный IP и видим страницу с предложением поменять пароль для пользователя root:

Задаём новый пароль и заходим в GitLab.

6. Создаём новый проект, например cicd-test и инициализируем его стартовым файлом README.md:

7. Теперь нам необходимо установить GitLab Runner: агента, который будет по запросу запускать все необходимые операции.
Скачиваем последнюю версию (в данном случае под Linux 64-bit):
sudo curl -L --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd64

8. Делаем его исполняемым:
sudo chmod +x /usr/local/bin/gitlab-runner

9. Добавляем пользователя ОС для Runner-а и запускаем сервис:
sudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bashsudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runnersudo gitlab-runner start

Должно получиться примерно так:

local@osboxes:~$ sudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runnerRuntime platform arch=amd64 os=linux pid=8438 revision=0e5417a3 version=12.0.1local@osboxes:~$ sudo gitlab-runner startRuntime platform arch=amd64 os=linux pid=8518 revision=0e5417a3 version=12.0.1

10. Теперь регистрируем Runner, чтобы он мог взаимодействовать с нашим инстансом GitLab.
Для этого открываем страницу Settings-CI/CD (http://personeltest.ru/away/OUR_ IP_ADDRESS/root/cicd-test/-/settings/ci_cd) и на вкладке Runners находим URL и Registration token:

11. Регистрируем Runner, подставляя URL и Registration token:
sudo gitlab-runner register \--non-interactive \--url "http://<URL>/" \--registration-token "<Registration Token>" \--executor "docker" \--docker-privileged \--docker-image alpine:latest \--description "docker-runner" \--tag-list "docker,privileged" \--run-untagged="true" \--locked="false" \--access-level="not_protected"

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

Конфигурация pipeline

1. Добавим в репозиторий файлы mydockerfile.df (это некий тестовый Dockerfile, который мы будем проверять) и конфигурационный файл GitLab CI/CD процесса .gitlab-cicd.yml, который перечисляет инструкции для сканеров (обратите внимание на точку в названии файла).

YAML-файл конфигурации содержит инструкции по запуску трех утилит (Hadolint, Dockle и Trivy), которые проанализируют выбранный Dockerfile и образ, заданный в переменной DOCKERFILE. Все необходимые файлы можно взять из репозитория: https://github.com/Swordfish-Security/docker_cicd/

Выдержка из mydockerfile.df (это абстрактный файл с набором произвольных инструкций только для демонстрации работы утилиты). Прямая ссылка на файл: mydockerfile.df

Содержимое mydockerfile.df
FROM amd64/node:10.16.0-alpine@sha256:f59303fb3248e5d992586c76cc83e1d3700f641cbcd7c0067bc7ad5bb2e5b489 AS tsbuildCOPY package.json .COPY yarn.lock .RUN yarn installCOPY lib libCOPY tsconfig.json tsconfig.jsonCOPY tsconfig.app.json tsconfig.app.jsonRUN yarn buildFROM amd64/ubuntu:18.04@sha256:eb70667a801686f914408558660da753cde27192cd036148e58258819b927395LABEL maintainer="Rhys Arkins <rhys@arkins.net>"LABEL name="renovate"...COPY php.ini /usr/local/etc/php/php.iniRUN cp -a /tmp/piik/* /var/www/html/RUN rm -rf /tmp/piwikRUN chown -R www-data /var/www/htmlADD piwik-cli-setup /piwik-cli-setupADD reset.php /var/www/html/## ENTRYPOINT ##ADD entrypoint.sh /entrypoint.shENTRYPOINT ["/entrypoint.sh"]USER root

Конфигурационный YAML выглядит таким образом (сам файл можно взять по прямой ссылке здесь: .gitlab-ci.yml):

Содержимое .gitlab-ci.yml
variables:    DOCKER_HOST: "tcp://docker:2375/"    DOCKERFILE: "mydockerfile.df" # name of the Dockerfile to analyse       DOCKERIMAGE: "bkimminich/juice-shop" # name of the Docker image to analyse    # DOCKERIMAGE: "knqyf263/cve-2018-11235" # test Docker image with several CRITICAL CVE    SHOWSTOPPER_PRIORITY: "CRITICAL" # what level of criticality will fail Trivy job    TRIVYCACHE: "$CI_PROJECT_DIR/.cache" # where to cache Trivy database of vulnerabilities for faster reuse    ARTIFACT_FOLDER: "$CI_PROJECT_DIR" services:    - docker:dind # to be able to build docker images inside the Runner stages:    - scan    - report    - publish HadoLint:    # Basic lint analysis of Dockerfile instructions    stage: scan    image: docker:git     after_script:    - cat $ARTIFACT_FOLDER/hadolint_results.json     script:    - export VERSION=$(wget -q -O - https://api.github.com/repos/hadolint/hadolint/releases/latest | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/')    - wget https://github.com/hadolint/hadolint/releases/download/v${VERSION}/hadolint-Linux-x86_64 && chmod +x hadolint-Linux-x86_64         # NB: hadolint will always exit with 0 exit code    - ./hadolint-Linux-x86_64 -f json $DOCKERFILE > $ARTIFACT_FOLDER/hadolint_results.json || exit 0     artifacts:        when: always # return artifacts even after job failure               paths:        - $ARTIFACT_FOLDER/hadolint_results.json Dockle:    # Analysing best practices about docker image (users permissions, instructions followed when image was built, etc.)    stage: scan       image: docker:git     after_script:    - cat $ARTIFACT_FOLDER/dockle_results.json     script:    - export VERSION=$(wget -q -O - https://api.github.com/repos/goodwithtech/dockle/releases/latest | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/')    - wget https://github.com/goodwithtech/dockle/releases/download/v${VERSION}/dockle_${VERSION}_Linux-64bit.tar.gz && tar zxf dockle_${VERSION}_Linux-64bit.tar.gz    - ./dockle --exit-code 1 -f json --output $ARTIFACT_FOLDER/dockle_results.json $DOCKERIMAGE            artifacts:        when: always # return artifacts even after job failure               paths:        - $ARTIFACT_FOLDER/dockle_results.json Trivy:    # Analysing docker image and package dependencies against several CVE bases    stage: scan       image: docker:git     script:    # getting the latest Trivy    - apk add rpm    - export VERSION=$(wget -q -O - https://api.github.com/repos/knqyf263/trivy/releases/latest | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/')    - wget https://github.com/knqyf263/trivy/releases/download/v${VERSION}/trivy_${VERSION}_Linux-64bit.tar.gz && tar zxf trivy_${VERSION}_Linux-64bit.tar.gz         # displaying all vulnerabilities w/o failing the build    - ./trivy -d --cache-dir $TRIVYCACHE -f json -o $ARTIFACT_FOLDER/trivy_results.json --exit-code 0 $DOCKERIMAGE            # write vulnerabilities info to stdout in human readable format (reading pure json is not fun, eh?). You can remove this if you don't need this.    - ./trivy -d --cache-dir $TRIVYCACHE --exit-code 0 $DOCKERIMAGE         # failing the build if the SHOWSTOPPER priority is found    - ./trivy -d --cache-dir $TRIVYCACHE --exit-code 1 --severity $SHOWSTOPPER_PRIORITY --quiet $DOCKERIMAGE             artifacts:        when: always # return artifacts even after job failure        paths:        - $ARTIFACT_FOLDER/trivy_results.json     cache:        paths:        - .cache Report:    # combining tools outputs into one HTML    stage: report    when: always    image: python:3.5         script:    - mkdir json    - cp $ARTIFACT_FOLDER/*.json ./json/    - pip install json2html    - wget https://raw.githubusercontent.com/shad0wrunner/docker_cicd/master/convert_json_results.py    - python ./convert_json_results.py         artifacts:        paths:        - results.html

При необходимости также можно сканировать и сохраненные образы в виде .tar-архива (однако потребуется в YAML файле изменить входные параметры для утилит)

NB: Trivy требует для своего запуска установленные rpm и git. В противном случае он будет выдавать ошибки при сканировании RedHat-based образов и получении обновлений базы уязвимостей.

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

В результате у нас есть четыре задачи. Три из них занимаются непосредственно сканированием и последняя (Report) собирает простой отчёт из разрозненных файлов с результатами сканирования.

По умолчанию Trivy останавливает своё выполнение, если были обнаружены CRITICAL уязвимости в образе или зависимостях. В то же время Hadolint всегда возвращает Success код выполнения, так как в результате его выполнения всегда есть замечания, что приводит к остановке сборки.

В зависимости от конкретных требований можно сконфигурировать код выхода, чтобы эти утилиты при обнаружении проблем определенной критичности, останавливали также и процесс сборки. В нашем случае сборка остановится, только если Trivy обнаружит уязвимость с критичностью, которую мы указали в переменной SHOWSTOPPER в .gitlab-ci.yml.


Результат работы каждой утилиты можно посмотреть в логе каждой сканирующей задачи, непосредственно в json-файлах в разделе artifacts или в простом HTML-отчёте (о нем чуть ниже):


3. Для представления отчётов утилит в чуть более человекочитаемом виде используется небольшой скрипт на Python для конвертации трёх json-файлов в один HTML-файл с таблицей дефектов.
Этот скрипт запускается отдельной задачей Report, а его итоговым артефактом является HTML-файл с отчётом. Исходник скрипта также лежит в репозитории и его можно адаптировать под свои нужды, цвета и т. п.


Shell-скрипт


Второй вариант подходит для случаев, когда необходимо проверять Docker-образы не в рамках CI/CD системы или необходимо иметь все инструкции в виде, который можно выполнить непосредственно на хосте. Этот вариант покрывается готовым shell-скриптом, который можно запустить на чистой виртуальной (или даже реальной) машине. Скрипт выполняет те же самые инструкции, что и вышеописанный gitlab-runner.

Для успешной работы скрипта в системе должен быть установлен Docker и текущий пользователь должен быть в группе docker.

Сам скрипт можно взять здесь: docker_sec_check.sh

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

В процессе выполнения скрипта все утилиты будут скачаны в директорию docker_tools, результаты их работы в директорию docker_tools/json, а HTML с отчётом будет находиться в файле results.html.

Пример вывода скрипта
~/docker_cicd$ ./docker_sec_check.sh[+] Setting environment variables[+] Installing required packages[+] Preparing necessary directories[+] Fetching sample Dockerfile2020-10-20 10:40:00 (45.3 MB/s) - Dockerfile saved [8071/8071][+] Pulling image to scanlatest: Pulling from bkimminich/juice-shop[+] Running Hadolint...Dockerfile:205 DL3015 Avoid additional packages by specifying `--no-install-recommends`Dockerfile:248 DL3002 Last USER should not be root...[+] Running Dockle...WARN    - DKL-DI-0006: Avoid latest tag        * Avoid 'latest' tagINFO    - CIS-DI-0005: Enable Content trust for Docker        * export DOCKER_CONTENT_TRUST=1 before docker pull/build...[+] Running Trivyjuice-shop/frontend/package-lock.json=====================================Total: 3 (UNKNOWN: 0, LOW: 1, MEDIUM: 0, HIGH: 2, CRITICAL: 0)+---------------------+------------------+----------+---------+-------------------------+|       LIBRARY       | VULNERABILITY ID | SEVERITY | VERSION |             TITLE       |+---------------------+------------------+----------+---------+-------------------------+| object-path         | CVE-2020-15256   | HIGH     | 0.11.4  | Prototype pollution in  ||                     |                  |          |         | object-path             |+---------------------+------------------+          +---------+-------------------------+| tree-kill           | CVE-2019-15599   |          | 1.2.2   | Code Injection          |+---------------------+------------------+----------+---------+-------------------------+| webpack-subresource | CVE-2020-15262   | LOW      | 1.4.1   | Unprotected dynamically ||                     |                  |          |         | loaded chunks           |+---------------------+------------------+----------+---------+-------------------------+juice-shop/package-lock.json============================Total: 20 (UNKNOWN: 0, LOW: 1, MEDIUM: 6, HIGH: 8, CRITICAL: 5)...juice-shop/package-lock.json============================Total: 5 (CRITICAL: 5)...[+] Removing left-overs[+] Making the output look pretty[+] Converting JSON results[+] Writing results HTML[+] Clean exit ============================================================[+] Everything is done. Find the resulting HTML report in results.html


Docker-образ со всеми утилитами


В качестве третьей альтернативы я составил два простых Dockerfile для создания образа с утилитами безопасности. Один Dockerfile поможет собрать набор для сканирования образа из репозитория, второй (Dockerfile_tar) собрать набор для сканирования tar-файла с образом.

1. Берем соответствующий Docker файл и скрипты из репозитория https://github.com/Swordfish-Security/docker_cicd/tree/master/Dockerfile.
2. Запускаем его на сборку:
docker build -t dscan:image -f docker_security.df .

3. После окончания сборки создаем контейнер из образа. При этом передаём переменную окружения DOCKERIMAGE с названием интересующего нас образа и монтируем Dockerfile, который хотим анализировать, с нашей машины на файл /Dockerfile (обратите внимание, что требуется абсолютный путь до этого файла):
docker run --rm -v $(pwd)/results:/results -v $(pwd)/docker_security.df:/Dockerfile -e DOCKERIMAGE="bkimminich/juice-shop" dscan:image

[+] Setting environment variables[+] Running Hadolint/Dockerfile:3 DL3006 Always tag the version of an image explicitly[+] Running DockleWARN    - DKL-DI-0006: Avoid latest tag        * Avoid 'latest' tagINFO    - CIS-DI-0005: Enable Content trust for Docker        * export DOCKER_CONTENT_TRUST=1 before docker pull/buildINFO    - CIS-DI-0006: Add HEALTHCHECK instruction to the container image        * not found HEALTHCHECK statementINFO    - DKL-LI-0003: Only put necessary files        * unnecessary file : juice-shop/node_modules/sqlite3/Dockerfile        * unnecessary file : juice-shop/node_modules/sqlite3/tools/docker/architecture/linux-arm64/Dockerfile        * unnecessary file : juice-shop/node_modules/sqlite3/tools/docker/architecture/linux-arm/Dockerfile[+] Running Trivy...juice-shop/package-lock.json============================Total: 20 (UNKNOWN: 0, LOW: 1, MEDIUM: 6, HIGH: 8, CRITICAL: 5)...[+] Making the output look pretty[+] Starting the main module ============================================================[+] Converting JSON results[+] Writing results HTML[+] Clean exit ============================================================[+] Everything is done. Find the resulting HTML report in results.html

Результаты


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

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

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

Перевод Построение конвейера IaC на AWS с полностью интегрированной безопасностью

02.12.2020 18:09:40 | Автор: admin

Перевод статьи подготовлен в преддверии старта курса "Infrastructure as a code in Ansible".


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


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

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

Теперь давайте разберем реальный пример того, как можно автоматически проконтролировать корректность методов разработки в конвейерах IaC для среды AWS посредством сотен проверок на соответствие правиламAWS Well-Architected Framework(безопасность, оптимизация затрат, производительность, высокие стандарты профессиональной деятельности и надежность) и другим стандартам.

Вот общее представление того, над чем мы будем работать:

Исходные требования

Для формирования нужной нам среды вам понадобятся следующие элементы:

Приступим к созданию вашего первого конвейера инфраструктура как код (IaC) с проверкой применения правильных методов разработки!

Этап1. Установка подключаемого модуля безопасности в среду программирования и получение токена API для сканирования шаблонов CloudFormation

После установки среды VSCode IDE не забудьте также установить подключаемый модуль Cloud Conformity Security, как показано здесь:

  • VSCode Marketplace подключаемый модуль Cloud ConformityССЛКА

Расширение для сканирования шаблонов Cloud One Conformity Template ScannerРасширение для сканирования шаблонов Cloud One Conformity Template Scanner

Создайте свою учетную запись в Cloud One Conformity, воспользовавшись приведенной здесь ссылкой (создать учетную запись), войдите в нее и сформируйте токен API.

В среде Cloud Conformity: щелкнитеимя пользователявверху справа и выберите User Settings (Настройки пользователя)API Keys (Ключи API)New API Key (Создать ключ API), чтобы создать ключ API, который будет использоваться в подключаемом модуле VSCode. Обязательно скопируйте ключ и сохраните его в надежном месте. Получить его еще раз невозможно.

Скопируйте ключ API и вернитесь в среду VSCode

1. Щелкните значок расширений (слева) и нажмитеExtension Settings (Настройки расширений) для записи сканера шаблонов Cloud Conformity Template Scanner.

2. В среде Cloud Conformity выберитеEdit in settings.json (Изменить файл settings.json). Перейдите к разделу ApiKey.

3. Введите ключ API, сформированный на предыдущем шаге, и сохраните изменения.

Теперь шаблоны CloudFormation можно сканировать с использованием сотен проверок на соответствие правилам AWS Well-Architected Framework и другим стандартам, гарантируя превосходное качество разрабатываемой облачной инфраструктуры.

Вот пример шаблона CloudFormation, не соответствующего передовым методам разработки, который вы можете протестировать:

Чтобы испытать расширение в работе, откройте указанный выше шаблон CloudFormation с помощью VSCode, после чего откройтепалитру команд, нажав:

  • macOS: + + P

  • Windows/Linux:Ctrl + Shift + P

Найдите элемент Cloud One Conformity: Scan the Current Open Template (Cloud One Conformity: сканировать открытый шаблон) и нажмите <Enter>, после чего автоматически начнется сканирование этого шаблона CloudFormation:

Результат сканирования появится на второй вкладке под названием Scan Result (Результат сканирования), как на приведенном ниже изображении. Также можно воспользоватьсяоблачной базой знаний, которая поможет лучше понять выявленные нарушения рекомендуемых методов, а также узнать способы их устранения в вашем шаблоне CloudFormation или в производственных средах:

Превосходно, первый этап автоматизации безопасности IaC завершен.

Этап2. Создание конвейера CI/CD средствами AWS с последующей интеграцией в него сканера шаблонов Conformity

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

С помощью такого шлюза можно отслеживать и выполнять аудит всех изменений в реальном времени и только затем производить развертывание в среду AWS.

В этом разделе мы покажем, как использовать сканер шаблонов в конвейере CI/CD сAWS CodeCommit,AWS CodeBuild иAWS CodePipeline. Приступим.

CodeCommit будет использоваться в качестве репозитория для размещения нашего кода. Многие люди и компании со всего мира используют для этого GitHub, GitLab и BitBucket. Чтобы ваша среда VSCode могла перемещать код в CodeCommit, нужно будет кое-что настроить.

Создание репозитория Git в AWS CodeCommit

  • Создайте пользователя IAM с разрешением использовать CodeCommit и доступом только с помощью ключа SSH. (Сведения о разрешении для Git и рекомендации приведены по этойссылкеот AWS.)

Перейдите в раздел Security credentials (Учетные данные безопасности) HTTPS Git credentials for AWS CodeCommit (Учетные данные Git HTTPS для AWS CodeCommit), чтобы сформировать учетные данные.

Загрузите учетные данные и сохраните их в надежное место.

Подробнее об этой процедуре на сайте AWS ССЛКА.

  • Создайте репозиторийAWS CodeCommit.

Можно выполнить команду git clone на своем компьютере и легко перенести на него git config.

git clone https://git-codecommit.us-east-1.amazonaws.com/v1/repos/{ИМЯ ВАШЕГО РЕПОЗИТОРИЯ}

Теперь можете воспользоваться собственным шаблоном CloudFormation или взять плохой шаблон CloudFormation, который я предоставил ранее, для передачи в AWS CodeCommit.

git add .

git commit -m "First Commit"

git push

Итак, первое сохранение в AWS CodeCommit выполнено. Теперь давайте перейдем к AWS CodeBuild и AWS CodePipeline.

Создание CodeBuild для автоматического сканирования шаблонов CloudFormation

Решение AWS CodeBuild очень похоже на GitHub Actions, Azure DevOps и GitLab. Это технология CI/CD, с помощью которой можно создавать новые проекты, автоматизировать процессы и развертывать новые приложения или инфраструктуры.

Мы будем использовать CodeBuild с определенным образом контейнера для запуска сканера шаблонов Conformity с целью выявления возможных проблем перед развертыванием новой IaC в производственной среде.

  • Создание проекта построения в AWS CodeBuild

Я буду использовать стандартный образ от AWS, но вы можете взять другой образ управления или создать собственный для автоматизации подобного типа.

Вот конфигурация для приведенного ниже образа:

Environment image: Managed ImageOperationg System: Amazon Linux 2Runtime: StandardImage: aws/codebuild/amazonlinux2-x8664-standard:3.0Image version: Always use the latestEnvironment type: LinuxService Role: NewRole Name: <NEW ROLE NAME>

Вот конфигурация для приведенного ниже образа:

Environment VariablesCC_API_KEY = <YOUR API KEY FROM CLOUD ONE - CONFORMITY>CC_REGION = <REGION SELECTED TO CREATE YOUR CONFORMITY TENANT>CC_RISK_LEVEL = <RISK LEVEL NOT BE ACCEPT>CFN_TEMPLATE_FILE_LOCATION = <YOUR CLOUDFORMATION TEMPLATE PATH>STACK_NAME = <THE CLOUDFORMATION STACK NAME

Вот конфигурация для приведенного ниже образа:

Build specifications: Insert build commandsBuild Commands:version: 0.2phases:    install:        runtime-versions:            python: 3.7    pre_build:        commands:            - pip3 install awscli --upgrade --user    build:        commands:            - pip3 install -r https://raw.githubusercontent.com/OzNetNerd/Cloud-Conformity-Pipeline-Scanner/master/requirements.txt            - wget https://raw.githubusercontent.com/OzNetNerd/Cloud-Conformity-Pipeline-Scanner/master/src/scanner.py            - CC_API_KEY=`jq -r '.CC_API_KEY' <<< $CC_API_KEY`            - python3 scanner.py                post_build:        commands:            - aws cloudformation deploy --template-file $CFN_TEMPLATE_FILE_LOCATION --stack-name $STACK_NAME --no-fail-on-empty-changese

Ссылка на Buildspec.yml на GitHub https://raw.githubusercontent.com/fernandostc/IaC-Security-Automation/master/buildspec.yml

Примечание. Сканер шаблонов, который я использую в этом примере, был создан Уиллом Робисоном (Will Robison) архитектором решений из компании Trend Micro вот ссылка на этот проект на GitHub:https://github.com/OzNetNerd/Cloud-Conformity-Pipeline-Scanner

Теперь нажмите Create build project (Создать проект построения), чтобы завершить процесс.

Создание секретного ключа в диспетчере секретов для хранения ключа API Cloud One Conformity

Теперь нажмите Store (Сохранить), чтобы завершить процесс.

Важно!Не забудьте создать политику, чтобы назначить своей службе ролей в CodeBuild следующее разрешение, с которым CodeBuild сможет получать значение от SecretManager:

{    "Version": "2012-10-17",    "Statement": [        {            "Sid": "VisualEditor0",            "Effect": "Allow",            "Action": "secretsmanager:GetSecretValue",            "Resource": "arn:aws:secretsmanager:us-east-1:<AWS Account ID>:secret:<Secret Key ARN>"        }    ]}

Создание AWS CodePipeline для автоматизации триггера запуска сканирования шаблонов

AWS CodePipeline поможет автоматизировать процесс запуска сканера шаблонов после каждого обновления кода IaC. В CodePipeline можно создать несколько этапов, однако наш пример простой, для него достаточно одного этапа. Он очень прост для понимания.

Создадим новый конвейер в AWS CodePipeline

Можно задать имя конвейеру и создать новую роль службы, если до этого у вас ни одной роли службы не было.

Теперь нужно задать следующие параметры с учетом созданного ранее CodeCommit:

Source Provider (Поставщик исходного кода): <AWS CodeCommit>Repository Name (Имя репозитория): <CloudOneConformity или заданное вами ранее имя репозитория>Branch Name (Имя ветви): <master>

Теперь нужно задать информацию о процессе построения:

Build provider (Поставщик построения): <AWS CodeBuild>Project Name (Имя проекта): <IaC-Security-Automation или заданное вами ранее имя репозитория>

В данном случае мы не планируем использовать команды развертывания, поскольку развертывание мы выполняем внутри CodeBuild в рамках одного из этапов.Можно нажать Skip the deploy stage (Пропустить этап развертывания).

Можно просмотреть конфигурацию и нажать Create pipeline (Создать конвейер).

Тестирование автоматизированного конвейера

1. Создайте собственный шаблон CloudFormation или воспользуйтесь вот этим:ссылка.

2. Просканируйте шаблон CloudFormation с помощью подключаемого модуля Cloud One Conformity и проверьте результат.

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

3. Шаблон можно отправить в репозиторий, чтобы проверить его работоспособность в конвейере. Это своеобразный тест перед исправлением всех проблем, найденных в шаблоне CloudFormation.

4. Сохраните новый код в CodeCommit:

git add .git commit -m "Test Automation"git push

5. Сохранение нового кода в CodeCommit приведет к запуску CodePipeline.

После завершения процесса автоматизации вы увидите, что оба этапа выполнены успешно.

ПРИМЕЧАНИЕ.В случае возникновения каких-либо проблем проверьте процесс построения. Перейдите к последнему построению и просмотрите его журналы. Они очень полезны для отладки в случае возникновения затруднений.

Заключение

Итак, у вас есть полностью автоматизированный конвейер IaC с AWS CodeCommit, AWS CodeBuild, AWS CodePipeline и Cloud One Conformity, позволяющий анализировать всевозможные отклонения конфигурации в ходе процесса создания шаблонов CloudFormation.

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

Благодарности

Хочу сказать БОЛЬШОЕ спасибо некоторым людям, которые своими потрясающими отзывами помогли мне сделать эту статью лучше:

  • Raphael Bottino (Рафаэль Боттино)

  • Melissa Clow (Мелисса Клоу)

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


- Узнать подробнее о курсе"Infrastructure as a code in Ansible".


- Записаться на супер-интенсив IaC Ansible

Подробнее..

Перевод Интеграция CICD для нескольких сред с Jenkins и Fastlane. Часть 3

22.11.2020 18:15:46 | Автор: admin

Всем привет. В преддверии старта курсов "iOS Developer. Basic" и "iOS Developer. Professional", публикуем заключительную часть статьи про интеграцию CI/CD для нескольких сред с Jenkins и Fastlane.

А также приглашаем вас на бесплатный демо-урок по теме: "Combine до iOS 13 и как добавить SwiftUI 2.0 в любое приложение"


Настройка Jenkins под разные среды

Подробнее..

Настройка CICD скриптов миграции БД с нуля с использованием GitLab и Liquibase

17.05.2021 14:19:21 | Автор: admin

Пролог

Добрый день, уважаемые читатели. Совсем недавно мне пришлось осваивать новую для себя область CI/CD, настраивая с нуля доставку скриптов миграции базы данных в одном из проектов. При этом было тяжело преодолеть самый первый этап "глаза боятся", когда задача вроде бы ясна, а с чего начать, не знаешь. Однако вопрос оказался на поверку значительно проще, чем казалось изначально, давая при этом неоспоримые преимущества ценой нескольких часов работы и не требуя никаких дополнительных средств, кроме обозначенных в заголовке.

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

Оглавление

Введение

Об авторе

Меня зовут Копытов Дмитрий, я являюсь главным разработчиком и архитектором проектов. Специализируюсь на C#/.NET, Vue.js и Postgres.

К теме CI/CD пришел после получения "в наследство" проекта с уже существующим CI/CD, который пришлось перерабатывать.

О статье

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

Целью статьи является подробное описание технических аспектов настройки связки GitLab + Liquibase на конкретном примере, а также основы теории, чтобы у читателя отложилась не только сама инструкция, но и понимание процесса. Многие моменты и возможности GitLab CI/CD & Liquibase будут сознательно опущены во избежание перегрузки информацией.

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

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

Кулинарный рецепт

Для приготовления блюда нужны следующие ингредиенты:

  • GitLab Community Edition хранилище Git

  • GitLab Runner рабочая лошадка CI/CD

  • Liquibase, на момент написания статьи версия 4.3.4

  • Драйвер Liquibase для целевой БД

  • Выделенный сервер с установленными Liquibase и GitLab Runner и доступом к целевой БД для наката скриптов и к GitLab для получения исходников. Необязательно.

  • 3 чашки чая или кофе. Обязательно.


Основы

Используемые технологии

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

Примеры из статьи (скрипты и конфиги) опубликованы на гитхабе: https://github.com/Doomer3D/Gliquibase.

GitLab CI/CD

CI/CD у всех на слуху, поэтому не буду вдаваться в детали, отмечу только, что в контексте статьи будет рассмотрен процесс автоматического (при мерже/коммите) или ручного (по команде) наката скриптов миграции базы данных на целевую среду, будь то дев или прод. В качестве конвейера будут использоваться GitLab pipelines, а непосредственным исполнителем будет GitLab Runner, поэтому если вы не используете GitLab в качестве хранилища, статья вам будет по большому счета бесполезна.

Все примеры будут рассмотрены в контексте используемого в нашей компании GitLab Community Edition13.x.

Liquibase

Liquibase (ликви) платформа c открытым кодом, которая позволяет управлять миграциями вашей базы. Если кратко, то Liquibase позволяет описывать скрипты наката и отката базы в виде файлов чейнжсетов (changeset). Сами скрипты при этом могут быть как обычными SQL-командами, так и БД-независимыми описаниями изменений, которые будут преобразованы в скрипт конкретно для вашей базы. Список поддерживаемых БД можно найти здесь: https://www.liquibase.org/get-started/databases.

Liquibase написан на Java, поэтому может быть запущен на любой машине с JVM.

В статье будет рассмотрен Oracle 19 в качестве целевой базы, потому что именно для него настраивал деплой на рабочем проекте. Но, как писал выше, тип базы не имеет принципиального значения.

Схема работы

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

Когда разработчик делает коммит или мерж реквест, запускается конвейер CI/CD, называемый пайплайном (pipeline), который включает в себя этапы (stage), состоящие из джобов (job).

В нашем примере будет один этап деплой (deploy), т.е. применение скриптов наката. При этом будет столько джобов, сколько стендов мы имеем. Предположим, что у нас всего два стенда дев и прод. Соответственно, будет два джоба деплой на дев (deploy-dev) и деплой на прод (deploy-prod), которые будут отличаться целевой БД. Кроме того, деплой на прод мы хотим запускать только вручную, по команде, а не в момент мержа или коммита.

Джобы обрабатываются раннером (GitLab Runner) специальной программой, которая скачивает себе исходники проекта и выполняет скрипты на сервере деплоя, при этом раннеру передаются различные параметры в виде переменных окружения. Это может быть пользовательская информация, хранимая в настройках проекта, такая как адрес и логин/пароль сервера БД, а также информация о самом процессе CI/CD, например, название ветки, автор, текст коммита и т.п.

Важно! Раннер сам обращается к GitLab по http, а не наоборот, поэтому машина с раннером должна иметь доступ к GitLab, в то время как разработчик и сервер GitLab могут ничего не знать о машине, где находится раннер.

Если оба стенда дев и прод, доступны с одной машины, достаточно будет одного раннера, однако обычно прод находится в другой сети, тогда в ней должен быть отдельный раннер с доступом к БД прода.

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

В случае успеха, если скрипты отработали без ошибок, пайплайн считается успешным, а мерж реквест вливается в ветку. Картина будет примерно такой:

В противном случае пайплайн будет в состоянии failed:

Мы сможем провалиться в джоб и посмотреть логи, чтобы найти ошибку или перезапустить пайплайн, если проблема была в сетевом доступе. В моей практике была ситуация, что поднятый VPN на машине деплоя в какой-то момент стал блокировать доступ к GitLab по доменному имени, при этом пайплайн оказывался в состоянии pending, т.е. ожидал, что раннер подхватит джоб, но этого не происходило. Правка hosts и перезапуск пайплайна разрешили проблему.

Итак, подытожим, как выглядит схема работы:

  1. Разработчик делает мерж реквест или коммит в ветку.

  2. Запускается пайплайн, который исполняет ассоциированный с этой веткой джоб.

  3. Джоб инициирует раннер, находящийся на удаленной машине.

  4. Раннер скачивает исходники и выполняет скрипт, вызывающий Liquibase.

  5. Liquibase генерирует и исполняет скрипты наката/отката.

  6. ...

  7. Profit!

А теперь обо всем по порядку.


Liquibase

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

Ссылка на официальную документацию по Liquibase: https://docs.liquibase.com/concepts/basic/home.html

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

Liquibase это платформа управления и наката миграций БД. В основе лежит понятие чейнжсета (changeset) атомарного изменения базы. Чейнжсетом, например, может быть создание таблицы в базе, добавление колонки/триггера, или наполнение таблицы данными. Чейнжсеты объединяются в чейнжлоги (changelog), которые выстраиваются в цепочку, которая применяется на целевой БД последовательно, таким образом обновляя базу до актуального состояния.

changelog

Начнем рассмотрение Liquibase с понятия чейнжлога. Чейнжлог это отдельный файл, содержащий в себе чейнжсеты или ссылки на другие чейнжлоги. Порядок включения чейнжлогов/чейнжсетов определяет порядок их наката. Как и чейнжсеты, чейнжлоги могут быть описаны в одном из четырех форматов: SQL, XML, JSON и YAML. В статье будут рассмотрены форматы SQL и XML как наиболее популярные и удобочитаемые.

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

  • Папка со временем разрослась до гигантских размеров, и ориентироваться в ней стало невозможно.

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

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

Структура проектаСтруктура проекта

Файлы Liquibase хранятся в папке db/changelog, в корне лежит мастер-файл master.xml

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

Разберем файл master.xml:

<?xml version="1.0" encoding="UTF-8"?><databaseChangeLog xmlns="http://personeltest.ru/away/www.liquibase.org/xml/ns/dbchangelog"                   xmlns:pro="http://personeltest.ru/away/www.liquibase.org/xml/ns/pro"                   xmlns:xsi="http://personeltest.ru/away/www.w3.org/2001/XMLSchema-instance"                   xsi:schemaLocation="http://personeltest.ru/away/www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.2.xsd     http://www.liquibase.org/xml/ns/pro http://www.liquibase.org/xml/ns/pro/liquibase-pro-4.2.xsd ">  <preConditions>    <dbms type="oracle" />  </preConditions>  <!-- предварительные скрипты -->  <include file="/common/pre_migration.xml" />  <!-- релизы -->  <includeAll path="/v156" relativeToChangelogFile="true" />  <includeAll path="/v157" relativeToChangelogFile="true" /></databaseChangeLog>

XML-файл выглядит несколько перегруженным неймспейсами, пусть это вас сильно не волнует. Это стандартная обертка для любого XML-файла Liquibase.

Раздел preConditions определяет различные параметры запуска, конкретно здесь мы указываем, что используем oracle, но также можно указать имя пользователя, от которого будет происходить запуск скриптов. Более плотно preConditions используются в чейнжсетах.

Далее идет включение скриптов в нужной последовательности. Для этого существуют команды include (включение отдельного файла) и includeAll (включение папки).

Использование includeAll позволяет избежать проблем с конфликтами при правке файла-аккумулятора внутри релиза, но тогда нужно выработать стратегию именования файлов, т.к. их включение будет происходить в алфавитном порядке. В примере выше я использую дату + номер таска, но этого будет недостаточно в реальном проекте, если изменения по таску с меньшим номером нужно применить после таска с большим номером, и при этом они будут в один день.

Как альтернативу includeAll вы можете внутри папки релиза создать свой мастер-файл (аккумулятор скриптов релиза), в который включать ссылки на отдельные скрипты, что в целом менее удобно.

changeset

Чейнжсеты описываются внутри файлов чейнжлогов в виде отдельных блоков. Рассмотрим на примере файла 2021-05-01 TASK-001 CREATE TEST TABLE.xml:

<?xml version="1.0" encoding="UTF-8"?><databaseChangeLog xmlns="http://personeltest.ru/away/www.liquibase.org/xml/ns/dbchangelog"                   xmlns:pro="http://personeltest.ru/away/www.liquibase.org/xml/ns/pro"                   xmlns:xsi="http://personeltest.ru/away/www.w3.org/2001/XMLSchema-instance"                   xsi:schemaLocation="http://personeltest.ru/away/www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.2.xsd     http://www.liquibase.org/xml/ns/pro http://www.liquibase.org/xml/ns/pro/liquibase-pro-4.2.xsd ">  <changeSet author="Doomer" id="20210501-01">    <preConditions onFail="MARK_RAN">      <not>        <tableExists tableName="TEST"/>      </not>    </preConditions>    <createTable tableName="TEST" remarks="Тестовый справочник">      <column name="ID" type="NUMBER(28,0)" remarks="Идентификатор">        <constraints nullable="false" primaryKey="true" primaryKeyName="TEST_PK" />      </column>      <column name="CODE" type="VARCHAR2(64)" remarks="Код">        <constraints nullable="false" />      </column>      <column name="NAME" type="VARCHAR2(256)" remarks="Наименование">        <constraints nullable="false" />      </column>    </createTable>    <rollback>      <dropTable tableName="TEST" />    </rollback>  </changeSet></databaseChangeLog>

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

Элемент preConditions указывает, что чейнжсет нужно применять только в случае, если таблица не существует в базе.

Элемент rollback указывает, как нужно откатывать скрипт, если такой механизм планируется использовать. Откаты в нашей стране, конечно, популярны, но выходят за рамки данной статьи.

Рассмотрим основные атрибуты чейнжсетов.

Атрибут

Описание

id

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

author

Автор, обязательный атрибут, использование см. ниже.

dbms

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

contexts

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

Подробнее по ссылке.

runAlways

Булево. Указывает, что чейнжсет применяется при каждом запуске. Полезно для скриптов установки контекста окружения, см. ниже.

runOnChange

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

Подробную документацию по чейнжсетам можно почитать по ссылке.

DATABASECHANGELOG

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

В таблице нет физических ключей, но у самих чейнжсетов есть уникальный ключ это комбинация идентификатора, автора и имени файла. Политика именования чейнжсетов у всех может быть своя, у нас исторически сложился формат <дата>-<номер><ФИ автора>, например 20210501-01KD. Отмечу, что это мой первый проект с использованием Liquibase, поэтому не буду давать советов, как лучше именовать чейнжсеты.

Также у каждого чейнжсета вычисляется MD5-сумма на основе его тела, поэтому нельзя просто так менять файл чейнжсета, если он уже был применен к базе. Это может иметь значение при разработке, особенно при освоении Liquibase. Поначалу вы будете часто ошибаться с тем, как правильно составить чейнжсет, в итоге в базе окажется сохраненный чейнжсет с MD-5 суммой и неверно выполненный скрипт. В этом случае нужно будет вручную откатить его изменения, а также удалить соответствующие записи из DATABASECHANGELOG.

runAlways

База данных предоставляет инструменты для настройки контекста окружения, что может быть полезно, например, в сценарии аудита таблиц, когда записи, измененные скриптами, выполненных через Liquibase, нужно как-то помечать. Мы это решаем через установку переменной сессии USER_ID.

Для этой цели в описании мастер-чейнжлога включается скрипт пре-миграции, который занимается настройкой окружения. Приведу его текст:

<?xml version="1.0" encoding="UTF-8"?><databaseChangeLog xmlns="http://personeltest.ru/away/www.liquibase.org/xml/ns/dbchangelog"                   xmlns:pro="http://personeltest.ru/away/www.liquibase.org/xml/ns/pro"                   xmlns:xsi="http://personeltest.ru/away/www.w3.org/2001/XMLSchema-instance"                   xsi:schemaLocation="http://personeltest.ru/away/www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.2.xsd     http://www.liquibase.org/xml/ns/pro http://www.liquibase.org/xml/ns/pro/liquibase-pro-4.2.xsd ">  <changeSet author="SYSTEM" id="PRE_MIGRATION" runAlways="true">    <sql splitStatements="true" stripComments="true">      -- устанавливаем в контекст сессии пользователя liquibase      CALL DBMS_SESSION.SET_CONTEXT('CLIENTCONTEXT','USER_ID', 13);    </sql>  </changeSet></databaseChangeLog>

Здесь в качестве тела используется SQL-скрипт, который записывает в переменную сессии USER_ID значение 13 наш внутренний идентификатор пользователя Liquibase. Этот скрипт будет влиять на все последующие скрипты, поэтому помечен флагом runAlways и включен перед скриптами релизов.

SQL-чейнжсет

Чейнжсеты можно оформлять и в формате SQL, что особенно полезно при написании сложных запросов. Рассмотрим файл 2021-05-01 TASK-002 TEST.sql, который выполняется сразу после создания таблицы TEST:

--liquibase formatted sql--changeset Doomer:20210501-02--preconditions onFail:MARK_RAN--precondition-sql-check expectedResult:1 SELECT COUNT(*) FROM ALL_TABLES WHERE TABLE_NAME = 'TEST' AND OWNER = 'STROY';--precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM TEST WHERE ID = 1;insert into TEST (ID, CODE, NAME)values (1, 'TEST', 'Какое-то значение');--rollback not required--changeset Doomer:20210501-03--preconditions onFail:MARK_RAN--precondition-sql-check expectedResult:1 SELECT COUNT(*) FROM ALL_TABLES WHERE TABLE_NAME = 'TEST' AND OWNER = 'STROY';--precondition-sql-check expectedResult:1 SELECT COUNT(*) FROM TEST WHERE ID = 1;update TEST   set NAME = 'CONTEXT USER_ID=' || nvl(SYS_CONTEXT('CLIENTCONTEXT', 'USER_ID'), 'NULL') where ID = 1;--rollback not required

Это файл с двумя чейнжсетами в составе.

Первый добавляет новую запись в таблицу TEST, проверяя существование таблицы и отсутствие элемента с ID = 1. Если одно из условий не выполнится, чейнжсет не будет применен, но будет помечен в DATABASECHANGELOG как выполненный (MARK_RAN). Подробнее можно почитать в документации по preConditions.

Второй чейнжсет обновляет созданную запись значением из переменной сессии USER_ID.

После наката скриптов мы предсказуемо увидим следующую картину в таблице TEST:

К сожалению, формат статьи и ее и без того обширная тема не позволяет описать чейнжсеты Liquibase более подробно, но тут можно только сказать классическое RTFM.

Командная строка Liquibase

Liquibase является консольным приложением, поэтому нужно понимать, как его вызывать. Вообще, это тема больше относится к области GitLab Runner'а, но чтобы не смешивать обе темы, рассмотрим вызов Liquibase в этом разделе.

Документация по командам Liquibase: https://docs.liquibase.com/commands/community/home.html

Нас в первую очередь интересуют команды:

  • update применение изменений

  • updateSQL получение SQL-скриптов для анализа, полезно для обучения

Обе команды имеют схожий набор параметров, поэтому рассмотрим основные из них на нашем тестовом примере:

Параметр

Описание

Пример значения

changeLogFile

Путь к мастер-файлу чейнжлога, обязательный параметр

master.xml

url

Адрес БД, обязательный параметр

jdbc:oracle:thin:1.2.3.4:1521:orastb

username

Логин пользователя БД, обязательный параметр

vasya

password

Пароль пользователя БД, обязательный параметр

pupkin

defaultSchemaName

Имя схемы по умолчанию

DATA

contexts

Контекст БД для фильтров чейнжсетов по контексту

dev / prod

driver

Тип драйвера БД

oracle.jdbc.OracleDriver

classpath

Путь до драйвера

/usr/share/liquibase/4.3.4/drivers/ojdbc10.jar

outputFile

Путь до файла, куда выводить результат для команды updateSQL. Если не указано, вывод будет в консоль.

Для обучения можно создать батник, который будет запускать Liquibase c вашим скриптом до его добавления в гите. Разумеется, надо помнить о необходимости отката изменений (см. выше). Рядом помещаете папку с Liquibase и драйвером, об установке которых рассказывается в следующем разделе, а также файл скрипта.

Пример батника для Windows:

call "C:\Temp\liqui\liquibase-4.3.1\liquibase.bat" ^--defaultSchemaName=STROY ^--driver=oracle.jdbc.OracleDriver ^--classpath="C:\Temp\liqui\ojdbc5.jar" ^--url=jdbc:oracle:thin:@1.2.3.4:1521:dev ^--username=xxx ^--password=yyy ^--changeLogFile=.\master.xml ^--contexts="dev"--logLevel=info ^updateSQL

Настройка сервера деплоя

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

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

Установка Java

Liquibase рекомендует использовать Java 11+, давайте установим его. Я использую OpenJRE 11:

sudo yum install java-11-openjdkjava --version

Установка Liquibase

Официальная документация: https://www.liquibase.org/get-started/quickstart

Liquibase не требует установки, потому что является программой на Java. Заходим на сайт и скачиваем архив с программой. Архив распаковываем в папку по вашему усмотрению, но для себя я решил использовать путь /usr/share/liquibase/<version>, например /usr/share/liquibase/4.3.4

Там же, в папке с установленным Liquibase, создаем папку drivers и копируем в нее нужный драйвер для вашей БД. В моем случае это был ojdbc10.jar

Проверяем, что Liquibase работает:

cd /usr/share/liquibase/4.3.4liquibase --version

Установка Git

Git является зависимостью GitLab Runner и ставится автоматически, но нужно отметить одну неприятную особенность, характерную для Centos 7 и, возможно, для других версий пингвинообразных. Если вы просто установите GitLab Runner на голую ось, он потянет за собой git в виде зависимости, который в стандартном репозитории имеет версию 1.8. Эта версия откровенно баганая, что в связке с GitLab приводит к тому, что в какой-то момент, причем не сразу, CI/CD перестает работать с выдачей совершенно непонятной ошибки.

Чтобы избежать неприятных сюрпризов, необходимо установить более свежую версию git до установки GitLab Runner:

# проверяем текущую версию гитаgit --version# удаляем гит, если он версии 1.8sudo yum remove git*# устанавливаем последнюю версию гита на момент написания статьи (2.30)sudo yum -y install https://packages.endpoint.com/rhel/7/os/x86_64/endpoint-repo-1.7-1.x86_64.rpmsudo yum install git

Установка GitLab Runner

Официальная документация: https://docs.gitlab.com/runner/install/linux-manually.html

# добавляем репуcurl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.rpm.sh" | sudo bash# устанавливаемexport GITLAB_RUNNER_DISABLE_SKEL=true; sudo -E yum install gitlab-runner

Настройка GitLab Runner

В этом разделе будет рассказано о том, как создать и настроить свой экземпляр GitLab Runner, который будет вызывать Liquibase.

Ссылка на официальную документацию по GitLab Runner: https://docs.gitlab.com/runner/configuration/

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

# определяем место установкиwhich gitlab-runner # /usr/bin/gitlab-runner# выдаем права на исполнениеsudo chmod +x /usr/bin/gitlab-runner

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

# создаем пользователяsudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash# запускаем демонаsudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner

Управлять сервисом раннера можно по аналогии с systemctl:

# статус сервисаsudo gitlab-runner status# запуск сервисаsudo gitlab-runner start# останов сервисаsudo gitlab-runner stop# получения списка зарегистрированных раннеровsudo gitlab-runner list

После завершения настройки сервиса GitLab Runner нужно создать экземпляр раннера для запуска Liquibase. Для этого воспользуемся командой register, но перед этим нам нужно получить токен для нашего проекта в GitLab.

Переходим в интерфейсе GitLab в раздел Settings CI/CD Runners. Здесь мы видим список доступных нам раннеров, которые либо привязаны к группе проектов, либо к конкретному проекту. У меня этот список выглядит примерно так (немного законспирировал имена):

Нас интересуют два пункта:

  1. Адрес вашего GitLab. Раннер будет обращаться к нему для получения исходников и команд на запуск джобов.

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

Также у каждого раннера вы можете видеть теги, выделенные синим цветом. Это своего рода метки, по которым определяется, будет ли использовать раннер для конкретного джоба, об этом подробнее будет рассказано ниже. Пока достаточно знать, что раннер будет запущен, если его теги содержат теги джоба.

Регистрация раннера

Для создания раннера нужно выполнить команду sudo gitlab-runner register

Вас попросят ввести следующие параметры:

  1. Enter the GitLab instance URL

    Вводим адрес вашего GitLab

  2. Enter the registration token

    Вводим токен

  3. Enter a description for the runner

    Вводим имя раннера, например, my-awesome-runner

  4. Enter tags for the runner

    Вводим теги раннера через запятую. Пример: liquibase,dev

    Выбор можно позже изменить через интерфейс GitLab CI/CD

  5. Enter an executor

    Выбираем механизм исполнения раннера. Подробнее по ссылке.

    Вводим shell

shell это простейший механизм исполнения команд оболочки целевой ОС. Мы будем использовать скрипты на bash.

После регистрации раннера можно проверить его наличие через команду sudo gitlab-runner list или через интерфейс GitLab CI/CD:

my-awesome-runnermy-awesome-runner

Настройка CI/CD

Информация о том, что делать в процессе CI/CD хранится в файлах проекта. Главным является файл .gitlab-ci.yml в корне. Остальные файлы, в нашем случае скрипты на bash, размещаются на усмотрение разработчика, например, в папке /ci.

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

Для начала отмечу, что есть встроенный линтер для файла .gitlab-ci.yml, доступный по относительному пути ci/lint в проекте, например: https://gitlab.example.com/gitlab-org/my-project/-/ci/lint. Рекомендую воспользоваться им перед пушем конфига. Также предполагается, что вы знакомы с форматом YAML.

Полный листинг конфига в тестовом проекте:

variables:    LIQUIBASE_VERSION: "4.3.4"stages:    - deploydeploy-dev:    stage: deploy    tags:        - liquibase        - dev    script:        - 'bash ./ci/deploy-db.sh $DEV_DB $DEV_DB_USER $DEV_DB_PASS'    environment:        name: dev    only:        - devdeploy-prod:    stage: deploy    tags:        - liquibase        - prod    script:        - 'bash ./ci/deploy-db.sh $DEV_DB $DEV_DB_USER $DEV_DB_PASS'    environment:        name: prod    when: manual    only:        - prod

Переменные

Документация: https://docs.gitlab.com/ee/ci/variables/README.html

variables:    LIQUIBASE_VERSION: "4.3.4"

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

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

Переменные проекта можно настроить в разделе Settings CI/CD Variables.

Пример того, как выглядят переменные в интерфейсе:

CI/CD VariablesCI/CD Variables

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

Этапы (stages)

stages:    - deploy

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

Джобы (jobs)

Рассмотрим на примере джоба деплоя на дев:

deploy-dev:    stage: deploy    tags:        - liquibase        - dev    script:        - 'bash ./ci/deploy-db.sh $DEV_DB $DEV_DB_USER $DEV_DB_PASS'    environment:        name: dev    only:        - dev

Строка stage: deploy указывает на этап, к которому относится данный джоб.

Теги перечислены массивом, т.е. это liquibase и dev. Наш ранее созданный раннер отреагирует на эти теги и запустит скрипты в разделе script. Важно отметить, что в джобе прода указан тег prod, поэтому раннер для него должен быть отдельный, исходя из соображений, что он, вероятно, находится в другой сети. А вообще, один и тот же раннер может обслуживать несколько проектов, если это требуется.

Блок environment указывает на окружение, которое можно настроить в отдельном разделе по пути Operations Environments. Это своего рода дашборд ваших стендов (дев, предпрод, прод и т.п.), где можно увидеть их статус и выполнить, например, ручной деплой. Также можно настроить переменные, привязанные к окружению, но это премиум-фича, которую мне попробовать не удалось.

Пример страницы окружений:

EnvironmentsEnvironments

Блок only указывает, для каких веток нужно применять данный джоб. Его братом является блок except, который указывает для каких веток джоб применять не нужно. Помимо веток можно настроить более сложные условия: https://docs.gitlab.com/ee/ci/jobs/job_control.html.

Для прода мы также указали when: manual. Это означает, что запуск деплоя для прода будет в ручном режиме при нажатии на кнопку в пайплайне, а не в момент коммита. Это позволит нам собрать чемодан до того, как что-то пойдет не так выполнить деплой в запланированное время.

Мерж в прод, ручной запускМерж в прод, ручной запуск

Скрипт наката

В блоке script мы указываем список скриптов, которые будут выполнены раннером. Так как у нас shell-исполнитель на линуксе, будем использовать bash. Все переменные автоматически передаются в скрипт в виде переменных окружения, но т.к. реквизиты базы отличаются для разных стендов, передаем их непосредственно при вызове скрипта.

Разберем непосредственно скрипт вызова Liquibase:

#!/bin/bashecho "Environment: $CI_ENVIRONMENT_NAME"cd db/changelog/usr/share/liquibase/$LIQUIBASE_VERSION/liquibase \    --classpath=/usr/share/liquibase/$LIQUIBASE_VERSION/drivers/ojdbc10.jar \    --driver=oracle.jdbc.OracleDriver \    --changeLogFile=master.xml \    --contexts="$CI_ENVIRONMENT_NAME" \    --defaultSchemaName=STROY \    --url=jdbc:oracle:thin:@$1 \    --username=$2 \    --password=$3 \    --logLevel=info \    update

Подробно параметры вызова Liquibase описаны в соответствующем разделе.

Переменные DEV_DB, DEV_DB_USER, DEV_DB_PASS приходят в скрипт в виде $1, $2 и $3 соответственно. Помимо них мы используем указанное в джобе имя окружения, которое приходит в предопределенной переменной $CI_ENVIRONMENT_NAME, что можно использовать, чтобы какие-то скрипты накатывались только на определенном стенде, а не на всех.

Если все настроено правильно, то после коммита конфига и скриптов в гит, заработает деплой.

Успешный лог пайплайна для Liquibase выглядит примерно так:

Отклонение мержей с ошибками

Если процесс деплоя пойдет с ошибками, можно не допустить соответствующий мерж. Особенно это полезно, если в CI/CD встроены тесты как отдельный этап. В нашем примере тестов нет, но Liquibase тоже может сломаться, если, например, указаны неверные пути до файлов.

Для запрета ошибочных мержей нужно установить галочку в разделе General Merge requests.

Важно! Галочку нужно устанавливать после настройки CI/CD, иначе перестанут проходить любые мержи.


Заключение

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

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

Как обычно, если понравилась статья, посмотрите другие:

Подробнее..

Как мы строили параллельные вселенные для нашего (и вашего) CICD пайплайна в Octopod

08.02.2021 18:21:29 | Автор: admin

Как мы строили параллельные вселенные для нашего (и вашего) CI/CD пайплайна в Octopod



Привет, Хабр! Меня зовут Денис и я вам расскажу как нас надоумило сделать техническое решение для оптимизации процесса разработки и QA у себя в Typeable. Началось с общего ощущения, что вроде делаем все правильно, но все равно можно было бы двигаться быстрее и эффективнее принимать новые задачки, тестировать, меньше синхронизироваться. Это все нас привело к дискуссиям и экспериментам, результатом которых стало решение, которое я опишу ниже.

Давайте, впрочем, не будем бежать впереди паровоза, а начнем с самого начала и разберемся предметно в том, о чем я говорю. Представим достаточно стандартную ситуацию проект с трехуровневой архитектурой (хранилище, бэкенд, фронтенд). Есть процесс разработки и процесс проверки качества, в котором присутствует несколько окружений (их часто называют контурами) для тестирования:

  • Production основное рабочее окружение, куда попадают пользователи системы.
  • Pre-Production окружение для тестирования релиз-кандидатов (версий, которые будут использованы в production, если пройдут все этапы тестирования; их также называют RC), максимально схожее с production, где используются production доступы для интеграции с внешними сервисами. Цель тестирования на Pre-production получить достаточную уверенность в том, что на Production проблем не будет.
  • Staging окружение для черновой проверки, как правило тестирование последних изменений, по возможности использует тестовые интеграции со сторонними системами, может отличаться от Production, используется для проверки правильности реализации новых функциональностей.

Что и как нам хотелось улучшить


C Pre-production все достаточно понятно: туда последовательно попадают релиз-кандидаты, история релизов такая же, как на Production. Со Staging же есть нюансы:

  1. ОРГАНИЗАЦИОННЕ. Тестирование критических частей может потребовать задержки публикации новых изменений; изменения могут взаимодействовать непредсказуемым образом; отслеживание ошибок становится трудным из-за большого количества активности на сервере; иногда возникает путаница, что в какой версии реализовано; бывает непонятно, какое из накопившихся изменений вызвало проблему.
  2. СЛОЖНОСТЬ УПРАВЛЕНИЯ ОКРУЖЕНИЯМИ. Нужны разные окружения для тестирования разных изменений: для одной внешней системы может потребоваться production доступ, а работать с другой нужно в тестовой среде вместо боевой. А если staging один, то эти настройки распространяются на все реализованные фичи до следующего деплоймента. Приходится всё время об этом помнить и предостерегать сотрудников. Ситуация в целом похожа на работу многопоточного приложения с единым разделяемым ресурсом: он блокируется одними потребителями, остальные ждут. Например, один QA инженер ждет возможности проверки платежного шлюза с production интеграцией, пока другой проверяет все на интеграции тестовой.
  3. ДЕФЕКТ. Критичный дефект может заблокировать тестирование всех новых изменений разом

  4. ИЗМЕНЕНИЯ СХЕМ БД. Тяжело управлять изменениями схемы баз данных на одном стенде в периоды её активной разработки. Откати туда-сюда. Упс, тут не ревертится. А тут отревертили, но данные тестировщиков потеряли. Хочется для тестирования разных функциональностей иметь разные, изолированные друг от друга базы.
  5. УВЕЛИЧЕННОЕ ВРЕМЯ ПРИЁМКИ. Из-за того, что комбинация прошлых пунктов иногда приводит к ситуациям, когда часть фичей становится недоступна тестировщикам вовремя, или настройки окружения не позволяют приступить к тестированию сразу, происходят задержки на стороне разработки и тестирования. Допустим, в фиче обнаруживается дефект, и она возвращается на доработку разработчику, который уже вовсю занят другой задачей. Ему было бы удобнее получить её на доработку скорее, пока контекст задачи не потерян. Это приводит к увеличению отрезка времени от разработки до релиза в production для этих фич, что увеличивает так называемые time-to-production и time-to-market метрики.

Каждый из этих пунктов так или иначе решается, но все это привело к вопросу, а получится ли упростить себе жизнь, если мы уйдем от концепции одного staging-стенда к их динамическому количеству. Аналогично тому, как у нас есть проверки на CI для каждой ветки в git, мы можем получить и стенды для проверки QA для каждой ветки. Единственное, что нас останавливает от такого хода недостаток инфраструктуры и инструментов. Грубо говоря, для принятия фичи создается отдельный staging с выделенным доменным именем, QA тестирует его, принимает или возвращает на доработку. Примерно вот так:


Проблематика различных окружений при таком подходе решается естественным способом:


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

Наш путь к решению


Первое, что мы попробовали это прототип, собранный нашим DevOps: комбинация из docker-compose (оркестрация), rundeck (менеджмент) и portainer (интроспекция), которая позволила протестировать общее направление мысли и подход. С удобством были проблемы:

  1. Для любого изменения требовался доступ к коду и rundeck, которые были у разработчиков, но их не было, например, у QA инженеров.
  2. Поднято это было на одной большой машине, которой вскоре стало недостаточно, и для следующего шага уже был нужен Kubernetes или что-то аналогичное.
  3. Portainer давал информацию не о состоянии конкретного staging, а о наборе контейнеров.
  4. Приходилось постоянно мерджить файлик с описанием стейджингов, старые стенды надо было удалять.

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

  1. Использовать Kubernetes, чтобы масштабироваться на любое количество staging-окружений и иметь стандартный для современного DevOps набор инструментов.
  2. Решение, которое было бы просто интегрировать в инфраструктуру, уже использующую Kubernetes.
  3. Простой и удобный графический интерфейс для сотрудников на таких ролях как Руководители проектов, Product менеджеры и QA-инженеры. Они могут не работать с кодом напрямую, но инструменты для интроспекции и возможности задеплоить новый стейджинг у них должны быть. Результат не дергаем разработчиков на каждый чих.
  4. Решение, которое удобно интегрируется со стандартными CI/CD пайплайнами, чтобы его можно было использовать в разных проектах. Мы начали с проекта, который использует Github Actions как CI.
  5. Оркестрацию, детали которой сможет гибко настраивать DevOps инженер.
  6. Возможность скрывать логи и внутренние детали кластера в графическом интерфейсе, если проектная команда не хочет, чтобы они были доступны всем и/или есть какие-то опасения на этот счет.
  7. Полная информация и список действий должны быть доступны суперпользователям в лице DevOps инженеров и тимлидов.

И мы приступили к разработке Octopod. Названием послужило смешение нескольких мыслей про K8S, который мы использовали для оркестрации всего на проекте: множество проектов в этой экосистеме отражает морскую эстетику и тематику, а нам представлялся эдакий осьминог, щупальцами оркестрирующий множество подводных контейнеров. К тому же Pod один из основополагающих объектов в Kubernetes.

По техническому стеку Octopod представляет из себя Haskell, Rust, FRP, компиляцию в JS, Nix. Но вообще рассказ не об этом, поэтому я подробнее на этом останавливаться не буду.

Новая модель стала называться Multi-staging внутри нашей компании. Эксплуатация одновременно нескольких staging окружений сродни путешествиям по параллельным вселенным и измерениям в научной (и не очень) фантастике. В ней вселенные похожи друг на друга за исключением одной маленькой детали: где-то разные стороны победили в войне, где-то случилась культурная революция, где-то технологический прорыв. Предпосылка может быть и небольшой, но к каким изменениям она может привести! В наших же процессах эта предпосылка содержимое каждой отдельно взятой feature-ветки.


Внедрение у нас происходило в несколько этапов и начиналось с одного проекта. Оно включает в себя и подгонку оркестрации проекта со стороны DevOps и реорганизацию процесса тестирования и коммуникации со стороны руководителя проекта.

В результате ряда итераций некоторые фичи самого Octopod были удалены или изменены до неузнаваемости. Например, у нас в первой версии была страница с логом деплоя для каждого контура, но вот незадача не в каждой команде приемлемо то, что credentials могут через эти логи протекать ко всем сотрудникам, задействованным в разработке. В итоге мы решили избавиться от этой функциональности, а потом вернули её в другом виде теперь это настраиваемо (а поэтому опционально) и реализовано через интеграцию с kubernetes dashboard.

Есть еще и другие моменты: при новом подходе мы используем больше вычислительных ресурсов, дисков и доменных имен для поддержки инфраструктуры, что поднимает вопрос оптимизации стоимости. Если объединить это с DevOps-тонкостями, то материала наберется на отдельный пост, а то и два, поэтому тут продолжать об этом не буду.

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

В результате


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

Самым сложным, как водится, было первое внедрение на нем выловили большую часть технических вопросов и проблем. Фидбек от пользователей позволил лучше понять, что требует доработки в первую очередь. В последних версиях интерфейс и работа с Octopod выглядит примерно так:


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

Open-source


Надеемся, что Octopod будет полезен не только нам. Будем рады предложениям, пулл реквестам и информации о том, как вы оптимизируете схожие процессы. Если проект будет интересен аудитории, напишем об особенностях оркестрации и эксплуатации у нас.

Весь исходный код с примерами настройки и документацией доступен в репозитории на Github.
Подробнее..

Перевод Как сократить время сборки образов Docker в GitLab CI

18.01.2021 02:05:59 | Автор: admin

Делаем контейнерные CI среды по-настоящему практичными, ускорив сборку образов Docker.

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

В этой статье мы обсудим различные способы ускорения сборки образов Docker в конвейере непрерывной интеграции путем реализации различных стратегий.

Локальная упаковка приложения

В качестве примера мы возьмем достаточно простое приложение Python Flask:

from flask import Flaskapp = Flask(__name__)@app.route('/')def hello_world():    return 'Hello, World!'

Написание Dockerfile

Давайте напишем соответствующий Dockerfile:

FROM python:3.7-alpine as builder# установка зависимостей, необходимых для сборки пакетов pythonRUN apk update && apk add --no-cache make gcc && pip install --upgrade pip# настройка venv и загрузка или сборка зависимостейENV VENV="/venv"ENV PATH="${VENV}/bin:${PATH}"COPY requirements.txt .RUN python -m venv ${VENV} \    && pip install --no-cache-dir -r requirements.txtFROM python:3.7-alpine# настройки venv с зависимостями с этапа компоновкиENV VENV="/venv"ENV PATH="${VENV}/bin:$PATH"COPY --from=builder ${VENV} ${VENV}# копирование файлов приложенияWORKDIR /appCOPY app .# запуск приложенияEXPOSE 5000ENV FLASK_APP="hello.py"CMD [ "flask", "run", "--host=0.0.0.0" ]

Здесь вы можете наблюдать классический многоступенчатый процесс сборки:

  • Мы начинаем с легкого базового образа, в который мы устанавливаем инструменты сборки и загружаем или компилируем зависимости в виртуальную среду Python.

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

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

Запуск и тестирование образа.

Убедитесь, что все работает должным образом:

docker build -t hello .docker run -d --rm -p 5000:5000 hellocurl localhost:5000Hello, World!

Если вы запустите команду docker build во второй раз:

docker build -t hello ....Step 2/15 : RUN apk update && apk add --no-cache make gcc && pip install --upgrade pip ---> Using cache ---> 24d044c28dce...

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

Отправка образа

Давайте опубликуем наш образ во внешнем реестре и посмотрим, что произойдет:

docker tag hello my-registry/hello:1.0docker push my-registry/hello:1.0The push refers to repository [my-registry/hello]8388d558f57d: Pushed 77a59788172c: Pushed 673c6888b7ef: Pushed fdb8581dab88: Pushed6360407af3e7: Pushed68aa0de28940: Pushedf04cc38c0ac2: Pushedace0eda3e3be: Pushedlatest: digest: sha256:d815c1694083ffa8cc379f5a52ea69e435290c9d1ae629969e82d705b7f5ea95 size: 1994

Обратите внимание, как каждый из промежуточных слоев идентифицируется хэшем. Мы можем насчитать 8 слоев, потому что у нас есть ровно 8 docker команд в Dockerfile поверх нашей последней инструкции FROM.

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

С локальной сборкой все достаточно просто, давайте теперь посмотрим, как это будет работать в CI среде.

Сборка образа Docker в контексте CI конвейера

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

Тестовая CI среда

Мы реализуем CI среду, используя:

Последний пункт важен, потому что наши CI задачи будут выполняться в контейнерной среде. Учитывая это, каждая задача создается в виде пода Kubernetes. Каждое современное CI решение использует контейнерные задачи, и при создании Docker контейнеров все они сталкиваются с одной и той же проблемой: вам нужно заставить Docker команды работать внутри Docker контейнера.

Чтобы все прошло гладко, у вас есть два пути:

  • Забиндить /var/run/docker.sock, который слушает демон Docker, сделав демон хоста доступным для нашего контейнера задач.

  • Использовать дополнительный контейнер, запускающий Docker in Docker (также известный как dind) вместе с вашей задачей. Dind - это особый вариант Docker, работающий с привилегиями и настроенный для работы внутри самого Docker ?

Для нашего примера мы будем использовать второй вариант.

Реализация GitLab конвейера

В GitLab конвейере обычно вы создаете служебные контейнеры, такие как DinD, с помощью ключевого слова service.

В приведенном ниже фрагменте конвейера и задача docker-build, и служебный dind контейнер будут выполняться в одном и том же поде Kubernetes. Когда в сценарии задачи используется docker, он отправляет команды вспомогательному dind контейнеру благодаря переменной среды DOCKER_HOST.

stages:  - build  - test  - deployvariables:  # отключаем проверку Docker TLS  DOCKER_TLS_CERTDIR: ""  # адрес localhost используется как контейнером задачи, так и dind контейнером (поскольку они используют один и тот же под)  # Таким образом, при выполнении команд Docker эта конфигурация делает службу dind нашим демоном Docker   DOCKER_HOST: "tcp://localhost:2375"services:  - docker:stable-dinddocker-build:  image: docker:stable  stage: build  script:      - docker build -t hello .      - docker tag my-registry/hello:${CI_COMMIT_SHORT_SHA}      - docker push my-registry/hello:${CI_COMMIT_SHORT_SHA}

Запуск конвейера

Этот конвейер должен работать нормально. Запустив его один раз и проверив вывод задачи, мы получим:

docker build -t hello .Step 1/15 : FROM python:3.7-alpine as builder...Step 2/15 : RUN apk update && apk add --no-cache make gcc && pip install --upgrade pip---> Running in ca50f59a21f8fetch http://dl-cdn.alpinelinux.org/alpine/v3.12/main/x86_64/APKINDEX.tar.gz...

Поскольку мы впервые создаем наш контейнер, каждый слой создается путем выполнения команд. Общее время выполнения задачи составляет около 1 минуты.

Если вы запустите конвейер во второй раз, ничего не меняя, вы должны увидеть то же самое: каждый слой перестраивается! Когда мы запускали наши команды сборки локально, кэшированные слои использовались повторно. А здесь - нет. Для такого простого образа это на самом деле не имеет значения, но в реальной жизни, где для создания некоторых образов могут потребоваться десятки минут, это может стать настоящей проблемой.

Почему так происходит? Просто потому, что в этом случае dind - это временный контейнер, который создается вместе с задачей и умирает после ее завершения, поэтому все кэшированные данные теряются. К сожалению, вы не можете легко сохранить данные между двумя запусками конвейера.

Как же нам получить выгоду от кэширования и по-прежнему использовать Dind-контейнер?

Использование кэша Docker вместе с Docker in Docker

Первое решение: Pull/Push танцы

Первое решение довольно простое: мы будем использовать наш удаленный реестр (тот, в который мы пушим) в качестве удаленного кэша для наших слоев.

Если быть точнее:

  • Мы начинаем с извлечения (pull) самого актуального образа (т. е. последнего) из удаленного реестра, который будет использоваться в качестве кэша для последующей docker команды сборки.

  • Затем мы создаем образ, используя извлеченный образ в качестве кэша (аргумент --cache-from), если он доступен. Мы помечаем эту новую сборку в качестве последней и коммитим SHA.

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

stages:  - build  - test  - deploy    variables:   # отключаем проверку Docker TLS  DOCKER_TLS_CERTDIR: ""  DOCKER_HOST: "tcp://localhost:2375"services:  - docker:stable-dinddocker-build:  image: docker:stable  stage: build  script:    - docker pull my-registry/hello:latest || true    - docker build --cache-from my-registry/hello:latest -t hello:latest .    - docker tag hello:latest my-registry/hello:${CI_COMMIT_SHORT_SHA}    - docker tag hello:latest my-registry/hello:latest    - docker push my-registry/hello:${CI_COMMIT_SHORT_SHA}    - docker push my-registry/hello:latest

Если вы запустите этот новый конвейер два раза, разница от использования кэша все равно будет неудовлетворительной.

Все слои из базового образа компоновщика пересобираются. Только первые 2 слоя (8 и 9) заключительного этапа используют кэш, но последующие слои перестраиваются.

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

Затем, когда наш финальный образ собран (шаги с 8 по 15), первые два слоя присутствуют в образе, который мы извлекли и использовали в качестве кэша. Но на шаге 10 мы получаем зависимости образа компоновщика, которые изменились, поэтому все последующие шаги также строятся заново.

Подводя итог, можно сказать, что использование кэша в значительной степени ограничено: только 2 шага из 15 используют кэш! Чтобы исправить это, нам нужно отправлять образ промежуточного компоновщика в удаленный реестр, чтобы сохранить его слои:

stages:  - build  - test  - deploy    variables:    # отключаем проверку Docker TLS  DOCKER_TLS_CERTDIR: ""  DOCKER_HOST: "tcp://localhost:2375"services:  - docker:stable-dinddocker-build:  image: docker:stable  stage: build  script:    - docker pull my-registry/hello-builder:latest || true    - docker pull my-registry/hello:latest || true    - docker build --cache-from my-registry/hello-builder:latest --target builder -t hello-builder:latest .    - docker build --cache-from my-registry/hello:latest --cache-from my-registry/hello-builder:latest -t hello:latest .    - docker tag hello-builder:latest my-registry/hello-builder:latest        - docker tag hello:latest my-registry/hello:${CI_COMMIT_SHORT_SHA}    - docker tag hello:latest my-registry/hello:latest    - docker push my-registry/hello-builder:latest    - docker push my-registry/hello:${CI_COMMIT_SHORT_SHA}    - docker push my-registry/hello:latest

Мы создаем промежуточный этап нашего сборщика в качестве образ докера, используя опцию target. После этого мы пушим его в удаленный реестр, в конечном итоге извлекая его в качестве кэша для создания нашего финального образа. При запуске конвейера наше время сократилось до 15 секунд!

Как видите, сборка постепенно усложняется. Если вы уже начинаете путаться, тогда представьте образ с 3 или 4 промежуточными стадиями! Но это метод работает. Другой недостаток заключается в том, что вам придется каждый раз загружать и выгружать все эти слои, что может быть довольно дорогостоящим с точки зрения затрат на хранение и передачу.

Второе решение: внешняя служба dind

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

Почему бы не сделать Dind гражданином первого класса, создав службу Dind в нашем кластере Kubernetes? Она будет работать с подключенным PersistentVolume для обработки кэшированных данных, и каждая задача может отправлять свои docker команды в эту общую службу.

Создать такую службу в Kubernetes достаточно просто:

apiVersion: v1kind: PersistentVolumeClaimmetadata:  labels:    app: docker-dind  name: dindspec:  accessModes:    - ReadWriteOnce  resources:    requests:      storage: 500Gi---apiVersion: apps/v1kind: Deploymentmetadata:  labels:    app: docker-dind  name: dindspec:  replicas: 1  selector:    matchLabels:      app: docker-dind  template:    metadata:      labels:        app: docker-dind    spec:      containers:        - image: docker:19.03-dind          name: docker-dind          env:            - name: DOCKER_HOST              value: tcp://0.0.0.0:2375            - name: DOCKER_TLS_CERTDIR              value: ""          volumeMounts:            - name: dind-data              mountPath: /var/lib/docker/          ports:            - name: daemon-port              containerPort: 2375              protocol: TCP          securityContext:            privileged: true # Требуется для работы dind контейнера.      volumes:        - name: dind-data          persistentVolumeClaim:            claimName: dind            ---apiVersion: v1kind: Servicemetadata:  labels:    app: docker-dind  name: dindspec:  ports:    - port: 2375      protocol: TCP      targetPort: 2375  selector:    app: docker-dind

Затем мы немного изменим наш исходный GitLab конвейер, чтобы он указывал на эту новую внешнюю службу, и удалим встроенные dind службы:

stages:  - build  - test  - deploy    variables:   # отключаем проверку Docker TLS  DOCKER_TLS_CERTDIR: ""   # здесь имя хоста dind разрешается как dind служба Kubernetes с помощью kube dns  DOCKER_HOST: "tcp://dind:2375"docker-build:  image: docker:stable  stage: build  script:    - docker build -t hello .    - docker tag hello:latest my-registry/hello:{CI_COMMIT_SHORT_SHA}    - docker push my-registry/hello:{CI_COMMIT_SHORT_SHA}

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

И последний вариант: использование Kaniko

Последним вариантом может быть использование Kaniko. С его помощью вы можете создавать образы Docker без использования демона Docker, делая все, что мы сейчас делали, без особых проблем.

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

Заключение

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


А прямо сейчас приглашаем ва ознакомиться с программой супер-интенсива"CI/CD или Непрерывная поставка с Docker и Kubernetes", а таже записаться надень открытых дверей.


Подробнее..

Сам себе DevOps строим cloud-only CI для веб приложения

22.05.2021 18:12:52 | Автор: admin

Привет, Хабр! Сегодня мы немного поговорим о DevOps и самоорганизации на примере одного из наших проектов.

Начнем с фразы, с которой не соглашается добрая половина разработчиков в индустрии: "каждый разработчик должен быть сам себе DevOps". Кто-то считает, что этим должен заниматься отдельно выделенный человек, чтобы у разработчика оставалась забота только о качестве кода. А кому-то свойственно думать о конвейере доставки кода в той же степени, как и о самом коде. Я же считаю, что в современных реалиях рынка и избытке инструментов/знаний разработчик должен уметь настроить и обслуживать конвейер быстрой и предсказуемой доставки артефакта в нужную ему среду. В отличие от мобильных разработчиков, для которых вопросы инфраструктуры и доставки приложения в большей степени решены самим вендором (Google и Apple), backend и web разработчики должны если не владеть, то хотя бы интересоваться практиками доставки кода.

И речь не идет о настройке каких-то больших и громоздких билд-систем, для которых обычно приносится в жертвую целая штатная единица. Нет. DevOps - не человек, а система ежедневных маленьких привычек, основанных на самоорганизации. Понятие, взрастающее снизу вверх, а не сверху или в бок. И если вы, как разработчик, смогли ускорить поток артефактов (любимое американцами понятие "Value Stream") на небольшой процент, то поздравляем - это уже DevOps way. Рекомендуем прочесть книгу "DevOps Handbook" by Gene Kim - лучшая книга для понимания этого концепта (ссылка в конце статьи).

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

Кто

Одна из наших команд занимается разработкой системы интернет-банкинга для одного крупного банка. Команда достаточно большая, но речь сегодня пойдет о конкретных персонажах:

  • 3 фронтенд разработчика с кучей пулл реквестов в день

  • 2 тестировщика, бастующие за улучшение QX (QA experience)

Что

Клиентское и администраторское web-приложения на Angular 9.0, собираемые из одного репозитория.

Где

Моя команда известна как ярый адепт продуктов Atlassian, поэтому вся экосистема нашего проекта живет в "австралийских облаках":

  • задачи и релизы в Jira

  • код в Bitbucket

  • CI в Bitbucket Pipelines

  • подробная документация в Confluence.

Наша команда использует стандартный план Bitbucket за $4/чел, включающий в себя 1500 минут сборки в Bitbucket Pipelines. О нем в сегодняшней статье и пойдет речь. Принцип работы и синтаксис настройки на 90 процентов похожи на Gitlab CI, поэтому любому пользователю Gitlab вся схема работы будет максимально понятной.

Сама система интернет банкинга разбита на микросервисы и работает в контейнерах на серверах Банка. Но в этой статье речь будет идти не о контейнерах, хотя настройка CI с помощью Docker-образов звучит очевидным.

Немного контекста

Первые наши шаги в DevOps и конкретно в улучшении QX (QA experience) мы начали задолго до этого в проектах мобильных приложений. Мы интегрировали между собой Jira, Bitbucket и сервис Bitrise.io во всех наших пулл-реквестах, что позволило иметь на выходе конкретный билд на каждый коммит по конкретной задаче. Для наглядности: тестировщик понимал, что пулл реквест 30 выдает билд приложения 170, в которой нужно тестировать Jira-задачу 500. Если вкратце описать процесс пулл-реквестов, то обязательными требованиями к слиянию пулл-реквеста являются

  • Зеленый билд на последнем коммите

  • Добро от разработчика-ревьюера

  • Добро от тестировщика

Если один из этих шагов давал красный свет, то пулл-реквест проходит все шаги заново.
Такой процесс позволяет нам обеспечить высокое качество кода и продукта в стабильной ветке репозитория. Мы с высокой долей уверенности можем релизить приложение, собранное с master (мы начали работать по trunk-based development и поэтому master наша стабильная ветка).

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

Подобного механизма пулл-реквестов никогда не существовало в web приложениях. Мы всегда делали приемку задач после слияния пулл-реквестов в стабильную ветку, из-за чего каждый третий коммит в ней был дефектным. Настроить такой же процесс приема пулл реквестов, как в мобилке, было для нас очевидным шагом. Сделать CI окружение для web приложения на инфраструктуре Банка было для нас слишком долгой историей, потому что хотелось настроить и поехать очень быстро. А все, кто работал с большими банками, почувствовал "скорость" продвижения задач по железу. Все процессы, что мы опишем в этой статье, мы планируем воссоздать в инфраструктуре банка с помощью оркестратора (Kubernetes или OpenShift, на усмотрение заказчика), но это уже другая история. В тот момент нам нужно было как можно быстрее начать работать правильно.

Первый очевидный вопрос: куда доставлять? Мы начали присматриваться к разным вариантам: Heroku, AWS, Netlify, Surge итд. В итоге остановились на использовании AWS S3. Для тех, кто думал, что S3 это всего лишь файловое хранилище - S3 может работать как сайт и его можно привязать к доменному имени. Подробнее об этом можно прочитать на страничке AWS.

Так почему же AWS?

  • Доступная цена. При всей репутации AWS как дорогой экосистемы, ежемесячные счета за S3 выходят в среднем 2 доллара при следующих метриках:

    • Новых ПР в день ~ 2

    • Пайплайнов в день ~ 12

    • Кол-во единовременно существующих бакетов ~ 5

    • Средний размер бакета = 13 Mb

  • У AWS отличный API и CLI. у "Surge" и других легковесных сервисов хостинга не настолько качественный и полноценный тулинг, как у Amazon AWS. Надо отметить, что CLI и документация Heroku не уступает Амазону, но высокий на наш взгляд порог вхождения и специфика работы Heroku Dynos заставили нас отойти от его выбора.

  • У команды уже был опыт работы с продуктами AWS.

Можно было бы настроить весь этот процесс в контейнерах в самом Amazon, но это повлечет за собой запуск EC2 машин. Даже с использованием Docker Hub вместо Elastic Container Registry, прогноз затрат вываливался у нас за $100 в месяц. В конечном итоге у нас получилась именно та схема работы с пулл-реквестами, которую мы представляли себе в самом начале. Но давайте проанализируем каждую ступень нашей эволюции и посмотрим на принятые решения.

Уровень 1: создание S3 бакета

Мы начали с того, что создали по одному выделенному S3 bucket для хостинга клиентского и админского приложений. Настроили конфигурацию сборки нашего проекта (bitbucket-pipelines.yml), чтобы он собирал приложения (html/css/js/img) и заливал их в соответствующий S3 bucket. В начале был использован AWS CLI, но, как оказалось, Bitbucket предоставляет набор готовых официальных Pipes (аналог Github actions), среди которых оказался Pipe для выгрузки файлов в S3 bucket. В итоге: тестировщик имеет сайт, на котором он может проверить реализацию задачи пулл-реквеста с постоянной условной ссылкой web.s3-website.ap-northeast-2.amazonaws.com.

Обязательным предварительным шагом при создании бакета через консоль AWS является включение опции "Enable static hosting" в настройках бакета. Без этой опции bucket является просто файловым хранилищем.

- step:      name: Build and deploy webadmin PR version into AWS for QA      caches:        - node      script:        # начальная конфигурация        - apk update && apk add git        - npm install        # сборка        - npm run build:admin        - cd dist/admin        # загрузка в S3        - pipe: atlassian/aws-s3-deploy:0.2.4          variables:            AWS_ACCESS_KEY_ID: $AWS_ACCESS_KEY_ID            AWS_SECRET_ACCESS_KEY: $AWS_SECRET_ACCESS_KEY            AWS_DEFAULT_REGION: $AWS_DEFAULT_REGION            S3_BUCKET: $S3_WEBADMIN_BUCKET_NAME            DELETE_FLAG: 'true'            LOCAL_PATH: $(pwd)            ACL: 'public-read'

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

Оценка:

  • за старания - четверка

  • за QX - двойка

Уровень 2: выделение S3 bucket под каждого автора

В ответ на обратную связь от тестировщиков командой было решено выделить по одному S3 bucket на каждого фронтенд разработчика. В нашем проекте были разработчики Манар, Миша - следовательно были созданы условные S3 бакеты jsn-web-manar и jsn-web-michael. В bitbucket-pipelines.yml в step для пулл-реквестов была добавлена логика определения конечного S3 бакета в зависимости от PR автора.

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

  1. Гонка пулл-реквестов одного автора. Если один и тот же разработчик создаст 3 параллельных пулл-реквеста, то все они вызовут запуск пайплайна сборки. Мы не можем точно знать, какой из пайплайнов закончится быстрее. Команде, в частности тестировщику, без использования консоли Chrome сложно понять, какой из пулл-реквестов сейчас развернут на S3 бакете разработчика Михаила.

  2. Появление нового автора. В наших репозиториях создавать пулл-реквест может любой член команды, поэтому эта схема сломалась ровно в тот момент, когда ПР создал кто-то, кроме фронтенд-разработчиков. По нашей тривиальной логике определения бакета его запущенный пайплайн "угонит" S3 бакет одного из разработчиков. В итоге другой тестировщик может потерять version-under-test сайт прям в момент тестирования.

  3. Смена никнейма. Наши разработчики забавы ради любят менять свои git author name время от времени. Для нас это никогда не являлось проблемой до того, как мы применили логику с бакетами на каждого автора. К сожалению, Bitbucket Pipelines из коробки не предоставляют возможности определения автора по его Jira account, поэтому в логике присвоения бакета пришлось оперировать стандартным commit git author. Как вы и сами догадались, при смене имени с "Manar Kurmanov" на "Dark Lord" повторилась ситуация из пункта 2 - был угнан бакет другого разработчика.

С этой шаткой схемой команда прожила еще несколько месяцев.

Оценка:

  • за старания - четверка

  • за QX - тройка

Уровень 3: добавление штампа авторства в web приложение

Команда решила проблему гонки пулл-реквестов добавлением пояснительного текста в footer сайта:

Каждый пайплайн добавлял в футер сайта название ветки, автора и timestamp. Таким образом решалась проблема параллельных пулл-реквестов от одного автора - тестировщик четко понимает, какая Jira-задача разработчика Георгия представлена в бакете.

Фрагмент из bitbucket-pipelines.yml

- step:    name: Build PR version    caches:      - node    script:      # initial configuration      - apk update && apk add git      - npm install      # preparing site footer text      - TIMESTAMP_FILE="./src/app/some/folder/copyright.timestamp.html"      - GIT_AUTHOR=$(git log -n 1 --format=format:'%an')      - PR_URL="$BITBUCKET_GIT_HTTP_ORIGIN/pull-requests/$BITBUCKET_PR_ID"      - BRANCH_TEXT="PR branch <a href=\\"$PR_URL\\">$BITBUCKET_BRANCH</a><br>"      - echo $BRANCH_TEXT >> $TIMESTAMP_FILE      - echo "Author $GIT_AUTHOR<br>" >> $TIMESTAMP_FILE      - echo "Built at $(TZ=UTC-6 date '+%d-%m-%Y %H:%M') <br>" >> $TIMESTAMP_FILE      - echo "</small>" >> $TIMESTAMP_FILE      - cat $TIMESTAMP_FILE > src/app/target/folder/copyright.component.html      # building artefacts      - npm run build    artifacts:      paths:        # кеширование артефактов для следующего Build Step         - dist/web/**

Казалось бы, +100 к QX, куда еще прозрачнее. Но поставьте себя на место тестировщика в ежедневной работе и вы поймете еще одно скрытое неудобство. Допустим, что разработчик создал 3 параллельных пулл-реквеста и тестировщик проверил сайт на S3 бакете. Что он должен делать дальше? Тестировщику не очевидно, что он находится в ситуации очереди ПР-ок на один и тот же S3 бакет. После он должен зайти в странице Pipelines, найти нужную ветку и сделать ручной Rerun.

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

Оценка:

  • за старания - четверка

  • за QX - тройка с плюсом

Уровень 4: динамичные бакеты под каждый пулл реквест

Мы решил копнуть глубже в возможности AWS API и воссоздать поведение динамических сред для тестировщиков и разработчиков. Какие были требования:

  • Каждый пулл реквест должен породить свой отдельный S3 бакет и задеплоить сайт туда.

  • Нужно, чтобы в комментарий к пулл-реквесту писалась ссылка на этот бакет при каждом новом билде.

  • Автоматика должна уметь подчищать за собой неиспользуемые бакеты

Для реализации этих требований не хватало стандартных Bitbucket Pipes, поэтому нужно было писать кастомные скрипты для взаимодействия с AWS S3. К счастью Bitbucket Pipelines, как и многие CI системы, является cloud-first и предоставляет возможность запускать свои пайплайны на базе любого публичного Docker образа. Мы использовали официальный образ aws-cli, включающий в себя AWS CLI и все базовые утилиты командной строки (curl, sed, xargs).

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

- step:    name: Deploy PR version into AWS bucket for QA    image:      name: amazon/aws-cli    script:      # 1. Настройка сессии в aws cli с помощью ключей      - aws configure set aws_access_key_id=$AWS_ACCESS_KEY_ID aws_secret_access_key=$AWS_SECRET_ACCESS_KEY      # 2. определяем название для динамического бакета      - export BUCKET_NAME=web-pullrequest-$BITBUCKET_PR_ID      # 3. если в AWS нету бакета с таким названием, создаем его с нужными флагами      - if [ -z $(aws s3 ls | grep $BUCKET_NAME) ]; then aws s3api create-bucket --bucket $BUCKET_NAME --acl public-read --region ap-northeast-2 --create-bucket-configuration LocationConstraint=ap-northeast-2; fi      # 4. задаем это бакету настройку статичного хостинга      - aws s3api put-bucket-website --website-configuration "{\\"ErrorDocument\\":{\\"Key\\":\\"error.html\\"},\\"IndexDocument\\":{\\"Suffix\\":\\"index.html\\"}}" --bucket $BUCKET_NAME      # 5. очищаем содержимое бакета      - aws s3 rm s3://$BUCKET_NAME --recursive       # 5. заливаем в него собранные html/css/js      - aws s3 cp dist/web s3://$BUCKET_NAME --acl public-read --recursive      # 6. Пишем коммент со ссылкой от имени сервисной учетки в нужный пулл реквест      - export PR_API_URL=https://api.bitbucket.org/2.0/repositories/$BITBUCKET_REPO_FULL_NAME/pullrequests/$BITBUCKET_PR_ID/comments      - export BUCKET_PUBLIC_URL=http://$BUCKET_NAME.s3-website.ap-northeast-2.amazonaws.com      - curl $PR_API_URL -u $CI_BB_USERNAME:$CI_BB_APP_PASSWORD --request POST --header 'Content-Type:application/json' --data "{\\"content\\":{\\"raw\\":\\"[http://$BUCKET_NAME.s3-website.ap-northeast-2.amazonaws.com](http://personeltest.ru/away/$BUCKET_NAME.s3-website.ap-northeast-2.amazonaws.com)\\"}}"

В качестве автора комментарий в пулл реквест мы использовали нашу сервисную учетную запись для CI с использованием App-specific password. В этой статье от Atlassian можно узнать, как создать такой пароль.

Данная схема может обслуживать и двоих и сотню разработчиков - мы обеспечили масштабируемость и прозрачность процесса тестирования.

"Единственный ручной процесс в этой схеме - чистка неиспользуемых S3 бакетов раз в неделю. Зачем это автоматизировать?" - подумали мы. Но по закону жанра команда благополучно забывала подчищать бакеты и вспомнила об этом только после того, как бухгалтер показал счет на 25 долларов от AWS из-за скопившихся бакетов.

В итоге мы решили добавить логику чистки неиспользуемых бакетов в пайплайн при слиянии пулл-реквеста.

- step:    name: Remove dangling s3 buckets left after PR merges    image:        name: amazon/aws-cli    script:      # 1. Запросить список 10 последних MERGED пулл реквестов      - export API_URL="<https://api.bitbucket.org/2.0/repositories/$BITBUCKET_REPO_FULL_NAME/pullrequests?state=MERGED>"      - curl "$API_URL" -u $CI_BB_USERNAME:$CI_BB_APP_PASSWORD > pr_list.json      # 2. выделить бакеты, соответствующие спец-формату       - aws s3 ls | grep -o '[a-zA-Z\\-]\\+pullrequest\\-[0-9]\\+' > buckets.txt- set +e      # очистить все бакеты с номер ПР-ок, которые уже MERGED      # (AWS API требует очистки бакета перед его полным удалением)      - echo "$(cat pr_list.json | grep -o '"id":\\s[0-9]\\+')" | sed 's/[^0-9]//g' | xargs -I{} grep {} buckets.txt | xargs -I{} aws s3 rm s3://{} --recursive      # удалить все бакеты с номер ПР-ок, которые уже MERGED      - echo "$(cat pr_list.json | grep -o '"id":\\s[0-9]\\+')" | sed 's/[^0-9]//g' | xargs -I{} grep {} buckets.txt | xargs -I{} aws s3api delete-bucket --bucket {}

Оценка:

  • За старания пятерочка

  • за QX - четверка с плюсом. Почему не пять? Потому что на своей шкуре мы поняли, что улучшение любого X (QX, DevX, HX) - это бесконечный процесс

Технические ремарки

Есть несколько важных моментов, которые стоит отметить для всех желающих опробовать данную схему.

#1: По поводу CORS

Так как API запросы совершаются с одного хоста (.amazonaws.com) на другой хост (*.somebank.com), по умолчанию они будут блокироваться браузером из-за настроек CORS (cross origin resource sharing) сервера. Если вкратце, то браузер позволяет отправлять запросы только из того же хоста, откуда сайт был запрошен. Для примера, API на api.server.com будет принимать запросы только с сайта server.com. При попытке сделать GET запрос с сайта another.com браузер сначала совершит "pre-flight" запрос на сервер и поймет, что сервер строго выдерживает правило "same-origin-policy".

Для того, чтобы запросы со статичного сайта S3 бакета проходили в ваш API, вы должны добавить хост бакета в серверные настройки Headers.

Access-Control-Allow-Origin: <http://bucket.s3-website.amazonaws.com># илиAccess-Control-Allow-Origin: *

Во всех популярных фреймворках есть поддержка управления Cross Origin.

#2: По поводу расходов

В уровне 4 в скрипте присутствует строка очистки содержимого бакета:

aws s3 rm s3://$BUCKET_NAME --recursive 

Это микро оптимизация расходов компании на AWS. Мы на каждом запуске ПР пайплайна очищаем предыдущее содержимое бакета, чтобы в нем не скопились файлы от 4 предыдущих сборок одного и того же пулл реквеста.

Если этого не делать, то размер бакета будет увеличиваться пропорционально кол-ву пайплайнов на 1 ПР. В масштабах 3 разработчиков это экономит нам пару центов, но в масштабе десяток разработчиков и долгих ПР - это десятки долларов. Мы считаем, что это полезное упражнение как минимум с точки зрения практики владения AWS API.

ВАЖНО! Если в вашем проекте будет использоваться долгоживущий S3 bucket и вы будете использовать официальный aws-s3-deploy pipe, то убедитесь, что вы используете DELETE_FLAG. Этот флаг очищает bucket перед очередной выгрузкой файлов. Во время уровня #1 наша команда об этом флаге не знала в течение 2 месяцев и узнала только после обнаружения нескольких тысяч файлов в одном бакете. Поэтому парочку десяток американских долларов было сожжено во имя наших познаний.

# вызов пайпа загрузки файлов в S3 с флагом DELETE_FLAG- pipe: atlassian/aws-s3-deploy:0.2.4    variables:      AWS_ACCESS_KEY_ID: $AWS_ACCESS_KEY_ID      AWS_SECRET_ACCESS_KEY: $AWS_SECRET_ACCESS_KEY      AWS_DEFAULT_REGION: $AWS_DEFAULT_REGION      S3_BUCKET: $S3_WEBADMIN_BUCKET_NAME      DELETE_FLAG: 'true' # не забыть этот флаг      LOCAL_PATH: $(pwd)      ACL: 'public-read'

Вывод

Эта история проб и ошибок одного отдельного процесса позволила нам не только улучшить конкретно этот процесс, а посеяла в нас зерно DevOps ментальности и дала настрой на мини улучшения в других местах проекта и продукта. Мы рекомендуем всем, кто еще не погружался в практики CI/CD, изучить и отточить это направление в своей карьере.

Финальную версию bitbucket-pipelines.yml можно посмотреть в github репозитории.

Материалы к прочтению

Подробнее..
Категории: S3 , Angular , Devops , Frontend , Amazon web services , Aws , Cicd , Pipelines , Bitbucket

Приглашаем на Live-Вебинар Автоматизация процессов с GitLab CICD 29 Окт., 1500 -1600 (MST)

06.10.2020 12:23:44 | Автор: admin

Расширяем знания и переходим на следующий уровень.




Вы только начинаете изучать основные принципы Continuous Integration / Continuous Delivery или написали уже не один десяток пайплайнов? Вне зависимости от уровня Ваших знаний, присоединяйтесь к нашему вебинару, чтобы на практике разобраться, почему тысячи организаций по всему миру выбирают GitLab в качестве ключевого инструмента для автоматизации IT процессов.

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

В этом вебинаре мы разберем:
  • Основные элементы автоматизации в GitLab CI/CD
  • Принципы Pipelines-as-Code
  • AutoDevOps автоматический полноценный CI/CD конвейер, который самостоятельно автоматизирует весь процесс
  • Расширенные настройки и оптимизацию GitLab CI/CD

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

Перевод Интеграция CICD для нескольких сред с Jenkins и Fastlane. Часть 1

04.11.2020 02:21:17 | Автор: admin

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


Внедрение технологий непрерывной интеграции (Continuous Integration - CI) и непрерывного развертывания (Continuous Delivery - CD) в процесс разработки - бесспорно единственный способ отслеживать актуальность изменений кода и определять ошибки интеграции на самых ранних этапах. Это также еще и путь к отлаженным билдам, практически сразу же доступным для тестирования и готовым к отправке в продакшн даже после значительных изменений кода.

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

Но что происходит, когда у нас есть несколько промежуточных сред, и тесты должны выполняться на разных серверах или функции должны тестироваться в разных средах? Вот где на помощь приходят Jenkins и Fastlane - инструменты, которые позволяют автоматизировать процесс под различные конфигурации, и именно на этом фокусируется данная статья.

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

Jenkins и Fastlane

Чтобы понять, как мы можем интегрировать CI в проект, мы должны понимать, как Jenkins и Fastlane работают. Вкратце, Jenkins - это сервер непрерывной интеграции с открытым исходным кодом, написанным на Java, и это один из наиболее широко используемых инструментов для управления сборками непрерывной интеграции. Jenkins предоставляет возможность создавать пайплайны (Pipelines), которые по сути являются жизненными циклами процессов, называемых задачами (Jobs), которые включают сборку, документирование, тестирование, упаковку, стейджинг, развертывание, статический анализ и многое другое. Задачи связаны друг с другом в последовательности, образующей конвейер (пайплайн), и именно здесь в игру вступает Fastlane.

Fastlane - это инструмент с открытым исходным кодом, который используется для автоматизации процесса развертывания и распространения мобильных проектов (iOS и Android), предлагающий широкий спектр функций автоматизации в рамках жизненного цикла мобильного приложения, таких как упаковка, подписание кода, распространение сборок и многое другое. Fastlane позволяет создавать задачи, именуемые Lanes, которые по сути представляют из себя скрипты, то есть серию команд, называемых Actions, которые описывают рабочий процесс, который мы хотим автоматизировать.

Наша цель

Итак, то, чего мы здесь хотим достичь, - это автоматизировать процесс распространения нашего iOS-приложения путем автоматизации загрузки нашего приложения в Testflight, откуда заинтересованные лица (заказчик, инженер по обеспечению качества, продавец, который хочет продемонстрировать приложение, и в том же духе) могут войти в систему и загрузить его без особых усилий. Более того, мы хотим продвинуться на один шаг дальше и реализовать возможность автоматически загружать билды в Testflight по одному нажатию кнопки:

a) для разных веток под разные фичи

b) для нескольких конфигураций, соответствующих разным средам

Такова будет наша конечная цель ?.

Предварительные требования

В этой статье мы считаем, что Jenkins и Fastlane уже были установлены и настроены. Существует множество руководств, в которых подробно описывается, как нужно выполнять настройку. Кроме того, для Jenkins потребуется установить некоторые плагины, чтобы позволить Jenkins запускаться из веб-хуков Github и запускать задание. Это плагины Github, Xcode, SCM (Source Control Management), которые будут использоваться для того, чтобы сделать чекаут нашего проекта с Github, и Credentials Plugin для того, чтобы связать учетные данные для переменных окружения.

Начнем

Для начала нам нужно создать новый пайплайн в Jenkins. Этот пайплайн будет описан созданным нами скриптом, который выполняет загрузку нашего приложения в Testflight.

В главном меню панели инструментов Jenkins (ниже) мы выбираем первый вариант => New Item (Pipeline) и создаем задачу (Job) Jenkins с именем Upload to Testflight.

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

В поле Definition ниже, в разделе Pipeline выбираем параметр Pipeline Script from SCM. Этот параметр указывает Дженкинсу получить пайплайн из системы управления исходным кодом (SCM), в роли которой будет наш клонированный Git-репозиторий.

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

Наконец, мы определяем скрипт, который будет описывать весь процесс пайплайна.

Нажимаем кнопку Save и вуаля! Нам удалось создать собственную задачу в Jenkins. Теперь мы готовы приступить к написанию нашего скрипта!

В основном мы будем использовать подход Scripted Pipeline. Наш скрипт будет состоять из нескольких этапов, называемых стадиями (stages), которые описывают, что собирается делать наш пайплайн. В конечном итоге мы хотим закончить загрузкой в Testflight, но перед загрузкой мы хотим убедиться, что наши модульные тесты проходятся успешно. Таким образом, различные фазы скрипта автоматизации должны включать следующие стадии:

  1. Чекаут репозитория

  2. Установка зависимостей

  3. Сброс симуляторов

  4. Запуск тестов

  5. Сборка билда

  6. Загрузка в Testflight

  7. Очистка

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

После того, как мы нажмем кнопку Build, и конвейер будет успешно запущен, мы увидим следующее в Jenkins Stage view:

что будет означать, что мы успешно загрузили наше приложение в Testflight!

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

Скрипт

В нашем скрипте, который мы назвали MyScript.groovy, мы определим функцию называемую deploy(), внутри которой мы намерены реализовать вышеперечисленные стадии следующим образом:

1. Чекаут репозитория

Мы делаем чекаут нашего репозитория с помощью команды checkout плагина SCM, которая будет запускать проверку проекта с использованием параметров конфигурации, которые мы указали в Jenkins Pipeline.

stage('Checkout') {checkout scm}

2. Установки зависимостей

stage('Install dependencies') {sh 'gem install bundler'sh 'bundle update'sh 'bundle exec pod repo update'sh 'bundle exec pod install'}

Наш проект использует в качестве менеджера зависимостей CocoaPods, поэтому нам нужно запустить pod install, чтобы загрузить зависимости проекта. Для выполнения этой команды и всех других шелл-команд мы используем Bundler, который представляет собой инструмент для управления гемом приложения Ruby. Выполнив первые 2 команды, нам удается установить Bundler, а затем Bundler загружает все гемы, указанные в Gemfile проекта. Это стадия, на которой мы можем указать набор инструментов, которые могут понадобиться нашему приложению, например Fastlane, который мы будем использовать следующим, или, если нам нужен линтер или Danger, это место как раз для определения их ruby гемов. Мы продолжаем дальше и запускаем pod repo update, чтобы всегда иметь последние версии pod'а, и, наконец, мы запускаем pod update, чтобы загрузить зависимости проекта.

3. Сброс симуляторов

stage('Reset Simulators') {sh 'bundle exec fastlane snapshot resetsimulators --force'}

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

Это первый раз, когда Fastlane появляется в кадре. Как я упоминал ранее, Fastlane использует Lanes и уже имеет большое количество заготовленных лейнов, но мы можем создавать и свои собственные настраиваемые лейны внутри файла под именем Fastfile. Здесь мы используем заготовленный лейн, который делает именно то, что нам нужно.

Команда выше удалит и пересоздаст все iOS-симуляторы, что можно счесть чрезмерным, но иногда отчаянные времена требуют отчаянных мер .

4. Запуск тестов

stage('Run Tests') {sh 'bundle exec fastlane test'}

На этом этапе мы запускаем модульные тесты для нашего проекта. Теперь test представляет собой кастомный лейн, который мы реализовали внутри нашего Fastfile, и имеет следующую реализацию:

lane :test doscan(clean: true,devices: ["iPhone X"],workspace: "ourproject.xcworkspace",scheme: "productionscheme",codecoverage: true,outputdirectory: "./testoutput",outputtypes: "html,junit")slather(coberturaxml: true,proj: "ourproject.xcodeproj",workspace: "ourproject.xcworkspace",outputdirectory: "./testoutput",scheme: "productionscheme",jenkins: true,ignore: [arrayofdocstoignore])end

Здесь мы используем два основных экшена Fastlane: scan и slather.

Scan используется для фактического запуска модульных тестов и может быть настроен с несколькими параметрами, такими как workspace (рабочее пространство), scheme (схема), codecoverage (покрытие кода) и, что наиболее важно, devices (устройства), где можем указать симулятор, в котором мы хотим запускать наши модульные тесты.

Slather используется для сбора покрытия кода при выполнении модульных тестов, а также принимает несколько аргументов, таких как результирующий каталог для отчета о покрытии, а также массив документов, которые следует игнорировать при сборе покрытия кода (ignore).

Здесь и происходит Fastlane-магия . Определяя этот лейн всего двумя экшенами, нам удается легко запускать наши модульные тесты внутри Jenkins. Обратите внимание, что все они, очевидно, должны быть успешно пройдены, иначе эта стадия и вся задача закончатся неудачей.


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

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

Подробнее..

Перевод GitOps плохой и злой

04.11.2020 04:15:59 | Автор: admin

Эксперт OTUS - Владимир Дроздецкий приглашает всех желающих на бесплатный вебинар, в рамках которого он подробно расскажет о программе курса "DevOps практики и инструменты" и ответит на интересующие вопросы. А прямо сейчас, по устоявшейся традиции, делимся с вами интересным переводом.


Недавно я общался с разработчиками из Humanitec (это Continuous Delivery-платформа для Kubernetes). Humanitec интересен тем, что вопреки современным тенденциям, он не основан на GitOps.

Лично я большой фанат GitOps, потому что он позволяет строить CI/CD без сложных инструментов с использованием только Git и декларативного описания конфигураций. Но несмотря на то, что я недавно написал статью "11 Reasons for Adopting GitOps" (11 причин для внедрения GitOps), в своей практике я неоднократно сталкиваюсь с ограничениями этого подхода. Беседа с ребятами из Humanitec побудила меня написать об этом негативном опыте для того, чтобы предоставить вам более объективную картину GitOps и поговорить об альтернативных подходах.

Что не так с GitOps?

Не предназначен для автоматических обновлений

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

Однако редактирование и разрешение конфликтов в Git осуществляется вручную. И легко может возникнуть ситуация, когда несколько CI-процессов завершаются записью в один и то же GitOps-репозиторий, что приводит к конфликту.

Конфликт возникает не в отдельных файлах, а между двумя процессами, клонировавшими репозиторий, когда один из них делает push раньше другого. Если после этого другой процесс попытается сделать push, то его локальная копия будет устаревшей, поэтому ему придется сделать pull, а потом повторный push. На этом этапе, если система достаточно нагружена, могут возникнуть конфликты с еще каким-нибудь процессом. Причины этого всего кроятся непосредственно в принципах работы Git. Этот эффект можно уменьшить, использовав больше репозиториев (например, по одному репозиторию на один namespace).

Решение этой проблемы в одном из проектов, через добавление в Groovy-скрипты Jenkins сложного механизма повторов, нам стоило значительных затрат.

Увеличение количества Git-репозиториев

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

Команда, с которой я работал, потратила в сложной корпоративной среде более 30% всего времени разработки на автоматизацию провижининга GitOps-репозиториев. Эту проблему можно смягчить, используя меньшее количество репозиториев, например, по одному репозиторию на кластер. Но это увеличит нагрузку на конкретный репозиторий с точки зрения контроля доступа и управления Pull Request'ами. И самое главное, только усугубит проблему автоматического обновления, описанную в предыдущем разделе.

Отсутствует прозрачность

GitOps обещает прозрачность того, что происходит в вашем окружении, так как все целевое состояние хранится в Git в виде обычного текста. Но это работает только для относительно простых конфигураций при небольшом количестве GitOps-репозиториев, содержащих обозримое количество файлов конфигурации.

В корпоративной среде количество GitOps-репозиториев и/или файлов конфигурации растет с невероятной скоростью. В результате просмотр текстовых файлов становится не таким уж и удобным способом для поиска ответов на вопросы. Например, даже на вопрос о том, как часто развертываются определенные приложения, ответить трудно, поскольку изменения в Git-репозиториях сложно сопоставить с развертываниями приложений. Некоторые изменения могут привести к развертыванию нескольких приложений, а некоторые это всего лишь минорные изменения в конфигурации.

Не решает проблему централизованного управления секретами

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

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

Аудит не так хорош, как кажется

GitOps-репозитории это отличный инструмент для аудита процессов, так как они хранят полную историю всех изменений. Поэтому легко ответить на вопрос: Что случилось с этим окружением?.

Но так как GitOps-репозитории хранят версии текстовых файлов, ответить на другие вопросы становится сложнее. Например, для ответа на вопрос: Когда разворачивалось приложение X?, потребуется изучение истории Git и полнотекстовый поиск в текстовых файлах, что сложно реализовать и чревато появлением ошибок.

Отсутствие валидации входных данных

Если Git-репозиторий является интерфейсом между кластером Kubernetes и CI/CD-процессом, то нет простого способа проверить закомиченные файлы. Представьте себе, что вместо Git PR вы делаете вызов API. В этом случае осуществляется валидация запроса, а в случае с GitOps вся проверка манифеста и Helm-файлов ложится на пользователя.

Другое решение?

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

Итак, есть ли решение, со всеми преимуществами GitOps, но без указанных недостатков? Давайте сначала выделим то, что мы хотели бы сохранить:

  • Логирование всех изменений окружения.

  • Описание и конфигурация окружений в декларативном виде.

  • Процесс утверждения/согласования изменений окружений.

  • Контроль того, кто может вносить изменения в окружения.

  • Просмотр целевого состояния среды и сверка его с реальным состоянием.

Как упоминалось ранее, проблема заключается в том, что, хотя в Git и присутствуют все эти возможности, но он не рассчитан на частые автоматические обновления. Также Git не подходит для анализа хранящихся в нём данных. Очевидным решением является API-сервис со своей базой данных, и аналогичным GitOps-процессом синхронизации на основе агента. (Если вы читаете статью на маленьком экране, то можете скачать диаграмму здесь.)

В базе данных хранятся все предыдущие версии манифестов и Helm-диаграмм. Таким образом можно поддержать большое количество обновлений API и убрать неудобный процесс разрешения конфликтов с помощью Git (если только мы не столкнемся с реальными конфликтами, вероятность которых в этом сценарии очень низка). Процесс утверждения запросов на изменение реализуется с помощью вызовов API и базы данных. RBAC реализуется аналогично.

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

  • Структурированный поиск по базе данных (Как часто развертывается приложение X?).

  • Единая централизованная система, обслуживающая все среды: отсутствует увеличение количества git-репозиториев.

  • Простое управление конфигурациями нескольких сред. Можно реализовать иерархию конфигураций.

  • Централизованное управление секретами или интеграция со сторонними продуктами.

  • Валидация входных данных.

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

Самый популярный из таких инструментов Spinnaker. В свою очередь, Humanitec это следующее поколение, полностью ориентированное на Kubernetes. Некоторые из вышеперечисленных возможностей в нем уже реализованы, а некоторые есть в планах. Мы видим большой потенциал в подобных системах как в альтернативе GitOps.

Узнать подробнее о курсе.


Читать ещё:

Подробнее..

Перевод Семь паттернов пайплайнов непрерывной поставки

23.11.2020 20:22:46 | Автор: admin

Перевод статьи подготовлен в преддверии старта курса "DevOps практики и инструменты".

Прямо сейчас у вас есть шанс успеть на курс по специальной цене. Узнать подробности.


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

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

Непрерывная поставка

Непрерывная поставка (Continuous Delivery) это "возможность надежно и быстро поставлять изменения всех типов в руки пользователей". Если посмотреть на непрерывную поставку по матрице Agile vs Effort, то можно увидеть, что она находится прямо между непрерывной интеграцией и непрерывным развертыванием. Часто их вместе называют CI / CD.

В отчете о состоянии DevOps за 2019 год более 31 000 респондентов сообщили об эффективности своих процессов разработки и поставки. Но разница между лидерами и отстающими ошеломляет. У лидеров в 200 раз больше развертываний и в 100 раз выше скорость развертывания, а также в 2 600 раз быстрее восстановление после инцидентов и в 7 раз меньше вероятность отката релизов.

Это исследование показывает, что для лидеров скорость и стабильность не противоположны! Вы можете получить и то и другое (на самом деле вам нужно и то и другое), чтобы получить реальные конкурентные преимущества для ваших цифровых продуктов и услуг.

Пайплайны

Пайплайны (конвейеры) это основные технические артефакты непрерывной поставки. Современные пайплайны преобразуют исходный код приложения и инфраструктуры в версионированные пакеты, которые можно разворачивать в любой среде. Автоматизируя все рутинные задачи по сборке и развертыванию систем, разработчики могут сосредоточиться на реализации действительно полезных фич.

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

Основываясь на нашем опыте и опыте наших клиентов, мы выделили семь паттернов пайплайнов, которые мы наблюдали во многих современных технологических компаниях.

Паттерны пайплайнов

Паттерн 1 пайплайны как код

Логика пайплайна кодируется и хранится вместе с кодом приложения и инфраструктуры. Для выполнения пайплайнов используются контейнеры.

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

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

  • Для образов контейнеров сред сборки используются проверенные Docker-образы.

  • Конфигурация CI runner автоматизированная, одинаковая и не требует ручной настройки. CI runner при необходимости могут масштабироваться и находиться в режиме ожидания для минимизации задержек.

  • Секреты хранятся вне пайплайнов, а их вывод маскируется, что повышает безопасность.

Паттерн 2 перенос логики в повторно используемые библиотеки

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

  • Относитесь к пайплайн-библиотекам как к любому другому программному обеспечению. У них есть собственные репозитории, пайплайны, модульные тесты и релизы.

  • По возможности для запуска внешних задач пайплайны используют специфичные для языка инструменты, такие как Make, Rake, npm, Maven и т.п. Для того чтобы упростить пайплайн и сохранить идентичность процесса локальной сборки и CI.

  • Библиотеки легко найти и у них хорошая документацию.

Паттерн 3 отдельные пайплайны для сборки и развертывания

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

  • Должна быть одна сборка и много развертываний. Сосредоточьтесь на сборке. В ее результате появляются артефакты, которые можно развернуть несколько раз.

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

  • Упакуйте всё вместе. Всё весь исходный код, включая код инфраструктуры, должен храниться вместе, чтобы стать версионнированным пакетом.

Паттерн 4 запуск правильного пайплайна

Коммиты в ветки, пул реквесты и слияние с основной веткой всё это может инициировать различное поведение пайплайна, оптимизированное под конкретную команду.

  • Открытие pull request'а создает эфемерную среду для тестирования.

  • Слияния с основной веткой развертываются в не-продакшн или демо-среде, содержащей последний интегрированный код.

  • Создание новых тэгов приводит к выпуску релиза.

Паттерн 5 быстрая обратная связь

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

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

  • Быстрые пайплайны сборки выполняют необходимые задания за несколько минут.

  • Каждый успешный запуск завершается созданием версии пакета и результатами статического анализа.

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

Паттерн 6 стабильные внутренние релизы

Развертываются только версионнированные пакеты, созданные пайплайном сборки. Эти развертывания запускаются вручную или автоматически по событиям.

  • Каждая ветка кода получает полное эфемерное окружение, названное в соответствии с именем ветки, которое можно легко создать или уничтожить.

  • Любой инженер может создать или удалить эфемерную среду.

  • CI runners используют возможности cloud-native IAM с временными разрешениями, чтобы получить необходимые роли и разрешения для выполнения своей работы.

Паттерн 7 история релизов

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

  • "Релизный шлюз" (release gate) в виде кода и стандартизированные процессы релиза позволяют выпускать релизы по требованию.

  • Автоматизированные релизы оставляют историю, которую можно проанализировать в дальнейшем.

  • Release gate может вызывать внешние API и использовать ответ, чтобы решить продолжать релиз или остановить его.

Проблемы

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

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

Итог

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

Посмотреть специальное предложение.

Читать ещё:

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru