«Эталонный» скейлинг подов — groovy

0
(0)

В предыдущей статье я описывал довольно громоздкий скрипт на 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")
                }
            }
        }
    }
}

Насколько статья полезна?

Нажмите на звезду, чтобы оценить!

Средняя оценка 0 / 5. Количество оценок: 0

Оценок пока нет. Поставьте оценку первым.

Оставить комментарий