/* ! Jenkins Watchdog Pipeline Этот Pipeline реализует сборку на Kubernetes worker-ноде с контролем и завершением со стороны Jenkins master-ноды. Master отслеживает статус worker'а в реальном времени и завершает процесс в случае ошибки, таймаута или успешного завершения. */ // Статусы воркера. Необходимо мастеру для определения состояния джоба def workerFailed = false def workerSuccess = false def DEBUGGING_MODE = false // Мапа для запуска мастера и воркера параллельно def parallelSteps = [:] // Блок констант // Мапа, в которой будут переменные из vars/build.yaml buildVars = [:] buildVars.system = [:] buildVars.kubeconfig = "config.kube" // Подгрузка либы. Версия в свойствах папки Jenkins library "jlib" cmdbVars = constants.cmdbVars TimeoutTime = [ time: binding.hasVariable('timeoutTime') ? timeoutTime : (cmdbVars?.system?.timeoutTime ?: 20), unit: binding.hasVariable('timeoutUnit') ? timeoutUnit : (cmdbVars?.system?.timeoutUnit ?: "MINUTES") ] parallelSteps["worker"] = { node("docker") { timestamps() { ansiColor("xterm"){ dir (env.BUILD_NUMBER) { try { stage ("Prepare environments") { // Выкачивание проекта из Git checkout scm // Определение типа джобы: // - сборка и пуш в кубер: DEV / PR // - проверка образа (из DEV) и пуш в кубер: TEST / PROD logger.info("Targeting stand for deploy") // Если PR - в DEV if (env.CHANGE_ID) buildVars.gitBranch = "dev" else buildVars.gitBranch = env.BRANCH_NAME // Проверка на нужные бренчи if (!(buildVars.gitBranch in ["prod", "test", "dev"])) { logger.err("Ветка в Git не соответствует 'prod', 'test' или 'dev'", true) } envConfig = "build/vars/build.yaml" support.getVarsAll(envConfig, buildVars) logger.info("Get kubeconfig") try { withCredentials([vaultString(credentialsId: "${buildVars.vaultPath}", variable: "pem")]) { runsh """ set +x echo "${pem}" | base64 -d > ${ buildVars.kubeconfig } set -x """, true } logger.info("kubeconfig getting successfully") } catch (Exception kubeconfigGetError) { logger.err("Не удалось получить кубконфиг для подключению к Kubernetes-кластеру. Ошибка: ${kubeconfigGetError.message}", true) } logger.info("Getting version...") try { version = runsh buildVars.getCommandVersion logger.info("Version app: ${ version }") } catch (Exception versionGetError) { logger.err("Не удалось получить версию приложения. Ошибка: ${versionGetError.message}", true) } } // Получение токена для авторизации в GITEA withCredentials([vaultString(credentialsId: "gitea.services.ngcloud.ru-deck-token", variable: "token")]) { GITEA_AUTH_TOKEN = token } // Сборка Image (Только DEV) if (buildVars.gitBranch == "dev") { if (buildVars.needBuildLib.toBoolean()) { stage ("Check library") { headers = [ [ name: "Authorization", value: "Bearer ${ GITEA_AUTH_TOKEN }" ] ] // Проверка что либа существует try { retry(3) { httpcall.get( url="https://gitea.services.ngcloud.ru/api/v1/packages/deck/npm/%40nubes%2F${ buildVars.app.name }/${ version }", headerList=headers) } needToBuildLib = false } catch (Exception errGetPackage) { logger.info("Package does not exist. It's OK") needToBuildLib = true } } stage ("Build library") { if (needToBuildLib.toBoolean()) { retry(3) { try { docker.build("${ buildVars.registryImageName }:${ version }", "--no-cache --build-arg GITEA_AUTH_TOKEN=${ GITEA_AUTH_TOKEN } -f ./build/Dockerfile_build_lib .") } catch (Exception errorDocker) { logger.err("Не удалось собрать библиотеку. Ошибка: ${ errorDocker.message }", true) } } } } } stage ("Build image") { catchError(buildResult: 'FAILURE', stageResult: 'FAILURE') { logger.info("Start building image ${ buildVars.registryContainerName }:${ version }") retry(3) { try { app = docker.build("${ buildVars.registryImageName }:${ version }", "--no-cache --build-arg GITEA_AUTH_TOKEN=${ GITEA_AUTH_TOKEN } -f ./build/Dockerfile .") } catch (Exception errorDocker) { logger.err("Не удалось собрать основной образ. Ошибка: ${ errorDocker.message }", true) } } } logger.info("Start uploading image ${ buildVars.registryImageName }:${ version }") docker.withRegistry("https://${ buildVars.registryUrl }/${ buildVars.repoPath }", "registry-jenkins-cred") { // retry на случай если 500 случайно отдаст registry retry(3) { try { app.push("${ version }") } catch (Exception errorDocker) { logger.err("Не удалось запушить образ в Registry. Ошибка: ${ errorDocker.message }", true) } } } } } // Проверка Image в Registry (Всё кроме DEV) if (buildVars.gitBranch != "dev") { stage("Check if image exist") { docker.withRegistry("https://${ buildVars.registryUrl }/${ buildVars.repoPath }", "registry-jenkins-cred") { def img = docker.image("${ buildVars.registryImageName }:${ version }") // TODO переписать либу docker под себя // try/catch не поможет, ошибка выведет всегда 'script returned exit code 1' // retry на случай если 500 случайно отдаст registry retry(3) { try { img.pull() } catch (Exception errorDocker) { logger.err("Не удалось получить образ из Registry. Ошибка: ${ errorDocker.message }", true) } } } } } // Деплой в Kubernetes (все стенды) stage("Deploy to K8s test") { logger.info("Check exist pods in k8s...") retry(3) { runsh "kubectl --kubeconfig=${ buildVars.kubeconfig } --namespace ${ buildVars.namespace } get po" } logger.info("Prepare list of args on helm...") commandArgs = [ "--set deployment.image.repository=${ buildVars.registryUrl }/${ buildVars.registryImageName }", "--set deployment.readinessProbe.httpGet.path=${ buildVars.app.health }", "--set deployment.containerPort=${ buildVars.app.port }", "--set deployment.resources.requests.cpu=${ buildVars.app.cpu }m", "--set deployment.resources.requests.memory=${ buildVars.app.mem }Mi", "--set deployment.resources.limits.cpu=${ buildVars.app.cpu }m", "--set deployment.resources.limits.memory=${ buildVars.app.mem }Mi", "--set version=${ version }", "--set deployment.image.tag=${ version }", "--set stand=${ buildVars.gitBranch }" ] // Если включен Vault if (buildVars.needStoreVaultSecret.toBoolean()) { commandArgs.add("--set vault.secretStore=${ buildVars.namespace }") commandArgs.add("--set serviceAccountRole=${ buildVars.namespace }-sa") commandArgs.add("--set vault.secrets=\"{${ buildVars.storedSecretsList.join(',') }}\"") commandArgs.add("--set vault.enable=true") } else { commandArgs.add("--set vault.enable=false") } installArgs = [ "--kubeconfig ${ buildVars.kubeconfig }", "--install", "--namespace ${ buildVars.namespace }", ] cmdTemplate = "helm template ${commandArgs.join(' ')} ./build/helm/" cmd = "helm upgrade ${ buildVars.app.name } ${installArgs.join(' ')} ${commandArgs.join(' ')} ./build/helm/" logger.info("""Command for push... ${cmd} """) logger.info("Template helm-release...") runsh cmdTemplate logger.info("Push helm-release...") retry(3) { // От того, что Helm-чарт будет ждать, мало что изменится runsh cmd } logger.info("Check exist pods in k8s...") retry(3) { runsh "kubectl --kubeconfig=${ buildVars.kubeconfig } --namespace ${ buildVars.namespace } get po" } // Нужны проверки после установки buildVars.application = [:] buildVars.application.typeOfProject = "deployments.apps" buildVars.application.namespace = buildVars.namespace // sleep ибо загрузка манифестов в K8S занимает какое-то время sleep(5) try { k8sStagesV2.correctReplicaActive(cmdbVars=buildVars, applicationName=buildVars.app.name) // Чтобы не оставлять мусора за собой в эвентах sh "kubectl --kubeconfig=${ buildVars.kubeconfig } --namespace ${ buildVars.namespace } delete events --all" } catch (Exception errorRun) { logger.info("Не удалось произвести проверку работы микросервиса. Дебаг:") runsh "kubectl --kubeconfig=${ buildVars.kubeconfig } --namespace ${ buildVars.namespace } events | grep ${ buildVars.app.name }" runsh "kubectl --kubeconfig=${ buildVars.kubeconfig } --namespace ${ buildVars.namespace } logs -l 'app=${ buildVars.app.name }'" error("Не удалось произвести проверку работы микросервиса. Детали в логах pipeline") } } currentBuild.result = 'SUCCESS' } catch (Exception err) { logger.err("Произошла ошибка") logger.err(err.message, true) currentBuild.description = err.message currentBuild.result = "FAILURE" } finally { if (currentBuild.result == 'FAILURE') { echo "Worker stage действительно упал — ставим флаг workerFailed" workerFailed = true } else if (currentBuild.result == 'SUCCESS') { echo "Worker stage завершился успешно - ставим флаг workerSuccess" workerSuccess = true } else { echo "Неопознаный currentBuild.result: ${currentBuild.result} — ставим флаг workerFailed" workerFailed = true } runsh "rm -rf ${env.BUILD_NUMBER}" } }}} } } parallelSteps["master"] = { node("master") { try { stage ("Master: start watchdog") { timeout(time: TimeoutTime.time, unit: TimeoutTime.unit) { while (true) { // Следим за окончанием сборки if (DEBUGGING_MODE) { sleep 1 // Только в режиме отладки используем echo echo "DEBUG: workerFailed = ${workerFailed}" echo "DEBUG: workerSuccess = ${workerSuccess}" } // Проверка флагов без использования шагов Jenkins if (workerFailed) { echo "Watchdog: обнаружен сбой в воркере" break } else if (workerSuccess) { echo "Watchdog: воркер завершил работу в Jenkins" break } } } } runsh "rm -rf ${env.BUILD_NUMBER}" } catch (Exception masterErrorTimeout) { logger.err("Ошибка по таймауту") } } } parallel parallelSteps