diff --git a/Jenkinsfile b/Jenkinsfile index 62609b6..0f8f319 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,20 +1,22 @@ -// Wrapped — CI: multi-arch push (Harbor + Docker Hub) + Helm deploy +// Wrapped — CI: multi-arch push (Harbor + Docker Hub), затем асинхронный Deploy // -// Триггер: push в main (webhook Gitea → Jenkins Multibranch). -// Версия образа = файл VERSION из коммита (без авто-bump в CI). -// Bump: make bump-patch / make push локально — что запушили, то и уйдёт в релиз. +// Ресурсы k3s: Builder и Deploy не должны держать два DinD-пода сразу. +// 1) стадии сборки на agent label 'docker' (DinD) +// 2) при успехе — trigger Deploy с wait:false и agent none → Builder-под умирает +// 3) Deploy стартует отдельно уже без Builder // -// Credentials (Manage Jenkins → Credentials → Global): +// Версия образа = VERSION из коммита. Bump: make bump-patch / make push. +// +// Credentials (Global): // harbor-devops-tools-push-pull-access — Harbor devops-tools (robot) // docker-hub — Docker Hub (inecs) // ssh-gitea-key — SCM checkout Gitea // k3s-kubeconfig — в job Deploy (Jenkinsfile.deploy) // -// Теги образа: :, :, :latest -// Deploy: devops-tools/wrapped/wrapped-deploy/main +// Deploy job: devops-tools/wrapped/wrapped-deploy/main pipeline { - agent { label 'docker' } + agent none options { buildDiscarder(logRotator(numToKeepStr: '20')) @@ -34,104 +36,123 @@ pipeline { } stages { - stage('Checkout') { - steps { - checkout scm - } - } - - stage('Version') { + stage('Build image') { when { anyOf { branch 'main' branch 'master' } } - steps { - container('docker') { - script { - env.IMAGE_VERSION = sh( - script: "tr -d '[:space:]' < VERSION", - returnStdout: true - ).trim() - if (!env.IMAGE_VERSION) { - error('VERSION file is empty') + agent { label 'docker' } + stages { + stage('Checkout') { + steps { + checkout scm + } + } + + stage('Version') { + steps { + container('docker') { + script { + env.IMAGE_VERSION = sh( + script: "tr -d '[:space:]' < VERSION", + returnStdout: true + ).trim() + if (!env.IMAGE_VERSION) { + error('VERSION file is empty') + } + echo "IMAGE_VERSION from VERSION → ${env.IMAGE_VERSION}" + } } - echo "IMAGE_VERSION from VERSION → ${env.IMAGE_VERSION}" - } - } - } - } - - stage('Build & push') { - when { - anyOf { - branch 'main' - branch 'master' - } - } - steps { - withCredentials([ - usernamePassword( - credentialsId: 'harbor-devops-tools-push-pull-access', - usernameVariable: 'HARBOR_USER', - passwordVariable: 'HARBOR_PASS' - ), - usernamePassword( - credentialsId: 'docker-hub', - usernameVariable: 'DOCKERHUB_USER', - passwordVariable: 'DOCKERHUB_PASS' - ) - ]) { - container('docker') { - sh ''' - set -eux - - test -n "${IMAGE_VERSION}" - echo "Building tags: ${IMAGE_VERSION}, ${RELEASE_TAG}, latest" - - echo "$HARBOR_PASS" | docker login "$HARBOR_REGISTRY" -u "$HARBOR_USER" --password-stdin - echo "$DOCKERHUB_PASS" | docker login -u "$DOCKERHUB_USER" --password-stdin - - docker buildx rm "$BUILDX_BUILDER" 2>/dev/null || true - docker buildx create --name "$BUILDX_BUILDER" --driver docker-container --use - docker buildx inspect --bootstrap >/dev/null - - docker buildx build \ - --platform linux/amd64 \ - --provenance=false --sbom=false --push \ - -t "${HARBOR_IMAGE}:${RELEASE_TAG}-amd64" \ - -t "${DOCKERHUB_IMAGE}:${RELEASE_TAG}-amd64" \ - -f Dockerfile . - - docker buildx build \ - --platform linux/arm64 \ - --provenance=false --sbom=false --push \ - -t "${HARBOR_IMAGE}:${RELEASE_TAG}-arm64" \ - -t "${DOCKERHUB_IMAGE}:${RELEASE_TAG}-arm64" \ - -f Dockerfile . - - for IMAGE in "$HARBOR_IMAGE" "$DOCKERHUB_IMAGE"; do - docker buildx imagetools create \ - -t "${IMAGE}:${RELEASE_TAG}" \ - -t "${IMAGE}:${IMAGE_VERSION}" \ - -t "${IMAGE}:latest" \ - "${IMAGE}:${RELEASE_TAG}-amd64" \ - "${IMAGE}:${RELEASE_TAG}-arm64" - done - - echo "--- Harbor ---" - docker buildx imagetools inspect "${HARBOR_IMAGE}:${IMAGE_VERSION}" | sed -n '1,40p' - echo "--- Docker Hub ---" - docker buildx imagetools inspect "${DOCKERHUB_IMAGE}:${IMAGE_VERSION}" | sed -n '1,40p' - - docker buildx rm "$BUILDX_BUILDER" || true - ''' } } + + stage('Build & push') { + steps { + withCredentials([ + usernamePassword( + credentialsId: 'harbor-devops-tools-push-pull-access', + usernameVariable: 'HARBOR_USER', + passwordVariable: 'HARBOR_PASS' + ), + usernamePassword( + credentialsId: 'docker-hub', + usernameVariable: 'DOCKERHUB_USER', + passwordVariable: 'DOCKERHUB_PASS' + ) + ]) { + container('docker') { + sh ''' + set -eux + + test -n "${IMAGE_VERSION}" + echo "Building tags: ${IMAGE_VERSION}, ${RELEASE_TAG}, latest" + + echo "$HARBOR_PASS" | docker login "$HARBOR_REGISTRY" -u "$HARBOR_USER" --password-stdin + echo "$DOCKERHUB_PASS" | docker login -u "$DOCKERHUB_USER" --password-stdin + + docker buildx rm "$BUILDX_BUILDER" 2>/dev/null || true + docker buildx create --name "$BUILDX_BUILDER" --driver docker-container --use + docker buildx inspect --bootstrap >/dev/null + + docker buildx build \ + --platform linux/amd64 \ + --provenance=false --sbom=false --push \ + -t "${HARBOR_IMAGE}:${RELEASE_TAG}-amd64" \ + -t "${DOCKERHUB_IMAGE}:${RELEASE_TAG}-amd64" \ + -f Dockerfile . + + docker buildx build \ + --platform linux/arm64 \ + --provenance=false --sbom=false --push \ + -t "${HARBOR_IMAGE}:${RELEASE_TAG}-arm64" \ + -t "${DOCKERHUB_IMAGE}:${RELEASE_TAG}-arm64" \ + -f Dockerfile . + + for IMAGE in "$HARBOR_IMAGE" "$DOCKERHUB_IMAGE"; do + docker buildx imagetools create \ + -t "${IMAGE}:${RELEASE_TAG}" \ + -t "${IMAGE}:${IMAGE_VERSION}" \ + -t "${IMAGE}:latest" \ + "${IMAGE}:${RELEASE_TAG}-amd64" \ + "${IMAGE}:${RELEASE_TAG}-arm64" + done + + echo "--- Harbor ---" + docker buildx imagetools inspect "${HARBOR_IMAGE}:${IMAGE_VERSION}" | sed -n '1,40p' + echo "--- Docker Hub ---" + docker buildx imagetools inspect "${DOCKERHUB_IMAGE}:${IMAGE_VERSION}" | sed -n '1,40p' + + docker buildx rm "$BUILDX_BUILDER" || true + ''' + } + } + } + } + } + post { + always { + script { + try { + container('docker') { + sh 'docker buildx rm "$BUILDX_BUILDER" 2>/dev/null || true' + } + } catch (Ignored) { + // pod may already be gone + } + } + } + success { + echo "✓ Build OK: ${DOCKERHUB_IMAGE}:{${RELEASE_TAG},${IMAGE_VERSION},latest}" + } + failure { + echo "✗ Build/push не удались — Deploy не запускается" + } } } + // Без DinD: только очередь Deploy, Builder-под уже освобождён stage('Trigger deploy') { when { anyOf { @@ -139,19 +160,21 @@ pipeline { branch 'master' } } + agent none steps { script { - echo "Triggering ${DEPLOY_JOB} → ${DOCKERHUB_IMAGE}:${IMAGE_VERSION}" + echo "Build finished — triggering ${DEPLOY_JOB} → ${DOCKERHUB_IMAGE}:${IMAGE_VERSION} (wait: false)" build( job: env.DEPLOY_JOB, - wait: true, - propagate: true, + wait: false, + propagate: false, parameters: [ string(name: 'IMAGE_REPOSITORY', value: env.DOCKERHUB_IMAGE), string(name: 'IMAGE_TAG', value: env.IMAGE_VERSION), string(name: 'IMAGE_PULL_POLICY', value: 'Always'), ] ) + echo "Deploy queued; Builder pipeline exits and releases cluster resources." } } } @@ -159,24 +182,12 @@ pipeline { post { success { - echo "✓ Version: ${IMAGE_VERSION}" - echo "✓ Harbor: ${HARBOR_IMAGE}:{${RELEASE_TAG},${IMAGE_VERSION},latest}" - echo "✓ Docker Hub: ${DOCKERHUB_IMAGE}:{${RELEASE_TAG},${IMAGE_VERSION},latest}" - echo "✓ Deploy: ${DEPLOY_JOB} (${DOCKERHUB_IMAGE}:${IMAGE_VERSION})" + echo "✓ Version: ${IMAGE_VERSION}" + echo "✓ Image: ${DOCKERHUB_IMAGE}:{${RELEASE_TAG},${IMAGE_VERSION},latest}" + echo "✓ Deploy: ${DEPLOY_JOB} triggered (async)" } failure { - echo "✗ Version / build / push / deploy — см. лог стадий" - } - always { - script { - try { - container('docker') { - sh 'docker buildx rm "$BUILDX_BUILDER" 2>/dev/null || true' - } - } catch (Ignored) { - // agent may already be gone - } - } + echo "✗ Сборка не удалась — см. лог Build image" } } } diff --git a/README.md b/README.md index 03ce151..e2d79dd 100644 --- a/README.md +++ b/README.md @@ -492,7 +492,7 @@ kubectl -n wrapped rollout status deployment/wrapped Credential: `k3s-kubeconfig` (Secret file) — kubeconfig к K3S. -Рекомендуемая связка job’ов уже в [`Jenkinsfile`](Jenkinsfile): после **Version** (чтение `VERSION`) + **Build & push** стадия **Trigger deploy** вызывает `devops-tools/wrapped/wrapped-deploy/main` с тем же SemVer (`wait: true`). +Рекомендуемая связка job’ов уже в [`Jenkinsfile`](Jenkinsfile): после успешного **Build image** (DinD) стадия **Trigger deploy** с `wait: false` и `agent none` — под Builder освобождается, затем отдельно стартует Deploy. Так на кластере не нужны два DinD одновременно. Ручной запуск Deploy: **Build with Parameters** (пустой `IMAGE_TAG` → `cat VERSION`).