Архив метки: Devops

Helm: пошаговое создание чарта и деплоймента из Jenkins



Сам Хельм в общих чертах рассмотрели в посте 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 – но это, может быть, потом. В целом – реализуемо, и даже есть helm/chart-releaser.




Jenkins build steps




Дальше.




Билд Docker-образов – и билд/деплой самих чартов – делать одной джобой – или разными?




Сейчас у нас образы собираются в одной джобе, после чего пушатся в DockerHub, после чего девелопер должен сходить в джобу деплоймента, и при запуске в параметрах указать тег (номер билда) + дополнительные параметры:





Это, конечно, ни разу неудобно, поэтому переделаем.




Итак, будет один пайплайн, в котором нам надо:




  1. чекаут кода проекта
  2. линт чарта
  3. сгенерить kubeconfig
  4. helm install example-chart --dry-run --debug example-chart/
  5. helm install example-chart example-chart/
  6. тест деплоя – 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.








Что дальше?




Дальше локально сделаем:




  1. создадим чарт для приложения – используем реальное, но отбранчуемся, что бы не мешать девелоперам.
  2. настроим kubectl на новый Dev-кластер
  3. выполним 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 — обзор файла, добавление кластера, пользователя и контекста:




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




Создаём тестовый чарт:




cd /tmp/
helm create deployment-testing-chart
Creating deployment-testing-chart




Helm: the server has asked for the client to provide credentials




Пробуем залить в default namespace:




helm install deployment-testing-chart --dry-run deployment-testing-chart/
Error: Kubernetes cluster unreachable: the server has asked for the client to provide credentials




WTF?




Проверяем контекст:




kk config current-context
arseniy@bttrm-eks-dev-1




(kk – алиас для kubectl в ~/.bashrcalias kk="kubectl")




Всё верно…




А доступ есть?




kk get pod
error: You must be logged in to the server (Unauthorized)




Нет.




В автоматизации создания кластера не добавлен мой 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




Удаляем созданный контекст:




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




Генерим новый:




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




Проверяем доступ:




kk get po
NAME                                 READY   STATUS    RESTARTS   AGE
reloader-reloader-55448df76c-wdzqp   1/1     Running   0          28h




(про reloader – см. Kubernetes: ConfigMap и Secrets — auto-reload данных в подах)




Окей – возвращаемся к хельму:




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
...




Найс!




Helm – deploy to  a Namespace




А что с деплоем в кастомный неймспейс?




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
...




Тоже вроде ОК.




Helm и неймспейс сам создаёт? Не верится…




Попробуем реальный запуск, без --dry-run:




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




Нет, не создаёт)




Окей – это хорошо




Гуглим – понятное дело, что до нас люди с таким уже сталкивались, например – 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.




А у нас какая?




helm version
version.BuildInfo{Version:"v3.2.0", ...




Ага!




Пробуем:




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
...




Бимба!




Проверяем НСы:




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
...




Няшка.




Сервисы:




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




Окей, вроде работает – можно думать за 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-ресурсы через него деплоить).




Окей – значит так и сделаем.




Создание чарта




Поехали – клонируем репозиторий, создаём каталог, создаём чарт:




mkdir k8s
cd k8s/
helm create bttrm-apps-backend
Creating bttrm-apps-backend




Удаляем шаблоны, копируем из старого репозитория:




cd bttrm-apps-backend
rm -rf templates/*
cp ~/Work/devops/projects/EKS/roles/eks/templates/bttrm-apps/* templates/




Что у нас тут есть:




ls -1 templates/
bttrm-apps-deployment.yaml
bttrm-apps-ns-ingress-hpa.yaml
bttrm-apps-secrets.yaml




Посмотрим файл деплоймента:




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 }} – по ходу дела посмотрим. Да и не факт, что использовать версии в имени хорошая идея.




По теме:







Пока в лейблах сделал так:




...
  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 – тогда само значение в переменных будет с кавычками. Посмотрел, как приложение запущено сейчас – без кавычек:




kk -n bttrm-apps-stage-ns describe pod bttrm-apps-stage-7c5cfd9698-tkmjp | grep CLIENT_HOST
CLIENT_HOST:                          https://test.example.com




Значит пока без 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




Ну и что – пробуем?




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}




Ах ты ж…




Вот честно – я тут очень матерился.




Может я не догоняю чего-то, но ты же мне везде в документации говоришь, что 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 – ругается.




Рукалицо. Ладно.




Деплоим ещё раз:




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
...




Йай!




Проверяем:




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




Ну – бимба!




Оно 100% не работает, но нам важен сам файкт, что оно задеплоилось:




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
...




Хорошо.




Удаляем, передеплоим в нормальный НС:




helm uninstall bttrm-apps-backend-release
release "bttrm-apps-backend-release" uninstalled




Деплоим ещё раз, указываем --create-namesapce:




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




Проверяем:




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




Супер, но почему Pending?




Что-то с реквестами, наверно?




Глянем events:




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




А почему?




В values.yaml 500 CPU requests, WorkerNodes у нас на ЕС2 t3.medium – у каждого 2000 CPU юнитов…




Проверим реквесты:




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
...




Да, вроде всё верно.




Или реквесты криво описал…




Что там… Как они задаются вообще, где дока?




Вот тут вроде было толково – 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 }} 

...




Пробуем:




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




Евенты:




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

...




Ага!




Всё создалось.




Теперь поды не стартуют, потому что не файлов сертификатов для Apple – но это уже детали, сейчас их создадим.




Helm secrets from files




Добавим файлы сертификата – в Jenkins они будут маунтиться из Credentials типа Secret file, тут добавляем в текущий репозиторий:




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




Добавляем в ../.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 }}




Запускаем:




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




Проверяем секреты:




kk -n bttrm-apps-dev-1-ns get secret

NAME                                          TYPE                                  DATA   AGE

apple-certificates                            Opaque                                2      37s

apple-key                                     Opaque                                1      37s




Но поды серты не увидели, и не запустилиись, так и будут висеть в FailedMount.




Пересоздаём их – вызываем upgrade с --recreate-pods:




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




Проверяем:




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




Тут готово.




.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 добавляем registryusername и 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




Поехали к сиаю.




Что нам надо?




Сначала давай подумаем про шаги, которые надо выполнить в джобе:




  1. склонировать репозиторий приложения с helm-чартом, кодом приложения и докерфайлом
  2. билд образа – тегаем Jenkins $BUILD_NUMBER + $GIT_COMMIT
  3. docker push собранного образа, пока в DockerHub
  4. упаковка релиза + задать номера релиза и AppVersion, AppVersion == Docker Tag. т.е. $BUILD_NUMBER + $GIT_COMMIT
    1. позже – релиз в Гитхаб
  5. сгенерить кубконфиг
  6. выполнить helm install




Версинирование




Тут, конечно, по желанию каждого.




Мне пока в голову пришла такая схема:




  • chart release number == Jenkins $BUILD_NUMBER джобы
  • AppVersion == $BUILD_NUMBER + $GIT_COMMIT
  • Docker image tag == $BUILD_NUMBER + $GIT_COMMIT




Неймспейсы опять (снова?)




Что там с НС-ами было?




Давайте вспоминать – день четвёртый, на самом деле.




Хочется давать на выбор три варианта:




  1. дефолтный неймпейс – APP_NAME + CLUSTER_ENV + “NS”, т.е. поулчим bttrm-apps-dev-1ns
  2. неймспейс с именем бранча – APP_NAME + CLUSTER_ENV + GIT_BRANCH_NAME + “NS”, т.е. bttrm-apps-dev-1-WLIOS-5848-Projects-deployments
  3. кастомный неймспейс, задаётся юзером




Есть сомнения по второму пункту – 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:




Alan J Castonguay  3 days ago




Namespace names are DNS1123 Labels: maximum 63 characters

You may want to populate generateName with a prefix, and let kubernetes make a unique suffix.




Хорошо – раз лимит в 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 после чекаута




Получается такой код:




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: 'git@github.com: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}"

        }

...




Запускаем – докер-образ собрался, локальный пакет хельм-чарта собрался – всё хорошо:




...

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




Дальше – собственно деплой приложения.




helm install




Ну и деплоим – добавляем создание Namespace.




Сам неймспейс пока ставим дефолтный.




Можно добавить --atomic, что бы удалять зафейлившиеся деплои:




...

        stage("Helm install") {

            

            sh "helm install --atomic bttrm-apps-be bttrm-apps-be-${RELEASE_VERSION}.tgz"

        }




Запускаем, и:




...

[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




Ага, ну да – секреты в репозиторий мы не добавляли, они заигнорены через .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: 'git@github.com: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 мы:




  1. переходим в каталог k8s репозитория
  2. проверяем наличие каталога bttrm-apps-backend/secrets, если его нет – создаём
  3. из Jenkins Credentials создаём три файла ключей
  4. и выполняем установку чарта




Параметры при запуске выглядят так:





Запускаем, проверяем:





Супер!




“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:





В целом-то это всё…




Что осталось ещё?







Но пока, думаю, хватит.




helm upgrade – resource cannot be imported into the current release




P.S. Когда добавлял RBAC-роль – то при апдейте получил ошибку:




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"




Собственно, это к тому, что Helm задаёт аннотации и лейблы сам – но есть смысл добавлять их в шаблоны манифестов:




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

...




Такие дела.




Источник: https://rtfm.co.ua/helm-poshagovoe-sozdanie-charta-i-deplojmenta-iz-jenkins/



2022-08-08T20:57:39
DevOps

Как диагностировать проблемы соединения в Kubernetes при помощи Mizu

Стоит попробовать Mizu – это ПО для мониторинга Kubernetes-трафика. Программа может сильно упростить ежедневную диагностику сетей, да и жизнь в целом.




Одна из наиболее частых задач, с которыми сталкиваются администраторы Kubernetes по ходу тестирования и дебаггинга – проверка коммуникаций между компонентами внутри сети. 




Если вы хоть как-то взаимодействуете c Kubernetes по работе, то наверняка частенько проверяете входящий трафик, чтобы изучить входящие запросы и т.п. Обычно задачи подобного рода решаются при помощи утилиты tcpdump, установленной в конкретный контейнер. Таким образом, проверяются некоторые сетевые аспекты вне контейнеров, но иногда такой подход оказывается довольно сложным из-за специфики окружения или конфигурации системы. 




Чтобы не натыкаться на кучу проблем по ходу мониторинга сети, советую использовать утилиту Mizu. Многие программисты, работающие с Kubernetes, мечтали бы найти подобный инструмент раньше.




Mizu можно описать как простой, но при этом мощный инструмент для отслеживания трафика в Kubernetes. Он позволяет отслеживать все API-коммуникации между микросервисами независимо от используемого протокола и упрощает процесс дебаггинга соединений.




Установка Mizu




Процесс установки довольно простой. Все что нужно – загрузить бинарный файл Mizu и настроить соответствующие разрешения в системе. Выбор бинарного файла (установщика) зависит от архитектуры устройства. Например, чтобы установить Mizu на компьютер Apple с чипом Intel нужно ввести в терминал команду:




‌curl -Lo mizu github.com/up9inc/mizu/releases/latest/download/mizu_darwin_amd64 && chmod 755 mizu && mv mizu /usr/local/bin




Обычно этого достаточно. Загруженный бинарный файл теперь можно использовать для подключения к кластеру Kubernetes и работы с Kubernetes API, но для этого нужно внести несколько изменений в настройки Докера. 




Вот как это может выглядеть в случае с базовым nginx-сервером. Начать стоит с команды:




‌kubectl run simple-app --image=nginx --port 3000




После развертки базового приложения на основе nginx переходим непосредственно к запуску Mizu. Делается это всего одной командой:




mizu tap




Через пару секунд на экране появится веб-страница с интерфейсом Mizu. Здесь и отображается весь трафик на выбранном сервере. Но для настройки такого поведения необходимо внести изменения в параметры портов. К примеру, если ваше приложение называется ‘my-app’, команда будет выглядеть так:




kubectl expose pod/my-app




После этого деплоим еще один временный сервер, используя готовый образ (при помощи следующей команды):




kubectl run -it --rm --image=curlimages/curl curly -- sh




Теперь можно использовать утилиту curl для отправки запросов непосредственно на nginx-сервер. Например, так:




curl -vvv http://my-app:3000




Уже после первых нескольких запросов вы увидите большой объем полезной информации, отображающейся в интерфейсе Mizu. В первую очередь стоит отметить то, насколько детально описываются все процессы, происходящие внутри Kubernetes. Но что еще важнее, в Mizu есть диаграммы, наглядно показывающие зависимости между запросами с учетом используемого протокола и другой полезной информации. 




Конечно, Mizu не сможет заменить более сложные продукты и полноценные системы наблюдения за сетью. Но это довольно удобный инструмент, который поможет вам исправить немало багов по ходу работы с Kubernetes, и его точно стоит иметь под рукой.




Источник: https://te.legra.ph/Kak-diagnostirovat-problemy-soedineniya-v-Kubernetes-pri-pomoshchi-Mizu-07-09



2022-08-07T23:59:02
DevOps

🐳 Как изменить конфигурацию запущенных контейнеров Docker

Контейнеры Docker обычно считаются неизменяемыми после начала их работы.




Однако некоторые параметры конфигурации можно изменять динамически, например, имя контейнера и лимиты его аппаратных ресурсов.




В этом руководстве мы покажем вам, как использовать встроенные команды Docker для изменения выбранных параметров запущенных контейнеров.




Мы также рассмотрим, что не следует изменять, и обходные пути, которые можно использовать, если вы считаете, что это необходимо.




Переименование контейнера




Самым простым изменением является переименование созданного контейнера. Имена присваиваются с помощью флага –name для команды docker run.




Если имя не указано, демон Docker присваивает случайное имя.




Вы можете использовать имена для ссылок на контейнеры в командах Docker CLI; выбор подходящего запоминающегося имени позволяет избежать выполнения команды docker ps для поиска автоматически назначенного имени или ID контейнера.




Команда docker rename используется для изменения имен контейнеров после создания.




Она принимает два аргумента: ID или текущее имя целевого контейнера и новое имя, которое нужно присвоить:




# docker rename <target ID or name> <new name>
docker rename old_name new_name




Изменение политики перезапуска




Restart policies определяют, должны ли контейнеры запускаться автоматически после перезагрузки хоста или запуска демона Docker.




Четыре доступные политики позволяют принудительно запустить контейнер, заставить его оставаться остановленным или условно запустить на основе предыдущего кода выхода или состояния работы контейнера.




Docker поддерживает изменение политик перезапуска “на лету”.




Это полезно, если вы планируете перезагрузить хост или демон Docker и хотите, чтобы определенный контейнер оставался остановленным – или автоматически запускался – после определенного события.




docker update --restart unless-stopped demo_container




Приведенный выше пример изменяет политику перезапуска demo_container на unless-stopped.




Эта политика заставляет контейнер запускаться вместе с демоном, если он не был остановлен вручную перед последним выходом демона.




Изменение лимитов аппаратных ресурсов




Команда docker update также может быть использована для изменения ограничений ресурсов, применяемых к контейнерам.




Вы должны передать один или несколько идентификаторов или имен контейнеров, а также список флагов, определяющих ограничения, которые необходимо установить для этих контейнеров.




Флаги доступны для всех ограничений ресурсов, поддерживаемых docker run.




Вот краткий список опций, которые вы можете использовать:




  • –blkio-weight – Изменить относительный вес Block IO контейнера.
  • –cpus – Установить количество процессоров, доступных контейнеру.
  • –cpu-shares – Установить относительный вес доли ЦП.
  • –memory – Изменить лимит памяти контейнера (например, 1024M).
  • –memory-swap – настройка объема памяти, который контейнер может обменять на диск; используйте размер, например 1024M, чтобы установить определенный лимит, или -1 для неограниченной замены.
  • –kernel-memory – Изменить лимит памяти ядра контейнера. Контейнеры, испытывающие недостаток памяти ядра, могут негативно влиять на другие рабочие нагрузки на хост-машине.
  • –pids-limit – настройка максимального количества идентификаторов процессов, разрешенных внутри контейнера, что ограничивает количество процессов, которые могут быть запущены.




Вот пример использования docker update для изменения лимита памяти и количества CPU для двух контейнеров




docker update --cpus 4 --memory 1024M first_container second_container




Все доступные флаги, кроме –kernel-memory, можно использовать с запущенными контейнерами Linux.




Чтобы изменить лимит памяти ядра, необходимо сначала остановить контейнер с помощью docker stop.




Имейте в виду, что ни один из этих флагов в настоящее время не поддерживается для контейнеров на базе Windows.




Однако они будут работать с контейнерами Linux, запущенными на хост-машине Windows.




Когда не следует использовать эти команды?




Команды docker update и docker rename следует использовать с контейнерами, которые вы создали вручную с помощью команды docker run.




Опасайтесь использовать их для контейнеров, созданных с помощью других инструментов, таких как docker-compose.




Изменение имени контейнера может сделать его необнаружимым для исходного инструмента, что может привести к поломке других компонентов вашего стека.




Кроме того, если вы декларативно определяете ограничения на ресурсы в файле docker-compose.yml, повторное выполнение команды docker-compose up приведет к повторному применению этих исходных ограничений к вашему контейнеру.




Поэтому вам следует придерживаться существующего решения для управления контейнерами, если вы его используете.




Для Compose это означает изменение имен контейнеров и лимитов ресурсов в файле docker-compose.yml, а затем выполнение команды docker-compose up -d для автоматического применения изменений.




Это позволит избежать непреднамеренного сиротства контейнеров или возникновения непреднамеренных побочных эффектов.




Что касается других свойств (образа/порты/тома)?




Аппаратные ограничения, политики ресурсов и имена контейнеров – это единственные параметры конфигурации, которые позволяет изменять Docker CLI.




Вы не можете изменить образ запущенного контейнера; вы также не можете легко изменить другие параметры, такие как привязка портов и тома.




Вам следует создать другой контейнер, если эти значения устарели.




Уничтожьте текущий экземпляр и с помощью команды docker run запустите новый контейнер с новым образом и исправленными настройками.




Поскольку контейнеры должны быть без статических данных и эфемерными, вы должны иметь возможность заменить их в любое время.




Используйте тома для хранения постоянных данных контейнера; этот механизм позволяет повторно подключить файлы с состоянием к новому контейнеру, повторив флаги -v, переданные исходной команде docker run:




docker run -v config-volume:/usr/lib/config --name demo example-image:v1

docker rm demo

# Существующие данные в /usr/lib/config сохранены
docker run -v config-volume:/usr/lib/config --name demo2 example-image:v2




Опасная зона: Изменение других свойств контейнера




Хотя вы должны стремиться заменять контейнеры везде, где это возможно, можно изменять свойства существующих контейнеров путем прямого редактирования конфигурационных файлов Docker.




Будьте осторожны при использовании этого метода: он совершенно не поддерживается, и неправильное изменение может привести к поломке вашего контейнера.




Хотя эта опция предоставляет возможность произвольно редактировать существующие контейнеры, она не будет работать во время их работы.




Используйте команду docker stop my-container, чтобы остановить контейнер, который вы хотите отредактировать, а затем продолжайте вносить свои изменения.




Конфигурационные файлы контейнеров имеют следующий путь




/var/lib/docker/containers/<container id>/config.v2.json




Вам нужно знать полный ID контейнера, а не усеченную версию, которую показывает docker ps.




Для этого можно использовать команду docker inspect:




docker inspect <short id or name> | jq | grep Id







Добравшись до config.v2.json контейнера, вы можете открыть его в текстовом редакторе, чтобы внести необходимые изменения.




В JSON хранится конфигурация контейнера, созданная при запуске docker run.




Вы можете изменить его содержимое, чтобы изменить такие свойства, как привязка портов, переменные окружения, тома, а также точку входа контейнера и команду.




Чтобы добавить привязку к порту, найдите в файле ключ PortBindings, затем вставьте новый элемент в объект:




{
    "PortBindings": {
        "80/tcp": {
            "HostIp": "",
            "HostPort": "8080"
        }
    }
}




Здесь порт 80 в контейнере привязан к порту 8080 на хосте.




Аналогично просто добавлять переменные окружения – найдите ключ Env, затем вставьте новые элементы в массив:




{
    "Env": [
        "FOO=bar",
        "CUSTOM_VARIABLE=example"
    ]
}




После завершения редактирования перезапустите службу Docker и ваш контейнер:




sudo service docker restart

docker start my-container




Заключение




Контейнеры Docker должны быть эфемерными единицами, которые вы заменяете, когда их конфигурация устаревает.




Несмотря на это намерение, существуют сценарии, когда необходимо изменить существующий контейнер.




Docker обрабатывает наиболее распространенные случаи – изменение имени и корректировка лимита ресурсов в реальном времени – с помощью встроенных команд CLI, таких как docker update.




Когда вы хотите изменить другое свойство, всегда старайтесь в первую очередь заменить контейнер.




Это минимизирует риск поломки и соответствует модели неизменяемости Docker.




Если вы все же попали в ситуацию, когда необходимо изменить существующий контейнер, вы можете вручную изменить внутренние конфигурационные файлы Docker, как описано выше.




Наконец, помните, что контейнеры, управляемые другими инструментами экосистемы, такими как Docker Compose, должны быть изменены с помощью этих механизмов.




В противном случае вы можете обнаружить, что контейнеры неожиданно осиротеют или будут перезаписаны, если инструмент не знает о внесенных вами изменениях.







Источник: https://t.me/i_DevOps https://te.legra.ph/Kak-izmenit-konfiguraciyu-zapushchennyh-kontejnerov-Docker-07-09



2022-08-07T23:55:44
DevOps

Как настроить удобный терминал Kubernetes

Kubernetes поставляется в комплекте с выдающимся CLI.




Для основных операций это работает чудесно.




Увы, когда нужно что-то сделать быстро, сложность возрастает.




Сообщество Kubernetes создало все виды веб-инструментов для мониторинга вашего кластера – kube ops, grafana и т. д.




Однако наличие полностью настроенного терминала быстро сократит время, необходимое для поиска причины проблемы.




Это основная часть вашего швейцарского армейского ножа.




Ниже приведен очень короткий список инструментов с открытым исходным кодом, которые можно применить в своем терминале.




При совместном использовании они позволяют управлять кластером kubernetes, быстро устранять неполадки и отслеживать поведение.




 Предпосылки




Прежде чем приступить к изучению этих инструментов, я настоятельно рекомендую установить zsh.




Это выдающаяся оболочка с открытым исходным кодом для стандартного терминала OSX.




Он более многофункциональный и интуитивно понятный, а плагины, которые вы можете установить, просто фантастические.




Некоторые из перечисленных инструментов предполагают, что у вас установлен ZSH.




Лучшие инструменты для управления Kubernetes




k9s







Я начинаю с самого мощного.




K9s –  это основа CLI для кластера kubernetes.




Вы можете проваливаться по SSH прямо в поды одним нажатием клавиши, просматривать журналы, удалять ресурсы и многое другое.




Он обеспечивает выдающийся доступ к наиболее распространенным операциям, которые вы будете выполнять.




Это основной продукт для любого инженера, использующего kubernetes.







kubectx




Очень редко у нас будет только один кластер.




Переключение между ними можно так же просто осуществлять:




kubectl config use-context my-context




Но при этом есть некоторые предпосылки:




  • Вам нужно знать имя кластера, прежде чем запускать команду.
  • Есть другая, похожая команда set-context, которая может сбить вас с толку.




kubectx представляет более простую альтернативу этому варианту.




Если вы запустите kubectx самостоятельно, он перечислит все контексты в вашем файле .kube/config.




Затем вы можете указать название интересующего вас контекста:




kubectx my-context




Не нужно запоминать все контексты, не нужно вручную проверять файлы и нет возможности ввести неправильную команду.




Красиво и просто.




В сочетании с K9s, этот набор обеспечивает классную навигацию из вашего CLI с минимальными нажатиями клавиш.







kubens




Как только вы переключаетесь между контекстами, вы можете долго копаться в определенном пространстве имен.




Еще раз, очень часто в вашем кластере имеется несколько пространств имен.




Короче, в двух словах это то же самое, что kubectx, только для пространств имен.




kubens kube-system




Теперь все ваши команды по умолчанию выполняются в пространстве имен системы kube-system.




Вы также можете запустить Kubens без флагов, чтобы увидеть список ваших пространств имен.




kube-ps1




Таким образом, вы можете переключаться между контекстами и пространствами имен.




Но как узнать, на кого вы сейчас нацелены?




Каждый раз постоянно это проверять?




На данный момент, чтобы узнать, вам нужно запустить:




kubens
kubectx
kubectl <my-command>




Чтобы не делать этого, ps1 является плагином zsh, который автоматически покажет вам ваш текущий контекст и пространство имен:







Теперь вы можете увидеть, на какое пространство имен и контекст вы указываете, не выполняя ни одной команды.




Он также очень настраиваемый – вы можете отключить пространство имен или контекст, если вас интересует только что-то одно из них, или вы можете использовать kubeoff, чтобы полностью отключить все это.




popeye




Popeye запускает автоматическое сканирование ресурсов в вашем хранилище и выявляет очевидные проблемы.




Это новый инструмент, который я нашел очень полезным.




Если вы затеяли генеральную уборку в кластере, начните с popeye, и вы получите четкие указания о том, что нужно исправить.







Stern




Вы когда-нибудь использовали логи kubectl?




Заметили, что вы можете следить только за журналами с одного пода одновременно?




Не беспокойтесь больше об этом!




Stern – это инструмент, который позволяет вам извлекать логи из нескольких подов, основываясь на очень гибком запросе.




Источник: https://itisgood.ru/2020/01/22/kak-nastroit-udobnyj-terminal-kubernetes/



2022-08-07T23:52:21
DevOps

Обзор k9s — продвинутого терминального интерфейса для Kubernetes

K9s предоставляет пользовательский интерфейс терминала для взаимодействия с кластерами Kubernetes. Цель этого Open Source-проекта — облегчить удобную навигацию по приложениям в K8s, наблюдение за ними и управление ими. K9s постоянно следит за изменениями в Kubernetes и предлагает быстрые команды для работы с наблюдаемыми ресурсами.




Проект написан на Go, существует уже более полутора лет: первый коммит был сделан 1 февраля 2019 года. На момент написания статьи насчитывается 9000+ звезд на GitHub и около 80 контрибьюторов. Посмотрим, что умеет k9s?




Установка и запуск




Это клиентское (по отношению к кластеру Kubernetes) приложение, которое проще всего запустить как Docker-образ:




docker run --rm -it -v $KUBECONFIG:/root/.kube/config quay.io/derailed/k9s




Для некоторых Linux-дистрибутивов и других ОС также есть готовые для установки пакеты. В общем случае для Linux-систем можно установить бинарный файл:




sudo wget -qO- https://github.com/derailed/k9s/releases/download/v0.22.0/k9s_Linux_x86_64.tar.gz | tar zxvf -  -C /tmp/
sudo mv /tmp/k9s /usr/local/bin




Каких-то специфических требований к самому кластеру K8s нет. Судя по отзывам, приложение работает и с такими старыми версиями Kubernetes, как 1.12.




Приложение запускается, используя стандартный конфиг .kube/config — аналогичному тому, как это делает kubectl.




Навигация




По умолчанию открывается окно со стандартным namespace, который указан для контекста. То есть, если вы прописали kubectl config set-context --current --namespace=test, то и откроется namespace test(О смене контекстов/пространств имён см. ниже.)







Переход в режим команд осуществляется нажатием на «:». После этого можно управлять работой k9s с помощью команд — например, для просмотра списка StatefulSets (в текущем пространстве имен) можно ввести :sts.







Для некоторых других ресурсов Kubernetes:




  • :ns — Namespaces;
  • :deploy — Deployments;
  • :ing — Ingresses;
  • :svc — Services.




Чтобы вывести полный список типов ресурсов, доступных для просмотра, есть команда :aliases.




Удобно просматривать и список команд, доступных по горячим комбинациям клавиш в рамках текущего окна: для этого достаточно нажать на «?».







Также в k9s есть режим поиска, для перехода в который достаточно ввести «/». С ним осуществляется поиск по содержимому текущего «окна». Допустим, если вы до этого ввели :ns, у вас открыт список пространств имён. Если их слишком много, то, чтобы не скроллить долго вниз, достаточно в окне с namespaces ввести /mynamespace.




Для поиска по лейблам можно выбрать все pod’ы в нужном пространстве имён, после чего ввести, например, / -l app=whoami. Мы получим список pod’ов с этим лейблом:







Поиск работает во всех видах окон, включая логи, просмотр YAML-манифестов и describe для ресурсов — подробнее об этих возможностях см. ниже.




Как в целом выглядит последовательность действий для навигации?




С помощью команды :ctx можно выбрать контекст:







Для выбора namespace’а есть уже упоминавшаяся команда :ns, а далее можно воспользоваться поиском для нужного пространства: /test.




Если теперь выбрать интересующий нас ресурс (например, всё тот же StatefulSet), для него покажется соответствующая информация: сколько запущено pod’ов с краткими сведениями о них.







Могут быть интересны только pod’ы — тогда достаточно ввести :pod. В случае с ConfigMap’ами (:cm — для списка этих ресурсов) можно выбрать интересующий объект и нажать на «u», после чего K9s подскажет, кто конкретно его (этот CM) использует.




Ещё одна удобная фича для просмотра ресурсов — их «рентген» (XRay view). Такой режим вызывается командой :xray RESOURCE и… проще показать, как он работает, чем объяснять. Вот иллюстрация для StatefulSets:







(Каждый из этих ресурсов можно редактировать, изменять, делать describe.)




А вот Deployment с Ingress:







Работа с ресурсами




О каждом ресурсе можно получить информацию в YAML или его describe нажатием на соответствующие клавиатурные сочетания («y» и «d» соответственно). Базовых операций, конечно, ещё больше: их список и клавиатурные сочетания всегда на виду благодаря удобной «шапке» в интерфейсе (скрывается нажатием на Ctrl + e).







При редактировании любого ресурса («e» после его выбора) открывается текстовый редактор, определённый в переменных окружения (export EDITOR=vim).




А вот как выглядит подробное описание ресурса (describe):







Такой вывод (или вывод просмотр YAML-манифеста ресурса) можно сохранить с помощью привычного сочетания клавиш Ctrl + s. Куда он сохранится, будет известно из сообщения K9s:




Log /tmp/k9s-screens-root/kubernetes/Describe-1601244920104133900.yml saved successfully!




Из созданных файлов-бэкапов можно и восстанавливать ресурсы, предварительно убрав системные лейблы и аннотации. Для этого потребуется перейти в директорию с ними (:dir /tmp), после чего выбрать нужный файл и применить apply.




К слову, в любой момент можно откатиться и на прошлый ReplicaSet, если с текущим есть проблемы. Для этого надо выбрать нужный RS (:rs для их списка):







… и выполнить rollback с помощью Ctrl + l. Мы должны получить уведомление, что все прошло успешно:




k9s/whoami-5cfbdbb469 successfully rolled back




А чтобы масштабировать реплики, достаточно нажать на «s» (scale) и выбрать нужное количество экземпляров:







В любой из контейнеров можно зайти с помощью shell: для этого перейдите к нужному pod’у, нажмите на «s» (shell) и выберите контейнер.




Другие возможности




Конечно, поддерживается и просмотр логов («l» для выбранного ресурса). А чтобы смотреть новые логи, нет необходимости постоянно нажимать Enter: достаточно сделать маркировку («m»), после чего отслеживать только новые сообщения.







Также в этом же окне можно выбрать временной диапазон для вывода логов:




  • клавиша «1» — за 1 минуту;
  • «2» — 5 минут;
  • «3» — 15 минут;
  • «4» — 30 минут;
  • «5» — 1 час;
  • «0» — за все время жизни pod’а.




Специальный режим работы Pulse (команда :pulse) показывает общие сведения о Kubernetes-кластере:







В нем можно увидеть количество ресурсов и их состояние (зеленым показываются те, что имеют статус Running).




Еще одна интересная функция K9s называется Popeye. Она проверяет все ресурсы на определённые критерии корректности и выводит получившийся «рейтинг» с пояснениями. Например, можно увидеть, что не хватает проб или лимитов, а какой-то контейнер может запускаться под root…







Имеется базовая поддержка Helm. Например, так можно посмотреть релизы, задеплоенные в кластер:




:helm all # все
:helm $namespace # в конкретном пространстве имен




Benchmark




В K9s встроили даже hey — это простой генератор нагрузки на HTTP-сервер, альтернатива более известному ab (ApacheBench).




Чтобы включить его, потребуется активация port-forward в pod’е. Для этого выбираем pod и нажимаем на Shift + f, переходим в подменю port-forward с помощью алиаса «pf».







После выбора порта и нажатия на Ctrl + b запустится сам benchmark. Результаты его работы сохраняются в /tmp и доступны для последующего просмотра в K9s.










Для изменения конфигурации benchmark’а нужно создать файл $HOME/.k9s/bench-<my_context>.yml (определяется для каждого кластера).




NB: Важно, чтобы расширение всех YAML-файлов в директории .k9s было именно .yml (.yaml не работает корректно).




Пример конфигурации:




benchmarks:
  defaults:
    # Количество потоков
    concurrency: 2
    # Количество запросов
    requests: 1000
  containers:
    # Настройки для контейнера с бенчмарком
    # Контейнер определяется как namespace/pod-name:container-name
    default/nginx:nginx:
      concurrency: 2
      requests: 10000
      http:
        path: /
        method: POST
        body:
          {"foo":"bar"}
        header:
          Accept:
            - text/html
          Content-Type:
            - application/json
 services:
    # Можно проводить бенчмарк на сервисах типа NodePort и LoadBalancer
    # Синтаксис: namespace/service-name
    default/nginx:
      concurrency: 5
      requests: 500
      http:
        method: GET
        path: /auth
      auth:
        user: flant
        password: s3cr3tp455w0rd




Интерфейс




Вид столбцов для списков ресурсов модифицируется созданием файла $HOME/.k9s/views.yml. Пример его содержимого:




k9s:
 views:
   v1/pods:
     columns:
       - AGE
       - NAMESPACE
       - NAME
       - IP
       - NODE
       - STATUS
       - READY
   v1/services:
     columns:
       - AGE
       - NAMESPACE
       - NAME
       - TYPE
       - CLUSTER-IP




Правда, не хватает колонки по лейблам, на что есть issue в проекте.




Сортировка по столбцам осуществляется клавиатурными сочетаниями:




  • Shift + n — по имени;
  • Shift + o — по узлам;
  • Shift + i — по IP;
  • Shift + a — по времени жизни контейнера;
  • Shift + t — по количеству рестартов;
  • Shift + r — по статусу готовности;
  • Shift + c — по потреблению CPU;
  • Shift + m — по потреблению памяти.




Если же кому-то не нравится цветовое оформление по умолчанию, в K9s даже поддерживаются скины. Готовые примеры (7 штук) доступны здесь. Вот пример одного из таких скинов (in the navy):







Плагины




Наконец, плагины позволяют расширять возможности K9s. Сам я в работе использовал только один из них — kubectl get all -n $namespace.




Выглядит это следующим образом. Создаем файл $HOME/.k9s/plugin.yml с таким содержимым:




plugin:
 get-all:
   shortCut: g    
   confirm: false    
   description: get all
   scopes:
   - all
   command: sh
   background: false
   args:
   - -c
   - "kubectl -n $NAMESPACE get all -o wide | less"




Теперь можно перейти в пространство имён и нажать на «g» для выполнения с соответствующей команды:







Среди плагинов есть, например, интеграции с kubectl-jq и утилитой для просмотра логов stern.




Заключение




На мой вкус, K9s оказалась очень удобна в работе: с ней довольно быстро привыкнуть искать всё нужное без использования kubectl. Порадовал просмотр логов и их сохранение, быстрое редактирование ресурсов, скорость работы в целом*, оказался полезным режим Popeye. Отдельного упоминания стоят возможности создавать плагины и дорабатывать приложение под свои нужды.




* Хотя при большом объеме логов замечал также медленную работу K9s. В такие моменты утилита «съедала» 2 ядра у Intel Xeon E312xx и могла даже зависать.




Чего не хватает в настоящий момент? Быстрого отката на предыдущую версию (речь не про RS) без перехода в директорию. К тому же, восстановление происходит только для всего ресурса: если вы удалили аннотацию или лейбл, придется удалить и восстановить весь ресурс (здесь и понадобится переходить в директорию). Другая мелочь — не хватает даты таких сохраненных «бэкапов».




Источник: https://habr.com/ru/company/flant/blog/524196/



2022-07-28T23:48:08
DevOps

Создание переиспользуемых пайплайнов для GitLab CI на bash

За последние несколько лет я очень полюбил GitLab CI. В основном за его простоту и функциональность. Достаточно просто создать в корне репозитория файл .gitlab-ci.yml , добавить туда несколько строчек кода и при следующем коммите запустится пайплайн с набором джобов, которые будут выполнять указанные команды.




А если добавить к этому возможности include и extends, можно делать достаточно интересные вещи: создавать шаблонные джобы и пайплайны, выносить их в отдельные репозитории и повторно использовать в разных проектах без копирования кода.




Но к сожалению, не всё так радужно, как хотелось бы. Инструкция script в GitLab CI очень низкоуровневая. Она просто выполняет те команды, которые ей переданы в виде строк. Писать большие скрипты внутри YAML не очень удобно. По мере усложнения логики количество скриптов увеличивается, они перемешиваются с YAML делая конфиги нечитаемыми и усложняя их поддержку.




Мне очень не хватало какого-то механизма, который бы упростил разработку больших скриптов. В результате у меня родился микрофреймворк для разработки GitLab CI, про который я и хочу рассказать в этой статье (на примере простого пайплайна для сборки docker-образов).




Пример: Сборка docker-образов в GitLab




Я хочу рассмотреть процесс создания пайплайна на примере простой задачи, которую часто встречал у разных команд: создание базовых docker-образов в GitLab для их повторного использования.




Например, команда пишет микросервисы и хочет для них для всех использовать свой собственный базовый образ с набором предустановленных утилит для отладки.




Или другой пример, команда пишет тесты и хочет используя services прямо в GitLab создавать для них временную базу данных (или очередь, или что-то ещё) используя свой собственный образ.




В целом, создать docker-образ из отдельного докерфайла в GitLab достаточно просто. Например можно сделать это с помощью такого .gitlab-ci.yml:




services:
  - docker:dind

Build:
  image: docker
  script:
    - |
      docker login "$CI_REGISTRY" 
        --username "$CI_REGISTRY_USER" 
        --password "$CI_REGISTRY_PASSWORD"

      docker build 
        --file "Dockerfile" 
        --tag "$CI_REGISTRY/$CI_PROJECT_PATH:$CI_COMMIT_REF_SLUG" .

      docker push "$CI_REGISTRY/$CI_PROJECT_PATH:$CI_COMMIT_REF_SLUG"




Здесь мы создаём пайплайн с одним джобом Build, который на каждый коммит логинится в GitLab Container Registry, собирает образ из файла докерфайла в корне репозитория и пушит полученный образ. В качестве тега образа используется название ветки или тега, где происходит сборка (возможно не самая лучшая схема, но это сделано для примера).




Но предположим, что нам необходимо создать чуть более сложный пайплайн, который будет работать следующим образом:




  • В репозитории может находиться несколько докерфайлов, которые будут располагаться в подпапке dockerfiles.
  • На каждый коммит должен запускаться пайплайн с двумя джобами:
    • Build All — будет находить и пересобирать все докерфайлы в подпапке dockerfiles. Этот джоб будет ручным (запускается по кнопке из интерфейса).
    • Build Changed— будет находить и пересобирать все докерфайлы в подпапке dockerfiles, которые были изменены в последнем коммите. Этот джоб будет автоматическим (запускается сразу при коммите) и будет появляться только при изменении файлов.




Подобный пайплайн должен достаточно хорошо работать. Он будет быстро пересобирать все изменённые докерфайлы при каждом коммите. При этом у разработчиков останется возможность вручную пересобрать сразу всё, если это потребуется.




Структура репозитория для такого пайплайна может выглядеть следующим образом:







Содержимое и название докерфайлов особой роли не играет, но в данном случае это докерфайлы для разных версий .NET.




В интерфейсе гитлаба подобный пайплайн может выглядеть следующим образом:







Результатом работы такого пайплайна станет набор docker-образов, которые можно будет найти в Container Registry проекта:







А теперь попробуем создать пайплайн, который бы решал нашу задачу.




Шаг 1: Решение «в лоб»




Начнём самого простого и очевидного решения. Мы можем поместить весь код в файл .gitlab-ci.yml. В таком случае он будет выглядеть следующим образом:




services:
  - docker:dind

stages:
  - Build

Build All:
  stage: Build
  image: docker
  when: manual
  script:
    - |
      dockerfiles=$(find "dockerfiles" -name "*.Dockerfile" -type f)

      docker login "$CI_REGISTRY" 
        --username "$CI_REGISTRY_USER" 
        --password "$CI_REGISTRY_PASSWORD"

      for dockerfile in $dockerfiles; do
        path=$(echo "$dockerfile" | sed 's/^dockerfiles///' | sed 's/.Dockerfile$//')
        tag="$CI_REGISTRY/$CI_PROJECT_PATH/$path:$CI_COMMIT_REF_SLUG"

        echo "Building $dockerfile..."
        docker build --file "$dockerfile" --tag "$tag" .

        echo "Pushing $tag..."
        docker push "$tag"
      done

Build Changed:
  stage: Build
  image: docker
  only:
    changes:
      - 'dockerfiles/*.Dockerfile'
      - 'dockerfiles/**/*.Dockerfile'
  script:
    - |
      apk update
      apk add git # Вообще говоря, так не очень хорошо делать, но для примера можно...

      dockerfiles=$(git diff --name-only HEAD HEAD~1 -- 'dockerfiles/***.Dockerfile')

      docker login "$CI_REGISTRY" 
        --username "$CI_REGISTRY_USER" 
        --password "$CI_REGISTRY_PASSWORD"

      for dockerfile in $dockerfiles; do
        path=$(echo "$dockerfile" | sed 's/^dockerfiles///' | sed 's/.Dockerfile$//')
        tag="$CI_REGISTRY/$CI_PROJECT_PATH/$path:$CI_COMMIT_REF_SLUG"

        echo "Building $dockerfile..."
        docker build --file "$dockerfile" --tag "$tag" .

        echo "Pushing $tag..."
        docker push "$tag"
      done




