Задача – содать 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
- с файлом
- с папкой
- и в Jenkins деплоим как
helm install backend-redis -f env/${ENV}/values.yaml
Делаем.
Содержание
Создание Helm-чарта
В репозитории с нашими сервисами создаём новый чарт:
[simterm]
$ helm create backend-redis Creating backend-redis
[/simterm]
Удаляем файлы шаблонов – они нам тут не нужны:
[simterm]
$ rm -rf backend-redis/templates/*
[/simterm]
Создаём каталоги для values.yaml
:
[simterm]
$ mkdir -p backend-redis/env/{dev,stage,prod}
[/simterm]
Копируем конфиг из предыдущего поста:
[simterm]
$ cp ~/Temp/redis-opts.yaml backend-redis/env/dev/values.yaml
[/simterm]
Проверяем его содержимое:
[simterm]
$ head backend-redis/env/dev/values.yaml global: redis: password: "blablacar" metrics: enabled: true serviceMonitor: enabled: true namespace: "monitoring"
[/simterm]
Хорошо, только пароль тут вырежем – он будет передаваться через 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, находим чарт:
[simterm]
$ helm search repo redis NAME CHART VERSION APP VERSION DESCRIPTION bitnami/redis 11.2.3 6.0.9 Open source, advanced key-value store. It is of... bitnami/redis-cluster 3.2.10 6.0.9 Open source, advanced key-value store. It is of... stable/prometheus-redis-exporter 3.5.1 1.3.4 DEPRECATED Prometheus exporter for Redis metrics stable/redis 10.5.7 5.0.7 DEPRECATED Open source, advanced key-value stor... stable/redis-ha 4.4.4 5.0.6 Highly available Kubernetes implementation of R... stable/sensu 0.2.3 0.28 Sensu monitoring framework backed by the Redis ...
[/simterm]
Используем bitnami/redis 11.2.3, добавляем dependencies в Chart.yaml
родительского чарта:
... dependencies: - name: redis version: ~11.2 repository: "@bitnami"
Добавляем репозиторий:
[simterm]
$ helm repo add bitnami https://charts.bitnami.com/bitnami
[/simterm]
Запускаем проверку:
[simterm]
$ helm install backend-redis . --dry-run -f env/dev/values.yaml --debug install.go:172: [debug] Original chart version: "" install.go:189: [debug] CHART PATH: /home/setevoy/Work/devops-kubernetes/projects/backend/services/backend-redis Error: found in Chart.yaml, but missing in charts/ directory: redis helm.go:94: [debug] found in Chart.yaml, but missing in charts/ directory: redis ...
[/simterm]
“Error: found in Chart.yaml, but missing in charts/ directory: redis” – ага, да. Забыл. Обновляем зависимости:
[simterm]
$ helm dependency update Hang tight while we grab the latest from your chart repositories... ...Successfully got an update from the "equinor-charts" chart repository ...Successfully got an update from the "bitnami" chart repository ...Successfully got an update from the "stable" chart repository Update Complete. ⎈Happy Helming!⎈ Saving 1 charts Downloading redis from repo https://charts.bitnami.com/bitnami Deleting outdated charts
[/simterm]
Проверяем архив сабчарта:
[simterm]
$ ll charts/ total 64 -rw-r--r-- 1 setevoy setevoy 64195 Oct 28 16:30 redis-11.2.3.tgz
[/simterm]
Запускаем ещё раз:
[simterm]
$ helm install backend-redis . --dry-run -f env/dev/values.yaml --debug
[/simterm]
И если всё хорошо – запускаем установку:
[simterm]
$ helm upgrade --install --namespace eks-dev-1-backend-redis-ns --create-namespace --atomic backend-redis . -f env/dev/values.yaml --set redis.global.redis.password=p@ssw0rd --debug history.go:53: [debug] getting history for release backend-redis Release "backend-redis" does not exist. Installing it now. install.go:172: [debug] Original chart version: "" install.go:189: [debug] CHART PATH: /home/setevoy/Work/devops-kubernetes/projects/backend/services/backend-redis client.go:108: [debug] creating 1 resource(s) client.go:108: [debug] creating 9 resource(s) wait.go:53: [debug] beginning wait for 9 resources with timeout of 5m0s wait.go:206: [debug] Service does not have load balancer ingress IP address: eks-dev-1-backend-redis-ns/backend-redis wait.go:329: [debug] StatefulSet is not ready: eks-dev-1-backend-redis-ns/backend-redis-node. 1 out of 2 expected pods have been scheduled ...
[/simterm]
Обратите внимание, что в --set
параметр password
мы передаём тоже с именем сабчарта, а затем нужный блок – redis.global.redis.password
.
Проверяем сервисы:
[simterm]
$ kk -n eks-dev-1-backend-redis-ns get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE backend-redis LoadBalancer 172.20.153.106 internal-a5d0d8f5a5d4a4438a2ca06610886d2f-1400935130.us-east-2.elb.amazonaws.com 6379:30362/TCP,26379:31326/TCP 70s backend-redis-headless ClusterIP None <none> 6379/TCP,26379/TCP 70s backend-redis-metrics ClusterIP 172.20.50.77 <none> 9121/TCP 70s 9121/TCP 21s
[/simterm]
Поды:
[simterm]
$ kk -n eks-dev-1-backend-redis-ns get pod NAME READY STATUS RESTARTS AGE backend-redis-node-0 3/3 Running 0 37s backend-redis-node-1 3/3 Running 0 21s
[/simterm]
Проверяем работу Redis:
[simterm]
admin@bttrm-dev-app-1:~$ redis-cli -h internal-a5d0d8f5a5d4a4438a2ca06610886d2f-1400935130.us-east-2.elb.amazonaws.com -p 6379 -a p@ssw0rd info replication # Replication role:master connected_slaves:1 slave0:ip=10.3.45.195,port=6379,state=online,offset=15261,lag=1 ...
[/simterm]
Отлично – осталось только создать 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
: бранч в этом репозитории
Структура каталогов у нас получилась такой:
[simterm]
projects/ └── backend └── services └── backend-redis ├── Chart.lock ├── charts │ └── redis-11.2.3.tgz ├── Chart.yaml ├── env │ ├── dev │ │ └── values.yaml │ ├── prod │ └── stage ├── templates └── values.yaml
[/simterm]
Создаём 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.
Запускаем:
Проверяем:
[simterm]
$ helm -n eks-dev-1-backend-redis-ns ls NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION backend-redis eks-dev-1-backend-redis-ns 2 2020-10-29 14:19:52.407047243 +0000 UTC deployed backend-redis-13 13.6da15d3e
[/simterm]
Готово.