Документация: https://docs.gitlab.com/ee/ci/
Get started with GitLab CI/CD: https://docs.gitlab.com/ee/ci/
Learn GitLab with tutorials: https://docs.gitlab.com/ee/tutorials/
GitLab with Git Essentials - Hands-On Lab: Use GitLab To Merge Code
Удобен тем, что содержит всё необходимое, не нужно использовать кучу различных сервисов:
Ещё один комбайн типа Гитлаба - Jetbrains Space.
.gitlab-ci.yml
, кладётся в корень репозитория..gitlab-ci.yml
, но лучше не задавать переменные в самом файле, а определять их в настройках CI/CD.GitLab Component | Function | Also Known As… |
---|---|---|
Project | The core building block where work is organized, managed, tracked and delivered to help the team to collaborate and plan work in the form of issues. | Repository |
Group | A collection of projects and/or other groups. They are like folders. | Project |
Issue | An issue is part of a project. It is the fundamental planning object where the team documents the use case in the description, discusses the approach, estimates the size/effort (issue weight), tracks actual time/effort, assigns work, and tracks progress. | Story, Narrative, Ticket |
Epic | A collection of related issues across different groups and projects to help organize by theme | Initiatives, Themes |
Merge Request | The linkage between the issue and the actual code changes. Captures the design, implementation details (code changes), discussions (code reviews), approvals, testing (CI Pipeline), and security scans. | Pull Request |
Label | Used to tag and track work for a project or group and associate issues with different initiatives | Tag |
Board | A visual listing of projects and issues useful for teams to manage their backlog of work, prioritize items, and move issues to the team or specific stage in the project. | Kanban |
Milestone | A sprint or deliverable(s), helping you organize code, issues, and merge requests into a cohesive group | Release |
Roadmap | A visual representation of the various epics for the group |
Поток
Code Review Workflow
Дополнительные инструменты
Дополнительно: Terraform Module Registry, Dependency Proxy (local proxy for frequently-used upstream images and packages. The Dependency Proxy caches both the manifest and blobs for a given image, so when you request it again, Docker Hub does not have to be contacted.)
Это снапшот проекта для конечных пользователей, включая инсталляционные пакеты и примечания к релизу. Релизы могут создаваться в любой ветке. Создание релиза также создаёт git-тэг для пометки точки релиза в исходном коде.
Релиз включает:
Когда релиз создаётся, то
После релиза можно
Creating a release
Release CI/CD examples
Release CLI tool
В Гитлабе есть несколько функций для удобства развертывания и теста релизов.
Пайплайн - это файл .gitlab-ci.yml
в корне git-репозитория. Состоит из последовательных этапов (stages), в каждом этапе есть задачи (jobs). По умолчанию, если не заданы условия, задачи выполняются параллельно. Если какая-то задача выполнилась с ошибкой, то весь пайплайн будет провален.
Вид простейшего пайплайна:
variables: ART_TOKEN: "37tui2v3pd8238PGd83g2" stages: - build - release - notify build_a: stage: build build_b: stage: build # https://docs.gitlab.com/ee/ci/yaml/index.html#only--except only: # срабатывать только, если: - master # ветка master - tags # может быть инициировано тэгом changes: - backend/* # при изменении файлов в каталоге backend - frontend/* # при изменении файлов в каталоге frontend except: # не срабатывать, если: refs: # refs: - значение по умолчанию, можно не писать. - schedules # этап вызван по расписанию - triggers # или по триггеру (через вызов API) variables: - $CI_COMMIT_MESSAGE =~ /skip tests/ # или сообщение коммита содержит текст release_a: stage: release needs: # зависимость задач друг от друга, в веб-интерфейсе GitLab есть их визуальное представление - build_a release_b: stage: release slack_notify_build_b: stage: notify only: changes: - backend/* - frontend/* script: - | curl -X POST -H 'Content-type: application/json' \ --data "{\"text\":\":building_construction: *$CI_PROJECT_NAME* ($CI_COMMIT_REF_NAME) backend - <$CI_API_V4_URL/projects/$CI_PROJECT_ID/jobs/artifacts/$CI_COMMIT_REF_NAME/download?job=build-backend-code-job&private_token=$ART_TOKEN|:package:>\"}" \ https://hooks.slack.com/services/XXXXXXXXX/XXXXXXXXXX/w8ydugdiug2p938 needs: - build_b
Релиз - это скомпилированный и упакованный исходный код + файл json, содержащий информацию о выпуске. Дополнительно можно добавить описание и прочее, например, ссылку на объект в хранилище артефактов. Можно использовать встроенное хранилище (Package Registry), а можно другое, например, Nexus.
Использование спецсимволов в строке скрипта: https://docs.gitlab.com/ee/ci/yaml/script.html (как экранировать символы, брать строку в кавычки и т. д.)
Переменные: https://docs.gitlab.com/ee/ci/variables/
Средство для проверки синтаксиса внутри Гитлаба - CI Lint.
Токен можно сгенерировать как для пользователя в целом (Edit profile → Access tokens), так и для отдельного репозитория (Settings → Access tokens).
- |-
сохраняет переносы строк. Удобно для heredoc, if/else и т. п.
- >
превращает переносы строк в пробелы. Удобно для простого переноса длинной строки.
release: image: node:12-stretch-slim stage: release before_script: - apt-get update && apt-get install -y curl git jq script: - |- PAYLOAD=$(cat << JSON { "branch": "master", "commit_message": "some commit message", "actions": [ { "action": "create", "file_path": "foo/bar", "content": "some content" } ] } JSON ) - > curl -X POST https://requestbin.io/1f84by61 --header 'Content-Type: application/json; charset=utf-8' --data-binary "$PAYLOAD" when: manual only: - /^release-.*$/
Могут являться частью этапа (stage). В рамках одного этапа задачи выполняются одновременно. Этап не начинается, пока не закончится предыдущий. Зависимость задач от выполнения этапов:
stages: - test - build - push - deploy lint-test: stage: test before_script: - echo "prepare lint test" script: - echo "lint test" after_script: - echo "cleaing up lint test data" unit-test: stage: test before_script: - echo "prepare unit test" script: - echo "unit test" after_script: - echo "cleaing up unit test data" build-windows: stage: build script: # задача закончится ошибкой, и задача push-windows и deploy будут пропущены - wrong-echo "build windows image" - echo "tag windows image" build-linux: stage: build script: - echo "build linux image" - echo "tag linux image" push-windows: stage: push needs: - build-windows script: - echo "log in to repository" - echo "push windows image" push-linux: stage: push needs: - build-linux script: - echo "log in to repository" - echo "push linux image" deploy: stage: deploy script: - echo "deploy"
Можно вызвать скрипт, но сначала его нужно сделать исполняемым:
script: - chmod +x ./scriptfile.sh - ./scriptfile.sh
Если скрипт что-то создал в каталоге, то можно это не удалять, т. к. пайплайн запускается каждый раз по новой и не использует старого окружения.
Определяют, при каких условиях задача выполняется.
# выполнять только в ветке main job: only: - main
workflow
определяет глобальные настройки пайплайна. Положим, чтобы не добавлять в каждую задачу only:
, можно задать это глобально:
workflow: rules: # если ветка не main и вызывается не с помощью merge/pull request, то - if: $CI_COMMIT_BRANCH != "main" && $CI_PIPELINE_SOURCE != "merge_request_event" when: never # никогда не выполнять - when: always # в остальных случаях - выполнять
$CI_COMMIT_BRANCH
и $CI_PIPELINE_SOURCE
- встроенные переменные.
Помимо встроенных переменных, есть произвольные.
Настройка: Settings (внутри проекта) → CI/CD → Variables.
Тип переменной может быть file. Например, можно занести туда конфигурационный файл для какого-либо сервиса. При вызове переменной в ней содержится полный путь к этому файлу, например,
# так выводится путь echo "$CONF_FILE" /builds/user/projectname.tmp/CONF_FILE # а так - содержимое cat $CONF_FILE
Можно задать переменные и внутри пайплайна
variables: image_repo: docker.io/id/app image_tag: v2.0
Если блок variables:
находится внутри задачи, то они действуют только внутри этой задачи. Чтобы переменные действовали во всех задачах пайплайна, блок variables:
нужно перенести на верхний уровень (вровень с самими задачами).
В пайплайне нужно добавить –build-arg CI_REGISTRY_IMAGE=${CI_REGISTRY_IMAGE}
в команду сборки.
build: stage: build image: docker:20.10.12-dind-rootless before_script: - until docker info; do sleep 1; done - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY script: - cd backend - > docker build --build-arg VERSION=$VERSION --build-arg CI_REGISTRY_IMAGE=${CI_REGISTRY_IMAGE} --tag $CI_REGISTRY_IMAGE/backend:$CI_COMMIT_SHA . - docker push $CI_REGISTRY_IMAGE/backend:$CI_COMMIT_SHA
Dockerfile:
ARG CI_REGISTRY_IMAGE=${CI_REGISTRY_IMAGE} FROM ${CI_REGISTRY_IMAGE}/node:16.18-alpine AS builder
Хранение файлов, которые могут быть использованы в соседней задаче на раннере повторно вместо того, чтобы каждый раз качать их из интернета или с сервера, например, npm/ruby/python/go-пакеты.
Артефакты помещаются на сервер, кэш хранится на раннере. Если раннеров много, нужно настраивать распределённый кэш (distributed caching), хранящийся в S3. Хоть это всё равно удалённый сервер, это более эффективно, чем качать из интернета, и качается один zip-файл вместо множества мелких.
Настраивается соответствующим разделом в задаче. Если не задать ключ (имя кэша), то он будет называться default. Все задачи, где совпадает ключ, будут использовать один и тот же кэш.
run_unit_tests: ... cache: key: "npmdeps_$CI_COMMIT_REF_NAME" paths: - app/node_modules policy: pull-push
«Pull-push» - значение по умолчанию, его можно не указывать, т. е., кэш используется и потом обновляется при необходимости. Если есть какая-то параллельно выполняющаяся задача, использующая тот же кэш, то политику нужно отрегулировать, чтобы обе задачи не пытались писать одни и те же файлы в кэш. В одной из задач ставится policy: pull
, чтобы она только использовала кэш без его обновления. Иногда, в больших пайплайнах, бывает выделенная задача для создания кэша, там тогда нужно ставить политику в push
.
Команду npm install
всё равно нужно оставить, чтобы отсутствие кэша или какие-то проблемы с ним не влияли на общую работу пайплайна.
Если кэш создаётся с помощью Docker executor, то нужно настраивать volume на раннере, чтобы кэш не уничтожился вместе с контейнером. Путь в cache_dir
и в volumes
должен быть одним и тем же.
[[runners]] ... executor = "docker" cache_dir = "/cache" ... [runners.docker] volumes = ["/cache"]
Разница между первым и вторым запуском пайплайна. Первый раз кэш формировался, второй - использовался.
Почистить кэш можно кнопкой «Clear runner caches» в CI/CD → Pipelines. «Почистить» в этом случае значит просто не использовать кэшв следующих запусках пайплайна, физически кэш не удаляется. Где хранится кэш?
Идут в комплекте с Гитлабом и могут быть включены в пайплайн в любом проекте, например, SAST (тест кода на безопасность).
Шаблоны: https://gitlab.com/gitlab-org/gitlab/-/tree/master/lib/gitlab/ci/templates
Включить SAST:
sast: stage: test tags: - remote - docker include: template: Jobs/SAST.gitlab-ci.yml
Можно включить до 100 шаблонов. Местоположение раздела include:
в пайплайне неважно.
Если в проекте ещё нет пайплайна, то в разделе CI/CD → Pipelines можно выбрать шаблон для быстрого его создания.
ssh-keygen -t ed25519 -C "dev-server"
~/.ssh/authorized_keys
пользователя, под которым будет деплой.nano /etc/ssl/certs/git.ca.crt # вставить содержимое сертификата с сервера
deploy_to_dev: stage: deploy tags: - remote - shell before_script: - chmod 400 $SSH_PRIVATE_KEY script: - ssh -o StrictHostKeyChecking=no -i $SSH_PRIVATE_KEY ssu@$DEV_SERVER_HOST " docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY && docker run -d -p 3000:3000 $IMAGE_NAME:$IMAGE_TAG "
Вышеприведённый вариант с Докером неудобен, т. к. контейнер не заменяется, и при повторном запуске пайпдайна будет ошибка, т. к. порт занят. Эту проблему решает docker-compose.
deploy_to_dev: stage: deploy tags: - remote - shell before_script: - chmod 400 $SSH_PRIVATE_KEY - scp -o StrictHostKeyChecking=no -i $SSH_PRIVATE_KEY ./docker-compose.yaml ssu@$DEV_SERVER_HOST:~ script: - ssh -o StrictHostKeyChecking=no -i $SSH_PRIVATE_KEY ssu@$DEV_SERVER_HOST " export IMAGE_NAME=$IMAGE_NAME && export IMAGE_TAG=$IMAGE_TAG && docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY && docker-compose down && docker-compose up -d " environment: name: development url: $DEV_ENDPOINT
Так как сервер, где запускается контейнер, ничего не знает об окружении, надо пробрасывать значения переменных через export
и копировать docker-compose.yaml
по SSH. CСоответственно, в docker-compose.yaml можно сослаться на переменные
version: "3.3" services: app: image: $IMAGE_NAME:$IMAGE_TAG ports: - 3000:3000
SSH keys, документация: https://docs.gitlab.com/ee/ci/ssh_keys/
Канон - major.minor.patch. Здесь в примере npm и версия находится в файле app/version.json, откуда она будет браться:
{ "name": "bootcamp-node-project", "version": "1.0", .. },
Недостающая часть добавляется из ID пайплайна. jd
надо поставить на воркере, если его там нет. Полученный файл передаётся через артефакт.
build_image: ... before_script: - export PACKAGE_JSON_VERSION=$(cat app/package.json | jq -r .version) - export VERSION=$PACKAGE_JSON_VERSION.$CI_PIPELINE_IID - echo $VERSION > version-file.txt artifacts: paths: - version-file.txt
Артефакты автоматически доступны в последующих этапах, но не между задачами внутри одного этапа. Чтобы сделать артефакт доступным для соседней задачи, нужно настроить dependencies/needs.
Needs ждёт выполнения задач, указанных там, а dependencies берёт артефакты из указанных задач. Если указаны и needs, и dependencies одновременно, то needs должен содержать задачи, перечисленные в dependencies, иначе работать не будет. В то же время, если указать только needs, артефакты будут скачаны из задач, перечисленных там, поэтому нет необходимости указывать dependencies, если они совпадают с needs.
push_image: ... needs: - build_image before_script: - export VERSION=$(cat version-file.txt) - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY script: - docker push $IMAGE_NAME:$VERSION
Чтобы заблокировать скачивание артефактов, например, на этапе последующего тестирования, нужно указать пустой массив:
test_dev: stage: deploy dependencies: [] script: - echo "testing dev"
В специфическом случае с npm можно провернуть вариант с .env вместо выгрузки артефактов. Это файл «ключ-значение». В блоке артефактов указывается reports → dotenv.
build_image: ... before_script: - export PACKAGE_JSON_VERSION=$(cat app/package.json | jq -r .version) - export VERSION=$PACKAGE_JSON_VERSION.$CI_PIPELINE_IID - echo "VERSION=$VERSION" > build.env - echo "DEVELOPER=Vasya" >> build.env artifacts: reports: dotenv: build.env
После этого последующие соседние задачи (c соответствующими dependencies/needs) будут уже знать про эти переменные, ничего указывать не надо.
dev → | staging → | prod |
---|---|---|
Functional tests Integration tests SAST tests | Performance tests DAST tests |
Для примера развёртывание будет на одну и ту же машину с использованием разных портов. Для начала нужно задать переменную для порта в докер-композе, чтобы управлять ей из пайплайна.
ports: - ${APP_PORT}:3000
Задать переменную в пайплайне в задаче deploy_to_dev: export APP_PORT=3000
, а также export COMPOSE_PROJECT_NAME=dev
, чтобы задать имя проекта, иначе docker-compose down
в другом деплое будет останавливать один и тот же контейнер (файл Докер-композ используется ведь тот же самый, и имя по умолчанию генерируется одинаковое).
Ну а затем копируется задача deploy_to_dev в deploy_to_staging и там меняется всё, что нужно. Создаются этапы deploy_dev и deploy_staging, меняются/дописываются переменные. Перед deploy_to_staging должна стоять задача каких-нибудь functional tests, чтобы, если тесты не прошли, развётрывание на тестовое окружение уже не шло. Чтобы не плодить этапов, эти тесты могут входить в этап deploy_dev с needs: - deploy_to_dev
.
Чтобы не копировать практически одинаковые задачи деплоя много раз, нужно сделать шаблон задачи.
Положим, взяли задачу deploy_to_dev, поменяли название (точка спереди - чтобы эта «задача» не выполнялась), удалён stage:
и все параметры, которые меняются от одного окружения к другому ($SSH_PRIVATE_KEY), указываются в блоке variables:
как задаваемые извне.
.deploy: tags: - remote - shell variables: SSH_PRIVATE_KEY: "" SERVER_HOST: "" DEPLOY_ENV: "" APP_PORT: "" ENDPOINT: "" before_script: - chmod 400 $SSH_PRIVATE_KEY - export VERSION=$(cat version-file.txt) - scp -o StrictHostKeyChecking=no -i $SSH_PRIVATE_KEY ./docker-compose.yaml ubuntu@$SERVER_HOST:~ script: - ssh -o StrictHostKeyChecking=no -i $SSH_PRIVATE_KEY ubuntu@$SERVER_HOST " export IMAGE_NAME=$IMAGE_NAME && export IMAGE_TAG=$VERSION && export APP_PORT=$APP_PORT && export COMPOSE_PROJECT_NAME=$DEPLOY_ENV && docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY && docker-compose down && docker-compose up -d " environment: name: $DEPLOY_ENV url: $ENDPOINT
А потом можно ссылаться на этот шаблон, задавая недостающие параметры и значения переменных. Имена переменных лучше делать разными, например, так работать не будет: SSH_PRIVATE_KEY: $SSH_PRIVATE_KEY
, если $SSH_PRIVATE_KEY
имеет тип File. В этом случае при присвоении вместо пути к файлу в переменную будет пихаться его содержимое.
deploy_to_dev: extends: .deploy stage: deploy_dev variables: SSH_KEY: $DEV_SSH_PRIVATE_KEY SERVER_HOST: $DEV_SERVER_HOST DEPLOY_ENV: development APP_PORT: 3000 ENDPOINT: $DEV_ENDPOINT deploy_to_staging: extends: .deploy stage: deploy_staging variables: SSH_KEY: $STAGING_SSH_PRIVATE_KEY SERVER_HOST: $STAGING_SERVER_HOST DEPLOY_ENV: staging APP_PORT: 4000 ENDPOINT: $STAGING_ENDPOINT
По состоянию на 2023 год репозитории для установки закрыты для России, качать и ставить надо пакетом.
https://packages.gitlab.com/app/gitlab/gitlab-ce/search?q=amd64&dist=jammy (для Ubuntu 22.04)
wget --content-disposition https://packages.gitlab.com/gitlab/gitlab-ce/packages/ubuntu/jammy/gitlab-ce_16.0.5-ce.0_amd64.deb/download.deb dpkg -i gitlab-ce_16.0.5-ce.0_amd64.deb
DockerHub: https://hub.docker.com/u/gitlab
Install on Docker: https://docs.gitlab.com/ee/install/docker.html
Private CI/CD using Docker: https://oramind.com/private-cicd-using-gitlab-docker/
https://packages.gitlab.com/gitlab/gitlab-ce
https://docs.gitlab.com/ee/update/package/index.html#upgrade-using-a-manually-downloaded-package
Скачать: https://gitlab.com/gitlab-org/gitlab-runner/-/releases
Установить:
curl -LJO "https://gitlab-runner-downloads.s3.amazonaws.com/latest/deb/gitlab-runner_amd64.deb" dpkg -i gitlab-runner_amd64.deb
Регистрация: gitlab-runner register
# Регистрировать можно несколько раз, если нужно несколько видов раннера на одной и той же машине. # URL и token берутся из Settings -> CI/CD -> Runners URL='http://git.example.com/' REG_TOKEN='GD1358941-xY4DE8k7Mr3dffgLLun' gitlab-runner register \ --url $URL \ --registration-token $REG_TOKEN \ --executor shell \ --tag-list "shell" \ --description t-docker2 gitlab-runner register \ --url $URL \ --registration-token $REG_TOKEN \ --executor docker \ --tag-list "docker" \ --docker-image "alpine:3.18" \ --description t-docker2 # Перезапустить сервис, если раннер не готов сразу systemctl restart gitlab-runner # Для докера нужно добавить пользователя gitlab-runner в группу docker sudo usermod -aG docker gitlab-runner reboot
Если задачи застревают (stack), надо поставить галку Run untagged jobs в Admin → Settings → CI/CD → Runners → Свойства раннера. Но лучше прописывать тэги раннера в задачах пайплайна.
https://docs.gitlab.com/runner/install/linux-manually.html
https://docs.gitlab.com/runner/register/
Здесь:
CRT_DIR=/etc/gitlab/ssl CN=git.example.com PORT=5050 mkdir -p -m 700 $CRT_DIR cd $CRT_DIR # Сгенерить сертификаты openssl genrsa -out $CN.ca.key 2048 openssl req -new -x509 -days 36500 -key $CN.ca.key -subj "/C=CN/ST=GD/L=SZ/O=$CN/CN=$CN Root CA" -out $CN.ca.crt openssl req -newkey rsa:2048 -nodes -keyout $CN.key -subj "/C=CN/ST=GD/L=SZ/O=$CN/CN=$CN" -out $CN.csr openssl x509 -req -extfile <(printf "subjectAltName=DNS:$CN") -days 36500 -in $CN.csr -CA $CN.ca.crt -CAkey $CN.ca.key -CAcreateserial -out $CN.crt chmod 600 $CRT_DIR/$CN.* ################################### # В конфиге /etc/gitlab/gitlab.rb ################################### # Включить SSL для самого Гитлаба sed -i "/[^_]external_url /c external_url 'https://$CN' /nginx\['ssl_certificate'\] /c nginx['ssl_certificate'] = \"/etc/gitlab/ssl/$CN.crt\" /nginx\['ssl_certificate_key'\] /c nginx['ssl_certificate_key'] = \"/etc/gitlab/ssl/$CN.key\" " /etc/gitlab/gitlab.rb # Включить реестр контейнеров sed -i "/registry_external_url /c registry_external_url 'https://$CN:$PORT' /registry_nginx\['enable'\] /c registry_nginx['enable'] = true /registry_nginx\['listen_port'\] /c registry_nginx['listen_port'] = $PORT " /etc/gitlab/gitlab.rb # Перечитать настройки sudo gitlab-ctl reconfigure
На раннере:
# Добавить сертификат git.ca.crt в доверенные nano /etc/ssl/certs/git.ca.crt # вставить содержимое сертификата с сервера update-ca-certificates # После этого можно регистрировать раннер
Если не добавить в доверенные на уровне самой системы, надо будет париться с регистрацией раннера на сервере и с докером:
### Let Docker accept your self-signed certificate # Per default, Docker will not accept your self-signed certificate. You need to create a folder with your CA in order to make Docker aware that # your certificate is valid. For that reason, you create a folder of your trusted Docker registry and copy your CA into the folder. # If you will not copy the CA in the folder. You will receive the following error: # Registry fails with x509 certificate signed by unknown authority sudo mkdir -p /etc/docker/certs.d/git.example.com:5050 sudo cp /etc/gitlab/ssl/git.example.com.ca.crt /etc/docker/certs.d/git.example.com:5050/ ### Let GitLab runners accept your self-signed certificate # The same error will occur when you want to register your GitLab runner: # Post “https://git.example.com/api/v4/runners”: x509: certificate signed by unknown authority # Please use the following command in order to register your GitLab runner successfully: sudo gitlab-runner register --tls-ca-file="/etc/gitlab/ssl/git.example.com.ca.crt"
Справка: <YOUR_GITLAB_URL>/help/administration/packages/container_registry.md
x509: certificate relies on legacy Common Name field, use SANs instead
Installing a root CA certificate in the trust store
GitLab server with a self-signed certificate and embedded docker registry
Если микросервисов в приложении больше чем один, возникает 2 подхода к хранению кода
Чтобы контейнеры могли взаимодействовать, их нужно поместить в одну сеть. Для этого в докер-композ-файле указывается определённая сеть, которая должна уже присутствовать (параметр external)
version: "3.3" services: app: image: ${DC_IMAGE_NAME}:${DC_IMAGE_TAG} ports: - ${DC_APP_PORT}:${DC_APP_PORT} networks: - micro_service networks: micro_service: external: name: micro_service
Параметр name:
в опциях сети предписывает не прибавлять к её названию имя проекта, т. е. будет именно micro_service
, а не project_micro_service
.
А в пайплайне в шаблоне задачи деплоя прописать создание этой сети и указание, чтобы задача не обламывалась, если сеть уже есть:
docker network create micro_service || true && docker-compose down && docker-compose up -d
Подготовка:
Дальше в каждую репу копируются docker-compose.yml и .gitlab-ci.yml и редактируются соответственно. Шаблоны задач можно переделать в реальные задачи, т. к. сервис в репе только один, отредактировав их и убрав задачи, вызывающие эти шаблоны, удалить из них блоки переменных, ожидаемых извне. Убрать детекты срабатывания в подкаталогах и заходы в подкаталоги из задач.
Полный листинг .gitlab-ci.yml (polyrepo) сервиса frontend
Проблема множества репозиториев в том, что одна и та же конфигурация повторяется из раза в раз, а если работают разные команды разрабов, то эти конфигурации начинают разниться в подходах, появляются разные тесты и т. п. и управлять этим барахлом становится тяжело. Для решения этой проблемы есть шаблоны задач, но не в том же пайплайне, а вынесенные в отдельный проект, на которые можно ссылаться как на шаблон SAST, к примеру.
Надо вынести повторяющиеся задачи в отдельные .yml-файлы, например
# Можно также вписать блок с переменными, которые нужно передать извне для большей наглядности, но это необязательно variables: MICRO_SERVICE: "" SERVICE_VERSION: "" build: stage: build before_script: - export IMAGE_NAME=$CI_REGISTRY_IMAGE/microservice/$MICRO_SERVICE - export IMAGE_TAG=$SERVICE_VERSION script: - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - docker build -t $IMAGE_NAME:$IMAGE_TAG . - docker push $IMAGE_NAME:$IMAGE_TAG
В файле шаблона всё должно быть параметризовано по-максимуму. В основном пайплайне нужно сослаться на файлы шаблона, а в самой задае добавить только те разделы, которых нет в шаблоне.
Местоположение раздела include:
в пайплайне не имеет значения.
Если нужно что-то добавить в раздел, который уже есть в шаблоне (например, before_script), то придётся писать его полностью, т. к. при наличии раздела в основном пайплайне раздел шаблона полностью заменяется.
include: - local: '.build-template.yml' - local: '.deploy-template.yml' ... build: tags: - group - shell before_script: - echo "Добавленные действия, а дальше то, что уже есть в шаблоне, иначе не заработает" - export IMAGE_NAME=$CI_REGISTRY_IMAGE/microservice/$MICRO_SERVICE - export IMAGE_TAG=$SERVICE_VERSION
Файлы шаблонов можно вынести в отдельный репозиторий, например, ci-templates. В этом случае ссылаться на эти шаблоны нужно так:
include: - project: mymicroservice-cicd/ci-templates ref: main file: - build.yml - deploy.yml
ref: main
указывает на ветку.
Т. к. репозиторий в той же группе, указывается группа/репозиторий
. Если бы он был вне группы, надо было бы указывать имя пользователя/репозиторий
.
If you would rather send application email via an SMTP server instead of via Sendmail or Postfix, add the following configuration information to /etc/gitlab/gitlab.rb
and run gitlab-ctl reconfigure
.
https://docs.gitlab.com/omnibus/settings/smtp.html
gitlab_rails['smtp_enable'] = true gitlab_rails['smtp_address'] = "mail.example.com" gitlab_rails['smtp_port'] = 25 gitlab_rails['smtp_domain'] = "example.com" gitlab_rails['smtp_enable_starttls_auto'] = true gitlab_rails['smtp_openssl_verify_mode'] = 'none' ### Email Settings gitlab_rails['gitlab_email_from'] = 'git@example.com' gitlab_rails['gitlab_email_display_name'] = 'Git'
Проверка отправки писем из консоли
gitlab-rails console -e production Notify.test_email('user@example.com', 'Hello World', 'This is a test message').deliver_now
После перезапуска возникает ошибка 502. Нужно подождать, т. к. Gitlab запускается небыстро.
См. статус: gitlab-ctl
В конфигурацию /etc/gitlab/gitlab.rb добавить gitlab_rails['smtp_openssl_verify_mode'] = 'none' # Затем gitlab-ctl reconfigure gitlab-rake cache:clear RAILS_ENV=production
Возникает при сборке образа на раннере (shell executor). Решение:
usermod -aG docker gitlab-runner
service docker restart
Раннер не может подключиться к реестру контейнеров:
$ echo "$CI_REGISTRY_PASSWORD" |docker login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY error during connect: Post "http://docker.example.com/v1.24/auth": command [ssh -l cicd -- 10.1.0.138 docker system dial-stdio] has exited with exit status 255, please make sure the URL is valid, and Docker 18.09 or later is installed on the remote host: stderr=Permission denied, please try again. Permission denied, please try again. cicd@10.1.0.138: Permission denied (publickey,password).
Решение: не раннере удалить всё в каталоге /home/gitlab-runner
.
Shifting Security Left - GitLab DevSecOps Overview: https://www.youtube.com/watch?v=XnYstHObqlA
Гитлаб предлагает сканеры безопасности:
Отчёты / управление:
# SSH / Context ssh -o StrictHostKeyChecking=no -i $SSH_PRIVATE_KEY $DEPLOY_USER@$DEPLOY_HOST docker context create remote --docker "host=ssh://cicd@host" docker context use remote # Вход в репозиторий docker login -u mail@example.com -p P@ssw0rd git.example.com:5050 # Пулл для локального тестирования docker pull git.example.com:5050/personal-account/backend-laravel/lk/backend_app:1.0 docker pull git.example.com:5050/personal-account/backend-laravel/lk/backend_nginx:1.0 # Сети надо создавать overlay и swarm, иначе не будет работать внутренний DNS docker network create --driver=overlay --scope=swarm lk_rabbitmq docker network create --driver=overlay --scope=swarm lk_backend # Запуск docker stack deploy -c ./docker-compose.yml lk #docker stack rm lk docker exec $(docker ps -qf name=lk_app) mkdir -p /var/www/html/storage/framework/sessions mkdir -p /var/www/html/storage/framework/sessions chown -R 82:82 /var/www/html/storage
version: "3.9" services: app: &app image: git.example.com:5050/personal-account/backend-laravel/lk/backend_app:1.0 healthcheck: test: ["CMD", "netstat", "-an", "|fgrep", ":9000"] volumes: - /docker/lk/www/storage:/var/www/html/storage networks: - lk_backend - lk_rabbitmq nginx: image: git.example.com:5050/personal-account/backend-laravel/lk/backend_nginx:1.0 environment: NGINX_ROOT: /var/www/html/public NGINX_FASTCGI_PASS: app healthcheck: test: ["CMD", "curl", "-f", "http://localhost:80"] ports: - 8080:80 networks: - lk_backend db: image: mysql:8.0 environment: MYSQL_DATABASE: personal-area MYSQL_ROOT_PASSWORD: password MYSQL_PASSWORD: password MYSQL_USER: personal-area-user SERVICE_TAGS: dev SERVICE_NAME: mysql healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-p$$MYSQL_ROOT_PASSWORD"] volumes: - /docker/lk/db:/docker-entrypoint-initdb.d networks: - lk_backend rabbitmq: image: rabbitmq:management-alpine hostname: rabbitmq environment: RABBITMQ_DEFAULT_USER: admin RABBITMQ_DEFAULT_PASS: admin healthcheck: test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"] ports: - 5672:5672 - 15672:15672 volumes: - /docker/lk/rabbitmq:/var/lib/rabbitmq networks: - lk_rabbitmq queue-lk-job-broadcast: &queue <<: *app command: php artisan queue:work --queue=lk.job.broadcast healthcheck: disable: true queue-lk-job-catalog_order: <<: *queue command: php artisan queue:work --queue=lk.job.catalog_order queue-lk-job-notification: <<: *queue command: php artisan queue:work --queue=lk.job.notification queue-lk-job-schedule: <<: *queue command: php artisan queue:work --queue=lk.job.schedule # И ещё куча таких же, слушающих очереди websockets: <<: *queue command: php artisan websockets:serve networks: lk_backend: external: true name: lk_backend lk_rabbitmq: external: true name: lk_rabbitmq