Эфемерное хранилище openshift что это
Хранилища находятся во многих частях стека OpenStack и различные типы могут привести к путанице даже у искушенных инженеров, работающих с облаками. В данном разделе рассматриваются параметры устройств постоянного хранения, которые вы можете настраивать в своем облаке. Важно понимать разницу между эфемерным хранилищем и постоянным хранилищем.
Эфемерное хранилище
При развертывании только вычислительных служб OpenStack (nova), ваши пользователи не имеют доступа к какой- либо постоянной системе хранения по умолчанию. Диски, связанные с виртуальными машинами «эфемерные», что означает (с точки зрения пользователя), что они эффективно исчезают после завершения работы виртуальной машины.
Постоянные хранилища
Постоянное хранилище означает, что данный хранимый ресурс останется после завершения любого другого ресурса и всегда является доступным, вне зависимости от состояния выполняемого экземпляра.
На сегодняшний день облака OpenStack в явном виде поддерживают два вида постоянных хранилищ: объектные хранилища и блочные хранилища .
Хранилища объектов
При помощи хранилища объектов пользователи получают доступ к двоичным объектам через REST API. Вы можете быть знакомы с Amazon S3, который является широко известным примером системы хранения объектов. В OpenStack хранилище объектов реализуется в рамках проекта OpenStack Object Storage (swift) Если у ваших пользователей существует необходимость архивирования или управления большими наборами данных, значит вы хотите предоставить им хранилище объектов. Помимо этого, OpenStack может хранить образы ваших виртуальных машин (ВМ) внутри системы хранения объектов, как альтернатива хранению образов в файловой системе.
Хранилище объектов OpenStack предоставляет решение хранения с высокой масштабируемостью и высокой доступностью ослабляя некоторые ограничения традиционных файловых систем. При проектировании и приобретении такого кластера важно понять некоторые основные концепции о его работе. По существу, этот тип хранения построен на идее, что любое оборудование хранение подвержено сбоям, причем в какой-то момент это происходит на любом уровне. Редко происходящие отказы, которые ставят подножку другим системам хранения, например, проблемы выхода из строя RAID контроллеров или целых серверов великолепно обрабатываются хранилищем объектов OpenStack.
Хорошая документация, описывающая архитектуру хранилища объектов может быть найдена в документации разработчика— прочтите ее для начала. Прим. пер.: у нас на сайте присутствует в свободном доступе перевод книги коллектива авторов «Реализация облачного хранилища с OpenStack Swift» (Проектирование, реализация и успешное управление вашим собственным кластером облачного хранилища с использованием популярного программного обеспечения OpenStack Swift. Кападиа Амар, Варма Средхар, Раджана Крис.), 978-1782168058> После того, как вы поймете архитектуру, вам необходимо будет разобраться с тем, что делают серверы прокси и как работают зоны. Однако некоторые важные моменты упускаются при первом беглом ознакомлении.
При проектировании кластера необходимо учитывать надежность и доступность. Осознайте, что их основным источником являются распределение размещение ваших данных, а не надежность оборудования.Рассмотрим значение по умолчанию для реплик, которое равно трем. Это означает, что прежде чем объект будет помечен как записанный, по крайней мере две копии существуют — в случае отказа на запись одного сервера, третья копия может присутствовать или еще не существовать при начальном возврате операции записи. Изменение данного значения повышает надежность хранения данных, но уменьшает имеющийся у вас объем хранилища. Далее, ознакомьтесь с размещением серверов. Проанализируйте их распространение по всем своим центрам обработки данных, и зонам отказоустойчивости энергоснабжения. Является ли зоной стойка, сервер или диск?
Сетевые модели хранилищ объектов могут показаться необычными на первый взгляд. Рассмотрим эти основные транспортные потоки:
- Между объектами, контейнерами, и серверами учетных записей
- Между этими серверами и серверами прокси
- Между серверами прокси и вашими пользователями
Хранилища объектов очень «разговорчивые» по сравнению с остальными серверами, размещающими данные, даже небольшой кластер производит обем, измеряемый мегабайтами в секунду, причем он преимущественно такой: “У вас есть оъект?”/“Да, у меня есть объект!” Разумеется, если ответ на приведенный вопрос отрицательный или вышло время ожидания ответа, начинается репликация объекта.
Рассмотрим ситуацию, при которой отказывает сервер целиком и 24ТБайт данных необходимо переслать «немедленно» для сохранения трех экземпляров — это может предоставить значительную загруженность сети.
Оставшийся пункт полосы пропускания это общедоступная часть. Службы swift-proxy не сохраняет состояние, что означает, что вы легко можете добавить дополнительные методы балансировки HTTP для совместного использования полосы пропускания между ними.
БОльшее число серверов прокси означает бОльшую полосу пропускания, если ваше хранилище поспевает за ними.
Блочные хранилища
Блочные хранилища (иногда называемые хранилищами томов) предоставляют для пользователя блочные устройства. Пользователи взаимодействуют с блочными хранилищами путем присоединения томов к своим работающим экземплярам виртуальных машин.
Эти тома являются постоянными: они могут быть отделены от одного экземпляра и повторно подключены к другому, причем данные остаются не поврежденными. Блочные хранилища реализуются в OpenStack проектом блочных хранилищ OpenStack (Cinder), который поддерживает множество серверов хранения в форме драйверов устройств. Выбранный вами сервер хранения должен поддерживаться устройством блочного хранилища.
Большинство устройств блочного хранилища позволяют экземпляру осуществлять прямой доступ к аппаратным средствам устройства блочной системы хранения. Это помогает увеличить общую производительность чтения/записи системы ввода/вывода.
В редакции Folsom начала осуществляться экспериментальная поддержка использования файлов в качестве томов. Изначально она стартовала как драйвер ссылок для поддержки NFS в Cinder. Начиная с редакции Grizzly, поддержка была расширена до полномасштабного драйвера NFS, а также дополнена драйвером GlusterFS.
.
Работа этих драйверов слегка отличается от обычных драйверов «блочных» систем хранения. В файловых системах NFS и GlusterFS создается некий файл, который затем отображается в виде тома в экземпляре. Такое отображение/ трансляция аналогична использованию OpenStack-ом в QEMU виртуальных машин на основе файлов, хранящихся в /var/lib/nova/instances .
Концепции хранилища OpenStack
Таблица 6.1, “Хранилища OpenStack” объясняет различные хранилища OpenStack concepts provided by OpenStack.
Работы операционной системы и рабочей области
Добавляет дополнительные устройства постоянного хранения виртуальным машинам (ВМ)
Хранит данные, включающие образы ВМ
блочные устройства которые могут разбиваться на разделы, подвергаться форматированию и монтироваться (такие как /dev/vdc)
OpenStack Compute (nova)
OpenStack Block Storage (cinder)
OpenStack Object Storage (swift)
Установками администратором настроек размеров, известных как предпочтения (flavor)
Описываются пользователем в начальном запросе
Размером доступного физического хранилища
Пример типичного применения…
10 ГБ первый диск, 30 ГБ второй диск
10-ки ТБ-ов наборов хранимых данных
Хранилища файлового уровня (для он-лайн миграции)
Применяя хранилще файлового уровня, пользователи получают доступ хранимым данным с помощью файловой системы операционной системы. Большинство пользователей, если они ранее использовали решения сетевого хранения данных, уже встречались с такой формой сетевого хранения. В мире Unix наиболее распространенной формой является NFS. В мире Windows, наиболее распространенная форма называется CIFS (ранее SMB).
Облака OpenStack не предоставляют конечным пользователям хранилища файлового уровня. Тем не менее, когда вы проектируете свое облако, важно предусмотреть возможность хранения на файловом уровне для хранения экземпляров в /var/lib/nova/instances при проектировании облака, так как вы должны иметь совместно используемую файловую систему, если вы хотите использовать миграцию в режиме он-лайн.
Выбор серверов хранения
Пользователи будут указывать различные потребности для своих случаев использования. Некоторым, возможно, потребуется быстрый доступ ко многим объектам, которые не очень часто изменяются, или вы захотите установить на файл значение времени жизни(TTL). Другим может потребоваться доступ только к хранилищу, смонтированному в пределах самой файловой системы, однако требующего мгновенной репликации при запуске нового экземпляра. Для других систем, эфемерное хранилище — хранилище, которое высвобождается, когда прекращается выполнение подключенной к нему виртуальной машщины — является предпочтительным вариантом. Когда вы выбираете сервер храненияs, , задайте себе следующие вопросы от имени пользователей:
- Требуются ли моим пользователям блочные хранилища?
- Требуются ли моим пользователям хранилища объектов?
- Требуются ли мне поддержка миграции в режиме реального времени?
- Должны ли мои устройства постоянного хранения содержаться в моих вычислительных узлах, или я должен использовать внешнюю СХД?
- На какое количество пластин устройств хранения я могу рассчитывать? Даст ли лучший результат ввода/вывода большее количество шпинделей, несмотря на сетевой доступ?
- Какой результат является моей целью в выборе наилучшего соотношения стоимость- производительность?
- Как я должен управлять эксплуатацией систем хранения?
- Насколько резервируемой и распределенной является система хранения? Что произойдет в случае отказа узла хранения? В какой степени это может смягчить сценарии угрозы потери моих данных?
Для развертывания вашей системы хранения с использованием серийных аппаратных средств, вы можете использовать ряд пакетов с открытым исходным кодом, что демонстрируется в следующей : Таблице 6.2, “Поддержка постоянных хранилищ на основе файлов”.
[a] Данный список решений хранения совместно используемых данных на файловом уровне с открытым исходным кодом не является исчерпывающим; существуют и другие решения с открытым исходным кодом (MooseFS). Возможно, ваша организация уже развернула решение хранения совместно используемых данных файлового уровня, которое можно использовать.
Storage Driver Support
Помимо существующих технологий с открытым исходным кодом, существует ряд фирменных решений, которые официально поддерживаются блочными хранилищами OpenStack. Они предлагаются в следующем списке поставщиков:
- IBM (Storwize family/SVC, XIV)
- NetApp
- Nexenta
- SolidFire
Вы можете найти матрицу функциональных возможностей, предоставляемых всеми поддерживаемыми драйверами блочных хранилищ, на OpenStack wiki.
Кроме того, вы должны решить: хотите ли вы поддерживать в своем облаке хранилище объектов. Двумя общими случаями использования для поддержки хранилищ объектов в компьютерном облаке являются:
- Обеспечение пользователей механизмом устройств постоянного хранения
- Для масштабируемых, надежных систем хранения для образов виртуальных машин
Технологии серийно выпускаемых серверов хранения
В данном разделе приводится общий анализ отличий между различными технологиями серийных серверов хранения. В зависимости от потребностей ваших пользователей облака, вы можете реализовать одну или несколько из этих технологий в различных комбинациях:
Хранилище объектов OpenStack (swift)
Официальное хранилище объектов OpenStack. Это зрелая технология, которая использовалась в течение ряда лет в практической деятельности Rackspace как технология, лежащая в основе Rackspace Cloud Files. Поскольку технология обладает высокой масштабируемостью, она хорошо подходит для управления петабайтами хранимых данных. Преимуществами хранилищ объектов OpenStack являются лучшая интеграция с OpenStack (интегрируется с OpenStack Identity, работает с интерфейсом инструментальной панели OpenStack), а также лучшая поддержка многократного развертывания центров обработки данных за счет поддержки асинхронных эпизодических непротиворечивых репликаций.
Поэтому, если вы в конечном итоге планируете раccредоточить свой кластер хранения в нескольких центрах обработки данных и если вам нужно унифицировать учетные данные пользователей, как для вычислений, так и для объектов хранения, или, если вы хотите управлять своими хранимыми объектами через инструментальную панель OpenStack, вы должны рассмотреть хранилище объектов OpenStack. Более подробную информацию о хранилище объектов OpenStack можно найти в разделе Хранилище объектов (исправлено переводчиком).
Масштабируемое решение для хранения, которое реплицирует данные между серийными узлами хранения. Ceph первоначально был разработан одним из основателей DreamHost и в настоящее время используется в данном продукте.
Ceph был разработан, чтобы представить конечному пользователю различные типы интерфейсов хранилищ данных: он поддерживает хранение объектов, блочные хранилища и интерфейсы файловой системы, хотя интерфейс файловой системы пока не считается готовым продуктом. Для хранения объектов Ceph поддерживает тот же API, что и Swift, может использоваться в качестве сервера хранения для блочных хранилищ Cinder, а также сервера хранения для образов glance. Ceph поддерживает «Слабую инициализацию» («Thin Provisioning»), реализованную копированием при записи.
Он может быть полезным при загрузке с тома, поскольку новый том может быть подготовлен очень быстро. Ceph также поддерживает аутентификацию на основе keystone (начиная с версии 0.56), поэтому он может быть бесшовным обменником в реализации OpenStack Swift по умолчанию.
Преимущество Ceph в том, что он предоставляет администратору более тонкое управление стратегией распределения данных и репликации, позволяет консолидировать ваши хранилище объектов и блочное хранилище, допускает очень быструю инициализацию загрузки с томов при использовании слабой инициализации, и поддерживает интерфейс распределенной файловой системы, хотя этот интерфейс пока не рекомендуется пока не рекомендуется для использования в промышленном развертывании в рамках проекта Ceph. Прим. пер.: в текущих версиях файловая система Ceph является штатным компонентом, см. например, http://ceph.com/docs/master/cephfs/, что явно отменяет данную рекомендацию!>
Если вы хотите управлять хранилищем объектов и блочным хранилищем в рамках одной системы, или если вы хотите поддержать быструю загрузку-с-тома, вы должны рассмотреть вариант использования Ceph.
Распределенная, совместно используемая файловая система. По состоянию на момент выхода версии Gluster 3.3, вы можете использовать Gluster для консолидации хранилища объектов и хранилища файлов в единое решение хранения файлов и объектов, которое называется Gluster For OpenStack (GFO). GFO использует настраиваемую версию Swift, которая, в свою очередь, использует Gluster в качестве сервера хранения.
Основное преимущество использования GFO над обычным swift, проявляется, когда вы также хотите поддерживать распределенную файловую систему, либо для поддержки миграции совместно используемого хранилища в реальном времени или предоставить ее в качестве отдельной услуги для ваших конечных пользователей. Если вы хотите управлять хранилищами объектов и файлов в пределах одной системы, вы должны рассмотреть использование GFO.
Cистема управления логическим томами (Logical Volume Manager) на основе Linux, которая обеспечивает уровень абстракции поверх физических дисков, чтобы представлять логические тома операционной системе. Сервер LVM (Logical Volume Manager) реализует блочные хранилища как LVM логических разделов.
На каждом хосте, где размещается блочное хранилище, администратор должен сначала создать группу томов, выделенную для томов блочного хранилища. Блоки создаются из логических томов LVM.
LVM не обеспечивает никакой репликации. Как правило, администраторы настраивают RAID на узлах, использующих LVM как блочное хранилище, для защиты от сбоев отдельных жестких дисков. Однако, RAID не защищает от отказа всего хоста.
Драйвер iSCSI Solaris для блочного хранилища OpenStack реализует блоки как примитивы ZFS. ZFS является файловой системой, которая также имеет функциональность диспетчера томов. Это отличает ее от системы Linux, в которой существует разделение диспетчера томов (LVM) и файловых систем (таких как, ext3, ext4, XFS, btrfs). ZFS имеет ряд преимуществ по сравнению с ext4, в том числе улучшенную проверку целостности данных.
Сервера ZFS для блочных хранилищ OpenStack поддерживают только основанные на Solaris системы, такие как Illumos. Хотя и существует портация ZFS для Linux, она не содержится ни в каких стандартных дистрибутивах, а также не была протестирована с блочным хранилищем OpenStack. Как и LVM, ZFS не обеспечивает репликацию по хостам самостоятельно; вам требуется добавить решение по репликации поверх ZFS если ваше облако нуждается в возможности обработки отказов узлов хранения.
Мы не рекомендуем ZFS, если у вас нет опыта работы с ее развертыванием, поскольку сервера ZFS для блочных хранилищ требуют операционную систему на основе Solaris, а мы предполагаем, что ваш опыт, прежде всего, базируется на системах Linux.
коммерческая файловая система, поддерживаемая OpenStack начиная с редакции Havana (17 октября 2013). Данная файловая система предоставляет поддержку всех типов хранилищ при наличии собственных систем обеспечения надежности, отказоустойчивости, масштабируемости хранимых объемов и пропускной способности, при совместимости с большим спектром серийно выпускаемого оборудования различных производителей и гарантированном сопровождении и поддержке производителем самой GPFS (IBM) При наличии требований уровня критических приложений или доступности бюджетных средств, рекомендуем обратить внимание на данный продукт. >
Выводы
Мы надеемся, что теперь у вас есть некие соображения анализа и вопросы, которые вы зададите своим будущим пользователям облака об их вариантах использования хранилищ. Как вы можете увидеть, ваши решения по хранилищам будут также влиять на проект вашей сети в отношении требований производительности и безопасности. ПРодолжите изучение с нами для принятия более обоснованных решений по проекту вашего облака OpenStack.
Сохраняем кластеры Kubernetes в чистоте и порядке
Одновременно с ростом кластера Kubernetes растет количество ресурсов, volume и других API-объектов. Рано или поздно вы упретесь в потолок, будь то etcd , память или процессор. Зачем подвергать себя ненужной боли и проблемам, если можно установить простые — хотя и довольно изощренные — правила? Вы можете настроить автоматизацию и мониторинг, которые будут содержать кластер в аккуратном состоянии. В статье разберемся, как избавиться от лишних нагрузок, через которые утекают ресурсы, и устаревших накопившихся объектов.
Что будет в статье:
- В чем проблема?
- Основы
- Ручная очистка
- Kube-janitor
- Мониторинг ограничений кластера
- Заключение
- От переводчиков
В чем проблема?
Несколько забытых подов, неиспользуемые volume, завершенные задачи или, возможно, старый ConfigMap/Secret — насколько все это важно? Все это просто где-то лежит и ждет, пока не понадобится.
Вообще-то этот хлам не сильно вредит до поры до времени. Но когда эти штуки накапливаются, то начинают влиять на производительность и стабильность кластера. Давайте посмотрим, какие проблемы могут вызвать эти забытые ресурсы.
Ограничение количества подов. Каждый кластер Kubernetes имеет несколько основных ограничений. Первое из них — количество подов на узел, которое по документации не рекомендуется делать больше 110. При этом, если у вас достаточно мощные узлы с большим количеством памяти и CPU, вы можете увеличить это количество — возможно, даже до 500, как было протестировано на OpenShift. Но если вы достигнете этих пределов, не удивляйтесь, если что-то пойдет не так, и не только потому, что не хватает памяти или мощности процессора.
Нехватка хранилища ephemeral storage. Все запущенные поды на узле используют по крайней мере часть этого пространства для логов, кэша, рабочего пространства или emptyDir волюмов. Вы можете достичь предела довольно быстро, что приведет к вытеснению уже существующих подов, или невозможности создания новых. Запуск слишком большого количества подов на узле тоже может способствовать этой проблеме: ephemeral storage используется для образов контейнеров и их записываемых слоев. Если узел достигает предела хранилища, он становится нерабочим (на него будет применен taint), о чем вы узнаете довольно быстро. Если вы хотите узнать текущее состояние диска на узле, запустите на нем df -h /var/lib.
Лишние расходы на PVC. Схожим образом источником проблем могут стать persistent volume, особенно если вы запускаете Kubernetes в облаке и платите за каждый предоставленный PVC. Очевидно, что для экономии денег необходимо чистить неиспользуемые PVC. Содержание используемых PVC чистыми тоже важно, потому что позволяет избежать нехватки места для ваших приложений. Особенно, если вы запускаете базы данных в кластере.
Низкая производительность etcd. Еще один источник проблем — чрезмерное количество объектов, поскольку все они находятся в хранилище etcd . По мере роста количества данных в etcd , его производительность может начать снижаться. Этого нужно стараться не допускать всеми силами: etcd — мозг кластера Kubernetes. Учитывая вышесказанное, чтобы упереться в etcd, вам понадобится действительно большой кластер, как продемонстрировано в этом посте OpenAI. В то же время нет какой-то единой метрики для замера производительности etcd , потому что она зависит от количества объектов, их размеров и частоты использования. Так что наилучшим выходом будет профилактика: простое сохранение чистоты и порядка. В противном случае вас могут ждать весьма неприятные сюрпризы.
Нарушение границ безопасности. Наконец, мусор в кластере может стать источником проблем сам по себе. Не забывайте подчищать Role Bindings и учетные записи (Service Account), когда ими никто не пользуется.
Основы
Для решения большинства этих проблем не нужно делать ничего особенно сложного. Лучшим выбором будет совсем не допускать их. Один из вариантов превентивных мер — использование объектных квот, которые вы, как администратор кластера, можете применять в каждом отдельном неймспейсе.
Первое, что можно решить с помощью квот — количество и размер PVC:
apiVersion: v1 kind: LimitRange metadata: name: pvc-limit spec: limits: - type: PersistentVolumeClaim max: storage: 10Gi min: storage: 1Gi --- apiVersion: v1 kind: ResourceQuota metadata: name: pvc-quota spec: hard: persistentvolumeclaims: "10" requests.storage: "10Gi" # сумма запрошенного хранилища в bronze storage class не может превышать 5 Гб bronze.storageclass.storage.k8s.io/requests.storage: "5Gi"
Выше мы имеем два объекта.
LimitRange устанавливает минимальный и максимальный размеры PVC в неймспейсе. Это оградит пользователей от запрашивания слишком больших объемов.
ResourceQuota дополнительно обеспечивает жесткое ограничение на количество PVC и их совокупный размер.
Затем вы можете предотвратить создание кучи объектов и оставление их в качестве мусора после использования. Для этого используйте object count, квоты на количество объектов, которые зададут жесткое ограничение на количество объектов определенного типа в конкретном неймспейсе:
apiVersion: v1 kind: ResourceQuota metadata: name: object-count-quota spec: hard: configmaps: "2" secrets: "10" services: "5" count/jobs.batch: "8"
Есть несколько встроенных полей, через которые вы можете задать квоты на количество объектов. Например, configmaps , secrets или services , показанные выше. Для всех прочих ресурсов можно использовать формат count/. , как показано в примере с count/jobs.batch , что может помочь от бесконтрольного создания джоб из-за неправильно настроенного CronJob.
Вероятно, большинству известно о функции установки квот на память и CPU. Но, возможно, для вас станет новостью квота ephemeral storage. Альфа-поддержка квот для эфемерного хранилища была добавлена в v1.18 и дала возможность установить границы ephemeral storage так же, как как для памяти и процессора.
apiVersion: v1 kind: ResourceQuota metadata: name: ephemeral-storage-quota spec: hard: requests.ephemeral-storage: 1Gi limits.ephemeral-storage: 2Gi
Однако будьте осторожны с этой настройкой. Поды могут быть вытеснены из-за превышения лимита, что может быть вызвано слишком большим размером логов контейнера.
Помимо установки квот и границ ресурсов, можно установить ограничение для истории ревизий Deployment — для снижения количества репликасетов, хранящихся в кластере. Для этого используйте .spec.revisionHistoryLimit, который по умолчанию равен 10.
Наконец, вы можете установить время жизни (TTL) для очистки кластера от объектов, которые существуют там слишком долго. Эта процедура использует TTL-контроллер, который находится в стадии бета-тестирования с версии v1.21, и в настоящее время работает только для задач, использующих поле .spec.ttlSecondsAfterFinished. В будущем, возможно, он будет расширен на другие ресурсы, например, поды.
Ручная очистка
Если превентивных мер уже недостаточно, потому что у вас накопилось достаточно неиспользуемых ресурсов, вы можете попробовать разовое удаление. Это делается просто с помощью kubectl get и kubectl delete . Пара основных примеров того, что вы можете сделать:
kubectl delete all -l some-label=some-value # Delete based on label kubectl delete pod $(kubectl get pod -o=jsonpath='') # Delete all "Succeeded" Pods
Первая команда выполняет базовое удаление с использованием лейблов ресурсов. Поэтому этот метод потребует от вас предварительно пометить все подлежащие удалению объекты некоторой парой ключ-значение.
Вторая показывает, как вы можете удалить тип ресурсов, основанный на некотором поле, как правило, каком-то поле статуса. В примере выше это будут все завершенные/успешные поды. Этот вариант можно применить и к другим ресурсам, например, завершенным задачам.
Кроме этих двух команд, довольно сложно найти шаблон, который помог бы разом очистить кластер от хлама. Поэтому вам придется специально поискать конкретные неиспользуемые ресурсы.
Могу посоветовать такой инструмент, как k8spurger. Он ищет неиспользуемые объекты вроде RoleBinding, ServiceAccounts, ConfigMaps и создает список ресурсов-кандидатов на удаление. Это поможет сузить круг поиска.
Kube-janitor
В разделах выше мы рассмотрели несколько вариантов простой очистки для конкретных случаев. Но лучшим решением для наведения порядка в любом кластере будет использование kube-janitor. Этот инструмент работает в вашем кластере так же, как любая другая рабочая нагрузка, и использует JSON-запросы для поиска ресурсов, которые можно удалить на основе TTL или истечения срока действия.
Для развертывания kube-janitor на вашем кластере запустите:
git clone https://codeberg.org/hjacobs/kube-janitor.git cd kube-janitor kubectl apply -k deploy/
Это разложит kube-janitor в default namespace и запустит его с правилами по умолчанию в пробном режиме с использованием флага —dry-run.
Перед отключением dry-run нужно установить собственные правила. Они лежат в config map kube-janitor, которая выглядит примерно так:
apiVersion: v1 kind: ConfigMap metadata: name: kube-janitor namespace: default data: rules.yaml: |- rules: .
Конечно, для нас самый интересный раздел здесь — правила, rules . Вот несколько полезных примеров, которые вы можете использовать для очистки своего кластера:
rules: # Удаление Jobs в development namespaces после 2 дней. - id: remove-old-jobs resources: - jobs jmespath: "metadata.namespace == 'development'" ttl: 2d # Удаление тех подов в development namespaces, которые не в состоянии выполнения (Failed, Completed). - id: remove-non-running-pods resources: - pods jmespath: "(status.phase == 'Completed' || status.phase == 'Failed') && metadata.namespace == 'development'" ttl: 2h # Удаление всех PVC, которые не использует ни один под - id: remove-unused-pvcs resources: - persistentvolumeclaims jmespath: "_context.pvc_is_not_mounted" ttl: 1d # Удаление всех Deployments, чье имя начинается с 'test-' - id: remove-test-deployments resources: - deployments jmespath: "starts_with(metadata.name, 'test-')" ttl: 1d # Удаление всех ресурсов в playground namespace через неделю - id: remove-test-deployments resources: - "*" jmespath: "metadata.namespace == 'playground'" ttl: 7d
Этот пример показывает несколько базовых вариантов настройки для очистки от временных, устаревших или неиспользуемых ресурсов. Помимо правил такого рода, можно установить абсолютные дату/время истечения срока действия для конкретных объектов.
Это можно сделать с помощью аннотаций, например:
apiVersion: v1 kind: Namespace metadata: annotations: # будет удалено 18.6.2021 в полночь janitor/expires: "2021-06-18" name: temp spec: <> --- apiVersion: apps/v1 kind: Deployment metadata: annotations: # будет удалено 20.6.2021 в 17:30 janitor/expires: "2021-06-20T17:30:00Z" name: nginx spec: replicas: 1 .
Когда закончите устанавливать правила и аннотации, вам стоит дать kube-janitor поработать какое-то время в dry-run режиме с включенными логами отладки. Это мера предосторожности, чтобы инструмент не удалил то, что удалять было не нужно.
Другими словами, не обвиняйте меня, если сотрете production волюмы из-за неправильной конфигурации и отсутствия тестирования.
Наконец, при использовании kube-janitor нужно учитывать его потребности в ресурсах. Если в кластере много объектов, ему может потребоваться больше памяти, чем выделенные по умолчанию 100 Мб. Чтобы его под не застревал в CrashLoopBackOff, я выставляю ему лимит 1 Гб.
Мониторинг ограничений кластера
Не все проблемы можно решить ручной или даже автоматической очисткой. В некоторых случаях мониторинг будет лучшим выбором для обеспечения уверенности в том, что вы не упираетесь в лимиты кластера — будь то количество подов, доступное ephemeral storage или количество объектов в etcd .
Мониторинг — это огромная тема, которая требует отдельной статьи, а то и нескольких. Поэтому для сегодняшних целей я просто перечислю несколько Prometheus-метрик, которые могут оказаться полезными для поддержания порядка в кластере:
- etcd_db_total_size_in_bytes — размер базы данных etcd
- etcd_object_counts — количество объектов в etcd
- pod:container_cpu_usage:sum — использование CPU на каждый под в кластере
- pod:container_fs_usage_bytes:sum — использование файловой системы на каждый под в кластере
- pod:container_memory_usage_bytes:sum — использование памяти на каждый под в кластере
- node_memory_MemFree_bytes — свободная память на каждом узле
- namespace:container_memory_usage_bytes:sum — использование памяти по неймспейсам
- namespace:container_cpu_usage:sum — загрузка CPU на каждый namespace
- kubelet_volume_stats_used_bytes — используемое место по каждому волюму
- kubelet_running_pods — количество запущенных подов на узле
- kubelet_container_log_filesystem_used_bytes — размер логов на каждый контейнер/под
- kube_node_status_capacity_pods — рекомендованный максимум подов на узел
- kube_node_status_capacity — максимум для всех метрик (CPU, поды, ephemeral storage, память, hugepages)
Это только некоторые метрики из тех, которые вы можете использовать. Какие из них будут доступны, зависит также от ваших инструментов мониторинга, т.е. вы можете использовать какие-то специальные метрики, доступные на вашем сервисе.
Заключение
Мы рассмотрели несколько вариантов очистки кластера Kubernetes — некоторые совсем простые, а некоторые посложнее. Независимо от того, что вы выберете, старайтесь не забывать делать уборку в кластере и сохранять порядок. Это может спасти вас от большой головной боли и, по крайней мере, избавит от ненужного хлама в кластере. В конечном счете такая уборка служит тем же целям, что наведение порядка на рабочем столе.
Также имейте ввиду, что если вы оставляете объекты лежать без использования долгое время, вы просто забудете, зачем они там. Это может сильно осложнить понимание, что должно существовать, а что — нет.
Помимо описанных здесь подходов, вы также можете использовать какое-либо решение GitOps — например, ArgoCD или Flux для создания ресурсов и управления ими, что может значительно упростить их очистку. Обычно потребуется удалить только один кастомный ресурс, что вызовет каскадное удаление всех зависимых ресурсов.
От переводчиков
Если хотите научиться девопсу, посмотрите программу нашего курса «Деплой приложений в Kubernetes».
Другие статьи про DevOps для начинающих:
- Сравнение Kubernetes с другими оркестраторами: Docker Swarm, Nomad, Openshift
- Как изучать Kubernetes джуну — и зачем
- Как деплоить приложение на Django в Kubernetes с нуля
Другие статьи про DevOps для продолжающих:
- Как построить эргономичный мониторинг
- Зачем мы сделали собственный контроллер для копирования секретов в Kubernetes
Тонкости настройки CI/CD: как работает GitLab runner, когда использовать Docker-in-Docker и где пригодится Argo CD
В конце прошлого года в «Слёрме» вышел видеокурс по CI/CD. Авторы курса инженер Southbridge Александр Швалов и старший системный инженер Tinkoff Тимофей Ларкин ответили на вопросы первых студентов.
В частности, обсудили:
- Как работает GitLab runner: сколько задач берёт и сколько ресурсов потребляет, где его лучше размещать и как настроить шаринг между проектами?
- Как настраиваются пайплайны для проектов в монорепозитории? А как в ситуации, когда для каждого микросервиса свой репозиторий?
- Как бороться с тем, что во время сборки артефакта в Docker очень быстро забивается свободное место на диске?
- Когда лучше использовать подход Docker-in-Docker?
- Как организовать доставку и развёртывание сервисов в закрытые окружения заказчика?
С версии 20.10 Docker Engine стал rootless. Раньше эта фича была экспериментальной, а теперь оказалась в проде. Изменится ли что-то с точки зрения безопасности и сборки Docker-образов без root-привилегий?
Тимофей Ларкин: Я не думаю, что это сильно на что-то повлияет. Возможно, они идут к тому, чтобы появился отдельный сборщик образов от Docker. Но пока мы используем Docker-in-Docker, и, скорее всего, этот Docker-in-Docker в режиме rootless просто не будет запускаться.
Надо смотреть апдейты к документации основных пользователей этих фич, например, того же Gitlab, который сейчас предлагает пользоваться kaniko. Возможно, когда-то функциональность станет достаточно зрелой, Gitlab обновит документацию и напишет, что можно собирать образы ещё и так. Но пока, я думаю, мы до этого не дошли.
Александр Швалов: В документации Gitlab есть открытый баг (issue), мол, давайте включим режим rootless, но официальной поддержки пока нет. Для сборки поддерживается kaniko, и мы добавили пример с kaniko в наш курс.
Дайте пример реального размещения репозиториев с кодом, секретами и helm-чартами — где всё должно лежать в жизни? Как выглядит по умолчанию шаблон? Боевой deployment.yml не должен быть в репозитории сервиса?
Тимофей Ларкин: Ответ на такие вопросы всегда it depends — зависит от ситуации. Если это open source проект, то там может и не быть деплойментов, там может быть makefile, который покажет, как собрать артефакт, как из него собрать Docker-образ. Но это репозиторий на Github, в лучшем случае он через github actions делает регулярные билды и кладёт их на Docker Hub или другой репозиторий образов контейнеров. Оно и понятно: это просто open source проект, куда его деплоить.
Другое дело, если это проект, который вы деплоите на инфраструктуре: своей, облачной — неважно. Например, это приложение, которое разрабатывается и используется у вас в компании. Действительно, довольно простой способ — держать и код, и скрипты сборки артефактов, и какой-нибудь helm-чарт в одном репозитории. Разнести по разным папкам, а GitLab CI будет и собирать, и сохранять артефакты, и пушить изменения в Kubernetes.
Подход не очень масштабируемый: когда приложений много, становится тяжело отслеживать, что задеплоено в Kubernetes. Чтобы решить проблему, подключают сервисы вроде Argo CD, а код и описание конфигурации хранят в разных репозиториях. Деплоят через CI (push-модель) или через кубы в Argo.
Когда компания занимается заказной разработкой, всё ещё сложнее. Встаёт вопрос, как деплоить код на инфраструктуру заказчика, и готов ли он принять Continuous Deployment исполнителя или хочет больше контроля? Надо смотреть на специфику.
Александр Швалов: У каждой команды свои стандарты, некоторые сложились исторически, некоторые появились на основе шаблонов или документации. В нашем курсе по CI/CD есть примеры, они рабочие — можете адаптировать под свой проект. Исходите из своих потребностей.
Есть ли краткий справочник по полям gitlab-ci файла?
Александр Швалов: Краткого справочника нет, если нужна конкретная фича, лучше идти в документацию и смотреть, что там есть. Ну а базовые принципы — как из большого набора кирпичиков собрать свой gitlab-ci.yml — вы можете почерпнуть из курса или документации. Если в курсе нет, в документации точно будет.
Тимофей Ларкин: Если есть запрос, такой краткий справочник можно сделать, это несложно. Я бы хотел добавить, что мы изначально не хотели перепечатывать документацию и по возможности старались этого не делать, но всё равно получили некоторое количество негативного фидбека: мол, зачем ваш курс, если я могу всё это в документации прочесть.
Как можно привязать Jira к GitLab?
Александр Швалов: У бесплатной версии GitLab есть интеграция с Jira, ищите в соответствующем разделе.
Тимофей Ларкин: Более того, мы работали одновременно с двумя issue-трекерами: все проекты вязались в Jira, но отдельные команды настраивали для своих репозиториев привязку к YouTrack. Это было не очень удобно, потому что надо было ходить по каждому репозиторию, который хочешь привязать. Но да, такая функциональность действительно есть даже в бесплатном GitLab.
Почему job’a release триггерится при изменении тега, хотя в родительском пайплайне стоит only changes?
Александр Швалов: Я провёл небольшое исследование и выяснил: это не баг, это фича. В GitLab был заведён баг, и его закрыли с комментарием, что так задумано. Если вам нужно обойти это поведение, используйте один из двух вариантов, которые предлагает GitLab.
GitLab не обновляет статусы дочерних пайплайнов. Что с этим делать?
Александр Швалов: А вот это уже баг. Возможно, когда-нибудь починят.
Есть ли в GitLab профили переменных? Например, я хочу сделать переменную host, и чтобы она приезжала разная в зависимости от окружения. Есть ли какое-нибудь профилирование? Например, я не хочу называть переменную host_dev, host_prod и host_test, а хочу указать окружение, и оно определённый набор переменных вытащит? Можно ли такое сделать?
Тимофей Ларкин: С ходу на ум мало что приходит. Можно, наверное, какие-то env-файлы держать в репозитории и просто их сорсить в пайплайне.
Нормальная практика — называть host_dev, host_prod, host_test и т. д?
Александр Швалов: Скорее всего, есть встроенные переменные. Если у вас описано разделение по окружениям, то встроенная переменная будет знать, как называется окружение.
Тимофей Ларкин: Я не сталкивался с таким наименованием переменных. Это кажется оправданным, только если один пайплайн должен работать одновременно с несколькими разными окружениями. Если просто хочется разного поведения пайплайна в зависимости от ветки или чего угодно ещё…
У меня в пайплайне может быть стадия deploy и стадия release, и тогда эти переменные должны быть разные, иначе как?
Тимофей Ларкин: То есть сначала один job деплоит на stage, а потом следующий job деплоит на prod?
Нет, job, который на prod работает, он срабатывает, когда only text. Всё это описано в одном пайплайне.
Тимофей Ларкин: Я решал это ямловскими (YAML — прим. редактора) якорями, у меня были очень однотипные job’ы из трёх строчек. Или можно теми же extends, как в примере с Docker. А дальше в каждом job пишешь свой блок variables, поэтому основное тело job’а работает с одними и теми же скриптами, но в зависимости от значения переменной host или переменной environment, оно деплоит на разное окружение.
Мы не имеем разные переменные для разных job’ов, мы используем одни и те же названия переменных, просто с разными значениями. Возможно, оправдано в репозиторий поместить какие-то скрипты, которые сами внутри себя это разруливают, чтобы не раздувать gitlab-ci.yml.
SSH executor создаётся по одному на сервер?
Тимофей Ларкин: Фактически да. Можно, разве что, поставить какой-нибудь tcp-балансировщик и рандомно попадать на тот или иной сервер.
Имеет смысл разделять раннеры, которые деплоят на test и staging, и раннеры, которые деплоят на prod?
Тимофей Ларкин: Наверное, можно. Действительно, запустить раннеры в разных сетевых сегментах, которые не могут общаться друг с другом. В моей практике это не было нужно. У нас была ориентированная на Kubernetes система, а если речь идёт про «SSH на тот сервер, SSH на этот сервер» — наверное, это будет оправдано.
В Kubernetes вы, наверное, по разным namespace деплоили и всё?
Тимофей Ларкин: Да, но при этом все раннеры в одном и том же месте запускались. Был отдельный namespace для раннеров.
Правильно ли я понимаю, что раннер берёт только одну задачу? Потому что иначе переменные окружения пересекутся.
Тимофей Ларкин: Необязательно, это зависит от того, какой executor работает.
Александр Швалов: Есть параметр concurrency. Если раннер один и идёт долгий пайплайн, то получается, что остальные девелоперы сидят и курят бамбук — мы такое проходили, и для обхода настраивали concurrency. В зависимости от ресурсов раннера: сколько job’ов он потянет одновременно, можно настраивать.
Он под каждую job’у своё окружение создаст?
Тимофей Ларкин: Да, он либо свой инстанс в bash запустит, либо несколько SSH-подключений, либо несколько Docker-контейнеров или подов в Kubernetes.
Есть ли в Argo CD и других GitOps-инструментах возможность параметризации реакции на изменения? Например, обновлять prod окружение, только если мастер + тэг или если фича, то в dev окружении менять состояние/производить обновления?
Тимофей Ларкин: В вопросе есть очень распространённое заблуждение, я и сам на нём спотыкался. Надо запомнить: не бывает, чтобы у нас был тег на мастер-ветке. Тег никогда на ветке не бывает. Где-то в GitLab даже есть issue, который это очень подробно объясняет. Но это лирическое отступление.
В принципе, Argo CD может что-то похожее сделать. Но надо понимать, что он не совсем про это. Его основная и довольно простая функция — это чтобы в таком-то месте (namespace или кластере Kubernetes) была задеплоена такая-то ветка, такой-то тег определённого репозитория.
Как мне показалось, в вопросе речь была о пайплайнах и CI/CD. Но это не основная функциональность Argo, и кажется, он такого не поддерживает. Можно посмотреть на другие GitOps-инструменты. По-моему, у werf от «Фланта» есть функционал отслеживания — что там меняется в Docker-репозитории. Но в целом GitOps — это не совсем про это. Вот как в гите опишите, то и будет задеплоено.
На коммит Argo увидит: «О! Что-то поменялось, значит надо поменять это и в Kubernetes», но без какой-то сильно ветвистой логики.
Александр Швалов: Я добавлю, что тэг — это не ветка, а по смыслу ближе к коммиту. Тут поможет семантическое версионирование, и можно настраивать шаблоны для Argo CD. Если для продакшена, то конкретный релиз: 1.2.0, 1.2.1. Для stage будет 1.2.* — любая циферка в конце приедет на stage. Для QA это 1.* — всё остальное приедет. Для совсем свежего, для локальной разработки — просто звёздочка *, любой тег Argo CD будет сразу подтягивать.
Какой сканер посоветуете для Docker-образов? Мне trivy понравился, но может что удобнее есть?
Александр Швалов: Я использовал Trivy, нареканий не было.
Как настраиваются пайплайны для проектов в монорепе, и как в ситуации, когда для каждого микросервиса свой репозиторий?
Александр Швалов: Возможно, мы добавим такой пример в курс. Спасибо за запрос! Вообще, ходят слухи, что у Google и Microsoft всё хранилось или хранится в монорепах — миллиарды строк кода. Здесь многое зависит от человеческого фактора, а не от инструментов. Если говорить о GitLab CI/CD, то всё просто: в шаге сборки какой-то части, например, фронтенда — используем only changes, выбираем каталог и дальше поехали. Что-то изменилось, GitLab производит деплой. С тестами будет посложнее, потому что фронтенд (или какая-то часть, особенно, если это микросервисы) запустится и будет неполноценным. Поэтому для тестов придётся поднимать минимальные зависимости.
Тимофей Ларкин: Я не видел open source систем контроля версий, которые поддерживают такую модель работы, как монорепозиторий. У больших игроков свои системы, и разработчики даже рассказывают: «Вот, я работал в Google/Amazon/Facebook, а потом я ушёл оттуда, пошёл в среднего размера компанию, и я не знаю, что делать. Нигде нет магии этих больших систем, которые сами решают все проблемы с версионированием кода. Внезапно я должен работать с тем же GitLab».
Поэтому если вы не огромная корпорация с ресурсом написать свою систему контроля версий, то можно костылить пайплайны, чтобы они были как монорепозитории — писать кучу “only changes” на различные куски. До каких-то масштабов это, наверное, будет работать. Взять тот же Kubernetes, который выпускает свои известные 5 бинарей (и даже чуть больше) с параллельным версионированием. Нет такого, что один компонент в своём репозитории, другой в своём, и у них свой набор версий. Нет, они выпускаются из одного репозитория, поэтому у них хэши комитов, теги и всё остальное — одинаковое. Да, так можно работать. Go позволяет собирать несколько бинарников из одного модуля, но в целом так не очень легко работать. Для какого-то проекта — да.
В масштабах организации, наверное, у вас есть несколько продуктов, которые логически не связаны и их не стоит пихать в монорепы. По крайней мере, не стоит для этого пытаться использовать GitLab.
Ну и как всегда: если хотите оставаться в парадигме «один репозиторий — один микросервис», тогда где-то храните метаданные (какой хеш коммита соответствует какому тегу, какому релизу) и с помощью Argo оркестрируйте всё это.
Какие существуют best practices по размещению раннеров? Как лучше организовать размещение нескольких раннеров на одной машине, чтобы можно было добавлять новые? Стоит ли размещать раннеры в Docker-контейнерах, или лучше использовать виртуальные машины, например, kvm?
Александр Швалов: GitLab для раннеров по запросу предлагает использовать Docker Machine, и соответственно использует драйверы оттуда — это всяческие облака и виртуализации (AWS, Azure, VirtualBox, Hyper V, vmWare). KVM в списке нет. Для множественных раннеров можно настраивать также шареный кэш. Например, в AWS S3 хранилище.
Однако этот подход через Docker Machine сам GitLab считает малость устаревшим. Есть открытый баг, где разработчики размышляют, куда лучше перейти, какие-то варианты есть. Самый очевидный — перейти в Kubernetes. Ну а best practice в общем — не размещать раннер на одном хосте с GitLab, чтобы они друг друга не аффектили. Ну и на продакшене раннер тоже лучше не размещать, потому что вдруг туда что-то прилетит критичное.
Для небольших проектов достаточно одного раннера (на своих проектах я так и делал). Просто настроить ему concurrency, чтобы разработчики не стояли в очереди друг за другом, и дать какое-то количество процессора и памяти, настроить опытным путём — и так жить.
Тимофей Ларкин: Когда мы пайплайны гоняли в Kubernetes, то мы просто на несколько хостов вешали taint с эффектом PreferNoSchedule, чтобы пользовательские нагрузки приложения запускались преимущественно где-то на других хостах. Но при этом на раннеры мы вешали nodeSelector как раз на эти же самые хосты. Это к вопросу о разделении нагрузки от приложения и нагрузки от раннера.
Да, есть беда, что у сборок совершенно другой профиль нагрузки, чем у обычных приложений. Как правило, сборка — это хорошая нагрузка на CPU (обычно именно на CPU, а не на память). Затем идёт сборка Docker-образа, (если мы более-менее всё аккуратно делаем: сначала собрали артефакт, затем его как артефакт передали в следующий этап пайплайна, а затем в Docker-образ кладём готовые бинарники), то идёт нагрузка на диск, потому что там все эти слои формируются, пишутся и, наконец, после этого мы загружаем сеть, перекачивая собранный Docker-образ в наш registry.
Из-за всех этих неровностей лучше раннеры держать подальше от обычных приложений. И они достаточно много ресурсов требуют. Когда мы занимались ограничениями, мы ставили командам лимит 4 ядра, и у них сборка какой-нибудь тяжелой Java существенно замедлялась.
С Kubernetes всё понятно. Мне даже хотелось убрать раннеры из Kubernetes и где-то отжать два хоста, которые использовать как build-серверы, чтобы совсем всё отдельно было. Всякие деплои, понятно, это очень легковесная задача. Запушить ямлик в Kubernetes никаких ресурсов не требует. Ну а если у нас SSH или shell-раннер, то тогда сама ситуация диктует, где их размещать. А если вопрос про бинарь GitLab Runner, то он очень мало ресурсов потребляет, его можно где угодно расположить. Тут больше зависит от требований сетевой доступности.
Когда лучше использовать подход Docker-in-Docker? Какие еще есть инфраструктурные идиомы, связанные с GitLab?
Александр Швалов: Скажу очевидную вещь: использовать Docker-in-Docker стоит, когда у вас сам раннер запущен в Docker. Ну а если у вас нужно запускать какие-то команды в Docker… как это задумывалось вообще: если раннер запущен в Docker, то вы можете просто в Docker-in-Docker брать другой Docker-образ (Python, например, и в нём выполнять какие-то действия из кода).
Тимофей Ларкин: Я буду чуть более категоричен. Docker-in-Docker стоит использовать почти никогда. Бывают случаи, когда мне надо собрать кастомный образ kaniko, но когда я пытаюсь собрать его через kaniko, то всё уходит в бесконечную рекурсию и падает (есть такие интересные особенности). Тогда приходится использовать Docker-in-Docker. Кроме того, Docker-in-Docker можно использовать на какой-нибудь виртуалке, которой мы сделали хорошую изоляцию ото всего, чтобы там вообще нельзя было дотянуться ни до инфраструктуры, ни до ещё чего-то, чтобы там можно было только Docker-образы собирать.
В остальных ситуациях Docker-in-Docker — это огромная зияющая дыра в безопасности. Очень легко использовать: у тебя есть root-права, у тебя есть привилегии, ты можешь монтировать хостовую файловую систему. Накатал Dockerfile, в котором первым шагом устанавливаешь SSH-демон, прокидываешь туннель туда куда надо, потом заходишь на эту машину и с root-правами монтируешь на эту машину dev/sda1 — и всё, у тебя доступ к хосту, ты делаешь что хочешь.
Александр Швалов: Лучше посмотреть в сторону новомодных Podman, Buildah и kaniko. Совсем недавно были новости, что Kubernetes хочет отказаться от Docker — все схватились за голову, но это в принципе ожидаемо. И сам Docker (мы с этого начали) уже выкатил rootless mode. Поэтому всеми силами стоит уходить от выполнения от root.
Как можно бороться с тем, что когда происходит сборка артефакта в Docker, очень быстро забивается свободное место на диске (ну кроме docker prune -a)?
Александр Швалов: Только одно решение — выделить больше диска, чтобы хватало этого запаса на тот период, когда у вас срабатывает по расписанию сборка мусора. Либо использовать одноразовые раннеры где-то в облаках.
Тимофей Ларкин: Регулярно подчищать за собой: docker prune -a. Совершенно точно плохая практика — использовать хостовый Docker-демон для этих сборок. Потому что доступ к хостовому демону — это огромная дыра в безопасности, мы можем делать всё что угодно на хосте от имени рута. Ну и плюс, если мы для сборки используем хостовый Docker-демон, то он моментально забивается всяким мусором.
Допустим, даже не используя никакой хостовый Docker-демон, даже имея политику подчистки Docker-образов в GitLab registry, когда мы только стартовали, у нас был раздел под GitLab на 250 Гб. Потом мы стали упираться, сделали отдельный раздел под GitLab на 250 Гб, а под GitLab registry ещё один на 250 Гб. У нас GitLab Omnibus подключал два persistent volume одновременно. Потом раздел под registry разросся до 500 Гб, сейчас он, кажется, 750 Гб и надо узнать у бывших коллег, что у них там происходит — хватает места или надо ещё что-то придумывать. И это при том, что есть политика удаления всех, кроме последних пяти тегов какого-то образа. И это без всяких артефактов сборок, это просто конечные образы, которые дальше запускаются на каких-то окружениях.
Как организовать мирроринг стороннего репозитория (например, из GitHub) в GitLab средствами самого GitLab? То есть чтобы автоматически в GitLab подтягивались все изменения, обновления из стороннего репозитория, новые теги и т. д. Без необходимости периодически делать pull «руками», без использования скриптов или сторонних решений для автоматизации этого процесса. Если нельзя обойтись без сторонних решений, то какое из них вы бы порекомендовали?
Александр Швалов: Сразу скажу, что полная поддержка этой функциональности есть в платной версии Starter. Для ускорения автоматики можно дополнительно использовать вебхуки в GitHub, чтобы он при каждом чихе тыкал палочкой в GitLab и GitLab в ответ на это делал pull из GitHub. Если надо обойтись исключительно бесплатной версией, то мне не приходилось этим заниматься, скорее всего, придётся использовать дополнительные сторонние скрипты. Сходу можно порекомендовать настроить для этого CI/CD пайплайн: грубо говоря, можно делать операции с гитом на уровне раннера и запускать это всё по расписанию. Но это, конечно, костыль.
Тимофей Ларкин: Я бы не брал этот подход. Это очень способствует плохим практикам. Чаще всего проблемы возникали, когда мы работали с внешними подрядчиками, которые упорно не хотели хранить свой код в нашем GitLab, а хранили его в своём. Поскольку это большая корпорация, собственные TLS-сертификаты самоподписные и так далее, то подрядчик не знает, как их себе в системное хранилище добавить, или ещё какая-то беда — в результате всегда было довольно тяжело получить от подрядчика не просто артефакт, а код. Потому что «а зачем? а мы попытаемся помиррорить! не работает — ладно, тогда будем на своём GitLab работать». Возможно, есть ситуации, когда это важная и нужная функциональность, но частенько это абьюзится.
Какой аппаратный ресурс наиболее востребован для инстанса GitLab в docker-контейнере: процессор, оперативная память или хранилище? А для раннеров?
В случае, если есть только один мощный сервер с мощным процессором, большим объемом оперативной памяти и большим хранилищем и еще один-два сервера меньшей мощности с процессорами послабее, как наиболее оптимально задействовать первый сервер для развертывания GitLab-инфраструктуры
(то есть самого GitLab и раннеров) и что лучше перенести на сервера меньшей мощности? Как целесообразно в этом случае размещать раннеры: в Docker-контейнерах на хосте или в виртуальных машинах (например, kvm)?
Ориентировочная нагрузка на инстанс GitLab: 100 пользователей, 200 проектов.
Александр Швалов: Как адепт классических решений, я бы предложил KVM как более проверенное решение. Docker-контейнеры для меня — это до сих пор что-то эфемерное: сейчас запустил, через 15 минут можно грохнуть. GitLab же должен работать и работать, там вы храните свою конфигурацию. Зачем его поднимать, гасить?
Требования по железу есть у самого GitLab. Для 100 пользователей нужно 2 ядра (хватит до 500 юзеров) и 4 Гб памяти (до 100 юзеров). При расчёте объема диска лучше исходить из простой математики: объём всех репозиториев, которые есть, умножить на 2. И не забыть продумать, как вы будете добавлять к серверу новые диски, если репозитории разрастутся.
Железные требования для раннеров предсказать сложно. Зависит от проектов, что вы там собираете: html-страницы или java-код. Надо взять изначальные требования к сборке и от них отталкиваться. Возможно, стоит взять что-то виртуальное, докинуть ресурсов и настраивать по необходимости.
Тимофей Ларкин: Увидев этот вопрос, я специально попросил у коллег графики по потреблению GitLab. Там всё не так весело. Их инстанс GitLab так-то на 500 пользователей, но реально что-то разрабатывают не более 200 человек. Там безупречно ровная полка… ну как, колеблется от 1,5 до 2 ядер на протяжении нескольких дней, возможно, по ночам чутка потише. Полка по памяти в районе 50 Гб тоже довольно стабильно.
То есть возвращаясь к рекомендациям: по ядрам, наверное, они реальны, а по памяти — занижены. 4 Гб хватит только для запуска, а для активной работы понадобится гораздо больше. И это даже вместе с тем, что базы данных PostgreSQL, которые под этим GitLab, они сейчас живут на отдельных хостах. Раньше, по моим наблюдениям, процессор загружался гораздо сильнее.
Независимо от способа деплоя я бы запустил GitLab на жирном хосте: нам важна надёжность GitLab, многие его компоненты достаточно прожорливы. На других хостах поменьше, наверное, можно было бы гонять Docker executor.
Интересует деплой не в Kubernetes. Допустим, по SSH или же docker\docker-compose.
Александр Швалов: Да, это популярный запрос. Мы планируем добавить это в наш курс (на момент публикации статьи уже добавили — прим. редактора) — деплой в простой Docker. Всё делается очень просто: раннер с предварительно настроенными ключами заходит по SSH на хост, делает там docker stop, docker rm (удаляет старый контейнер) и docker run с прямым указанием на конкретный образ, который мы только что собрали. В результате поднимается новый образ.
Голый Docker это не оркестратор, и репликации там нет, поэтому при таком CI/CD у вас будет перерыв в обслуживании. Если у вас нет образа контейнера, в моём примере лучше его запустить самостоятельно.
Тимофей Ларкин: Если интересует совсем голый SSH, то пишите скрипты и запускайте. Можем, наверное, минимальный пример в курс добавить. Но надо понимать, что Kubernetes уйму проблем с оркестрацией решает, ну и Docker тоже достаточно можно решает (перезапуски, healthcheck, что угодно).
Если я был вынужден описывать голый SSH, наверное, я бы запускал что-нибудь через systemd. Да, можно Ansible использовать, но опять же, через тот же systemd.
Александр Швалов:Если ещё нет образа контейнера на хосте (я вспомнил, как это у меня делалось), там тоже через Bash проверяется, есть что-нибудь или нет. Если нет, то делаем docker run без всего; docker run, и конкретный образ из registry, который только что создан. Если что-то есть, то сначала останавливаем это всё, и после этого docker run.
Можно ли контейнер с раннером создавать динамически (только на момент сборки)?
Александр Швалов: Да. Очень популярно брать дешёвые инстансы AWS и запускать раннеры там, а потом их глушить по прошествии какого-то времени. Пошла активная сборка, пошёл деплой, насоздавались раннеры и через какое-то время, когда нагрузки нет, они сами по себе схлопнутся. Это всё реализуется через Docker compose.
Тимофей Ларкин: Мы говорим про GitLab runner, который управляющий бинарник, или мы про сами пайплайны? Ну да, пайплайны, наверное. А сам управляющий бинарь? Тогда что будет триггерить создание этого самого бинаря? Опять возникают проблемы курицы и яйца.
Александр Швалов: В Kubernetes, насколько я знаю, можно через какие-то метрики, когда нагрузка есть, он создаёт… Так же для OpenShift я нашёл, есть оператор, который управляет раннерами. Как-то можно автоматизировать, люди движутся в этом направлении. Но, как правило, на простых проектах, если что-то нужно, мы берём и виртуалке добавляем ресурсов, а когда проходит час пик — убираем ресурсы.
Тимофей Ларкин: Автоскейлинг нод можно делать. Потому что так-то Docker-контейнеры с пайплайнами создаются автоматически только на время существования пайплайна по дефолту. Управляющий бинарь должен существовать по дефолту. Иначе как кто-то узнает, что надо создавать управляющий бинарь?
Как можно настроить шаринг раннера только между определённым количеством проектов?
Александр Швалов: Для этого в GitLab есть группы, создаёте группу, привязываете раннер и в эту группу добавляете проекты. Доступ юзеров, соответственно, распределяется. Всё просто!
Тимофей Ларкин: Ссылка на issue, где описывается, как это делать. Необязательно даже, чтобы это был раннер на группу. Можно делать раннер на конкретный список репозиториев. Первый создаётся через регистрационный токен на какой-то конкретный репозиторий, но потом, через UI GitLab можно добавить его ещё нескольким. Можно ещё тегами всё это разрулить.
Прошу рассказать, если есть опыт, о практике организации доставки и развертывания сервисов в закрытые окружения заказчика, когда нет возможности «прорубить» доступ до внутренних репозиториях заказчика. Как при этом упростить доставку артефактов и по максимуму автоматизировать развертывание в условиях, когда Git находится далеко снаружи runtime-окружения?
Александр Швалов: У меня, к сожалению, не было такого опыта. Я знаю, что в серьезных организациях такое сплошь и рядом практикуется. Я могу лишь придумать такой способ: взять артефакт, сделать архив с релизной веткой репозитория, принести на флешке, там есть внутренний GitLab, сделать push в нужную ветку и сделать CI/CD как обычно, только в локальной сети.
Тимофей Ларкин: Вообще, я к таким историям скептически отношусь. Когда заказчик говорит, что у него невероятно секретно, гостайна (номера карт лояльности клиентов) и всё такое, то надо посмеяться и понять, что он врёт, и не работать с такими заказчиками. Но если работать очень надо (в конце концов, нам всем надо счета оплачивать и еду покупать), то есть вариант — разместить раннер (управляющий бинарь; и пайплайны тоже будут где-то рядом запускаться) именно внутри контура заказчика.
Раннер умеет работать за NAT, умеет постучаться во внешний GitLab. Главное, чтобы сам GitLab не был за NAT, чтобы была нормальная доступность до GitLab. Поэтому да, раннер может изнутри контура заказчика сходить в ваш GitLab, стянуть код и делать сборку уже внутри инфраструктуры заказчика. И тогда чуть легче: артефакт сборки кладётся во внутренний репозиторий заказчика и оттуда уже деплоится — всё хорошо. Не исключено, что там будет много сложностей. Наверняка, у заказчика свои самоподписные TLS-сертификаты, у него интернет недоступен на большинстве хостов (надо будет согласовать proxy, которая позволит раннеру ходить до вашего GitLab) и так далее.
Александр Швалов: Если proxy, NAT недопустимы, то в таком варианте остаётся паковать всё на своей стороне, собирать в инсталлятор, приходить к заказчику и обновлять приложение инсталлятором. Это уже другая задача, к CI/CD она вряд ли относится. Хотя можно настроить CI/CD, чтобы на выходе получался инсталлятор.
Тимофей Ларкин: Ну да, или держать все эти артефакты у себя в инфраструктуре, заказчику выдать публичный или приватный ключ, и просто ему на почту писать: «Мы сделали новый этап работы, выложили новую версию, приходите забирайте».
А вообще я считаю, что такая ситуация возможна только в случае, если заказчик заплатил очень большие деньги или менеджер провалил переговоры. Потому что как в таком случае работать: постоянно ездить к заказчику? В принципе, если разработчики готовы ездить на территорию заказчика с флешками, это тоже вариант. Но для меня это фактически deal breaker, если заказчик предложит подобное.
Может нам как-то помочь CI/CD GitLab, если поставщик сам присылает собранные бинари в zip-архиве, и эти бинари необходимо распределить на нужное количество нод? Где это будет работать?
Александр Швалов:Речь о том, что есть в качестве исходного кода бинари в zip-архиве, и GitLab CI будет их каким-то образом распределять? В принципе, такое возможно. Почему нет? Можно это как-то сканировать, тестировать и деплоить, просто по SSH закидывать. В принципе, можно обойтись и без GitLab, одними скриптами.
Тимофей Ларкин: Можно какую-нибудь регулярную job’у запилить, которая, допустим, смотрит на папку, проверяет сумму у zip-архива, если обновилась, распаковывает, раскладывает его на внутренние nexus (приватный docker registry — прим. редактора) в виде артефактов. Если надо, деплоит. Да, я думаю, GitLab может помочь в плане автоматизации этого процесса.
Может быть интересно:
- Интенсив по Python для инженеров и разработчиков
- Курс по GoLang для инженеров
Контейнеризация понятным языком: от самых азов до тонкостей работы с Kubernetes. Часть 1
Чем контейнеры отличаются от виртуальных машин, почему 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 состоит из нескольких компонентов, которые выполняют каждый свою роль (подробно о них ещё поговорим). Из этого вытекает две особенности:
- Компоненты друг друга не аффектят. Если один упал, то все остальные могут продолжить работать.
- Эти компоненты работают по pull-модели. Нет никакого центрального компонента, командира, который всем раздаёт команды, а как сдох, так все не знают что делать. Каждый компонент выполняет свою часть работы: сделал, завершил. Если он умер, ну значит этот кусочек не выполнится.
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, но для этого надо несколько строчек в манифест добавить и сказать, как именно проверять.
На данный момент поддерживается три возможности проверять приложение:
- http-get — это http-запрос в контейнер на инпоинт, и мы видим, работает оно, не работает, отвечает, не отвечает. С 200 по 399 код — это ок, если 301 даже редирект — это ок. Если 404 — это не ок. 500 тем более.
- exec — мы внутрь контейнера делаем какой-то запрос, какую-то команду, проваливаемся. Например, select 1, проверяем, всё ли нормально с базой.
- tcp socket — Kubelet просто проверяет доступные сокеты. Если все хорошо, то все хорошо.
Liveness проба — это контроль за жизнью приложения. Постоянно Kubelet смотрит, ходит и смотрит. Там гибкие настройки, можно написать, как часто ходить, как проверять и так далее.
Readiness проба — проверяет, а готово ли приложение принимать трафик. Потому что разные истории могут быть, это могут быть разные инпоинты у приложения. Мы проверяем, работает ли приложение, готово ли оно принимать трафик.
Startup проба – это больше для легаси таких историй, которые очень долго поднимаются, молотятся в самом начале. То есть очень долго инициализируются. И startup проба проверяет, запустилось ли вообще приложение.
На каком размере архитектуры может работать Kubernetes? Может ли он контролировать сразу миллион инстансов?
Насколько я помню из документации, это 5000+, что ли, нод. На одной ноде по умолчанию можно запустить 110 подов, 110 экземпляров приложения.
Под — это экземпляр одного и того же приложения? Или могут быть два разных контейнера на одном серваке и это будет два разных пода?
Под — это абстракция, в которой запускается приложение. Тут важно понять, что это не какая-то физическая оболочка, не какой-то процесс ещё один, это скорее именно абстракция, с которой работает Kubernetes.
Kubernetes не умеет работать с контейнерами. Мы не можем сказать: «заскейль нам вот это приложение в трёх контейнерах». Мы можем только сказать: «заскейль нам в трех подах». В поде может быть как один контейнер, так и несколько. То есть мы можем туда запихнуть, например, nginx и php-fpm в связке, и они будут скейлится по два контейнера в связке.
Но тут надо понимать, что хорошая практика — засовывать в контейнер неделимые части приложения. Всё-таки, если 2-3 контейнера надо засовывать, то может, стоит ещё поиграться с логикой приложения. Обычно один контейнер засовывают и там есть еще второй, который сам запускается, это pause контейнер, который держит сетевой namespace, чтобы все контейнеры в поде были в одном namespace, и чтобы всё хорошо работало.
Это первая часть беседы. Во второй будет про хранение данных в Kubernetes и про Ansible. Не пропустите!
Вопросы задавал Лекс АйТиБорода @ iamitbeard