Refactor pipe
This commit is contained in:
Vendored
+15
-277
@@ -1,287 +1,25 @@
|
|||||||
/*
|
env.DEBUGGING_MODE = true
|
||||||
! Jenkins Watchdog Pipeline
|
env.FORCE_BUILD = false
|
||||||
|
timeoutTime = 15
|
||||||
|
|
||||||
Этот Pipeline реализует сборку на Kubernetes worker-ноде с контролем и завершением со стороны Jenkins master-ноды.
|
node("docker") {
|
||||||
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() {
|
timestamps() {
|
||||||
ansiColor("xterm"){
|
ansiColor("xterm"){
|
||||||
|
|
||||||
dir (env.BUILD_NUMBER) {
|
dir (env.BUILD_NUMBER) {
|
||||||
try {
|
|
||||||
stage ("Prepare environments") {
|
|
||||||
// Выкачивание проекта из Git
|
|
||||||
checkout scm
|
checkout scm
|
||||||
// Определение типа джобы:
|
dir ("executorJob") {
|
||||||
// - сборка и пуш в кубер: DEV / PR
|
checkout scm: [
|
||||||
// - проверка образа (из DEV) и пуш в кубер: TEST / PROD
|
$class: 'GitSCM',
|
||||||
logger.info("Targeting stand for deploy")
|
userRemoteConfigs: [[
|
||||||
// Если PR - в DEV
|
url: 'gitea@gitea.mgmt.nubes.ru:deck/universalDeckPipelines.git',
|
||||||
if (env.CHANGE_ID)
|
credentialsId: 'gitea-jenkins-tech'
|
||||||
buildVars.gitBranch = "dev"
|
]],
|
||||||
else
|
branches: [[ name: "master" ]]
|
||||||
buildVars.gitBranch = env.BRANCH_NAME
|
], poll: false
|
||||||
|
load "universal.groovy"
|
||||||
// Проверка на нужные бренчи
|
|
||||||
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
|
|
||||||
+47
-32
@@ -1,28 +1,32 @@
|
|||||||
##### Переменные #####
|
##### Переменные Контейнера #####
|
||||||
######################
|
#################################
|
||||||
app:
|
application:
|
||||||
# Имя приложения / Имя микросервиса
|
|
||||||
name: "svc-api"
|
name: "svc-api"
|
||||||
# Порт на котором приложение будет работать
|
|
||||||
port: 8080
|
port: 8080
|
||||||
# Health-Check приложения
|
|
||||||
health: "/api/v1"
|
|
||||||
# CPU пода (milicores)
|
|
||||||
cpu: 1000
|
cpu: 1000
|
||||||
# Mem пода (megabytes)
|
|
||||||
mem: 1024
|
mem: 1024
|
||||||
|
probes:
|
||||||
|
readinessProbe:
|
||||||
|
failureThreshold: 5
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 30
|
||||||
|
httpGet:
|
||||||
|
port: ${ application.port }
|
||||||
|
livenessProbe:
|
||||||
|
failureThreshold: 5
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 15
|
||||||
|
tcpSocket:
|
||||||
|
port: ${ application.port }
|
||||||
|
|
||||||
# Получение версии приложения
|
###### Переменные Секретов ######
|
||||||
# Используется для построения дашбордов / публикации в Registry
|
#################################
|
||||||
getCommandVersion: "awk -F'\"' '/APIVersion/ {print \\$2; exit}' v1/Application.cfc"
|
vault:
|
||||||
|
enable: true
|
||||||
# Если включено, то необходимо сделать файл Dockerfile_build_lib
|
type: inject
|
||||||
# Либа будет загружена в deck/npm/nubes/{appName}/{version}
|
inject:
|
||||||
needBuildLib: false
|
secretStore: "deck-${ gitBranch }"
|
||||||
# Нужно ли хранить секреты в Vault
|
secrets:
|
||||||
needStoreVaultSecret: true
|
|
||||||
# Наименование key, которые нужно забрать из Vault
|
|
||||||
storedSecretsList:
|
|
||||||
- "LUCEE_EXTENSIONS"
|
- "LUCEE_EXTENSIONS"
|
||||||
- "ORCHESTRATOR_AUTH"
|
- "ORCHESTRATOR_AUTH"
|
||||||
- "VAULT_ROLE_ID"
|
- "VAULT_ROLE_ID"
|
||||||
@@ -35,16 +39,27 @@ storedSecretsList:
|
|||||||
- "cfconfig_datasources_cmdb_username"
|
- "cfconfig_datasources_cmdb_username"
|
||||||
- "cfconfig_datasources_cmdb_password"
|
- "cfconfig_datasources_cmdb_password"
|
||||||
|
|
||||||
##### Константы #####
|
custom:
|
||||||
#####################
|
dev:
|
||||||
# Путь до секрета в Vault
|
ingressAddr: deck-api-dev.ngcloud.ru
|
||||||
vaultPath: "config.deck-${ app.name }-${ gitBranch }"
|
test:
|
||||||
# Где хранится образ (Registry.app)
|
ingressAddr: deck-api-test.ngcloud.ru
|
||||||
registryUrl: "registry.ngcloud.ru"
|
prod:
|
||||||
registryRepoPath: "deck"
|
ingressAddr: deck-api.ngcloud.ru
|
||||||
registryContainerName: "deck-${ app.name }"
|
|
||||||
registryImageName: "${ registryRepoPath ? registryRepoPath + '/' + registryContainerName : registryContainerName }"
|
|
||||||
|
|
||||||
# В какой namespace попадает проект в Kubernetes (по хорошему нужно изменить)
|
ingress:
|
||||||
# Ибо счас всё хранится в одном кубере. Кубер умрёт - 3 сренда в OUT
|
enable: true
|
||||||
namespace: "deck-${ gitBranch }"
|
hosts:
|
||||||
|
- domain: "${application.custom[gitBranch].ingressAddr}"
|
||||||
|
service: "sys-${ application.name }"
|
||||||
|
port: "${ application.port }"
|
||||||
|
path: /
|
||||||
|
|
||||||
|
###### Переменные Стенда ######
|
||||||
|
###############################
|
||||||
|
kubernetes:
|
||||||
|
namespace: "deck-${ gitBranch }"
|
||||||
|
stand:
|
||||||
|
dev: k8s-1.dmz.nubes.ru
|
||||||
|
test: k8s-1.dmz.nubes.ru
|
||||||
|
prod: k8s-1.dmz.nubes.ru
|
||||||
Reference in New Issue
Block a user