Ссылки на проект:







Здесь мы делаем следующее:




  • Создаём джоб Build All, который:
    • Запускается вручную, т.к. содержит настройку when: manual.
    • Выполняет поиск всех докерфайлов при помощи команды:
      • find "dockerfiles" -name "*.Dockerfile" -type f
  • Создаём джоб Build Changed, который:
    • Создаётся только при изменении докерфайлов, т.к. содержит настройку only:changes.
    • Выполняет поиск всех докерфайлов, изменённых в последнем коммите при помощи команды:
      • git diff --name-only HEAD HEAD~1 -- 'dockerfiles/***.Dockerfile'
  • В остальном оба джоба работают одинаково, после поиска докерфайлов, они проходят по ним циклом, вырезают из названия каждого докерфайла префикс dockerfiles/ и суффикс .Dockerfile, после чего собирают его и пушат в GitLab Container Registry.




Данный пайплайн работает но есть очевидные проблемы:




  • Скрипты обоих джобов практически полностью дублируются (за исключением поиска докерфайлов)
  • Скрипты сильно загромождают конфиг, усложняя его понимание.




В этом месте появляется тот микрофреймворк для разработки GitLab CI про который я упоминал в самом начале.




GitLab CI Bootstrap




GitLab CI Bootstrap — это микрофреймворк для разработки GitLab CI. Его основные цели:




  • Разделить описание пайплайна (файл .gitlab-ci.yml) и его скрипты (bash или shell), чтобы оставить YAML более декларативным.
  • Дать возможность разбивать большие скрипты на более мелкие и подключать одни скрипты к другим, чтобы улучшить структурирование кода и упростить его поддержку.
  • Дать возможность выносить скрипты и джобы (со скриптами) в отдельные репозитории, чтобы повторно использовать их в разных проектах.




