Глава 1: То, что и так видят все, но не каждый готов признать
Новый день, солнце, душ, кофе и инженер садится заниматься своими задачами по созданию или обновлению продукта. В свою очередь пользователь находится по другую сторону баррикад — использует готовый продукт, который упрощает его жизнь. Время показывает, что любой развивающийся бизнес должен идти в ногу со временем. По большей части, суть этой концепции в том, что каждый человек в этой цепочке чего-то хочет. Получается, что быстро придуманная идея — требует быстрой реализации.
В текущих реалиях все IT-продукты разрабатываются с использованием какого-либо ПО, способного управлять репозиториями программного кода для Git. В нашем случае, хотелось бы рассказать про один из самых популярных продуктов Gitlab. «Gitlab — наше всё» должно быть слоганом каждой компании, которая его использует, иначе могут произойти события, которые приведут к печальным последствиям. На Habr можно найти множество различной информации, связанной с кейсами, туториалами или просто интересными историями про GitLab. Но сколько бы ни было написано, найти место где было бы собрано всё и сразу — не получилось. Придется исправлять.
Начнём?

Глава 2: Latest говорит не только о том, что релиз последний, но и о том, что всем вашим релизам может прийти конец
Новый день, солнце, душ, кофе и разработчик садится разбирать эпик. Первое, что он там видит — это деплой в среду тестирования нового функционала. Смело вешая тэг на master ветку, его релиз уверенно падает на стадии build-а образа контейнера. Как такое могло произойти и почему так вышло? Неожиданный результат, опубликованный в рамках канала slack, создаёт отклик в душе других сотрудников компании.
Коллеги, не работают все процессы CI/CD, все релизы и тесты падают с различными ошибками.
Как бы это странно ни звучало, но все Devops-инженеры, изучая проблему, приходят в лёгкое удивление, ведь причина возникновения множества ошибок непонятна. То, что вчера ещё работало — сегодня уже остановило весь процесс разработки.
Лучше всегда всё начинать сначала, этот подход помог и здесь. Ранее при первоначальных настройках различных pipelines во все проектах для стадий build, test и deploy использовался всем известный подход DIND (Docker-in-Docker) и тут нас не может не радовать, что образ для всех job имеет тэг latest.
build_rc: stage: build image: docker:dind
Вечерним релизом (накануне проблемы) разработчики продукта docker произвели обновление версии docker в образе dind до 24.0 и все описанные старым способом процессы сборки, тестирования и деплоя начали падать с ошибками docker. Быстрое понимание проблемы помогло найти и решение, изменив текущий tag образа в каждой джобе на 23.0-dind, но и тут возникла весьма интересная проблема, которая говорит об отсутствии детального понимания принципов настройки Gitlab и его раннеров. Образ джобы не был описан глобально, а в индивидуальном варианте для каждого pipeline. «Зачем?» — остается загадкой. В процессе настройки runner-ов каждый имеет свой подход:
-
Закрепление runners за определенными группами, подгруппами, репозиториями.
-
Настройка runners для деплоя джоб имеющих соответствующий ему tag.
-
Подключение runners глобально на уровне панели администратора, предоставляя доступ к нему всем проектам вашего Gitlab.
Если же вы используете именно последний подход, то самым простым и правильным вариантом решения для вас будет настройка образа джобы именно на уровне этого раннера в config.toml:
concurrent = 10 check_interval = 0 shutdown_timeout = 0 [session_server] session_timeout = 1800 [[runners]] name = "runner-01.git.test.ru" url = "https://git.test.ru/" id = 44 token = "super_secure_token" token_obtained_at = 2023-07-13T10:24:48Z token_expires_at = 0001-01-01T00:00:00Z executor = "docker" [runners.cache] MaxUploadedArchiveSize = 0 [runners.cache.s3] [runners.cache.gcs] [runners.cache.azure] [runners.docker] tls_verify = false image = "docker:23.0-dind" privileged = true disable_entrypoint_overwrite = false oom_kill_disable = false disable_cache = false volumes = ["/cache"] shm_size = 0
Что получается в итоге?
При отсутствии описания во всех ваших .gitlab-ci.yml образа джобы, по умолчанию будет вызываться необходимый нам образ docker:23.0-dind. В результате подобного инцидента, который произошёл фактически из-за очень маленькой ошибки разработки, трудозатраты DevOps-инженеров на причину понимания проблемы, глобальные правки во всех манифестах .gitlab-ci.yml, которые не имели бы никакого смысла при исходно правильной настройке.

