Перейти к содержанию

Описание структуры CI/CD шаблона и принципов его работы


Что уже есть и как это работает

Есть общий набор файлов в проекте generic/common-ci — туда вынесены общие jobs (build, deploy, linter, security-scan и т.д.).

В каждом репозитории с сервисом вы подключаете (с помощью include) нужные файлы из generic/common-ci. Локально в .gitlab-ci.yml можно переопределять/добавлять джобы.

Шаблон управляет поведением по веткам:

  • stage — автоматический deploy в stage-namespace
  • master — деплой в preprod-namespace, чаще вручную
  • теги vX.Y.Z — deploy в prod-namespace

Пайплайн использует переменные (CI/CD Variables) для передачи настроек: RELEASE_NAME, NAMESPACE, CHART_VERSION, DOCKERFILE_PATH и т.д.


Структура common-ci

Шаблоны CI/CD разбиты на модульные файлы:

Файл Назначение
base.yaml Глобальные переменные и стадии
docker.yaml Сборка Docker-образов
helm.yaml Сборка Helm-чартов и деплой
universal-pipeline.yaml Оркестратор пайплайна (объединяет всё)
common-security-scan.yaml SAST, Secret-Detection, Trivy

base.yaml — глобальные переменные

variables:
  CR: cr.yandex/crp3ccidau046kdj8g9q
  HELM_REGISTRY: oci://cr.yandex/crp3ccidau046kdj8g9q/charts
  DOCKER_HOST: "tcp://docker:2375"
  DOCKERFILE_PATH: "Dockerfile"
  HELM_PATH: ".helm"
  BUILD_ARGS: ""
  HELM_SET_ARGS: ""
  K8S_HUSTLER_BRANCH: ""
  CI_TRIGGER_SOURCE: ""
  ENABLE_BUILD_IMAGE: "true"
  ENABLE_BUILD_CHART: "true"
  ENABLE_DEPLOY: "true"
  ENABLE_STATE_UPDATE: "false"

stages:
  - lint
  - test
  - prebuild-secscan
  - security
  - build
  - state-update
  - deploy

docker.yaml — сборка образов

.base_docker:
  image: docker:24.0.5
  stage: build
  before_script:
    - echo ${SAREX_REGISTRY_KEY} | base64 -d | docker login --username json_key --password-stdin cr.yandex
    - export DOCKER_BUILDKIT=1
  script:
    - docker build -f ${DOCKERFILE_PATH} ${BUILD_ARGS} -t ${IMAGE_NAME} .
    - docker push ${IMAGE_NAME}
  tags:
    - build

helm.yaml — сборка чартов и деплой

.base_helm_build:
  image: alpine/helm:3.17.0
  stage: build
  script:
    - helm dependency update ${HELM_PATH}
    - helm package ${HELM_PATH} --version ${CHART_VERSION} -d ./dist
    - helm push dist/*.tgz ${HELM_REGISTRY}

.base_deploy:
  image: alpine/helm:3.17.0
  stage: deploy
  variables:
    IMAGE_NAME: ${CR}/${RELEASE_NAME}:${STAND}_${CI_COMMIT_SHORT_SHA}
  script:
    - helm dependency update ${HELM_PATH}
    - helm template "${RELEASE_NAME}" ${HELM_PATH} -n "${NAMESPACE}" -f ${HELM_PATH}/values.yaml ${HELM_SET_ARGS}
    - helm upgrade -i "${RELEASE_NAME}" ${HELM_PATH} --create-namespace --atomic -n "${NAMESPACE}" -f ${HELM_PATH}/values.yaml ${HELM_SET_ARGS}

Общая сборка pipeline (порядок событий)

  1. GitLab загружает .gitlab-ci.yml и разрешает include
  2. Все подключённые файлы мёрджатся в один итоговый YAML
  3. Выполняется workflow.rules — задаются переменные для конкретной ветки/тега
  4. Собирается список job'ов и запускается пайплайн

Стадии — порядок выполнения

lint → test → prebuild-secscan → security → build → state-update → deploy

Jobs проходят в этом порядке, если rules для них сработали.


Описание джоб

build

build:
  extends: .base_docker
  variables:
    IMAGE_NAME: ${CR}/${RELEASE_NAME}:${STAND}_${CI_COMMIT_SHORT_SHA}
  rules:
    - if: '$ENABLE_BUILD_IMAGE == "false"'
      when: never
    - if: '$CI_COMMIT_BRANCH == "stage"'
    - if: '$CI_COMMIT_BRANCH == "master"'
      when: manual
    - if: '$CI_COMMIT_TAG =~ /^v?\d+\.\d+\.\d+/'
      when: manual

Цель: собрать и запушить Docker-образ в registry.

build_chart

build_chart:
  extends: .base_helm_build
  rules:
    - if: '$ENABLE_BUILD_CHART == "false"'
      when: never
    - if: '$CI_COMMIT_BRANCH == "stage"'
    - if: '$CI_COMMIT_BRANCH == "master"'
      when: manual

Цель: собрать и опубликовать Helm chart в HELM_REGISTRY.

deploy_stage / deploy_preprod / deploy_prod

deploy_stage:
  extends: .base_deploy
  needs:
    - job: build
      optional: true
    - job: build_chart
      optional: true
  rules:
    - if: '$CI_COMMIT_BRANCH == "stage" && $ENABLE_DEPLOY == "true"'
  tags:
    - stage

Цель: задеплоить чарт в нужный namespace через helm upgrade -i.

update_k8s_hustler_config

update_k8s_hustler_config:
  stage: state-update
  trigger:
    project: "infra/k8s-hustler"
    branch: "${K8S_HUSTLER_BRANCH}"
    strategy: depend
  rules:
    - if: '$ENABLE_STATE_UPDATE != "true"'
      when: never

Цель: автоматически обновлять state/конфигурацию инфраструктуры (отключено по умолчанию).


Как имя образа прокидывается в Helm

  1. workflow.rules задаёт STAND, RELEASE_NAME, NAMESPACE и HELM_SET_ARGS
  2. build job собирает IMAGE_NAME = ${CR}/${RELEASE_NAME}:${STAND}_${CI_COMMIT_SHORT_SHA}
  3. deploy job использует HELM_SET_ARGS для передачи образа в чарт: --set universal-chart.services.main.image.name.stage=${IMAGE_NAME}
  4. Helm применяет чарт с новым образом

Как и когда что запускается

Push в ветку stage

  1. workflow.rules срабатывает на ветку stage
  2. Устанавливаются переменные: STAND=stage, NAMESPACE=...
  3. Pipeline: build → build_chart → deploy_stage

Merge в master

  1. workflow.rules срабатывает на ветку master
  2. Устанавливаются переменные: STAND=preprod, NAMESPACE=...
  3. Pipeline: build → build_chart → deploy_preprod (manual)

Создание тега vX.Y.Z

  1. workflow.rules срабатывает на тег
  2. Устанавливаются переменные: STAND=production, NAMESPACE=...
  3. Pipeline: build → build_chart → deploy_prod (manual)

Security scan

Файл common-security-scan.yaml включает SAST, Secret-Detection, semgrep, trivy.

trivy_repo_scanning:
  stage: prebuild-secscan
  image: aquasec/trivy
  script:
    - trivy fs .
  rules:
    - if: $TRIVY_REPO_SCAN_ENABLED
  allow_failure: true

Сканы не ломают пайплайн по умолчанию (allow_failure: true). Запускаются только на merge_requests.