Русский
Русский
English
Статистика
Реклама

Контейнеризация

Альтернативы VirtualBox для любителей приватности и свободы. Гипервизоры и менеджеры виртуальных машин. Часть I

03.01.2021 22:21:10 | Автор: admin

Приветствую читателей.

Меня зовут Андреас, давно веду видеоблог (SunAndreas) на темы гражданской информационной безопасности и сегодня о том, какими гипервизорами и менеджерами виртуальных машин лучше пользоваться особым любителям приватности и свободы. Если вы не знакомы с иными материалами моего YouTube-канала, рекомендую ознакомиться с его содержимым.

В сети крайне мало информации по более этичным аналогам инструментов виртуализации, а также имеющаяся информация не достаточная и не вполне доступная для широкого круга лиц. Сегодняшний теоретический выпуск больше для рядовых пользователей и входит в цикл видео на моём канале о виртуализации, ожидайте и последующие практические части об установке, настройке и использовании более правоверной виртуализации, чем VirtualBox. В практических частях также поговорим и о работе виртуальных машин с VPN и TOR. А сегодня пока что немного теории.

В сети только и пестрит VirtualBox, да, VirtualBox. Конечно, это очень простой и удобный вариант для совсем новичков, а также и для пользователей виндоус, которые больше ценят удобство и популярность, но сознательным людям, использующим GNU/Linux системы, настоятельно рекомендую перейти, если вы всё ещё этого не сделали, на более этичные аналоги, после того как, вроде пару лет назад, Oracle, пересмотрела немного свои позиции в отношении VirtualBox.

Ранее, более 4 лет назад, когда ещё на канале публиковалось почти легендарное видео с названием SunbooK, тогда VirtualBox был большей мере открытым и свободным проектом, не было с моей стороны к нему никакой пренебрежительности, но ныне уже лучше всё же использовать иные гипервизоры, тем более, что они позволяют легко импортировать виртуальные диски, то есть по сути виртуальные машины, которые ранее были созданы с помощью VirtualBox. Таким образом переезд с VirtualBox на иные решения может обойтись вам даже без потери данных.

Перечисленные далее мной примеры графических оболочек, то есть программы-менеджеры, используют такие инструменты виртуализации как QEMU и KVM, а уже в свою очередь с этими инструментами виртуализации работает утилита с названием libvirt, которая функционирует как демон.

Далее я перечислю графические оболочки, то есть праграммы-менеджеры виртуальных машин, с которыми и будет работать рядовой пользователь не вникая, что такое QEMU, KVM и libvirt. Продвинутый пользователь может работать и напрямую через терминал с libvirt, но я озвучу программные решения для более широкого круга лиц.

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

AQEMU это более продвинутый вариант графической оболочки, но всё же не лучший, на мой взгляд. Менеджер позволяет осуществлять различные настройки, но по моему опыту немного уступает следующему менеджеру. Знайте о существовании AQEMU и фанаты тематики могут его опробывать самостоятельно, а мы двигаемся дальше.

Третий вариант. Virt-manager также, как и предыдущие менеджеры работает с QEMU и KVM через libvirt, но кроме того может работать и с иными вариантами виртуализации, не только с гипервизорами, но и с более лёгким вариантом виртуализации, а именно, с контейнерной виртуализацией,

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

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

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

Возвращаясь к Virt-manager, если вы пожелаете использовать с помощью него лёгкую контейнерную виртуализацию, то я могу порекомендовать не заморачиваться и не выискивать варианты, и использовать LXC. Если же вас интересуют иные варианты, ещё раз обращаю ваш взор к изучению отдельного видео на канале, ссылка под этим видео.

Останавливаться на работе Virt-manager с контейнерной виртуализацией с помощью LXC я не буду, так как, полагаю, это будет интересно значительно меньшему числу зрителей. А если я не прав, можете оставить комментарий с пожеланием отдельно осветить это направление. Вдруг желающих окажется не мало и я ошибаюсь.

На этом завершаем теорию и в следующей публикации разберём работу с QEMU и KVM. Существует и ещё один добротный вариант, это Xen, с которым также работает virt-manager, и я уважаю этот гипервизор и регулярно использую его. О нём я рассказывал в отдельном выпуске о виртуализации. А связка QEMU и KVM является доступной, лёгкой в работе, пожалуй, чуть более удобнее для простых пользователей и не требуется при загрузке операционной системы в загрузочном GRUB-меню выбирать никакие варианты.

В последующих публикациях я покажу работу virt-manager с QEMU-KVM, которая будет понятна даже не продвинутому пользователю, что считаю особо общественно-полезным делом, тем более, что работе данного ПО даже в английском сегменте интернета не достаточно информации на мой взгляд. А в русско-язычной сфере, так я вообще не встречал ничего особо достойного по данной теме. Лишь малоценные поверхностные обзоры.

Всем развития и успехов.

Подробнее..

Перевод Знакомство с Docker

01.06.2021 08:12:59 | Автор: admin

Это первая статья в цикле Знакомство с Docker. Если вы раньше не работали с Docker, мы расскажем, что он из себя представляет.

Что такое Docker?

Docker - это инструмент DevOps для контейнеризации сервисов и процессов... Подождите... Подождите... Подождите! Что такое DevOps? Что такое контейнеризация? Какие услуги и процессы я могу контейнеризовать? Начнём с самого начала.

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

Контейнер - это не более чем процесс, который выполняется изолированно в операционной системе. У него есть собственная сеть, собственная файловая система и выделенная память. Вы можете подумать, а почему бы просто не использовать виртуальную машину? Что ж, виртуальная машина - это отдельная ОС, сильно загруженная множеством других процессов, которые могут вам никогда не понадобиться, вместо виртуализации всей операционной системы для запуска одной службы вы можете виртуализировать службу. Точнее говоря, вы можете создать легкую виртуальную среду для одной службы. Этими службами могут быть серверы Nginx, NodeJS или приложения angular. И Docker помогает нам в этом.

Название Docker происходит от слова док (dock). Док используется для погрузки и разгрузки грузов на кораблях. Здесь можно провести простую аналогию, груз может быть контейнерами, а корабль может быть нашей операционной системой. Все товары в составе груза изолированы от товаров другого груза и самого корабля. Точно так же в Docker процесс одного Docker контейнера (Docker Container) изолирован от процесса другого контейнера и самой операционной системы.

Как работает контейнеризация

Docker использует технологию Linux Containers (LXC) и механизмы ядра Linux. Поскольку у docker-контейнера нет собственной операционной системы, он полагается на хостовую операционную систему. Контейнер, созданный в Linux, может быть запущен в любом дистрибутиве Linux, но не может работать в Windows, и то же самое касается образа, созданного в Windows. Docker расширяет возможности LXC, но также использует контрольные группы (cgroups), которые позволяют ядру хоста разделять использование ресурсов (ЦП, память, дисковый ввод-вывод, сеть и т. д.) на уровни изоляции, называемые пространствами имён (namespaces).

Как создать Docker контейнер?

Чтобы создать Docker контейнер, нам нужно сначала создать архив, содержащий все файлы и зависимости, необходимые для нашего проекта. Этот архив называется Docker образом (Docker Image). Важно помнить, что после создания Docker образа его уже нельзя изменить или модифицировать.

Большое количество готовых образов можно найти в DockerHub, общедоступном репозитории Docker, который позволяет вам делиться своими образами или использовать образы, созданные другими людьми. Вы также можете создавать свои собственные образы и помещать их в свой частный репозиторий (например, Harbor). В дальнейшем этот образ будет использован для создания контейнеров. Один и тот же образ можно использовать для создания одного или нескольких контейнеров, используя Docker-CLI. А что такое Docker CLI спросите вы?

Рассмотрим архитектуру Docker,

  1. Docker демонслушает запросы Docker API и управляет всеми объектами Docker, такими как образы, контейнеры, сети и тома. Это основная служба Docker, которая необходима для работы контейнеров и других компонентов Docker. Если Docker демон перестанет работать, так же перестанут работать все запущенные контейнеры.

  2. Docker демон также предоставляет REST API. Различные инструменты могут использовать его для взаимодействия с демоном. Вы также можете создать приложение, для работы с Docker REST API.

  3. Docker-CLIэто инструмент командной строки, который позволяет вам общаться с демоном Docker черезREST API.

Сеть Docker

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

  • Host networks -Контейнер Docker будет использовать сеть хоста, соответственно он не будет изолирован с точки зрения сети, это не коснётся изоляции контейнера в целом, например изоляции процессов и файловой системы.

  • Bridge networks - Позволяет изолировать ваши приложения, но они могут взаимодействовать между собой и принимать трафик снаружи, если включено сопоставления портов (port forwarding).

  • Overlay networks - Оверлейные сети соединяют вместе несколько демонов Docker и позволяют службам Docker Swarm взаимодействовать друг с другом. Docker Swarm (аналог Kubernetes) может использоваться, если у вас несколько серверов с Docker.

  • Macvlan networks - Позволяет назначить MAC-адрес контейнеру, чтобы он отображался как физическое устройство в вашей сети.

  • None -Сеть отсутствует, соответственно вы не сможете подключиться к контейнеру.

И всё же, почему стоит использовать Docker?

  • Как разработчик вы можете легко упаковать свой проект со всеми его зависимостями и файлами, создать для него образ, и при этом быть уверенными, что он будет работать в любом дистрибутиве Linux.

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

  • Вы можете ограничить потребляемые контейнером ресурсы - ЦП и память, это позволит запустить больше контейнеров на одном сервере.

  • Возможность запуска нескольких контейнеров из одного образа, позволит сэкономить место на диске и в репозитории образов.

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

  • Вы можете передать ваш образ коллегам из команды тестирования, они смогут создать несколько экземпляров приложения (контейнеров) из этого образа и выполнять необходимые тесты.

Вместо заключения

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

Подробнее..

Как устроен Kubernetes as a Service на платформе Mail.ru Cloud Solutions

28.10.2020 14:12:50 | Автор: admin

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

Я Дмитрий Лазаренко, директор по продуктам облачной платформы Mail.ru Cloud Solutions (MCS). Сегодня я расскажу, что под капотом у нашего Kubernetes aaS, как обеспечивается его надёжность и какие у него есть интересные функциональности, которыми любят пользоваться наши клиенты. Это автомасштабирование, интеграция с другими PaaS нашей платформы и многое другое.

Главные фичи Kubernetes на платформе MCS


Наш Kubernetes aaS включает:

  • Интерфейс управления для создания кластера в несколько кликов, масштабирования и настройки.
  • Автоматическое масштабирование узлов кластера в большую или меньшую сторону, то есть добавление или удаление нод (Cluster Autoscaler).
  • Встроенный мониторинг на основе Prometheus Operator и Grafana. Многие наши пользователи начинают с базовых инсталляций, где запускается приложение. Когда оно выходит в продуктив, это позволяет им мониторить сервисы и сам кластер.
  • Свой Terraform-провайдер для Kubernetes. Он полностью поддерживает API MCS.
  • Интеграция с Docker Registry для хранения и управления образами.
  • Автоматизированное развёртывание федеративных кластеров Kubernetes на базе AWS и Mail.ru Cloud Solutions (о чём мы писали тут).
  • Возможность сделать Start/Stop для кластера целиком экономия для тестовых сред. Вы можете выключить кластер одним кликом в интерфейсе и платить только за диски в случае остановленных кластеров.
  • Поддержка создания Node Pools, пулов виртуальных машин разных размеров: можно запускать тяжелые задачи на больших машинах, веб-приложения на маленьких. Масштабировать группы можно независимо и размещать их в разных регионах либо зонах доступности (для большей надежности и доступности).
  • Persistent Volumes интегрированы с системой хранения OpenStack.
  • Поддержка приватных кластеров, доступных только через VPN-соединение.
  • Поддерживается Cluster Policy: Local, которая позволяет получать реальные IP пользователей внутри кластеров.
  • Создание и масштабирование кластеров Kubernetes с помощью UI или API MCS, управление сущностями через Kubernetes dashboard и kubectl.
  • Плавное обновление (rolling update) в один клик без простоя как для минорных, так и для мажорных версий. Обновления кластеров до 1.16.
  • На момент написания статьи мы поддерживаем Kubernetes вплоть до версии 1.17.




Создание кластера Kubernetes в несколько кликов

Дальнейшее развитие сервиса:

  • CI/CD aaS, интегрированный с Kubernetes и другими сервисами платформы: дополнительные сервисы, которые обеспечивают CI/CD, на базе наших собственных доработок OpenStack.
  • Логирование aaS для приложений приложений, которые работают в нашем Kubernetes. Логирование будет реализовано на базе нескольких решений OpenStack.
  • Service mesh: у нас появятся плагины для Kubernetes, которые в рамках реализации service mesh будут выполнять шифрование, бэкапирование и другие функции.

Сертификация дистрибутива в Cloud Native Computing Foundation


Mail.ru Cloud Solutions входит в CNCF (Cloud Native Computing Foundation). Дистрибутив Kubernetes от MCS получил сертификат Certified Kubernetes Hosted. Его проверили на надежность и соответствие стандартам, он отвечает всем функциональным требованиям сообщества и совместим со стандартным Kubernetes API. MCS пока единственный в России облачный провайдер, получивший такую сертификацию.

Место Kubernetes в инфраструктуре облачной платформы


Самый нижний слой типовые физические серверы (compute nodes). Сейчас их несколько тысяч, они используются под вычисления и хранение. Для хранения мы предоставляем файловые и блочные хранилища на базе Ceph и S3-совместимые объектные хранилища. Серверы распределены по дата-центрам, между которыми проложена сеть 40 Gbps.

Поверх уровня серверов работает OpenStack, который обеспечивает виртуализацию для пользовательских окружений. А уже поверх виртуальных машин, сетей и балансировщиков работают PaaS-решения: Kubernetes, базы данных, DWH на базе ClickHouse, Hadoop, Spark и другие.

Аналогичную схему мы строим и в приватных инсталляциях Kubernetes как сервиса в дата-центрах наших заказчиков в формате частного облака.



Архитектура облачной платформы

Интеграция Kubernetes с облаком не односторонняя. Kubernetes не просто развертывается на виртуальных машинах, он полностью интегрируется с IaaS OpenStack.

На основе провайдера Cloud Provider OpenStack мы сделали Cloud Provider для MCS, который в рамках вашего проекта (тенанта) OpenStack соединяется с API MCS и создает, конфигурирует, удаляет диски, балансеры, внешние IP-адреса, подключает их к нодам Kubernetes, конфигурирует security-группы (фактически виртуальный firewall). Без Cloud Provider создание тех же Persistent Volumes головная боль для всех, кто запускает Kubernetes on-premise, на железе либо просто в облаке.



Интеграция Kubernetes с IaaS OpenStack

Какие инструменты мы используем


  1. Операционная система. Сначала мы использовали CoreOS, которая работает на хостах, сейчас у нас Fedora Atomic (1.14-1.15) и CentOS (1.16).
  2. Сеть Calico. Сети Kubernetes зависят от облачной сети, которая обеспечивается SDN всего облака. В основе нашей SDN изначально был OpenStack Neutron. Но год назад мы начали разработку модуля Sprut нашего собственного SDN-решения, которое поддерживает API Neutron, но работает по другим принципам. Подход Sprut решил наши проблемы масштабируемости, возникающие из-за десятков тысяч сетевых сущностей (портов) у нас в облаке, когда при падении сетевых нод в сети такого размера начинался процесс полной синхронизации (fullsync). Сейчас Sprut мы задействуем для тех клиентов, для которых в силу особенностей нагрузки на сеть использовать его целесообразнее, чем Calico, в перспективе мы его откроем для всех.
  3. Кластерный DNS на базе CoreDNS, со всеми его Service Discovery, метриками Prometheus и другими стандартными фичами.
  4. Ingress Controller. Сейчас это Nginx, но мы также планируем добавить Envoy, как дополнительный Ingress Controller. Наши тесты показывают, что Envoy часто быстрее. Ingress Controller интегрирован с облачным балансировщиком нагрузки на базе OpenStack Octavia и поддерживает Proxy Protocol.
  5. Мониторинг на базе Prometheus Operator. Раньше использовали просто Prometheus, но сейчас все хотят автоматизацию и сервис-мониторы, поэтому мы уже несколько месяцев предлагаем Prometheus Operator + Grafana, в рамках которой можно добавлять сервис-мониторы и выполнять мониторинг кластеров.
  6. Аддоны (опциональные расширения). В один клик можно установить Docker registry, интегрированный с нашим S3-хранилищем, ingress controller, различные системы мониторинга (Heapster, Prometheus).

Multi Master и сетевая топология


Kubernetes от Mail.ru поддерживает деплой в формате Multi Master, при этом каждая пользовательская группа нод уже находится в конкретной зоне доступности.


Multi Master в облаке

В Multi Master etcd работает в кластерном режиме, так что если что-то случается с одним из мастеров, другие продолжают работать. Под каждый etcd выделен отдельный SSD-диск, что обеспечивает хороший latency и быструю работу API-сервера, т.к. в etcd находится служебная информация о всех ресурсах кластера Kubernetes.

Для доступа извне используется балансировщик нагрузки API сервера Kubernetes, который имеет внешний IP-адрес. При этом все ноды и мастера, и миньоны находятся в приватной сети (фактически в виртуальном частном облаке) и не имеют публичных адресов.

Доступ к кластеру Kubernetes из публичной сети: запуск трафика и балансировка нагрузки


В общем случае способы доступа к сервисам внутри кластера перечислены здесь. Подробности нашей реализации:

NodePort открывает публичный порт на ноде. Однако есть ограничение: в целях безопасности по умолчанию публичные IP-адреса не установлены ни на мастера, ни на миньоны, кластеры создаются без белых IP-адресов. Пользователь может их сам установить.

Load Balancer. Наш Kubernetes интегрирован с облачной платформой MCS, так что платформа предоставляет Load Balancer как сервис и может сама создавать балансировщики. Для сравнения, если пользователь настраивает Kubernetes (например, в он премисе), нужно самостоятельно поднимать и настраивать софтверные балансеры. На платформе MCS балансировщики поднимаются сразу в отказоустойчивом режиме active-standby. Когда поднимается основной балансер (на HAProxy), у него всегда есть standby, спящий балансер. Между ними настроен VRRP. Если основной балансер отказывает, весь трафик мгновенно переключается на standby, при этом IP-адрес не меняется.



Отказоустойчивый Load Balancer как сервис на платформе MCS. Kubernetes создаёт nodeport на каждой ноде и балансировщик

В настройке балансировки для Kubernetes помогает наш Cloud Provider. Нужно создать манифест, в котором пользователь указывает тип манифеста сервис и тип сервиса Load Balancer. После деплоя этого манифеста Kubernetes (точнее, Cloud Provider, который работает в Kubernetes) обращается к OpenStack API, создаёт балансировщик и внешний IP-адрес, если это необходимо. Если внешний адрес не нужен, нужно поставить аннотацию, что требуется внутренний балансировщик, и можно пускать трафик на кластер, не открывая публичный IP-адрес на каждой ноде.

apiVersion: v1kind: Servicemetadata:name: nginxlabels:  k8s-app: nginx-backend annotations:  service.beta.kubernetes.io/openstack-internal-load-balancer:"true"spec: type: LoadBalancer externalTrafficPolicy: Cluster selector:  k8-app: nginx-backend ports: -port: 80  name: http  targetPort: http -port: 443  name: https  targetPort: httpn

Сервисный манифест для создания балансировщика нагрузки с помощью Cloud Provider

Не всегда удобно создавать по балансеру на каждый сервис, 10 сервисов есть 10 балансировщиков, 50 сервисов 50 балансировщиков. Ими потом также приходится управлять, это тяжелые сущности. Эту проблему решает Ingress.

Ingress. Чтобы можно было не создавать много балансировщиков, мы добавили поддержку Ingress Controller. Ingress Controller интегрирован с балансировщиком OpenStack. То есть в декларации сервиса конкретного Ingress Controller указан тип Load Balancer. Для кластера создается один балансировщик, по которому Ingress Controller работает и дальше распределяет трафик по сервисам. Ingress Controller балансирует по DNS-именам.



Схема работы Ingress

Для некоторых клиентов было важно, чтобы в подах было видно IP-адреса клиентов, получающих доступ в кластер. При балансировке теряются заголовки IP-пакетов: приложение не получает реальный IP-адрес клиента. Балансировщик OpenStack ещё видит заголовок X-Forwarded-For, но Ingress Controller и под его уже не получают. Это не позволяет настроить доступ пользователей по White Lists, не работают сервисы типа GeoIP или anti-DDoS, которым нужно видеть реальные IP-адреса клиентов.



IP-адрес клиента не доходит до пода

И здесь у нас оказалось два решения:

Сделать режим proxy-протокола как в Amazon. Ради этой возможности мы перешли на балансировщик OpenStack Octavia, так как в стандартном балансировщике OpenStack нет такой опции. В итоге мы сделали новый балансировщик, который поддерживал как TCP-балансировку, так и HTTP с терминацией SSL.

При этом поддержку proxy-протокола нужно включать как на самом балансировщике (HAproxy), так и на Nginx Ingress Controller, который выступает таким приемником. Иначе схема пропускания трафика ломается. Также важно, что SSL-терминация, если у вас стандартный веб-трафик, должна проходить на Ingress:



Терминация SSL на балансировщике. Здесь на балансер приходит HTTPS, он расшифровывается, и в кластер идет HTTP. Если всё это сделать и активировать в сервисе ExternalTrafficPolicy: Local, вы будете видеть заголовки IP-пакетов:



Storage и Kubernetes


Если разворачивать Kubernetes локально или в облаке просто на виртуальных машинах, то по умолчанию в нем нет нормальной работы с постоянными дисками. Можно использовать Host Path, Local volume (no-provisioner), либо прямо в кластере Kubernetes разворачивать экзотические программно-определяемые системы хранения типа Linstor или OpenEBS. Но что произойдет с данными или очередью данных, которая размещается в кластере, если умрет нода или под?

При самостоятельном подключении блочных устройств к кластеру есть проблемы: CSI-драйверы не идеальны для многих типов стораджей, и автоматическое перемонтирование может не произойти. Мы сделали работу с блочными устройствами автоматизированной. Чтобы при отключении пода блочное устройство переподключалось к новому поду само.

Мы используем Ceph. Главное, что они работают через OpenStack, который предоставляет специальные модули, абстрагирующие Kubernetes (или любые виртуальные машины, работающие в облаке), на конкретных драйверах OpenStack Cinder.

У нас несколько разных storage-классов, которые работают в Kubernetes: SSD Ceph, HDD Ceph, геораспределенные Ceph между нашими ЦОДами. Есть storage-класс, отвечающий за блочные диски: фактически это дисковые шкафы с SSD, они подключаются к хост-машинам по iSCSI.



Несколько Storage-классов в MCS

При необходимости мы используем NFS, когда клиенты не могут переписать приложения в микросервисную архитектуру. У нас есть аналог сервиса EFS от Amazon файловое хранилище с NFS-протоколом, доступное как сервис. Оно подходит, если у вас legacy-приложение, которое вы переводите в Kubernetes.

Кроме того, у нас есть локальные SSD, но здесь сложно гарантировать их доступность и переезд данных, поскольку они доступны только с физических серверов, к которым подключены.

Всё это подключается через единый модуль OpenStack OpenStack Cinder, к каждой ноде Kubernetes и обеспечивает возможность переезда стораджа в случае падения ноды. А также когда повышается нагрузка чтения/записи и Kubernetes решает перевозить неважные поды на другие ноды тогда он автоматически переводит монтирование этого диска к другим Kubernetes-нодам.



Так происходит автоматическое перемонтирование

Можно использовать storage class, написав декларации PersistentVolumeClaim. На примере, который изображён ниже, Cloud Provider выделит в заданной зоне доступности новый Persistent Volume, размером 30 ГБ с типом диска SSD, подключит его к ноде и примонтирует к подам. Также он будет следить, чтобы этот диск переезжал между нодами в случае переезда подов:

kind: PersistentVolumeClaimapiVersion: v1metadata: name: nginx-pvc-ssdspec: accessModes: -ReadWriteOnce storageClassName: dp1-ssdresources: requests:  storage: 30Gi


Автоматическое масштабирование


В MCS есть Cluster Autoscaler. Это не просто автоскейлинг подов внутри кластера, а автоскейлинг самого кластера по необходимости: новые ноды добавляются, когда нагрузка выросла, и удаляются, если нагрузка упала. Масштабирование происходит автоматически до 100 узлов и обратно за несколько минут.

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

Cluster Autoscaler лучше настраивать совместно с Horizontal Pod Autoscaler. Различие использования двух вариантов Autoscaler:

  • Cluster Autoscaler позволяет расширять сами выделенные для кластера ресурсы. По сути он может автоматически арендовать дополнительные ресурсы или сократить их использование через Cloud Provider.
  • Horizontal Pod Autoscaler позволяет расширять ресурсы подов в рамках существующих выделенных ресурсов кластера, чтобы оптимально их использовать.




Настройка автоскейлинга

Функциональности


Совместимость со стандартными инструментами Kubernetes


Так как наш Kubernetes aaS полностью совместим со стандартным Kubernetes API, вы можете свободно пользоваться всеми возможностями экосистемы Kubernetes.

  • Хранение и обработка serverless-функций в контейнерах: OpenFaaS, OpenWhisk, Kubeless.
  • Инструменты Service Mesh: Istio, Consul, Linkerd.
  • Мониторинг, аналитика, логирование: Prometheus, Fluentd, Jaeger, OpenTracing.
  • CI/CD: Gitlab, CircleCI, Travis CI.
  • IaC (описание приложений): Terraform, Helm.

И многие другие инструменты.

Про Terraform отдельно стоит сказать, что стандартный провайдер OpenStack не был полностью совместим с API платформы MCS, так что мы сделали собственный Terraform-провайдер, который полностью совместим с последней версией API MCS. Поддержка API включает:

  • листинг ресурсов MCS (cluster, cluster template, node group)
  • поддержку managed node groups
  • поддержку действий через API: создание/удаление, горизонтальное и вертикальное масштабирование, включение/выключение кластера, обновление версии.


Безопасность


  • Kubernetes использует аутентификацию по сертификатам.
  • Систему безопасности кластеров можно интегрировать с LDAP/Active Directory для аутентификации пользователей. При этом ролевую модель безопасности в Kubernetes можно настроить на проверку прав доступа на основе принадлежности пользователя к группам в LDAP-каталоге.
  • Для сетевой безопасности можно применять Calico Network Policy.
  • В наш Kubernetes aaS интегрирован Docker Registry, защищённый SSL.
  • Планируем реализовать SSO (single sign-on) в интеграции с нашим IAM (identity and access management) на уровне OpenStack.

Резервное копирование и миграция


  • Мы поддерживаем интеграцию с Velero. Velero выполняет резервное копирование, которое позволяет бэкапить манифесты etcd и Persistent Volumes, вот гайд по тому, как это сделать.
  • Также с помощью Velero можно мигрировать кластеры on-premises и других провайдеров на наш Kubernetes.
  • Или запросите миграцию на наш Kubernetes под ключ. Поможем.

Работа с большими данными


Kubernetes по сути можно использовать для любых микросервисных приложений, работающих с данными. Чем Kubernetes на платформе MCS интересен для data scientistов:

  • Автомасштабирование позволяет выдерживать большие вычислительные нагрузки.
  • Можно создавать событийные (event-triggered) обработчики данных.
  • Приложения на Kubernetes легко интегрировать с другими нашими PaaS для Big Data, машинного обучения, в рамках одной сети.
  • Если хочется поэкспериментировать, то для ускорения обучения к очереди событий или событийному обработчику на базе Kubernetes можно напрямую подключить GPU.

Ещё о нашем Kubernetes aaS


  1. Попробовать бесплатно наш Kubernetes aaS можно тут.
  2. В этих двух Telegram-каналах вас ждут новости нашего Kubernetes aaS и анонсы мероприятий @Kubernetes meetup.
Подробнее..

Контейнеризация понятным языком от самых азов до тонкостей работы с Kubernetes

27.11.2020 18:11:38 | Автор: admin


Чем контейнеры отличаются от виртуальных машин, почему Docker настолько популярен, что такое Kubernetes и в чём его преимущества и недостатки. В интервью АйТиБороде СТО Слёрма Марсель Ибраев и старший инженер Southbridge Николай Месропян рассказали о контейнеризации понятным языком. Мы перевели интервью в текст для тех, кому лень смотреть.
Мне не лень смотреть, мне лень читать


Разница между контейнеризацией и виртуализацией


Что такое виртуализация?


Виртуализация появилась как средство уплотнения окружений на одном и том же железе. Сначала программный продукт выполнялся на железном сервере. Потом, чтобы иметь возможность поселять в одно и то же железо больше клиентов, чтобы максимально полно утилизировать производительные мощности, придумали виртуализацию. Теперь на одном и том же железе можно держать несколько окружений. В зависимости от среды, опять же. Есть полностью проприетарные решения, такие как vmware vsphere, есть опенсорсные решения, как QEMU KVM, на основе которого Red Hat делает свой коммерческий гипервизор Red Hat Virtualization. На платформе Windows есть Hyper-V.


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


Ядра разделяются физически или можно виртуально разделить там одно физическое ядро на несколько при виртуализации?


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


Что такое контейнеры и контейнеризация, и чем отличаются?


Детали зависят от операционной системы, на которой выполняется контейнер, но вообще контейнер делит с хостом ядро, пространство памяти ядра, и своё у контейнера только пользовательское окружение. Первая широко распространенная технология контейнеризации в Linux это OpenVZ, которая потом превратилась в коммерческий продукт Virtuozzo. Мы много работали и работаем с OpenVZ. У нас клиентские окружения жили в контейнерах OpenVZ, пока мы не перешли на более современные технологии.


То есть контейнер от виртуальной машины отличается только тем, что в контейнере общее адресное пространство?


Нет. Виртуальная машина изолируется полностью средствами процессора (технологии Intel, AMD, VMX).


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


Контейнер это продолжение виртуализации? То есть это технология, которая является преемником виртуализации?


Нет. Они ни в коем случае не конкурируют. Они занимают совершенно разные ниши в использовании.


Тогда почему их постоянно сравнивают? И постоянно есть вопрос, что лучше виртуализация или контейнеризация?


С моей точки зрения сравнить контейнеризацию и виртуализацию нельзя. Это сравнение теплого с мягким.


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


Для тебя виртуальная машина это обычная изолированная операционная система, целиком: своё ядро, свой init, systemd и так далее. Чем она отличается от контейнера с точки зрения потребления ресурсов? Тем, что она полностью занимает все ресурсы, под неё выделенные. То есть, есть механизмы, когда можно динамически, то есть в зависимости от потребления процессами внутри виртуальной машины, освобождать память на хосте или занимать её. Но это всё полумеры.


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


Если мы говорим о Docker (а в рамках разговора мы не сможем обсудить все варианты контейнеризации), то он рассчитан на то, что в одном контейнере работает одно приложение.


Возвращаясь к твоему первому вопросу, разница вот в чём. Допустим, если у тебя на хосте Linux или VMware, то виртуальная машина у тебя может быть Windows. Если у тебя в контейнере Linux, то у тебя и снаружи Linux. Потому что мы в первую очередь пользуемся для изоляции не средствами железа, не средствами гипервизора, а средствами операционной системы cgroups и namespace.


Почему контейнеры разворачиваются быстрее? Потому что они маленькие, содержат в себе там одно приложение? Почему быстрее развернуть контейнер, нежели зафигачить, законфигурировать?


Виртуалка сама по себе большая, так как содержит целую операционную систему. Если нам нужно развернуть виртуальную машину, то нужно нести с собой и ядро, и всё пользовательское окружение, и какой-то запас места (потому что динамически оно с хостом шариться в общем случае не может). Я не видел линуксовую виртуальную машину весом меньше 10 Гб, и это без данных. Потом к ней еще нужно прицепить диски для данных, в зависимости от того, что будет внутри.


Если говорить о контейнерах, есть разные дистрибутивы, в том числе специально созданные для контейнеризации, тот же Alpine Linux, который в голом виде весит 20 или 50 Мб в зависимости от версии. То есть ничего не весит, собственно говоря.


Виртуалка тянет полностью всю операционку, а когда Docker создаешь, ты тянешь только какие-то небольшие пакеты?


Нет. Чтобы создать Docker-контейнер ты должен собрать образ. Ты берёшь какой-то базовый образ, тот же Alpine, CentOS или Ubuntu. В него с помощью специальных команд зашиваешь свое приложение и выгружаешь уже туда, где оно будет работать.


То есть все равно ты в контейнере используешь полноценную операционку? Вот тот же образ Alpine Linux.


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


Но потенциально ты можешь и полноценный Linux запустить в контейнере?


Потенциально да, можешь.


Но смысла в этом, наверное, нет.


В этом совершенно нет никакого смысла, потому что хорошей практикой при использовании Docker считается один контейнер одно приложение.


Один контейнер? А это не слишком жирно использовать для одного приложения, ну пусть и урезанную, но операционную систему?


Когда нужна изоляция это не слишком жирно.


Понял. Есть ли какие-то еще инструменты, которые позволяют сделать что-то похожее на контейнеризацию, но не контейнеризация?


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


Почему Docker захватил весь рынок? Вот ты говорил, что было решение какое-то изначально в Linux?


Нет. Оно занимало и занимает совершенно другую нишу. Docker захватил весь рынок в первую очередь потому, что он первым начал использовать технологии namespace и cgroups для, так сказать, народа. Понизил порог вхождения в эти технологии до того, чтобы можно было выйти на широкий рынок, на широкого пользователя.


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


А как тут обстоит дело с дебагом твоего кода, логированием и всем остальным? Со стороны кажется, что это сложновато: нужно залезть внутрь какого-то контейнера, который представляет из себя урезанную операционку


Когда работаешь с контейнерами, в принципе не обязательно думать, что это операционка, не операционка. Там начинается другой мир. Да, к нему надо привыкнуть, но с дебагом, логированием проблем нет никаких, потому что хорошим тоном считается писать все логи в stdout/stderr контейнера, а не в файлики внутри него.


Docker-контейнер знаменит тем, что он одноразовый. Он запустился, а после того, как ты контейнер удаляешь, если ты специально никаких мер не предпринимал, чтобы сохранить в нём данные, у тебя всё удаляется. Поэтому все логи обычно пишут в stdout/stderr, средствами Docker или внешних утилит экспортируют их в ElasticSearch, ClickHouse или какие-то другие системы хранения логов и централизованно уже с ними работают. В первую очередь потому, что контейнеров много. Контейнеров в сетапах могут быть десятки, сотни, тысячи и десятки тысяч.


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


Что насчет контейнеризации в Windows? Насколько я помню, там если не всё очень плохо, то не всё так просто, как на Linux.


Там, действительно, очень сложно. Я ни в коем случае не Windows-админ, знаком поверхностно. Но насколько я знаю, нативная контейнеризация в Windows есть. Есть средства изоляции и по ресурсам, и по пространствам имен, сетевые пространства имен, для памяти, под файлы и так далее. То есть можно Windows запустить как контейнер Windows. Это Windows Server Containerization, если я не ошибаюсь (Windows-админы, не обессудьте).


Но если мы говорим о том, чтобы запускать Docker в Windows, то здесь начинаются пляски. Docker это технология Linux, потому что использует специфические средства для изоляции, для создания контейнеров.


Когда контейнер выполняется, он не представляет собой некий образ. Когда выполняется виртуальная машина это образ, внутри которого своя файловая система, свои разделы, где всё это нарезано и всё это варится. Когда выполняется контейнер, для операционной системы это просто набор ограничений. Когда мы смотрим на процесс виртуальной машины с хоста, мы видим один процесс. В винде это Microsoft Hyper-V, в Linux это QEMU KVM, в vSphere это тоже один процесс. Когда мы смотрим с хоста на контейнер, то видим дерево процессов.


Но почему мы образы передаем друг другу? Я приложение запаковываю в Docker, и мы девелоперы передаём друг другу образы.


Образ это то, из чего контейнер запускается. А с точки зрения хоста это дерево процессов, которые ограничены через встроенные средства ограничения, то есть через namespace и cgroups. Это я к тому, что Docker по своей сути линуксовый.


А почему нельзя было сделать универсальное решение, чтобы оно и для Linux, и для винды работала? Там нет общих API или в Linux есть что-то, чего нет


Ядро-то разное.


Архитектура разная, да?


Да, API Windows и API Linux это совершенно разные вещи. По той же причине нет нативного Docker для macOS. Потому что используются средства изоляции линуксового ядра.


Я думал, что ядра macOS и Linux очень похожи.


Нет. macOS больше UNIX-like, нежели Linux. Потому что, как известно GNU is not UNIX (рекурсивный акроним). А macOS внутри более, так сказать, близка к юниксам. И там нет таких механизмов, как в Linux. Они развиваются независимо.


Docker и для Windows, и для macOS это чужеродное тело, которое запускается в линуксовой виртуалке.


Получается, чтобы запустить контейнер, нужно запустить еще и виртуалку?


Мы запускаем линуксовую виртуалку, а уже в ней мы уже запускаем эти контейнеры. Docker Desktop скрывает от пользователя все сложные процессы, но внутри все равно остаются всякие. Ну не то, чтобы это очень неэффективно. Если вам нужно разрабатывать что-то под Docker, но у вас только Windows или только macOS, то это позволяет работать, да. Но в продакшене с нагрузками так ничего толком не запустишь.


Я понял, что ты в основном с Linux работаешь, но вдруг ты слышал про WSL (Windows Subsystem for Linux)?


Разумеется, я на OpenNET читаю об этом всём и удивляюсь.


А может ли эта штука запустить контейнеры нативно? Я просто не знаю, она тоже под виртуалкой?


Насколько я понимаю, WSL это Wine наоборот. То есть трансляция вызовов API в нативные для винды. Если у нас Wine это трансляция вызовов виндового API для ядра Linux, то WSL это наоборот. И поэтому средств изоляции там ядерных линуксовых нет. Поэтому увы, увы.


Про оркестрацию


Скажем, у нас микросервисная архитектура, и не одно приложение, а много всего: 10, 20, 40, 100 микросервисов. Руками их конфигурировать совсем не прикольно. Как с этим разбираются?


Да, это вполне типовая ситуация. Сейчас особенно, потому что стильно, модно, молодежно. Постепенно приложение обрастает логикой, микросервисов становится больше и больше. И одного Docker, и даже Docker Compose уже становится мало. Ну и плюс ко всему, наверное, еще хочется какую-то отказоустойчивость, чтобы это на нескольких серверах работало. Возможно, какой-то Service Discovery и прочее.


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



Марсель Ибраев, СТО Слёрм


Docker Compose не позволяет нам ничего делать? Ведь это тоже средство управления несколькими контейнерами.


Docker Compose, как минимум, не позволяет запускать проект на нескольких серверах. То есть это все равно все-таки история про одну ноду. Поэтому, да.


ОК. Что придумано? Что есть сейчас, чтобы это все делать?


Сразу нужно сказать, что инфраструктурный стандарт это всё-таки Kubernetes. Штука, которая в свое время была произведена в Google. И Google по зову сердца, по доброте своей решил поделиться с миром.


Есть ещё ряд решений, например, Docker Swarm, Mesos, OpenShift и другие, но всё-таки наиболее популярен и пользуется спросом Kubernetes. Компании, которые задумываются о том, что им нужен оркестратор, в первую очередь смотрят на Kubernetes.


Где обычно применяются оркестраторы, в частности Kubernetes?


Да, это очень важный вопрос. Дело в том, что Kubernetes все проблемы не решает. Компания работает, работает, у них всё плохо (как правило, плохо с процессами) они такие: Блин, Kubernetes классная штука. Давайте её себе поставим у нас всё сразу станет хорошо! Но становится сильно хуже.


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


Если вы небольшая компания или если у вас монолит, и вы такие: Сейчас мы его в куб засунем и все станет хорошо! Нет, не станет.


Kubernetes работает только с контейнерами Docker и с их оркестрацией?


Kubernetes работает с контейнерами, но не только с Docker. У Kubernetes есть такая штука, которая называется Container Runtime Interface. В принципе, все системы контейнеризации, которые сейчас есть и которые поддерживают Container Runtime Interface, могут работать с Kubernetes. Например, rkt.


Сейчас возникло движение энтузиастов, которые выкорчевывают Docker из Kubernetes и используют что-то другое. Потому что Docker тоже не без проблем. Главная проблема Docker это его демон, который имеет свойство зависать, особенно при большой нагрузке. Но зачем демон, если у нас уже есть Kubernetes, есть достаточно зрелая инфраструктура и нам надо просто какое-то место для запуска контейнеров.


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


А что может быть использовано вместо Docker более оптимальным путем?


Оптимально пока сложно сказать, потому что у конкурентов Docker есть свои минусы. К примеру, containerd не имеет нормального средства управления им. К слову, с версии 1.11, кажется, под капотом Docker containerd и работает. По сути, сейчас Docker выполняет роль обёртки над containerd, а там containerd, а внутри ещё runC, если уж совсем углубляться.


Кто-то говорит про Podman: делайте просто алиас Podman Docker, и можно сразу работать. Но тоже есть свои нюансы, поэтому мы в том числе пока работаем с Docker.


Расскажи подробнее, как вообще Kubernetes работает? Что у него происходит под капотом? Для начала уточним, Kubernetes это сервис или это какое-то ПО, которое можно ставить на сервера, или это и то и то?


Ну это ПО, да. И при этом ПО, которое сейчас очень активно развивается и предоставляется облачными провайдерами как сервис. При этом ничто не мешает его поставить на железные серверы.


Всегда нужно держать в голове, что Kubernetes это в первую очередь оркестратор контейнеров. Когда вы это понимаете, то вы понимаете, для чего он нужен.


Kubernetes состоит из нескольких компонентов, которые выполняют каждый свою роль (подробно о них ещё поговорим). Из этого вытекает две особенности:


  1. Компоненты друг друга не аффектят. Если один упал, то все остальные могут продолжить работать.
  2. Эти компоненты работают по pull-модели. Нет никакого центрального компонента, командира, который всем раздаёт команды, а как сдох, так все не знают что делать. Каждый компонент выполняет свою часть работы: сделал, завершил. Если он умер, ну значит этот кусочек не выполнится.

Теперь по поводу самих компонентов. Основной компонент это API-сервер. Это просто апишка, REST API (разработчики понимают, о чём речь): управление с помощью http-запросов, версионирование кстати тоже. Очень важно, что там есть версия API, при обновлении мы можем на эти версии API завязываться и за счёт этого обновляться менее болезненно. Есть API, с которым работают все: и клиент (мы, как оператор кластера), и компоненты остальные в том числе.


API-сервер работает с хранилищем, которое представляет из себя просто etcd. Etcd это key-value хранилище, то есть ключ-значение. Вот и API-сервер это единственный компонент, который с этим хранилищем взаимодействует.


Это какая-то разработка команды kubernetes?


Нет, это отдельная штука, очень древняя.


А почему её у Redis нет, например?


У Redis есть проблемы с многопоточностью, есть проблемы с кластеризацией (хотя они ее постепенно решают). А etcd штука древняя, все детские болезни там уже вылечены, и она достаточно такая отказоустойчивая.


Кстати, это хороший показатель, что если разработчики Kubernetes уже начиная с первых версий используют etcd, то, наверное, у себя его тоже можно использовать как key-value в кластер-режиме.


API-сервер единственный, кто с etcd работает, он записывает, считывает информацию. А в etcd у нас хранится всё. Там наши настройки кластера, манифесты всё, что мы делаем.


Мы как клиент хотим что-то создать, запустить приложение, и мы эту информацию передаем в API-сервер. Мы непосредственно это не делаем, конечно, там есть такая утилита, которая называется kubectl. С её помощью мы управляем всем кластером, делаем все операции, в том числе и запускаем приложения. Передаем yaml-манифест, где у нас в декларативном формате описано, как должно выглядеть приложение в кластере. Вот мы это передаем. Оно сохраняется в etcd и следующие компоненты постоянно смотрят в API-сервер.


Если немного углубиться, там есть подписка на событие и они по сути watch'ат. То есть никакого DDoS'а самого себя там нет. Следующий компонент, который берёт эту историю в работу это kube-controller-manager. По сути, мозг кластера Kubernetes. В него вшиты множество контроллеров: node-controller, endpoint-controller. Практически у всех абстракций, которые есть в Kubernetes, есть контроллер, и он вшит в этот бинарь. Эти контроллеры занимаются просто контролем вот этой абстракции: смотрят, есть ли новые, нужно ли что-то удалить и так далее.


Давай на примере. Если продолжать говорить о приложении, то контроллер, который отвечает за какое-то конкретное приложение, точнее за его манифест, за его абстракцию он видит, что мы что-то хотим создать, запустить. И он выполняет соответствующую работу, а именно дописывает манифест в etcd, обновляет информацию. Тут, конечно, без некоторого углубления нормально не объяснишь. Но есть такая абстракция, которая называется ReplicaSet. Она позволяет запускать приложение в нескольких инстансах. Через нее мы можем увеличивать, уменьшать количество реплик. И все здорово.


Это балансировка нагрузки?


Это просто контроль за количеством инстансов одного и того же приложения.


А зачем?


Чтобы иметь возможность в случае чего скейлить свое приложение или скейлить обратно. То есть хотим в три инстанса реплики просто пишем три у нас три инстанса.


Ну это очень похоже на балансировку нагрузок.


Балансировкой уже занимается другая абстракция, которая уже трафик распределяет на вот эти три инстанса.


То есть они в принципе могут в паре работать?


Да. Они в паре и работают.


ReplicaSet не только создаёт реплики, она ещё и следит, чтобы их действительно было три. Причем не больше, не меньше.


Инстансы, которые запускает ReplicaSet, называются подами. В подах и работает наше приложение (про поды мы ещё поговорим).


И вот как раз, когда мы создаем, например, ReplicaSet, у нас есть такой ReplicaSet controller в этом контроллер-менеджере, который описание подов для ReplicaSet генерирует, и туда же, грубо говоря, в etcd через API-сервер скидывает.


Потом подключается следующий компонент. После того, как мы поняли, какое приложение нам нужно из скольких инстансов запускать, оно вот в etcd хранится этот манифест. Далее у нас идет такой компонент, который называется scheduler. Его роль достаточно проста. Он решает, на каких серверах это приложение надо запускать. Но делает это не просто так, у него есть свои алгоритмы принятия решения.


Ну в частности, он смотрит на ресурсы, то есть сколько ресурсов на ноде, если мы для этого приложения запрашиваем 1 ГБ ОЗУ, а на ноде только 512 свободны, он туда не отправляет.


Под приложением ты понимаешь Docker-контейнер с приложением?


Да, контейнер.


Контейнер с приложением каким-то.


Да.


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


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


На стабильность он не смотрит. Он смотрит в первую очередь на ресурсы. Какой смысл отправлять приложение на запуск туда, где их недостаточно. Смотрит ещё на priority class это такая штука, с помощью которой мы можем задать приоритет.


Например, в кластере два окружения: продакшн и стейджинг. Конечно, продакшн более важен. И для них мы priority class выставляем высокий. Для стейджинга мы можем поставить поменьше. Когда происходит авария, Kubernetes понимает, что часть серверов отвалилась (за это будет отвечать Node Controller, который контролирует жизнь нод), он принимает решение, что надо те поды, которые там были, запустить в живых серверах. Scheduler будет запускать в первую очередь поды продакшена.


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


А если не хватит под продакшн места?


Если не хватит, ну сорян. Поды будут висеть в pending, то есть ждать, когда появятся ресурсы. И scheduler назначает Если на такой низкий уровень опуститься, то в манифесте пода есть специальное поле, которое называется nodeName имя ноды. И вот пока scheduler не принял решение оно пустое. Scheduler говорит, что вот этот под, вот это приложение нужно запускать там на Node 2, и он эту информацию передает, API-сервер это записывает в etcd и в это поле вносит это имя. А далее в работу вступает последний компонент всей этой схемы, который называется kubelet.


Kubelet это компонент своего рода "node agent", то есть агент, который запущен на всех серверах кластера. Он тоже постоянно в API-сервер смотрит. И он видит, что появился под, то есть приложение, у которого в поле имя сервера написано его имя, там, где он работает. Он такой: Ага! Значит его нужно у себя запустить! Он видит, что у него запущено, и что от него хотят. Он передает Docker API, из манифеста считывает, что там конкретно нужно запустить, и говорит Docker, какой контейнер нужно запустить.


Kubelet, получается, замена Docker демона?


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


Хелс-чек такой?


В том числе хелс-чек тоже делает Kubelet. И мы постоянно видим, какой статус у наших приложений в реальном времени. Вот то, что там сейчас пулинг образа идёт, что там сейчас она запускается, вот он на раннинг все хорошо, все запустилось. И вот только на этом этапе у нас физический запуск произошёл. То есть всё вот это это подготовка.


Ноды это всегда сервер или это может быть кластер серверов?


Ноды это место, где запущен Kubelet.


Это может быть виртуалка, как я понимаю?


Да, может быть виртуалка.


Ты сказал, что в результате этих действий мы получаем физически развёрнутое приложение. Kubelet посылает какие-то свои статусы, либо он просто stdout контейнера фигачит? К чему этот вопрос. Потому что, если у тебя приложение в stdout выдает логи, какой-то дебаг kubernetes как-то умеет это в одно место собирать и предоставлять в удобочитаемом виде, или это не его обязанность вообще?


В твоем вопросе, два вопроса скрыты. Статус самого контейнера (жив или не жив) берёт из Docker. Функционал приложения (работает ли оно) вот эти дебаг, логи, какие-то хелс-чеки это все тоже делает Kubelet, но для этого надо несколько строчек в манифест добавить и сказать, как именно проверять.


На данный момент поддерживается три возможности проверять приложение:


  1. http-get это http-запрос в контейнер на инпоинт, и мы видим, работает оно, не работает, отвечает, не отвечает. С 200 по 399 код это ок, если 301 даже редирект это ок. Если 404 это не ок. 500 тем более.
  2. exec мы внутрь контейнера делаем какой-то запрос, какую-то команду, проваливаемся. Например, select 1, проверяем, всё ли нормально с базой.
  3. tcp socket Kubelet просто проверяет доступные сокеты. Если все хорошо, то все хорошо.

Есть три типа проверки контейнеров: это liveness, readiness и startup пробы.


Liveness проба это контроль за жизнью приложения. Постоянно Kubelet смотрит, ходит и смотрит. Там гибкие настройки, можно написать, как часто ходить, как проверять и так далее.


Readiness проба проверяет, а готово ли приложение принимать трафик. Потому что разные истории могут быть, это могут быть разные инпоинты у приложения. Мы проверяем, работает ли приложение, готово ли оно принимать трафик.


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


На каком размере архитектуры может работать Kubernetes? Может ли он контролировать сразу миллион инстансов?


Насколько я помню из документации, это 5000+, что ли, нод. На одной ноде по умолчанию можно запустить 110 подов, 110 экземпляров приложения.


Под это экземпляр одного и того же приложения? Или могут быть два разных контейнера на одном серваке и это будет два разных пода?


Под это абстракция, в которой запускается приложение. Тут важно понять, что это не какая-то физическая оболочка, не какой-то процесс ещё один, это скорее именно абстракция, с которой работает Kubernetes.


Kubernetes не умеет работать с контейнерами. Мы не можем сказать: заскейль нам вот это приложение в трёх контейнерах. Мы можем только сказать: заскейль нам в трех подах. В поде может быть как один контейнер, так и несколько. То есть мы можем туда запихнуть, например, nginx и php-fpm в связке, и они будут скейлится по два контейнера в связке.


Но тут надо понимать, что хорошая практика засовывать в контейнер неделимые части приложения. Всё-таки, если 2-3 контейнера надо засовывать, то может, стоит ещё поиграться с логикой приложения. Обычно один контейнер засовывают и там есть еще второй, который сам запускается, это pause контейнер, который держит сетевой namespace, чтобы все контейнеры в поде были в одном namespace, и чтобы всё хорошо работало.


Это первая часть беседы. Во второй будет про хранение данных в Kubernetes и про Ansible. Не пропустите!


Вопросы задавал Лекс АйТиБорода iamitbeard

Подробнее..

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

07.12.2020 16:06:42 | Автор: admin


В чём проблема с базами данных и как позаботиться о безопасности в Kubernetes? Как врубиться в Ansible? Ответы на эти и другие вопросы читайте в продолжении интервью Лекса АйТиБороды со старшим инженером Southbridge Николаем Месропяном и СТО Слёрма Марселем Ибраевым.


Прочесть первую часть
Посмотреть интервью целиком


На вопросы отвечает Марсель Ибраев


В чём проблема с базами данных в Kubernetes? Для меня как для разработчика проблем нет: зафигачил базу в Docker-контейнер и работаешь.


Проблемы нет для половины, наверное, комьюнити. Это вообще холиварный вопрос. В чатике kubernetes_ru (официальном чате русскоязычного сообщества Kubernetes) постоянно эти вопросы поднимаются.


А может, Docker не нужен? Зачем мне Docker для базы?


И это тоже. Были даже такие разговоры, что если база данных удовлетворяет 12 факторам, то это обычное приложение, запускайте и всё, 2020 год на дворе. Зачем вы её выносите отдельно, туда-сюда.


Но 12 факторов это не совсем про базу данных, там больше про stateless говорится. У нас все-таки stateful приложение, то есть приложение, которое должно сохранять статус и результат работы. После перезапуска оно этот результат терять не должно, в отличие от stateless.


И вот именно здесь краеугольный камень. Чтобы научить Kubernetes сохранять статус приложения (базы данных, таблички вот это всё), нужно присыпать ещё несколько абстракций сверху: PersistentVolume, PersistentVolumeClaim, StorageClass не буду сильно углубляться. То есть просто нужно иметь в виду, что повышается планка работы с Kubernetes.
Нужен человек, который умеет настраивать кластер этой базы данных. Потому что Kubernetes не важно, что запускается в поде (Pod): stateless приложение или stateful приложение он с ними работает одинаково. Он не соберёт магическим образом кластер баз данных и не будет за ним следить. Вы напишете манифест с тремя инстансами базы данных, Kubernetes его запустит, и это будет три отдельных инстанса базы данных, которые будут не в кластере.


Но с приложениями он умеет это делать?


Тоже нет. По факту это как бы отдельные инстансы, отдельные реплики приложения. Они по идее самостоятельные.


То есть на уровне записи и чтения данных всё равно нужно будет абстракцию какую-то свою клепать, Kubernetes ни при чём?


Да, и поэтому здесь нужны компетенции какого-то DBA, который на монолитах это уже 1000 раз настраивал, и скажет: Да, без проблем! Настроим!.


Следующий вопрос возникает с запуском базы данных, когда нам нужно выставить request/limit. Request/limit это инструмент, который позволяет работать с ресурсами.


Request это запрос на ресурсы. Например, мы знаем, что этому приложению минимально нужно 200 МБ ОЗУ. Если будет меньше, оно просто крашнется и не запустится. Мы выставляем request 200.


limit максимальная планка. Мы говорим, что 512 максимум, а если выше, то оно явно потекло и надо с ним что-то делать.


А вот как это сделать для базы данных хрен его поймёшь! В понедельник мы работаем на типовой нагрузке, в пятницу пришла баба Люда из бухгалтерии и запустила выгрузку из 1С миллиона строк, и нагрузка в потолок.


На моей истории было дважды, когда приложение (не база данных, для базы данных это будет суперкритично) начало утилизировать очень много ресурсов, и на сервере ресурсов не хватило всё померло. Kubernetes умный, он всё понял: взял и перетащил это приложение на другой сервер, но потреблять много ресурсов оно не перестало. Новый сервер упал, и как домино всё сложилось. Может повезти, что это всё в цикл замкнётся и будет переезжать, но ничего хорошего всё равно не будет. А с базой данных это критично, потому что вам нужно сохранять свои данные.


Есть 4 способа, как это сделать:


  1. очень стабильно, но дорого,
  2. не очень производительно, но дёшево,
  3. более или менее по производительности, но без гарантии,
  4. и дёшево, и круто.

Очень стабильно, но дорого это когда мы покупаем энтерпрайз, какую-нибудь железку с поддержкой. Там стоит такая бандура, SaaS всякие, и все работает. Здорово, но не у всех есть такие возможности.


Не очень производительно, но дёшево это использование облачных сервисов, которые предоставляют диски. Но здесь нужно понимать, что у многих есть в инструкции (в том числе и в документации баз данных) такая сноска: Ребята, если вы будете использовать какие-нибудь диски Google или Digital Ocean, например, то поставьте галочку using local SSD, чтобы использовать локальные диски, а не какие-то там через сеть, и вот тогда всё с базой данных будет OK.


Более-менее по производительности, но без гарантии (этот вариант нативно поддерживает Kubernetes) это когда мы просто берём, грубо говоря, прокидываем диск сервера в контейнер. Мапим папочку прям. У нас на сервере могут быть SSD, и мы SSD внутрь контейнера и получаем. Минус в том, что мы тем самым прибиваем базу данных к этому серверу. Спрашивается, зачем мы запускаем её в Kubernetes, если что случись, она там и сдохнет. Ну и лежала бы на своём монолитовском сервере.


И дёшево, и круто это собственная СХД, которую построил и поддерживает какой-то бородатый админ. При этом он ещё должен вывести уровень производительности этой СХД на такой уровень, чтобы она сравнялась как-то с SSD. Первое, что приходит в голову, это Ceph. Но пока мы делали курс по Ceph, пообщались с теми, кто много с этой технологией работает, они сказали: Не, чувак, там столько костылей, что это всё равно придётся закрывать мощностями оборудования. То есть такого нет.


Вот такие варианты: даже не 2 стула, а 4 выбирай, куда садиться. Если у вас возникает вопрос: Могу ли я запустить базу данных в Kubernetes?, значит, конкретно вы не можете пока.


В Kubernetes есть чарты (charts) и хельм (Helm), что это такое?


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


Приложение может состоять из множества микросервисов, которые надо как-то деплоить в кластер. Конечно, мы можем каждый раз править манифесты руками. Но одно приложение может содержать от пяти и до бесконечности манифестов.


Манифесты это yaml-файлики, которые отвечают за самые разные абстракции. В том числе сам под (Pod), хотя это будет не под, а абстракция, которая называется либо Deployment (именно в ней запускают приложение), либо StatefulSet (если это stateful-приложение), либо DaemonSet, ну неважно. К нему добавляется абстракция типа Service внутренний кубернетский балансировщик. К нему же добавится абстракция Ingress внешний балансировщик. Там же ещё могут быть PV/PVC короче, много всего.


Когда мы приложение разрабатываем, нам надо сделать какой-то релиз. Что-то поменялось в приложении, что-то поменяться могло в манифестах. Например, версия image была 2, стала 3. Нам придётся каждый раз ходить в репы и менять руками 2 на 3. Таких реп может быть сотни, и осталось только не забыть везде нужное поменять. В этот момент типичный админ задумывается об инструментах, которые позволяют менять все файлы внутри папки с одного паттерна на другой. И в принципе, это будет работать.


Только проблема в том, что это решение kubectl based, то есть всё равно весь вот этот вывод, который мы в папке перелопатили, мы через пайп отправляем в kubectl. Говорим: kubectl, вот теперь всё, что у нас есть новое в папке, отправляй в кластер. И это тоже будет работать. Но если что-то пойдёт не так (циферку не ту поставили или приложение просто не поднялось), нативного способа откатиться через kubectl нет. Там есть команда kubectl rollout undo, но она работает только с deployment, а у нас могут быть и другие абстракции, с которыми она не работает. И тут возникает вопрос, а как откатываться? Потому что шаблонизация вроде более-менее понятна, да и шаблонизаторов много всяких (и Kustomize, и json.net), но как откатываться большой вопрос.


Явный ответ на это дает Helm, потому что у него из коробки есть возможность cделать rollout, то есть откат, в случае, если какие-то есть проблемы. Работает он достаточно просто. Он все манифесты из папки, что мы нагенирировали, сохраняет в артефакт. Например, в config какой-то. Прям вот как оно есть, так и сохраняет подряд. Назвал его артефакт версия 1, сохранил в кластер. Потом мы выпустили новый релиз. Он ага, артефакт версия 2. И вот так они копятся. Потом мы выкатили новый релиз, какой-нибудь версии 9, и поняли, что это не то. Он говорит: Ок, у меня есть версия 8, достает все манифесты оттуда и фигачит в кластер.


Возвращаясь к вопросу, как вменять всё, когда этих манифестов становится за сотню и просто невозможно в голове удержать, что где поменять. Тогда на помощь приходят шаблонизаторы, темплейтирование и Helm это тоже умеет. Набор темплейтированных манифестов называется Chart.


Ты уже не раз говорил про stateful и stateless приложения. Разработчикам эти термины знакомы. И понятно, что со stateless просто: развернул и не паришься. Есть какие-то особенности разворачивания stateful приложений? И что для этого используется внутри Kubernetes? Какие-то может быть особенные штуки для хранения состояния приложения? Как оно хранится? Где оно хранится?


Сам по себе Kubernetes никакого решения не предоставляет. Нет внутренней системы хранения данных. В любом случае нужно использовать стороннее решение. Сейчас появился CSI Driver, и стало проще.


CSI Driver это ещё один интерфейс, который позволяет унифицировать подключение внешних инструментов. Как есть Container Runtime Interface, который не только Docker может подключить, но всё, что CRI поддерживает, так и CSI может подключить всё, что его поддерживает. Технология не так давно вышла в прод, развивается и допиливается, но в целом это хороший шаг. Остаётся подключить, например, Ceph как систему хранения данных и через абстракции Kubernetes запрашивать диски или файловые системы в этой СХД и подключать себе. То есть по сути приложение будет работать с этой СХД, а Kubernetes будет в роли посредника.


А до появления этого механизма в Kubernetes нельзя было разворачивать stateful приложения?


Можно было. Есть такая штука, как storage class. Это ещё одна абстракция, и там это всё настраивается. Скажем так, объём тех решений, которые можно применять в Kubernetes, будет расти. Уже есть всякие облачные хранилища, которые можно подключать, и множество всего другого.


Как построен механизм безопасности всего процесса поднятия, слежения за твоими приложениями? Общение наверняка ведётся по https, но есть ли ещё что-нибудь? Что, например, будет, если API Kubernetes завладеет злоумышленник? Как этого избежать?


Не открывать API наружу! Достаточно просто.


А как не открывать? Выстраивать приватную сеть?


Ну да, VPN, двухфакторная авторизация что-нибудь такое. API нужно закрывать очень хорошо, потому что каждый год находят уязвимости. В 2018 году, например, обнаружили критическую уязвимость с индексом 9,8, если я не путаю. Она позволяла поднять привилегии в API, и всё делай, что хочешь. Были уязвимости в 2019 году. Мне запомнилась возможность подменять файлы на хосте через контейнер.


И всё это можно сделать, если авторизоваться в API. Естественно, API не дураки писали, и там есть аутентификация, тот же самый TLS, просто так ничего не сделаешь. Но уязвимости бывают, поэтому API надо всеми возможными способами закрывать.


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


Например?


Глобально можно посмотреть в первую очередь как раз с точки зрения уязвимостей, получения прав и так далее. Решение закрытие API. Если Kubernetes развёрнут не в облаках, а на своих виртуалках, то надо регулярно обновлять свои сервера. Про iptables и SSH наружу, я думаю, говорить не надо. Это общие и достаточно банальные требования, но их надо выполнять. Kubernetes не панацея, это дополнительный слой абстракции. Сверху намазали, и за этим теперь тоже надо как-то следить.


Второй аспект безопасности это уровень привилегий пользователей. В Kubernetes это хорошо сделано. Есть так называемый Role Based Access Control или RBAC сокращенно. Мы можем разграничить пользователей по тому, какие действия им доступны: ты можешь работать только здесь, а ты только здесь. Но там есть достаточно простые хаки, которые позволяют это всё очень легко обойти.


Например, в Kubernetes есть namespace пространство имен. Условно, у нас есть Kubernetes, есть два namespace: стейджинг и продакшн. В стейджинг мы пускаем разработчиков, они там работают, но мы им не даем что-то править на проде. В прод ходит только CI/CD. Но просто имея возможность запускать свои приложения хоть где и ничего больше, я могу за 2 минуты стать админом кластера. Я запущу под (Pod), которого отправлю на запуск на master, где у нас находятся все доступы и креды, и подмонтирую их себе в контейнер. Потом я посмотрю токены, скопирую себе на компьютер и стану админом.


Это такая штука, о которой не все задумываются. Но эту штуку тоже уже прикрыли. Есть инструмент Pod security policy, который позволяет чётко сказать, какие поды и где и как могут запускаться, какие порты могут использовать.


Ещё один аспект безопасности касается работы нескольких команд в Kubernetes. Особенно, если есть на аутсорсе команды, которые пишут свою часть приложения. По умолчанию, любой человек с доступом в namespace может постучаться во все эндпоинты (endpoints), которые есть. Прикрыть это позволяет Network policies. Это своего рода firewall внутри кластера. Когда мы внешнюю команду к себе зовём, мы спрашиваем, к каким эндпоинтам она должна иметь доступ, чтобы взаимодействовать с приложением, и только эту часть открываем. В основной кластер они зайти не могут.


Если говорить про безопасность, то в Kubernetes есть ещё одна интересная штука защита от человеческого фактора. Известно много громких факапов, произошедших по вине человека (история с GitLab, когда они положили пол своего продакшена, с Yandex.Cloud и другими компаниями). Прод базу данных снёс и оказалось, что в шести местах бэкапов нет или они неконсистентные, и случайным образом где-то там дампик лежал несколько часовой давности и типа: Ух! (перекрестился) Слава Богу! Восстановили!


Чтобы этого избежать, в Kubernetes тоже есть инструменты. Например, limit ranges, resource quotas. Они позволяют держать в узде тех, кто работает с Kubernetes. Например, не дают заскейлить свое приложение в какое-то очень большое количество. А такое один раз было. Коллега рассказывал: админ хотел поставить единицу. Клавиша залипла, интернет лагнул, вместо одного раза он нажал несколько и всё. Правда, это было не на Kubernetes, а на Nomad. Но факт такой, что он: Вам надо 111 млн приложений? Хорошо. Сейчас будет! И всё начало лагать, естественно. Чтобы этого избежать, мы можем сказать: Здесь максимум 10 реплик, и всё.


Ты упомянул Nomad. Какие ещё есть аналоги Kubernetes?


Самый яркий аналог, про который все говорят, это Docker Swarm. Docker Swarm это разработка от Docker, их нативное решение. Не требует никакой дополнительной установки: переходим в swarm-режим, и у нас появляется возможность Docker Swarm.


Docker Swarm это тоже оркестратор, поэтому многие сравнивают его с Kubernetes. Но если говорить глобально, то Kubernetes это более мощный комбайн, в нём больше возможностей. В Docker Swarm их чуть поменьше, но я бы сказал так: если у вас супертребований нет, то можно пользоваться и Docker Swarm. Но в отличие от него, Kubernetes, например, хорошо умеет автоскейлить. Причем, как ноды автоскейлить, заказывать, так и поды, то есть реплики приложений. В облаках это работает вообще шикарно. Пришла нагрузка с рекламной кампании мы не сидим, не заказываем руками, а он сам сходил и заказал. Когда нагрузка ушла, он обратно их схлопнул до нужного количества.


На каких операционных системах обычно запускается Kubernetes?


Ну обычно на Linux: CentOS, Debian.


А на винде умеет?


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


Какими навыками нужно обладать, чтобы успешно работать с Kubernetes? Программирование, может, какое-то нужно? YAML, понятно.


Знание YAML, некоторое понимание, как работает контейнеризация Я не знаком лично, но слышал о таких людях, которые не имеют базового бэкграунда, но с Kubernetes работать умеют. Они могут налажать с сетью, например, но при этом с Kubernetes работают.


Я бы добавил, что нужны базовые знания по сетям, по Linux (если Kubernetes будет запускаться на Linux-машинах, их надо будет тюнить) и умение читать документацию.


В каких случаях Kubernetes вообще не стоит использовать?


Не стоит использовать, если рассчитываете, что поставите его, и все вопросы решатся. К внедрению надо подходить системно, и ставить Kubernetes только тогда, когда есть реальная необходимость.


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


С технической точки зрения Kubernetes не нужно применять для приложений, которые не удовлетворяют 12 факторам. Для приложений, которые неправильно работают на уровне микросервисов, которые не допилены до конца. В одном из проектов мы пилили и переносили, пилили и переносили, и это было сложно, потому что часть проекта работала в Kubernetes, а часть продолжала работать на монолите и надо было выстраивать взаимосвязь.


В идеальном мире, конечно, Kubernetes ставится сразу же Сначала приложение готовится, потом уже туда. Но чаще всего нет.


Переходя к следующему блоку, спрошу: в чём различие Kubernetes и Ansible?


Ansible это, в первую очередь, система управления конфигурацией. Kubernetes это оркестратор контейнеров.
Знание Ansible не будет лишним, когда вы начнёте работать с Kubernetes, и наоборот. Потому что Kubernetes нужно как-то управлять, и Ansible для этого хорошо подходит.
Если мы говорим, что Kubernetes это Infrastructure as code (IaC), то вот этим IaC нужно как-то управлять. Руками туда лезть? В идеальном мире не лезут, всё прогоняют через пайплайны: надо что-то сделать в Kubernetes запускаешь плейбук он пошёл сам сделал. Поэтому знание Ansible и Kubernetes, наверное, важны в равной степени.


Далее отвечает Николай Месропян


Что такое Ansible и для чего он нужен?


Это средство управления конфигурациями. Для чего нужно? Когда у вас один сервер, им можно управлять руками: просто ходить на него, набирать команды или тыкать мышкой, что-то руками править, настраивать. Если их 2, 10 это ещё реально. Но в конце концов серверов может стать и 20, и 100, и 1000. Вот тогда возникает необходимость управлять ими более автоматизированно и централизованно.



Мы говорим про управление конфигурациями операционной системы или вообще любыми? Я задеплоил 25 приложений разных на хост, и я хочу


Обычно я начинаю рассказ про Ansible и свой курс с концепции, которая называются питомцы и стадо Pets vs Cattle. Pets это питомцы. Ты за ними ухаживаешь, лечишь, даёшь им уникальные имена. Cattle это стадо. Если кто-то заболевает, ты его забиваешь и создаешь вместо него новый сервер. У тебя их очень много, и ты их в лицо не знаешь, а знаешь только по номерам. Питомцы превращаются в стадо, когда их становится много.


Но когда они заболевать начинают!


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


Jenkins вроде как тоже кроме того, что поддерживает пайплайны, умеет конфигурировать приложения. Это в чём-то похоже на Ansible?


Нет, это совершенно разные вещи. Ansible похож на Chef, Puppet, SaltStack. Jenkins больше похож на GitLab CI и TeamCity. Это совершенно разные классы программного обеспечения для разных целей, но они могут работать вместе.


Я встречался с решениями, когда через пайплайн на GitLab CI производился деплой приложений посредством приложения, использующего внутри себя Chef.
Сам я писал пайплайны для GitLab CI на Ansible. То есть они вместе работать могут. Иногда и просто должны.


Из чего состоит Ansible, и как он вообще работает? Это какое-то приложение над приложениями или отдельный сервер, что это вообще?


Cначала расскажу про ПО для управления конфигурациями. Расскажу, чем они друг от друга отличаются, и потом перейдём конкретно к Ansible.


ПО для управления конфигурациями можно разделить на два основных класса: pull и push.
Когда работает система принципа pull, на клиентских хостах, то есть на управляемых хостах, выполняется некий daemon (сервис), который подтягивает конфигурацию с центрального репозитория и на месте уже её раскатывает.


push это когда управляющий процесс выполняется на одном центральном сервере, а на конечные ноды доезжают только конечные изменения (по сети). То есть конфигурирование производится не на месте, а по сети.


По сети SSH, FTP?


Как правило, SSH. Вот Ansible относится именно к push. На центральном сервере выполняется процесс, который использует конфигурацию, чаще всего хранящуюся в репозитории.


Зачем вообще нужен pull, если push выглядит достаточно красиво и ненапряжно?


Push медленный.


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


При модели push центральный процесс каждый раз идёт к ноде по SSH, что-то там делает, возвращает результат, здесь он оценивается, и всё это повторяется.


Тогда чем push лучше pull?


На конечных нодах не хранится никакой конфигурации, что называется нулевой footprint (след).


Бывает важно, чтобы на клиентских машинах никто не мог понять, как и что конфигурируется. Расскажу про нашу компанию, Southbridge. У нас аутсорсинг, множество клиентов, и информация под клиентов хранится в репозитории. Потому что мы не хотим, чтобы клиент мог своими руками что-то подкрутить и сломать. Он может сделать это без злого умысла, просто: Программист сказал, что можно сделать так, давай-ка сделаем! А потом раз продакшн снесся, и он приходит к нам: У вас все плохо работает!


В действительности таких случаев у нас не было, когда мы использовали pull-системы. Но тем не менее. К тому же лучше подстраховаться, чтобы конфигурация одних клиентов не попала каким-то другим. Именно поэтому мы используем push.


Плюс нам понравилась сама парадигма, что всё лежит в одном месте. Сами клиентские процессы иногда написаны на тяжёлых языках (Ruby, например) и тоже подъедают системные ресурсы: и память, и процессор. Поэтому это в каких-то случаях может быть нежелательным.


А клиентские ПО, клиентские ресурсы, которые в push работают, они пишутся руками или есть готовые какие-то решения?


Вот теперь я могу рассказать, из чего Ansible состоит (без слайдов и доски сложно, но попробую объяснить на пальцах).


Для Ansible мы пишем конфигурацию, состоящую из сущности под названием плейбуки (playbook), ролей и инвентаря. Плейбуки и роли это определение конфигурации. Инвентарь это параметризация.


Плейбук это файл, состоящий из набора так называемых плеев (play).
Сразу скажу, что вся конфигурация пишется на YAML. Ansible написан на Python, поэтому для него родной язык разметки YAML.


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


А если это виртуалка?


Всё равно. Всё-таки используются системные вызовы, и чтобы не адаптироваться, в качестве управляющего хоста поддерживается только Linux. Управлять можем Linux-системами, Windows-системами и Mac OS. Но для Mac, насколько я знаю, оно пока в зачаточном состоянии.


Android, мобильные платформы?


Наверняка что-то есть, но пока оно большого распространения не получило.


У нас выполняется центральный процесс, который этот плейбук читает.


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


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


А команды это просто команды консоли или?


Да, есть такой модуль command, который просто выполняет консольную команду.


Он на клиенте?


На клиенте, да.


А почему нельзя просто через SSH напрямую в терминал фигачить?


Модуль command это последний шанс, если у тебя нет специализированного модуля для выполнения определённой задачи.


Чтобы запустить сервис, мы не делаем command systemctl start engine, мы используем модуль service или модуль systemd, который приводит систему, в данном случае сервис, в заданное состояние. То есть он запущен и включён либо остановлен и выключен.


Вообще, управление конфигурациями задумывалось как декларативное. То есть мы описываем желаемое состояние системы, потом эта самая управлялка приводит систему в заданное состояние. Если система уже находится в нужном состоянии, то не производится никаких действий. Это свойство называется идемпотентность.


Понятно, в не зависимости от того, сколько раз ты запускаешь


Результат всегда один и тот же.


Это прям как в REST API, разработчикам должно быть понятно.


Да!


Поэтому мы описываем в задачах желаемую конфигурацию и Ansible приводит (если это грамотно написано) систему в заданное состояние. Если система уже в заданном состоянии, то действие не производится. Потом мы в выводе команды всё это видим.


Что такое роли? Роли можно уподобить подключаемым библиотекам. То есть мы пишем роль, которая, допустим, настраивает веб-сервер nginx, и потом из разных плейбуков для разных хостов с разными параметрами мы эту роль подключаем.


Роли могут храниться как на диске вместе с плейбуками конфигурации, так и во внешних репозиториях. Есть такая штука, называется Ansible Galaxy это что-то вроде центрального хранилища ролей. Туда можно свою роль подать на рассмотрение. Это не то чтобы пакетный менеджер, а как GitHub, допустим. Только специализированный, исключительно для Ansible.


То есть роли это готовые куски конфигураций, которые ты можешь у себя встраивать и переиспользовать?


Это описание конфигурации, которому ты можешь сам задавать разные параметры из инвентаря.


Отлично. И тут включается механизм параметризации?


Да. При запуске команды ansible-playbook, которая и делает нужные изменения на хостах, мы задаём ей параметром так называемый инвентарь откуда брать параметры. В инвентаре у нас чаще всего список хостов, объединенных в группы (чтобы Ansible знал, на каких хостах выполнять те или иные задачи), и набор переменных для них, которые подставляются в роль и меняют поведение в нужную сторону.


Jinja2 это из этой темы?


Jinja2 это весьма мощный язык шаблонизации. Jinja2 используется в ролях.


Это параметризация, как я понимаю, нет?


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


Как гарантировать, что твой конфиг работоспособный, что он не устарел, не сломался вдруг на сервере что-то поменялось, и у тебя команда ansible-play не сработала. Это как-то тестируется?


Да, разумеется, это тестируется.


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


Это подход инфраструктура как код (Infrastructure-as-Code, IaC). Код в каком смысле? Код должен проходить ревью, то есть твои коллеги должны видеть, что ты написал и подтвердить, что это вменяемо.


Речь про конфигурации?


Да. То есть точно также весь инфраструктурный код хранится в репозитории GitLab, GitHub чем вы там пользуетесь.


Рядом с продуктовым кодом? Или это отдельная репа?


Разумеется, отдельная. Это много реп чаще всего. И потом перед тем как что-то изменить, ты делаешь merge request с изменениями (в зависимости от того, какая схема у вас в компании принята). В простейшем случае пишешь в телегу коллеге: Вот, я написал, посмотри и кидаешь ссылку. Он говорит: Всё нормально, идём на тестирование.


Раз инфраструктура как код, у нас код проходит пайплайн. Всегда самый первый или второй job это lint. Команда ansible-lint просматривает код на синтаксические ошибки и дает дельные советы. Если ты ставишь пакет, вызывая его как command "yum" или "apt-get" install, то тебе линтер напишет: Используй специализированный модуль yum или apt. То есть тут не только синтаксические ошибки, но и семантика.


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


Главный вопрос: не на проде же ты это будешь делать?


Да, разумеется, запускается виртуалка, в ней согласно описанной для Molecule конфигурации запускается Ansible плейбук.


А где гарантия, что виртуалка похожа на прод?


Надо самому следить.


Продакшн у нас же изначально чистый, как я понимаю?


Продакшн у нас изначально чистый. То есть с момента рождения он у нас сделан с помощью Ansible. Поэтому тестовая виртуалка обязана быть похожа на прод. Если нужно, туда можно и приложения вкатить отдельным jobом.


А по ресурсам она тоже должна быть такая же, как прод, или необязательно?


Это зависит от конкретного случая. Если после запуска Molecule ты тестируешь ещё что-то с помощью каких-то внешних инструментов Условно говоря, выкатываешь сайт, тестируешь Selenium (такое тоже бывает), то, естественно, ресурсов должно быть больше. Если он у тебя просто проходит набор ролей, то ресурсов нужно ровно столько, чтобы этот Python в память влез.


Тебе как человеку, который работает с Ansible, нужно программирование? Ты говорил, язык разметки YAML, написан на Python, нужно ли дописывать какие-то модули, ещё что-то. Просто, чтобы понимали девелоперы: нужно ли владеть языком при работе с Ansible?


Можно не владеть. Но лучше владеть.


Каким и для чего?


Python. Потому что, если под конкретную задачу нет нужного модуля, конечно, можно написать это всё в виде скрипта и дёргать его из task, но это плохой тон, это называется bashsible в комьюнити, связанных с Ansible. И это прям фу-фу.


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


Нативном для Ansible, да?


Да.


Потому что можно на C попробовать это сделать.


Ну, если затраченные усилия будут оправданы, почему бы и нет.


Какие есть более или менее популярные аналоги Ansible? Или это монополист на рынке?


Нет, это, конечно, не монополист. Значительно более старые решения (в смысле прошедшие более длинный путь развития) это Chef и CFEngine. Последний сейчас уже мало используется.


Они все работают примерно одинаково?


Они работают в основном как pull: на настраиваемой ноде выполняется сервис, который тянет конфигурацию.


Ansible единственный, кто делает push из крупных?


Ansible умеет pull, но это не основной его режим работы. SaltStack тоже умеет push, но это не его основной режим работы. Ansible, можно сказать, в этом плане единственный. Ну и видимо, народу нравится, потому что я смотрю время от времени Google Trends по запросам, относящимся к Chef, к Ansible, и вижу, что Ansible набирает всё большую популярность. Более старых решения на пятилетнем промежутке её чуть-чуть теряют. Хотя есть энтузиасты, которые за них топят и их поддерживают.


Может, есть какие-то книги, что можно почитать, чтобы базово познакомиться с Ansible?


Знакомство с Ansible лучше всего начать с документации. На docs.ansible.com расписаны и самые основы, и примеры простых плейбуков, и вообще всё что нужно.
Я еще скажу про книгу Ansible: Up and Running. Книга неплохая, но Ansible достаточно быстро развивается и поэтому многие вещи в ней уже устарели. Поэтому лучше всего начинать именно с документации на сайте, она там всегда актуальна.


Вопросы задавал Лекс АйТиБорода iamitbeard

Подробнее..

Перевод Как предоставить доступ к кластеру Kubernetes с помощью клиентского сертификата простое руководство

03.02.2021 18:19:31 | Автор: admin

Источник

Предположим, мы создали кластер Kubernetes. И кто-то из команды разработчиков хочет развернуть и протестировать на нем новое приложение. Как нам предоставить ему доступ в кластер?

Команда Kubernetes aaS Mail.ru Cloud Solutions перевела простое руководство по предоставлению доступа к новому кластеру Kubernetes, включая настройку аутентификации и привязку ролей. Автор показывает процесс, используя клиентский сертификат x509.

Управление пользователями в Kubernetes

Для управления кластером Kubernetes и запущенными в нем приложениями обычно используют утилиту kubectl или веб-интерфейс. Под капотом эти инструменты вызывают API Server: HTTP Rest API, открывающий конечные точки управления кластером. Этот HTTP API хорошо документирован посмотрите сами.

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

Аутентификацию выполняют с помощью плагинов, есть плагины с разными механизмами:

  • сертификаты клиентов о них в этой статье;

  • Bearer tokens (персональные токены);

  • аутентифицирующий прокси;

  • базовая аутентификация HTTP.

В зависимости от механизма аутентификации плагин ищет информацию о пользователе в определенных местах. Например, для аутентификации по сертификату клиента идентификацию пользователя (идентификатор, имя, адрес электронной почты и так далее) указывают в поле Common Name (CN) сертификата. Информацию о группе, если она есть, добавляют в поле Organisation (O).

Внутри кластера Kubernetes нет ни ресурсов пользователей, ни ресурсов групп. Их обрабатывают вне кластера и предоставляют с каждым запросом, который направляют на сервер API я проиллюстрирую это ниже.

Некоторые соображения и допущения

  1. Кластер используют несколько команд или клиентов (подход с несколькими пользователями), так что нужно изолировать рабочую нагрузку для каждого клиента. Мы создадим пространство имен для команды разработчиков, в которую входит разработчик, которому надо дать доступ (пусть его зовут Дейв). Это пространство имен мы назовем development.

  2. Дейву предстоит развернуть стандартные ресурсы Kubernetes. Затем он получит право создавать, просматривать, обновлять, получать список и удалять ресурсы Deployment и Service. Дополнительные права можно предоставить при необходимости, но они ограничены пространством имен development.

  3. Скорее всего, членам команды Дейва потребуется такой же уровень доступа. Мы заведем группу dev и предоставим права на уровне группы.

  4. Дейву потребуется kubectl, а также openssl он сгенерирует закрытый ключ и запрос на вход с сертификатом.

Создание закрытого ключа и запроса на подпись сертификата (CSR)

Сначала Дейв генерирует закрытый ключ RSA и CSR. Закрытый ключ можно создать с помощью команды:

$ openssl genrsa -out dave.key 4096

С CSR немного сложнее, поскольку Дейву нужно убедиться, что он:

  1. использует свое имя в поле Common Name (CN) оно требуется для идентификации на сервере API;

  2. использует имя группы в поле Organisation (O) это имя нужно для идентификации группы на сервере API.

Ниже файл конфигурации, который Дейв использует для создания CSR:

[ req ]default_bits = 2048prompt = nodefault_md = sha256distinguished_name = dn[ dn ]CN = daveO = dev[ v3_ext ]authorityKeyIdentifier=keyid,issuer:alwaysbasicConstraints=CA:FALSEkeyUsage=keyEncipherment,dataEnciphermentextendedKeyUsage=serverAuth,clientAuth

Примечание: запись clientAuth в поле extendedKeyUsage нужна, поскольку сертификат будут использовать для идентификации клиента.

С помощью указанного файла конфигурации, сохраненного в csr.cnf, CSR можно создать одной командой:

$ openssl req -config ./csr.cnf -new -key dave.key -nodes -out dave.csr

Создав файл .csr, Дейв отправляет его администраторам, чтобы они подписали его с помощью центра сертификации кластера.

Подписание CSR

После подписания файла .csr выпускается сертификат. Он будет использоваться для аутентификации запросов, который Дейв отправит на сервер API.

Начнем с создания ресурса Kubernetes Certificate Signing Request.

Примечание мы могли создать управляемый кластер (например, в DigitalOcean, Google GKE, Microsoft Azure, Mail.ru Cloud Solutions или другой платформе) или собственный (допустим, kubeadm или kubespray). Процесс подписи везде устроен одинаково.

Мы используем следующую спецификацию и сохраняем ее в csr.yaml:

apiVersion: certificates.k8s.io/v1beta1kind: CertificateSigningRequestmetadata:name: mycsrspec:groups:- system:authenticatedrequest: ${BASE64_CSR}usages:- digital signature- key encipherment- server auth- client auth

Значение ключа request содержимое переменной окружения BASE64_CSR. Первый шаг получить кодированный в base64 файл .csr, созданный Дейвом. Затем использовать envsubst, чтобы заменить значения этой переменной перед созданием ресурса.

# Кодируем файл .csr в base64$ export BASE64_CSR=$(cat ./dave.csr | base64 | tr -d '\n')# Подставляем переменную env BASE64_CSR и создаем ресурс CertificateSigninRequest$ cat csr.yaml | envsubst | kubectl apply -f -

Проверяем статус созданного CSR мы видим, что он находится в состоянии ожидания:

# Проверяем статус созданного CSR$ kubectl get csrNAME        AGE   REQUESTOR            CONDITIONmycsr       9s    28b93...d73801ee46   Pending

Подтверждаем CSR с помощью команды:

$ kubectl certificate approve mycsr

Еще раз проверяем статус CSR теперь он одобрен:

$ kubectl get csrNAME        AGE   REQUESTOR            CONDITIONmycsr       9s    28b93...d73801ee46   Approved,Issued

Сертификат создан, теперь извлечем его из ресурса CSR, сохраним в файле с именем dave.crt и проверим, что внутри:

$ kubectl get csr mycsr -o jsonpath='{.status.certificate}' \| base64 --decode > dave.crt

Следующая команда openssl показывает: сертификат подписан центром сертификации кластера DigitalOcean (часть Issuer). Subject содержит dave в полях CN (CommonName) и O (Organisation), как указал Дейв при создании файла .csr:

$ openssl x509 -in ./dave.crt -noout -textCertificate:Data:    Version: 3 (0x2)    Serial Number:        48:29:cf:ae:d6:...:09:33:ef:14:58Signature Algorithm: sha256WithRSAEncryption    Issuer: O=DigitalOcean, CN=k8saas Cluster CA    Validity        Not Before: Jun  3 07:56:00 2019 GMT        Not After : Jun  2 07:56:00 2020 GMT    Subject: O=dev, CN=dave    Subject Public Key Info:        Public Key Algorithm: rsaEncryption            Public-Key: (4096 bit)            Modulus:...

Примечание в примере используем управляемый кластер Kubernetes, созданный в DigitalOcean мы видим это в Issuer кластера. На другой платформе будет похоже.

Создание пространства имен

Начинаем с создания пространства имен development благодаря этому ресурсы, которые развернут Дейв и его команда, будут изолированы от остальной рабочей нагрузки кластера.

Его можно создать с помощью простой команды:

$ kubectl create ns development

или с помощью файла dev-ns.yaml:

apiVersion: v1kind: Namespacemetadata:name: development

Применяем dev-ns.yaml с помощью команды:

$ kubectl apply -f dev-ns.yaml

Примечание рекомендую создать ресурс ResourceQuota и связать его с пространством имен. Это позволит ограничить объем CPU и ОЗУ, которые можно использовать в пространстве имен.

Настройка правил RBAC

С помощью сертификата Дейв может пройти аутентификацию на сервере API. Но пока у него нет прав, так что он не может делать многие вещи. Давайте дадим ему права создавать, получать список, обновлять, просматривать и удалять ресурсы Deployment и Service в пространстве имен dev.

Ресурсы, задействованные в управлении доступом к базе ролей Kubernetes (RBAC)Ресурсы, задействованные в управлении доступом к базе ролей Kubernetes (RBAC)

Коротко: роль (то же самое справедливо и для ClusterRole) содержит список правил. Каждое правило определяет действия, которые могут быть выполнены (например: list, get, watch) со списком ресурсов (например: Pod, Service, Secret) в apiGroups (например: core, apps/v1). Роль определяет права для конкретного пространства имен, область ClusterRole весь кластер.

Создание роли

Создадим ресурс Role со следующей спецификацией:

kind: RoleapiVersion: rbac.authorization.k8s.io/v1metadata:namespace: developmentname: devrules:- apiGroups: [""]resources: ["pods", "services"]verbs: ["create", "get", "update", "list", "delete"]- apiGroups: ["apps"]resources: ["deployments"]verbs: ["create", "get", "update", "list", "delete"]

Ресурсы подов и служб принадлежат основной группе API (значение ключа apiGroups пустая строка), а ресурсы развертывания группе API приложений. Для этих двух групп apiGroup мы определили список ресурсов и действия, которые нужно авторизовать на этих ресурсах.

Строки сохраняем в файл role.yaml, для создания роли используем команду:

$ kubectl apply -f role.yaml

Создание RoleBinding

Назначение RoleBinding связать роль, то есть список разрешенных действий, с пользователем или группой. Чтобы у Дейва были права, указанные в созданной выше роли, мы привязываем его к этой роли. Для этого используем ресурс RoleBinding:

kind: RoleBindingapiVersion: rbac.authorization.k8s.io/v1metadata:name: devnamespace: developmentsubjects:- kind: Username: daveapiGroup: rbac.authorization.k8s.ioroleRef:kind: Rolename: devapiGroup: rbac.authorization.k8s.io

Эта RoleBinding связывает:

  • субъект пользователь Дейв;

  • роль: с именем dev, которая позволяет создавать, просматривать, обновлять, получать список, удалять ресурсы Deployment и Service.

Примечание: поскольку Дейв входит в группу разработчиков, то можно использовать следующую привязку RoleBinding для связи роли с группой, а не отдельным пользователем. Помните: информация о группе указывается в поле Organisation (O) сертификата, его отправляют с каждым запросом.

kind: RoleBindingapiVersion: rbac.authorization.k8s.io/v1metadata:name: devnamespace: developmentsubjects:- kind: Groupname: devapiGroup: rbac.authorization.k8s.ioroleRef:kind: Rolename: devapiGroup: rbac.authorization.k8s.io

Мы сохранили спецификацию ресурса RoleBinding в файле role-binding.yaml и создаем его с помощью команды:

$ kubectl apply -f role-binding.yaml

Создание файла конфигурации KubeConfig

Все настроено. Теперь отправляем Дейву информацию, которая необходима для настройки его локального клиента kubectl для связи с нашим кластером. Сначала создаем файл kubeconfig.tpl со следующим содержанием, его мы будем использовать в качестве шаблона:

apiVersion: v1kind: Configclusters:- cluster:certificate-authority-data: ${CLUSTER_CA}server: ${CLUSTER_ENDPOINT}name: ${CLUSTER_NAME}users:- name: ${USER}user:client-certificate-data: ${CLIENT_CERTIFICATE_DATA}contexts:- context:cluster: ${CLUSTER_NAME}user: davename: ${USER}-${CLUSTER_NAME}current-context: ${USER}-${CLUSTER_NAME}

Чтобы создать kubeconfig из этого шаблона, нужно сначала установить переменные среды:

# Имя пользователя$ export USER="dave"# Имя кластера (полученное из текущего контекста)$ export CLUSTER_NAME=$(kubectl config view --minify -o jsonpath={.current-context})# Сертификат клиента$ export CLIENT_CERTIFICATE_DATA=$(kubectl get csr mycsr -o jsonpath='{.status.certificate}')# Данные центра сертификации кластера$ export CLUSTER_CA=$(kubectl config view --raw -o json | jq -r '.clusters[] | select(.name == "'$(kubectl config current-context)'") | .cluster."certificate-authority-data"')# Точка входа API$ export CLUSTER_ENDPOINT=$(kubectl config view --raw -o json | jq -r '.clusters[] | select(.name == "'$(kubectl config current-context)'") | .cluster."server"')

Подставляем их, используя удобную утилиту envsubst:

$ cat kubeconfig.tpl | envsubst > kubeconfig

Отправляем Дейву файл kubeconfig. Чтобы взаимодействовать с кластером, ему достаточно добавить в файл свой закрытый ключ.

Использование контекста

Чтобы использовать kubeconfig, Дейв устанавливает переменную среды KUBECONFIG, указав путь к файлу.

$ export KUBECONFIG=$PWD/kubeconfig

Примечание: есть разные способы использовать конфигурации Kubernetes. Можно установить переменную среду KUBECONFIG, добавить новую запись в файл $ HOME/.kube/config по умолчанию или использовать флаг --kubeconfig для каждой команды kubectl.

Чтобы добавить закрытый ключ dave.key, Дейв использует команду:

$ kubectl config set-credentials dave \--client-key=$PWD/dave.key \--embed-certs=true

Команда создает ключ client-key-data в записи пользователя файла kubeconfig и устанавливает dave.key в кодировку base64 в качестве значения.

Если все успешно, Дейв может проверить версию сервера (и клиента) с помощью команды:

$ kubectl versionClient Version: version.Info{Major:"1", Minor:"14", GitVersion:"v1.14.2", GitCommit:"66049e3b21efe110454d67df4fa62b08ea79a19b", GitTreeState:"clean", BuildDate:"2019-05-16T16:23:09Z", GoVersion:"go1.12.5", Compiler:"gc", Platform:"darwin/amd64"}Server Version: version.Info{Major:"1", Minor:"14", GitVersion:"v1.14.2", GitCommit:"66049e3b21efe110454d67df4fa62b08ea79a19b", GitTreeState:"clean", BuildDate:"2019-05-16T16:14:56Z", GoVersion:"go1.12.5", Compiler:"gc", Platform:"linux/amd64"}

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

$ kubectl get nodesError from server (Forbidden): nodes is forbidden: User "dave" cannot list resource "nodes" in API group "" at the cluster scope

Конечно, нет! Но Дейв может что-то развертывать в кластере по крайней мере, в пространстве имен development. Давайте проверим это с помощью YAML-файла, который определяет Deployment на основе образа nginx и Service для его предоставления:

# www.yamlapiVersion: apps/v1kind: Deploymentmetadata:name: wwwnamespace: developmentspec:replicas: 3selector:matchLabels:    app: wwwtemplate:metadata:    labels:    app: wwwspec:    containers:    - name: nginx    image: nginx:1.14-alpine    ports:    - containerPort: 80---apiVersion: v1kind: Servicemetadata:name: wwwnamespace: developmentspec:selector:app: votetype: ClusterIPports:- port: 80targetPort: 80

Из результата следующей команды видно, что Дейв может создавать эти ресурсы в кластере:

$ kubectl apply -f www.yamldeployment.apps/www createdservice/www created

Дейв ограничен пространством имен development. Если он попытается получить список всех подов в пространстве имен по умолчанию, то получит сообщение об ошибке:

$ kubectl get podsError from server (Forbidden): pods is forbidden: User "dave" cannot list resource "pods" in API group "" in the namespace "default"

Еще он не может создавать другие ресурсы, кроме тех, к которым ему предоставили доступ. Например, мы можем попробовать следующую спецификацию ресурса типа Secret:

# credentials.yamlapiVersion: v1kind: Secretmetadata:name: mysecretnamespace: developmentdata:username: YWRtaW4=password: MWYyZDFlMmU2N2Rm

Давайте посмотрим, как Дейв попытается его создать:

$ kubectl apply -f credentials.yamlError from server (Forbidden): error when retrieving current configuration of:Resource: "/v1, Resource=secrets", GroupVersionKind: "/v1, Kind=Secret"Name: "mysecret", Namespace: "development"Object: &{map["apiVersion":"v1" "data":map["password":"MWYyZDFlMmU2N2Rm" "username":"YWRtaW4="] "kind":"Secret" "metadata":map["annotations":map["kubectl.kubernetes.io/last-applied-configuration":""] "name":"mysecret" "namespace":"development"]]}from server for: "credentials.yaml": secrets "mysecret" is forbidden: User "dave" cannot get resource "secrets" in API group "" in the namespace "development"

Заключение

Мы показали, как использовать сертификат клиента для авторизации пользователей в кластере Kubernetes. Можно настраивать аутентификацию другим способом, но этот довольно прост.

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

Что еще почитать:

  1. 90+ инструментов, без которых не обойтись при использовании Kubernetes.

  2. Основы безопасности внутри Kubernetes: 10 простых советов.

  3. Как устроен Kubernetes as a Service на платформе Mail.ru Cloud Solutions.

Подробнее..

Как жили до Kubernetes сравниваем самый популярный оркестратор с другими решениями

24.02.2021 12:05:30 | Автор: admin


Kubernetes сейчас называют стандартом для оркестрации контейнеров. Он лежит в основе многих облачных платформ контейнеризации: например, мы давно развиваем наш Kubernetes aaS на платформе Mail.ru Cloud Solutions.


Однако Kubernetes далеко не первый подобный инструмент на рынке: некоторые из систем-предшественников продолжают активно использовать и вроде бы даже успешно.


Почему так происходит, несмотря на то, что Kubernetes, можно сказать, одержал победу в своем классе и мы видим много примеров, когда он приходит на смену другим решениям? Например, не так давно разработчики Mesosphere DC/OS, в основе которой лежал Apache Mesos, прекратили ее развитие и сфокусировались на другой своей платформе D2iQ Kubernetes (DKP). Думаю, что стоит разобраться, всегда ли хорош Kubernetes, когда оправдано использовать другие оркестраторы и о каких подводных камнях стоит знать.


Я Дмитрий Лазаренко, директор по продуктам облачной платформы Mail.ru Cloud Solutions (MCS). В этой статье расскажу об устройстве ряда оркестраторов-предшественников, сравню их с Kubernetes, посмотрю на его преимущества и недостатки по сравнению с ними.



Критерии для сравнения


В статье я сравню Kubernetes с наиболее известными и часто используемыми системами оркестрации: Docker Swarm, Nomad, Apache Mesos и Fleet.


Для сравнения использую следующие критерии:


  1. Типы рабочих нагрузок: предназначен ли оркестратор для управления Docker-контейнерами, иными контейнерами и неконтейнерными приложениями.
  2. Легкость управления: насколько оркестратор прост в установке и настройке.
  3. Требования к платформе для развертывания: является ли решение платформонезависимым или есть определенные требования к ОС и иные ограничения.
  4. Производительность: среднее время обработки API-вызовов, запуска контейнеров и других важных операций.
  5. Ограничения на количество узлов и контейнеров в кластере: какие существуют ограничения на число управляемых компонентов.
  6. Конфигурация как код: есть ли возможность загрузки схем приложений из YAML- или JSON-файлов.
  7. Шаблоны: доступна ли настройка пользовательских шаблонов.
  8. Сети: как строится сеть, есть ли собственное сетевое решение или необходимо использовать сторонние сетевые плагины.
  9. Возможность обнаружения сервисов: поддерживается ли динамическое обнаружение сервисов путем использования DNS, Proxy и так далее.
  10. Автомасштабирование: есть ли возможность автомасштабирования в зависимости от изменений нагрузки.
  11. Выполнение обновлений и отката: какие стратегии обновлений поддерживаются. Существует ли возможность последовательных обновлений (Rolling Update), когда старые инстансы приложения постепенно заменяются на новые. Насколько просто проводится откат.
  12. Отказоустойчивость: как выполняется обработка сбоев, что происходит при выходе из строя одного из узлов.
  13. Мониторинг: есть ли встроенные механизмы или необходимо использовать внешние инструменты и какие.
  14. Безопасность: есть ли встроенное решение для управления конфиденциальной информацией (логины и пароли).

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


I. Docker Swarm vs Kubernetes


Docker Swarm встроенное в сам Docker решение для управления контейнерами на физических или виртуальных машинах. Когда несколько хостов Docker используют режим Swarm, некоторые из них могут быть настроены как управляющие ноды, или менеджеры (Manager), а остальные как рабочие ноды (Worker). Задача менеджеров управлять кластером и делегировать задачи рабочим узлам.


1. Типы рабочих нагрузок


Если Docker Swarm предназначен для работы исключительно с Docker-контейнерами, то Kubernetes поддерживает несколько типов контейнеров: Docker, containerd, CRI-O и любое решение, придерживающееся стандарта CRI (Container Runtime Interface).


2. Легкость управления


По сравнению с Kubernetes Docker Swarm намного проще в установке вручную. Swarm работает поверх Docker, использует интерфейс его командной строки и легко интегрируется с Docker Compose. Kubernetes также может работать поверх Docker, но взаимодействовать с инфраструктурой в этом случае нужно будет при помощи двух разных утилит: Docker CLI и kubectl CLI.


Устройство Kubernetes на порядок сложнее. Он включает в себя множество компонентов: kube-api-server, kube-scheduler, kube-controller-manager, kube-proxy, kubelet и поддерживает несколько типов установщиков для разных типов инфраструктур и задач: Kubeadm, Kops, Kubespray и так далее. Все это требует дополнительного обучения.


То есть порог вхождения в Kubernetes выше по сравнению с Docker Swarm. Но все, что связано непосредственно с администрированием контейнеров: deployments, доведение кластера до нужного состояния, добавление новых нод, в K8s можно выполнить проще и быстрее. Также многие, поработав с обоими оркестраторами, отмечают, что Kubernetes более стабилен.


3. Требования к платформе для развертывания


Docker Swarm, как и Kubernetes, может использоваться в различных ОС: Windows, macOS, Linux. Однако Kubernetes использует различные настройки для каждой ОС. Если требуемые вам конфигурации выходят за рамки стандартной реализации, самостоятельная настройка Kubernetes может превратиться в гигантскую задачу. В этом плане Docker Swarm выигрывает.


4. Производительность


По сравнению с Kubernetes Docker Swarm быстрее. Kubernetes сложнее из-за большего числа дополнительных уровней в архитектуре (планировщик и так далее), что замедляет выполнение операций, но обеспечивает стабильность кластера в целом.


5. Ограничения на количество узлов и контейнеров в кластере


Kubernetes v1.20 поддерживает конфигурации, отвечающие следующим критериям:


  • не более 110 подов на узел, при этом параметр можно настраивать,
  • не более 5 000 узлов,
  • всего не более 150 000 подов,
  • всего не более 300 000 контейнеров.

В документации Docker Swarm даны рекомендации только по числу узлов-менеджеров 7 штук. Максимальное число контейнеров будет определяться, скорее, ограничениями ОС, но пользователи Swarm советуют придерживаться похожих рекомендаций: 100 контейнеров на ноду.


6. Конфигурация как код


В обоих оркестраторах можно использовать YAML-файлы для определения конфигураций. В Kubernetes также доступен JSON-формат, но YAML предпочтителен.


7. Шаблоны


В Docker Swarm нет шаблонизатора, аналогичного Helm в Kubernetes. Некий шаблонизатор приложений анонсировался в Docker Desktop Enterprise 3.0, но в настоящее время это решение не поддерживается.


8. Сети


С точки зрения сетевой модели Kubernetes смотрится предпочтительнее. Он работает с виртуальными сетями и поддерживает намного большее количество разных сетевых решений по сравнению со Swarm. Каждый под получает свой IP-адрес, что означает отсутствие необходимости сопоставления портов контейнера портам хоста. Kubernetes может работать с любым CNI-совместимым сетевым решением.


В Docker Swarm вы можете указать оверлейную сеть для своих служб. Менеджер будет автоматически назначать адреса контейнерам в оверлейной сети при инициализации или обновлении приложения. Однако некоторые пользователи жаловались на производительность нативной сети, советуя применять сетевые плагины вроде Calico.


9. Возможность обнаружения сервисов


Реализована в обоих оркестраторах с использованием встроенного DNS-сервера.


10. Автомасштабирование


В Docker Swarm автомасштабирование недоступно. Для каждой службы вы можете вручную указать количество задач, которые хотите запустить. Когда это число увеличивается или уменьшается, управляющий узел (Manager) автоматически адаптируется, добавляя или удаляя задачи для поддержания желаемого состояния.


В Kubernetes поддерживается автомасштабирование на основе наблюдаемого использования ЦП, памяти и ряда других пользовательских метрик. При этом масштабирование доступно на нескольких уровнях:


  1. Автомасштабирование кластера с использованием Cluster Autoscaler, отвечающее за изменение числа узлов в кластере.
  2. Горизонтальное автомасштабирование подов (Horizontal Pod Autoscaler, HPA), которое автоматически изменяет количество подов в зависимости от значений выбранных показателей.
  3. Вертикальное автомасштабирование подов (Vertical Pod Autoscaler, VPA), которое автоматически изменяет объем ресурсов, выделяемых существующим подам.

11. Выполнение обновлений и отката


И Kubernetes, и Docker Swarm поддерживают последовательные обновления (Rolling Update): во время развертывания вы можете постепенно применять обновления служб к узлам. В случае сбоя можно вернуться к предыдущей версии сервиса. Однако Kubernetes предоставляет возможность более гибкой настройки.


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


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


12. Отказоустойчивость


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


В Docker Swarm управляющий узел (Manager) постоянно отслеживает состояние кластера и согласовывает любые различия между фактическим состоянием и настроенным вами желаемым состоянием.


В Kubernetes службы балансировки нагрузки обнаруживают поды со сбоями и удаляют их. Используются проверки работоспособности (Health Check).


13. Мониторинг


Использование в Docker Swarm решений для логирования и мониторинга (ELK, Prometheus) возможно, как и в Kubernetes, но требует больше кастомных настроек или использования дополнительных наборов инструментов, таких как Swarmprom.


14. Безопасность


С точки зрения безопасности в Swarm еще недавно не было практически ничего. Для хранения секретов приходилось использовать Vault. Сейчас каждый узел в Swarm применяет взаимную аутентификацию и шифрование TLS для защиты коммуникаций внутри себя и с другими узлами. Также есть возможность использовать самоподписанные корневые сертификаты или сертификаты от настраиваемого корневого Центра сертификации. Есть и встроенное решение для управления секретами.


Однако Kubernetes все равно находится на шаг впереди, предлагая гибкую настройку сетевых политик (Network Policies), использование пространств имен (Namespaces) и прочие преимущества.


II. Nomad vs Kubernetes


Nomad это оркестратор от компании HashiCorp для управления контейнерами и неконтейнерными приложениями. Основу его архитектуры составляют клиенты (Client), на которых можно запускать задачи, и серверы (Server), управляющие клиентами и задачами. Серверы выбирают лидера для обеспечения высокой доступности.


Рабочую нагрузку в Nomad определяет работа (Job), описывающая желаемое состояние кластера и состоящая из одной или нескольких групп задач. Задача (Task) самая маленькая единица работы, которая выполняется драйверами (Docker, QEMU, Java и так далее). Обязанность Nomad поддерживать соответствие между желаемым состоянием, описанным в Job, и фактическим состоянием кластера.


За консультацию по работе Nomad отдельное спасибо manefesto.

1. Типы рабочих нагрузок


В то время как Kubernetes ориентирован на использование контейнеров, Nomad работает с различными видами рабочих нагрузок. Он поддерживает виртуализированные, контейнерные и автономные, микросервисные и пакетные приложения, включая Docker, Java, Qemu и другие, полный список можно посмотреть здесь. Это позволяет оркестрировать не только контейнерами / виртуальными машинами, но и запускать приложения на нодах. Например, можно запустить на нодах тяжелые Java-приложения.


2. Легкость управления


Установка Nomad значительно проще, чем Kubernetes. Nomad доступен в виде предварительно скомпилированного двоичного файла (единого как для клиентов, так и для серверов) и в виде пакетов для различных операционных систем. Вы также можете собрать Nomad из исходников.


Минимальный тестовый стенд можно поднять на хосте без использования сторонних утилит типа Minikube. Достаточно запустить Nomad в dev-режим, и он будет выполнять роль сервера/агента, чего вполне достаточно для локального тестирования.


Однако если Kubernetes стремится предоставить все функции, необходимые для запуска контейнерных приложений (включая управление кластером, автообнаружение служб, обеспечение безопасности и многое другое), то Nomad сосредоточен только на управлении кластером и планировании.


K8s считается фреймворком для построения кластера, в то время как подход HashiCorp больше близок к UNIX-way. Настроить кластер Nomad под нужные требования можно с помощью внешних инструментов: Consul для обнаружения сервисов, Vault для управления конфиденциальной информацией и других. До определенного момента в Nomad не было возможности использовать внешние хранилища, сети, отличные от хостовых и Bridge, но сейчас есть поддержка плагинов CSI, CNI, появилась возможность использовать сети, хранилища так же, как это делает K8s.


В любом случае использовать Nomad несложно: по факту достаточно скопировать бинарный файл и создать Systemd-сервис, который работает в связке с Consul как Service Discovery.


3. Требования к платформе для развертывания


И Kubernetes, и Nomad могут быть развернуты в различных ОС: Linux, macOS, Windows. Однако Kubernetes требует различных настроек в зависимости от ОС поэтому его установка, проводимая вручную, сложнее.


4. Производительность


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


5. Ограничения на количество узлов и контейнеров в кластере


Kubernetes поддерживает кластеры до 5 000 узлов и 300 000 контейнеров. В документации Nomad указано, что он масштабируется до размеров кластера, превышающих 10 000 узлов в реальных производственных средах. Также Nomad успешно участвовал в ряде напряженных тестов на масштабируемость: 1 миллион контейнеров в 2016 году и 2 миллиона контейнеров в 2020 году. Однако на практике вероятность применения настолько масштабных кластеров невелика.


6. Конфигурация как код


Kubernetes поддерживает декларативные развертывания на основе YAML-файлов. Nomad использует собственный язык описания конфигураций HashiCorp HCL. То есть при использовании Nomad вам потребуется дополнительное обучение HCL. Но стоит отметить, что в Nomad присутствуют стандартные средства и команды для преобразования описаний из YAML и JSON в HCL (и обратно): yamlencode/yamldecode, jsonencode/jsondecode. Правда, работают они с определенными ограничениями, а некоторые в тестовом режиме.


7. Шаблоны


В Nomad нет инструмента для шаблонизации, аналогичного Helm для Kubernetes.


8. Сети


В плане организации сетей Kubernetes выглядит лаконичнее и стабильнее. В нем изначально была введена абстракция Pod, позволяющая запускать несколько контейнеров в одном сетевом пространстве, и использовался CNI (Container Networking Interface). Nomad же развивался от меньшего к большему и долгое время использовал исключительно хостовые сети без дополнительных слоев абстракции. Вместо Pod в нем использовались Job без возможности объединения контейнеров внутри них в единое сетевое пространство. Мультиинтерфейсные сети и возможность подключения CNI стали развиваться в Nomad относительно недавно. Можно подключить CNI-плагин для использования Calico, Cilium, Weave.


9. Возможность обнаружения сервисов


В отличие от Kubernetes, в Nomad нет Autodiscovery на основе DNS. Для обнаружения сервисов необходимо использовать дополнительный инструмент Consul. В этом Nomad сильно уступает Kubernetes. И многие считают это одной из основных причин, почему Nomad оказался менее востребован в свое время.


10. Автомасштабирование


Общая черта двух оркестраторов поддержка следующих типов автомасштабирования:


  • горизонтальное автомасштабирование приложений в Nomad и подов в Kubernetes,
  • автомасштабирование кластера.

Дополнительно в Kubernetes реализовано вертикальное автомасштабирование подов, которое автоматически изменяет объем ресурсов, выделяемых существующим подам. Похожее решение в Nomad, состоящее в построении рекомендаций по ЦП и памяти на основе анализа исторических данных, доступно исключительно в Enterprise-версии.


11. Выполнение обновлений и отката


Оба оркестратора предоставляют возможность гибкого управления обновлениями, включая стратегии последовательного (Rolling update), сине-зеленого (Blue and Green) и канареечного (Canary) обновлений. Для настройки доступен целый ряд показателей, влияющих на ход обновлений, включая временные интервалы для проверки работоспособности и прочее.


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


12. Отказоустойчивость


По умолчанию поведение у обеих систем схожее. В Kubernetes дается минута на определение узла со сбоем и до пяти минут на вытеснение подов на другую ноду. В Nomad примерно такие же дефолтные показатели. Но в Kubernetes с использованием опций kubelet и Control Manager реально добиться того, чтобы уже в течение 10 секунд поды были вытеснены на другую работоспособную ноду.


13. Мониторинг


Оба оркестратора совместимы с популярными инструментами логирования и мониторинга: ELK, Prometheus/Grafana и прочими. Ведется сбор метрик, которые можно получить впоследствии через API либо настроив автоматическую пересылку стороннему провайдеру.


14. Безопасность


Основное, в чем проигрывает Nomad в плане безопасности, это необходимость использования сторонней системы Vault для управления конфиденциальной информацией (логинами и паролями). Хотя безопаснее Vault на современном рынке Open Source-решений, пожалуй, нет его настройка и использование совместно с Nomad является довольно сложной задачей. Коробочный Kubernetes в этом отношении проще, так как может предложить встроенное решение.


Кроме этого, если в Kubernetes по умолчанию поддерживаются пространства имен (Namespaces), которые можно эффективно использовать для разделения сред разработки, то в Nomad данная функциональность доступна только в Enterprise-версии.


III. Apache Mesos (+ Maraphon, Aurora) vs Kubernetes


Apache Mesos это менеджер кластера, поддерживающий различные рабочие нагрузки. Основу архитектуры Mesos составляют мастер (Mesos Master), агенты (Mesos Agent), работающие на каждом узле кластера и управляемые мастером, и фреймворки (Mesos Frameworks), которые запускают задачи на агентах. Обычно разворачивается несколько резервных мастеров, готовых взять на себя управление в случае сбоя, а за выбор лидера среди них отвечает ZooKeeper.


Фреймворк состоит из двух компонентов: планировщика (Scheduler), он регистрируется на главном сервере, которому будут предлагаться ресурсы, и исполнителя (Executor), который запускается на узлах агентов для выполнения задач фреймворка. Мастер предлагает ресурсы агентов фреймворкам, а планировщики выбирают, какие из предложенных ресурсов использовать для запуска своих задач на агентах. В кластере Mesos может работать несколько фреймворков для разных типов задач.


То есть сам по себе Apache Mesos является лишь неким диспетчером, а конечная функциональность будет определяться используемым фреймворком. Поэтому при описании показателей будем иногда ссылаться на два конкретных фреймворка: Maraphon для управления контейнерными приложениями и Apache Aurora для планирования Cron-заданий и долго работающих служб. Aurora больше не поддерживается, но в свое время была довольно распространена и в том числе могла использоваться для контейнерных приложений. Также иногда будем упоминать Mesosphere DC/OS операционную систему, основанную на Apache Mesos, Maraphon и предлагающую ряд дополнительных возможностей (правда, часть из них доступна в платной версии).


1. Типы рабочих нагрузок


Apache Mesos предназначен для обработки различных типов рабочих нагрузок, которые могут быть как контейнерными, так и неконтейнерными. В качестве планировщика заданий (Scheduler) могут быть использованы, например, Hadoop (Big Data), MPI (обмен сообщениями), Jenkins (система непрерывной интеграции). Используя Apache Mesos, можно разработать даже собственный планировщик.


Kubernetes ориентирован исключительно на контейнерные приложения.


2. Легкость управления


Apache Mesos проще развернуть, чем Kubernetes, если мы говорим о ручной установке. Однако дальнейшее администрирование Apache Mesos сложнее по сравнению с K8s, так как необходимо работать с ZooKeeper, уметь администрировать Java и быть готовым к связанным с этим трудностям: утечкам памяти, Xmx, лимитам и так далее. Kubernetes с Golang в этом плане проще.


3. Требования к платформе для развертывания


Mesos работает на Linux и macOS. Агенты могут быть установлены и на Windows.
Kubernetes также поддерживает все ОС.


4. Производительность


Mesos изначально был ориентирован на работу с Big Data поэтому по производительности он опережает Kubernetes.


5. Ограничения на количество узлов и контейнеров в кластере


По масштабируемости Apache Mesos выигрывает у Kubernetes, так как способен поддерживать десятки тысяч узлов (K8s рассчитан на 5 000 максимум). Когда Mesos сочетается с Mesosphere DC/OS, получается платформа, предлагающая практически неограниченную масштабируемость, которая идеально подходит для больших систем. Разработчики заявили, что прекратили ее развитие, но существующих клиентов продолжают поддерживать. Еще в 2015 году Mesos с успехом выдержал тест на масштабирование до 50 000 узлов.


6. Конфигурация как код


В Apache Mesos в сочетании с Maraphon можно использовать определения JSON (для указания репозиториев, ресурсов, числа экземпляров и команд для выполнения). В Kubernetes для этой цели поддерживаются декларативные развертывания и в YAML, и в JSON (первый предпочтительнее).


7. Шаблоны


Если вы работаете в Mesosphere DC/OS, то можете использовать шаблоны конфигураций (с использованием Maraphon-LB). В Apache Aurora при настройке услуг в DSL также были доступны шаблоны во избежание дублирования конфигураций. Но Helm, используемый в K8s, будет более мощным и удобным инструментом.


8. Сети


Долгое время Apache Mesos отставал от Kubernetes в плане сетевой реализации, так как по умолчанию в нем не назначались IP-адреса контейнерам, требовалось проведение сопоставления портов контейнера с портами хоста, которые являются ограниченным ресурсом. Впоследствии была добавлена поддержка CNI (Container Networking Interface) и появилась возможность использования виртуальных сетей вида Calico, Cilium, Weave. Но в Kubernetes сейчас поддерживается большее число сетевых решений.


9. Возможность обнаружения сервисов


Присутствует в обеих системах.


Mesos-DNS обеспечивает обнаружение служб и базовую балансировку нагрузки для приложений. При использовании совместно с Marathon дополнительно доступен Marathon-LB для обнаружения на основе портов с помощью HAProxy. В Aurora была также представлена экспериментальная поддержка Mesos DiscoveryInfo для создания настраиваемой системы обнаружения без использования ZooKeeper.


В Kubernetes доступен встроенный DNS-сервер.


10. Автомасштабирование


Реализация автомасштабирования лучше в Kubernetes. Как уже отмечалось выше, доступны три стратегии: масштабирование кластера, горизонтальное масштабирование подов и вертикальное масштабирование подов. В качестве триггеров можно настраивать показатели ЦП, памяти и пользовательские показатели.


В Apache Mesos автомасштабирование доступно в комбинации с Maraphon (в Mesosphere DC/OS) на основе значений ЦП, памяти и числа запросов в секунду.


11. Выполнение обновлений и отката


Поддерживается в обеих системах. Сине-зеленое (Blue and Green) развертывание доступно в Apache Mesos, также его можно реализовать в Kubernetes, используя встроенные механизмы.


12. Отказоустойчивость


Обе системы отказоустойчивы.


В Apache Mesos экземпляры приложений распределяются по агентам, обеспечивая высокую доступность. Кроме того, ZooKeeper поддерживает доступность кластера через кворум и выборы лидера.


Аналогично поды в Kubernetes реплицируются на несколько узлов. Обычно кластер Kubernetes состоит из нескольких рабочих узлов. В нем также может быть несколько мастеров.


Кроме этого, оба оркестратора предоставляют проверки работоспособности (Health Check). Для Mesos они доступны в случае использования Mesosphere DC/OS.


13. Мониторинг


И в Mesos, и в Kubernetes доступно получение метрик, относящихся к работоспособности и другим показателям. Данные можно запрашивать и агрегировать с помощью внешних инструментов. Типичной практикой является развертывание ELK и Prometheus + Grafana.
Но подключение мониторинга в Kubernetes происходит проще, так как многое строится на готовых решениях благодаря поддержке многочисленного сообщества и богатому инструментарию. В Mesos многое приходится делать самостоятельно, вследствие чего неизбежно увеличивается Time to Market.


14. Безопасность


Сложно отдать кому-то предпочтение. Если говорить об Apache Mesos в связке с Maraphon или Aurora вне Mesosphere DC/OS, то Kubernetes выглядит лучше. Долгое время Mesos не мог предложить в плане безопасности практически ничего. Так, встроенное решение по работе с секретами не имело в нем надлежащей реализации, и пользователи предпочитали использовать сторонние инструменты, например Vault.


Но если обратиться к ОС DC/OS Enterprise сегодня она предлагает богатый функционал в плане корпоративной безопасности, возможно, даже лучший, чем в Kubernetes. Однако эта версия системы является платной, кроме того, ее развитие свернуто разработчиками в пользу новой платформы на основе как раз Kubernetes.


IV. Fleet vs Kubernetes


Fleet это низкоуровневый кластерный движок от CoreOS, похожий на распределенную систему инициализации. В настоящее время он не поддерживается, так как компания стала активно продвигать Kubernetes.


Fleet был основан на systemd. В то время как systemd обеспечивал инициализацию системы и служб на уровне одной машины, Fleet расширил этот процесс до кластера. В архитектуре Fleet на каждой машине запускался fleetd-демон, обеспечивающий две роли: движок (Engine) и агент (Agent). Движок принимал решения по планированию, а агент обрабатывал Systemd Unit-файлы (Unit). Эта обработка чаще всего сводилась к запуску контейнера.


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


1. Типы рабочих нагрузок


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


2. Легкость управления


Установка Fleet проводилась значительно проще и быстрее по сравнению с Kubernetes. Чтобы запустить свои службы в кластере, необходимо было отправить обычные модули systemd в сочетании с несколькими свойствами, специфичными для Fleet. То есть для управления кластером было достаточно знаний по работе с systemd.


3. Требования к платформе для развертывания


Fleet изначально разрабатывался на базе CoreOS. Kubernetes в настоящее время поддерживает практически все виды ОС.


4. Производительность


Сложно оценить, так как продукт более не поддерживается. Вероятно, Fleet был быстрее, учитывая его архитектурную простоту и ограниченную функциональность.


5. Ограничения на количество узлов и контейнеров в кластере


Fleet уступал по масштабируемости. В последней версии продукта не рекомендовалось запускать кластеры более 100 узлов или с более чем 1000 служб. Kubernetes сейчас поддерживает до 5 000 узлов и 300 000 контейнеров в кластере.


6. Конфигурация как код


В документации Fleet отсутствуют данные о возможности загрузки определений приложений из YAML или JSON, доступной в Kubernetes. Но формировавшиеся представления сохранялись в etcd в формате JSON. Также Fleet предоставлял API для управления состоянием кластера с использованием JSON: создание и удаление модулей, изменение желаемого состояния, вывод списка модулей.


7. Шаблоны


Fleet поддерживал несколько простых шаблонов развертывания на основе настраиваемых параметров модуля systemd. В них, например, можно было использовать определения места запуска контейнеров (Affinity и Anti-Affinity), похожие на селекторы Kubernetes. Однако используемый в Kubernetes шаблонизатор Helm более мощный инструмент.


8. Сети


Fleet это низкоуровневое решение, и его нельзя сравнивать с K8s в плане организации сетей.


9. Возможность обнаружения сервисов


Присутствует в обоих. Но, в отличие от Kubernetes, Fleet не поддерживал интеграцию с DNS. Взамен этого он предлагал модель Sidekick, в которой отдельный агент обнаружения запускался рядом с основным контейнером.


10. Автомасштабирование


В документации Fleet не описана возможность автомасштабирования.


11. Выполнение обновлений и отката


Fleet не поддерживал последовательные обновления, в отличие от K8s: новые модули (Units) планировались, а старые уничтожались вручную.


12. Отказоустойчивость


Обеспечивается в обоих оркестраторах. Архитектура Fleet разработана так, чтобы быть отказоустойчивой: если машина выходила из строя, любые запланированные на ней задания (Unit) перезапускались на новых хостах.


13. Мониторинг


Fleet уступал Kubernetes по возможностям мониторинга. Он поддерживал в экспериментальном режиме получение незначительного количества метрик в формате Prometheus. О возможности использования других инструментов логирования и мониторинга (ELK и прочее) в документации не сказано.


14. Безопасность


Fleet как низкоуровневое решение предлагал меньше возможностей в плане безопасности по сравнению с K8s. Например, он не поддерживал контроль доступа (Access Control List, ACL): все, кто имели доступ к etcd, могли управлять Fleet. Также в нем не было инструментов для управления конфиденциальной информацией (логины и пароли).


V. Преимущества и недостатки систем оркестрации


1. Docker Swarm


Преимущества:


  1. Ниже порог входа: быстро устанавливается, легок в изучении, интегрирован с Docker Compose и Docker CLI.
  2. Позволяет быстрее разворачивать контейнеры.

Недостатки:


  1. Уступает Kubernetes в плане администрирования, особенно в Production-среде.
  2. Отсутствует автомасштабирование.
  3. Проигрывает в плане поддержки сообществом: инструментарий намного меньше, чем у Kubernetes, что приводит к отсутствию готовых решений и необходимости многое настраивать самостоятельно.

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


2. Nomad


Преимущества:


  1. Прост в установке и эксплуатации, так как сосредоточен только на управлении кластером.
  2. Поддерживает различные виды рабочих нагрузок.

Недостатки:


  1. Ограниченная функциональность. Требуется установка сторонних инструментов для решения задач, которые K8s реализует по умолчанию: Autodiscovery, управление секретами.
  2. Проигрывает Kubernetes в плане поддержки сообществом.

Для каких задач подходит:


  1. Для небольших/средних команд с ограниченными возможностями поддержки оркестратора.
  2. При сочетании контейнерных и неконтейнерных нагрузок.
  3. При построении сетей L2 с малым количеством контейнеров.

3. Apache Mesos (+ Maraphon, Aurora)


Преимущества:


  1. Высокая масштабируемость.
  2. Поддержка контейнерных и неконтейнерных приложений, возможность совмещения нескольких фреймворков (включая Kubernetes).

Недостатки:


  1. Сложность администрирования.
  2. Проигрывает в плане поддержки сообществом.

Для каких задач подходит:


  1. Масштабные проекты с участием нескольких центров обработки данных и/или десятков тысяч узлов.
  2. При сочетании контейнерных и неконтейнерных нагрузок. В частности, при работе с приложениями по обработке данных: Hadoop, Kafka, Spark.
  3. Комбинация Apache Mesos и Aurora отлично подходила для оркестрации задач, не связанных с контейнерами, но требующих аналогичной обработки, например перезапуска в случае сбоя.

4. Fleet


Преимущества:


  1. Простота

Недостатки:


  1. Ограниченная функциональность
  2. Продукт более не поддерживается

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


5. Kubernetes


Преимущества:


  1. Самодостаточный инструмент оркестровки, в который встроено множество сервисов. Kubernetes предоставляет все функции, необходимые для запуска приложений на основе контейнеров, включая: управление кластером, планирование, обнаружение служб, мониторинг, управление безопасностью и многое другое.
  2. Поддерживается фондом CNCF (Cloud Native Computing Foundation). У Kubernetes самое впечатляющее по числу участников сообщество среди всех оркестраторов, что обеспечивает богатый инструментарий и большое число готовых решений.
  3. Это бесплатный инструмент с открытым исходным кодом, который работает в любой ОС.

Недостатки:


  1. Сложнее настроить вручную, хотя это можно решить с помощью использования Kubernetes aaS.
  2. Предназначен только для контейнерных приложений.
  3. Меньшее количество поддерживаемых узлов по сравнению с Nomad и Mesos.

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


Как перейти на Kubernetes с других оркестраторов


Предположим, вы использовали одно из рассмотренных решений и задумались о переходе на Kubernetes.


Что теперь предпринять?


Продумайте окружение. Kubernetes интегрирован со множеством инструментов мониторинга, логирования, построения CI/CD и так далее. Возможно, вы не захотите ограничиться лишь сменой оркестратора и построите более обширную инфраструктуру по сравнению с текущей, максимально задействовав возможности экосистемы K8s.


Перепишите манифесты на YAML Kubernetes. Сложность операции будет определяться их количеством и особенностями прежнего оркестратора:


  1. Переход с Docker Swarm наиболее прост. Можно либо самостоятельно переписать все манифесты, либо использовать доступные инструменты преобразования из файлов Docker Compose в YAML Kubernetes, например Kompose. Первый способ представляется более правильным, так как позволит учесть все преимущества Kubernetes, связанные с deployments, labels, tolerations и так далее. Второй способ позволит избежать переписывания, но менее нативен.
  2. Переход с Nomad и Apache Mesos наиболее сложен, так как их манифесты не совместимы с Kubernetes. Скорее всего, переход с Apache Mesos/Maraphon будет проще, так как в нем использовались JSON-определения в отличие от Apache Mesos/Aurora с собственным синтаксисом (похожим на Python) и Nomad с его HCL.
  3. Если вы использовали Fleet, то, вероятно, как основу для более высокоуровневого оркестратора. То есть для перехода на Kubernetes необходимо переписать манифесты, созданные для того оркестратора, что вы применяли. Либо ничего не переписывать, если у вас уже был Kubernetes поверх Fleet.

Разверните Kubernetes, вспомогательную инфраструктуру и настройте свой кластер. Если у вас нет команды, способной справиться с непростой задачей ручной установки, то можно использовать одно из готовых (Managed) решений. Их провайдеры не только предлагают услуги по развертыванию кластеров Kubernetes, но и оказывают дальнейшую техническую поддержку, а также могут помочь с миграцией (например, с Docker Swarm). В результате вы получите готовый кластер за считаные минуты и все преимущества Cloud Native-приложений.


При этом, если вы хотите по максимуму отказаться от администрирования кластера, с выбором провайдера тоже нужно не ошибиться: не все Managed-решения находятся в зрелом состоянии и позволяют снять с IT-отдела компании многие сложности работы с Kubernetes. Кроме того, не стоит забывать, что Managed-решения не серебряная пуля и кое-какой поддержкой кластера заниматься все равно придется.


Чек-лист: на что обратить внимание при выборе провайдера Kubernetes


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


Так, KaaS от Mail.ru Cloud Solutions из коробки включает:


  • автоматическое масштабирование кластера до сотен узлов с использованием Kubernetes Cluster Autoscaler;
  • наличие выделенных балансировщиков нагрузки для распределения трафика;
  • интегрированные Persistent Volumes на базе надежного блочного хранилища CEPH, виртуального файлового хранилища или SSD/NVME-дисков, подключенных по iSCSI к каждому вычислительному серверу;
  • собственный Terraform-провайдер для работы с инфраструктурой как кодом.

Совместимость со стандартными инструментами Kubernetes. Нужно проверить, поддерживает ли провайдер интеграцию с другими приложениями экосистемы K8s, например:


  • Serverless: OpenFaaS, Kubeless;
  • Service Mesh: Istio, Consul, Linkerd;
  • мониторинг: Prometheus, Fluentd, Jaeger, OpenTracing;
  • CI/CD: Gitlab, CircleCI, Travis CI;
  • IaC (описание приложений): Terraform, Helm.

Возможность подключения других сервисов провайдера. Обратите внимание на дополнительные решения провайдера и возможность их использования в своих кластерах. KaaS от MCS позволяет подключить объектное хранилище (Cloud Storage) и DBaaS для хранения данных Stateful-приложений, а также его легко совместно использовать с другими PaaS, например Cloud Big Data. Например, централизованно разворачивать нужные сервисы и управлять ими на одной платформе.


Сертификация CNCF. Подтверждает то, что сервис отвечает всем функциональным требованиям сообщества Cloud Native Computing Foundation (CNCF) и совместим со стандартным Kubernetes API. MCS пока единственный в России облачный провайдер, получивший такую сертификацию.


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


Выводы: сравнительный анализ систем оркестрации


Результаты сравнения Kubernetes и других известных оркестраторов наглядно демонстрирует таблица ниже:


  • самая удачная и удобная в использовании реализация;
  • удовлетворительная реализация, уступающая другим по ряду параметров;
  • реализация, уступающая другим по большинству параметров и/или требующая значительно больше времени и ресурсов для ее использования.

Критерий/Оркестратор Docker Swarm Nomad Apache Mesos Fleet K8s
Типы рабочих нагрузок
Легкость установки и исходной настройки (вручную)
Легкость администрирования кластеров
Требования к платформе для развертывания
Производительность
Ограничения на количество узлов и контейнеров в кластере
Конфигурация как код
Шаблоны
Сети
Возможность обнаружения сервисов
Автомасштабирование
Выполнение обновлений и отката
Отказоустойчивость
Мониторинг
Безопасность

Как мы увидели, Kubernetes лидирует по многим показателям. Но есть и недостатки, и самый весомый из них высокий порог входа для новых пользователей при выборе ручной установки. Однако современный рынок предлагает множество Managed-решений, способных преодолеть эту сложность. Главное грамотно подойти к выбору между ними, взвесив все за и против.


Что еще почитать по теме:


  1. Зачем крупным компаниям микросервисы, контейнеры и Kubernetes: как эти три технологии выводят IT на новый уровень.
  2. 10 антипаттернов деплоя в Kubernetes: распространенные практики, для которых есть другие решения.
  3. Наш Телеграм-канал Вокруг Kubernetes в Mail.ru Group.
Подробнее..

Деплоим проект на Kubernetes в Mail.ru Cloud Solutions. Часть 1 архитектура приложения, запуск Kubernetes и RabbitMQ

07.04.2021 18:21:37 | Автор: admin

О Kubernetes и его роли в построении микросервисных приложений известно, пожалуй, большинству современных IT-компаний. Однако при его внедрении часто возникает вопрос какой вариант установки выбрать: Self-Hosted или Managed-решение от одного из облачных провайдеров. О недостатках первого варианта, думаю, известно всем, кто проходил через ручное конфигурирование K8s: сложно и трудоемко. Но в чем лучше Cloud-Native подход?

Я Василий Озеров, основатель агентства Fevlake и действующий DevOps-инженер (опыт в DevOps 8 лет), покажу развертывание Kubernetes-кластера на базе облака Mail.ru Cloud Solutions. В этом цикле статей мы создадим MVP для реального приложения, выполняющего транскрибацию видеофайлов из YouTube.

На его базе мы посмотрим все этапы разработки Cloud-Native приложений на K8s, включая проектирование, кодирование, создание и автомасштабирование кластера, подключение базы данных и S3-бакетов, построение CI/CD и даже разработку собственного Helm-чарта. Надеюсь, этот опыт позволит вам убедиться, что работа с K8s может быть по-настоящему удобной и быстрой.

В первой части статьи мы выберем архитектуру приложения, напишем API-сервер, запустим Kubernetes c балансировщиком и облачными базами, развернем кластер RabbitMQ через Helm в Kubernetes.

Также записи всех частей практикума можно посмотреть: часть 1, часть 2, часть 3.

Выбор архитектуры приложения

Определимся с архитектурой будущего приложения. В первую очередь нам потребуется API, к которому будет обращаться клиентское приложение. Будем использовать стандартные форматы: HTTPS и JSON. В JSON необходимо передавать URL видео, а также некоторый идентификатор или уникальное имя запроса для возможности отслеживания его статуса.

Следующий необходимый компонент очередь сообщений. Очевидно, что обработку видео не получится проводить в real-time режиме. Поэтому будем использовать RabbitMQ для асинхронной обработки.

Далее нам потребуются обработчики, которые будут читать сообщения из очереди и заниматься непосредственной конвертацией запрошенных видео в текст. Назовем их Worker. Для транскрибации будем использовать не внешнее API, а какую-нибудь библиотеку, установленную локально. Так как для этого потребуются ресурсы, обязательно настроим автомасштабирование в кластере, чтобы число обработчиков изменялось пропорционально количеству сообщений в очереди.

Для сохранения текстовых расшифровок видео, которые будут формировать обработчики Worker, потребуется хранилище. Будем использовать S3, которое идеально подходит для хранения неструктурированных данных в облаке.

Наконец, чтобы иметь возможность получать статус обработки запросов, их необходимо где-то сохранять. Для этого выберем обычную базу PostgreSQL.

Сценарий взаимодействия выбранных компонентов включает в себя следующие шаги:

  1. Клиент отправляет на API-сервер запрос POST, передавая в теле запроса имя и URL видео на YouTube, которое необходимо перевести в текст.

  2. API-сервер формирует сообщение с полученными параметрами и передает его в очередь RabbitMQ.

  3. API-сервер сохраняет информацию о полученном запросе на конвертацию видео в базе данных PostgreSQL. Статус обработки запроса по умолчанию равен false.

  4. API-сервер информирует клиента об успешном завершении операции. Клиент может продолжать свою работу, не дожидаясь конвертации видео.

  5. Свободный обработчик Worker извлекает сообщение из очереди RabbitMQ.

  6. Получив сообщение, Worker выполняет его обработку: загружает видео по указанному URL, получает из него аудио и переводит при помощи стороннего ПО в текст.

  7. Обработав видео, Worker сохраняет транскрипт видео в хранилище S3.

  8. Worker отправляет в API-сервер информацию об успешной обработке запроса с исходным именем. В запросе передается статус обработки, равный true, и ссылка на текстовый файл в S3. Endpoint для отправки статуса обработки запросов можно либо жестко прописывать в environment-переменных обработчика Worker, либо передавать его в теле сообщений наряду с другими параметрами. В нашем MVP будет реализован первый вариант. То есть обработчикам будет известно, какой API вызвать для обновления статуса запросов.

  9. API-сервер обновляет полученную от Worker информацию о запросе в базе данных PostgreSQL. Альтернативный вариант можно настроить обновление базы данных непосредственно из обработчиков Worker, однако это потребует знания структуры БД с их стороны, что чревато проблемами при миграциях БД. Поэтому в нашем приложении взаимодействие с БД будет происходить исключительно через API-сервер.

  10. Клиент спустя некоторое время после отправки исходного видео запрашивает статус его обработки, передавая в API-сервер имя исходного запроса.

  11. API-сервер извлекает данные о запросе из PostgreSQL по полученному имени.

  12. API-сервер получает информацию о запросе из PostgreSQL.

  13. API-сервер отправляет данные о запросе клиенту. Клиент получает статус обработки и URL, по которому сможет в дальнейшем загрузить транскрипт исходного видео из S3.

Упрощенная схема архитектуры будущего приложенияУпрощенная схема архитектуры будущего приложения

Настройка кластера Kubernetes в облаке MCS

Начинаем с создания кластера Kubernetes. Для этого в панели управления облаком MCS необходимо выбрать пункт меню Контейнеры Кластеры Kubernetes и добавить новый кластер.

На первом шаге настраивается конфигурация будущего кластера. Можно выбрать тип среды и один или несколько предустановленных сервисов. Мы выберем среду Dev и сразу добавим Ingress Controller Nginx для управления внешним доступом к кластеру:

На следующем шаге вводим название кластера и выбираем тип виртуальной машины для ноды Master. Оставим стандартную конфигурацию с 2 CPU и 4 ГБ памяти. Далее можно указать зону доступности мы оставим для нее автоматическое заполнение:

Далее на этом же шаге выбирается тип и размер диска. Нам достаточно HDD размером 20 Гб. Оставляем одну Master-ноду, выбираем предварительно добавленную подсеть и назначаем внешний IP для удобного доступа к кластеру извне:

На следующем шаге создаются группы рабочих узлов. В рамках проекта нам потребуются две группы. Сейчас создадим первую для развертывания API и RabbitMQ, а впоследствии добавим еще одну, для обработчиков Worker.

Вводим название группы узлов и указываем конфигурацию: 2 CPU и 4ГБ памяти. Для зоны доступности вновь выбираем автоматический выбор:

Чтобы обеспечить работу RabbitMQ, выбираем более производительный тип дисков SSD размером 50 ГБ. Оставляем один узел, автомасштабирование пока не указываем его рассмотрим позднее на примере другой группы узлов:

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

При успешном добавлении кластера на экране отобразится информация о его параметрах:

Для последующей работы с кластером необходимо:

  1. Установить локальный клиент kubectl и запустить его.

  2. Экспортировать в локальный клиент конфигурационный файл созданного кластера с расширением .yaml командой export KUBECONFIG=<путь к файлу>.

  3. Для безопасного подключения к кластеру запустить proxy-сервер командой kubectl proxy.

Эта инструкция отображается под списком параметров кластера после его добавления.

У нас kubectl установлен поэтому берем из загрузок сформированный конфигурационный файл kub-vc-dev_kubeconfig.yaml и экспортируем его в kubectl:

После экспорта конфигурационного файла можно убедиться в работоспособности кластера:

  1. Сначала смотрим доступные контексты: kubectl config get-contexts

    Видим, что у нас создался кластер kub-vc-dev:

  2. Смотрим доступные ноды: kubectl get nodes

    В кластере создались две ноды master и workload:

  3. Смотрим доступные Namespace: kubectl get ns

    Получаем ответ:

  4. Смотрим доступные поды: kubectl -n ingress-nginx get pods

    В Namespace ingress-nginx запущены поды для Nginx Controller:

  5. Смотрим доступные сервисы: kubectl -n ingress-nginx get svс

В списке сервисов также отображается Nginx Controller, для которого указан внешний адрес, который мы сможем прописывать в DNS, чтобы попадать в наши сервисы извне:

Разработка API-сервера на Go

Следующий шаг написать API для отправки запросов на конвертацию видео и получения статуса их обработки. С полной версией исходного кода можно ознакомиться здесь.

Ниже отображена структура проекта. Это стандартное Go-приложение. В файлах go.mod, go.sum описываются зависимости, в папке migrations миграции для базы данных PostgreSQL. В main.go содержится основная логика программы, в requests.go реализация API на добавление, редактирование, удаление и выборку запросов. И есть Dockerfile.

Структура API-сервераСтруктура API-сервера

Остановимся подробнее на содержимом main.go.

Вначале импортируем нужные зависимости. В первую очередь, это migrate для автоматического осуществления миграций, database/sql для работы с базами данных, go-env для работы с переменными окружения, web-фреймворк Gorilla и AMQP для работы с RabbitMQ:

package mainimport (    "encoding/json"    "os"    "github.com/golang-migrate/migrate/v4"    "github.com/golang-migrate/migrate/v4/database/postgres"    _ "github.com/golang-migrate/migrate/v4/source/file"    "database/sql"    env "github.com/Netflix/go-env"    _ "github.com/lib/pq"    "log"    "net/http"    "github.com/gorilla/handlers"    "github.com/gorilla/mux"    "github.com/streadway/amqp")

Далее идут environment, которые мы будем использовать. PGSQL_URI и RABBIT_URI нужны для того, чтобы подключиться к PostgreSQL и RabbitMQ соответственно, LISTEN номер порта, на котором необходимо слушать входящие запросы:

type environment struct {    PgsqlURI  string `env:"PGSQL_URI"`    Listen    string `env:"LISTEN"`    RabbitURI string `env:"RABBIT_URI"`}

Далее следует функция main, которая занимается инициализацией. Сначала происходит чтение environment-переменных, подключение к базе данных PostgreSQL и запуск миграций:

func main() {var err error// Getting configurationlog.Printf("INFO: Getting environment variables\n")cnf := environment{}_, err = env.UnmarshalFromEnviron(&cnf)if err != nil {    log.Fatal(err)}// Connecting to databaselog.Printf("INFO: Connecting to database")db, err = sql.Open("postgres", cnf.PgsqlURI)if err != nil {    log.Fatalf("Can't connect to postgresql: %v", err)}// Running migrationsdriver, err := postgres.WithInstance(db, &postgres.Config{})if err != nil {    log.Fatalf("Can't get postgres driver: %v", err)}m, err := migrate.NewWithDatabaseInstance("file://./migrations", "postgres", driver)if err != nil {    log.Fatalf("Can't get migration object: %v", err)}m.Up()

Затем следует подключение к RabbitMQ и инициализация работы с ним:

// Initialising rabbit mq// Initing rabbitmqconn, err := amqp.Dial(cnf.RabbitURI)if err != nil {    log.Fatalf("Can't connect to rabbitmq")}defer conn.Close()ch, err = conn.Channel()if err != nil {    log.Fatalf("Can't open channel")}defer ch.Close()err = initRabbit()if err != nil {    log.Fatalf("Can't create rabbitmq queues: %s\n", err)}

И в завершение запускается web-сервер. При этом каждому из возможных API-запросов сопоставляется функция обработки, описанная в отдельном файле requests.go:

// Setting handlers for querylog.Printf("INFO: Starting listening on %s\n", cnf.Listen)router := mux.NewRouter().StrictSlash(true)// PROJECTSrouter.HandleFunc("/requests", authMiddleware(getRequests)).Methods("GET")router.HandleFunc("/requests", authMiddleware(addRequest)).Methods("POST")router.HandleFunc("/requests/{name}", authMiddleware(getRequest)).Methods("GET")router.HandleFunc("/requests/{name}", authMiddleware(updRequest)).Methods("PUT")router.HandleFunc("/requests/{name}", authMiddleware(delRequest)).Methods("DELETE")http.ListenAndServe(cnf.Listen, handlers.LoggingHandler(os.Stdout, router))

Далее следует аутентификация в сильно упрощенном варианте, так как на стадии MVP этого достаточно. Разумеется, при разработке Enterprise-решений указание токенов и прочих переменных в явном виде неприемлемо:

func authMiddleware(next http.HandlerFunc) http.HandlerFunc {    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {        tokenString := r.Header.Get("X-API-KEY")        if tokenString != "804b95f13b714ee9912b19861faf3d25" {            w.WriteHeader(http.StatusUnauthorized)            w.Write([]byte("Missing Authorization Header\n"))            return        }        next(w, r)    })}

Переходим к инициализации RabbitMQ. Тут мы будем использовать два Exchange и три очереди.

Первый Exchange VideoParserExchange. К нему подключены две очереди:

  • VideoParserWorkerQueue это основная очередь, которую будут слушать обработчики (на иллюстрации для примера приведен один обработчик Worker-0).

  • VideoParserArchiveQueue архивная очередь, в которую дублируются сообщения на случай возникновения ошибок. Вместо нее можно использовать другие средства бэкапирования, например хранилище S3.

У VideoParserExchange тип fanout, это значит, что все сообщения из него будут отправляться во все подключенные очереди одновременно.

Второй Exchange VideoParserRetryExchange, к нему подключена очередь VideoParserWorkerRetryQueue. К ней не подключены обработчики.

Архитектура очередей сообщенийАрхитектура очередей сообщений

Цель такого решения отложить попытки отправки сообщений на вышедшие из строя Worker до момента, когда они с большей долей вероятности смогут вернуться к обработке.

Например, если во время обработки сообщения из основной очереди обработчик по какой-то причине отключится и не обработает сообщение, то оно отправится в VideoParserRetryExchange. Этот переход настроен при помощи параметра x-dead-letter-exchange.

Далее VideoParserRetryExchange отправит сообщение в очередь VideoParserWorkerRetryQueue. В ней при помощи параметра x-message-ttl ограничено время хранения сообщения. Также при помощи параметра x-dead-letter-exchange мы указываем, что по прошествии таймаута сообщение должно вернуться в VideoParserExchange для последующей обработки.

Алгоритм работы очередей сообщенийАлгоритм работы очередей сообщений

Вся эта логика описана в функции initRabbit. Сначала мы объявляем два Exchange:

