В предыдущей статье я описывал довольно громоздкий скрипт на bash для скейлинга подов с упоминанием, что он позже был переписан на groovy и запускался в рамках jenkins pipelines. Окей, посмотрим на то, как это выглядит. Комментарии в коде)
Groovy
// Описываем переменные в тупую в виде мапы
vars = [:]
vars['stopFile'] = "microservice_stop.txt"
vars['stopForDrpFile'] = "stop_for_drp.txt"
vars['stopForProdDrFile'] = "stop_for_prod_dr.txt"
vars['startProdDrFile'] = "start_prod_dr.txt"
vars['stopDcAirflowDrpFile'] = "stop_dc_airflow_drp_file.txt"
vars['stopStsAirflowDrpFile'] = "stop_sts_airflow_drp_file.txt"
vars['twentyPersentFile'] = "20_persent_up.txt"
vars['fullFile'] = "full_up.txt"
vars['statefulsetDownFile'] = "statefulset_down.txt"
vars['statefulsetTwentyPersentFile'] = "statefulset_20.txt"
vars['statefulsetFullFile'] = "statefulset_full.txt"
vars['statefulsetUserCustomFile'] = "statefulset_user_custom.txt"
vars['userCustomFile'] = "user_custom.txt"
vars['onlyCadenceFile'] = "only_cadence.txt"
vars['notCadenceMdmFile'] = "minus_cadence_mdm.txt"
vars['onlyMdmFile'] = "mdm.txt"
vars['prodComponentsSize'] = "prod_deployment_config_size.txt"
vars['prodStateFulSetSize'] = "prod_statefulset_size.txt"
cronJobStateFile = "cronjobState-${env.NAMESPACE}.txt"
// Функа нотификатор
def notify(type, name, replicas) {
println("[JENKINS][DEBUG] Scale ${type} ${name} in ${replicas} replicas")
}
// Проверяем поды с cadence
def cadencePodCheking(namespace, component) {
def cadenceComponentStatus = sh(script: "oc -n ${namespace} get pod -l app=${component} -o 'jsonpath={..status.conditions[?(@.type==\"Ready\")].status}'", returnStdout: true).trim()
return cadenceComponentStatus
}
// Проверяем другие поды, которые уже позже вошли в список нашей проверки
def mdmPodChecking(namespace, component) {
def mdmComponentStatus = sh(script: "oc -n ${namespace} get pod -l app=${component} -o 'jsonpath={..status.conditions[?(@.type==\"Ready\")].status}'", returnStdout: true).trim()
return mdmComponentStatus
}
// Проверяем знакомый нам ldap
def ds389PodChecking(namespace) {
def dsComponentStatus = sh(script: "oc -n ${namespace} get pods -l app=ds389 -o 'jsonpath={..status.conditions[?(@.type==\"Ready\")].status}'", returnStdout: true)
return dsComponentStatus
}
// Ожидаем поднятие подов candence
def waitCadencePod() {
def cadenceList = ["cadence-frontend", "cadence-history", "cadence-matching", "cadence-worker"]
// Используем timeout, тк дальше идет бесконечный цикл
timeout(time: 10, unit: 'MINUTES') {
while (true) {
cadenceList.each {
if (it == "cadence-frontend") {
cadenceFrontendStatus = cadencePodCheking(env.NAMESPACE, it)
} else if (it == "cadence-history") {
cadenceHistoryStatus = cadencePodCheking(env.NAMESPACE, it)
} else if (it == "cadence-matching") {
cadenceMatchingStatus = cadencePodCheking(env.NAMESPACE, it)
} else if (it == "cadence-worker") {
cadenceWorkerStatus = cadencePodCheking(env.NAMESPACE, it)
}
}
if (cadenceFrontendStatus.contains("False") || cadenceHistoryStatus.contains("False") || cadenceMatchingStatus.contains("False") || cadenceWorkerStatus.contains("False")) {
println("[JENKINS][DEBUG] waiting for cadence component is UP")
sleep(30)
} else {
println("[JENKINS][DEBUG] cadence component is UP.")
break
}
}
}
}
// Тоже самое для подов Ldap
def waitDs389Pod(namespace) {
dsPodStatus = ds389PodChecking(namespace)
timeout(time: 10, unit: 'MINUTES') {
while (dsPodStatus != "True True True") {
println("[JENKINS][DEBUG] waiting for ds389 pod is UP")
sleep(30)
dsPodStatus = ds389PodChecking(namespace)
}
}
}
// И для этих подов. Имена подов изменены
def waitMdmPod() {
sleep(10)
mdmPodList = ["pod_name_1", "pod_name_2"]
timeout(time: 10, unit: 'MINUTES') {
while (true) {
mdmPodList.each {
if (it == "pod_name_1") {
mdmDataStatus = mdmPodChecking(env.NAMESPACE, it)
} else if (it == "pod_name_2") {
mdmMetadataStatus = mdmPodChecking(env.NAMESPACE, it)
}
}
if (mdmDataStatus.contains("False") || mdmMetadataStatus.contains("False")) {
println("[JENKINS][DEBUG] waiting for mdm component is UP")
sleep(30)
} else {
println("[JENKINS][DEBUG] mdm component is UP.")
break
}
}
}
}
// Получаем текущее состояние cronjob и записываем его в файл
def getCronJobState(namespace) {
if (fileExists ("${env.GERRIT_PROJECT_NAME}/${cronJobStateFile}")) {
println("[JENKINS][DEBUG] File ${cronJobStateFile} exists and will be deleted.")
sh(script: "rm -f ${env.GERRIT_PROJECT_NAME}/${cronJobStateFile}", returnStdout: true)
}
else {
println("[JENKINS][DEBUG] File ${cronJobStateFile} does not exist and will be created")
}
def cronJobName = sh(script: "oc -n ${namespace} get cronjob --no-headers|awk '{print \$1}'", returnStdout: true).trim()
for (cj in cronJobName.split("\n")) {
state = sh(script: "oc -n ${namespace} get cronjob ${cj} -o yaml |grep -w \"suspend:\"|awk '{print \$2}'", returnStdout: true).trim()
println("[JENKINS][DEBUG] Cronjob ${cj}:${state} save in ${cronJobStateFile}.")
sh """
echo ${cj}:${state} >> ${env.GERRIT_PROJECT_NAME}/${cronJobStateFile}
"""
}
}
// Выключаем cronjob
def disableCronJob(namespace) {
def cronJobName = sh(script: "oc -n ${namespace} get cronjob --no-headers|awk '{print \$1}'", returnStdout: true).trim()
for (cj in cronJobName.split("\n")) {
println("[JENKINS][DEBUG] Disable cronJob ${cj} in ${namespace}")
sh """
oc -n ${namespace} patch cronjobs ${cj} -p '{"spec" : {"suspend" : true }}'||true
"""
}
}
// Удаляем cronjob
def deleteJobs(namespace) {
def jobName = sh(script: "oc -n ${namespace} get job --no-headers|awk '{print \$1}'", returnStdout: true).trim()
if (!(jobName.contains("No resources found."))) {
for (job in jobName.split("\n")) {
println("[JENKINS][DEBUG] Delete job ${job} in ${namespace}")
sh("oc -n ${namespace} delete job ${job}")
}
}
}
// Возврщаем cronjob в исходное состояние
def cronJobUp(namespace) {
def file = sh(script: "cat ${env.GERRIT_PROJECT_NAME}/${cronJobStateFile}", returnStdout: true).trim()
for (cj in file.split("\n")) {
cjName = cj.tokenize(":")[0]
cjState = cj.tokenize(":")[1]
println("[JENKINS][DEBUG] Cronjob ${cjName} set suspend: ${cjState}")
sh """
oc -n ${namespace} patch cronjobs ${cjName} -p '{"spec" : {"suspend" : '${cjState}' }}' || true
"""
}
}
// Пушим стейт cronjob в репозиторий
def pushCronJobState() {
withCredentials([usernamePassword(credentialsId: 'jenkinsHTTP', passwordVariable: 'GIT_PASSWORD', usernameVariable: 'GIT_USERNAME')]) {
sh """
cd ${env.GERRIT_PROJECT_NAME}
git config --global user.name ${GIT_USERNAME}
git config --global user.email ${GIT_USERNAME}@example.com
git add ${cronJobStateFile}
git commit --allow-empty -m 'update cronjob state file'
git push http://${GIT_USERNAME}:${GIT_PASSWORD}@gerrit:8080/${env.GERRIT_PROJECT_NAME} HEAD:master
"""
}
}
// тут общая функция скейлинга либо dc либо sts
def scaleResources(namespace, fileName, type) {
def file = sh(script: "cat ${env.GERRIT_PROJECT_NAME}/${fileName}", returnStdout: true)
for (item in file.split("\n")) {
componentName = item.tokenize(":")[0]
componentReplicas = item.tokenize(":")[1]
if (type == "dc") {
notify(type, componentName, componentReplicas)
sh """
oc -n ${namespace} scale deploymentConfig ${componentName} --replicas=${componentReplicas} || true
"""
} else if (type == "st") {
notify(type, componentName, componentReplicas)
sh """
oc -n ${namespace} patch statefulsets ${componentName} -p '{\"spec\":{\"replicas\":${componentReplicas}}}' || true
"""
}
}
}
// Метод подтяние сущностей
def startResourses(namespace, stFileName, onlyCadenceFile, dcFileName, onlyMdmFile, notCadenceMdmFile) {
scaleResources(namespace, stFileName, "st")
waitDs389Pod(namespace)
sh("cat ${env.GERRIT_PROJECT_NAME}/${dcFileName}|grep -E \"^cadence\" > ${env.GERRIT_PROJECT_NAME}/${onlyCadenceFile}")
scaleResources(namespace, onlyCadenceFile, "dc")
waitCadencePod()
sh("cat ${env.GERRIT_PROJECT_NAME}/${dcFileName} |grep -E '(pod_name_1|pod_name_2)'|grep -v exchange > ${env.GERRIT_PROJECT_NAME}/${onlyMdmFile}")
scaleResources(namespace, onlyMdmFile, "dc")
waitMdmPod()
sh("cat ${env.GERRIT_PROJECT_NAME}/${dcFileName} |grep -v -E \"^cadence\"|grep -v -E '(pod_name_1|pod_name_2)' > ${env.GERRIT_PROJECT_NAME}/${notCadenceMdmFile}")
scaleResources(namespace, notCadenceMdmFile, "dc")
if (fileExists ("${env.GERRIT_PROJECT_NAME}/${cronJobStateFile}")) {
cronJobUp(namespace)
} else {
println("[JENKINS][DEBUG] Cronjob state file does not exist. Skipped cronJob UP method.")
}
}
// Уже пайплайновое. Используем агент с тегом docker
node("docker") {
// Первый стейдж. Пуллим репозиторий себе
stage("Pull pod-scaling repository") {
withCredentials([usernamePassword(credentialsId: 'jenkinsHTTP', passwordVariable: 'GIT_PASSWORD', usernameVariable: 'GIT_USERNAME')]) {
sh """
rm -rf ${env.GERRIT_PROJECT_NAME}
git config --global user.name ${GIT_USERNAME}
git config --global user.email ${GIT_USERNAME}@example.com
git clone http://${GIT_USERNAME}:${GIT_PASSWORD}@gerrit:8080/${env.GERRIT_PROJECT_NAME}
"""
}
}
// Второй стейдж и последний. Скейлим собственно поды, на основе выпадающей переменной
// в jenkins
stage("Scaling pod") {
// Используем оператор switch для этой переменной
switch (env.FUNCTION) {
// Далее просто в зависимости от выбранного значения, скелим поды
case "start_20":
startResourses(env.NAMESPACE, vars.statefulsetTwentyPersentFile, vars.onlyCadenceFile, vars.twentyPersentFile, vars.onlyMdmFile, vars.notCadenceMdmFile)
break
case "start_all":
startResourses(env.NAMESPACE, vars.statefulsetFullFile, vars.onlyCadenceFile, vars.fullFile, vars.onlyMdmFile, vars.notCadenceMdmFile)
break
case "start_custom":
startResourses(env.NAMESPACE, vars.statefulsetUserCustomFile, vars.onlyCadenceFile, vars.userCustomFile, vars.onlyMdmFile, vars.notCadenceMdmFile)
break
case "start_prod_dr":
startResourses(env.NAMESPACE, vars.statefulsetUserCustomFile, vars.onlyCadenceFile, vars.startProdDrFile, vars.onlyMdmFile, vars.notCadenceMdmFile)
break
case "prod_sync":
startResourses(env.NAMESPACE, vars.prodStateFulSetSize, vars.onlyCadenceFile, vars.prodComponentsSize, vars.onlyMdmFile, vars.notCadenceMdmFile)
break
// Тут несколько кейсов для переиспользования кода на резервном проде
case "stop_for_drp":
getCronJobState(env.NAMESPACE)
disableCronJob(env.NAMESPACE)
pushCronJobState()
deleteJobs(env.NAMESPACE)
scaleResources(env.NAMESPACE, vars.stopForDrpFile, "dc")
scaleResources(env.NAMESPACE, vars.stopDcAirflowDrpFile, "dc")
scaleResources(env.NAMESPACE, vars.stopStsAirflowDrpFile, "st")
break
case "stop":
scaleResources(env.NAMESPACE, vars.statefulsetDownFile, "st")
scaleResources(env.NAMESPACE, vars.stopFile, "dc")
getCronJobState(env.NAMESPACE)
disableCronJob(env.NAMESPACE)
pushCronJobState()
deleteJobs(env.NAMESPACE)
break
case "stop_for_prod_dr":
scaleResources(env.NAMESPACE, vars.statefulsetDownFile, "st")
scaleResources(env.NAMESPACE, vars.stopForProdDrFile, "dc")
getCronJobState(env.NAMESPACE)
disableCronJob(env.NAMESPACE)
pushCronJobState()
deleteJobs(env.NAMESPACE)
break
}
}
}
А теперь давайте посмотрим как это выглядит в файле DSL, который использует jenkins-dsl plugin для описание спеки джобы в виде кода.
Groovy
// Описываем пайплайн джоб
pipelineJob("pod-scaling") {
description("Описание")
logRotator {
numToKeep(numberOfBuildsToKeep)
daysToKeep(daysToKeepBuilds)
}
parameters {
activeChoiceReactiveParam('NAMESPACE') {
description('The existing namespace for deploy')
choiceType('SINGLE_SELECT')
// В виде groovy скрипта получаем список ns, дабы их можно было выбрать в переменной
// NAMESPACE
groovyScript {
script('''def svc = new ProcessBuilder('sh','-c',"/usr/bin/oc get namespaces | grep -v NAME | awk '{print \\$1}'").redirectErrorStream(false).start().text
list = svc.readLines()
return list
''')
fallbackScript()
}
}
// Скрытая переменная определенная где то выше. В ней строка с путем до пайплайнов
wHideParameterDefinition {
name('PIPELINES_PATH')
defaultValue("${pipelinePath}")
description('Path to pipelines')
}
// Да, да на проекте использовался gerrit в качестве системы код ревью)
wHideParameterDefinition {
name('GERRIT_PROJECT_NAME')
defaultValue("pod-scaling")
description('Pod-scaling')
}
// Та самая переменная, которую в коде мы используем для оператора switch
// простым groovy скриптом возвращаем список доступных функций
activeChoiceParam('FUNCTION') {
description('Script function')
choiceType('SINGLE_SELECT')
groovyScript {
script('''return['stop','start_20','start_all', 'start_custom', 'prod_sync', 'stop_for_drp', 'stop_drp_airflow', 'stop_for_prod_dr', 'start_prod_dr']''')
fallbackScript()
}
}
}
// Эти параметры для связки с герритом и определены где-то выше
definition {
cpsScm {
scm {
git {
remote {
url(repositoryUrl)
credentials(repositoryCreds)
}
branches("master")
scriptPath("${pipelinePath}/pod-scaling.groovy")
}
}
}
}
}