Продолжаем трогать Kubernetes и Helm.
Сам Хельм в общих чертах рассмотрели в посте Helm: Kubernetes package manager — обзор, начало работы – теперь надо прикрутить его в Jenkins. И не просто прикрутить его вызов – а создать чарт, потому что сейчас приложение деплоится через “голые” манифест-файлы Kubernetes, в котором sed
проставляет теги Докер-образа и значения переменных для загрузки на окружение через kubectl apply -f
.
Данный пост очередной НЕ-HowTo, а пример знакомства с Helm: возьмём существующий проект, который уже деплоится в Kubernetes-кластер, обновим его манифесты, что бы использовать их с Helm, продумаем – как именно и куда будем деплоить, и создадим Jenkins-джобу.
По ходу дела будем ближе знакомиться с Helm, и его подводными камнями.
В общем, ещё один пример того, как очередная задача из смутной идеи в голове превращается в рабочий сервис – длиннопост в вольном стиле, аналог AWS Elastic Kubernetes Service: автоматизация создания кластера, часть 1 — CloudFormation и AWS Elastic Kubernetes Service: — автоматизация создания кластера, часть 2 — Ansible, eksctl, где шаг за шагом реализуем свою задумку.
Содержание
Планирование
Репозитории
Итак, у нас имеется некое веб-приложение, которое надо деплоить в Kubernetes.
Само приложение разбито на две части – фронтенд на React, хостится и деплоится в AWS S3, и бекенд – NodeJS, работает в Docker-контейнере.
Первый вопрос, который появляется в голове – монорепа, или нет?
Т.е. – где хранить код приложения, где – файлы чартов Helm-а, а где – jenkins-файлы?
Хотелось бы монорепу, и всё держать в одном репозитории, но у нас подобных проектов будет не один, и смысла под каждый писать отдельные дженкис-файлы нет. Тем более, что если (когда!) захочется что-то изменить в одном – то придётся делать это во всех по отдельности.
При этом и их файлы манифестов для Kubernetes будут практически одинаковы – и можно было бы заморочится вообще, и используя возможности шаблонизатора Go – использовать один общий чарт на всех.
Но так как их одинаковость пока под вопросом – запускаемые веб-проекты ещё только разрабатываются, да ещё и отдельными группами девелоперов – то чарты будем делать под каждый проект отдельно.
Значит, у нас будет:
- отдельный репозиторий с кодом проекта
- там же в отдельной папке храним файлы чартов
- отдельный репозиторий с файлами для Jenkins-pipeline
Хочется прикрутить ещё и релизы, да ещё и связать их с Github Releases – но это, может быть, потом. В целом – реализуемо, и даже есть /chart-releaser.
Jenkins build steps
Дальше.
Билд Docker-образов – и билд/деплой самих чартов – делать одной джобой – или разными?
Сейчас у нас образы собираются в одной джобе, после чего пушатся в DockerHub, после чего девелопер должен сходить в джобу деплоймента, и при запуске в параметрах указать тег (номер билда) + дополнительные параметры:
Это, конечно, ни разу неудобно, поэтому переделаем.
Итак, будет один пайплайн, в котором нам надо:
- чекаут кода проекта
- линт чарта
- сгенерить kubeconfig
helm install example-chart --dry-run --debug example-chart/
helm install example-chart example-chart/
- тест деплоя –
helm get manifest example-chart
Опционально – удалять с chart uninstall
. Про удаление ресурсов вообще поговорим ниже.
Что ещё?
Namespaces
Ооо – нейспейсы жеж!
Что хочется:
- иметь возможность деплоить по одной кнопке в какой-то дефолтный namespace, имя которого зависит от проекта/приложения/чарта
- деплоить в кастомный namespace – по имени бранча репозитория, из которого запускался билд
- деплоить в вообще кастомный namespace – дать возможность указать его при старте джобы
А как их потом удалять?
Можно добавить вебхук из Гитхаба при delete-операциях, но во-первых часть проектов хостятся в Gitlab – хотя он тоже умеет в вебхуки. Но во-вторых – а все ли девелоперы всех команд будут удалять свои закрытые бранчи?
Т.е. тут проблема как раз из-за разных команд и разных проектов, а решение хочется иметь маскимально универсальное.
А удалять НС-ы надо – иначе будет пачка заброшенных ресурсов – подов, ингресов, волюмов, и т.д.
Что делать?…
Окей. А пусть будет какой-то дефолтный неймспейс. Например, чарт/приложение называются bttrm-apps – значит, для Dev-деплой джобы НС будем создавать bttrm-apps-dev-ns.
В джобе добавить параметр, в нём сделать возможность выбора – использовать этот дефолтный НС, создать/использовать НС по имени бранча, или указать свой.
Можно так в Jenkins?
Не факт – идём глянем.
Choise parameter – тут просто задаётся список предефайненных значений – нельзя будет задать своё имя НС-а:
А Multi string parameter?
Вроде как что-то аналогичное Choise parameter?
Смотрим на него:
Нет, тоже не подходит – оно просто из параметра передаст нам в переменную дженкисфайла значение из нескольких строк, а нам нужна одна string.
Значит – или таки обычный String параметр, или Choise parameter с набором уже заданных значений… Но тогда потеряем возможность создать кастомный НС…
Или… Или… Или… Мысль…
А что на счёт Boolien parameter?
Типа “поменять НС”…
Дефолтное значение “не менять” – и используем предефайненное значение bttrm-apps-dev-1-ns…
А если юзер выбирает менять – то используем имя НС как имя бранча – а имя бранча вытянем из репозитория после чекаута…
Мысль? А может быть!
Хотя снова-таки – лишаемся возможности создать кастомный НС…
А что если просто использовать тот же Choise parameter с набором уже заданных значений, но создать три значения:
- use default bm-apps-dev-ns
- use github-branch-name
- set own name
А потом в коде самого пайплайна проверять выбор, и если выбрано set own name – то просто запросить User input, и пользователь задаст имя неймспейса, который хочет создать.
Тоже не самое кошерное решение – но может сработать. Пока остановимся на нём.
Остаётся открытым вопрос про удаление НС-ов – будем думать потом 🙂
Хотя потом может быть больно.
В конце-концов – мы спокойно можем раз в месяц удалять весь Dev-кластер, и провиженить заново – дело 20 минут и одного клика в Jenkins, а для Stage/Prod деплоев будут использоваться только жёстко заданные неймспейсы, потому там такой проблемы не будет (но это не точно).
Окей, make sense.
Что дальше?
Дальше локально сделаем:
- создадим чарт для приложения – используем реальное, но отбранчуемся, что бы не мешать девелоперам.
- настроим
kubectl
на новый Dev-кластер - выполним
helm install
Потом пойдём делать джобу в дженкисе.
Джоба будет клонировать репозиторий с приложением, и делать helm install
в нужный namespace.
А что с релизами-то? Го, почитаем Charts and Versioning.
А релиз-версии нужны судя по всему только если мы будем заливать чарты в виде архивов в свой Helm-репозиторий – в самом Кубере они никак не применяются…
Значит – пока этот вопрос отложим, потом вернёмся к chart-releaser.
Поехали тестить?
Helm test deploy
kubectl
config
У меня есть новый тестовый кластер bttrm-eks-dev-1 – создаём для него конфиг.
Генерим конфиг, создаём новый контекст с именем arseniy@bttrm-eks-dev-1, см. Kubernetes: kubectl и kubeconfig — обзор файла, добавление кластера, пользователя и контекста:
[simterm]
$ aws --profile arseniy eks update-kubeconfig --name bttrm-eks-dev-1 --alias arseniy@bttrm-eks-dev-1 Added new context arseniy@bttrm-eks-dev-1 to /home/setevoy/.kube/config
[/simterm]
Создаём тестовый чарт:
[simterm]
$ cd /tmp/ $ helm create deployment-testing-chart Creating deployment-testing-chart
[/simterm]
Helm: the server has asked for the client to provide credentials
Пробуем залить в default namespace:
[simterm]
$ helm install deployment-testing-chart --dry-run deployment-testing-chart/ Error: Kubernetes cluster unreachable: the server has asked for the client to provide credentials
[/simterm]
WTF?
Проверяем контекст:
[simterm]
$ kk config current-context arseniy@bttrm-eks-dev-1
[/simterm]
(kk
– алиас для kubectl
в ~/.bashrc
: alias kk="kubectl"
)
Всё верно…
А доступ есть?
[simterm]
$ kk get pod error: You must be logged in to the server (Unauthorized)
[/simterm]
Нет.
В автоматизации создания кластера не добавлен мой IAM user…
Что там вообще? Читаем пост AWS Elastic Kubernetes Service: RBAC-авторизация через AWS IAM и RBAC группы, вспоминаем – делал через роли, отдельных пользователей в aws-auth ConfigMap уже не добавляю.
Надо локально настроить AssumeRole.
Надеюсь я прописал в полиси этой роли доступ для моего юзера…
Пробуем – обновляем ~/.aws/config
:
... [profile iam-bttrm-eks-root] role_arn = arn:aws:iam::534***385:role/iam-bttrm-eks-root-role source_profile = arseniy region = us-east-2
Удаляем созданный контекст:
[simterm]
$ kk config delete-context arseniy@bttrm-eks-dev-1 warning: this removed your active context, use "kubectl config use-context" to select a different one deleted context arseniy@bttrm-eks-dev-1 from /home/setevoy/.kube/config
[/simterm]
Генерим новый:
[simterm]
$ aws --profile iam-bttrm-eks-root eks update-kubeconfig --name bttrm-eks-dev-1 --alias iam-bttrm-eks-root@bttrm-eks-dev-1 Updated context iam-bttrm-eks-root@bttrm-eks-dev-1 in /home/setevoy/.kube/config
[/simterm]
Проверяем доступ:
[simterm]
$ kk get po NAME READY STATUS RESTARTS AGE reloader-reloader-55448df76c-wdzqp 1/1 Running 0 28h
[/simterm]
(про reloader – см. Kubernetes: ConfigMap и Secrets — auto-reload данных в подах)
Окей – возвращаемся к хельму:
[simterm]
$ helm install deployment-testing-chart --dry-run deployment-testing-chart/ NAME: deployment-testing-chart LAST DEPLOYED: Thu May 7 20:06:44 2020 NAMESPACE: default STATUS: pending-install ...
[/simterm]
Найс!
Helm – deploy to a Namespace
А что с деплоем в кастомный неймспейс?
[simterm]
$ helm install deployment-testing-chart --dry-run deployment-testing-chart/ --namespace deployment-testing-chart-ns NAME: deployment-testing-chart LAST DEPLOYED: Thu May 7 20:08:00 2020 NAMESPACE: deployment-testing-chart-ns STATUS: pending-install ...
[/simterm]
Тоже вроде ОК.
Helm и неймспейс сам создаёт? Не верится…
Попробуем реальный запуск, без --dry-run
:
[simterm]
$ helm install deployment-testing-chart deployment-testing-chart/ --namespace deployment-testing-chart-ns Error: create: failed to create: namespaces "deployment-testing-chart-ns" not found
[/simterm]
Нет, не создаёт)
Окей – это хорошо
Гуглим – понятное дело, что до нас люди с таким уже сталкивались, например – https://github.com/helm/helm/issues/4456, и там комментарий https://github.com/helm/helm/issues/4456#issuecomment-412134651:
This is a known limitation of Helm. Helm will not do cross-namespace management, and we do not recommend setting namespace: directly in a template.
If you want to install resources in a namespace other than the default namespace, helm install –namespace=foo will install the resources in that namespace. If the namespace does not exist, it will create it.
Эээ? Так мы ж так и делали…
Читаем ниже:
For Helm 3, the namespace creation doesn’t happen during helm install any more, so you’ll have to create that namespace ahead of time as you do today.
Ага!
И ещё ниже – https://github.com/helm/helm/issues/4456#issuecomment-607927999:
For Helm 3, the namespace creation doesn’t happen during helm install any more, so you’ll have to create that namespace ahead of time as you do today.Right now, use kubectl create namespace foo prior to helm install. When Helm 3.2.0 is released, use helm install … –create-namespace.
А у нас какая?
[simterm]
$ helm version version.BuildInfo{Version:"v3.2.0", ...
[/simterm]
Ага!
Пробуем:
[simterm]
$ helm install deployment-testing-chart deployment-testing-chart/ --namespace deployment-testing-chart-ns --create-namespace NAME: deployment-testing-chart LAST DEPLOYED: Thu May 7 20:15:38 2020 NAMESPACE: deployment-testing-chart-ns STATUS: deployed REVISION: 1 ...
[/simterm]
Бимба!
Проверяем НСы:
[simterm]
$ kk get ns NAME STATUS AGE bttrm-apps-dev-1-ns Active 28h bttrm-workouts-dev-1-ns Active 28h default Active 28h deployment-testing-chart-ns Active 19s ...
[/simterm]
Няшка.
Сервисы:
[simterm]
$ kk -n deployment-testing-chart-ns get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE deployment-testing-chart ClusterIP 172.20.227.114 <none> 80/TCP 2m1s
[/simterm]
Окей, вроде работает – можно думать за Helm chart для самого приложения.
Хотя тут можно начать наоборот с Jenkins-джобы и скрипта, и использовать тестовый чарт, а потом создавать чарт реального приложения.
Но давайте сначала всё-таки приведём в рабочий вид приложение.
Helm – создание чарта
Сейчас у нас файлы для Kubernetes лежат в отдельном репозитории devops, а код приложения – в репозитории самого приложения.
Что надо сделать – это создать чарт в репозитории с приложением – в этом репозитории будет и Dockerfile
, там же будем хранить Chart.yaml
.
Скрипты для Jenkins (jekinsfiles) хранятся в ещё одном отдельном репозитории для общего доступа из различных, но однотипных джоб.
What about RBAC?
Кстати. Задумался я о том, как мы будем деплоить с Jenkins и о правах доступа.
Сам Jenkins использует свою EC2 Instance Profile, которая даёт ему полный доступ к кластерам. Ту же роль использую сейчас я при AssumeRole, которую мы настроили выше в ~/.aws/config
.
А что с девелоперами?
У них-то создаются отдельные RoleBinding
из определённых неймспейсов, в которые им разрешается доступ, а не глобальный рутовый доступ, как у меня.
Значит – эти RoleBinding надо включить в чарт, и создавать их в тех неймспейсах, которые будут создавать при деплое чарта из Jenkins в – напомню – том числе и кастомные неймспейсы.
Как быть? Делать RBAC сабчартом?
Вопрос… Да и для самого Helm, по-хорошему, надо создавать отдельный Kubernetes ServiceAccount при создании кластера.
Хотя – зачем нам выносить доступы юзеров в отдельный чарт или сабчарт?
Там всего-то создать одну RoleBinding в нужном неймпсейсе. А “глобальная” ClusterRole создаётся при провижене самого кластера из Ansible (потом надо будет переписать на Helm, и все RBAC-ресурсы через него деплоить).
Окей – значит так и сделаем.
Создание чарта
Поехали – клонируем репозиторий, создаём каталог, создаём чарт:
[simterm]
$ mkdir k8s $ cd k8s/ $ helm create bttrm-apps-backend Creating bttrm-apps-backend
[/simterm]
Удаляем шаблоны, копируем из старого репозитория:
[simterm]
$ cd bttrm-apps-backend $ rm -rf templates/* $ cp ~/Work/devops/projects/EKS/roles/eks/templates/bttrm-apps/* templates/
[/simterm]
Что у нас тут есть:
[simterm]
$ ls -1 templates/ bttrm-apps-deployment.yaml bttrm-apps-ns-ingress-hpa.yaml bttrm-apps-secrets.yaml
[/simterm]
Посмотрим файл деплоймента:
apiVersion: apps/v1 kind: Deployment metadata: name: bttrm-apps-EKS_ENV annotations: secret.reloader.stakater.com/reload: "bttrm-docker-secret" spec: replicas: DEPLOY_REPLICAS_NUM strategy: type: RollingUpdate revisionHistoryLimit: 10 selector: matchLabels: app: bttrm-apps-EKS_ENV template: metadata: labels: app: bttrm-apps-EKS_ENV spec: containers: - name: bttrm-apps-EKS_ENV image: bttrm/bttrm-apps:IMAGE_TAG env: - name: CLIENT_HOST value: CLIENT_HOST_VAl - name: DATABASE_HOST value: DATABASE_HOST_VAL ...
Собственно, как говорилось в начале – все КАПСы вырезаются в Jenkins с помощью sed
, и меняются на реальные значения, полученные из параметров джобы, а потом выполняется kubectl apply -f
.
Как-то так:
... dir ('eks') { git branch: "${DEVOPS_REPO_BRANCH}", url: "${DEVOPS_REPO_URL}", credentialsId: "jenkins-github" def templates_path = "projects/EKS/roles/eks/templates/bttrm-apps" // .env sed_env("$templates_path","bttrm-apps-deployment.yaml","BACKEND_HOST_VALUE","$BACKEND_HOST") sed_env("$templates_path","bttrm-apps-deployment.yaml","CLIENT_HOST_VAl","$CLIENT_HOST") sed_env("$templates_path","bttrm-apps-deployment.yaml","DEPLOY_REPLICAS_NUM","$DEPLOY_REPLICAS_NUM") ...
Ужас, правда?)
Для тестов было ОК, давайте приводить в порядок.
Сначала во всех файлах удаляем все metadata: namespace:
– неймспейс будет определяться самим Хельмом.
Удаляем файл values.yaml
, который сгененрировал Helm во время инициализации чарта, создаём новый файл, пустой.
Открываем в одном окне values.yaml
, в другом – файл деплоймента, начинаем обновлять:
kind: Deployment metadata: name: bttrm-apps-EKS_ENV
испольуем{{ .Chart.Name }}
spec: replicas: DEPLOY_REPLICAS_NUM
вот – тут уже пора вvalues.yaml
идти, добавляем в нёмreplicaCount: 2
а в деплойменте используемreplicas: {{ .Values.replicaCount }}
, см. пример нижеselector: matchLabels: app: bttrm-apps-EKS_ENV
меняем наapplication: {{ .Chart.Name }}
Сначала думал в имя деплоймента включить .Chart.Version
или .Chart.AppVersion
– но тут у Хельма всё оказалось через… Печаль какую-то, т.к. задать их значения во время helm install
нельзя – только во время package
, см. https://github.com/helm/helm/issues/3555 (с 2018 обсуждают!).
Ладно – оставим на совести разработчиков – жираф большой, ему виднее. Пока в имени оставлю {{ .Chart.Name }}
– по ходу дела посмотрим. Да и не факт, что использовать версии в имени хорошая идея.
По теме:
- 9 Best Practices and Examples for Working with Kubernetes Labels (2018 год, но интересно про лейблы и аннотации)
Пока в лейблах сделал так:
... selector: matchLabels: application: {{ .Chart.Name }} template: metadata: labels: application: {{ .Chart.Name }} version: {{ .Chart.Version }}-{{ .Chart.AppVersion }} managed-by: {{ .Release.Service }}-{{ .Release.Name }} ...
{{ Chart.version }}
и {{ .Chart.appVersion }}
будет задаваться в Jenkins, пока будем брать дефолтные значения из Chart.yaml
.
Далее, для выбора Docker-образа надо в values задать имя образа и тег.
Мы сейчас хостим образы в DockerHub, но потом думаю переезжать на AWS Elastic Container Registry, поэтому сразу пусть будет с возможностью поменять.
Заполняем values.yaml
– добавляем image
:
replicaCount: 2 image: repository: bttrm name: bttrm-apps tag: ""
Получится строка типа bttrm/bttrm-apps:TAG, а TAG думаю формировать при сборке образа из $BUILD_NUMBER
+ $GIT_COMMIT
, т.е. получим что-то вроде bttrm/bttrm-apps:123.67554433.
Дальше у нас идёт пачка переменных:
... env: - name: CLIENT_HOST value: CLIENT_HOST_VAL - name: DATABASE_HOST value: DATABASE_HOST_VAL - name: DB_USERNAME value: DB_USERNAME_VAL ...
Значения для плейсхолдеров типа CLIENT_HOST_VAL
в основном будут задаваться из параметров джобы Дженкинса, но добавляем их в values тоже. Где можно – задаём дефолтные значения, где нет – просто “”.
Пока, кстати, вообще все значения можно задать в values – до Jenkins-джобы ещё будем тестить локально.
Кавычки
А что там с кавычками, кстати?
Тоже мрак)
С одной стороны – документация по созданию чарта (тыц>>>) и по переменным (тыц>>>) говорит использовать quote
-функцию, которая будет “оборачивать” наши значения в кавычки.
При этом, если посмотреть файлы, сгенерированные при helm create
– то там нет ни quote
, ни кавычек – в большинстве случаев. Но не везде 🙂
Например, для:
... image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" ...
Кавычки есть. Почему? Потому что “составная строка” – конкатенируется из нескольких? Окей, допустим.
Но почему:
... containers: - name: {{ .Chart.Name }} ...
Без кавычек, если документация говорит их использовать? Потому что это данные из Chart.yaml
, а не values.yaml
?
В чём принципиальная разница?
Ну и сам YAML, как всегда – “радует”, читаем в той же документации по Values – Make Types Clear:
YAML’s type coercion rules are sometimes counterintuitive. For example, foo: false is not the same as foo: “false”. Large integers like foo: 12345678 will get converted to scientific notation in some cases.The easiest way to avoid type conversion errors is to be explicit about strings, and implicit about everything else. Or, in short, quote all strings.
Мрак!
Ладно – в values.yaml
стринги заключаем в кавычки, целочисленные – без кавычек.
В файле шаблонов… Я не знаю 🙂
Использовать quote
– тогда само значение в переменных будет с кавычками. Посмотрел, как приложение запущено сейчас – без кавычек:
[simterm]
$ kk -n bttrm-apps-stage-ns describe pod bttrm-apps-stage-7c5cfd9698-tkmjp | grep CLIENT_HOST CLIENT_HOST: https://test.example.com
[/simterm]
Значит пока без quote
, и без явного указания “” в шаблоне вообще.
(у нас тут Stage, который pre-Dev даже)
values.yaml
Получается такой файл:
replicaCount: 2 image: repository: "bttrm" name: "bttrm-apps" tag: "" # backend app config backendConfig: clientHost: "https://test.example.com" nodeEnv: "dev" host: "https://test.api.example.com" port: 3001 database: host: "stage.aurora.example" user: "dbuser" password: "dbpass" connection: "mysql" port: 3306
Passwords… Secrets…
Пароли пока хардкодим – потом будем передавать из Credentials parameter в Jenkins-джобе.
А ещё лучше – таки добавить helm secrets – но это потом (добавлено – см. Helm: helm-secrets — шифрование sensitive данных с AWS KMS и деплой из Jenkins)
Секреты
Так, секреты.
В деплойменте передаём секретами, секреты создаём из файла bttrm-apps-secrets.yaml
.
Сейчас он выглядит так же, как деплоймент – обрабатывается с sed
в старой джобе Jenkins:
--- apiVersion: v1 kind: Secret metadata: name: bttrm-app-secret type: Opaque stringData: backend-db-password: DB_PASSWORD_VAL ...
В Деплойменте маунтим из секретов:
... spec: containers: - name: {{ .Chart.Name }} image: {{ .Values.image.repository }}/{{ .Values.image.name }}:{{ .Values.image.tag }} env: ... - name: DB_PASSWORD valueFrom: secretKeyRef: name: bttrm-app-backend-secret key: backend-db-password ...
Обновляем секрет – передаём из values:
--- apiVersion: v1 kind: Secret metadata: name: bttrm-app-backend-secrets type: Opaque stringData: backend-db-password: {{ .Values.backendConfig.db.password }} backend-apple-cert-passphrase: {{ .Values.backendConfig.apple.certPassphrase }} backend-apple-sigin-key-id: {{ .Values.backendConfig.apple.signInKeyID }}
chart install
Ну и что – пробуем?
[simterm]
$ helm install --debug bttrm-apps-backend-release bttrm-apps-backend install.go:159: [debug] Original chart version: "" install.go:176: [debug] CHART PATH: /home/setevoy/Work/landing-backend/k8s/bttrm-apps-backend client.go:108: [debug] creating 6 resource(s) Error: Deployment in version "v1" cannot be handled as a Deployment: v1.Deployment.Spec: v1.DeploymentSpec.Template: v1.PodTemplateSpec.Spec: v1.PodSpec.Containers: []v1.Container: v1.Container.Env: []v1.EnvVar: v1.EnvVar.Value: ReadString: expects " or n, but found 3, error found in #10 byte of ...|,"value":3001} ...
[/simterm]
Ах ты ж…
Вот честно – я тут очень матерился.
Может я не догоняю чего-то, но ты же мне везде в документации говоришь, что Integer без кавычек кушаешь нормально?
Что ж ты теперь ругаешься на цифры?
Про то, как Go вообще ошибки выводит промолчим – в них часто сложно что-то понять.
Но тут в принципе видно значение, вызвавшее ошибку – 3001:
v1.PodSpec.Containers: []v1.Container: v1.Container.Env: []v1.EnvVar: v1.EnvVar.Value: ReadString: expects ” or n, but found 3, error found in #10 byte of …|,”value”:3001}
Непонятно, конечно, где именно – но так как порты в деплойменте встречаются не так часто – то просто добавляем кавычки для всех, меняем:
... - name: DB_PORT value: {{ .Values.backendConfig.port }} ...
На:
... - name: DB_PORT value: {{ .Values.backendConfig.db.port | quote }} ...
При этом в ports: - containerPort:
на кавычки он ругается.
А в livenessProbe: port:
при --dry-run
не ругается. А при install
– ругается.
Рукалицо. Ладно.
Деплоим ещё раз:
[simterm]
$ helm install --debug bttrm-apps-backend-release bttrm-apps-backend/ install.go:159: [debug] Original chart version: "" install.go:176: [debug] CHART PATH: /home/setevoy/Work/landing-backend/k8s/bttrm-apps-backend client.go:108: [debug] creating 6 resource(s) NAME: bttrm-apps-backend-release LAST DEPLOYED: Sun May 10 14:35:37 2020 NAMESPACE: default STATUS: deployed REVISION: 1 ...
[/simterm]
Йай!
Проверяем:
[simterm]
$ helm ls NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION bttrm-apps-backend-release default 1 2020-05-10 14:35:37.364401788 +0300 EEST deployed bttrm-apps-backend-0.1.0 1.16.0
[/simterm]
Ну – бимба!
Оно 100% не работает, но нам важен сам файкт, что оно задеплоилось:
[simterm]
$ kk get pod NAME READY STATUS RESTARTS AGE bttrm-apps-backend-c4d8f8c5d-98vd2 1/1 Running 0 54s bttrm-apps-backend-c4d8f8c5d-v6slt 1/1 Running 0 54s ...
[/simterm]
Хорошо.
Удаляем, передеплоим в нормальный НС:
[simterm]
$ helm uninstall bttrm-apps-backend-release release "bttrm-apps-backend-release" uninstalled
[/simterm]
Деплоим ещё раз, указываем --create-namesapce
:
[simterm]
$ helm install bttrm-apps-backend-release bttrm-apps-backend/ --namespace bttrm-apps-dev-1-ns --create-namespace NAME: bttrm-apps-backend-release LAST DEPLOYED: Sun May 10 14:45:23 2020 NAMESPACE: bttrm-apps-dev-1-ns STATUS: deployed REVISION: 1 TEST SUITE: None
[/simterm]
Проверяем:
[simterm]
$ kk -n bttrm-apps-dev-1-ns get po NAME READY STATUS RESTARTS AGE bttrm-apps-backend-6c54d5c676-5j6mc 0/1 Pending 0 47s bttrm-apps-backend-6c54d5c676-vchvh 0/1 Pending 0 47s
[/simterm]
Супер, но почему Pending?
Что-то с реквестами, наверно?
Глянем events
:
[simterm]
$ kk -n bttrm-apps-dev-1-ns get event LAST SEEN TYPE REASON OBJECT MESSAGE 7s Warning FailedScheduling pod/bttrm-apps-backend-6c54d5c676-5j6mc 0/8 nodes are available: 8 Insufficient cpu. 10s Normal NotTriggerScaleUp pod/bttrm-apps-backend-6c54d5c676-5j6mc pod didn't trigger scale-up (it wouldn't fit if a new node is added): 4 Insufficient cpu
[/simterm]
А почему?
В values.yaml
500 CPU requests, WorkerNodes у нас на ЕС2 t3.medium – у каждого 2000 CPU юнитов…
Проверим реквесты:
[simterm]
$ kk -n bttrm-apps-dev-1-ns describe pod bttrm-apps-backend-6c54d5c676-5j6mc Name: bttrm-apps-backend-6c54d5c676-5j6mc Namespace: bttrm-apps-dev-1-ns ... Host Port: 0/TCP Requests: cpu: 500 ...
[/simterm]
Да, вроде всё верно.
Или реквесты криво описал…
Что там… Как они задаются вообще, где дока?
Вот тут вроде было толково – https://cloud.google.com/blog/products/gcp/kubernetes-best-practices-resource-requests-and-limits.
Хм…
Кавычки и m?
Редактируем values – вместо 500 указываем “500m”:
... resources: requests: cpu: "500m" ...
И добавим quote
в деплоймент:
... resources: requests: cpu: {{ .Values.resources.requests.cpu | quote }} ...
Пробуем:
[simterm]
$ helm upgrade bttrm-apps-backend-release bttrm-apps-backend/ --namespace bttrm-apps-dev-1-ns Release "bttrm-apps-backend-release" has been upgraded. Happy Helming! NAME: bttrm-apps-backend-release LAST DEPLOYED: Sun May 10 14:54:57 2020 NAMESPACE: bttrm-apps-dev-1-ns STATUS: deployed REVISION: 2 TEST SUITE: None
[/simterm]
Евенты:
[simterm]
$ kk -n bttrm-apps-dev-1-ns get event LAST SEEN TYPE REASON OBJECT MESSAGE 47s Normal Scheduled pod/bttrm-apps-backend-69d74849df-nvtt2 Successfully assigned bttrm-apps-dev-1-ns/bttrm-apps-backend-69d74849df-nvtt2 to ip-10-3-33-123.us-east-2.compute.internal 15s Warning FailedMount pod/bttrm-apps-backend-69d74849df-nvtt2 MountVolume.SetUp failed for volume "apple-keys" : secret "apple-keys" not found 15s Warning FailedMount pod/bttrm-apps-backend-69d74849df-nvtt2 MountVolume.SetUp failed for volume "apple-certificates" : secret "apple-certificates" not found 47s Normal SuccessfulCreate replicaset/bttrm-apps-backend-69d74849df Created pod: bttrm-apps-backend-69d74849df-nvtt2 ...
[/simterm]
Ага!
Всё создалось.
Теперь поды не стартуют, потому что не файлов сертификатов для Apple – но это уже детали, сейчас их создадим.
Helm secrets from files
Добавим файлы сертификата – в Jenkins они будут маунтиться из Credentials типа Secret file, тут добавляем в текущий репозиторий:
[simterm]
$ ll bttrm-apps-be/secrets/ total 12 -rw-r--r-- 1 setevoy setevoy 258 May 10 17:32 AppleAuth.key.p8 -rw-r--r-- 1 setevoy setevoy 3088 May 10 17:27 ApplePay.crt.pem -rw-r--r-- 1 setevoy setevoy 2006 May 10 17:28 ApplePay.key.pem
[/simterm]
Добавляем в ../.gitignore
репозитория и в .helmignore
чарта:
... k8s/bttrm-apps-backend/secrets/
Обновляем bttrm-apps-secrets.yaml
– используем Files
, см https://helm.sh/docs/chart_template_guide/accessing_files/:
... --- apiVersion: v1 kind: Secret metadata: name: apple-certificates type: Opaque data: ApplePay.crt.pem: {{ .Files.Get "secrets/ApplePay.crt.pem" | b64enc }} ApplePay.key.pem: {{ .Files.Get "secrets/ApplePay.key.pem" | b64enc }} --- apiVersion: v1 kind: Secret metadata: name: apple-key type: Opaque data: AppleAuth.key.p8: {{ .Files.Get "secrets/AppleAuth.key.p8" | b64enc }}
Запускаем:
[simterm]
$ helm upgrade bttrm-apps-be-release bttrm-apps-be/ --namespace bttrm-apps-dev-1-ns Release "bttrm-apps-be-release" has been upgraded. Happy Helming! NAME: bttrm-apps-be-release LAST DEPLOYED: Sun May 10 17:36:47 2020 NAMESPACE: bttrm-apps-dev-1-ns STATUS: deployed REVISION: 4 TEST SUITE: None
[/simterm]
Проверяем секреты:
[simterm]
$ kk -n bttrm-apps-dev-1-ns get secret NAME TYPE DATA AGE apple-certificates Opaque 2 37s apple-key Opaque 1 37s
[/simterm]
Но поды серты не увидели, и не запустилиись, так и будут висеть в FailedMount.
Пересоздаём их – вызываем upgrade
с --recreate-pods
:
[simterm]
$ helm upgrade bttrm-apps-be-release bttrm-apps-be/ --namespace bttrm-apps-dev-1-ns --recreate-pods Flag --recreate-pods has been deprecated, functionality will no longer be updated. Consult the documentation for other methods to recreate pods Release "bttrm-apps-be-release" has been upgraded. Happy Helming! NAME: bttrm-apps-be-release LAST DEPLOYED: Sun May 10 17:39:39 2020 NAMESPACE: bttrm-apps-dev-1-ns STATUS: deployed REVISION: 5 TEST SUITE: None
[/simterm]
Проверяем:
[simterm]
$ helm -n bttrm-apps-dev-1-ns ls NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION bttrm-apps-be-release bttrm-apps-dev-1-ns 2 2020-05-10 17:44:56.505478002 +0300 EEST deployed bttrm-apps-be-0.1.0 1.16.0 $ kk -n bttrm-apps-dev-1-ns get pod NAME READY STATUS RESTARTS AGE bttrm-apps-be-dp-6f86d5c7d7-k4rjm 1/1 Running 0 2m bttrm-apps-be-dp-6f86d5c7d7-ztcvp 1/1 Running 0 83s
[/simterm]
Тут готово.
.dockerconfigjson
Сейчас в секретах файла bttrm-apps-secrets.yaml
значение для .dockerconfigjson
задан хардкодом:
--- apiVersion: v1 kind: Secret metadata: creationTimestamp: null name: bttrm-docker-secret type: kubernetes.io/dockerconfigjson data: .dockerconfigjson: eyJhdXRo[...] ...
Находим пример в документации – Creating Image Pull Secrets.
Обновляем values.yaml
– в блок image
добавляем registry, username и password, пароль позже будем передавать из Jenkins:
... image: registry: "docker.io" username: "username" password: "pass" repository: "project" name: "bttrm-apps" tag: "120" ...
Создаём templates/_helpers.tpl
, см. Nested template definitions.
Копируем функцию из примера в Creating Image Pull Secrets, меняем .Values.imageCredentials
на наш .Values.image
:
{{- define "imagePullSecret" }} {{- printf "{\"auths\": {\"%s\": {\"auth\": \"%s\"}}}" .Values.image.registry (printf "%s:%s" .Values.image.username .Values.image.password | b64enc) | b64enc }} {{- end }}
Обновляем секреты – вместо хардкод-строки в base64
вызываем этот самый {{ template "imagePullSecret" . }}
:
--- apiVersion: v1 kind: Secret metadata: creationTimestamp: null name: bttrm-docker-secret type: kubernetes.io/dockerconfigjson data: .dockerconfigjson: {{ template "imagePullSecret" . }} ...
Передеплоиваем для проверки, и если всё ОК – то можно приступать в Jenkins.
Jenkins
Поехали к сиаю.
Что нам надо?
Сначала давай подумаем про шаги, которые надо выполнить в джобе:
- склонировать репозиторий приложения с helm-чартом, кодом приложения и докерфайлом
- билд образа – тегаем Jenkins
$BUILD_NUMBER
+$GIT_COMMIT
docker push
собранного образа, пока в DockerHub- упаковка релиза + задать номера релиза и AppVersion, AppVersion == Docker Tag. т.е.
$BUILD_NUMBER
+$GIT_COMMIT
- позже – релиз в Гитхаб
- сгенерить кубконфиг
- выполнить
helm install
Версинирование
Тут, конечно, по желанию каждого.
Мне пока в голову пришла такая схема:
- chart release number == Jenkins
$BUILD_NUMBER
джобы - AppVersion ==
$BUILD_NUMBER
+$GIT_COMMIT
- Docker image tag ==
$BUILD_NUMBER
+$GIT_COMMIT
Неймспейсы опять (снова?)
Что там с НС-ами было?
Давайте вспоминать – день четвёртый, на самом деле.
Хочется давать на выбор три варианта:
- дефолтный неймпейс – APP_NAME + CLUSTER_ENV + “NS”, т.е. поулчим bttrm-apps-dev-1ns
- неймспейс с именем бранча – APP_NAME + CLUSTER_ENV + GIT_BRANCH_NAME + “NS”, т.е. bttrm-apps-dev-1-WLIOS-5848-Projects-deployments
- кастомный неймспейс, задаётся юзером
Есть сомнения по второму пункту – bttrm-apps-dev-1-WLIOS-5848-Projects-deployments – не слишком ли длинно?
Загуглим “kubernetes namespace best practices”, но лучшее, что нашлось – это вот эта запись в блоге, в которой говорится:
An easy to grasp anti-pattern for Kubernetes namespaces is versioning. You should not use Namespaces as a way to disambiguate versions of your Kubernetes resources
Ну и ладно – версии в НС мы включать и не думали.
Но подсказали в Kubernetes Slack:
Хорошо – раз лимит в 63 символа – мы можем в дженкисфайле добавить хендлер, который будет проверять имя неймспейса, переданного из Jenkins-парамтеров, и при необходимости обрезать его.
Про удаление НС-ов – потом, потом…
Тестирование
https://dzone.com/articles/easily-automate-your-cicd-pipeline-with-jenkins-he
Хорошая идея запускать тесты во время билда/деплоя – сначала какие-то юнит-тесты после сборки Docker-образа, потом – smoke-тесты после деплоя.
Но добавим потом – достаточно будет дописать функции на их вызов, пока задача запустить билд-деплой вообще.
Поехали?
Jenkins job
Создаём новую джобу, тип пайплайн:
Будем использовать Jenkins Scripted Pipelines.
Теперь поехали по шагам, которые напланировали выше.
Jenkins job stages
Init stage
Начнём с такого себе init-стейджа, который будет клонировать репозиторий с кодом, настраивать kubectl
, и определять общие параметры и переменные.
Что нам в нём надо выполнить?
- клонируем репозиторий
- генерируем kubeconfig
- вызываем
kubctl cluster-info
– для проверки kubeconfig - вызываем
helm version
– для проверки Helm вообще - задаём параметры:
- release version –
$BUILD_NUMBER
, получаем из самого Jenkins - Docker Tag: будет
$BUILD_NUMBER
+$GIT_COMMIT
, приходит в джобу из Git plugin после чекаута - AppVersion –
$BUILD_NUMBER
+$GIT_COMMIT
, приходит в джобу из Git plugin после чекаута
- release version –
Получается такой код:
node { docker.image('bttrm/kubectl-aws:4.0').inside('-v /var/run/docker.sock:/var/run/docker.sock') { stage('Init') { gitenv = git branch: "master", credentialsId: 'jenkins-github', url: '[email protected]:example-org/bttrm-apps-backend.git' GIT_COMMIT_SHORT = gitenv.GIT_COMMIT.take(8) RELEASE_VERSION = "${BUILD_NUMBER}" DOCKER_TAG = "${BUILD_NUMBER}.${GIT_COMMIT_SHORT}" APP_VERSION = "${BUILD_NUMBER}.${GIT_COMMIT_SHORT}" echo "DOCKER_TAG == ${DOCKER_TAG}" echo "APP_VERSION == ${APP_VERSION}" echo "RELEASE_VERSION == ${RELEASE_VERSION}" sh "aws eks update-kubeconfig --region us-east-2 --name bttrm-eks-dev-1" sh "kubectl cluster-info" sh "helm version" sh "helm -n bttrm-apps-dev-1-ns ls " } } }
Весь билд будет выполняться нашим кастомным Docker-образом kubectl-aws, собирается из такого Dockerfile:
FROM bitnami/minideb:stretch RUN apt update && install_packages ca-certificates wget RUN install_packages curl python-pip python-setuptools jq git RUN curl -LO https://storage.googleapis.com/kubernetes-release/release/`curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt`/bin/linux/amd64/kubectl RUN chmod +x ./kubectl RUN mv ./kubectl /usr/local/bin/kubectl WORKDIR /tmp RUN curl --location "https://github.com/weaveworks/eksctl/releases/latest/download/eksctl_$(uname -s)_amd64.tar.gz" | tar xz -C /tmp RUN mv /tmp/eksctl /usr/local/bin RUN pip install ansible boto3 awscli WORKDIR /tmp RUN curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 RUN /bin/bash get_helm.sh USER root
Пока все значения тпиа Github URL и имени бранча в скрипте билда хардкодим – потом вынесем в параметры джобы.
Запускаем, проверяем – всё работает.
Пошли дальше.
Docker build
Далее нам надо собрать Docker-образ, и запушить его в репозиторий.
Добавлять ли тег latest? Вопрос.
Наверно нет – билдов может быть много, из разных бранчей – перезаписывать каждый раз latest смысла нет, тем более деплоится в Dev окружение всё будет автоматом при создании пул-реквестов, см. Jenkins: Github Pull-Request Builder плагин.
Значит – делаем только docker build
с тегом из переменной $DOCKER_TAG
.
Создаём данные доступа для DockerHub:
Добавляем новый стейдж “Docker build”, в нём с помощью Docker-плагина для Jenkins собираем образ, для логина в DockerHub через credentialsId
передаём созданные выше данные:
node { docker.image('bttrm/kubectl-aws:4.0').inside('-v /var/run/docker.sock:/var/run/docker.sock') { stage('Init') { ... } stage("Docker build") { withDockerRegistry(registry: [credentialsId: 'bttrm-docker-hub']){ docker.build("bttrm/bttrm-apps:${DOCKER_TAG}").push() } } } }
Запускаем, проверяем, всё работает?
Хорошо, что дальше?
helm package
Дальше генерируем пакет. Пока надо только для того, что бы можно было задать release verion и app-version. Позже хочется добавить их пуш куда-то в репозиторий – или Github, или S3-backended.
... stage("Helm package") { sh "helm package k8s/bttrm-apps-be --version ${RELEASE_VERSION} --app-version ${APP_VERSION}" } ...
Запускаем – докер-образ собрался, локальный пакет хельм-чарта собрался – всё хорошо:
[simterm]
... 4d1ab3827f6b: Layer already exists 5.872a4dc3: digest: sha256:558e3b0ec0a7c5fc6302adca8321de2a3ee75656202930dcb979960d49273c0b size: 4089 [Pipeline] } [Pipeline] // withDockerRegistry [Pipeline] } [Pipeline] // stage [Pipeline] stage [Pipeline] { (Helm package) [Pipeline] sh + helm package k8s/bttrm-apps-backend --version 5 --app-version 5.5.872a4dc3 Successfully packaged chart and saved it to: /var/lib/jenkins/workspace/DO_EKS/Deployments/bttrm-apps/bttrm-apps-TEST/bttrm-apps-backend-5.tgz [Pipeline] } [Pipeline] // stage [Pipeline] } $ docker stop --time=1 023b7970d36e20b85912062b35bc7aa4136af7807b975a373c655c0cf38a6010 $ docker rm -f 023b7970d36e20b85912062b35bc7aa4136af7807b975a373c655c0cf38a6010 [Pipeline] // withDockerContainer [Pipeline] } [Pipeline] // node [Pipeline] End of Pipeline Finished: SUCCESS
[/simterm]
Дальше – собственно деплой приложения.
helm install
Ну и деплоим – добавляем создание Namespace.
Сам неймспейс пока ставим дефолтный.
Можно добавить --atomic
, что бы удалять зафейлившиеся деплои:
... stage("Helm install") { sh "helm install --atomic bttrm-apps-be bttrm-apps-be-${RELEASE_VERSION}.tgz" }
Запускаем, и:
[simterm]
... [Pipeline] { (Helm install) [Pipeline] sh + helm install --atomic bttrm-apps-backend bttrm-apps-backend-6.tgz Error: unable to build kubernetes objects from release manifest: error validating "": error validating data: [unknown object type "nil" in Secret.data.ApplePay.crt.pem, unknown object type "nil" in Secret.data.ApplePay.key.pem] [Pipeline] } [Pipeline] // stage [Pipeline] } $ docker stop --time=1 fa1e59d52af1c622d5122ddc8db25b72e5e2afff82a211f077fd8c3a8d1a48bb $ docker rm -f fa1e59d52af1c622d5122ddc8db25b72e5e2afff82a211f077fd8c3a8d1a48bb [Pipeline] // withDockerContainer [Pipeline] } [Pipeline] // node [Pipeline] End of Pipeline ERROR: script returned exit code 1 Finished: FAILURE
[/simterm]
Ага, ну да – секреты в репозиторий мы не добавляли, они заигнорены через .gitignore
, и должны мапится из Secret file самой джобы.
Собственно – пора добавить и параметры.
Job parameters
Посмотрим на весь скрипт, который у нас уже получился:
node { docker.image('bttrm/kubectl-aws:4.0').inside('-v /var/run/docker.sock:/var/run/docker.sock') { stage('Init') { gitenv = git branch: "WLIOS-5848-Projects-deployments", credentialsId: 'jenkins-github', url: '[email protected]:example-org/bttrm-apps-backend.git' GIT_COMMIT_SHORT = gitenv.GIT_COMMIT.take(8) RELEASE_VERSION = "${BUILD_NUMBER}" DOCKER_TAG = "${BUILD_NUMBER}.${GIT_COMMIT_SHORT}" APP_VERSION = "${BUILD_NUMBER}.${DOCKER_TAG}" echo "DOCKER_TAG == ${DOCKER_TAG}" echo "APP_VERSION == ${APP_VERSION}" echo "RELEASE_VERSION == ${RELEASE_VERSION}" sh "aws eks update-kubeconfig --region us-east-2 --name bttrm-eks-dev-1" sh "kubectl cluster-info" sh "helm version" sh "helm -n bttrm-apps-dev-1-ns ls " } stage("Docker build") { withDockerRegistry(registry: [credentialsId: 'bttrm-docker-hub']){ docker.build("bttrm/bttrm-apps:${DOCKER_TAG}").push() } } stage("Helm package") { sh "helm package k8s/bttrm-apps-backend --version ${RELEASE_VERSION} --app-version ${APP_VERSION}" } stage("Helm install") { sh "helm install --atomic bttrm-apps-backend bttrm-apps-backend-${RELEASE_VERSION}.tgz" } } }
Что из этого надо вынести в параметры, и в какие?
Собственно – почти всё надо выносить в параметры джобы, потому что это же скрипт будет использоваться для деплоя аналогичных проектов.
Пока сделаем:
- репозиторий проекта – String parameter
- бранч проекта – String parameter
- регион для
aws eks update-kubeconfig
– можно задать в скрипте, вряд ли кто-то будет деплоить в кластера в других регионах (хотя Jenkins-джоба для создания кластеров в любом регионе есть, см. AWS Elastic Kubernetes Service: — автоматизация создания кластера, часть 2 — Ansible, eksctl) - имя кластера для создания kubeconfig – в параметры джобы, String parameter
- namespace – пока сделаем один, дефолтный – выносим в параметр джобы, Choice parameter
- имя чарта – String parameter
Добавляем секретные файлы.
Выбираем тип Credentials parameter, тип Secret file:
Либо, если ключи для Dev/Stage/Prod одинаковые – то не выносить их в параметры, а сразу использовать в скрипте.
Теперь нам надо замапить эти ключи в контейнер во время билда, см. Jenkins: Credentials Binding Plugin и использование нескольких Secret file в Jenkins pipeline.
Получается такой скрипт:
node { docker.image('bttrm/kubectl-aws:4.0').inside('-v /var/run/docker.sock:/var/run/docker.sock') { stage('Init') { gitenv = git branch: '${APP_REPO_BRANCH}', credentialsId: 'jenkins-github', url: '${APP_REPO_URL}' GIT_COMMIT_SHORT = gitenv.GIT_COMMIT.take(8) RELEASE_VERSION = "${BUILD_NUMBER}" DOCKER_TAG = "${BUILD_NUMBER}.${GIT_COMMIT_SHORT}" APP_VERSION = "${BUILD_NUMBER}.${DOCKER_TAG}" AWS_EKS_REGION = "us-east-2" echo "DOCKER_TAG == ${DOCKER_TAG}" 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 " } stage("Docker build") { /* withDockerRegistry(registry: [credentialsId: 'bttrm-docker-hub']){ docker.build("bttrm/bttrm-apps:${DOCKER_TAG}").push() } */ echo "Docker build" } stage("Helm package") { dir("k8s") { sh "helm package ${APP_CHART_NAME} --version ${RELEASE_VERSION} --app-version ${APP_VERSION}" } } stage("Helm install") { dir("k8s") { withCredentials([ file(credentialsId: 'bttrm-apps.applepay.crt', variable: 'APPLEPAY_CERT'), file(credentialsId: 'bttrm-apps.applepay.key', variable: 'APPLEPAY_KEY'), file(credentialsId: 'bttrm-apps.appleauth.key', variable: 'APPLEAUTH_KEY'), ]) { sh "test -d bttrm-apps-backend/secrets || mkdir bttrm-apps-backend/secrets" sh "cp \$APPLEPAY_CERT bttrm-apps-backend/secrets/ApplePay.crt.pem" sh "cp \$APPLEPAY_KEY bttrm-apps-backend/secrets/ApplePay.key.pem" sh "cp \$APPLEAUTH_KEY bttrm-apps-backend/secrets/AppleAuth.key.p8" sh "helm install --namespace ${AWS_EKS_NAMESPACE} --create-namespace --atomic ${APP_CHART_NAME} ${APP_CHART_NAME}-${RELEASE_VERSION}.tgz" } } } } }
Стейдж сборки докер-образа пока комментируем, что бы не захламлять репозиторий.
Тут в Helm install мы:
- переходим в каталог k8s репозитория
- проверяем наличие каталога
bttrm-apps-backend/secrets
, если его нет – создаём - из Jenkins Credentials создаём три файла ключей
- и выполняем установку чарта
Параметры при запуске выглядят так:
Запускаем, проверяем:
Супер!
“It works!” (c)
Helm: “Error: cannot re-use a name that is still in use”
Но – если мы запустим билд повторно – он уже сфейлится с ошибкой “Error: cannot re-use a name that is still in use“:
Ожидаемо – раз уже есть задплоенный резил с таким именем – Helm не будет его перезаписывать.
Тут достаточно просто заменить install на helm upgrade --install
:
В целом-то это всё…
Что осталось ещё?
- добавить lint шаблонов
- вырезать пароли из values.yaml, и прикрутить helm secrets (добавлено – см. Helm: helm-secrets — шифрование sensitive данных с AWS KMS и деплой из Jenkins)
- добавить создание неймспейса по имени бранча и кастомный НС
- проверка длины неймспейсов
Но пока, думаю, хватит.
helm upgrade – resource cannot be imported into the current release
P.S. Когда добавлял RBAC-роль – то при апдейте получил ошибку:
[simterm]
+ helm upgrade --install --namespace bttrm-apps-dev-1-ns --create-namespace --atomic bttrm-apps-backend bttrm-apps-backend-17.tgz Error: UPGRADE FAILED: rendered manifests contain a resource that already exists. Unable to continue with update: RoleBinding "rbac-bttrm-web-apps-ro-role-binding" in namespace "bttrm-apps-dev-1-ns" exists and cannot be imported into the current release: invalid ownership metadata; label validation error: missing key "app.kubernetes.io/managed-by": must be set to "Helm"; annotation validation error: missing key "meta.helm.sh/release-name": must be set to "bttrm-apps-backend"; annotation validation error: missing key "meta.helm.sh/release-namespace": must be set to "bttrm-apps-dev-1-ns"
[/simterm]
Собственно, это к тому, что Helm задаёт аннотации и лейблы сам – но есть смысл добавлять их в шаблоны манифестов:
[simterm]
$ kk -n bttrm-apps-dev-1-ns get rolebinding rbac-bttrm-web-apps-ro-role-binding -o yaml apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: annotations: meta.helm.sh/release-name: bttrm-apps-backend meta.helm.sh/release-namespace: bttrm-apps-dev-1-ns creationTimestamp: "2020-05-14T16:02:42Z" labels: app.kubernetes.io/managed-by: Helm ...
[/simterm]
Такие дела.
Ссылки по теме
- Chart Development Tips and Tricks
- 9 Best Practices and Examples for Working with Kubernetes Labels
- HELM Best practices
- Create, Install, Upgrade, and Rollback a Helm Chart (Part 2)
- Helm from basics to advanced
- Helm from basics to advancedHelm Charts and Template Basics – Part 2