Глава 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-инженера это, само собой, реализация подхода деплоя и здесь, когда спрашиваешь у коллеги: «А как сделали вы?», возникает целая дискуссия с дебатами и беспрерывными выяснениями, а кто же прав.

Поехали? :) 

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

  1. Распределяйте все микросервисы по раздельным репозиториям в рамках определенной группы или подгруппы — это упростит предоставления доступов для каждого проекта, а также поможет в реализации использования Gitlab CI/CD variables. Тут же хочется поделиться, что периодически встречается архитектура, когда в рамках одного репозитория лежит 20 каталогов с различными микросервисами, где используются разные подходы и исходные образы для деплоя. «Зачем и почему?» — вопросы хорошие. 

  2. Определяйте переменные для Gitlab CI/CD пайплайнов глобально в панели администратора или на уровне групп, подгрупп. Согласитесь, дублировать одну и ту же переменную в рамках каждого репозитория — себя не уважать. Особенно будет интересно править их все после изменения переменной или протухания токена.

  3. Отойдите от концепции использования кастомных .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
  1. Если вы используете Gitlab CI/CD variables и у вас есть переменные, идентичные по названию, но разные по содержанию — всегда используйте Environments.

build dev-kg:
    stage: build
    services:
        - docker:dind
    environment:
        name: dev-kg

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

  1. Выберите свой подход в процессе 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 надо будет кардинально подготовиться и переключить все существующие деплои, потеряв часть приятного интерактивного функционала и минусом является даже не это, а то, что вероятнее всего в будущем ситуация повторится и нужно предельно внимательно следить за релизами и это, по сути, является императивной нормой любого продукта.

  1. Обновления — от них не застрахован никто.

Весьма частая и регулярная задача, с которой сталкиваются администраторы и 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 — мы всегда рады новым друзьям!

Комментарии (1)


  1. Apoheliy
    12.09.2023 15:43
    -2

    В текущих реалиях все IT-продукты разрабатываются с использованием какого-либо ПО, способного управлять репозиториями программного кода для Git.

    Сразу видно - автор не потерпит альтернативного мнения.

    Сарказм: компиляторы, ide, редакторы, статические анализаторы, библиотеки, санитайзеры, +100500. Не, не слышали!