GitLab CI Bootstrap состоит из одного единственного файла bootstrap.gitlab-ci.yml, который необходимо подключить в файле .gitlab-ci.yml при помощи include. Сделать это можно несколькими способами. Проще всего скопировать файл в свой проект и подключить его через include:local:




include:
  - local: 'bootstrap.gitlab-ci.yml'




В этом файле находится один единственный скрытый джоб .bootstrap, который имеет следующий вид:




.bootstrap:
  before_script:
    - |
     	...




Другие джобы могут расширять джоб .bootstrap, используя extends:




example:
  extends: '.bootstrap'
  script:
    - '...'




В таких джобах будет включаться механизм загрузки скриптов (который описан ниже), который позволяет загружать скрипты из текущего репозитория или из любого другого репозитория внутри того же экземпляра GitLab.




При этом для загрузки скриптов не нужны специальные токены доступа. Загружать скрипты можно даже из внутреннего или приватного репозиториев. Главное, чтобы у разработчика, запустившего пайплайн был доступ на чтение к этим репозиториям.




Единственным требованием для загрузчика является наличие bash или shell. Также желательно наличие git, однако при его отсутствии он будет установлен автоматически. Благодаря этому джоб .bootstrap можно использовать практически с любыми официальными docker-образами и он будет там корректно работать.




