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

  • Вступление с подводками, что за клиент пришёл, какие у него были проблемы, что нужно было закрыть первым приоритетом, какие задачи были в долгой перспективе?

  • Описание инфраструктуры.

  • Представление решений. Рассказ про схему унифицированности подхода CI/CD.

  • Внутренняя инфраструктура и как проще, но правильнее ЛИ?

  • "Скорость" и "качество" синонимы? 

  • Итоги с “грустными” выводами, но хорошей моралью.

Ценность

  • Для инженеров: понимание общей концепции деплоя и осознания, какой путь правильный, а какой ведёт к проблемам или провалу при будущем администрировании.

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

Глава 1: Надо было вчера

Каждый человек, который имеет отношение к IT, будь то ПМ, разработчик, Sales или DevOps, рано или поздно за свою карьеру столкнётся с фразой “надо было вчера”. К сожалению, при погружении в эту ситуацию можно прийти к выводам, что никто не виноват в причинах ее возникновения.

  • Не получилось найти общий язык с предыдущим подрядчиком.

  • Долго происходил розыгрыш тендера, а бизнес не ждет.

  • Предыдущие сотрудники не смогли закрывать необходимый объем задач.

  • Нехватка своих кадров.

В целом, всё это не удивительно и знакомо, волей случая и мне удалось стать участником таких событий. Относительно недавно к нам пришёл проект, у которого уже были сформулированы требования к будущей инфраструктуре, содержащие в себе следующие задачи: несколько окружений для разработки, отказоустойчивость, быстрота деплоя будущих приложений, мониторинг и стабильная доступность работоспособности приложений. Исходно сроки весьма сжатые, а список микросервисов составляет около 18 штук. Что же делать и как быть?

Глава 2: Пойми свой путь и сможешь его пройти

Чтобы не пугаться больших поставленных задач, рекомендуется изначально разбивать их на более мелкие. Если речь идёт про 3 контура для разработки, то нам будет достаточно лишь одной схемы архитектуры для понимания всех будущих задач. В нашем случае она имеет весьма простые составляющие, о каждой из которых по порядку:

Первое, о чем хочется сказать — это оркестратор контейнеров. Ранее уже был развернут стандартный кластер kubernetes, как managed решение от Azure. Важным пунктом в этом процессе является выбор 3-х worker нод кластера в различных зонах доступности (AZ1,AZ2,AZ3). Посыл данного решения весьма понятен и не завязан на ограничении по количеству допустимых конфигураций виртуальных машин в регионе. Его целью является повышение отказоустойчивости и доступности микросервисов, так как каждый знает, что могут возникнуть проблемы в одной из зоны облака, хотя и вероятность данного события для каждого уважаемого облака меньше 1%.

Второй немаловажный момент — нам нужно понимать, как запрос будет поступать и проксироваться внутри нашего кластера. Здесь стандартным решением является использование излюбленного и гибкого продукта ingress-controller-nginx, разворачивание которого создает LoadBalancer в рамках облака, готового принимать запросы на внешний IP адрес и проксировать в дальнейшем внутри инфраструктуры запрос в приложение.

Открывая любой сайт в интернете, мы всегда используем https протокол, и тут перед нами снова встает выбор: “как нам организовать наличие SSL сертификата при запросе к нашим доменам?”. Но чтобы ответить на этот вопрос, нужно понимать, с чем мы будем иметь дело. 

  1. У нас будет использоваться 1 единый домен 2-ого уровня с разделением трафика до микросервисов через URL path

  2. Мы будем использовать различные домены 3-его уровня, создавая А-запись с “*” и резолвя все запросы на External IP

  3. Мы будем использовать различные виды доменов не подвязываясь под всё что описано выше?

Не важно какой случай ваш, ведь у вас всегда будет 3 варианта пути.

  1. Деплой cert-manager и создания сущности ClusterIssuer для автоматического выпуска и перевыпуска бесплатных SSL сертификатов от центра сертификации Let’s Encrypt. Это практично, легко и удобно, а самое главное — бесплатно.

  2. Покупка платных сертификатов и прокидывание их на уровне каждого ingress в виде secret в том же namespace, где располагается pod с контейнером. 

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

  1. Использовать один единный default сертификат для всех наших ingress на уровне ingress-controller. Для этого необходимо лишь во время деплоя нашего Helm чарта указать extra arguments в следующем виде:

controller:
  kind: Deployment
  extraArgs:
    default-ssl-certificate: "kube-system/ssl-crt"

Где в первую очередь идёт имя namespace, а во вторую имя secret-а.

Третий немаловажный момент, про который все любят забывать и из-за которого можно встрять в очень неприятную ситуацию — мониторинг. В рамках стандартных задач мониторинга чаще всего используется PGA (Prometheus Grafana Alertmanager), их разворачивание, путём использования Helm чарта kube-prometheus-stack, при правильной настройке может покрыть весь ваш кластер и помочь обложиться минимально необходимым набором метрик. В совокупности эти инструменты могут с легкостью дать визуальное отображение работоспособности инфраструктуры и всех ее основных моментов, а также при настройке триггеров могут оповестить заранее о наличии проблемы в любой удобный канал общения (Telegram, Slack, Mattermost, mail). Согласитесь, с точки зрения бизнеса о наличии возможной или текущей проблемы лучше всего узнавать самому, а не от конечного пользователя.

Пункт четыре: не надо делать всё, что есть только для администраторов. Иногда помощь нужна и разработчикам.

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

Способов сбора и хранения логов существует множество. Стек технологий, которые это покрывают, весьма большой, а выбор того или иного приложения в вашем случае должен зависеть от конечных потребностей. Если ваши запросы весьма простые и у вас нет необходимости обрабатывать и хранить большое количество информации, то ваш выбор Promtail и Loki, но несмотря на новаторство данных средств, у них есть значительных ряд минусов, которые нас не устраивали. Именно по этим причинам был с радостью выбран стек ELK (ElasticSearch Logstash Kibana) и за ними прячется filebeat.

«Почему именно они?»:

  • Предполагается большой поток данных в рамках каждого контура

  • Нам нужна возможность кластеризации.

  • Наличие web-интерфейса с детальной настройкой паттернов.

  • Возможность распределение привилегий доступов различным пользователям.

  • Индивидуальные настройки ротации.

Немного про архитектурные особенности микросервисов. Нам требовались Redis для хранения кеша микросервисов и Minio для хранения статики, например фотографий. Каждый, кто хоть немного понимает принцип работы облака, с ходу может задать вопрос:

- Зачем вам Minio
- Храните статику в PVC предоставляемых на уровне облака Azure или же используйте просто blob-fuse-azure.

Но всё не так просто, как кажется. Разработчики, как и само приложение, нуждались в бакетах, имеющих работу протокола по принципу S3. И этими словами уже нельзя описать работу blob-fuse от Azure. Также была необходимость в web-интерфейсе для работы со статикой и возможность раздачи контента с URL path.

Любимый и часто возникающий вопрос демагогий, в рамках которого каждый имеет своё мнение — это база данных. Чтобы ускорить время обработки запроса между микросервисом и БД, можно произвести ее разворачивание в кластере k8s через statefullset и pvc, но является ли это решение правильным с точки зрения поддержки и обслуживания? Я думаю нет, так как БД всегда лучше всего располагать либо на отдельных серверах, либо использовать managed, ведь это принесёт вам следующие плюсы:

  • Минимизация затрат. В рамках каждого облачного manage решения вы всегда платите только за то, что используете.

  • Легкость масштабируемости

  • Безопасность и производительность как гарант со стороны облака.

  • Отсутствие необходимости в детальном тюнинге и настройке.

  • Встроенные возможности создания snapshot-ов и бэкапов.

Глава 3: Размер имеет значение

Любая крупная архитектура всегда сложна как в правлении, так и в контроле, но самое сложное — разворачивание. Так как kubernetes в своей основе имеет декларативный подход, отсюда можно сделать вывод о том, что нам нужно не говорить системе “что” надо делать, а описывать “как” она должна выглядеть. И здесь появляется золотое правило, которое гласит: 

/// успех всего процесса — это IaC (Infrastructure As Code). 

Инструменты, помогающие нам решить этот вопрос — Terraform и Helm. Почему же они полезны?

  1. Сохранение состояния релиза приложения или ресурса.

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

  3. И как бы это странно и смешно не звучало, но ещё один пункт — это уважение к будущим или текущим коллегам. Все правки в инфраструктуру должны быть закоммичены и описаны в виде манифестов. Это дает всем единую точку управления ресурсом и позволяет понять, как реализована инфраструктура без глубокого погружения.

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

//// А нужно ли это кому-то? 

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

Способов деплоя в Gitlab существует бесконечно много, ручное создание и генерация ServiceAccount-ов c необходимыми ClusterRole или обычными Role. Использование интеграций внутри системы управления репозиториями, создание шаблонных .gitlab-ci.yml и универсальных чартов или же кастомизированные решения. В случае, если вам нужен единый и быстрый деплой, а все ваши микросервисы имеют идентичную структуру, то вы можете использовать универсальный CI/CD. Создаём отдельный репозиторий и описываем общие блоки для билда и деплоя нашего микросервиса.

Единожды описав 2 стадии общим шаблоном для build Dockerfile:

.build_template: &build
  image:
    name: gcr.io/kaniko-project/executor:debug
    entrypoint: [ "" ]
  script:
    - mkdir -p /kaniko/.docker
    - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
    - /kaniko/executor --context . --dockerfile ./Dockerfile --destination ${CI_REGISTRY_IMAGE}:${TAG}

И деплоя:

.deploy_template: &deploy
  image: 
    name: alpine/k8s:1.22.6
    entrypoint: [""]
  script:
    - helm upgrade --create-namespace --install ${CI_PROJECT_NAME} --namespace ${NAMESPACE} helm -f ${FILE} --insecure-skip-tls-verify --kubeconfig ~/.kube/config --set "pods.env=${IMAGES_ENV}" --set "microservicename=${CI_PROJECT_NAME}" --debug

Можно создать общий default-gitlab-ci.yml с включением в себя этих глобальных job:

include:
  - project: ‘$PROJECT_PATH’  ## имя проекта/путь до вызываемого файла
    ref: main                                ## ветка репозитория, в рамках которой лежит файл
    file: '.build.yml'                       ## имя вызываемого файла

  - project: ‘$PROJECT_PATH’
    ref: main 
    file: '.deploy.yml'

stages:
  - build
  - deploy

build:
  extends: .build_template          ## вызов шаблона деплоя из include
  stage: build                               ## имя стадии
  variables:
    TAG: "${CI_PIPELINE_ID}"
  only:
    refs:
      - /^release\/.*$/

deploy-dev:
  extends: .deploy_template ## вызов шаблона деплоя из include
  when: manual              ## включение ручного деплоя
  stage: deploy               ## имя стадии
  variables:
    TAG: "${CI_PIPELINE_ID}"  ## образ build-а контейнера
    NAMESPACE: "dev"    ## namespace куда деплоится микросервис
FILE: "helm/dev.yml" ## путь до файла values Helm внутри репозитория микросервиса
  environment:
    name: dev                 ## окружение для деплоя
  only:
    refs:
      - /^release\/.*$/        ## тригер джобы только в release ветках

/// Как быть дальше? 

Ответ прост: указываем на уровне настроек каждого репозитория, какой .gitlab-ci.yml мы желаем использовать:

Глава 4: Скорость и качество это синонимы?

После проделанной работы всегда нужно ответить на вопрос: что мы получаем в конечном счёте?

  1. У нас есть отказоустойчивая инфраструктура в различных зонах доступности для наших микросервисов. Если у нас корректно настроены nodeAffinity и мы имеем по 3 реплики для каждого микросервиса, то даже падение двух нод не приведет к недоступности нашего ПО.

  2. Мы имеем мониторинг и логирование, что повышает время анализа инцидентов и исключает возможные простои.

  3. У нас есть бэкапы данных наших пользователей и мы имеем гарантирую сохранения всех данных.

  4. У нас есть описанная инфраструктура, которую может с лёгкостью администрировать другой человек и завязки процессов на одном сотруднике.

  5. У нас имеется общая концепция гибкого процесса CI/CD, которая позволяет быстро добавлять новые микросервисы/фичи для клиента и соответствовать принципам Time To Market на рынке.

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

Существует множество различных решений для различных проблем. Возможно, что пока статья создавалась, были разработаны новые подходы в деплое микросервисов и в способах построения архитектуры. Нельзя почти всегда говорить, что какой-то вариант правильный, а какой-то нет. Каждый случай нужно разбирать сугубо индивидуально, но зачастую банальная шаблонизация ведёт к улучшению понимания концепции инфраструктуры и к её дальнейшему масштабированию. Придя на весьма большой проект и имея сжатые сроки, нужно всегда понимать, как разбить большую задачу на маленькие. 

И как не сорвать сроки сдачи, а перенести в зону комфорта.

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


  1. zergon321
    17.08.2023 06:05

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


    1. AlexSheverev Автор
      17.08.2023 06:05

      Вопрос немного риторический, однако ответ можно сформулировать как "потому что мы все одна команда и у нас есть задача выполнить свою работу, прикрывая друг-друга" ;)


      1. zergon321
        17.08.2023 06:05

        Вот только крайним всегда будет далеко не мэнэджмент