Задача — содать Jenkins джобу, которая будет деплоить Redis на Dev/Stage/Prod кластера.
В посте Redis: Master-Slave репликация и запуск в Kubernetes сделали это вручную, посмотрели что и как вообще запускается и работает — теперь надо автоматизировать.
Главный вопрос — как вообще деплоить и передавать параметры? Хочется использовать уже готовый чарт, но в тоже время — деплоить тоже Helm-ом со своим values.yaml
для каждого окружения — Dev/Stage/Prod.
Значит — можем создать свой Helm-чарт, а готовый чарт с Redis подключить как Helm dependency, которому будем передавать values.yaml
из родительского чарта.
Документация
Делаем:
- родительский чарт — backend-redis
- в нём dependency
bitnami/redis - в нём папку
env
- с папкой
dev
- с файлом
values.yaml
- в которой опишем Redis options
- с файлом
- с папкой
- в нём dependency
- и в Jenkins деплоим как
helm install backend-redis -f env/${ENV}/values.yaml
Делаем.
Содержание
Создание Helm-чарта
В репозитории с нашими сервисами создаём новый чарт:
Удаляем файлы шаблонов — они нам тут не нужны:
Создаём каталоги для values.yaml
:
Копируем конфиг из предыдущего поста:
Проверяем его содержимое:
Хорошо, только пароль тут вырежем — он будет передаваться через helm install --set
из Jenkins Password parameter.
Helm: values.yaml
для сабчарта
Что в этом конфиге надо изменить — это добавить имя сабчарта, что бы мы могли его «скормить» Хельму, что бы он использовал эти значения для чарта Redis от Bitnami.
В начале файла добавляем имя сабчарта redis и вырезаем пароль:
redis: global: redis: password: "" metrics: enabled: true serviceMonitor: enabled: true namespace: "monitoring" master: persistence: enabled: false service: type: LoadBalancer annotations: service.beta.kubernetes.io/aws-load-balancer-internal: "true" ...
Теперь можем добавить dependency в наш чарт backend-redis, находим чарт:
Используем bitnami/redis 11.2.3, добавляем dependencies в Chart.yaml
родительского чарта:
... dependencies: - name: redis version: ~11.2 repository: "@bitnami"
Добавляем репозиторий:
Запускаем проверку:
«Error: found in Chart.yaml, but missing in charts/ directory: redis» — ага, да. Забыл. Обновляем зависимости:
Проверяем архив сабчарта:
Запускаем ещё раз:
И если всё хорошо — запускаем установку:
Обратите внимание, что в --set
параметр password
мы передаём тоже с именем сабчарта, а затем нужный блок — redis.global.redis.password
.
Проверяем сервисы:
Поды:
Проверяем работу Redis:
Отлично — осталось только создать Jenkins-джобу.
Тут уже всё стандартно, по аналогии с джобами из Helm: пошаговое создание чарта и деплоймента из Jenkins.
Jenkins deploy job
Создаём Jenkins Pipeline Job.
Parameters
Описываем параметры:
Тут будут:
APP_CHART_NAME
: имя родительского чарта,AWS_EKS_NAMESPACE
: Kubernetes Namespace, в который деплоимAWS_EKS_CLUSTER
: Kubernetes кластер, в который деплоим и для которого генерируется .kube/config для kubectlAPP_ENV
: используется для подстановки вbackend-redis/env/$APP_ENV/values.yaml
и дляverify()
, см. Jenkins: Scripted Pipeline — подтверждение выполнения для Production окруженияAPP_REPO_URL
: репозиторий с чартомAPP_REPO_BRANCH
: бранч в этом репозитории
Структура каталогов у нас получилась такой:
Создаём Password Parameter с паролем, который будет задан Redis:
Pipeline script
Далее, описываем сам пайплайн:
// ask confirmation for build if APP_ENV == prod def verify() { def userInput = input( id: 'userInput', message: 'This is PRODUCTION!', parameters: [ [$class: 'BooleanParameterDefinition', defaultValue: false, description: '', name: 'Please confirm you sure to proceed'] ]) if(!userInput) { error "Build wasn't confirmed" } } // Add slack Notification def notifySlack(String buildStatus = 'STARTED') { // Build status of null means success. buildStatus = buildStatus ?: 'SUCCESS' def color //change for another slack chanel def token = 'devops-alarms-ci-slack-notification' if (buildStatus == 'STARTED') { color = '#D4DADF' } else if (buildStatus == 'SUCCESS') { color = '#BDFFC3' } else if (buildStatus == 'UNSTABLE') { color = '#FFFE89' } else { color = '#FF9FA1' } def msg = "${buildStatus}: `${env.JOB_NAME}` #${env.BUILD_NUMBER}:\n${env.BUILD_URL}" slackSend(color: color, message: msg, tokenCredentialId: token) } node { try { docker.image('projectname/kubectl-aws:4.1').inside('-v /var/run/docker.sock:/var/run/docker.sock') { stage('Verify') { switch("${env.APP_ENV}") { case "prod": verify() echo "Run job" break; default: echo "Dev deploy" break; } } stage('Init') { gitenv = git branch: '${APP_REPO_BRANCH}', credentialsId: 'jenkins-projectname-github', url: '${APP_REPO_URL}' GIT_COMMIT_SHORT = gitenv.GIT_COMMIT.take(8) RELEASE_VERSION = "${BUILD_NUMBER}" APP_VERSION = "${BUILD_NUMBER}.${GIT_COMMIT_SHORT}" AWS_EKS_REGION = "us-east-2" echo "APP_VERSION: ${APP_VERSION}" echo "RELEASE_VERSION: ${RELEASE_VERSION}" sh "aws eks update-kubeconfig --region ${AWS_EKS_REGION} --name ${AWS_EKS_CLUSTER}" sh "kubectl cluster-info" sh "helm version" sh "helm -n ${AWS_EKS_NAMESPACE} ls " } // install dependencies stage("Helm dependencies") { dir("projects/backend/services/${APP_CHART_NAME}") { sh "helm repo add bitnami https://charts.bitnami.com/bitnami" sh "helm dependency update" } } // lint the chart stage("Helm lint") { dir("projects/backend/services/") { sh "helm lint ${APP_CHART_NAME} -f ${APP_CHART_NAME}/env/${APP_ENV}/values.yaml" } } // just to create --app-version stage("Helm package") { dir("projects/backend/services/") { sh "helm package ${APP_CHART_NAME} --version ${RELEASE_VERSION} --app-version ${APP_VERSION}" } } // --dry-run first, if OK then run install stage("Helm install") { dir("projects/backend/services/") { sh "helm secrets upgrade --install --namespace ${AWS_EKS_NAMESPACE} --create-namespace --atomic ${APP_CHART_NAME} ${APP_CHART_NAME}-${RELEASE_VERSION}.tgz -f ${APP_CHART_NAME}/env/${APP_ENV}/values.yaml --set redis.global.redis.password=${REDIS_PASSWORD} --debug --dry-run" sh "helm secrets upgrade --install --namespace ${AWS_EKS_NAMESPACE} --create-namespace --atomic ${APP_CHART_NAME} ${APP_CHART_NAME}-${RELEASE_VERSION}.tgz -f ${APP_CHART_NAME}/env/${APP_ENV}/values.yaml --set redis.global.redis.password=${REDIS_PASSWORD} --debug" } } stage("Helm info") { dir("projects/backend/services/") { sh "helm ls -a -d --namespace ${AWS_EKS_NAMESPACE}" sh "helm get manifest --namespace ${AWS_EKS_NAMESPACE} ${APP_CHART_NAME}" } } } // send Slack notification if Jenkins build fails } catch (e) { currentBuild.result = 'FAILURE' notifySlack(currentBuild.result) throw e } }
Увдомления в Slack описаны в Jenkins: уведомление в Slack из Jenkins Scripted Pipeline.
Запускаем:
Проверяем:
Готово.