Глава 3: Сохранись, а то мало ли
Тот же день, пекло, обед, сэндвич, латте, yaml манифесты и IaC (Infrastructure-as-Code).
IaC — это очень популярный и полезный подход, так как если мы говорим про Kubernetes, то в первую очередь мы подразумеваем декларативный подход. Соответственно, когда мы планируем использовать Terraform с использованием облачного провайдера для описания нашей инфраструктуры, то наш путь исконно верный. Ещё один интересный кейс, с которым мы столкнулись в схожей проблеме, был завязан вновь на неправильном подходе или настройке Gitlab.
Описав развертывание большого количества виртуальных машин в облаке и параллельно настроив сеть для них, межсетевое взаимодействие, security groups, релиз был успешно запушен в репозиторий, где посредством заранее описанного процесса CI/CD выполнялись стадии validate, plan, apply и сохранение terraform.state в Infrastructure/Terraform.
После успешного отрабатывания всех 3-х стадий, можно фактически убедиться в веб-панели облака о наличии развернутых ресурсов, но каким было удивление, когда было обнаружено, что terraform.state был записан не полностью? Получается, все повторные запуски нашего pipeline при изменении ресурса имели безукоризненное падение, а при анализе вывода успешно отработанных джоб результат посмотреть не удавалось из-за ограниченности вывода количества информации в output.
Как быть и как этого избежать?
Вариантов решения данного вопроса множество, в первую очередь надо не забывать о существующих ограничениях в Gitlab на размер вашего terraform.tfstate:

Во-вторых, не забываем поднимать лимит по количеству строк для gitlab-runners, добавляя output_limit в основной блок:
concurrent = 10 check_interval = 0 shutdown_timeout = 0 [session_server] session_timeout = 1800 [[runners]] name = "runner-01.git.test.ru" url = "https://git.test.ru/" id = 44 token = "super_secure_token" token_obtained_at = 2023-07-13T10:24:48Z token_expires_at = 0001-01-01T00:00:00Z executor = "docker" output_limit = 50000000 [runners.cache] MaxUploadedArchiveSize = 0 [runners.cache.s3] [runners.cache.gcs] [runners.cache.azure] [runners.docker] tls_verify = false image = "docker:23.0-dind" privileged = true disable_entrypoint_overwrite = false oom_kill_disable = false disable_cache = false volumes = ["/cache"] shm_size = 0
В-третьих, очень важным моментом является разделение создаваемых ресурсов Terraform на различные модули. Как пример, вынос создания Network и Security groups в отдельные модули и дальнейшее переиспользование готовых сущностей через data sources для упрощения понимания аспектов управления инфраструктурой и уменьшение исходного tfstate.
Глава 4: Процессы, подходы и слёзы
Самая интересная и самая любимая часть каждого Devops-инженера это, само собой, реализация подхода деплоя и здесь, когда спрашиваешь у коллеги: «А как сделали вы?», возникает целая дискуссия с дебатами и беспрерывными выяснениями, а кто же прав.
Поехали? 🙂
Каждый любит строить проект и процессы деплоя с нуля, но если вы находитесь на любой из стадий, то обратите сразу внимание на ряд принципиально важных моментов, которые упростят вам жизнь.
-
Распределяйте все микросервисы по раздельным репозиториям в рамках определенной группы или подгруппы — это упростит предоставления доступов для каждого проекта, а также поможет в реализации использования Gitlab CI/CD variables. Тут же хочется поделиться, что периодически встречается архитектура, когда в рамках одного репозитория лежит 20 каталогов с различными микросервисами, где используются разные подходы и исходные образы для деплоя. «Зачем и почему?» — вопросы хорошие.
-
Определяйте переменные для Gitlab CI/CD пайплайнов глобально в панели администратора или на уровне групп, подгрупп. Согласитесь, дублировать одну и ту же переменную в рамках каждого репозитория — себя не уважать. Особенно будет интересно править их все после изменения переменной или протухания токена.

