Развитие контейнерной инфраструктуры Мир Plat.Form
Эффективность — как часто в своей жизни мы слышим это слово. Эффективность бывает самая разная и обычно под ней подразумевают улучшение тех или иных бизнес-процессов.
Мы же поговорим сегодня об эффективности чисто технологической — как бы нам так сделать, чтобы сервера были загружены поплотнее, и управлять ими было удобней, а результат получился надежным и безопасным.
Я — Антон Будкевич, глава разработки IT-решений НСПК Мир Plat.Form, и сегодня с мы Олегом Чирухиным @olegchir — деврелом в Axiom JDK и топ-1 контрибьютором в блог Java на Хабре — поговорим о контейнеризации.
В далеком 1965 году, один из основателей Intel, Гордон Мур, сформулировал свой «Закон Мура». Интегральные схемы появились всего шесть лет назад, и Мур отметил закономерность: каждый новый чип содержал примерно вдвое больше транзисторов, а новые модели появлялись примерно каждый год.
С тех пор закон Мура немного потускнел в смысле количества транзисторов, но его эквивалент мы видим в облачной инфраструктуре. Серверные мощности растут каждые 2–3 года, и не было момента, когда нам как разработчикам, не хотелось бы заполучить в свои руки всё больше и больше ресурсов.
С другой стороны, появляются свои особенности и ограничения. Например, хочется, чтобы сервера не простаивали. Как мы загрузим их работой? Нам видятся, как минимум, три варианта:
Запуск приложений непосредственно в операционной системе сервера, на «голом железе».
Установка на сервере платформы виртуализации. На этой платформе запускаются виртуальные сервера, и уже в этих виртуальных серверах работает наш софт.
Поднятие платформы контейнеризации и запуск приложений в контейнерах.
Вероятно, можно придумать что-то ещё, но это основные способы развертывания, встречающиеся на практике. В Мир Plat.Form используется множество современных архитектурных решений, применение контейнеров — один из вариантов.
Поскольку Docker это уже, наверное, синоним термина «контейнер», то про него и поговорим.
Первые эксперименты
Первые эксперименты НСПК с Docker начались в 2016 году, когда компании было два с небольшим года. В это время мы начали разрабатывать платформу нового проекта. При разработке и тестировании нужно развертывать инфраструктуру локально, поэтому мы попробовали сделать это в Docker. Как и обещалось в рекламе, сделать это оказалось очень просто. Запуск minio — одна команда, PostgreSQL — ещё одна. Дальше — больше. Нам в руки попал docker-compose, и это оказалось очень удобно: в едином файле описываются все компоненты системы, а ее запуск происходит с помощью «docker-compose up». Удобно. Но это было только начало.
Долгий путь к внедрению
Первый минус Docker — разная среда для разработки, тестирования и прода. Зачастую, Docker рекламируется именно как унифицированная среда. Но если прод ещё не готов к таким переменам и не рассчитан на запуск контейнеров, то всё происходит с точностью до наоборот. Вы разрабатываете всё в контейнерах, а потом вытаскиваете свой код наружу. Что же могло пойти не так.
Раньше приложения жили в виртуальных машинах или на голом железе. Это была понятная конфигурация, которую привычно эксплуатировать на проде (можно говорить «сервере промышленной эксплуатации», но слово «прод» звучит куда приятней и короче).
Получается, локально у разработчика всё работает в правильном контейнере, а на стенде препрода, где никаких контейнеров нет, установлена не та версия JRE. Или не хватает какой-нибудь библиотеки, или ещё что-то важного.
В общем, проблема в том, что нужно параллельно поддерживать три совершенно разных плана развертывания — для теста, для препрода, для прода.
Докер попал на прод в ходе внедрения новой архитектуры при создании новой платформы. Несмотря на премудрости Continuous Delivery, поддержка сразу нескольких планов развертывания оказалась накладной и приходилось закладывать дополнительное время на отладку и написание всевозможных скриптов.
Но ведь скрипты тоже надо тестировать! Это дополнительное время.
Говорят «время — деньги». В нашем случае, это не только деньги, но и наше место в конкурентной борьбе, добрая репутация и имидж, и так далее. Поэтому мы решились на создание нашей новой платформы уже с применением Docker Swarm.
Docker Swarm — это инструмент оркестрации контейнеров, который позволяет создавать полномасштабные кластеры и делать в них балансировку нагрузки, обнаружение сервисов, автоматическое резервирование, позволяет управлять сетями и томами, и так далее.
Нам хотелось запустить прод сразу в кластеризованном режиме. Архитектура получилась непривычная, в ней stateless-приложения общались между собой внутри кластера, а хранилища (базы данных, кэш) находились вне его.
Переход на Kubernetes
На архитектуре, построенной вокруг Swarm, мы жили довольно долго. Но к 2020 году мы осознали, что пора двигаться вперед и развернули Kubernetes.
Теперь все новые проекты мы разрабатываем, упаковывая софт в Helm Charts. Это позволяет получить «из коробки» мониторинг, управление ресурсами, автоматическое распределение подов в кластере, и многое другое.
По сути, инфраструктура на основе Kubernetes позволяет поднять плотность вычислений, и Kubernetes за этим сам проследит. Переход на Kubernetes хоть и не прост, но полностью оправдывает вложенные усилия.
Безопасность
Конечно же, вам недостаточно запустить команду «apt-get install docker» и надеяться, что Docker сам по себе достаточно безопасный, чтобы ничего больше не делать. Вам нужно закрыть API от злоумышленников, нужно выпустить сертификаты, настроить TLS, внимательно изучить все параметры конфигураций, настроить оптимальные режимы, соответствующие нагрузкам и многое другое.
Мы специально тщательно изучали все моменты, связанные с безопасностью контейнеров. Мы подключили шифрование трафика, настроили контейнеры для работы в режиме «только чтение», и так далее.
Кстати, если вас интересует тема безопасности, стоит взглянуть на фреймворк CIS Docker Benchmarks («CIS» — это аббревиатура для «Center for Internet Security»).
После того, как все работы завершились, один из разработчиков сказал памятную фразу: «Я раньше любил докер. Теперь я его ненавижу».
Сборка контейнеров
Образы контейнеров можно получить самыми разными способами. Например, скачать готовый базовый образ с Docker Hub и положить туда свое Java-приложение.
А можно собрать его самостоятельно с тем ПО, которое действительно нужно. На каждом шагу стоят выборы и развилки. Какая операционная система подойдет лучше? Alpine или Centos? Ubuntu или Debian? Всё ли ПО совместимо друг с другом, и, если нет — что нам обязательно нужно использовать, а от чего — отказаться?
По-хорошему, хотелось бы ни от чего не отказываться. Тем более, что наш процесс DevSecOps требует, чтобы основное системное ПО было протестировано на совместимость. Поэтому мы используем отечественные контейнеры Axiom Runtime Container Pro, и вам рекомендуем на них взглянуть.
Помните начало этой статьи, когда мы встретились с проблемой: на компьютерах разработчиков Java была в контейнере, а на проде работало голое железо и виртуальные машины, и нам пришлось писать несколько планов развертывания. Команда разработчиков российской платформы Java Axiom JDK физически сидит в Санкт-Петербурге и разрабатывает экосистему, цель которой — универсальная среда, где у тебя и в контейнере, и вне его находится один и тот же дистрибутив Java. То есть, на тесте и проде будут совместимые сценарии развертывания, даже если Kubernetes еще не внедрен.
Небольшая иллюстрация, как это вообще работает. Выбор между Alpine и Debian — неприятный. Alpine маленький по размеру, но он может в определенных случаях проседать под нагрузкой на проде или вести себя странно, потому что там внутри работает musl вместо glibc. А Debian работает на glibc, и в тех же самых местах с нагрузкой справляется. Но Debian огромный, он весит сотни мегабайтов. Логично в разных случаях использовать разные дистрибутивы. В качестве основной операционной системы использовать Alpine, и изредка переходить на Debian. Так обычно и делают, но это приводит к первоначальной проблеме: нам нужно несколько планов развертывания и несколько разных скриптов. Хотя бы потому, что состав пакетов в Alpine и Debian отличается.
В случае с линейкой продуктов Axiom JDK такой проблемы нет: у них дистрибутив не только основан на Alpine, но и поставляется в двух вариантах — один собран для musl, другой — для glibc. Просто изменяя название базового образа, можно переключаться на правильный дистрибутив, не меняя никаких скриптов. Точно так же, можно пойти в Dockerfile, сказать «apk add jemalloc» — и это всё, что нужно для переключения между нативными аллокаторами памяти, чтобы адаптироваться к нагрузке. Эта команда без изменений переносится между различными контейнерами, с разными версиями Java.
Но наверное, самое важное для нас: на текущий момент, это самые маленькие контейнеры по размеру в мегабайтах. Это позволяет больше экономить на облачных ресурсах. Образ вместе с JDK весит примерно в три раза меньше, чем аналогичный образ с Debian. В общем, Axiom JDK Pro и Axiom Runtime Container Pro, внесенные в реестр российского ПО — очень полезные инструменты для использования в облаках.
Заключение
В этой статье мы посмотрели, какие проблемы встречаются на пути контейнеризации больших приложений на примере истории развития Мир Plat.Form, и обсудили конкретные технологии — Docker, Docker Swarm, Kubernetes, CIS Docker Benchmarks, Alpine, Debian, Axiom JDK и Axiom Runtime Container.
На этом история не заканчивается, в следующих статьях мы продолжим делиться нашими наблюдениями и инсайтами, и расскажем новые нюансы использования современных технологий в большом продакшене. До новых встреч!