
Российские облачные провайдеры начали предоставлять неплохие managed-решения для Kubernetes. Однако многие из них требуют доводки до ума и установки большого количества компонентов, направленных на сбор логов, мониторинг и доступ к кластеру. Это вынуждает пользователей собирать свой собственный бандл с Prometheus, Grafana и т.д., что крайне неудобно и требует дополнительных усилий.
Вот и я, столкнувшись с Managed Kubernetes от Selectel, захотел использовать что-то готовое, желательно от российских разработчиков. Я обратил внимание на платформу Deckhouse, которую к тому моменту можно уже было ставить в готовые кластеры k8s. В этой статье я расскажу про свой путь интеграции D8 и werf в инфраструктуру Selectel и те проблемы, с которыми столкнулся в процессе.

Я прекрасно осведомлен, что EE-версия не требует столько усилий и запускается поверх облака Selectel с cloud provider OpenStack, однако данная опция мне недоступна. Поэтому работать будем с тем, что есть, а именно — с облачной платформой, Managed Kubernetes и Container Registry.
Подсчет инфраструктуры
Будем считать, что у нас уже есть Git-хранилище. Я предпочитаю использовать GitLab, поэтому дальнейшая интеграция будет именно с ним.
Для начала рассчитаем ресурсы. В идеологии Deckhouse есть разделение узлов на конкретные роли:
- Master — узлы Control Plane. На них крутится ETCD и K8S Apiserver. В Selectel они будут от нас скрыты.
 - System — узлы для системных компонентов. Сюда встанут различные компоненты мониторинга. Сюда же стоит вынести и другие кластерные компоненты — например, контроллер Container Storage Interface (CSI). Скорее всего это будут узлы со значительным объемом памяти.
 - Frontend – узлы для ingress и всяческого приема трафика. На них запускается Ingress, VPN и тому подобное.
 - Worker – узлы общего назначения, для приложений.
 

Чтобы просто запустить приложение, то есть для минимального кластера, будет достаточно нескольких нод из группы Worker. Однако создание продакшена потребует разделения ролей, а также настройку автоскейлинга. Именно поэтому важно создание нод группы System — в отличие от Worker, на них будет постоянный набор компонентов и эта группа будет относительно стабильна.
В различных примерах конфигурации, которые я приведу в статье, будут показаны примеры как для минимального кластера, так и production-ready решения. Они будут разделены по файлам с суффиксами:
- simple – для простого кластера,
 - ha – для отказоустойчивого решения.
 
Для своего проекта я выбрал следующую конфигурацию:
- два Frontend-узла,
 - три Worker-узла,
 - один System-узел.
 
Создание инфраструктуры
При создании инфраструктуры я решил сосредоточиться на повторяемости своих действий. Поэтому меньше действий руками, больше — через утилиты!

Воспользуемся Terraform — стандартом индустрии. Тем более у облака Selectel есть свой terraform provider в актуальном состоянии. Он поможет нам исключительно с PaaS-сервисами, такими как базы, кластера k8s. Для работы с виртуальными машинами и сетью в облаке нам потребуется еще и OpenStack terraform provider. Также у Selectel есть отличный репозиторий с примерами — его мы и будем использовать при создании виртуальной инфраструктуры.
В ходе работ я выяснил, что пример создания проекта с ключом не работает. О чем впоследствии был заведен issue.
Запустим наш проект и получим готовый кластер:
terraform init
env TF_VAR_sel_token=yyy_xxx TF_VAR_user_password=xxx terraform plan
env TF_VAR_sel_token=yyy_xxx TF_VAR_user_password=xxx terraform apply
Подготовка кластера Deckhouse
Теперь, когда мы получили готовый кластер, можно подготовить его к установке Deckhouse. Чтобы провести установку, нам потребуется создать Storage Class для нашего региона. Я собрал все это в один Chart, который:
- при первичной установке создает Storage Class для кластера, исходя из зоны,
 - при повторной установке создает Ingress, Auth provider и другие ресурсы D8.
 
Скачаем конфигурацию кластера из панели Selectel:

Установим Сhart в кластер:
export KUBECONFIG=~/.kube/d8test.yaml
werf converge --skip-build
Установка Deckhouse
Теперь установим Deckhouse. Вы всегда можете воспользоваться официальной документацией, однако я подготовил готовый конфиг для нашего кластера:
apiVersion: deckhouse.io/v1
kind: InitConfiguration
deckhouse:
  releaseChannel: Alpha
  bundle: Managed
  configOverrides:
    global:
      modules:
        publicDomainTemplate: '%s.d8test.example.com'
      storageClass: "fast.ru-3a"
    certManagerEnabled: true
    deckhouseWebEnabled: true
    chronyEnabled: false
    userAuthnCrdEnabled: true
    userAuthnEnabled: true
    userAuthzEnabled: true
    namespaceConfiguratorEnabled: true
    namespaceConfigurator:
      configurations:
      - annotations:
          extended-monitoring.flant.com/enabled: "true"
        includeNames:
        - "production"
        - "loki-logging"
    dashboard:
      accessLevel: ClusterAdmin
      auth:
        allowScale: true
        externalAuthentication:
          authURL: http://auth.dashboard-auth.svc.cluster.local/_tech/is_auth
          authSignInURL: https://dashboard.d8test.example.com/_tech/auth
          useBearerTokens: true
Внимательный читатель заметит, что у нас есть нестандартные настройки для Kube Dashboard. К этому мы вернемся позже. Примеры конфигураций можно найти тут.
Произведем установку:
docker run --pull=always -it -v "$PWD/config.yml:/config.yml" \
  -v "$HOME/.kube/d8test.yaml:/kubeconfig" registry.deckhouse.io/deckhouse/ce/install:alpha bash
dhctl bootstrap-phase install-deckhouse --kubeconfig=/kubeconfig --config=/config.yml
Deckhouse устанавливается без проблем за 5 минут.

Установим Сhart в кластер еще раз, чтобы развернулись недостающие ресурсы:
export KUBECONFIG=~/.kube/d8test.yaml
werf converge --skip-build
После применения оставшихся ресурсов останется дождаться получения прямого IP нашим кластером:
export KUBECONFIG=~/.kube/d8test.yaml
kubectl –n d8-ingress get svc
После чего потребуется направить на него DNS-запись *.d8test.example.com для компонентов.
Но что мы получим на выходе?
Managed K8S + D8 = ❤
А на выходе — полнофункциональный кластер с мониторингом, cert-manager, Ingress на основе nginx и возможностью сбора логов встроенным агентом на базе Vector.
Конечно, не обошлось и без ложки дегтя. Так как control plane контролирует Selectel, у нас нет возможности настроить multitenancy или же сконфигурировать Dex как OIDC-провайдер. Итого, что не работает:
- Нет авторизации в Kubernetes API через Dex.
 - Не работает incluster VPN.
 - Периодически отваливается сбор логов с ошибкой версии объекта:
 
Watcher Stream received an error. Retrying. error=WatchError(ErrorResponse { status: «Failure», message: «too old resource version: 114794284 (114797507)», reason: «Expired», code: 410 }) К счастью, это можно обойти. В Dashboard есть возможность внешней авторизации — именно ее я и использовал, сделав специальный Chart c nginx, который возвращает в Dashboard token. Установить его можно командой
werf converge.С логами я разобрался аналогичным образом — создав Chart, который раз в час перезапускает поды.
Документация по обоим чартам находится в связанных репозиториях.

Продолжим погружение
Чего же нам не хватает для полной утилизации инфраструктуры Selectel? Конечно же, быстрой сборки и доставки приложений в новоиспеченный кластер.
На текущий момент ноды кластера Selectel работают на относительно старом образе с Ubuntu 18.04 и ядром четвертой ветки. Это не позволит нам полностью раскрыть werf вкупе с Gitlab Kubernetes runner. По имеющейся у меня информации, вскоре образ для нод будет обновлен. До тех пор сборку будем производить на выделенных виртуальных машинах с Gitlab Runner. Чтобы их настроить, укажите количество раннеров в Terraform. Также потребуется активировать создание bastion хоста. Сами раннеры можно легко сконфигурировать с помощью Ansible. Шаг с установкой werf на Gitlab Runner я намеренно пропускаю, так как он и так отлично расписан в официальной документации.
Когда сборочная инфраструктура будет готова, перейдем к созданию Kubeconfig для работы werf. Можно воспользоваться статьей на сайте Deckhouse.

С начала года Selectel начал предоставлять Container Registry as a Service (CRaaS) в облаке. Это значит, что доставка приложений будет еще быстрее, ведь скачка образов происходит в рамках одной инфраструктуры по быстрым сетям провайдера. Но не все так просто — об этом сервисе мы поговорим отдельно.
werf и CRaaS

Статья писалась продолжительное время, во время бета-тестирования CRaaS. Описанные проблемы были успешно решены с техподдержкой Selectel. В werf добавлены подсказки для работы с Container Registry от Selectel.
Создадим реестр и получим к нему доступ. Подробнее в документации Selectel.

