Будуємо динамічний environment за допомогою Kubernetes та Gitlab CI
Вітаю спільноту DOU! Мене звати Юлій, я DevOps в UNI-OPS. Хочу розповісти про цікавий кейс з побудови динамічного environment за допомогою Kubernetes та Gitlab CI.
Як нам це допоможе? Дуже часто ми маємо проблему та постійну необхідність в тестуванні нових функцій і окремому середовищі для розробки. Найчастіше це призводить до запитів DevOps-команди щодо налагодження нового середовища або робить процес розробки поступовим. Тому є гарне рішення, яке дозволить зняти зайвий час на підняття нових ресурсів та налагодження їх під необхідні параметри та умови.
У нас є дуже корисні інструменти для побудови такої інфраструктури, яка б дозволила розробникам самотужки зробити деплоймент потрібної гілки та перевірити поточний код без потреби вимагати окремий сервер у DevOps-команди. Наша інфраструктура буде працює за допомогою Gitlab CI, Kubernetes та Cloudflare.
Процес вимагає від розробника лише зробити коміт у потрібну гілку та отримати URL по закінченню CI за декілька хвилин.
Схема базується на окремих гілках. Є «Main» та «Develop» — основні гілки для розробки, а ще використовуємо «feature/» гілки для впровадження нових функцій або тестування нових інструментів на проєкті.
Налаштування Kubernetes
Почнемо з додавання потрібних плагінів до Kubernetes.
Для цього нам знадобиться external-dns. Він створений для автоматичного додавання DNS A records до DNS-менеджера. Він працює як з Cloudflare, який ми сьогодні використовуємо, так і з багатьма іншими платформами (AWS route 53, Google Cloud DNS, Hetzner, OVH etc.).
У цьому helm chart нам потрібно змінити ресурси, з якими ми будемо працювати (у нашому кейсі — це ingress):
sources:
# - crd
# - service
- ingress
# - contour-httpproxy
Та шукаємо потрібного провайдера:
provider: cloudflare
Далі прописуємо наш API-ключ від Cloudflare. Надаємо потрібні доступи до доменів, якими він може керувати. Його можливо створити за цією адресою.
Далі — завантажуємо до Kubernetes цей плагін:
## Cloudflare configuration to be set via arguments/env. variables
##
cloudflare:
## @param cloudflare.apiToken When using the Cloudflare provider, `CF_API_TOKEN` to set (optional) ##
apiToken: ""
## @param cloudflare.apiKey When using the Cloudflare provider, `CF_API_KEY' to set (optional) ##
Також можливо додати nginx-ingress controller для більш детального налаштування ingress та керування доступом до хостів.
Ще можливо додати Kubernetes Janitor. Він допоможе нам прибирати ресурси, які вийшли по часу. Наприклад, можна створити правило для кожного namespace
або deployments
, де він буде жити лише обмежену кількість часу. Додається Annotation
до ресурсу janitor/ttl=24h
.
Все. У нас налаштована автоматизація на ingress-ресурс та можна йти далі.
Репозиторії GitLab
Ми маємо у проєкті gitlab-ci.yml файл, у котрому є кроки з лінтером, unit-test, білдом Docker-образа та деплоєм у Kubernetes:
stages:
- linting
- test
- build
- deploy-develop
- deploy-features
- deploy-prod
variables:
DOCKER_DRIVER: overlay2
TAG: "$CI_COMMIT_SHORT_SHA"
BRANCH: "$CI_COMMIT_REF_SLUG"
.default_rules:
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
when: never
linter:
stage: linting
image: registry.gitlab.com/pipeline-components/jsonlint:latest
script:
- |
find . -not -path './.git/*' -name '*.json' -type f -print0 |
parallel --will-cite -k -0 -n1 jsonlint -q
rules:
- !reference [.default_rules, rules]
- if: $CI_COMMIT_BRANCH
unit-test:
stage: test
image: node:20-alpine
script:
- npm install && npm cache clean --force
- ./node_modules/.bin/jest --ci --reporters=default --reporters=jest-junit
artifacts:
when: always
reports:
junit:
- junit.xml
rules:
- !reference [.default_rules, rules]
- if: $CI_COMMIT_BRANCH
Build:
stage: build
image: bitnami/kaniko:1.23.0
script:
- /kaniko/executor --context "${CI_PROJECT_DIR}" --dockerfile "${CI_PROJECT_DIR}/Dockerfile" --destination "${CI_REGISTRY_IMAGE}:$CI_COMMIT_SHORT_SHA"
rules:
- !reference [.default_rules, rules]
- if: $CI_COMMIT_BRANCH
environment:
name: $BRANCH
deploy-prod:
stage: deploy-prod
variables:
BRANCH: "$CI_COMMIT_REF_SLUG"
TAG: "${CI_COMMIT_SHORT_SHA}"
trigger:
project: devops/helm/example-helm-build-app
when: manual
only:
- master
deploy-features:
stage: deploy-features
variables:
BRANCH: "$CI_COMMIT_REF_SLUG"
TAG: "${CI_COMMIT_SHORT_SHA}"
trigger:
project: devops/helm/example-helm-build-app
rules:
- !reference [.default_rules, rules]
- if: '$CI_COMMIT_BRANCH =~ /^feature.*$/'
when: manual
deploy-develop:
stage: deploy-develop
variables:
BRANCH: "$CI_COMMIT_REF_SLUG"
TAG: "${CI_COMMIT_SHORT_SHA}"
trigger:
project: devops/helm/example-helm-build-app
rules:
- !reference [.default_rules, rules]
- if: '$CI_COMMIT_BRANCH == "develop"'
На виході в нас буде побудований наступний пайплайн, у котрому ми маємо downstream job deploy-features:
Схема, що ілюструє, як налаштований зв’язок між репозиторіями:
Репозиторій з Helm Chart
У репозиторії з Helm ми маємо стандартний helm chart та свій gitlab-ci.yml
gitlab-ci.yml:
stages:
- linting
- Deploy
- Cleanup
variables:
DOCKER_DRIVER: overlay2
TAG: "$CI_COMMIT_SHORT_SHA"
BRANCH: ${BRANCH}
linter:
stage: linting
image: registry.gitlab.com/pipeline-components/jsonlint:latest
script:
- |
find . -not -path './.git/*' -name '*.json' -type f -print0 |
parallel --will-cite -k -0 -n1 jsonlint -q
only:
- branches
Deploy:
stage: Deploy
image: alpine/helm
script:
- mkdir ~/.kube
- echo "$CI_KUBECONFIG" > ~/.kube/config
- export KUBECONFIG=~/.kube/config
- helm upgrade --install --namespace $BRANCH --create-namespace $CI_PROJECT_NAME . --set "image.tag=$TAG" --set "ingress.hosts[0].host=$CI_PROJECT_NAME-$BRANCH.example.com,ingress.hosts[0].paths[0].path=/,ingress.hosts[0].paths[0].pathType=ImplementationSpecific"
environment:
name: $CI_PROJECT_NAME-$BRANCH
url: https://$CI_PROJECT_NAME-$BRANCH.example.com
on_stop: stop_env
stop_env:
stage: Cleanup
image: alpine/helm
script:
- mkdir ~/.kube
- echo "$CI_KUBECONFIG" > ~/.kube/config
- export KUBECONFIG=~/.kube/config
- helm uninstall --namespace $BRANCH $CI_PROJECT_NAME
environment:
name: $CI_PROJECT_NAME-$BRANCH
url: https://$CI_PROJECT_NAME-$BRANCH.example.com
action: stop
when: manual
Для створення динаміки нам потрібен параметр
-set "image.tag=$TAG" --set "ingress.hosts[0].host=$CI_PROJECT_NAME-$BRANCH.example.com,ingress.hosts[0].paths[0].path=/,ingress.hosts[0].paths[0].pathType=ImplementationSpecific"
Зверніть увагу: на старих версіях ingress та Kubernetes <=1.19-0 синтаксис до тіла ingress матиме інакший вигляд. Перевірити це можливо в ingres.yml файлі "apiVersion: networking.k8s.io/v1"
.
Тепер ми маємо повністю робочий процес, в якому розробник може зробити гілку свою та отримати домен після виповнення всього CI.
Release "example-app" has been upgraded. Happy Helming!
NAME: example-app
NAMESPACE: develop
STATUS: deployed
REVISION: 1
NOTES:
1. Get the application URL by running these commands: https://example-app-ticket-53.example.com
Cleaning up project directory and file based variables
00:00
Job succeeded
Розробник бачить у GitLab після виконання CI посилання до свого проєкту.
Дякую за увагу. Сподіваюсь, вам була цікавою та корисною ця стаття. Відповім на всі питання у коментарях!
6 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарів