Задача — содать 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]
Готово.