Создадим простейший проект с Pipeline в Gitlab:
variables:
  WERF_REPO: ${SEL_REGISTRY_DOMAIN}/${SEL_REGISTRY}
before_script:
  - type trdl && . $(trdl use werf 1.2 ea)
  - werf version
  - type werf && source $(werf ci-env gitlab --as-file)
  - werf cr login ${SEL_REGISTRY_DOMAIN}
after_script:
  - werf cr logout ${SEL_REGISTRY_DOMAIN}
stages:
  - deploy
.base_deploy:
  stage: deploy
  script:
    - werf converge
  except: [schedules]
  tags: [werf]
Deploy to test:
  extends: .base_deploy
  environment:
    name: test
  when: manual
Deploy to production:
  extends: .base_deploy
  environment:
    name: production
  when: manual
Кроме того, нам потребуются несколько переменных в CI/CD GitLab:
- WERF_KUBECONFIG_BASE64 — его мы получили в предыдущем параграфе,
 - SEL_REGISTRY_DOMAIN со значением cr.selcloud.ru
 - SEL_REGISTRY c именем реестра — в нашем случае example,
 - WERF_USERNAME со значением token,
 - WERF_PASSWORD с токеном, который мы получили из панели Selectel.
 
На удивление, все заработало сразу и без проблем. Однако на первых порах образы были не оптимизированы и предоставленные в рамках беты бесплатные 20 ГБ довольно быстро закончились. Встал вопрос очистки.
В Pipeline добавился блок очистки, аналогичный представленному в документации werf:
stages:
  - deploy
  - cleanup
.base_clean_up:
  stage: cleanup
  script:
    - werf cleanup
             rules:
             - if: $CI_PIPELINE_SOURCE == "schedule"
               when: always
  tags: [werf]
  resource_group: cleanup
Clean up:
  extends: .base_clean_up
В этот момент стало понятно, что werf не поддерживает очистку данной реализации Registry.

К счастью, это можно исправить. У CRaaS был развитый API, который позволял сделать очистку. API позволяло:
- быстро получить список тегов и репозиториев,
 - сделать очистку образов,
 - получить токен для доступа к Registry.
 
Сходу нашлась и проблема, которую можно отнести к проблемам реализации Container Registry от Selectel: werf позволял класть образы в корень репозитория — например, cr.selcloud.ru/example:67a03ef931a263cd83c0fd3dcd7d4468f12d44e13755dad35e3f, но ни API, ни личный кабинет облака не отображали подобные образы. Баг-репорт я отправил в техподдержку Selectel, а в werf добавил подсказку.

К слову, на стороне Selectel сейчас тоже существует проверка:

Мне же в тот момент пришлось пересоздать реестр — других вариантов не было.
Пришлось скорректировать Pipeline, исправив переменную WERF_REPO:
variables:
  WERF_REPO: ${SEL_REGISTRY_DOMAIN}/${SEL_REGISTRY}/${CI_PROJECT_PATH}
Так и появилась первая реализация CRaaS в werf и добавлен раздел в документацию.
На этом приключения не закончились. В реализации были не учтены некоторые вещи:
- работа с лимитами на запросы к API,
 - работа у новых юзеров, у которых нет токенов, — сборка могла выдать ошибку.
 
Если вторую ошибку можно легко побороть, ведь достаточно поменять работу стадии очистки, то для решения вопроса с лимитами придется отказаться от многопоточности при очистке. Секция очистки станет выглядеть следующим образом:
.base_clean_up:
  stage: cleanup
  variables:
    WERF_PARALLEL: 0
  script:
    - werf cleanup
  rules:
  - if: $CI_PIPELINE_SOURCE == "schedule"
    when: always
  tags: [werf]
  resource_group: cleanup
Добавилась переменная WERF_PARALLEL, которая ограничивают количество одновременных задач. А для беспроблемной работы у новых пользователей появилась вторая реализация, которая учитывала ошибки первой.
Осенью столкнулся с другой бедой — любое удаление образа из Registry приводило к полной блокировке реестра. Пришлось перенастроить работу garbage collector на стороне GitLab, перенеся очистку на глубокую ночь по Москве. Сообщил о проблеме техподдержке Selectel, и они оперативно ее исправили.
Неделю назад CRaaS вышел в коммерческий релиз, что сопровождается обновлением API. Поэтому в разработке третья версия работы с CRaaS для werf. Stay tuned!
Итоговый Pipeline, который я использую у себя в проекте, вы сможете найти на GitHub.
В заключение хочу поблагодарить команду werf (в особенности aigrychev ), которые быстро отвечали на вопросы по работе с утилитой, и команду DevShop, с чьего разрешения я и публикую эти наработки.