-
Отойдите от концепции использования кастомных .gitlab-ci.yml в рамках каждого процесса деплоя. Пишите шаблонизированные .gitlab-ci.yml, подвязываясь под внутренние переменные самого Gitlab, для реализации этого подхода вполне прекрасно подходят include, extends, reference. В дальнейшим их можно с лёгкостью переиспользовать для деплоя других микросервисов:
Глобальный конфиг build:
.build_template: &build image: nexus.test.ru/generic-images/dind:dind-latest variables: DOCKER_HOST: tcp://docker:2375/ DOCKER_DRIVER: overlay2 DOCKER_TLS_CERTDIR: "" services: - docker:dind script: - docker login -u $CI_REGISTRY_USER_NEXUS -p $CI_REGISTRY_PASSWORD_NEXUS $CI_REGISTRY_NEXUS - docker build --no-cache -t ${CI_REGISTRY_NEXUS}/k8s-${CI_PROJECT_NAMESPACE}/${CI_PROJECT_NAME}:${CI_COMMIT_SHA} . - docker push ${CI_REGISTRY_NEXUS}/k8s-${CI_PROJECT_NAMESPACE}/${CI_PROJECT_NAME}:${CI_COMMIT_SHA}
Глобальный конфиг deploy:
.deploy_template: &deploy image: name: nexus.test.ru/generic-images/k8s-deployer:latest variables: HELM_KUBECONTEXT: ${KUBE_CONTEXT} script: - helm repo add nixys https://registry.nixys.ru/chartrepo/public - helm repo update - helm upgrade --install ${CI_PROJECT_NAME} nixys/universal-chart --namespace ${NAMESPACE} --insecure-skip-tls-verify --set "defaultImage=${CI_REGISTRY_NEXUS}/k8s-${CI_PROJECT_NAMESPACE}/${CI_PROJECT_NAME}" --set "defaultImageTag=${CI_COMMIT_SHA}" --set "secretEnvsString=${VAULT_IMAGES_ENV}" --create-namespace --values ${FILE} --version 2.4.0 --debug
Частный конфиг в виде джоб в pipeline:
include: - project: ‘test-devops/ci-cd/sample-ci-cd' ref: main file: '.build.yml' - project: 'test-devops/ci-cd/sample-ci-cd' ref: main file: '.deploy.yml' stages: - build - deploy ### # PROD ### build: extends: .build_template stage: build tags: - dind variables: KUBE_CONTEXT: ${CI_PROJECT_NAMESPACE}/k8s-agents:kubernetes-ya-default-cluster only: refs: - main deploy-prod: extends: .deploy_template when: manual stage: deploy tags: - k8s-runner-prod needs: - job: build variables: KUBE_CONTEXT: ${CI_PROJECT_NAMESPACE}/k8s-agents:kubernetes-ya-default-cluster NAMESPACE: "prod" FILE: ".helm/prod.yml" environment: name: production only: refs: - main ### # DEV ### deploy-dev: extends: .deploy_template when: manual stage: deploy tags: - k8s-runner-dev needs: - job: build variables: KUBE_CONTEXT: ${CI_PROJECT_NAMESPACE}/k8s-agents:kubernetes-ya-develop-cluster NAMESPACE: "dev" FILE: ".helm/dev.yml" environment: name: dev only: refs: - develop
-
Если вы используете Gitlab CI/CD variables и у вас есть переменные, идентичные по названию, но разные по содержанию — всегда используйте Environments.
build dev-kg: stage: build services: - docker:dind environment: name: dev-kg

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

-
Выберите свой подход в процессе Continuous Deployment/Delivery приложений и микросервисов, если вы работаете с kubernetes. На данный момент есть несколько вариантов деплоя.
6.1. Деплой в kubernetes, OLD средствами.
Старое — не значит неработающее. Создаём Namespace, ServiceAccount, Secret, rbac policy и генерируем для него kubeconfig, с помощью которого можно будет безопасно и уверенно совершать деплой в определённый Namespace.
Удобен ли данный вариант? — Вряд ли
Но он помогает детально понимать все аспекты деплоя со стороны безопасности в рамках кластера k8s и опять же в данном способе нет никаких завязок на стороннее ПО. При этом можно с легкостью автоматизировать весь этот процесс, написав Terraform модуль на основе kubernetes provider и gitlab provider. Но если безопасность у вас на данный момент не на первом месте и в целом вы человек прогрессивный, то никто не мешает обратиться к современным подходам.
Перейдём к динозаврам…
6.2. Деплой в kubernetes через Gitlab интеграцию (Kubernetes cluster) — да, да, да.
Для тех кто не знал, расскажу просто и легко. После предоставления ca.crt вашего кластера kubernetes Gitlab-у, вы получаете из группы или подгруппы настроенный деплой из коробки.

Если конкретизировать, то используя ca.crt, Gitlab integration генерирует kubeconfig для деплоя в namespace, добавляя его в виде tmp файла в рамках запущенной джобы вашего pipeline.
Получается все шаги описанные в пункте 1 можно не делать? — да
Помимо упрощения процесса деплоя эта интеграции предоставляет большое количество дополнительных функций. Как пример, при добавлении к deployment annotations можно было получить доступ до shell контейнера прямо из web интерфейса Gitlab.

Но не бывает хороших новостей без плохих, в версии Gitlab 17.0.0 данный функционал возможно уберут и заменят на другой подход реализации процесса интеграции, если ранее процесс деплоя настраивался из Gitlab до K8S через kubeconfig, то теперь процесс работает по принципу взаимодействия:
Gitlab with API Token- > gitlab-agent (предоставляем kubeconfig с правами на деплой)-> kubernetes
6.3. Деплой в kubernetes через Gitlab интеграцию (Kubernetes cluster).
Новый, актуальный, свежий и как уже было написано ранее — безопасный вариант. В настройке он также весьма прост. Делаем инсталляцию helm chart-а готовыми командами, предоставляемыми Gitlab-ом, и создаём конфигурацию в рамках репозитория, в которой мы подключали наш gitlab-agent:

В нашем репозитории необходимо создать схожую структуру с одним yaml конфигом, в рамках которого будут описаны те проекты, которым должны предоставлять возможности деплоя через наш gitlab-agent. Файл лежит по следующему пути и выглядит примерно следующим образом:
.gitlab/agents/kubernetes-ya-default-cluster/config.yaml
ci_access: projects: - id: test/test # Project/Repo name to authorize - id: test/backend - id: test/websocket - id: test/static observability: logging: level: debug
Здесь важно понимать, что вам нужно точечно на уровне репозиториев или глобально на уровне группы предоставить доступ для деплоя через gitlab-agent. Важно заметить, что gitlab-agent не может как и ранее выйти за пределы группы Gitlab. Получается в случае, если у вас 2 группы с микросервисами backend и frontend, то вам придётся для одного кластера создавать и деплоить 2 разных агента. Выглядит как минус и лишняя работа, но это маленький шаг в сторону безопасности. Однако нельзя забывать и про существенный минус, с которым можно столкнуться.
Все инженеры любят (и правильно делают), когда обновляют Gitlab — важным моментом здесь является то, что люди, использующие интеграцию для деплоя, неожиданно для себя и для своей команды узнали об уходе в deprecated старого механизма деплоя. Перед обновлением на 17.0.0 версию Gitlab надо будет кардинально подготовиться и переключить все существующие деплои, потеряв часть приятного интерактивного функционала и минусом является даже не это, а то, что вероятнее всего в будущем ситуация повторится и нужно предельно внимательно следить за релизами и это, по сути, является императивной нормой любого продукта.

-
Обновления — от них не застрахован никто.
Весьма частая и регулярная задача, с которой сталкиваются администраторы и DevOps-инженеры — это обновление Gitlab. Первая ошибка новичка, само собой, обновиться сразу с версии 13 до версии 16.2.1 в 1 шаг. На этот случай Gitlab уже давно разработали весьма удобную площадку, описывающую поэтапность обновления с релизами c вашей версии до последней актуальной. Но самая банальность проблемы заключается в не изучении изменения функционала релиза.
P.S. К ссылочке выше
Используйте VPN, чтобы открыть сайт.
К примеру, все ваши процессы build и deploy контейнера завязаны на использовании вышеописанных глобальных CI/CD переменных:
before_script: - export RELEASE_VERSION=$(echo $CI_BUILD_REF_NAME | grep -o '[[:digit:]]\{1,5\}\.[[:digit:]]\{1,5\}\.[[:digit:]]\{1,5\}' || echo development) build_rc: stage: build services: - docker:dind script: - docker login -u $CI_REGISTRY_USER_NEXUS -p $CI_REGISTRY_PASSWORD_NEXUS $CI_REGISTRY_NEXUS - docker build --no-cache -t ${CI_REGISTRY_NEXUS}/k8s-${CI_PROJECT_NAMESPACE}/${CI_PROJECT_NAME}:${RELEASE_VERSION} . - docker push ${CI_REGISTRY_NEXUS}/k8s-${CI_PROJECT_NAMESPACE}/${CI_PROJECT_NAME}:${RELEASE_VERSION}
В последнем релизе происходит удаление этой переменной или изменения её названия. Результат: все текущие и ранее актуальные pipeline переходят в нерабочий статус, что сразу же грозит простоем разработки и разбором нового инцидента.

Глава 5: Почему не получилось пройти мимо?
Вечер, горячий чай, тёплый ужин и осознание…
Имея под рукой Gitlab, у вас появляется большое количество возможностей и большое количество потенциальных проблем. Регулярные релизы со стороны разработки продукта покрывают оба описанных аспекта ранее, но многое зависит от инженера, реализующего подход как в деплое, так и в поддержке.
Прочитав всё описанное выше, можно сказать, что многие вещи уже рассказаны в разных статьях и мануалах, но каждый раз, подключаясь на проект, видишь одни и те же ошибки или проблемы, которые не могут не огорчать. Порой ошибаются все и никто не безгрешен, но вовремя понять потенциальную проблему, нависшую над текущими рабочими процессами, и исправить её сразу — лучше, чем игнорировать и получать с утра пораньше сообщения о глобальном инциденте всех конвейеров.
Главное — это понять, что правильные решения, видение инфраструктуры и процессов через 6 месяцев, поможет ускорить решение задач как со стороны инженеров, так и со стороны бизнеса. Ведь чем быстрее мы деплоим, тем быстрее растём.
Получается, крепких пайплайнов и быстрых релизов!

Если есть вопросы — пишите в комментариях. И, конечно, подписывайтесь на наш блог Хабр, TG-канал DevOps FM, интернет-издание VC и YouTube — мы всегда рады новым друзьям!
ссылка на оригинал статьи https://habr.com/ru/articles/758068/

Добавить комментарий