Итак, давайте посмотрим, как это поможет нам упростить наш пайплайн для сборки docker-образов.




Шаг 2: Выносим все скрипты в отдельный файл




Все джобы, использующие джоб .bootstrap автоматически проверяют наличие в репозитории специального файла .gitlab-ci.sh. При его наличии этот файл автоматически загружается и все переменные и функции, которые в нём определены становятся доступными на этапе выполнения скриптов джоба.




Поэтому мы можем вынести все скрипты в этот файл и разбить их на функции, чтобы избежать дублирования, а затем вызвать эти функции из файла .gitlab-ci.yml:




Файл .gitlab-ci.yml:




include:
  - project: '$CI_PROJECT_NAMESPACE/bootstrap'
    ref: 'master'
    file: 'bootstrap.gitlab-ci.yml'

services:
  - docker:dind

stages:
  - Build

Build All:
  stage: Build
  image: docker
  extends: .bootstrap
  when: manual
  script:
    - search_all_dockerfiles_task
    - build_and_push_dockerfiles_task

Build Changed:
  stage: Build
  image: docker
  extends: .bootstrap
  only:
    changes:
      - 'dockerfiles/*.Dockerfile'
      - 'dockerfiles/**/*.Dockerfile'
  script:
    - install_git_task
    - search_changed_dockerfiles_task
    - build_and_push_dockerfiles_task




Файл .gitlab-ci.sh:




DOCKERFILES=""

function search_all_dockerfiles_task() {
    DOCKERFILES=$(find "dockerfiles" -name "*.Dockerfile" -type f)
}

function search_changed_dockerfiles_task() {
    DOCKERFILES=$(git diff --name-only HEAD HEAD~1 -- 'dockerfiles/***.Dockerfile')
}

function install_git_task() {
    # Вообще говоря, так не очень хорошо делать, но для примера можно...
    apk update
    apk add git
}

function build_and_push_dockerfiles_task() {
    docker login "$CI_REGISTRY" 
        --username "$CI_REGISTRY_USER" 
        --password "$CI_REGISTRY_PASSWORD"

    for dockerfile in $DOCKERFILES; do
        path=$(echo "$dockerfile" | sed 's/^dockerfiles///' | sed 's/.Dockerfile$//')
        tag="$CI_REGISTRY/$CI_PROJECT_PATH/$path:$CI_COMMIT_REF_SLUG"

        echo "Building $dockerfile..."
        docker build --file "$dockerfile" --tag "$tag" .

        echo "Pushing $tag..."
        docker push "$tag"
    done
}




Ссылки на проект:







Теперь все скрипты у нас вынесены в отдельный файл gitlab-ci.sh и разбиты на функции, которые вызываются из файла .gitlab-ci.yml. Функции search_all_dockerfiles_task и search_changed_dockerfiles_task заполняют переменную DOCKERFILES, которая позже используется функцией build_and_push_dockerfiles_task для сборки докерфайлов.




Шаг 3: Переносим пайплайн в отдельный репозиторий




Мы сделали работающий пайплайн и вынесли его скрипты в отдельный файл, но пока этот пайплайн находится в том же репозитории, что и докерфайлы. Это означает, что если другая команда захочет использовать его для сборки своих докерфайлов, им придётся скопировать все скрипты. А это в свою очередь означает, что если нам необходимо будет внести в пайплайн какие-нибудь изменения, то потом их нужно будет внести и всем командам, которые скопировали себе пайплайн.




Вместо этого мы можем вынести пайплайн в отельный репозиторий и предложить нескольким командам подключить его через include.




Однако для этого придётся внести в пайплайн небольшое изменение. Дело в том, что поскольку скрипты теперь будут фактически расположены не в том же репозитории, где запускается пайплайн, джоб должен как-то это понять и определить, откуда загрузить скрипты. Это делается при помощи специальных переменных, которые добавляются в пайплайн.




Если в джобе, использующей джоб .bootstrap определена переменная CI_IMPORT, то этот джоб проверит наличие трёх переменных: CI_{module}_PROJECTCI_{module}_REF и CI_{module}_FILE, где {module} — это нормализованное значение переменной CI_IMPORT (приведённое в верхнему регистру с заменой дефисов на подчёркивания).




Если все три переменные существуют, джоб перед загрузкой скриптов из файла .gitlab-ci.sh сначала загрузит файл, указанный в этих переменных.




Наш репозиторий с докерфайлами теперь будет выглядеть так:




Файл .gitlab-ci.yml:




include:
  - project: '$CI_PROJECT_NAMESPACE/dockerfiles-example-ci'
    ref: 'step3'
    file: 'dockerfiles.gitlab-ci.yml'




Сам пайплайн в отдельном репозитории будет выглядеть следующим образом:




Файл dockerfiles.gitlab-ci.yml (сюда перенесён код из .gitlab-ci.yml):




include:
  - project: '$CI_PROJECT_NAMESPACE/bootstrap'
    ref: 'master'
    file: 'bootstrap.gitlab-ci.yml'

services:
  - docker:dind

stages:
  - Build

variables:
  CI_DOCKERFILES_PROJECT: '$CI_PROJECT_NAMESPACE/dockerfiles-example-ci'
  CI_DOCKERFILES_REF: 'step3'
  CI_DOCKERFILES_FILE: 'dockerfiles.gitlab-ci.sh'

Build All:
  stage: Build
  image: docker
  extends: .bootstrap
  variables:
    CI_IMPORT: dockerfiles
  when: manual
  script:
    - search_all_dockerfiles_task
    - build_and_push_dockerfiles_task

Build Changed:
  stage: Build
  image: docker
  extends: .bootstrap
  variables:
    CI_IMPORT: dockerfiles
  only:
    changes:
      - 'dockerfiles/*.Dockerfile'
      - 'dockerfiles/**/*.Dockerfile'
  script:
    - search_changed_dockerfiles_task
    - build_and_push_dockerfiles_task




Файл dockerfiles.gitlab-ci.sh (сюда перенесён код из .gitlab-ci.sh):




DOCKERFILES=""

function search_all_dockerfiles_task() {
    DOCKERFILES=$(find "dockerfiles" -name "*.Dockerfile" -type f)
}

function search_changed_dockerfiles_task() {
    DOCKERFILES=$(git diff --name-only HEAD HEAD~1 -- 'dockerfiles/***.Dockerfile')
}

function build_and_push_dockerfiles_task() {
    docker login "$CI_REGISTRY" 
        --username "$CI_REGISTRY_USER" 
        --password "$CI_REGISTRY_PASSWORD"

    for dockerfile in $DOCKERFILES; do
        path=$(echo "$dockerfile" | sed 's/^dockerfiles///' | sed 's/.Dockerfile$//')
        tag="$CI_REGISTRY/$CI_PROJECT_PATH/$path:$CI_COMMIT_REF_SLUG"

        echo "Building $dockerfile..."
        docker build --file "$dockerfile" --tag "$tag" .

        echo "Pushing $tag..."
        docker push "$tag"
    done
}




Ссылки на проект:







Оба наших джоба Build All и Build Changed содержат переменную CI_IMPORT со значением dockerfiles. Поэтому загрузчик при старте джоба также проверит наличие трёх переменых: CI_DOCKERFILES_PROJECTCI_DOCKERFILES_REF и CI_DOCKERFILES_FILE, которые также определены в пайплайне.




Благодаря этим переменным загрузчик понимает, что должен загрузить скрипты из файла dockerfiles.gitlab-ci.sh, который находится в репозитории $CI_PROJECT_NAMESPACE/dockerfiles-example-ci, в ветке step3.




Кроме того, мы убрали из скриптов функцию install_git_task. Явная установка git больше не нужна, т.к. загрузчик сам по себе требует git для загрузки скриптов из другого репозитория и поэтому устанавливает git самостоятельно.




Шаг 4: Разбиваем большие скрипты на отдельные файлы




Мы вынесли пайплайн в отдельный репозиторий и теперь другие команды могут подключать его в свои проекты простым инклюдом. Изменения, которые мы будем вносить в пайплайн будут автоматически отражаться на всех командах и нам не придётся ходить по командам с просьбами обновиться на последнюю версию.




Однако сейчас все скрипты у нас находятся в одном файле dockerfiles.gitlab-ci.sh. По мере добавления функциональности, количество кода в нём будет расти и ориентироваться в нём будет всё сложнее и сложнее.




К счастью, если в процессе загрузки скрипта в нём встречается вызов функции include, файл, указанный в аргументе этой функции также загружается. При этом не важно, в каком репозитории находится загружаемый скрипт. Функция include всегда загружет файл из того же репозитория, из которого она вызвана.




Также хочу отметить, что эта функция всегда загружает каждый файл только один раз, даже если вызов функции include с этим файлом встретится несколько раз.




Поэтому мы можем разбить наш файл dockerfiles.gitlab-ci.sh на несколько более мелких:




Файл dockerfiles.gitlab-ci.sh:




include "tasks/search.sh"
include "tasks/docker.sh"




Файл tasks/search.sh:




DOCKERFILES=""

function search_all_dockerfiles_task() {
    DOCKERFILES=$(find "dockerfiles" -name "*.Dockerfile" -type f)
}

function search_changed_dockerfiles_task() {
    DOCKERFILES=$(git diff --name-only HEAD HEAD~1 -- 'dockerfiles/***.Dockerfile')
}




Файл tasks/docker.sh:




function build_and_push_dockerfiles_task() {
    docker login "$CI_REGISTRY" 
        --username "$CI_REGISTRY_USER" 
        --password "$CI_REGISTRY_PASSWORD"

    for dockerfile in $DOCKERFILES; do
        path=$(echo "$dockerfile" | sed 's/^dockerfiles///' | sed 's/.Dockerfile$//')
        tag="$CI_REGISTRY/$CI_PROJECT_PATH/$path:$CI_COMMIT_REF_SLUG"

        echo "Building $dockerfile..."
        docker build --file "$dockerfile" --tag "$tag" .

        echo "Pushing $tag..."
        docker push "$tag"
    done
}




Ссылки на проект:







Заключение




В данной статье мы прошли по шагам создания переиспользуемого пайплайна для GitLab CI с использованием bash и GitLab CI Bootstrap.




Чтобы не увеличивать статью я описал только самые основные возможности своего микрофреймворка. Кроме этого он также позволяет:




  • Подключать скрипты из нескольких репозиториев по цепочке. Т.е. общий пайплайн, вынесенный в отдельный репозиторий сам по себе может также подключать скрипты из ещё нескольких репозиториев. Это создаёт большой простор для создания целых библиотек для GitLab CI.
  • Получать доступ к произвольным файлам в подключаемом репозитории. Это можно использовать для написания скриптов на других языках, таких как Python или C#.
  • Делать хуки на определённые функции. Это позволяет разработчикам, использующим общий пайплайн расширять его логику при необходимости.




Если нужно, я могу рассказать про эти возможности в комментариях.




Я пока не могу сказать, что мой микрофреймворк близок к версии 1.0. Ещё есть вещи, над которыми стоит поработать. Тем не менее, ранние его версии я успешно использовал в нескольких проектах и это действительно сильно упростило мне разработку.




Исходники: можно найти тут: https://gitlab.com/chakrygin/bootstrap




Репозитории с примерами тут:







Источник: https://habr.com/ru/post/557682/



2022-07-28T23:36:15
DevOps