func initRabbit() error {    err := ch.ExchangeDeclare(        "VideoParserExchange", // name        "fanout",              // type        true,                  // durable        false,                 // auto delete        false,                 // internal        false,                 // no wait        nil,                   // arguments    )    if err != nil {        return err    }    err = ch.ExchangeDeclare(        "VideoParserRetryExchange", // name        "fanout",                   // type        true,                       // durable        false,                      // auto delete        false,                      // internal        false,                      // no wait        nil,                        // arguments    )    if err != nil {        return err    }

Далее инициализируются три очереди:

args := amqp.Table{"x-dead-letter-exchange": "VideoParserRetryExchange"}    queue, err = ch.QueueDeclare(        "VideoParserWorkerQueue", // name        true,                     // durable - flush to disk        false,                    // delete when unused        false,                    // exclusive - only accessible by the connection that declares        false,                    // no-wait - the queue will assume to be declared on the server        args,                     // arguments -    )    if err != nil {        return err    }    args = amqp.Table{"x-dead-letter-exchange": "VideoParserExchange", "x-message-ttl": 60000}    queue, err = ch.QueueDeclare(        "VideoParserWorkerRetryQueue", // name        true,                          // durable - flush to disk        false,                         // delete when unused        false,                         // exclusive - only accessible by the connection that declares        false,                         // no-wait - the queue will assume to be declared on the server        args,                          // arguments -    )    if err != nil {        return err    }    queue, err = ch.QueueDeclare(        "VideoParserArchiveQueue", // name        true,                      // durable - flush to disk        false,                     // delete when unused        false,                     // exclusive - only accessible by the connection that declares        false,                     // no-wait - the queue will assume to be declared on the server        nil,                       // arguments -    )    if err != nil {        return err    }

И далее очереди связываются с соответствующими Exchange: VideoParserExchange с очередями VideoParserWorkerQueue и VideoParserArchiveQueue, а VideoParserRetryExchange с очередью VideoParserWorkerRetryQueue:

err = ch.QueueBind("VideoParserWorkerQueue", "*", "VideoParserExchange", false, nil)    if err != nil {        return err    }    err = ch.QueueBind("VideoParserArchiveQueue", "*", "VideoParserExchange", false, nil)    if err != nil {        return err    }    err = ch.QueueBind("VideoParserWorkerRetryQueue", "*", "VideoParserRetryExchange", false, nil)    if err != nil {        return err    }    return nil}

Переходим к файлам миграций БД. Они находятся в отдельной папке migrations:

Devices_up.sql предназначен для создания таблицы requests. В ней содержатся следующие поля:

  • id уникальный идентификатор запроса;

  • name уникальное имя, которое мы будем передавать в API при создании нового запроса и в дальнейшем использовать его для поиска нужного запроса;

  • description описание запроса;

  • video_url ссылка на исходное видео на YouTube, в котором необходимо распарсить текст;

  • text_url ссылка на место хранения результирующего текстового файла в S3;

  • processed логический признак того, что обработка запроса успешно завершена;

  • archived логический признак того, что запись таблицы архивирована. Будем использовать вместо физического удаления для сохранения истории;

  • created_at, updated_at временные метки для сохранения времени создания и последнего редактирования, соответственно.

Итак, создаем таблицу requests:

CREATE TABLE IF NOT EXISTS requests (    id SERIAL,    name VARCHAR(256),    description VARCHAR(2048),    video_url VARCHAR(64),    text_url VARCHAR(64),    processed BOOL DEFAULT FALSE,    archived BOOL DEFAULT FALSE,    created_at TIMESTAMP DEFAULT now(),    updated_at TIMESTAMP DEFAULT null,    UNIQUE(name));

В devices_down.sql описывается удаление таблицы requests:

DROP TABLE requests;

Переходим к файлу requests.go. В нем содержатся функции, которые обрабатывают запросы:

  • addRequest для добавления запроса;

  • updRequest для редактирования запроса;

  • delRequest для удаления запроса;

  • getRequest для получения запроса по имени;

  • getRequests для получения всех запросов.

Все функции довольно простые, в них выполняется проверка входных данных и отправка SQL-запроса в PostgreSQL. Поэтому приведем только фрагмент кода основной функции addRequest. Остальные функции можно посмотреть по ссылке выше.

Здесь происходит попытка отправить сообщение в VideoParserExchange, вывод сообщения в случае ошибки и добавление новой записи в таблицу requests, рассмотренную выше:

func addRequest(w http.ResponseWriter, r *http.Request) {    // Parsing event    req := postRequestRequest{}    err := json.NewDecoder(r.Body).Decode(&req)    if err != nil {        log.Printf("WARNING: Can't parse incoming request: %s\n", err)        returnResponse(400, "Can't parse json", nil, w)        return    }    request := Request{}    if req.Name == nil {        returnResponse(400, "name can't be null", nil, w)        return    }    request.Name = *req.Name    if req.Description != nil {        request.Description = *req.Description    }    if req.Processed != nil {        request.Processed = *req.Processed    }    if req.VideoURL != nil {        request.VideoURL = *req.VideoURL    }    if req.TextURL != nil {        request.TextURL = *req.TextURL    }    // Publishing data to rabbitmq    msg, err := json.Marshal(request)    if err != nil {        log.Printf("ERROR: Marshaling request: %s\n", err)        returnResponse(500, "Can't marshal request ", nil, w)        return    }    err = ch.Publish(        "VideoParserExchange", // exchange        "",                    // routing key        false,                 // mandatory - could return an error if there are no consumers or queue        false,                 // immediate        amqp.Publishing{            DeliveryMode: amqp.Persistent,            ContentType:  "application/json",            Body:         msg,        })    if err != nil {        log.Printf("ERROR: Publishing to rabbit: %s\n", err)        returnResponse(500, "Can't publish to rabbit ", nil, w)        return    }    stmt := `INSERT INTO requests (name, description, processed, video_url, text_url) VALUES ($1, $2, $3, $4, $5) RETURNING id`    err = db.QueryRow(stmt, &request.Name, &request.Description, &request.Processed, &request.VideoURL, &request.TextURL).Scan(&request.ID)    if err != nil {        log.Printf("ERROR: Adding new request to database: %s\n", err)        returnResponse(500, "Can't add new request ", nil, w)        return    }    returnResponse(200, "Successfully added new request", nil, w)}

В завершение рассмотрим Dockerfile, с помощью которого можно собрать приложение. Здесь используется образ golang-alpine, выполняется статическая компиляция, затем берется чистый alpine, куда переносится приложение со всеми миграциями и необходимыми файлами:

FROM golang:1.15-alpine AS build# Installing requirementsRUN apk add --update git && \    rm -rf /tmp/* /var/tmp/* /var/cache/apk/* /var/cache/distfiles/*# Creating workdir and copying dependenciesWORKDIR /go/src/appCOPY . .# Installing dependenciesRUN go getENV CGO_ENABLED=0RUN go build -o api main.go requests.goFROM alpine:3.9.6RUN echo "http://dl-cdn.alpinelinux.org/alpine/edge/testing/" >> /etc/apk/repositories && \    apk add --update bash && \    rm -rf /tmp/* /var/tmp/* /var/cache/apk/* /var/cache/distfiles/*WORKDIR /appCOPY --from=build /go/src/app/api /app/apiCOPY ./migrations/ /app/migrations/CMD ["/app/api"]

Создание БД PostgreSQL в облаке MCS

Базу данных для хранения статуса обработки запросов на конвертацию видео будем создавать из консоли управления облаком MCS. Для этого нужно выбрать пункт меню Базы данных и добавить БД PostgreSQL:

На первом шаге определяется конфигурация. Выберем последнюю версию PostgreSQL и тип конфигурации Single: для среды Dev нам достаточно единичного инстанса:

На следующем шаге указываем имя инстанса БД и выбираем конфигурацию виртуальной машины. Нам достаточно 1 CPU и 2 ГБ памяти. Для зоны доступности оставляем автоматический выбор:

В качестве диска выберем SSD размером 20 ГБ. Сеть можно создать отдельную, мы возьмем текущую. Внешний IP назначать не будем: база будет во внутренней сети. В настройках Firewall при необходимости можно указать ограничения на доступ, нам пока они не нужны все разрешаем. Создание реплики нам также не нужно. Ключ для доступа по SSH создаем свой. И устанавливаем периодичность резервного копирования раз в сутки:

На следующем шаге указываем имя БД, имя пользователя и генерируем пароль:

Далее запускается процесс создания инстанса, который займет некоторое время. После успешного создания параметры БД будут выведены на экран, в том числе внутренний IP-адрес сети, который впоследствии нам понадобится:

Установка RabbitMQ через Helm в Kubernetes

Для установки RabbitMQ воспользуемся Helm-чартом bitnami/rabbitmq. Достоинство чартов в том, что не нужно устанавливать по отдельности все необходимые сервису ресурсы: можно установить их одновременно в рамках общего релиза. А при изменениях в любом из ресурсов можно вынести новый релиз, в котором все обновления будут собраны воедино.

Создадим папку helm, добавим в нее репозиторий bitnami и найдем нужный нам Helm Chart bitnami/rabbitmq:

mkdir helmcd helmhelm repo add bitnami https://charts.bitnami.com/bitnamihelm search repo bitnami

Теперь мы нашли нужный чарт:

Копируем его имя, загружаем и распаковываем:

helm pull bitnami/rabbitmqtar zxv

Переходим в папку rabbitmq/templates. Здесь находятся все ресурсы, которые нужно будет создать в Kubernetes для корректной работы RabbitMQ: конфигурация, Ingress, сертификаты, сетевые политики, сервисные аккаунты, секреты, правила Prometheus и так далее. И Helm позволяет это сделать единой командой, без установки каждого файла по отдельности:

Возвращаемся в родительскую папку helm, чтобы посмотреть возможность настройки файла values.yaml. Скопируем содержимое rabbitmq/values.yaml в наш собственный файл values.dev.yaml и откроем его для редактирования:

cp rabbitmq/values.yaml ./values.dev.yamlvi values.dev.yaml

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

В данном файле содержится очень много параметров, которые можно настраивать под нужды своего проекта: режим debug, плагины RabbitMQ для подключения, необходимость включения TLS и memoryHighWatermark, аутентификация через LDAP, количество реплик, nodeSelector для создания RabbitMQ на нодах с определенной меткой, требования к CPU и памяти и многое другое.

Нас в первую очередь интересуют настройки Ingress. Находим секцию ingress, устанавливаем в enabled значение true и прописываем в поле hostname имя rabbitmq.stage.kis.im. Эта настройка необходима для внешнего доступа к RabbitMQ, без нее он будет доступен только внутри кластера. Kis.im это мой существующий домен:

Далее переходим непосредственно к развертыванию RabbitMQ. Создаем новый namespace stage и применяем к нему созданный файл values.stage.yaml (изменив dev на stage в названии для единообразия):

kubectl create ns stagehelm instal -n stage rabbitmq -f values.dev.yamlmv values.dev.yaml values. stage. yamlhelm install -n stage rabbitmq -f values.stage.yanl ./rabbitmq/

Вот, что получилось, когда Namespace создан:

После успешной установки можно посмотреть список подов и сервисов в Namespace stage rabbitmq успешно добавлен. Он имеет кластерный IP 10.254.178.84. Но так как наше приложение будет находиться в том же Namespace, мы сможем обращаться к нему по имени rabbitmq.

Еще один сервис rabbitmq-headless не имеет кластерного IP. Он используется при добавлении нескольких RabbitMQ для их автообнаружения и объединения в кластер с помощью kubectl -n stage get svc:

С помощью Helm можно получить дополнительные сведения о релизе: время последнего обновления, статус, название чарта, версию приложения, используем helm -n stage list:

Кроме этого, можно посмотреть Persistent Volumes, выделенные RabbitMQ, с помощью kubectl get pv. В нашем случае Volume имеет размер 8 ГБ и Storage Class csi-hdd:

При необходимости нужный Storage Class можно было прописать непосредственно в YAML-файле:

Список всех возможных классов можно вывести командой kubectl get storageclasses:

Здесь важен параметр RECLAIMPOLICY: в зависимости от его значения при удалении запроса на данный ресурс (PVC, Persistent Volume Claim) сам Persistent Volume будет удален или сохранен для будущего использования.

Осталось обеспечить внешний доступ к нашему сервису. Проверяем добавление ресурса Ingress для RabbitMQ командой kubectl -n stage get ingress:

Затем получаем внешний адрес Ingress Controller с помощью kubectl -n ingress-nginx get svc:

В Cloudflare прописываем DNS для RabbitMQ, связывая его внешний Hostname и IP-адрес Ingress Controller:

После этого RabbitMQ становится доступен по адресу rabbitmq.stage.kis.im:

Имя пользователя user. Пароль сохранился в переменные окружения после развертывания RabbitMQ, его можно получить с помощью команды env | grep RABBITMQ_PASSWORD.

Развертывание и предварительная проверка API

RabbitMQ мы развернули с помощью Helm. Для нашего приложения с API в последующем мы также создадим собственный Helm Chart, но пока посмотрим, как выполняется развертывание приложения вручную на основе YAML-файлов.

Образ приложения мною уже создан при помощи Dockerfile, который мы рассматривали ранее.

Далее определим необходимые ресурсы. Очевидно, что локальное хранилище приложению не нужно, так как приложение уже взаимодействует с PostgreSQL и RabbitMQ, размещенными в облаке. Поэтому Persistent Volumes создавать не будем. Основные ресурсы, которые нам потребуются, описывают файлы deployment.yaml, ingress.yaml и svc.yaml:

Начнем с deployment.yaml. Здесь описывается ресурс Deployment. Тут мы описываем шаблон пода, который будем запускать. Указываем, что будем запускать контейнер с именем api, образ vozerov/video-api:v1 (этот образ я уже залил на hub.docker.com).

Далее в блоке env указываем переменные, используемые в нашем API:

  • В переменной RABBIT_URI вводим сформированные при создании RabbitMQ имя и пароль пользователя, название сервиса rabbitmq и номер порта 5672 (имя сервиса можно проверить с помощью команды kubectl -n stage get svc).

  • В переменной LISTEN устанавливаем номер порта 8080.

  • В переменной PGSQL_URI заполняем сформированные при создании PostgreSQL имя и пароль пользователя, внутренний адрес БД 10.0.0.10, номер порта 5432 и название БД vc-dev. Все параметры БД можно найти в консоли управления облаком.

deployment.yaml: описываем шаблон подаdeployment.yaml: описываем шаблон пода

По хорошему, пароли нельзя хранить тут в открытом виде. Но как я уже говорил ранее, это MPV, и для упрощения мы сейчас сделаем так.

Применяем сформированный файл:

kubectl -n stage apply -f deployment.yamlkubectl -n stage get deploy

Video-api создан:

И проверяем создание нового пода с помощью kubectl -n stage get pods:

После успешного применения deployment.yaml можно зайти в RabbitMQ и убедиться в создании всех необходимых очередей и Exchange.

Созданные очередиСозданные очередиСозданные ExchangeСозданные Exchange

Следующий ресурс, который нам необходимо добавить для доступа к сервису извне это Service. Он описывается в файле svc.yaml. Мы указываем, что приложение video-api будет принимать входящие соединения на порт 8080 и пробрасывать их в контейнер на порт 8080. Применяем svc.yaml стандартной командой kubectl apply -n stage -f svc.yaml:

Последний ресурс, который необходим для нашего сервиса Ingress. В файле ingress.yaml мы указываем правила, по которым нужно направлять запросы к сервису. Заполняем внешнее имя api.stage.kis.im и в блоке path указываем, что все корневые запросы направляем на сервис video-api-svc, созданный на прошлом шаге. Применяем сформированный файл kubectl apply -n stage -f Ingress.yaml:

Убеждаемся в добавлении Ingress для нашего сервиса с помощью kubectl -n stage get ingress:

Затем добавляем запись в DNS аналогично тому, как делали это ранее для RabbitMQ:

Теперь можно провести первое тестирование API, используя отправку запросов через curl. В заголовках всех запросов нужно передавать X-API-KEY со значением токена из кода программы main.go.

Для начала с помощью метода GET получим список всех записей requests:

curl -H 'X-API-KEY: 804b95f13b714ee9912b19861faf3d25' -s http://api.stage.kis.im/requests | jq .

На текущий момент он пуст:

Отправим новый запрос на конвертацию видео, используя метод POST. В имени запроса (name) укажем test1. В ссылке на видео (video_url) введем тестовое значение, так как у нас пока нет обработчиков Worker:

curl -X POST -d '{"name": "test1", "video_url": "https://google.com" }' -H 'X-API-KEY: 804b95f13b714ee9912b19861faf3d25' -s http://api.stage.kis.im/requests | jq .

Запрос успешно создан:

Далее можно получить запрос по имени test1 и убедиться в наличии всех переданных при создании параметров:

curl -H 'X-API-KEY: 804b95f13b714ee9912b19861faf3d25' -s http://api.stage.kis.im/requests/request1 | jq .

Запрос создан, все параметры верные:

В очереди RabbitMQ сообщение также будет добавлено. Заходим в очередь:

Видим сообщение:

Осталось зайти в базу PostgreSQL и проверить ее структуру. Внешний доступ мы не настраивали поэтому можно подключиться, например, через psql из отдельно запущенного пода. Мы видим наличие таблицы requests, а в ней добавленный нами запрос:

Таким образом, проверка работы API пройдена.

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

Новым пользователям платформы Mail.ru Cloud Solutions доступны 3000 бонусов после полной верификации аккаунта. Вы сможете повторить сценарий из статьи или попробовать другие облачные сервисы.

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

Что еще почитать по теме:

  1. Как развернуть кластер Kubernetes на платформе MCS.

  2. Запускаем etcd-кластер для Kubernetes.

  3. Как устроен Kubernetes aaS на платформе Mail.ru Cloud Solutions.

Подробнее..

Перевод Сеть контейнеров это не сложно

26.05.2021 22:23:37 | Автор: admin

Работа с контейнерами многим кажется волшебством, пришло время разобраться как работает сеть контейнеров. Мы покажем на примерах, что это совсем не сложно. Помните, что контейнеры - всего лишь изолированные процессы Linux.

В этой статье мы ответим на следующие вопросы:

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

  • Как превратить контейнеры в дружелюбных соседей, не дать им мешать друг другу и научить хорошо общаться?

  • Как настроить сетевой доступ из контейнера во внешний мир (например, в Интернет)?

  • Как получить доступ к контейнерам, работающим на сервере, из внешнего мира (публикация портов)?

Отвечая на эти вопросы, мы настроим сеть контейнеров с нуля, используя стандартные инструменты Linux. В результате станет очевидно, что сеть контейнеров - это не что иное, как простая комбинация хорошо известных возможностей Linux:

  • Network namespaces

  • Virtual Ethernet devices (veth)

  • Virtual network switches (bridge)

  • IP маршрутизация и преобразование сетевых адресов (NAT)

Нам потребуется немного сетевой магии и никакого кода ...

С чего начать?

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

Создадим виртуальную машину с помощью Vagrant и подключимся к ней по SSH:

$ vagrant init centos/8$ vagrant up$ vagrant ssh[vagrant@localhost ~]$ uname -aLinux localhost.localdomain 4.18.0-147.3.1.el8_1.x86_64

Мы не будем использовать какое-либо популярное решение для контейнеризации (например, docker или podman). Вместо этого мы сосредоточимся на основных концепциях и воспользуемся минимальным набором инструментов для достижения наших учебных целей.

Изоляция контейнеров с помощью Network namespaces

Что составляет сетевой стек Linux? Ну, очевидно, набор сетевых устройств. Что еще? Набор правил маршрутизации. И не забываем про настройку netfilter, создадим необходимые правила iptables.

Напишем небольшой скрипт inspect-net-stack.sh:

#!/usr/bin/env bashecho "> Network devices"ip linkecho -e "\n> Route table"ip routeecho -e "\n> Iptables rules"iptables --list-rules

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

$ sudo iptables -N ROOT_NS

Запускаем скрипт:

$ sudo ./inspect-net-stack.sh> Network devices1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:002: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000    link/ether 52:54:00:e3:27:77 brd ff:ff:ff:ff:ff:ff> Route tabledefault via 10.0.2.2 dev eth0 proto dhcp metric 10010.0.2.0/24 dev eth0 proto kernel scope link src 10.0.2.15 metric 100> Iptables rules-P INPUT ACCEPT-P FORWARD ACCEPT-P OUTPUT ACCEPT-N ROOT_NS

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

Мы уже упоминали об одном из Linux namespaces, используемых для изоляции контейнеров, которое называет сетевое пространство имён (Network namespace). Если заглянуть в man ip-netns, то мы прочтём, что Network namespace логически является копией сетевого стека со своими собственными маршрутами, правилами брандмауэра и сетевыми устройствами. Мы не будем затрагивать другие Linux namespaces в этой статье и ограничимся только областью видимости сетевого стека.

Для создания Network namespace нам достаточно утилиты ip, которая входим в популярный пакет iproute2. Создадим новое сетевое пространство имён:

$ sudo ip netns add netns0$ ip netnsnetns0

Новое сетевое пространство имён создано, но как начать его использовать? Воспользуемся командой Linux под названием nsenter. Она осуществляет вход в одно или несколько указанных пространств имен, а затем выполняет в нём указанную программу:

$ sudo nsenter --net=/var/run/netns/netns0 bash# The newly created bash process lives in netns0$ sudo ./inspect-net-stack.sh> Network devices1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00> Route table> Iptables rules-P INPUT ACCEPT-P FORWARD ACCEPT-P OUTPUT ACCEPT

Приведённый выше пример показывает, что процесс bash, работающий внутри пространства имён netns0, видит совершенно другой сетевой стек. Отсутствуют правила маршрутизации, и правила iptables, есть только один loopback interface. Все идет по плану...

Подключаем контейнер к хосту через virtual Ethernet devices (veth)

Выделенный сетевой стек будет бесполезен, если к нему отсутствует доступ. К счастью, Linux предоставляет подходящее средство для этого - virtual Ethernet devices (veth)! Согласно man veth, veth-device - это виртуальные устройства Ethernet. Они работают как туннели между сетевыми пространствами имён для создания моста к физическому сетевому устройству в другом пространстве имён, а также могут использоваться как автономные сетевые устройства.

Виртуальные Ethernet устройства всегда работают парами. Создадим их прямо сейчас:

$ sudo ip link add veth0 type veth peer name ceth0

С помощью этой единственной команды мы только что создали пару взаимосвязанных виртуальных Ethernet устройств. Имена veth0 и ceth0 были выбраны произвольно:

$ ip link1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:002: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000    link/ether 52:54:00:e3:27:77 brd ff:ff:ff:ff:ff:ff5: ceth0@veth0: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000    link/ether 66:2d:24:e3:49:3f brd ff:ff:ff:ff:ff:ff6: veth0@ceth0: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000    link/ether 96:e8:de:1d:22:e0 brd ff:ff:ff:ff:ff:ff

И veth0, и ceth0 после создания находятся в сетевом стеке хоста (также называемом Root Network namespace). Чтобы связать корневое пространство имён с пространством имён netns0, нам нужно сохранить одно из устройств в корневом пространстве имён и переместить другое в netns0:

$ sudo ip link set ceth0 netns netns0# List all the devices to make sure one of them disappeared from the root stack$ ip link1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:002: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000    link/ether 52:54:00:e3:27:77 brd ff:ff:ff:ff:ff:ff6: veth0@if5: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000    link/ether 96:e8:de:1d:22:e0 brd ff:ff:ff:ff:ff:ff link-netns netns0

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

$ sudo ip link set veth0 up$ sudo ip addr add 172.18.0.11/16 dev veth0

Продолжим сnetns0:

$ sudo nsenter --net=/var/run/netns/netns0$ ip link set lo up  # whoops$ ip link set ceth0 up$ ip addr add 172.18.0.10/16 dev ceth0$ ip link1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:005: ceth0@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000    link/ether 66:2d:24:e3:49:3f brd ff:ff:ff:ff:ff:ff link-netnsid 0

Проверяем подключение:

# From netns0, ping root's veth0$ ping -c 2 172.18.0.11PING 172.18.0.11 (172.18.0.11) 56(84) bytes of data.64 bytes from 172.18.0.11: icmp_seq=1 ttl=64 time=0.038 ms64 bytes from 172.18.0.11: icmp_seq=2 ttl=64 time=0.040 ms--- 172.18.0.11 ping statistics ---2 packets transmitted, 2 received, 0% packet loss, time 58msrtt min/avg/max/mdev = 0.038/0.039/0.040/0.001 ms# Leave netns0$ exit# From root namespace, ping ceth0$ ping -c 2 172.18.0.10PING 172.18.0.10 (172.18.0.10) 56(84) bytes of data.64 bytes from 172.18.0.10: icmp_seq=1 ttl=64 time=0.073 ms64 bytes from 172.18.0.10: icmp_seq=2 ttl=64 time=0.046 ms--- 172.18.0.10 ping statistics ---2 packets transmitted, 2 received, 0% packet loss, time 3msrtt min/avg/max/mdev = 0.046/0.059/0.073/0.015 ms

Обратите внимание, если мы попытаемся проверить доступность любых других адресов из пространства имен netns0, у нас ничего не получится:

# Inside root namespace$ ip addr show dev eth02: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000    link/ether 52:54:00:e3:27:77 brd ff:ff:ff:ff:ff:ff    inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic noprefixroute eth0       valid_lft 84057sec preferred_lft 84057sec    inet6 fe80::5054:ff:fee3:2777/64 scope link       valid_lft forever preferred_lft forever# Remember this 10.0.2.15$ sudo nsenter --net=/var/run/netns/netns0# Try host's eth0$ ping 10.0.2.15connect: Network is unreachable# Try something from the Internet$ ping 8.8.8.8connect: Network is unreachable

Для таких пакетов в таблице маршрутизации netns0 просто нет маршрута. В настоящий момент существует единственный маршрут до сети 172.18.0.0/16:

# From netns0 namespace:$ ip route172.18.0.0/16 dev ceth0 proto kernel scope link src 172.18.0.10

В Linux есть несколько способов заполнения таблицы маршрутизации. Один из них - извлечение маршрутов из подключенных напрямую сетевых интерфейсов. Помните, что таблица маршрутизации в netns0 была пустой сразу после создания пространства имен. Но затем мы добавили туда устройство ceth0 и присвоили ему IP-адрес 172.18.0.10/16. Поскольку мы использовали не простой IP-адрес, а комбинацию адреса и сетевой маски, сетевому стеку удалось извлечь из него информацию о маршрутизации. Каждый пакет, предназначенный для сети 172.18.0.0/16, будет отправлен через устройство ceth0. Но все остальные пакеты будут отброшены. Точно так же есть новый маршрут в корневом пространстве имен:

# From root namespace:$ ip route# ... omitted lines ...172.18.0.0/16 dev veth0 proto kernel scope link src 172.18.0.11

На этом этапе мы ответили на первый вопрос. Теперь мы знаем, как изолировать, виртуализировать и подключать сетевые стеки Linux.

Объединение контейнеров с помощью virtual network switch (bridge)

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

# From root namespace$ sudo ip netns add netns1$ sudo ip link add veth1 type veth peer name ceth1$ sudo ip link set ceth1 netns netns1$ sudo ip link set veth1 up$ sudo ip addr add 172.18.0.21/16 dev veth1$ sudo nsenter --net=/var/run/netns/netns1$ ip link set lo up$ ip link set ceth1 up$ ip addr add 172.18.0.20/16 dev ceth1

Проверим доступность:

# From netns1 we cannot reach the root namespace!$ ping -c 2 172.18.0.21PING 172.18.0.21 (172.18.0.21) 56(84) bytes of data.From 172.18.0.20 icmp_seq=1 Destination Host UnreachableFrom 172.18.0.20 icmp_seq=2 Destination Host Unreachable--- 172.18.0.21 ping statistics ---2 packets transmitted, 0 received, +2 errors, 100% packet loss, time 55mspipe 2# But there is a route!$ ip route172.18.0.0/16 dev ceth1 proto kernel scope link src 172.18.0.20# Leaving netns1$ exit# From root namespace we cannot reach the netns1$ ping -c 2 172.18.0.20PING 172.18.0.20 (172.18.0.20) 56(84) bytes of data.From 172.18.0.11 icmp_seq=1 Destination Host UnreachableFrom 172.18.0.11 icmp_seq=2 Destination Host Unreachable--- 172.18.0.20 ping statistics ---2 packets transmitted, 0 received, +2 errors, 100% packet loss, time 23mspipe 2# From netns0 we CAN reach veth1$ sudo nsenter --net=/var/run/netns/netns0$ ping -c 2 172.18.0.21PING 172.18.0.21 (172.18.0.21) 56(84) bytes of data.64 bytes from 172.18.0.21: icmp_seq=1 ttl=64 time=0.037 ms64 bytes from 172.18.0.21: icmp_seq=2 ttl=64 time=0.046 ms--- 172.18.0.21 ping statistics ---2 packets transmitted, 2 received, 0% packet loss, time 33msrtt min/avg/max/mdev = 0.037/0.041/0.046/0.007 ms# But we still cannot reach netns1$ ping -c 2 172.18.0.20PING 172.18.0.20 (172.18.0.20) 56(84) bytes of data.From 172.18.0.10 icmp_seq=1 Destination Host UnreachableFrom 172.18.0.10 icmp_seq=2 Destination Host Unreachable--- 172.18.0.20 ping statistics ---2 packets transmitted, 0 received, +2 errors, 100% packet loss, time 63mspipe 2

Что-то пошло не так... По какой-то причине мы не можем подключиться из netns1 к root namespace. А из root namespace мы не можем подключиться к netns1. Однако, поскольку оба контейнера находятся в одной IP-сети 172.18.0.0/16, есть доступ к veth1 хоста из контейнера netns0. Интересно...

Возможно, мы столкнулись с конфликтом маршрутов. Давайте проверим таблицу маршрутизации в root namespace:

$ ip route# ... omitted lines ...172.18.0.0/16 dev veth0 proto kernel scope link src 172.18.0.11172.18.0.0/16 dev veth1 proto kernel scope link src 172.18.0.21

После добавления второй пары veth в таблице маршрутизации root namespace появился новый маршрут 172.18.0.0/16 dev veth1 proto kernel scope link src 172.18.0.21, но маршрут до этой подсети уже существовал! Когда второй контейнер пытается проверить связь с устройством veth1, используется первый маршрут и мы видим ошибку подключения. Если бы мы удалили первый маршрут sudo ip route delete 172.18.0.0/16 dev veth0 proto kernel scope link src 172.18.0.11 и перепроверили подключение, то увидели бы обратную ситуацию, то есть подключение netns1 будет восстановлено, но netns0 останется в подвешенном состоянии.

Пожалуй, если бы мы выбрали другую подсеть для netns1, все бы заработало. Однако наличие нескольких контейнеров в одной подсети является допустимым вариантом применения. Попробуем разобраться ...

Рассмотрим Linux Bridge - еще один виртуализированный сетевой объект! Linux Bridge ведёт себя как коммутатор. Он пересылает пакеты между подключенными к нему интерфейсами. А поскольку это коммутатор, то он работает на уровне L2 (то есть Ethernet).

Чтобы предыдущие этапы нашего эксперимента в дальнейшем не вносили путаницы, удалим существующие сетевые пространства имён:

$ sudo ip netns delete netns0$ sudo ip netns delete netns1# But if you still have some leftovers...$ sudo ip link delete veth0$ sudo ip link delete ceth0$ sudo ip link delete veth1$ sudo ip link delete ceth1

Заново создаём два контейнера. Обратите внимание, мы не назначаем IP-адреса новым устройствам veth0 и veth1:

$ sudo ip netns add netns0$ sudo ip link add veth0 type veth peer name ceth0$ sudo ip link set veth0 up$ sudo ip link set ceth0 netns netns0$ sudo nsenter --net=/var/run/netns/netns0$ ip link set lo up$ ip link set ceth0 up$ ip addr add 172.18.0.10/16 dev ceth0$ exit$ sudo ip netns add netns1$ sudo ip link add veth1 type veth peer name ceth1$ sudo ip link set veth1 up$ sudo ip link set ceth1 netns netns1$ sudo nsenter --net=/var/run/netns/netns1$ ip link set lo up$ ip link set ceth1 up$ ip addr add 172.18.0.20/16 dev ceth1$ exit

Убедимся, что на хосте нет новых маршрутов:

$ ip routedefault via 10.0.2.2 dev eth0 proto dhcp metric 10010.0.2.0/24 dev eth0 proto kernel scope link src 10.0.2.15 metric 100

И, наконец, создадим bridge интерфейс:

$ sudo ip link add br0 type bridge$ sudo ip link set br0 up

Теперь подключим к нему veth0 и veth1:

$ sudo ip link set veth0 master br0$ sudo ip link set veth1 master br0

... и проверим возможность подключения между контейнерами:

$ sudo nsenter --net=/var/run/netns/netns0$ ping -c 2 172.18.0.20PING 172.18.0.20 (172.18.0.20) 56(84) bytes of data.64 bytes from 172.18.0.20: icmp_seq=1 ttl=64 time=0.259 ms64 bytes from 172.18.0.20: icmp_seq=2 ttl=64 time=0.051 ms--- 172.18.0.20 ping statistics ---2 packets transmitted, 2 received, 0% packet loss, time 2msrtt min/avg/max/mdev = 0.051/0.155/0.259/0.104 ms
$ sudo nsenter --net=/var/run/netns/netns1$ ping -c 2 172.18.0.10PING 172.18.0.10 (172.18.0.10) 56(84) bytes of data.64 bytes from 172.18.0.10: icmp_seq=1 ttl=64 time=0.037 ms64 bytes from 172.18.0.10: icmp_seq=2 ttl=64 time=0.089 ms--- 172.18.0.10 ping statistics ---2 packets transmitted, 2 received, 0% packet loss, time 36msrtt min/avg/max/mdev = 0.037/0.063/0.089/0.026 ms

Прекрасно! Все отлично работает. При этом мы даже не настраивали интерфейсы veth0 и veth1. Мы назначили только два IP-адреса интерфейсам ceth0 и ceth1. Но поскольку они оба находятся в одном сегменте Ethernet (подключены к виртуальному коммутатору), существует возможность подключения на уровне L2:

$ sudo nsenter --net=/var/run/netns/netns0$ ip neigh172.18.0.20 dev ceth0 lladdr 6e:9c:ae:02:60:de STALE$ exit$ sudo nsenter --net=/var/run/netns/netns1$ ip neigh172.18.0.10 dev ceth1 lladdr 66:f3:8c:75:09:29 STALE$ exit

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

Настраиваем сетевой доступ из контейнера во внешний мир (IP routing and masquerading)

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

$ sudo nsenter --net=/var/run/netns/netns0$ ping 10.0.2.15  # eth0 addressconnect: Network is unreachable

Интерфейс eth0 не доступен. Всё очевидно, в netns0 отсутствует маршрут для этого подключения:

$ ip route172.18.0.0/16 dev ceth0 proto kernel scope link src 172.18.0.10

Корневое пространство имён также не может взаимодействовать с контейнерами:

# Use exit to leave netns0 first:$ ping -c 2 172.18.0.10PING 172.18.0.10 (172.18.0.10) 56(84) bytes of data.From 213.51.1.123 icmp_seq=1 Destination Net UnreachableFrom 213.51.1.123 icmp_seq=2 Destination Net Unreachable--- 172.18.0.10 ping statistics ---2 packets transmitted, 0 received, +2 errors, 100% packet loss, time 3ms$ ping -c 2 172.18.0.20PING 172.18.0.20 (172.18.0.20) 56(84) bytes of data.From 213.51.1.123 icmp_seq=1 Destination Net UnreachableFrom 213.51.1.123 icmp_seq=2 Destination Net Unreachable--- 172.18.0.20 ping statistics ---2 packets transmitted, 0 received, +2 errors, 100% packet loss, time 3ms

Чтобы установить связь между корневым пространством имён и пространством имён контейнера, нам нужно назначить IP-адрес сетевому интерфейсу моста:

$ sudo ip addr add 172.18.0.1/16 dev br0

Теперь после того, как мы назначили IP-адрес интерфейсу моста, мы получили маршрут в таблице маршрутизации хоста:

$ ip route# ... omitted lines ...172.18.0.0/16 dev br0 proto kernel scope link src 172.18.0.1$ ping -c 2 172.18.0.10PING 172.18.0.10 (172.18.0.10) 56(84) bytes of data.64 bytes from 172.18.0.10: icmp_seq=1 ttl=64 time=0.036 ms64 bytes from 172.18.0.10: icmp_seq=2 ttl=64 time=0.049 ms--- 172.18.0.10 ping statistics ---2 packets transmitted, 2 received, 0% packet loss, time 11msrtt min/avg/max/mdev = 0.036/0.042/0.049/0.009 ms$ ping -c 2 172.18.0.20PING 172.18.0.20 (172.18.0.20) 56(84) bytes of data.64 bytes from 172.18.0.20: icmp_seq=1 ttl=64 time=0.059 ms64 bytes from 172.18.0.20: icmp_seq=2 ttl=64 time=0.056 ms--- 172.18.0.20 ping statistics ---2 packets transmitted, 2 received, 0% packet loss, time 4msrtt min/avg/max/mdev = 0.056/0.057/0.059/0.007 ms

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

$ sudo nsenter --net=/var/run/netns/netns0$ ip route add default via 172.18.0.1$ ping -c 2 10.0.2.15PING 10.0.2.15 (10.0.2.15) 56(84) bytes of data.64 bytes from 10.0.2.15: icmp_seq=1 ttl=64 time=0.036 ms64 bytes from 10.0.2.15: icmp_seq=2 ttl=64 time=0.053 ms--- 10.0.2.15 ping statistics ---2 packets transmitted, 2 received, 0% packet loss, time 14msrtt min/avg/max/mdev = 0.036/0.044/0.053/0.010 ms# And repeat the change for netns1

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

Отлично, нам удалось добиться сетевой связности контейнеров с корневым пространством имён. Теперь давайте попробуем подключить их к внешнему миру. По умолчанию переадресация пакетов (ip packet forwarding), то есть функциональность маршрутизатора в Linux отключена. Нам нужно её включить

# In the root namespacesudo bash -c 'echo 1 > /proc/sys/net/ipv4/ip_forward'

Теперь самое интересное - проверка подключения:

$ sudo nsenter --net=/var/run/netns/netns0$ ping 8.8.8.8# hangs indefinitely long for me...

Всё равно не работает. Мы что-то упустили? Если бы контейнер отправлял пакеты во внешний мир, сервер-получатель не смог бы отправлять пакеты обратно в контейнер, потому что IP-адрес контейнера является частным и правила маршрутизации для этого конкретного IP-адреса известны только в локальной сети. К тому же многие контейнеры в мире имеют один и тот же частный IP-адрес 172.18.0.10. Решение этой проблемы называется преобразованием сетевых адресов (NAT). Принцип работы, следующий - перед отправкой во внешнюю сеть пакеты, отправленные контейнерами, заменяют свои исходные IP-адреса (source IP addesses) на адрес внешнего интерфейса хоста. Хост также будет отслеживать все существующие сопоставления (mapping) и по прибытии будет восстанавливать IP-адреса перед пересылкой пакетов обратно в контейнеры. Звучит сложно, но у меня для вас хорошие новости! Нам нужна всего одна команда, чтобы добиться требуемого результата:

$ sudo iptables -t nat -A POSTROUTING -s 172.18.0.0/16 ! -o br0 -j MASQUERADE

Команда довольно проста. Мы добавляем новое правило в таблицу nat цепочки POSTROUTING с просьбой выполнить MASQUERADE всех исходящих пакетов из сети 172.18.0.0/16, но не через интерфейс моста.

Проверьте подключение:

$ sudo nsenter --net=/var/run/netns/netns0$ ping -c 2 8.8.8.8PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.64 bytes from 8.8.8.8: icmp_seq=1 ttl=61 time=43.2 ms64 bytes from 8.8.8.8: icmp_seq=2 ttl=61 time=36.8 ms--- 8.8.8.8 ping statistics ---2 packets transmitted, 2 received, 0% packet loss, time 2msrtt min/avg/max/mdev = 36.815/40.008/43.202/3.199 ms
$ sudo nsenter --net=/var/run/netns/netns0$ ping -c 2 8.8.8.8PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.64 bytes from 8.8.8.8: icmp_seq=1 ttl=61 time=43.2 ms64 bytes from 8.8.8.8: icmp_seq=2 ttl=61 time=36.8 ms--- 8.8.8.8 ping statistics ---2 packets transmitted, 2 received, 0% packet loss, time 2msrtt min/avg/max/mdev = 36.815/40.008/43.202/3.199 ms

Помните, что политика iptables по умолчанию - ACCEPT для каждой цепочки, она может быть довольно опасной в реальных условиях:

sudo iptables -S-P INPUT ACCEPT-P FORWARD ACCEPT-P OUTPUT ACCEPT

В качестве хорошего примера Docker вместо этого ограничивает все по умолчанию, а затем разрешает только для известных маршрутов:

$ sudo iptables -t filter --list-rules-P INPUT ACCEPT-P FORWARD DROP-P OUTPUT ACCEPT-N DOCKER-N DOCKER-ISOLATION-STAGE-1-N DOCKER-ISOLATION-STAGE-2-N DOCKER-USER-A FORWARD -j DOCKER-USER-A FORWARD -j DOCKER-ISOLATION-STAGE-1-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT-A FORWARD -o docker0 -j DOCKER-A FORWARD -i docker0 ! -o docker0 -j ACCEPT-A FORWARD -i docker0 -o docker0 -j ACCEPT-A DOCKER -d 172.17.0.2/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 5000 -j ACCEPT-A DOCKER-ISOLATION-STAGE-1 -i docker0 ! -o docker0 -j DOCKER-ISOLATION-STAGE-2-A DOCKER-ISOLATION-STAGE-1 -j RETURN-A DOCKER-ISOLATION-STAGE-2 -o docker0 -j DROP-A DOCKER-ISOLATION-STAGE-2 -j RETURN-A DOCKER-USER -j RETURN$ sudo iptables -t nat --list-rules-P PREROUTING ACCEPT-P INPUT ACCEPT-P POSTROUTING ACCEPT-P OUTPUT ACCEPT-N DOCKER-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE-A POSTROUTING -s 172.17.0.2/32 -d 172.17.0.2/32 -p tcp -m tcp --dport 5000 -j MASQUERADE-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER-A DOCKER -i docker0 -j RETURN-A DOCKER ! -i docker0 -p tcp -m tcp --dport 5005 -j DNAT --to-destination 172.17.0.2:5000$ sudo iptables -t mangle --list-rules-P PREROUTING ACCEPT-P INPUT ACCEPT-P FORWARD ACCEPT-P OUTPUT ACCEPT-P POSTROUTING ACCEPT$ sudo iptables -t raw --list-rules-P PREROUTING ACCEPT-P OUTPUT ACCEPT

Настроим сетевой доступ из внешнего мира в контейнеры (port publishing)

Публикация портов контейнеров для некоторых (или всех) интерфейсов хоста - популярная практика. Но что на самом деле означает публикация порта?

Представьте, что у нас есть сервис, работающий внутри контейнера:

$ sudo nsenter --net=/var/run/netns/netns0$ python3 -m http.server --bind 172.18.0.10 5000

Если мы попытаемся отправить HTTP-запрос этому сервису с хоста, все будет работать (ну, есть связь между корневым пространством имён и всеми интерфейсами контейнера, почему бы и нет?):

# From root namespace$ curl 172.18.0.10:5000<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"># ... omitted lines ...

Однако, если бы мы получили доступ к этому серверу из внешнего мира, какой IP-адрес мы бы использовали? Единственный IP-адрес, который мы можем знать, - это адрес внешнего интерфейса хоста eth0:

$ curl 10.0.2.15:5000curl: (7) Failed to connect to 10.0.2.15 port 5000: Connection refused

Таким образом, нам нужно найти способ перенаправить все пакеты, поступающие на порт 5000 интерфейса eth0 хоста, на адрес172.18.0.10:5000. Или, другими словами, нам нужно опубликовать порт 5000 контейнера на интерфейсе eth0 хоста.

# External trafficsudo iptables -t nat -A PREROUTING -d 10.0.2.15 -p tcp -m tcp --dport 5000 -j DNAT --to-destination 172.18.0.10:5000# Local traffic (since it doesn't pass the PREROUTING chain)sudo iptables -t nat -A OUTPUT -d 10.0.2.15 -p tcp -m tcp --dport 5000 -j DNAT --to-destination 172.18.0.10:5000

Кроме того, нам нужно включить iptables intercepting traffic over bridged networks (перехватывать трафик bridged networks):

sudo modprobe br_netfilter

Время проверить!

curl 10.0.2.15:5000<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"># ... omitted lines ...

Разбираемся в работе Docker network drivers

Но что же вам сделать теперь со всеми этими бесполезными знаниями? Например, мы могли бы попытаться разобраться в некоторых сетевых режимах Docker!

Начнем с режима --network host. Попробуйте сравнить вывод следующих команд ip link и sudo docker run -it --rm --network host alpine ip link. Сюрприз, они совпадут! Таким образом host mode Docker просто не использует изоляцию сетевого пространства имён и контейнеры работают в корневом сетевом пространстве имён и совместно используют сетевой стек с хост-системой.

Следующий режим, который нужно проверить, - это --network none. Вывод команды sudo docker run -it --rm --network none alpine ip link показывает только один сетевой интерфейс обратной loopback. Это очень похоже на наши наблюдения за только что созданным сетевым пространством имен. То есть до того момента, когда мы добавляли какие-либо veth устройства.

И последнее, но не менее важное: режим --network bridge (по умолчанию), это именно то, что мы пытались воспроизвести в этой статье.

Сети и rootless контейнеры

Одной из приятных особенностей диспетчера контейнеров podman является его ориентация на rootless контейнеры. Однако, как вы, вероятно, заметили, в этой статье мы использовали много эскалаций sudo и без root-прав настроить сеть невозможно. При настройке сетей rootful контейнеров Podman очень близок к Docker. Но когда дело доходит до rootless контейнеров, Podman полагается на проект slirp4netns:

Начиная с Linux 3.8, непривилегированные пользователи могут создавать network_namespaces (7) вместе с user_namespaces (7). Однако непривилегированные сетевые пространства имен оказались не очень полезными, потому что для создания пар veth (4) в пространствах имен хоста и сети по-прежнему требуются привилегии root (иначе доступ в Интернету будет отсутствовать).

slirp4netns позволяет получить доступ из сетевое пространства имен в Интернет непривилегированным пользователям, подключая устройство TAP в сетевом пространстве имен к стеку TCP/IP usermode (slirp).

Сеть rootless контейнера весьма ограничена: технически сам контейнер не имеет IP-адреса, потому что без привилегий root невозможно настроить сетевое устройство. Более того, проверка связи (ping) из rootless контейнера не работает, поскольку в нем отсутствует функция безопасности CAP_NET_RAW, которая необходима для работы команды ping.

Заключение

Рассмотренный в этой статье подход к организации сети контейнеров является лишь одним из возможных (ну, пожалуй, наиболее широко используемым). Есть еще много других способов, реализованных через официальные или сторонние плагины, но все они сильно зависят от средств виртуализации сети Linux. Таким образом, контейнеризацию по праву можно рассматривать как технологию виртуализации.

Подробнее..

Self-Hosted, или Kubernetes для богатых почему самостоятельное развертывание кластера не всегда способ сэкономить

02.06.2021 18:06:54 | Автор: admin


Идея самостоятельно развернуть кластер Kubernetes на собственных серверах или в облаке выглядит привлекательной: кажется, что это дешевле, чем платить за Managed-решение от провайдера. На самом деле все не так однозначно: на практике можно обнаружить скрытые расходы и подводные камни.


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


Я Дмитрий Лазаренко, директор по продукту облачной платформы Mail.ru Cloud Solutions (MCS). В статье расскажу, в чем особенности развертывания Self-Hosted-кластера Kubernetes и о чем нужно знать перед запуском.


Для старта понадобятся время, деньги и администраторы, разбирающиеся в Kubernetes


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


Почему Kubernetes сильно увеличивает расходы на специалистов? Вроде бы развернуть кластер несложно, для этого есть официальная документация и инсталляторы, например Kubespray или Kubeadm. Однако если в компании есть инженер, который может прочитать строчку документации и разобраться, как поставить Kubernetes на серверы с помощью одной команды, это еще не все, этим его работа не ограничится.


В реальности развернуть кластер только половина дела. В таком виде он будет работать до первой проблемы, которая неизбежно возникнет через неделю или месяц. Например, перестанут создаваться поды из-за неверной конфигурации ресурсов на controller-manager. Или кластер начнет работать нестабильно из-за проблем с дисками у etcd. Или запущенные СronJob из-за ошибок controller-manager начнут бесконечно плодить новые поды. Или в кластере будут возникать сетевые ошибки из-за неправильного выбора конфигурации DNS.


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


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


Скорее всего, понадобится человек на фултайм, который будет заниматься только Kubernetes, поддержкой и развитием кластера. В большой компании может родиться отдел для поддержки инфраструктуры.


Конечно, если запускать Kubernetes только ради деплоя контейнеров, то можно не разбираться и не развивать кластер. Но тогда возникает вопрос: зачем вам Kubernetes? Можно взять более простой в настройке и поддержке инструмент, тот же Docker Swarm. Если вы хотите от Kubernetes что-то простое, просто его не используйте. Нет смысла тратить время на развертывание кластера лишь ради запуска простого кода. Эта технология предназначена для проектов, где постоянно идет разработка, часто запускаются новые релизы и нужно выдерживать требования HighLoad.

По этой причине Self-Hosted Kubernetes в большинстве случаев могут успешно запустить только крупные компании, где есть возможность выделить сотрудников для обслуживания кластера и нет потребности экономить ресурсы.


Кроме того, самостоятельное развертывание кластера дело небыстрое. Если понадобится запустить кластер в короткие сроки для проекта или тестовых сред, то на Self-Hosted это не выйдет: развертывание займет несколько часов, а то и недель. К этому стоит быть готовыми. Для сравнения: в облаке вы запустите кластер KaaS за 10 минут и сможете сразу его использовать, но это получается потому, что над инфраструктурной частью уже заранее поработали специалисты провайдера.


Kubernetes требует прокачки: он не работает сам по себе


Как я уже говорил выше, Kubernetes отдельная экосистема, которой нужно заниматься и подключать к ней дополнительные инструменты. Если брать Self-Hosted, то все это придется делать самостоятельно.


Все инструменты, дополняющие Kubernetes, Open Source-решения, которые нужно настраивать. В кластер потребуется установить систему мониторинга, реализовать балансировку нагрузки, сбор и хранение логов, настройки безопасности и авторизации пользователей, сети и многое другое.


Например, понадобится мониторить и сам кластер, и приложения в нем. Причем стандартного мониторинга через Zabbix вам не хватит, потребуется специфический Prometheus или Telegraph.


С логами аналогичная ситуация: из коробки вы получите только историю логов для уже запущенных приложений, при передеплое она исчезнет. Вручную собирать логи с Kubernetes не получится, нужно подключать сборщики логов вроде Fluentd и систему хранения, например Elasticsearch или Loki. Отдельно придется заниматься балансировкой нагрузки: понадобится отказоустойчивый балансер вроде MetalLB.


Системы хранения для Self-Hosted Kubernetes еще одна головная боль


Kubernetes изначально разработан для Stateless-приложений они ничего не хранят внутри контейнеров. При работе со Stateful-приложениями, хранящими данные, встает вопрос подключения внешних хранилищ.


Самый простой вариант, к которому часто прибегают, поднять один NFS-сервер, но это решение для бедных: оно не обеспечит высокую доступность и сохранность данных. Если в медленный и ненадежный NFS будут ходить продакшен-сервисы с важными данными, могут возникнуть большие проблемы.


Для нормальной работы приложения без изменения его логики понадобятся Persistent Volumes хранилища, связанные с подами. Они подключаются внутрь контейнеров как локальные директории, позволяя приложению хранить данные под собой. Среди рабочих вариантов CephFS, Glusterfs, FC (Fiber Channel), полный список СХД можно посмотреть в официальной документации.


Интеграция Kubernetes c Persistent Volumes нетривиальная задача. Чтобы развернуть тот же Ceph, недостаточно взять мануал с Хабра и выполнить ряд команд. Плюс в дальнейшем СХД должен кто-то заниматься опять нужен отдельный инженер, а то и несколько.


Если же Self-Hosted-кластер развернут не на железе, а на виртуальных машинах в облаке, то все немного проще собственный кластер Ceph поднимать не нужно. Можно взять кластер хранилища у провайдера и научить его работать с кластером K8s, если провайдер готов предоставить вам API к своей системе хранения данных, что есть не везде. Писать интеграцию при этом придется самостоятельно.


Правда, у провайдеров, предоставляющих IaaS, можно арендовать объектное хранилище или облачную СУБД, но только если логика приложения позволяет их использовать. А в Managed-решениях Kubernetes уже из коробки есть интегрированные Persistent Volumes.


Отказоустойчивость кластера отдельная проблема


С Kubernetes проще обеспечить отказоустойчивость приложений, однако потребуется еще и реализовать отказоустойчивость кластера.


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


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


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


Резерв зависит от того, какое количество вышедших из строя нод вероятно в вашем случае:


  • Если у вас одна стойка с серверами в одном дата-центре, то одномоментно, скорее всего, выйдет из строя максимум одна нода на одном сервере, например из-за ошибок ОС. Значит, нужен резерв на одну ноду. Конечно, может сломаться стойка, но тут уже нужно резервирование не средствами Kubernetes.
  • Если у вас несколько стоек с серверами, то есть вероятность потери одной стойки, например из-за проблем со свичем, когда все серверы в ней станут недоступны. Значит, нужен резерв в размере количества серверов в одной стойке.
  • Если у вас несколько дата-центров, то в каждом нужно держать резерв по размеру другого дата-центра, чтобы приложения работали в случае его выхода из строя.

Если проще, то это выглядит так: когда в кластере 10 нод и вы хотите без проблем пережить потерю одной ноды, то вам потребуется 10-процентный запас ресурсов. Если же приложения должны работать даже при потере 50% кластера, значит, на всех нодах нужен запас в 50%.


При этом лучше, если ноды в кластере небольшие по объему, но их много. Допустим, у вас есть пул ресурсов 100 ГБ оперативной памяти и 100 ядер CPU. Такой объем позволяет запустить 10 виртуалок и 10 нод кластера Kubernetes. И в случае выхода из строя одной ноды вы теряете только 10% кластера.

На железных серверах такую конфигурацию не создашь. Например, используя 300 ГБ оперативной памяти и 50 ядер CPU, вы развернете всего 23 ноды кластера. И в случае выхода из строя одной ноды рискуете сразу потерять 3050% кластера.

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


Автомасштабирование кластера нетривиальная задача


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


Автоскейлинг приложений в кластере возможен на любой инфраструктуре это делается средствами Kubernetes. А вот автоскейлинг кластера, который позволяет автоматически подключать и отключать ноды при изменении нагрузки, на Bare Metal реализуется только покупкой дополнительных серверов. Значит, заказываем их и ждем сразу масштабироваться не выйдет.


Плюс если мы говорим о Self-Hosted на Bare Metal, то все серверы, необходимые для работы приложений на случай нагрузки, придется держать в рабочем состоянии и постоянно за них платить.


Если Self-Hosted-кластер развернут на IaaS, то схема похожая: инженер добавляет новую виртуальную машину и вносит ее в кластер. Другой вариант взять API провайдера, если он его предоставляет, подключить через него кластер Kubernetes, научить его запускать для себя новые серверы и так реализовать автомасштабирование. Но потребуется разрабатывать отдельное решение это сложная задача, предполагающая высокий уровень экспертности в Kubernetes и облаках.


Кроме того, для быстрого масштабирования Self-Hosted-кластера на IaaS придется резервировать нужное количество ресурсов провайдера и создавать из них новые виртуальные машины по мере надобности. И за эти зарезервированные ресурсы придется платить: практика брать плату за выключенные ресурсы бывает у реселлеров VMware. На нашей платформе в случае отключенных ВМ вы не платите за ресурсы, только за диски. В некоторых Managed-решениях автоскейлинг включается по кнопке, уточните эту возможность у вашего провайдера.


Подводные камни Self-Hosted Kubernetes


  1. Для самостоятельной эксплуатации кластера нужен специалист на фултайм, который хорошо знает технологию и понимает, как все работает внутри Kubernetes.
  2. В кластере потребуется настроить мониторинг, сбор логов, балансировку нагрузки и многое другое.
  3. Отдельная проблема развернуть и интегрировать с кластером систему хранения данных.
  4. Чтобы обеспечить отказоустойчивость кластера, потребуются дополнительные серверы или виртуалки это дополнительные затраты.
  5. Для масштабирования кластера под нагрузкой нужен запас серверов или виртуалок это еще одна статья дополнительных расходов.

Рассчитывайте ваши возможности при старте проекта. То, какие ресурсы есть у вашей компании, ваш бэкграунд, навыки и другие детали сильно влияют на выбор решения, насколько вам будет выгодно разворачивать Kubernetes самостоятельно или лучше это сделать в облаке с помощью готового сервиса. И не забываем главный вопрос всего Kubernetes: нужна ли вообще эта технология на вашем проекте, как и для чего вы собираетесь ее использовать?


Тут можно почитать, как устроен наш Kubernetes aaS на платформе Mail.ru Cloud Solutions: что у него под капотом и что в него еще входит, кроме собственно Kubernetes.
Подробнее..

Хранение данных в Docker

23.01.2021 10:16:40 | Автор: admin


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


В этой статье рассмотрим docker volumes, bind mount и tmpfs, дадим советы по их использованию, проведём небольшую практику.


Особенности работы контейнеров


Прежде чем перейти к способам хранения данных, вспомним устройство контейнеров. Это поможет лучше понять основную тему.


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


На схеме показано устройство контейнера, запущенного из образа Ubuntu 15.04. Контейнер состоит из пяти слоёв: четыре из них принадлежат образу, и лишь один самому контейнеру. Слои образа доступны только для чтения, слой контейнера для чтения и для записи. Если при работе приложения какие-то данные будут изменяться, они попадут в слой контейнера. Но при уничтожении контейнера слой будет безвозвратно потерян, и все данные вместе с ним.



В идеальном мире Docker используют только для запуска stateless-приложений, которые не читают и не сохраняют данные о своём состоянии и готовы в любой момент завершиться. Однако в реальности большинство программ относятся к категории stateful, то есть требуют сохранения данных между перезапусками.


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


В Docker есть несколько способов хранения данных. Наиболее распространенные:


  • тома хранения данных (docker volumes),
  • монтирование каталогов с хоста (bind mount).

Особые типы хранения:


  • именованные каналы (named pipes, только в Windows),
  • монтирование tmpfs (только в Linux).


На схеме показаны самые популярные типы хранения данных для Linux: в памяти (tmpfs), в файловой системе хоста (bind mount), в томе Docker (docker volumes). Разберём каждый вариант.


Тома (docker volumes)


Тома рекомендуемый разработчиками Docker способ хранения данных. В Linux тома находятся по умолчанию в /var/lib/docker/volumes/. Другие программы не должны получать к ним доступ напрямую, только через контейнер.


Тома создаются и управляются средствами Docker: командой docker volume create, через указание тома при создании контейнера в Dockerfile или docker-compose.yml.


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


Один том может быть примонтирован одновременно в несколько контейнеров. Когда никто не использует том, он не удаляется, а продолжает существовать. Команда для удаления томов: docker volume prune.


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


Для чего стоит использовать тома в Docker:


  • шаринг данных между несколькими запущенными контейнерами,
  • решение проблемы привязки к ОС хоста,
  • удалённое хранение данных,
  • бэкап или миграция данных на другой хост с Docker (для этого надо остановить все контейнеры и скопировать содержимое из каталога тома в нужное место).

Монтирование каталога с хоста (bind mount)


Это более простая концепция: файл или каталог с хоста просто монтируется в контейнер.


Используется, когда нужно пробросить в контейнер конфигурационные файлы с хоста. Например, именно так в контейнерах реализуется DNS: с хоста монтируется файл /etc/resolv.conf.


Другое очевидное применение в разработке. Код находится на хосте (вашем ноутбуке), но исполняется в контейнере. Вы меняете код и сразу видите результат. Это возможно, так как процессы хоста и контейнера одновременно имеют доступ к одним и тем же данным.


Особенности bind mount:


  1. Запись в примонтированный каталог могут вести программы как в контейнере, так и на хосте. Это значит, есть риск случайно затереть данные, не понимая, что с ними работает контейнер.
  2. Лучше не использовать в продакшене. Для продакшена убедитесь, что код копируется в контейнер, а не монтируется с хоста.
  3. Для успешного монтирования указывайте полный путь к файлу или каталогу на хосте.
  4. Если приложение в контейнере запущено от root, а совместно используется каталог с ограниченными правами, то в какой-то момент может возникнуть проблема с правами на файлы и невозможность что-то удалить без использования sudo.

Когда использовать тома, а когда монтирование с хоста


Volume Bind mount
Просто расшарить данные между контейнерами. Пробросить конфигурацию с хоста в контейнер.
У хоста нет нужной структуры каталогов. Расшарить исходники и/или уже собранные приложения.
Данные лучше хранить не локально (а в облаке, например). Есть стабильная структура каталогов и файлов, которую нужно расшарить между контейнерами.

Монтирование tmpfs


Tmpfs временное файловое хранилище. Это некая специально отведённая область в оперативной памяти компьютера. Из определения выходит, что tmpfs не лучшее хранилище для важных данных. Так оно и есть: при остановке или перезапуске контейнера сохранённые в tmpfs данные будут навсегда потеряны.


На самом деле tmpfs нужно не для сохранения данных, а для безопасности, полученные в ходе работы приложения чувствительныеданные безвозвратно исчезнут после завершения работы контейнера. Бонусом использования будет высокая скорость доступа к информации.


Например, приложение в контейнере тормозит из-за того, что в ходе работы активно идут операции чтения-записи, а диски на хосте не очень быстрые. Если вы не уверены, в какой каталог идёт эта нагрузка, можно применить к запущенному контейнеру команду docker diff. И вот этот каталог смонтировать как tmpfs, таким образом перенеся ввод-вывод с диска в оперативную память.


Такое хранилище может одновременно работать только с одним контейнером и доступно только в Linux.


Общие советы по использованию томов


Монтирование в непустые директории


Если вы монтируете пустой том в каталог контейнера, где уже есть файлы, то эти файлы не удалятся, а будут скопированы в том. Этим можно пользоваться, когда нужно скопировать данные из одного контейнера в другой.


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


Монтирование служебных файлов


С хоста можно монтировать любые файлы, в том числе служебные. Например, сокет docker. В результате получится docker-in-docker: один контейнер запустится внутри другого. Выглядит как бред, но в некоторых случаях бывает оправдано. Например, при настройке CI/CD.


Монтирование /var/lib/docker


Разработчики Docker говорят, что не стоит монтировать с хоста каталог /var/lib/docker, так как могут возникнуть проблемы. Однако есть некоторые программы, для запуска которых это необходимо.


Практика: создадим тестовый том

Ключ командной строки для Docker при работе с томами.


Для volume или bind mount:


--volume | -v

Для tmpfs:


--tmpfs

Команды для управления томами в интерфейсе CLI Docker:


$ docker volumeCommands:  create   Create a volume (Создать том)  inspect  Display detailed information on one or more        volumes (Отобразить детальную информацию)  ls    List volumes (Вывести список томов)  prune Remove all unused volumes (Удалить все неиспользуемые тома)  rm    Remove one or more volumes (Удалить один или несколько томов)

Создадим тестовый том:


$ docker volume create slurm-storageslurm-storage

Вот он появился в списке:


$ docker volume lsDRIVER  VOLUME NAMElocal   slurm-storage

Команда inspect выдаст примерно такой список информации в json:


$ docker inspect slurm-storage[    {        "CreatedAt": "2020-12-14T15:00:37Z",        "Driver": "local",        "Labels": {},        "Mountpoint": "/var/lib/docker/volumes/slurm-storage/_data",        "Name": "slurm-storage",        "Options": {},        "Scope": "local"    }]

Попробуем использовать созданный том, запустим с ним контейнер:


$ docker run --rm -v slurm-storage:/data -it ubuntu:20.10 /bin/bash# echo $RANDOM > /data/file# cat /data/file13279# exit

После самоуничтожения контейнера запустим другой и подключим к нему тот же том. Проверяем, что в нашем файле:


$ docker run --rm -v slurm-storage:/data -it centos:8 /bin/bash -c "cat /data/file"13279

То же самое, отлично.


Теперь примонтируем каталог с хоста:


$ docker run -v /srv:/host/srv --name slurm --rm -it ubuntu:20.10 /bin/bash

Docker не любит относительные пути, лучше указывайте абсолютные!


Теперь попробуем совместить оба типа томов сразу:


$ docker run -v /srv:/host/srv -v slurm-storage:/data --name slurm --rm -it ubuntu:20.10 /bin/bash

Отлично! А если нам нужно передать ровно те же тома другому контейнеру?


$ docker run --volumes-from slurm --name backup --rm -it centos:8 /bin/bash

Вы можете заметить некий лаг в обновлении данных между контейнерами, это зависит от используемого Docker драйвера файловой системы.


Создавать том заранее необязательно, всё сработает в момент запуска docker run:


$ docker run -v newslurm:/newdata -v /srv:/host/srv -v slurm-storage:/data --name slurm --rm -it ubuntu:20.10 /bin/bash

Посмотрим теперь на список томов:


$ docker volume lsDRIVER  VOLUME NAMElocal   slurm-storagelocal   newslurm

Ещё немного усложним команду запуска, создадим анонимный том:


$ docker run -v /anonymous -v newslurm:/newdata -v /srv:/host/srv -v slurm-storage:/data --name slurm --rm -it ubuntu:20.10 /bin/bash

Такой том самоуничтожится после выхода из контейнера, так как мы указали ключ rm.


Если этого не сделать, давайте проверим что будет:


$ docker run -v /anonymous -v newslurm:/newdata -v /srv:/host/srv -v slurm-storage:/data --name slurm -it ubuntu:20.10 /bin/bash$ docker volume lsDRIVER  VOLUME NAMElocal     04c490b16184bf71015f7714b423a517ce9599e9360af07421ceb54ab96bd333local   newslurmlocal   slurm-storage

Хозяйке на заметку: тома (как образы и контейнеры) ограничены значением настройки dm.basesize, которая устанавливается на уровне настроек демона Docker. Как правило, что-то около 10Gb. Это значение можно изменить вручную, но потребуется перезапуск демона Docker.


При запуске демона с ключом это выглядит так:


$ sudo dockerd --storage-opt dm.basesize=40G

Однажды увеличив значение, его уже нельзя просто так уменьшить. При запуске Docker выдаст ошибку.


Если вам нужно вручную очистить содержимое всех томов, придётся удалять каталог, предварительно остановив демон:


$ sudo service docker stop$ sudo rm -rf /var/lib/docker

Если вам интересно узнать подробнее о работе с данными в Docker и других возможностях технологии, приглашаем на двухдневный онлайн-интенсив в феврале. Будет много практики.

Автор статьи: Александр Швалов, практикующий инженер Southbridge, Certified Kubernetes Administrator, автор и разработчик курсов Слёрм.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru