From b76997e17094d950d43cc7205fe1352f68fe48dc Mon Sep 17 00:00:00 2001 From: "Zimin A.N." Date: Thu, 21 May 2026 19:25:58 +0300 Subject: [PATCH] =?UTF-8?q?ci:=20bump=20infra=20image.tag=20=D0=BF=D1=80?= =?UTF-8?q?=D1=8F=D0=BC=D1=8B=D0=BC=20=D0=BA=D0=BE=D0=BC=D0=BC=D0=B8=D1=82?= =?UTF-8?q?=D0=BE=D0=BC=20=D0=B2=20main=20=D0=B2=D0=BC=D0=B5=D1=81=D1=82?= =?UTF-8?q?=D0=BE=20auto-MR?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit propose-prod-image-bump открывал MR в ordinis-infra на каждый release. MR никто не мерджил — накопилось 5+ открытых, image.tag в values отставал на несколько релизов, и infra deploy-prod (helm upgrade -f values-prod.yaml) откатывал prod на старый образ. Переименован в commit-prod-image-bump: коммитит bump image.tag в values-prod.yaml + values-staging.yaml напрямую в infra main с rebase-retry. Убран промежуточный MR и 405-квирк при merge вечно-manual инфра-пайплайна. Токену нужна роль Maintainer для push в protected main (было Developer); api-scope больше не нужен. --- .gitlab-ci.yml | 91 +++++++++++++++++++++++++++----------------------- RELEASE.md | 32 ++++++++++++------ 2 files changed, 70 insertions(+), 53 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 48f40ab..2b906b0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -79,8 +79,8 @@ default: # 3. Release commit body содержит `[skip ci]` → GitLab матчит → пропускает # tag pipeline (semantic-release/gitlab plugin создаёт tag, но pipeline # на этот tag-ref скипается). -# 4. Без tag pipeline нет images → нет propose-prod-image-bump → нет -# auto-PR в infra → manual escape потребуется. +# 4. Без tag pipeline нет images → нет commit-prod-image-bump → нет +# bump'а в infra → manual escape потребуется. # # Реальный случай: v2.16.1 (2026-05-12) — fix(release): 'remove [skip ci] из # semantic-release commit message'. Auto-changelog содержал эту строку → @@ -102,7 +102,7 @@ commit-msg-lint: script: # Runner не имеет outbound к dl-cdn.alpinelinux.org (Permission denied) — # переадресуем apk на internal Nexus mirror. Это нужно во всех Alpine - # jobs (commit-msg-lint, build-docs ниже, propose-prod-image-bump). + # jobs (commit-msg-lint, build-docs ниже, commit-prod-image-bump). - sed -i 's|https://dl-cdn.alpinelinux.org/alpine|https://repo.nstart.cloud/repository/alpine|g' /etc/apk/repositories # apk update обязателен ПОСЛЕ repo swap: image приходит с pre-cached # APKINDEX (старые package versions). Без update apk ищет старые .apk @@ -690,25 +690,33 @@ release: # кластере. Disaster-recovery `helm upgrade ... -f values-prod.yaml` # rolled back prod на несколько версий назад. # -# Решение. На каждый успешный release tag ordinis CI открывает MR в -# ordinis-infra обновляющий `image.tag` в values-prod.yaml на новый -# immutable tag (e.g. v2-16-0-). MR — single review point: после -# approve и merge infra pipeline auto-катит этот же tag через deploy-prod -# (rules: changes: charts/**/*). +# Решение. На каждый успешный release tag ordinis CI коммитит обновление +# `image.tag` в values-prod.yaml + values-staging.yaml на новый immutable +# tag (e.g. v2-16-0-) ПРЯМО в ordinis-infra main. +# +# Промежуточного MR нет. Раньше bump шёл через auto-MR — но его никто не +# мерджил: накопилось 5+ открытых MR, values отставали на несколько +# релизов, и infra `deploy-prod` (helm upgrade -f values-prod.yaml) +# откатывал prod на старый образ. Direct commit убирает ручной шаг — +# нет MR, нет 405-квирка при merge вечно-manual инфра-пайплайна. # # Git становится единственным источником истины: HEAD ordinis-infra # main всегда отражает что задеплоено на prod. # # Setup ONE-TIME (см. RELEASE.md): # 1. Создать group-level Access Token в 2-6/2-6-4/terravault group: -# - role: Developer на ordinis-infra (363) -# - scopes: write_repository, api +# - role: Maintainer на ordinis-infra (нужен push в protected main; +# Developer-роль push в protected branch не имеет) +# - scopes: write_repository # 2. Settings → CI/CD → Variables в ORDINIS project: # INFRA_PROJECT_TOKEN = (Masked, Protected) +# 3. ordinis-infra → Settings → Repository → Protected branches → main: +# "Allowed to push" должен покрывать этот токен (роль Maintainer +# покрывает по умолчанию). # # Trigger: только на semver tag (pipeline created semantic-release'ом). # Allow_failure: true — manual deploy backup path остаётся если что. -propose-prod-image-bump: +commit-prod-image-bump: stage: release image: repo.nstart.cloud/library/alpine:3.20 needs: @@ -732,17 +740,16 @@ propose-prod-image-bump: - if: '$CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+$/' when: on_success variables: - INFRA_PROJECT_ID: "368" INFRA_REPO_PATH: "2-6/2-6-4/terravault/ordinis-infra" before_script: - | if [ -z "$INFRA_PROJECT_TOKEN" ]; then echo "═══════════════════════════════════════════════════════════════" - echo "INFRA_PROJECT_TOKEN is empty — skipping auto-PR creation." + echo "INFRA_PROJECT_TOKEN is empty — skipping image bump." echo "" echo "Setup (one-time):" echo " 1. group 2-6/2-6-4/terravault → Settings → Access Tokens" - echo " scopes: write_repository + api, role: Developer" + echo " scope: write_repository, role: Maintainer (push в protected main)" echo " 2. ordinis project (367) → Settings → CI/CD → Variables" echo " add INFRA_PROJECT_TOKEN = " echo " flags: Masked + Protected (tag pipelines is Protected ref)" @@ -754,39 +761,33 @@ propose-prod-image-bump: # Internal Alpine mirror — см. commit-msg-lint выше для контекста. - sed -i 's|https://dl-cdn.alpinelinux.org/alpine|https://repo.nstart.cloud/repository/alpine|g' /etc/apk/repositories - apk update - - apk add --no-cache git curl jq + - apk add --no-cache git script: - | set -euo pipefail IMAGE_TAG="${CI_COMMIT_REF_SLUG}-${CI_COMMIT_SHORT_SHA}" VERSION="${CI_COMMIT_TAG}" - BRANCH="auto/bump-prod-image-${VERSION}" - INFRA_API="${CI_API_V4_URL}/projects/${INFRA_PROJECT_ID}" echo "Tag: $VERSION" echo "Image tag: $IMAGE_TAG" - echo "Target branch: $BRANCH" + echo "Target: ordinis-infra main (direct commit)" echo "" - # 1. Clone infra (shallow для скорости). + # 1. Clone infra main (shallow для скорости). git clone --depth=10 --branch=main \ "https://oauth2:${INFRA_PROJECT_TOKEN}@${CI_SERVER_HOST}/${INFRA_REPO_PATH}.git" infra cd infra git config user.email "ci@ordinis.local" git config user.name "ordinis-ci-bot" - # Branch может остаться от прошлой неудачной попытки — clean delete. - git push origin --delete "$BRANCH" 2>/dev/null || true - git checkout -b "$BRANCH" - # 2. Patch values-prod.yaml + values-staging.yaml — заменяем image.tag. # - # Patching ОБА файлов в одном MR. Зачем: до 2026-05-13 staging + # Patching ОБА файла. Зачем: до 2026-05-13 staging # values файл не задавал image.tag и default `:latest` + IfNotPresent # давал stale cache на нодах. Зафиксили (infra MR !22), теперь # staging тоже на immutable SHA. Чтобы оба env'а не drift'или — - # auto-PR bump'ит обе values файла lock-step. + # bump'им обе values файла lock-step. # # Awk c stateful scope-tracking: ищем `image:` block (top-level, без # leading spaces), затем заменяем ` tag: ...` строку. Не трогаем @@ -821,29 +822,35 @@ propose-prod-image-bump: exit 0 fi - # 4. Commit + push branch. + # 4. Commit прямо в main. # printf '%b' разворачивает \n в реальные newlines внутри одной shell # строки — избегаем multi-line YAML quoting issues (literal block scalar # ломается на unindented continuation lines). git add charts/ordinis/values-prod.yaml charts/ordinis/values-staging.yaml COMMIT_MSG=$(printf '%b' "chore(release): bump prod+staging image к ${IMAGE_TAG}\n\nAuto-generated ordinis-CI release pipeline. Patches values-prod.yaml AND values-staging.yaml в lock-step.\n\nSource: ${CI_PROJECT_URL}/-/commit/${CI_COMMIT_SHA}\nPipeline: ${CI_PIPELINE_URL}\nTag: ${VERSION}") git commit -m "$COMMIT_MSG" - git push origin "$BRANCH" - # 5. Create MR через GitLab API. - MR_DESC=$(printf '%b' "Auto-generated ordinis CI release pipeline после semantic-release ${VERSION}.\n\n**Что меняется:** \`image.tag\` в \`charts/ordinis/values-prod.yaml\` И \`charts/ordinis/values-staging.yaml\` → \`${IMAGE_TAG}\` (immutable SHA tag, гарантирует что ноды pull'нут именно этот digest, и оба env'а на одном release).\n\n**Source:**\n- Ordinis commit: ${CI_PROJECT_URL}/-/commit/${CI_COMMIT_SHA}\n- Tag pipeline: ${CI_PIPELINE_URL}\n\n**Deploy после merge:** infra-pipeline auto-катит helm upgrade в \`ordinis-staging\` (auto) и \`ordinis-prod\` (manual-gated \`deploy-prod\` job).\n\n**Verify checklist:**\n- [ ] CI 'helm-lint' pipeline зелёный (validate charts)\n- [ ] После merge play 'deploy-prod' job (staging авто-катится)\n- [ ] \`curl https://ordinis.k8s.264.nstart.cloud/api/v1/version\` (prod) показывает \`commit: ${CI_COMMIT_SHORT_SHA}\` и \`tag: ${VERSION}\`\n- [ ] \`curl https://ordinis.k8s.265.nstart.cloud/api/v1/version\` (staging) тот же commit/tag\n- [ ] Pods Running на обоих env (no split-brain)") + # 5. Push в main. До 5 попыток с rebase — если main подвинулся + # между clone и push (параллельный release, ручной коммит). + attempt=0 + until [ "$attempt" -ge 5 ]; do + if git push origin HEAD:main; then + echo "✓ Bump запушен в ordinis-infra main." + break + fi + attempt=$((attempt + 1)) + echo "Push отклонён (main подвинулся?) — rebase, попытка ${attempt}/5." + git fetch origin main + git rebase origin/main + done + if [ "$attempt" -ge 5 ]; then + echo "ERROR: push в main не прошёл после 5 попыток." >&2 + exit 1 + fi - curl -sS --fail \ - --request POST \ - --header "PRIVATE-TOKEN: ${INFRA_PROJECT_TOKEN}" \ - --data-urlencode "source_branch=${BRANCH}" \ - --data-urlencode "target_branch=main" \ - --data-urlencode "title=chore(release): bump prod image к ${VERSION}" \ - --data-urlencode "description=${MR_DESC}" \ - --data-urlencode "remove_source_branch=true" \ - --data-urlencode "labels=release,auto-generated" \ - "${INFRA_API}/merge_requests" \ - | jq -r '"MR создан: " + .web_url' - # Не блокирует tag pipeline если auto-PR не получился (token отсутствует, - # transient API error и т.п.). Manual MR в infra остаётся всегда доступен. + echo "" + echo "Деплой: новый infra main pipeline auto-катит staging," + echo "deploy-prod остаётся manual-gated." + # Не блокирует tag pipeline если bump не прошёл (token отсутствует, + # transient git error и т.п.). Manual bump в infra остаётся доступен. allow_failure: true diff --git a/RELEASE.md b/RELEASE.md index 8285e8b..3fe500c 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -26,20 +26,27 @@ human кликает Deploy. После этого release job в `.gitlab-ci.yml` будет работать. -### Auto-PR в ordinis-infra (опционально) +### Auto-bump image.tag в ordinis-infra (опционально) -`propose-prod-image-bump` job открывает MR в `ordinis-infra` после каждого -release tag — bump'ит `image.tag` в `values-prod.yaml` на свежий immutable -SHA tag (e.g. `v2-16-0-abc12345`). Чинит historical bug когда manual deploys -через infra repo получали `:latest` (stale cache → split-brain pods). +`commit-prod-image-bump` job коммитит обновление `image.tag` в +`values-prod.yaml` + `values-staging.yaml` на свежий immutable SHA tag +(e.g. `v2-16-0-abc12345`) **прямо в `ordinis-infra` main** после каждого +release tag. Чинит historical bug когда manual deploys через infra repo +получали `:latest` (stale cache → split-brain pods). + +> Раньше bump шёл через auto-MR — но MR никто не мерджил, values отставали +> на несколько релизов, и infra `deploy-prod` откатывал prod на старый +> образ. Перешли на direct commit (нет ручного шага, нет 405-квирка при +> merge вечно-manual инфра-пайплайна). Setup: 3. **Create Group Access Token** в `2-6/2-6-4/terravault`: - `Group → Settings → Access Tokens → Add new token` - Name: `ordinis-ci-infra-bump` - - Role: `Developer` - - Scopes: `write_repository`, `api` + - Role: `Maintainer` (нужен push в protected `main`; Developer-роль + push в protected branch не имеет) + - Scopes: `write_repository` - Expiry: 1 год 4. **Add CI variable в ordinis project (367)**: @@ -47,9 +54,12 @@ Setup: - Value: (token из шага 3) - Flags: `Masked` ✅, `Protected` ✅ +5. **ordinis-infra → Protected branches → `main`**: убедись что `Allowed + to push` покрывает этот токен (роль Maintainer покрывает по умолчанию). + Без этой переменной job exits early (`allow_failure: true`), tag pipeline -не блокируется. Manual MR в `ordinis-infra` остаётся всегда доступен как -fallback. +не блокируется. Ручной bump `image.tag` в `ordinis-infra` остаётся доступен +как fallback. ## Commit conventions @@ -106,8 +116,8 @@ exit 0 → нет нового tag → нет loop'а. > **Historical note:** до MR !154 (2026-05-12) сюда добавлялось `[skip ci]`. > Это работало для main pipeline'а, НО skip'ало и tag pipeline на том же SHA -> → images с tag'ом `v*-*-*-` не собирались → `propose-prod-image-bump` -> auto-PR не запускался → manual ручной MR в infra-repo. Удалено. +> → images с tag'ом `v*-*-*-` не собирались → `commit-prod-image-bump` +> не запускался → manual ручной bump в infra-repo. Удалено. ## ⚠ Skip-ci marker trap