Merge branch 'ci/direct-commit-prod-image-bump' into 'main'
ci: bump infra image.tag прямым коммитом вместо auto-MR See merge request 2-6/2-6-4/terravault/ordinis!255
This commit is contained in:
+49
-42
@@ -79,8 +79,8 @@ default:
|
|||||||
# 3. Release commit body содержит `[skip ci]` → GitLab матчит → пропускает
|
# 3. Release commit body содержит `[skip ci]` → GitLab матчит → пропускает
|
||||||
# tag pipeline (semantic-release/gitlab plugin создаёт tag, но pipeline
|
# tag pipeline (semantic-release/gitlab plugin создаёт tag, но pipeline
|
||||||
# на этот tag-ref скипается).
|
# на этот tag-ref скипается).
|
||||||
# 4. Без tag pipeline нет images → нет propose-prod-image-bump → нет
|
# 4. Без tag pipeline нет images → нет commit-prod-image-bump → нет
|
||||||
# auto-PR в infra → manual escape потребуется.
|
# bump'а в infra → manual escape потребуется.
|
||||||
#
|
#
|
||||||
# Реальный случай: v2.16.1 (2026-05-12) — fix(release): 'remove [skip ci] из
|
# Реальный случай: v2.16.1 (2026-05-12) — fix(release): 'remove [skip ci] из
|
||||||
# semantic-release commit message'. Auto-changelog содержал эту строку →
|
# semantic-release commit message'. Auto-changelog содержал эту строку →
|
||||||
@@ -102,7 +102,7 @@ commit-msg-lint:
|
|||||||
script:
|
script:
|
||||||
# Runner не имеет outbound к dl-cdn.alpinelinux.org (Permission denied) —
|
# Runner не имеет outbound к dl-cdn.alpinelinux.org (Permission denied) —
|
||||||
# переадресуем apk на internal Nexus mirror. Это нужно во всех Alpine
|
# переадресуем 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
|
- 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
|
# apk update обязателен ПОСЛЕ repo swap: image приходит с pre-cached
|
||||||
# APKINDEX (старые package versions). Без update apk ищет старые .apk
|
# APKINDEX (старые package versions). Без update apk ищет старые .apk
|
||||||
@@ -690,25 +690,33 @@ release:
|
|||||||
# кластере. Disaster-recovery `helm upgrade ... -f values-prod.yaml`
|
# кластере. Disaster-recovery `helm upgrade ... -f values-prod.yaml`
|
||||||
# rolled back prod на несколько версий назад.
|
# rolled back prod на несколько версий назад.
|
||||||
#
|
#
|
||||||
# Решение. На каждый успешный release tag ordinis CI открывает MR в
|
# Решение. На каждый успешный release tag ordinis CI коммитит обновление
|
||||||
# ordinis-infra обновляющий `image.tag` в values-prod.yaml на новый
|
# `image.tag` в values-prod.yaml + values-staging.yaml на новый immutable
|
||||||
# immutable tag (e.g. v2-16-0-<sha>). MR — single review point: после
|
# tag (e.g. v2-16-0-<sha>) ПРЯМО в ordinis-infra main.
|
||||||
# approve и merge infra pipeline auto-катит этот же tag через deploy-prod
|
#
|
||||||
# (rules: changes: charts/**/*).
|
# Промежуточного 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
|
# Git становится единственным источником истины: HEAD ordinis-infra
|
||||||
# main всегда отражает что задеплоено на prod.
|
# main всегда отражает что задеплоено на prod.
|
||||||
#
|
#
|
||||||
# Setup ONE-TIME (см. RELEASE.md):
|
# Setup ONE-TIME (см. RELEASE.md):
|
||||||
# 1. Создать group-level Access Token в 2-6/2-6-4/terravault group:
|
# 1. Создать group-level Access Token в 2-6/2-6-4/terravault group:
|
||||||
# - role: Developer на ordinis-infra (363)
|
# - role: Maintainer на ordinis-infra (нужен push в protected main;
|
||||||
# - scopes: write_repository, api
|
# Developer-роль push в protected branch не имеет)
|
||||||
|
# - scopes: write_repository
|
||||||
# 2. Settings → CI/CD → Variables в ORDINIS project:
|
# 2. Settings → CI/CD → Variables в ORDINIS project:
|
||||||
# INFRA_PROJECT_TOKEN = <token> (Masked, Protected)
|
# INFRA_PROJECT_TOKEN = <token> (Masked, Protected)
|
||||||
|
# 3. ordinis-infra → Settings → Repository → Protected branches → main:
|
||||||
|
# "Allowed to push" должен покрывать этот токен (роль Maintainer
|
||||||
|
# покрывает по умолчанию).
|
||||||
#
|
#
|
||||||
# Trigger: только на semver tag (pipeline created semantic-release'ом).
|
# Trigger: только на semver tag (pipeline created semantic-release'ом).
|
||||||
# Allow_failure: true — manual deploy backup path остаётся если что.
|
# Allow_failure: true — manual deploy backup path остаётся если что.
|
||||||
propose-prod-image-bump:
|
commit-prod-image-bump:
|
||||||
stage: release
|
stage: release
|
||||||
image: repo.nstart.cloud/library/alpine:3.20
|
image: repo.nstart.cloud/library/alpine:3.20
|
||||||
needs:
|
needs:
|
||||||
@@ -732,17 +740,16 @@ propose-prod-image-bump:
|
|||||||
- if: '$CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+$/'
|
- if: '$CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+$/'
|
||||||
when: on_success
|
when: on_success
|
||||||
variables:
|
variables:
|
||||||
INFRA_PROJECT_ID: "368"
|
|
||||||
INFRA_REPO_PATH: "2-6/2-6-4/terravault/ordinis-infra"
|
INFRA_REPO_PATH: "2-6/2-6-4/terravault/ordinis-infra"
|
||||||
before_script:
|
before_script:
|
||||||
- |
|
- |
|
||||||
if [ -z "$INFRA_PROJECT_TOKEN" ]; then
|
if [ -z "$INFRA_PROJECT_TOKEN" ]; then
|
||||||
echo "═══════════════════════════════════════════════════════════════"
|
echo "═══════════════════════════════════════════════════════════════"
|
||||||
echo "INFRA_PROJECT_TOKEN is empty — skipping auto-PR creation."
|
echo "INFRA_PROJECT_TOKEN is empty — skipping image bump."
|
||||||
echo ""
|
echo ""
|
||||||
echo "Setup (one-time):"
|
echo "Setup (one-time):"
|
||||||
echo " 1. group 2-6/2-6-4/terravault → Settings → Access Tokens"
|
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 " 2. ordinis project (367) → Settings → CI/CD → Variables"
|
||||||
echo " add INFRA_PROJECT_TOKEN = <token>"
|
echo " add INFRA_PROJECT_TOKEN = <token>"
|
||||||
echo " flags: Masked + Protected (tag pipelines is Protected ref)"
|
echo " flags: Masked + Protected (tag pipelines is Protected ref)"
|
||||||
@@ -754,39 +761,33 @@ propose-prod-image-bump:
|
|||||||
# Internal Alpine mirror — см. commit-msg-lint выше для контекста.
|
# 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
|
- sed -i 's|https://dl-cdn.alpinelinux.org/alpine|https://repo.nstart.cloud/repository/alpine|g' /etc/apk/repositories
|
||||||
- apk update
|
- apk update
|
||||||
- apk add --no-cache git curl jq
|
- apk add --no-cache git
|
||||||
script:
|
script:
|
||||||
- |
|
- |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
IMAGE_TAG="${CI_COMMIT_REF_SLUG}-${CI_COMMIT_SHORT_SHA}"
|
IMAGE_TAG="${CI_COMMIT_REF_SLUG}-${CI_COMMIT_SHORT_SHA}"
|
||||||
VERSION="${CI_COMMIT_TAG}"
|
VERSION="${CI_COMMIT_TAG}"
|
||||||
BRANCH="auto/bump-prod-image-${VERSION}"
|
|
||||||
INFRA_API="${CI_API_V4_URL}/projects/${INFRA_PROJECT_ID}"
|
|
||||||
|
|
||||||
echo "Tag: $VERSION"
|
echo "Tag: $VERSION"
|
||||||
echo "Image tag: $IMAGE_TAG"
|
echo "Image tag: $IMAGE_TAG"
|
||||||
echo "Target branch: $BRANCH"
|
echo "Target: ordinis-infra main (direct commit)"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# 1. Clone infra (shallow для скорости).
|
# 1. Clone infra main (shallow для скорости).
|
||||||
git clone --depth=10 --branch=main \
|
git clone --depth=10 --branch=main \
|
||||||
"https://oauth2:${INFRA_PROJECT_TOKEN}@${CI_SERVER_HOST}/${INFRA_REPO_PATH}.git" infra
|
"https://oauth2:${INFRA_PROJECT_TOKEN}@${CI_SERVER_HOST}/${INFRA_REPO_PATH}.git" infra
|
||||||
cd infra
|
cd infra
|
||||||
git config user.email "ci@ordinis.local"
|
git config user.email "ci@ordinis.local"
|
||||||
git config user.name "ordinis-ci-bot"
|
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.
|
# 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
|
# values файл не задавал image.tag и default `:latest` + IfNotPresent
|
||||||
# давал stale cache на нодах. Зафиксили (infra MR !22), теперь
|
# давал stale cache на нодах. Зафиксили (infra MR !22), теперь
|
||||||
# staging тоже на immutable SHA. Чтобы оба env'а не drift'или —
|
# staging тоже на immutable SHA. Чтобы оба env'а не drift'или —
|
||||||
# auto-PR bump'ит обе values файла lock-step.
|
# bump'им обе values файла lock-step.
|
||||||
#
|
#
|
||||||
# Awk c stateful scope-tracking: ищем `image:` block (top-level, без
|
# Awk c stateful scope-tracking: ищем `image:` block (top-level, без
|
||||||
# leading spaces), затем заменяем ` tag: ...` строку. Не трогаем
|
# leading spaces), затем заменяем ` tag: ...` строку. Не трогаем
|
||||||
@@ -821,29 +822,35 @@ propose-prod-image-bump:
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 4. Commit + push branch.
|
# 4. Commit прямо в main.
|
||||||
# printf '%b' разворачивает \n в реальные newlines внутри одной shell
|
# printf '%b' разворачивает \n в реальные newlines внутри одной shell
|
||||||
# строки — избегаем multi-line YAML quoting issues (literal block scalar
|
# строки — избегаем multi-line YAML quoting issues (literal block scalar
|
||||||
# ломается на unindented continuation lines).
|
# ломается на unindented continuation lines).
|
||||||
git add charts/ordinis/values-prod.yaml charts/ordinis/values-staging.yaml
|
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}")
|
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 commit -m "$COMMIT_MSG"
|
||||||
git push origin "$BRANCH"
|
|
||||||
|
|
||||||
# 5. Create MR через GitLab API.
|
# 5. Push в main. До 5 попыток с rebase — если main подвинулся
|
||||||
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)")
|
# между 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 \
|
echo ""
|
||||||
--request POST \
|
echo "Деплой: новый infra main pipeline auto-катит staging,"
|
||||||
--header "PRIVATE-TOKEN: ${INFRA_PROJECT_TOKEN}" \
|
echo "deploy-prod остаётся manual-gated."
|
||||||
--data-urlencode "source_branch=${BRANCH}" \
|
# Не блокирует tag pipeline если bump не прошёл (token отсутствует,
|
||||||
--data-urlencode "target_branch=main" \
|
# transient git error и т.п.). Manual bump в infra остаётся доступен.
|
||||||
--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 остаётся всегда доступен.
|
|
||||||
allow_failure: true
|
allow_failure: true
|
||||||
|
|||||||
+21
-11
@@ -26,20 +26,27 @@ human кликает Deploy.
|
|||||||
|
|
||||||
После этого release job в `.gitlab-ci.yml` будет работать.
|
После этого release job в `.gitlab-ci.yml` будет работать.
|
||||||
|
|
||||||
### Auto-PR в ordinis-infra (опционально)
|
### Auto-bump image.tag в ordinis-infra (опционально)
|
||||||
|
|
||||||
`propose-prod-image-bump` job открывает MR в `ordinis-infra` после каждого
|
`commit-prod-image-bump` job коммитит обновление `image.tag` в
|
||||||
release tag — bump'ит `image.tag` в `values-prod.yaml` на свежий immutable
|
`values-prod.yaml` + `values-staging.yaml` на свежий immutable SHA tag
|
||||||
SHA tag (e.g. `v2-16-0-abc12345`). Чинит historical bug когда manual deploys
|
(e.g. `v2-16-0-abc12345`) **прямо в `ordinis-infra` main** после каждого
|
||||||
через infra repo получали `:latest` (stale cache → split-brain pods).
|
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:
|
Setup:
|
||||||
|
|
||||||
3. **Create Group Access Token** в `2-6/2-6-4/terravault`:
|
3. **Create Group Access Token** в `2-6/2-6-4/terravault`:
|
||||||
- `Group → Settings → Access Tokens → Add new token`
|
- `Group → Settings → Access Tokens → Add new token`
|
||||||
- Name: `ordinis-ci-infra-bump`
|
- Name: `ordinis-ci-infra-bump`
|
||||||
- Role: `Developer`
|
- Role: `Maintainer` (нужен push в protected `main`; Developer-роль
|
||||||
- Scopes: `write_repository`, `api`
|
push в protected branch не имеет)
|
||||||
|
- Scopes: `write_repository`
|
||||||
- Expiry: 1 год
|
- Expiry: 1 год
|
||||||
|
|
||||||
4. **Add CI variable в ordinis project (367)**:
|
4. **Add CI variable в ordinis project (367)**:
|
||||||
@@ -47,9 +54,12 @@ Setup:
|
|||||||
- Value: (token из шага 3)
|
- Value: (token из шага 3)
|
||||||
- Flags: `Masked` ✅, `Protected` ✅
|
- Flags: `Masked` ✅, `Protected` ✅
|
||||||
|
|
||||||
|
5. **ordinis-infra → Protected branches → `main`**: убедись что `Allowed
|
||||||
|
to push` покрывает этот токен (роль Maintainer покрывает по умолчанию).
|
||||||
|
|
||||||
Без этой переменной job exits early (`allow_failure: true`), tag pipeline
|
Без этой переменной job exits early (`allow_failure: true`), tag pipeline
|
||||||
не блокируется. Manual MR в `ordinis-infra` остаётся всегда доступен как
|
не блокируется. Ручной bump `image.tag` в `ordinis-infra` остаётся доступен
|
||||||
fallback.
|
как fallback.
|
||||||
|
|
||||||
## Commit conventions
|
## Commit conventions
|
||||||
|
|
||||||
@@ -106,8 +116,8 @@ exit 0 → нет нового tag → нет loop'а.
|
|||||||
|
|
||||||
> **Historical note:** до MR !154 (2026-05-12) сюда добавлялось `[skip ci]`.
|
> **Historical note:** до MR !154 (2026-05-12) сюда добавлялось `[skip ci]`.
|
||||||
> Это работало для main pipeline'а, НО skip'ало и tag pipeline на том же SHA
|
> Это работало для main pipeline'а, НО skip'ало и tag pipeline на том же SHA
|
||||||
> → images с tag'ом `v*-*-*-<sha>` не собирались → `propose-prod-image-bump`
|
> → images с tag'ом `v*-*-*-<sha>` не собирались → `commit-prod-image-bump`
|
||||||
> auto-PR не запускался → manual ручной MR в infra-repo. Удалено.
|
> не запускался → manual ручной bump в infra-repo. Удалено.
|
||||||
|
|
||||||
## ⚠ Skip-ci marker trap
|
## ⚠ Skip-ci marker trap
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user