Как стать автором
Обновить
46.79
ОТП Банк
Давай сделаем вместе!

Облако для тех, кому нельзя в облака: как мы в ОТП Банке развернули закрытое облако на платформе Яндекса

Уровень сложностиСложный
Время на прочтение28 мин
Количество просмотров5K

Серьёзно, банк на облачной платформе? Те читатели, кто занимается инфобезом в финтехе, сейчас, наверное, или смеются, или в ужасе думают о последствиях такого решения.

И тем не менее мы в ОТП Банке полтора года назад взялись за эту задачу — и сейчас в Yandex Cloud чувствуем себя отлично. Привет, я из трайба IT4IT ОТП Банка. Мы занимались разработкой нашего закрытого облака. Под катом расскажу, зачем нам облако понадобилось, почему собственное решение не устроило и как мы выполнили требования Управления информационной безопасности (УИБ) никого не впускать и не выпускать — не забыв при этом сделать облако мощным инструментом для наших разработчиков.

Есть ли жизнь в облаке для финтеха?

Вообще-то, ещё до этого проекта частное облако у нас было — внутреннее, на двух ЦОДах. Но в нём даже близко не хватало ЦПУ и памяти для всех желающих там работать.

Голый, «нулевой» OpenStack, никакой возможности развернуть что-то полезное просто по кнопке, от того же Kubernetes до баз данных. Есть желание — настраивай с нуля ручками, а нет экспертизы — нет и сервисов.

Кроме того, двух ЦОДов, которые были доступны в частном облаке, не хватало для реализации кластеризованных систем, например, Kubernetes или PostgreSQL. Они вынужденно пользовались вариантами установки active-passive failover (пассивный резерв), то есть два отдельных инстанса в разных ЦОДах в режиме active-passive, либо вынужденно выносили кворумные ноды за пределы частного облака на классическую виртуализацию (у нас vSphere). Многим это было неудобно.

Так что мы пошли искать счастье на аутсорсе Ops: опции, ресурсы, три зоны доступности — качественный публичный облачный сервис с PaaS и хорошей техподдержкой. После проведения ряда пилотов выбор пал на Yandex Cloud.

Бизнес одобрил идею: он тоже читал умные книги и понимал, зачем нам облако необходимо. Казалось бы, вот вам зелёный свет, можно закатывать рукава и приступать к внедрению, но пришёл отдел безопасности — и всё сразу резко усложнилось.

Как финансовая организация, мы работаем с персональными и финансовыми данными пользователей. А значит, мы на коротком поводке у регулятора. Соответственно, первая реакция на слова «публичная платформа» у инфобеза: «Давайте никого не впускать, никого не выпускать и всё за всеми записывать».

Изолироваться от интернета? Звучит как ТЗ!

Конкретнее, для закрытого безопасного облака нам было нужно:

  1. Весь входящий, исходящий трафик пропускать через наши файрволы.

  2. Аутентифицировать пользователей нашими AD.

  3. Изолировать сети облаков от интернета.

  4. Вести аудит всех действий в Организации в облаке.

Hidden text

Организация с большой буквы — самый высокий уровень инфраструктурной иерархии в Yandex Cloud. В Организации содержатся облака (cloud), в них папки (folder), а в папках ресурсы: ВМ, Kubernetes и так далее.

Эти задачи стали нашим ТЗ.

Организация облаков

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

Решили, что выделим каждому трайбу по два облака (cloud): для прода (prod) и для всего остального (non-prod). За продом особый догляд и более строгие доступы. На non-prod, в котором можно расположить дев, тест и любого рода экспериментальные площадки, воздух свободнее: больше прав, легче доступы и можно творить что хочешь.

Hidden text

Трайб — нечто вроде «департамента» в Agile-мире. Каждый трайб в ОТП отвечает за определённое направление развития, выполняет свои задачи и выпускает свой продукт. Наш трайб, IT4IT, разрабатывает внутренние продукты и решения. Так мы помогаем трайбам, которые создают бизнес-продукты, работать эффективнее.

В итоге список Облаков у нас выглядит примерно так:

В ней всё, относящееся к интеграции нашей инфраструктуры и инфраструктуры Yandex Cloud. Здесь как минимум располагаются сервисные аккаунты (Service Account) для раздачи прав на облако, внутренние и кастомные DNS-зоны и, конечно, сердце облака — VPC.

VPC (Virutal Private Cloud) — это особый объект, который даёт возможность создавать в облаке подсети. С его помощью также поддерживается сетевая связность с нашей внутренней банковской инфраструктурой и обеспечивается DNS с возможностью создания кастомных записей, видимых внутри всего банка. Подробнее о том, как это работает и как мы это сделали, расскажем дальше в статье.

Поскольку папка infra такая важная, она для опытного пользователя выглядит натурально как большая красная кнопка «Удалить прод». К счастью, её нажать не так просто: нельзя удалить папку, если в ней есть объекты, и нельзя удалить VPC, если в нём есть подсети. Это стандартные механизмы защиты «от дурака» со стороны Yandex Cloud.

Чтобы никого не выпускать, надо кого-то впустить

Чтобы хорошенько закрыть публичное облако, необходимо настроить аутентификацию нашими средствами, без доступа к интернету, то есть без использования Yandex ID. Для этого есть готовое решение: ADFS, Active Directory Federation Service. В принципе, поскольку мы пришли за аутсорсом Ops, нашим девизом было изобретать как можно меньше — поэтому тут мы погрузились в документацию.

Итак, с ADFS мы заходим по специальной ссылке на страничку Yandex Cloud, вводим Federation ID и получаем страницу домена, логин, пароль — прямо как дома.

Все зашедшие пользователи будут видны на специальной странице, где отображаются все пользователи организации.

Аутентификация — это только начало

Защищённый вход в облако сделали. Но дальше встаёт вопрос прав и их распределения между юзерами. У каждого трайба здесь свои представления о прекрасном и правильном. Кому-то нужно, чтобы у разработчиков были права для развёртки Kubernetes, кто-то считает, что это исключительно прерогатива DevOps-инженера, кто-то хочет дополнительные роли для QA-инженеров или проджект-менеджера и так далее.

Настраивать роли вручную не очень хотелось: мы бы просто захлебнулись в волне запросов на создание групп в AD и их ассоциацию с ролями в облаке. Решили вопрос так: для каждого облака в AD есть две группы — привилегированные и не очень. При этом привилегированные могут — и должны — раздавать роли «простым смертным» так, как они считают правильным.

У привилегированных — по сути админов облака (cloud) — роль editor (редактора) на уровне облака и роль admin на все папки (folder в терминологии Яндекса). А остальные? Они получают только членство в организации, роль resource-manager.clouds.member. То есть право суметь залогиниться в облаке.

Пользователь с такими правами, залогинившись, ни одного облака не увидит. Зато он будет числиться в федерации. И привилегированные юзеры — а это самые доверенные люди ОТП: техлиды, главные девопсы, чаптер-лиды — смогут самостоятельно настроить права для него и других обычных пользователей своего трайба.

Это можно сделать через интерфейс Yandex Cloud или, если есть умение и желание, с помощью самостоятельной автоматизации через скрипт + API, Terraform или Pulumi. Права админы распределяют своим подопечным в соответствии с ролевой моделью в Yandex Cloud и своими пожеланиями.

Для каждого облака получается такая ассоциация:

У нас также есть третья правовая категория — люди с пропуском на все облака. Такие права нужны прежде всего сетевикам, которые обеспечивают интеграцию облаков в сеть банка, и безопасникам — для аудита.

Строим автоматизацию выдачи ролевой модели

 

Группы в AD можно заказывать в нашем случае по стандартным заявкам на доступы. Но у Yandex Cloud на момент реализации не было встроенного механизма, который мог бы сопоставить группы в AD со своей ролевой моделью. Сейчас это решается за счёт управления группами пользователей. А мы в тот момент написали для этого свою собственную автоматизацию.

Такая автоматизация должна:

  1. Выбрать юзеров из AD, относящихся к работе с Yandex Cloud.

  2. Выдать им права в облаках в соответствии с этими группами.

Первый пункт можно сделать скриптом, второй — с помощью Terraform. Так как первая версия сервиса создавалась для Windows, мы решили, что в AD будем лазить через Powershell, а права назначать с Terraform, благо для него у Yandex Cloud есть официально поддерживаемый провайдер.

Идея в том, что Powershell сформирует для Terraform values.tf и после этого запустит terraform apply. Terraform может оперировать только ID пользователей, состоящих в федерации, поэтому у Powershell получаются три задачи:

  1. Найти юзеров в AD.

  2. Для каждого юзера выяснить ID в Yandex Cloud (Federation User ID).

  3. Сформировать для Terraform значения в удобном для него формате.

Итог работы Powershell в формате JSON:

{
  "variable": {
    // права для "простых смертных"
    "tribes": {
      "default": {
        // облака
        "tribeA-prod": {
          // права
          "admins": [
            // юзера или сервисный аккаунты (Service Account)
            "federatedUser:xxxxxxxxxxxx",
            "serviceAccount:xxxxxxxxxxxx",
          ],
          "users": [
            "federatedUser:xxxxxxxxxxxx",
            "federatedUser:xxxxxxxxxxxx",
          ]
        },
        "tribeA-nonprod": {
          "admins": [
            "federatedUser:xxxxxxxxxxxx",
            "serviceAccount:xxxxxxxxxxxx",
          ],
          "users": [
            "federatedUser:xxxxxxxxxxxx",
            "federatedUser:xxxxxxxxxxxx",
          ]
        },
        // ... и далее для всех облаков  ...
      },
      // группа для сетевиков
      "org_network_admins": {
        "default": [
          "federatedUser:xxxxxxxxxxxx",
          // ... etc ...
        ]
      },
      // группа для безопасников
      "org_viewers": {
        "default": [
          "federatedUser:xxxxxxxxxxxx",
          // ... etc ...
        ]
      }
    }
  }
}

В Terraform, соответственно, есть определения не всех, но значительного количества ролей:

variable "tribe_admin_priveleges" {
  default = {
    cloud  = ["editor", ]
    folder = ["admin"]
  }
  type = map(list(string))
}

variable "tribe_user_priveleges" {
  default = {
    cloud  = ["viewer", ]roles
    folder = []
  }
  type = map(list(string))
}

variable "org_noc_priveleges" {
  default = {
    org    = ["vpc.admin","viewer"] 
    cloud  = []
    folder = []
  }
  type = map(list(string))
}

variable "org_viewer_priveleges" {
  default = {
    org    = ["viewer"]
    cloud  = []
    folder = []
  }
  type = map(list(string))
}

Всё вместе это собирается примерно так. Показываем на примере создания ролей на уровне облаков:

#### Cloud level #########

# Подготовка данных перед выдачей прав
locals {
  ### Маппинги списка пользователей на роли, которые им нужно выдать 

  # Маппинг ролей безопасников и сетевиков
  cloud_noc_mapping                     =setproduct(var.org_network_admins,var.org_noc_priveleges["cloud"])
  cloud_viewer_mapping                  =setproduct(concat(var.org_viewers,var.sa_org_viewers),var.org_viewer_priveleges["cloud"])

  # Маппинг юзеров трайбов
  all_tribes_clouds_mapping = chunklist(flatten([for cl in local.clouds: 
    concat(
      setproduct(var.tribes[cl.name].users,var.tribe_user_priveleges["cloud"],[cl.cloud_id]),
      setproduct(var.tribes[cl.name].admins,var.tribe_admin_priveleges["cloud"],[cl.cloud_id])
    )
    if contains(keys(var.tribes),cl.name)
  ]),3)

  all_clouds_mappings = chunklist(
    flatten(
      setproduct(
        concat(
          local.cloud_noc_mapping,
          local.cloud_viewer_mapping,
          )
        ,local.clouds.*.id)
      )
    ,3)
}
# Собственно выдача права - это создание объектов типа CloudIamBinding для каждого юзера
resource "yandex_resourcemanager_cloud_iam_binding" "cloud_binding" {
   # Финальный хак - формируем словарь
   # Где ключ строка "<cloud_id>/<role_name>", а значение - ID пользователя или сервисного аккаунта
  for_each = {for v in concat(local.all_clouds_mappings,local.all_tribes_clouds_mapping):
    "${v[2]}/${v[1]}" => v[0]...
  }
  cloud_id = split("/",each.key)[0]
  role     = split("/",each.key)[1]
  # Выдаются права сразу пачке пользователей
  members  = each.value
}

На уровне Организации и папок всё сделано аналогично.

В этом коде есть особенность (конечно, помимо того, что трансформация данных в Terraform — это адский ад). Здесь использован ресурс cloud_iam_binding, а не cloud_iam_member. Дело в том, что в Yandex Cloud список пользователей привязывается к роли, а не наоборот, как это может показаться, например, при взгляде в графический интерфейс. Эта привязка полностью описывается объектом *_iam_binding.

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

Мы настроили использование *iam_binding на уровне облака и Организации (cloud_iam_binding и organization_iam_binding соответвенно), чтобы чётко фиксировать права на уровне Организации и облаков. На уровне папок же сервис использует folder_iam_member, который способен «мягко» добавить пользователя в существующий биндинг без перезаписи. Это важно: админам нужна возможность самостоятельно выдать права рядовым членам команды и быть уверенными, что наш сервис их не перезатрёт.

Сервис выдачи прав создавался на самой заре захода в Yandex Cloud, когда очертания того, как всё у нас будет устроено, были весьма туманными. Со временем стало ясно, что связка Powershell + Terraform получается плохо читаемой и сложно поддерживаемой.

Сейчас в техдолге у нас есть живая задача — хорошенько отрефакторить этот сервис. Для инфраструктурного кода мы в трайбе используем Pulumi. С его помощью можно будет оформить и работу c AD, и работу с ресурсами Yandex Cloud в одну программу. Это сильно облегчит нам поддержку.

Hidden text

Опыт создания этого сервиса через Terraform — одна из причин, почему мы в нашем трайбе решили попробовать Pulumi. Pulumi, используя под капотом провайдеры от Terraform, позволяет писать инфраструктурный код на нормальном языке программирования. Те же ресурсы как и в Terraform — даже имена и свойства ресурсов те же самые! — только на полноценном объектно-ориентированном ЯП по выбору (TypeScript, Golang, C#, Java и так далее).

 Соответственно, к твоим услугам вся мощь современной разработки: нормальная IDE, дебаг, любые библиотеки, возможность создания красивых абстракций. И конкретно, например, для задачи с автоматизацией выдачи прав это даёт возможность запаковать и работу с AD, и маппинг, и создание ресурсов в одну программу.

Все зашли в облако… и сразу вышли

Потому что нет сетевой связности ни с интернетом, ни с нашими внутренними сетями. Публичный интернет закрыт квотами и отсутствием возможности сделать SNAT (source NAT) в подсетях, а с внутренней инфраструктурой нет физической связности.

То есть если мы создадим подсеть в облаке, она будет существует в вакууме. Можно развернуть виртуалки и даже Kubernetes PaaS, но существовать они будут в пузыре, без какой бы то ни было связности со внешним миром.

Инженеры Yandex Cloud для организации сетевой связности с нашей приватной инфраструктурой предложили использовать Yandex Cloud Interconnect. После прочтения документации по нему стало ясно, что надо звать сетевиков: без их черной магии дело дальше не пойдёт.

Как в итоге всё решилось?

Наше оборудование стоит в одном из московских ЦОДов рядом с сетевым оборудованием Яндекса и физически подключено к нему. Между ними настроен BGP-пиринг, и они обмениваются маршрутами. Таким образом наша инфраструктура видит сети в облаках, а облака, соответственно, нашу инфраструктуру.

Для обеспечения отказоустойчивости физический стык продублирован во втором ЦОДе, и оба канала работают в режиме active-active-балансировки через BGP.

Подробности об этой сетевой магии можно прочитать в документации по ссылке выше.

Для обеспечения связности помимо физического стыка нужен финальный штрих — это настройка VPC конкретного облака. Для этого наши сетевики приходят во вновь созданные облака и договариваются с техподдержкой Yandex Cloud о новых анонсах для VPC этого облака. Для каждого облака анонсятся три диапазона — по диапазону на каждый ЦОД — и после этого подсети, созданные из VPC этого облака, получают связность с нашей внутренней инфраструктурой. Именно для этих работ сетевикам и нужен пропуск и особые доступы, о которых писалось выше.

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

Как же пользоваться всем этим хозяйством обычному пользователю?

Простой ответ на этот вопрос будет таким: «Видишь, пользователь, на картинке три больших /21 диапазона? Они твои. Создавай внутри них подсети (Subnet) в строгом соответствии с ЦОДом, которому они принадлежат, и у тебя все будет хорошо».

И более развернутый ответ:

В каждом облаке есть три больших /21-диапазона (фиолетовая рамка с подписью VPC). Каждая такая подсеть закреплена за отдельным ЦОДом. Второй октет этой сети обозначает ЦОД. Так, на картинке 10.101.х.х — это ЦОД ru-central1-a, 10.102.х.х — это ЦОД ru-central1-b и так далее.

Эти /21-подсети полностью виртуальны. Они нигде в интерфейсе не фигурируют и прописаны где-то глубоко внизу на инфраструктуре Яндекса и нашей. Внутри них можно создать подсеть — и они будут маршрутизироваться как внутри облака, так и внутри приватной инфраструктуры. Можно завести подсеть сразу на весь диапазон, но мы рекомендуем нарезать сети поменьше — скажем, /24-диапазоны.

Если всё правильно создать, то можно начать всячески развлекаться — создавать виртуалки, Kubernetes и так далее — и всё будет иметь сетевую связность с банком.

Если подумать ещё немного дальше, то для красоты, читаемости и обеспечения возможности создавать катастрофоустойчивые приложения, лучше сразу создавать сети «тройняшки». То есть «зафиксировать» третий октет за командой или информационной системой и сделать её копии для каждого ЦОДа. Такие сети на картинке выше выделены цветами: синие для команды А, зелёные — для команды (или системы) C. Обратите внимание: у всех «тройняшек» один третий октет и разный второй.

В таких «тройняшках» удобно развёртывать кластеризированные системы, «размазывая» их по трём зонам доступности. Это как раз одна из киллер-фич, за которыми мы пришли в публичное облако.

Из минусов этой системы сейчас видим только один — это необходимость сообщать командам /21-диапазоны их облаком и объяснять, как правильно создавать подсети. Со стороны Yandex Cloud это, к сожалению, нигде не отображается, и мы не нашли возможность это как-то ограничить. Командам просто это нужно знать.

Далее последним желанием отдела безопасности остался аудит. Для него мы использовали готовый сервис Yandex Audit Trails. Он следит за всей Организацией и складывает в бакет отчёты о том, что происходит в облаке. И всё бы хорошо, но…

Аудит и SIEM

Мы обнаружили, что наша SIEM не умеет работать с бакетами напрямую. В документации Яндекса не было описания случая, целиком аналогичного нашему. Но концепция не была уникальной: просто пришлось копнуть чуть поглубже.

Как подключить разные SIEM — описано в этом разделе документации. Нашей SIEM там нет, так что мы чуть-чуть импровизировали. Сделали промежуточную ВМку, в которую этот бакет монтировали с помощью GeeseFS по инструкции.

По итоговой схеме Audit Trails складывает отчёты с событиями в наш бакет, этот бакет смонтирован на ВМ, а наша SIEM ходит к ней по SSH и забирает отчёты просто из файловой системы.

Концептуальная схема выглядит так:

Порядок действий, чтобы это всё заработало, примерно следующий:

  1. Создать Audit Trails.

  2. Создать Service Account (SA) и выдать ему права на работу с Audit Trails и чтение из бакета.

  3. Создать виртуальную машину (ВМ), куда будет смонтирован бакет. Важно, чтобы к ВМ был «подключен» сервисный аккаунт.

  4. Настроить GeeseFS, чтобы он монтировал бакет по нужному пути.

  5. Настроить SSH.

Создать Audit Trails можно по инструкции: https://cloud.yandex.ru/docs/audit-trails/quickstart

Выдать права SA через CLI можно так:

# Права на уровне ПАПКИ: storage.uploader, storage.viewer
yc resource-manager folder add-access-binding --name my-folder --service-account-name audit-trails --role storage.uploader
yc resource-manager folder add-access-binding --name my-folder --service-account-name audit-trails --role storage.viewer
 
### Выдать права сервисному аккаунту на просмотр событий аудита на уровне организации
# Права на уровне ОРГАНИЗАЦИИ: audit-trails.viewer
# WARNING! Это можно сделать только из-под пользователя или SA, которые имеют права админа на всю организацию
yc organization-manager organization add-access-binding --role audit-trails.viewer --id "<ORG ID>" --service-account-id "<SA ID>"

Виртуалку можно создать любую. Главное — указать ей «связь» с созданным SA. Об этом можно почитать в официальной инструкции Создание виртуальной машины Linux, поискав по слову доступ.

Это полезно, потому что на виртуалке будут созданы приватные ключи для работы из-под этого SA. Это нам дальше упростит монтирование бакета.

Само монтирование выглядит так:

# Доступа в интернет нет, поэтому пререквизиты для нас
# Это настроить пакетные репозитории на наш Nexus (хранилище и прокси для артефактов)
sed -i 's/^mirrorlist=http:\/\/mirrorlist.centos.org/#&/' /etc/yum.repos.d/*.repo
sed -i 's|^#\?baseurl=http://mirror.centos.org|'baseurl=http://nexus.our'|g' /etc/yum.repos.d/*.repo

## Установить вспомогательные утилиты для маунта S3 бакетов
sudo dnf install -y fuse fuse-utils

## Установить geesefs
# И бинарник скачиваем через проксю в Nexus
sudo curl -LO https://nexus.our/github-raw-proxy/yandex-cloud/geesefs/releases/latest/download/geesefs-linux-amd64 --output /usr/local/bin/geesefs
sudo chmod +x /usr/local/bin/geesefs

## Создать сам маунт
# Такой маунт безопасен и не приведет к падению при старте ОС, даже если будет ошибка подключения к бакету. Просто в journalctl будет запись об ошибке маунта
sudo echo "audit-trails-bucke-name /path/to/mount fuse.geesefs _netdev,allow_other,--iam,--subdomain 0 0" >> /etc/fstab
## В команде выше два флага, которых нет в официальных примерах:
# --iam - воспользоваться сервисным аккаунтом, связанным с ВМ. Помните, выше мы привязывали его к ВМ? Как раз для этого. Иначе нам бы пришлось добавлять AWS Credentials файл с ключами доступа к бакету.
# --subdomain - обращаться к бакету исключительно по доменному имени. Так называется virtual-domain-style (в противовес deprecated path-style — обращение по путем). Обращение по домену для нас очень важно, потому что только так в наших условиях будут работать сетевые доступы до бакета.

# Финальный штрих =)
sudo mount -a
Hidden text

Под капотом — ключ сервисного аккаунта для ВМ получается довольно любопытным образом. Если была сделана привязка SA к ВМ, достаточно сделать изнутри ВМ запрос на особый адрес:

 # Запрос токена...

curl -H Metadata-Flavor:Google http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token

 # И ответ будет примерно такой

{"access_token":"CggVAgAAA...","expires_in":39944,"token_type":"Bearer"}

169.254.0.0/21 — это специальный зарезервированный диапазон для локальных обращений. Dynamically configured link-local addresses — RFC3927. И в нём есть адрес 169.254.169.254, который многие облака используют для внутренних нужд. Google, AWS и вот теперь Yandex Cloud расположили на нём сервис получения метаданных ВМ. Более подробный пример смотрите здесь: Работа с Yandex Cloud изнутри виртуальной машины.

Kubernetes

Kubernetes в нашем облаке можно создать, до его API можно достучаться, у нод есть сетевая связность с банком… но в нём нельзя развернуть ни одно приложение!

Всё дело в доступах. Интернета нет, значит, нет ни регистри Яндекса, ни тем более docker.io и сотоварищей. Есть доступ до нашего Nexus… Однако на нём самоподписанные сертификаты — и Kubernetes, а точнее container runtime под ним, строго требует TLS и не позволяет скачать ни одного образа.

Выход? Подложить свои сертификаты на все ноды кубера. Свой образ ВМ под кубер использовать нельзя, кастомизировать под нас образ в Yandex Cloud тоже не будут. Мировое комьюнити подобного рода задачи — допиливание образов Kubernetes PaaS — решает с помощью DaemonSet.

Однако, чтобы подложить сертификаты, нужно запустить в кубере какой-то образ… Который, как мы уже выяснили, ниоткуда нельзя скачать! Единственная возможность — это сделать, когда нет доступов вообще ни к какому регистри — использовать образы, которые уже есть на нодах Kubernetes. Таким образом, например, может быть один из компонентов самого Kubernetes, которые зашиваются прямо в образы нод. Для начала мы взяли kube-proxy.

Сделали Helm-чарт, в нём DaemonSet, который запускает поды на каждой ноде из этого образа и выполняет наш скрипт-напильник. Мы обозвали этот чарт node-init.

Без проблем, конечно, не обошлось.

Containerd не видит сертификатов без перезапуска

У containerd есть конфиг по пути в /etc/containerd/config.toml. В нём есть много интересного, что специалисты Yandex Cloud туда записали. А вот чего в нём нет, так это особой записи, которая позволяет подложить в определённую папку свои сертификаты, чтобы containerd начинал им пользоваться без перезапуска. Я говорю о такой записи:

[plugins."io.containerd.grpc.v1.cri".registry]
   config_path = "/etc/containerd/certs.d"

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

echo "==== Optional containerd restart enabled. Restart containerd and dockerd. ===="
# /host — это маунт хостовой рутовой файловой системы в под.
# Таким образом под получает возможность управлять хостом через chroot /host
set -x
NODE_INIT_DIR=/host/opt/node-init
mkdir -p "${NODE_INIT_DIR}"
# файлы-флаги рестарта containerd, dockerd (см. ниже)
NODE_INIT_CONTAINERD_RESTARTED=$NODE_INIT_DIR/containerd.restarted
NODE_INIT_DOCKERD_RESTARTED=$NODE_INIT_DIR/dockerd.restarted
set +x

if [ ! -f "${NODE_INIT_CONTAINERD_RESTARTED}" ]; then
   echo "Restart Containerd..."
   # Так как рестартовать containerd при каждом рестарте пода node-init мягко говоря опасно
   # то добавляем специальный файл флаг, который говорит «нода уже инициализирована»
   # Флаг добавляется перед рестартом, чтобы избежать бесконечного цикла в случае неуспешного рестарта
   touch "$NODE_INIT_CONTAINERD_RESTARTED"
   chroot /host systemctl restart containerd
   echo "OK!"
else echo "Containerd has been already restarted on this host."; fi;

# Когда Яндекс переходил с dockerd на containerd,
# то несколько версий они работали одновременно в странном симбиозе.
# Экспериментально выяснилось, что в таких случаях нужно рестартовать и dockerd тоже,
# даже если он сам не видит ни одного контейнера, запущенного на ноде
if [ ! -f "${NODE_INIT_DOCKERD_RESTARTED}" ]; then
   echo "Restart Dockerd..."
   touch "$NODE_INIT_DOCKERD_RESTARTED"
   chroot /host systemctl restart docker
   echo "OK!"
else echo "Dockerd has been already restarted on this host."; fi;
Hidden text

Во время подготовки статьи техподдержка Yandex Cloud частично ответила на этот запрос. Они не внесли изменения в конфиг containerd, но зато предложили своё решение для подкладывания сертификатов с перезагрузкой. Можно посмотреть тут: https://github.com/yandex-cloud/yc-architect-solution-library/tree/main/yc-k8s-certificate-updater

На нодах нет ни одного образа с фиксированным тегом

На образе ВМ, который в Yandex Cloud используется для поднятия нод кубера, зашиты образы системных контейнеров, необходимых для первичной инициализации ноды.

Вот неполный список, который можете посмотреть и вы, если зайдёте на свежую ноду:

root@xxxxxxxxx-aaaaa:/etc/kubernetes# /home/kubernetes/bin/crictl image ls
IMAGE                                                                                     TAG                 IMAGE ID            SIZE
cr.yandex/crpsjg1coh47p81vh2lc/k8s-addons/calico/cluster-proportional-autoscaler-amd64    1.7.1               14afc47fd5aff       41.3MB
cr.yandex/crpsjg1coh47p81vh2lc/k8s-addons/cilium/cilium                                   v1.12.9             b606b891645b8       421MB
cr.yandex/crpsjg1coh47p81vh2lc/k8s-addons/kube-proxy/kube-proxy                           v1.26.2             6f64e7135a6ec       114MB
cr.yandex/crpsjg1coh47p81vh2lc/k8s-addons/metrics-server/metrics-server                   v0.6.1              e57a417f15d36       70.1MB
cr.yandex/crpsjg1coh47p81vh2lc/pause                                                      3.9                 221177c6082a8       715kB
cr.yandex/crpsjg1coh47p81vh2lc/yc-disk-csi-node                                           38ef4c9376d          91b2502cca9dd       33.5MB
# и т.д.

Обратите внимание, что всё сделано по лучшим практикам работы с образами: все теги, версии фиксированы, ни одного latest. Но хорошая практика в этот раз по иронии привела нас к неприятной проблеме.

Запустить node-init на кластере можно без проблем. Ты знаешь версию кластера, знаешь, что на нодах точно будет какой-нибудь kube-proxy и можешь загодя имя образа успешно сформировать.

Но… А если обновление? Теги образов поменяются, и наш node-init больше не сможет подняться на новой ноде. Проблема неприятна также тем, что со стороны Yandex Cloud регулярно вносятся изменения в образы. Они меняются даже в рамках одной минорной версии.

Например, у тебя есть кубер возрастом несколько месяцев с автоскейлингом. И в какой-то момент меняются патчевые версии системных компонентов, kubeproxy становится x.y.z+1. Наш node-init на новых нодах сразу же выходит покурить.

Вертели проблему и так, и эдак. В итоге получалось, что без поддержки Yandex Cloud это не сделать никак. Мы сформировали тикет, и специалисты Яндекса были довольно быстры — и в течение нескольких месяцев добавили образ со стабильным тегом в релизный канал RAPID, а через полгода он перешёл в STABLE. Вот так выглядит теперь этот красавец. Скорее всего, и на вашей ноде кубера он уже есть:

root@xxxxxxxxx-aaaaa:/etc/kubernetes# /home/kubernetes/bin/crictl image ls
# something...
# something...
# something...
cr.yandex/yc/mk8s-openssl                                                                 stable              8a55c996a8286       81.2MB
# dark side...

Теперь можно обновляться, скейлиться... Всё будет работать. Некоторое время.

Проблема GC (Kubernetes Garbage Collection)

Здесь история с открытым финалом. У Kubernetes есть своя система Garbage Collection. Подчищает много чего, но нам интереснее всего то, что он со временем удаляет с нод неиспользуемые образы.

Напомню, что в образах под кубер у Yandex Cloud зашито много разных образов. Среди них есть те, которые запускаются не на каждой ноде (coredns, metrics-server) и даже не в каждом инстансе Kubernetes (nvidia/k8s-device-plugin). Со временем эти неиспользуемые образы с некоторых нод могут исчезнуть. И если кубер решит мигрировать, например, coredns на соседнюю ноду, где этого образа никогда не было, нода полезет в cr.yandex… Где получит вечный ImagePullBackOff в статусе пода.

Недавно к нам пришла одна из команд в лице девопса и сообщила о проблеме: из их кластера исчез образ hubble-generate-certs, который используется в одной из предустановленных Job.

Также наши команды сталкивались с тем, что версия кластера и нод протухает, на них начинают пытаться автоматически встать патчевые обновления и тоже застревают на этапе скачивания образа из cr.yandex. Например, этим особо грешит почему-то CSI Yandex Cloud (поды yc-disk-csi-node-*).

Hidden text

Немного о процессах в нашем банке. У нас стараются по возможности придерживаться концепции InnerSource — OpenSource для внутреннего кода. Все репозитории доступны для чтения всем командам разработки, если репозиторий явно не закрыт по соображениям ИБ. Их можно форкать, а при желании можно и сделать свой PR (Pull Request) в чужой репозиторий. В проблеме с Garbage Collection Kubernetes эта система сыграла всем на руку. node-init создавали мы, трайб IT4IT, а DevOps с проблемой пришёл из совсем другого трайба. Он сразу сделал PR со своей альфа-версией реализации, и дальше мы уже исследовали проблему совместно.

Кроме того, у нас есть система гильдий — особых совместных и полностью добровольных комьюнити, посвящённых какой-то теме или технологии. Из активных у нас есть, например, гильдия Javа и гильдия Kubernetes. ОТП официально разрешает членам гильдии тратить 5% рабочего времени на её задачи. Так, например, последняя проблема по node-init решалась именно в рамках гильдии Kubernetes, без особой бюрократии в виде проведения по процессу и согласований со стороны проджект-менеджеров.

Немного о процессах в нашем банке. У нас стараются по возможности придерживаться концепции InnerSource — OpenSource для внутреннего кода. Все репозитории доступны для чтения всем командам разработки, если репозиторий явно не закрыт по соображениям ИБ. Их можно форкать, а при желании можно и сделать свой PR (Pull Request) в чужой репозиторий. В проблеме с Garbage Collection Kubernetes эта система сыграла всем на руку. node-init создавали мы, трайб IT4IT, а DevOps с проблемой пришёл из совсем другого трайба. Он сразу сделал PR со своей альфа-версией реализации, и дальше мы уже исследовали проблему совместно.

Кроме того, у нас есть система гильдий — особых совместных и полностью добровольных комьюнити, посвящённых какой-то теме или технологии. Из активных у нас есть, например, гильдия Javа и гильдия Kubernetes. ОТП официально разрешает членам гильдии тратить 5% рабочего времени на её задачи. Так, например, последняя проблема по node-init решалась именно в рамках гильдии Kubernetes, без особой бюрократии в виде проведения по процессу и согласований со стороны проджект-менеджеров.

[debug]
  level = "info"

[plugins.linux]
  shim = "/home/kubernetes/bin/containerd-shim"
  runtime = "/home/kubernetes/bin/runc"

[plugins.cri]
  stream_server_address = "127.0.0.1"
  enable_tls_streaming = false
  sandbox_image = "cr.yandex/crpsjg1coh47p81vh2lc/pause:3.7"
  [plugins.cri.containerd]
    snapshotter = "overlayfs"

[plugins.cri.cni]
  bin_dir = "/home/kubernetes/cni/bin"
  conf_dir = "/etc/cni/net.d"

Наша идея была в том, чтобы в начало конфига добавить строчку
imports = ["/etc/containerd/client_*.toml"]

И рядышком положить наш конфиг в /etc/containerd/client_otp.yaml
[plugins.cri.registry.mirrors]
    [plugins.cri.registry.mirrors."cr.yandex"]
      endpoint = ["https://nexus.our"]

Сначала вроде всё работало. Мы уже хотели идти к ребятам из Yandex Cloud и просить добавить в config.toml такой импорт, чтобы все клиенты могли по желанию расширять конфиг containerd. Но оказалось, мы рано обрадовались.

Совершенно внезапно выяснилось, что containerd не делает глубокого мерджа своих секций. Если в импорте, как у нас, была затронута хоть чуть-чуть секция plugins.cri, то он всю её перезаписывает!

Получается, что мы добавили только зеркала, но с точки зрения containerd — это полноценная секция, где все остальные значения, кроме зеркал — значения по умолчанию.

Итоговый конфиг containerd выглядел после нашего импорта таким образом (сильно сокращённый вариант):

Команда на получение текста рабочего конфига: sudo containerd config dump.

# ... something ...
[plugins."io.containerd.grpc.v1.cri"]
   # ...
   sandbox_image = "registry.k8s.io/pause:3.6"
   # ...

[plugins."io.containerd.grpc.v1.cri".cni]
   bin_dir = "/opt/cni/bin"
   conf_dir = "/etc/cni/net.d"
   # ...
# ... etc ...

Обратите внимание, что sandbox_image стал ссылаться на официальный регистри Kubernetes вместо cr.yandex, а пути к бинарникам cni стали стандартными.

Ожидания на умный мердж by-key у нас не оправдались: containerd мерджит по секциям. Народ ругается об этом в GitHub с 2021 года.

Мы, конечно, можем повторить в своём конфиге стандартный конфиг Yandex Cloud, который есть сейчас… А что, если его изменят во время обновления?

Получается, мы не можем делать import. Поэтому сейчас мы варварствуем и делаем нечто подобное:

if [ $(grep -Pzoc '\[plugins.cri.registry.mirrors."cr.yandex"\]' config.toml) -eq 0 ]; then
  cat >> config.toml <<EOF
[plugins.cri.registry.mirrors."cr.yandex"]
  endpoint = ["https://nexus.our"]
EOF
fi;

У нас получается конфиг Yandex Cloud с добавлением в конец нашего зеркала.

Решение кажется предельно костыльным и очень опасным. Мы висим на краю, любое обновление конфига со стороны Yandex Cloud при обновлениях нам может поломать кластера. Так почему нам просто не сделать уже эти доступы до cr.yandex? Две причины:

  1. Организационная. Централизованный инструмент для хранения и проксирования артефактов у нас — наш Nexus. Он контролируется и мониторится Управлением информационной безопасности (УИБ), на нём есть наши сканеры. Репозиторий Yandex Cloud находится вне периметра и поэтому считается опасным.

  2. Техническая. Наш ИБ всегда готов идти на уступки в случае реальной необходимости, но мешает техническая проблема. Под капотом Yandex Cloud Registry использует редиректы на Object Storage. По ряду причин наш файрвол испытывает трудности при дешифровке S3-подобного трафика, и скорость скачивания неприлично падает: 70 Мб могут скачиваться 20 минут.

Сейчас историю с модификацией конфига containerd мы используем как временный workaround, пока не найдём другой выход.

DNS

Состояние DNS, которое у нас было в самом начале:

  1. Внутри облака можно ресолвить имена всех ресурсов этого облака.

  2. Ресурсы банка можно ресолвить, если в настройках подсетей прописать наш корпоративный DNS.

  3. Из одного облака нельзя ресолвить ресурсы другого облака.

  4. Из банка нельзя ресолвить ничего, что находится внутри облаков.

  5. Postgres PaaS общается только по доменным именам и не даёт IP.

Пункты 3 и 4 неприятны, но с ними можно худо-бедно жить. Хоть и не очень хорошо, прямо скажем. Но вот последний — прямо ахтунг. Поэтому мы начали искать возможность сделать хороший глобальный DNS, чтобы все и отовсюду могли друг друга ресолвить.

В каждом облаке за DNS отвечает ресурс VPC — то самое «сердце облака», через которое мы делали сетевую связность с нашей инфрой. DNS этого VPC по умолчанию содержит внутренние зоны Yandex Cloud, которые хранят адреса ВМ и PaaS, и позволяет создавать кастомные зоны. Но что важно — только в рамках этого облака! Однако у VPC обнаружилась очень крутая фича, которая позволяет делать пиринг с зонами в других VPC, то есть других облаков. В терминологии Яндекса это называется «общая зона видимости». Зона видимости делает синхронизацию записей между облаками.

Получается, мы можем все зоны всех других облаков (всех VPC в них) спирить в одну глобальную — так, как нарисовано на схеме:

Пиринг зон возможно сделать только через Yandex CLI:

yc dns zone update <source_dns_zone_id> --network-ids="<source_vpc_id>,<target_vpc_id>"

target_vpc_id — VPC техклауда. Так мы обозвали наше специальное облако, созданное для обслуживания всей интеграции с Yandex Cloud, в том числе DNS.

source_vpc_id — VPC облака, в котором нам нужны записи. Это будет облако трайба. Те облака, где люди работу работают.

source_dns_zone_id — зона, которую нужно добавить.

Одно облако — одна зона. Поэтому команду придётся выполнять для каждого облака (точнее — для каждого VPC, но у нас это по сути равнозначные понятия, потому что на одно облако — одна VPC).

Задача вроде одноразовая, но руки чешутся автоматизировать. Руки чесались у потомственного Windows-админа, поэтому вот вам скрипт на Powershell:

## пререквизиты: YC CLI должен работать из-под админа Организации

$allClouds=((yc resource-manager cloud list --format json)|convertfrom-json)

# сформировать список всех VPC нашей Организации
$allvpcid=@()
foreach($cloud in $allClouds){
   # достать системную папку infra и VPC в ней
   $folder = (yc resource-manager folder get infra --cloud-id $cloud.id --format json|convertfrom-json)
   $vpc = (yc vpc network list --folder-id $folder.id --format json | convertfrom-json)
   "$($cloud.name) - $($vpc.name)"
   # сложить в массив allvpcid
   if($vpc.id){
      $allvpcid += $vpc.id 
   }
}

# Все внутренние зоны Яндекса в техклауде (ВМ, PaaS, обратные),
   # Исключаем internal и 10.in-addr.arpa.
   # В результате останутся зоны
   # mdb.yandexcloud.net. (Database as a Service), 
   # ru-central1-internal (ВМки)
   # три обратные зоны {101-103}.10.in-addr.arpa  (см. выше про сети, это те самые диапазоны, которые выделяли под каждый ЦОД в Яндексе)
$tech_cloud_infra = "xxxxxxxxxxxxxx" # ID папки infra в нашем техклауде
$zones = (yc dns zone list --folder-id $tech_cloud_infra --format=json | convertfrom-json)|?{$_.zone -ne 'internal.' -and $_.zone -ne '10.in-addr.arpa.'}

# Форматируем список для прокидывания в YC CLI
# нужна строка с ID всех VPC, через запятую
$network_ids = $allvpcid -join ','
foreach($zone in $zones){
   # создаем зону видимости (то что я обозвал выше пирингом) 
   # между зоной в техклауде и всеми VPC организации
   # команда идемпотентная, можно выполнять много раз
   # для каждой зоны делаем общую видимость (пиринг) с такими же зонами в других облаках
   yc dns zone update $zone.id --network-ids=$network_ids
}

Результат манипуляций выше: все имена всех ВМ и PaaS становятся видны в VPC техклауда.

Здесь была забавная история. Мы не ожидали подвоха, но, оказывается, в Yandex Cloud было ограничение: можно было спирить друг с другом не более 5 VPC. Сначала новости от Яндекса были тревожными: они говорили о технической сложности расширения лимита. Был шанс, что наша затея провалится, но всё обошлось, специалисты Yandex Cloud через несколько месяцев расширили нам лимит до 100 возможных зон видимости.

Забавно то, что, получается, никто до нас не пробовал провернуть такую схему на больших масштабах — в итоге мы спирили в наш техклауд около 30 облаков.

Бонус для разработчиков: собственная зона в каждом облаке

# Все облака
$allclouds = (yc resource-manager cloud list --format json|convertfrom-json)
foreach ($cloud in $allclouds){
   # В этом облаке получаем папку infra, в ней VPC и кастомную зону 
   $folder = (yc resource-manager folder get infra --cloud-id $cloud.id --format json|convertfrom-json)
   #VPC текущего облака в виде объекта
   $vpc = (yc vpc network list --folder-id $folder.id --format json | convertfrom-json) 
   # Создаем кастомную зону в каждом облаке
   $cloudCustomZoneName = "$($cloud.name).yc.our."
   yc dns zone create --zone "$cloudCustomZoneName" --private-visibility --network-ids $vpc.id --folder-id $folder.id --name $cloud.name
   $zone=(yc dns zone list --folder-id $folder.id --format json |convertfrom-json)|?{$_.zone -like "$cloudCustomZoneName"}

   # И создаем зону видимости (пиринг) с техклаудом
   if($zone.id.count -eq 1 -and $zone.zone -ne 'ynd-tech-cloud'){
      yc dns zone update $zone.id --network-ids="$($vpc.id),enp0p245kbph70biceku"
   }
}

Теперь разработчики и девопсы в каждом облаке могут создавать себе самостоятельно записи через GUI Яндекса или через Terraform, Pulumi или Ansible. Красивые доменные имена каждому, и пусть никто не уйдет обиженным.

Мы все DNS-записи успешно «разместили» в одном облаке. Что дальше? У Yandex Cloud в каждой подсети на втором IP-адресе работает DNS-ресолвер. Если отправить ему запрос, мы получим имена и адреса всего, о чём знает VPC, из которой развёрнута подсеть. То есть нам осталось только подцепить и настроить форвардер над этим адресом: если запрос идёт на доменные зоны Яндекса, он отправляется на второй IP подсети, иначе — в наш корпоративный DNS.

Для отказоустойчивости размещаем такие форвардеры в каждом ЦОДе, ставим над ними балансер и его IP-адрес сообщаем всем заинтересованным. Заинтересованных двое: с одной стороны — наши трайбы в Yandex Cloud. Они в своих подсетях прописывают в настройках DHCP IP-адрес балансера. С другой стороны — это DNS самого банка. На нём прописываем зону yc.our и пробрасываем запросы в этот же IP:

Форвардеры у нас работают внутри Kubernetes. Несколько реплик CoreDNS раскиданы по разным ЦОДам с помощью topologyContraints, и опубликованы через hostPort, чтобы избежать лишнего хопа в сеть кубера.

Над ними поднят L4 балансировщик и, собственно, именно его IP, мы и отдаем командам. Балансировщик поднят отдельно от кубера, то есть мы не использовали Service типа LoadBalancer в самом Kubernetes на тот невероятный случай, если нам придется пересоздавать весь кластер. Балансер вне Kubernetes позволит нам сделать это бесшовно для наших пользователей.

Пример конфига выглядит так:

 {
    health {
        lameduck 10s
    }
    ready
    forward . 192.168.0.1 192.168.0.2 # внутренний DNS
    whoami
}
# Внутренние зоны Яндекса
mdb.yandexcloud.net {
    forward . 10.101.0.2 10.102.0.2 10.103.0.2
}
auto.internal {
    forward . 10.101.0.2 10.102.0.2 10.103.0.2
}
ru-central1.internal {
    forward . 10.101.0.2 10.102.0.2 10.103.0.2
}
# Кастомные зоны команд
yc.our {
    forward . 10.101.0.2 10.102.0.2 10.103.0.2
}
# Обратные зоны
101.10.in-addr.arpa {
    forward . 10.101.0.2 10.102.0.2 10.103.0.2
}
102.10.in-addr.arpa {
    forward . 10.101.0.2 10.102.0.2 10.103.0.2
}
103.10.in-addr.arpa {
    forward . 10.101.0.2 10.102.0.2 10.103.0.2
}

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

Есть ещё несколько важных моментов. Во-первых, для оптимизации каждая реплика CoreDNS в идеале должна смотреть на второй IP именно той сети, в который она поднята. То есть если реплика поднята на ноде 10.102.0.131, то все её forward должны выглядеть так:

ru-central1.internal {
   # IP 10.102.0.2 идет первым 
   forward . 10.102.0.2 10.101.0.2 10.103.0.2
}

Это логично, ведь если реплика развёрнута в 10.102, то зачем смотреть в сеть 10.101? Помимо производительности это ещё чревато тем, что если ЦОД, хостящий сеть 10.101, упадёт, то реплика в 10.102, прежде чем перейти к опросу в своей сети, будет некоторое время ждать ответа от первой упавшей, и клиент заметит деградацию производительности DNS.

Во-вторых, если деплоить CoreDNS в кубер, то надо обязательно поставить опцию lameduck. Я имею в виду вот этот кусочек в самом верху конфига:

health {
        lameduck 10s

Эта настройка устанавливает время, которое CoreDNS будет выдерживать, прежде чем завершится после получения сигнала SIGTERM. Все readiness-пробы при этом будут падать, но пользовательские DNS-запросы всё ещё будут обрабатываться.

Зачем это нужно? Дело в том, что у вышестоящих балансеров всегда есть задержка, прежде чем они понимают, что приложение не работает и нужно выключить его из списка балансировки. У них есть таймаут и повторные попытки. Поэтому даже если выставить параметры проб максимально агрессивными, между ними всегда будут задержки. Всегда есть окно, когда приложение уже не работает, а балансеры об этом ещё не знают.

Настройка lameduck как раз позволяет это обойти для случаев штатного завершения контейнера. Это задержка, при которой CoreDNS ещё на самом деле работает, но отдаёт readiness-хелсчеки вида «я уже мёртв».

Эта настройка — важный ключ к обеспечению бесшовных rollout CoreDNS. Мы тестировали и так, и эдак — делали rollout Deployment, обновляли и убивали ноды Kubernetes… Продуктивный вариант сервиса пережил частичную неработоспособность кластера (та сама проблема с Garbage Collection, о которой мы рассказывали выше), а также последовательное обновление Kubernetes на 7 минорных версий вверх — и мы при этом не словили ни единого разрыва. В том числе благодаря этой настройке.

Значение lameduck подобрано исходя из параметров хелсчека балансеров по примерной формуле: (пауза между пробами + timeout) * (количество проб, прежде чем балансер пометит реплику как неживую) + 2–4 секунды запаса на всякий случай.

Жизнь в облаке для финтеха — есть!

Правда, она немножко колючая. Периодически возникают проблемы с сетевыми доступами. В первое время было много непоняток с тем, как организовано сопряжение нашей внутренней ролевой модели (AD) и Yandex Cloud и как правильно выдавать доступы своей команде.

Все, кто входят в облако, проходят отдельное обучение, входят в курс особенностей, о которых рассказал в статье.

Кроме того, несмотря на принятые меры, безопасники всё равно пускают в облако отнюдь не всех. У нас есть особая схема, которая позволяет примерно понять, можно ли приложению в облако или нет. Но даже после неё безопасники проводят свой дополнительный аудит и выдают окончательное решение. Некоторых заворачивают: «Нет, в Яндекс вам нельзя».

Но всё это точно перевешивают плюсы: классная техподдержка, PaaS, упрощённое использование IaC (ниже уровень вхождения, мы можем описать большую часть инфраструктуры). Всё это снимает очень много нагрузки с наших департаментов и упрощает жизнь командам.

Заключение

Развёртка нашего облака заняла от 9 до 15 месяцев, смотря как считать: первые четыре месяца выбирали между различными облаками и делали пилот. Cетевая связность, автоматизация выдачи ролей, допиливание Kubernetes и решение будущей организации всё вместе заняло еще полгода. Потом аккуратно, буквально на руках, заводили в облако первые трайбы, решая всплывающие недочёты. Так, например, глобальный DNS и улучшения node-init были сделаны именно на этих этапах.

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

Теги:
Хабы:
Всего голосов 11: ↑11 и ↓0+11
Комментарии14

Публикации

Информация

Сайт
www.otpbank.ru
Дата регистрации
Дата основания
1994
Численность
5 001–10 000 человек
Местоположение
Россия