Модернизация приложений для Kubernetes

Вступление

Современные приложения без сохранения состояния создаются и предназначены для работы в программных контейнерах, таких как Docker, и управляются кластерами контейнеров, такими как Kubernetes. Они разработаны с использованием принципов и шаблоновCloud Native иTwelve Factor, чтобы минимизировать ручное вмешательство и максимизировать переносимость и избыточность. Миграция виртуальных машин или приложений на основе «голого железа» в контейнеры (называемые «контейнеризацией») и развертывание их внутри кластеров часто приводит к значительным изменениям в том, как эти приложения создаются, упаковываются и доставляются.

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

Подготовка заявки на миграцию

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

Извлечь данные конфигурации

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

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

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

Вот быстрый пример, демонстрирующий, как вывести два значения конфигурацииDB_HOST иDB_USER из простого кода приложения PythonFlask. Мы сделаем их доступными в работающей среде приложения как env vars, откуда приложение будет их читать:

hardcoded_config.py

from flask import Flask

DB_HOST = 'mydb.mycloud.com'
DB_USER = 'sammy'

app = Flask(__name__)

@app.route('/')
def print_config():
    output = 'DB_HOST: {} -- DB_USER: {}'.format(DB_HOST, DB_USER)
    return output

Запуск этого простого приложения (обратитесь кFlask Quickstart, чтобы узнать, как) и посещение его конечной веб-точки отобразит страницу, содержащую эти два значения конфигурации.

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

env_config.py

import os

from flask import Flask

DB_HOST = os.environ.get('APP_DB_HOST')
DB_USER = os.environ.get('APP_DB_USER')

app = Flask(__name__)

@app.route('/')
def print_config():
    output = 'DB_HOST: {} -- DB_USER: {}'.format(DB_HOST, DB_USER)
    return output

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

export APP_DB_HOST=mydb.mycloud.com
export APP_DB_USER=sammy
flask run

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

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

Состояние приложения разгрузки

Собственные приложения Cloud запускаются в контейнерах и динамически управляются кластерным программным обеспечением, таким как Kubernetes или Docker Swarm. Определенное приложение или служба могут быть сбалансированы по нагрузке на несколько реплик, и любой отдельный контейнер приложения должен быть в состоянии сбой, с минимальным или вообще без прерывания обслуживания для клиентов. Чтобы включить это горизонтальное избыточное масштабирование, приложения должны разрабатываться без сохранения состояния. Это означает, что они отвечают на запросы клиентов без локального сохранения постоянных данных о клиентах и ​​приложениях, и в любой момент времени, если контейнер запущенного приложения уничтожается или перезапускается, критические данные не теряются.

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

Для приложений с состоянием, которым требуется постоянное хранилище данных (например, реплицированная база данных MySQL), Kubernetes имеет встроенные функции для присоединения томов постоянного хранения блоков к контейнерам и модулям. Чтобы гарантировать, что Pod может поддерживать состояние и получать доступ к тому же постоянному тому после перезапуска, должна использоваться рабочая нагрузка StatefulSet. StatefulSets идеально подходят для развертывания баз данных и других долговременных хранилищ данных в Kubernetes.

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

Чтобы узнать больше о дизайне и архитектуре облачных микросервисов без сохранения состояния, обратитесь к нашемуKubernetes White Paper.

Осуществлять проверки здоровья

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

Чтобы правильно сообщить о работоспособности приложения в плоскости управления Kubernetes, вы должны реализовать пользовательские проверки работоспособности приложения, которые указывают, когда приложение работает и готово к приему трафика. Первый тип проверки работоспособности называетсяreadiness probe и позволяет Kubernetes узнать, когда ваше приложение готово к приему трафика. Второй тип проверки называетсяliveness probe и позволяет Kubernetes знать, когда ваше приложение исправно и работает. Агент Kubelet Node может выполнять эти проверки при запуске модулей, используя 3 различных метода:

  • HTTP: зонд Kubelet выполняет HTTP-запрос GET для конечной точки (например,/health) и завершается успешно, если статус ответа находится между 200 и 399.

  • Команда контейнера: Зонд Kubelet выполняет команду внутри работающего контейнера. Если код выхода равен 0, то проверка завершается успешно.

  • TCP: Зонд Kubelet пытается подключиться к вашему контейнеру через указанный порт. Если он может установить TCP-соединение, то проверка завершается успешно.

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

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

Вот минимальная конечная точка работоспособности для примера Flask, упомянутого выше:

env_config.py

. . .
@app.route('/')
def print_config():
    output = 'DB_HOST: {} -- DB_USER: {}'.format(DB_HOST, DB_USER)
    return output

@app.route('/health')
def return_ok():
    return 'Ok!', 200

Проверка жизнеспособности Kubernetes, которая проверяет этот путь, будет выглядеть примерно так:

pod_spec.yaml

. . .
  livenessProbe:
      httpGet:
        path: /health
        port: 80
      initialDelaySeconds: 5
      periodSeconds: 2

ПолеinitialDelaySeconds указывает, что Kubernetes (в частности, Node Kubelet) должен проверять конечную точку/health после ожидания 5 секунд, аperiodSeconds сообщает Kubelet проверять/health каждые 2 секунды.

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

Код инструмента для регистрации и мониторинга

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

Одним из инструментов, который вы можете использовать для мониторинга своих сервисов, являетсяPrometheus, набор инструментов для мониторинга и оповещения систем с открытым исходным кодом, размещенный на сервере Cloud Native Computing Foundation (CNCF). Prometheus предоставляет несколько клиентских библиотек для оснащения вашего кода различными типами метрик для подсчета событий и их продолжительности. Например, если вы используете фреймворк Flask Python, вы можете использовать PrometheusPython client для добавления декораторов к функциям обработки запросов, чтобы отслеживать время, затраченное на обработку запросов. Эти показатели затем могут быть извлечены Prometheus в конечной точке HTTP, например/metrics.

При разработке инструментария вашего приложения полезно использовать метод RED. Он состоит из следующих трех ключевых показателей запроса:

  • Оценить: количество запросов, полученных вашей заявкой

  • Ошибки: количество ошибок, испущенных вашим приложением

  • Длительность: количество времени, которое требуется вашему приложению для подачи ответа.

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

Чтобы узнать больше о сигналах, которые необходимо измерять при мониторинге приложений, обратитесь кMonitoring Distributed Systems из книги Google Site Reliability Engineering.

В дополнение к размышлению и разработке функций для публикации данных телеметрии, вы также должны спланировать, как ваше приложение будет входить в распределенную кластерную среду. В идеале вы должны удалить жестко запрограммированные ссылки на конфигурацию для локальных файлов журналов и каталогов журналов, а вместо этого регистрироваться напрямую в stdout и stderr. Журналы следует рассматривать как непрерывный поток событий или последовательность упорядоченных по времени событий. Этот поток вывода будет затем захвачен контейнером, окружающим ваше приложение, из которого он может быть перенаправлен на уровень ведения журнала, такой как стек EFK (Elasticsearch, Fluentd и Kibana). Kubernetes обеспечивает большую гибкость при разработке архитектуры логирования, что мы рассмотрим более подробно ниже.

Построить логику администрирования в API

Как только ваше приложение будет упаковано и запущено в кластерной среде, такой как Kubernetes, у вас больше не будет доступа оболочки к контейнеру, в котором запущено ваше приложение. Если вы ввели надлежащую проверку работоспособности, ведение журналов и мониторинг, вы можете быстро получать предупреждения и устранять проблемы, связанные с производством, но выполнение действий, помимо перезапуска и повторного развертывания контейнеров, может быть затруднено. Для быстрых исправлений эксплуатации и обслуживания, таких как очистка очередей или кеша, вы должны реализовать соответствующие конечные точки API, чтобы вы могли выполнять эти операции без перезапуска контейнеров илиexec в запущенные контейнеры и выполнения серии команд. Контейнеры следует рассматривать как неизменяемые объекты, поэтому в производственной среде следует избегать ручного администрирования. Если вам необходимо выполнить одноразовые административные задачи, такие как очистка кэшей, вы должны предоставить эту функцию через API.

Резюме

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

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

Контейнерные приложения

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

Явно Объявить Зависимости

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

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

Чтобы узнать больше о настройке частного реестра образов, обратитесь кDeploy a Registry Server в официальной документации Docker и в разделеRegistries ниже.

Сохраняйте размеры изображения маленькими

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

  • Уменьшить размеры изображения

  • Ускорьте сборку изображений

  • Уменьшить задержку начала контейнера

  • Ускорить время передачи изображения

  • Улучшение безопасности за счет уменьшения поверхности атаки

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

  • Используйте минимальный базовый образ ОС, напримерalpine, или создайте изscratch вместо полнофункциональной ОС, напримерubuntu

  • Очистите ненужные файлы и артефакты после установки программного обеспечения

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

  • Игнорировать ненужные артефакты сборки и файлы при копировании в большие каталоги

Полное руководство по оптимизации контейнеров Docker, включая множество наглядных примеров, можно найти вBuilding Optimized Containers for Kubernetes.

Введите конфигурацию

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

Один из вариантов сделать это - указать переменные среды и их значения в Dockerfile с помощью оператораENV, чтобы данные конфигурации были встроены в изображения:

Dockerfile

...
ENV MYSQL_USER=my_db_user
...

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

Вы также можете передать переменные среды в качестве параметров при запуске контейнера, используяdocker run и флаг-e:

docker run -e MYSQL_USER='my_db_user' IMAGE[:TAG]

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

docker run --env-file var_list IMAGE[:TAG]

Если вы модернизируете свое приложение, чтобы запускать его с помощью диспетчера кластеров, такого как Kubernetes, вам следует дополнительно перенести конфигурацию из образа и управлять конфигурацией с помощью встроенных в Kubernetes объектовConfigMap иSecrets. Это позволяет вам отделять конфигурацию от манифестов изображений, так что вы можете управлять и создавать версии отдельно от вашего приложения. Чтобы узнать, как реализовать внешнюю конфигурацию с помощью ConfigMaps и Secrets, обратитесь кConfigMaps and Secrets section ниже.

Опубликовать изображение в реестре

После того как вы создали образы своих приложений, чтобы сделать их доступными для Kubernetes, вы должны загрузить их в реестр изображений контейнеров. В публичных реестрах, таких какDocker Hub, хранятся последние образы Docker для популярных проектов с открытым исходным кодом, таких какhttps://hub.docker.com//node/[Node.js] and https://hub.docker.com/ / nginx / [nginx]. Частные реестры позволяют вам публиковать изображения ваших внутренних приложений, делая их доступными для разработчиков и инфраструктуры, но не для всего мира.

Вы можете развернуть частный реестр, используя вашу существующую инфраструктуру (например, поверх облачного хранилища объектов) или при желании использовать один из нескольких продуктов реестра Docker, напримерQuay.io или платные планы Docker Hub. Эти реестры могут интегрироваться с размещенными службами контроля версий, такими как GitHub, так что при обновлении и отправке Dockerfile служба реестра автоматически извлекает новый Dockerfile, создает образ контейнера и делает обновленный образ доступным для ваших служб.

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

Внедрить конвейер сборки

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

Большинство сборочных конвейеров выполняют следующие основные функции:

  • Следите за изменениями в исходном коде

  • Запустите дым и юнит-тесты на модифицированный код

  • Построить изображения контейнера, содержащие модифицированный код

  • Запустите дальнейшие интеграционные тесты, используя встроенные образы контейнеров

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

  • (Необязательно, в настройках непрерывного развертывания) Обновите развертывания Kubernetes и разверните образы в промежуточные / производственные кластеры

Существует много платных продуктов для непрерывной интеграции, которые имеют встроенную интеграцию с популярными сервисами контроля версий, такими как GitHub, и реестрами изображений, такими как Docker Hub. Альтернативой этим продуктам являетсяJenkins, бесплатный сервер автоматизации сборки с открытым исходным кодом, который можно настроить для выполнения всех функций, описанных выше. Чтобы узнать, как настроить конвейер непрерывной интеграции Jenkins, обратитесь кHow To Set Up Continuous Integration Pipelines in Jenkins on Ubuntu 16.04.

Внедрить ведение журнала и мониторинг контейнеров

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

В Kubernetes по умолчанию контейнеры используют Dockerlogging driverjson-file, который захватывает потоки stdout и stderr и записывает их в файлы JSON на узле, на котором работает контейнер. Иногда ведения журнала непосредственно в stderr и stdout может быть недостаточно для вашего контейнера приложения, и вы можете связать контейнер приложения с контейнеромsidecar ведения журнала в Kubernetes Pod. Затем этот контейнер с коляской может получать журналы из файловой системы, локального сокета или системного журнала, предоставляя вам немного больше гибкости, чем простое использование потоков stderr и stdout. Этот контейнер также может выполнять некоторую обработку и затем передавать обогащенные журналы в stdout / stderr или непосредственно в бэкэнд журналирования. Чтобы узнать больше о шаблонах ведения журнала Kubernetes, см.section этого руководства по ведению журнала и мониторингу Kubernetes.

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

Подобно ведению журнала, вы должны начать думать о мониторинге в среде контейнера и кластера. Docker предоставляет полезную командуdocker stats для получения стандартных метрик, таких как использование ЦП и памяти, для запуска контейнеров на хосте, и предоставляет еще больше метрик черезRemote REST API. Кроме того, инструмент с открытым исходным кодомcAdvisor (установленный на узлах Kubernetes по умолчанию) предоставляет более расширенные функции, такие как сбор исторических показателей, экспорт данных показателей и полезный веб-интерфейс для сортировки данных.

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

Резюме

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

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

Развертывание в Куберне

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

Записать файлы развертывания и конфигурации Pod

После того, как вы контейнировали свое приложение и опубликовали его в реестре, теперь вы можете развернуть его в кластере Kubernetes с помощью рабочей нагрузки Pod. Наименьшее развертываемое подразделение в кластере Kubernetes - это не контейнер, а Pod. Модули обычно состоят из контейнера приложения (например, контейнерного веб-приложения Flask) или контейнера приложения и любых контейнеров «sidecar», которые выполняют некоторую вспомогательную функцию, например мониторинг или ведение журнала. Контейнеры в Pod совместно используют ресурсы хранения, пространство имен сети и пространство портов. Они могут связываться друг с другом с помощьюlocalhost и могут обмениваться данными с помощью подключенных томов. Кроме того, рабочая нагрузка Pod позволяет вам определятьInit Containers, которые запускают сценарии установки или утилиты до того, как начнется запуск основного контейнера приложения.

Модули обычно разворачиваются с использованием Deployments, которые представляют собой контроллеры, определенные в файлах YAML, которые объявляют определенное желаемое состояние. Например, в состоянии приложения могут быть запущены три реплики контейнера веб-приложения Flask и открыт порт 8080. После создания плоскость управления постепенно приводит фактическое состояние кластера в соответствие с желаемым состоянием, объявленным в развертывании, путем планирования контейнеров на узлах по мере необходимости. Чтобы масштабировать количество реплик приложений, работающих в кластере, скажем, с 3 до 5, вы обновляете полеreplicas файла конфигурации развертывания, а затемkubectl apply новый файл конфигурации. Используя эти файлы конфигурации, все операции масштабирования и развертывания можно отслеживать и создавать версии с помощью существующих служб контроля версий и интеграций.

Вот пример файла конфигурации развертывания Kubernetes для приложения Flask:

flask_deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: flask-app
  labels:
    app: flask-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: flask-app
  template:
    metadata:
      labels:
        app: flask-app
    spec:
      containers:
      - name: flask
        image: sammy/flask_app:1.0
        ports:
        - containerPort: 8080

Это развертывание запускает 3 модуля, которые запускают контейнер с именемflask с использованием образаsammy/flask_app (версия1.0) с открытым портом8080. Развертывание называетсяflask-app.

Чтобы узнать больше о модулях Kubernetes и развертываниях, обратитесь к разделамPods иDeployments официальной документации Kubernetes.

Настроить хранилище Pod

Kubernetes управляет хранилищем Pod с помощью томов, постоянных томов (PV) и утверждений о постоянных томах (PVC). Тома - это абстракция Kubernetes, используемая для управления хранилищем Pod и поддержки большинства предложений блочного хранилища облачных провайдеров, а также локального хранилища на узлах, на которых установлены работающие Pod. Чтобы увидеть полный список поддерживаемых типов томов, обратитесь к Kubernetesdocumentation.

Например, если ваш Pod содержит два контейнера NGINX, которые должны обмениваться данными между ними (скажем, первый, называемыйnginx, обслуживает веб-страницы, а второй, называемыйnginx-sync, извлекает страницы из внешнего местоположения и обновляет страницы, обслуживаемые контейнеромnginx), ваша спецификация Pod будет выглядеть примерно так (здесь мы используем тип томаemptyDir):

pod_volume.yaml

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx
    volumeMounts:
    - name: nginx-web
      mountPath: /usr/share/nginx/html

  - name: nginx-sync
    image: nginx-sync
    volumeMounts:
    - name: nginx-web
      mountPath: /web-data

  volumes:
  - name: nginx-web
    emptyDir: {}

Мы используемvolumeMount для каждого контейнера, указывая, что мы хотели бы смонтировать томnginx-web, содержащий файлы веб-страницы, в/usr/share/nginx/html в контейнереnginx и в/web-data в контейнереnginx-sync. Мы также определяемvolume с именемnginx-web типаemptyDir.

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

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

Для сохранения данных при перезапуске и обновлении Pod необходимо использовать объекты PersistentVolume (PV) и PersistentVolumeClaim (PVC).

PersistentVolumes - это абстракции, представляющие части постоянного хранилища, например тома облачных блоков или хранилища NFS. Они создаются отдельно от PersistentVolumeClaims, которые являются требованиями к частям хранилища для разработчиков. В своих конфигурациях Pod разработчики запрашивают постоянное хранилище, используя PVC, который Kubernetes сопоставляет с доступными томами PV (при использовании облачного блочного хранилища Kubernetes может динамически создавать PersistentVolumes при создании PersistentVolumeClaims).

Если вашему приложению требуется один постоянный том на реплику, как в случае многих баз данных, вам не следует использовать Deployments, а использовать контроллер StatefulSet, который предназначен для приложений, которым требуются стабильные сетевые идентификаторы, стабильное постоянное хранилище и гарантии упорядочения. Развертывания следует использовать для приложений без сохранения состояния, и если вы определите PersistentVolumeClaim для использования в конфигурации развертывания, этот PVC будет общим для всех реплик развертывания.

Чтобы узнать больше о контроллере StatefulSet, обратитесь к Kubernetesdocumentation. Чтобы узнать больше о утверждениях PersistentVolumes и PersistentVolume, обратитесь к хранилищу Kubernetesdocumentation.

Внедрение данных конфигурации с помощью Kubernetes

Подобно Docker, Kubernetes предоставляет поляenv иenvFrom для установки переменных среды в файлах конфигурации Pod. Вот пример фрагмента из файла конфигурации Pod, который устанавливает для переменной средыHOSTNAME в работающем Pod значениеmy_hostname:

sample_pod.yaml

...
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80
        env:
        - name: HOSTNAME
          value: my_hostname
...

Это позволяет перемещать конфигурацию из Dockerfiles в файлы конфигурации Pod и Deployment. Ключевым преимуществом дальнейшей экстернализации конфигурации из ваших файлов Docker является то, что теперь вы можете изменять эти конфигурации рабочей нагрузки Kubernetes (например, изменяя значениеHOSTNAME наmy_hostname_2) отдельно от определений контейнера вашего приложения. После того, как вы измените файл конфигурации модуля Pod, вы можете повторно развернуть модуль Pod, используя его новую среду, в то время как базовый образ контейнера (определенный через его файл Docker) не нужно перестраивать, тестировать и отправлять в репозиторий. Вы также можете создавать версии этих конфигураций Pod и Deployment отдельно от Dockerfiles, что позволяет быстро обнаруживать критические изменения и дополнительно отделять проблемы конфигурации от ошибок приложения.

Kubernetes предоставляет другую конструкцию для дальнейшей экстернализации и управления данными конфигурации: ConfigMaps и Secrets.

ConfigMaps и секреты

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

Вот пример, используя конфигурацию Pod сверху. Сначала мы сохраним переменную средыHOSTNAME как ConfigMap, а затем укажем на нее в конфигурации Pod:

kubectl create configmap hostname --from-literal=HOSTNAME=my_host_name

Чтобы ссылаться на него из файла конфигурации Pod, мы используем конструкцииvalueFrom иconfigMapKeyRef:

sample_pod_configmap.yaml

...
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80
        env:
        - name: HOSTNAME
          valueFrom:
            configMapKeyRef:
              name: hostname
              key: HOSTNAME
...

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

Если ваши приложения используют файлы конфигурации, ConfigMaps дополнительно позволяет хранить эти файлы как объекты ConfigMap (с использованием флага--from-file), которые затем можно монтировать в контейнеры как файлы конфигурации.

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

Чтобы узнать больше о ConfigMaps и Secrets, обратитесь к Kubernetesdocumentation.

Создать Сервисы

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

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

У Kubernetes Services есть 4 типа, которые задаются полемtype в файле конфигурации сервиса:

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

  • NodePort: это откроет вашу службу на каждом узле на статическом порте, по умолчанию между 30000-32767. Когда запрос попадает в узел по его IP-адресу узла иNodePort для вашей службы, запрос будет сбалансирован по нагрузке и направлен в контейнеры приложений для вашей службы.

  • LoadBalancer: это создаст балансировщик нагрузки с использованием продукта балансировки нагрузки вашего облачного провайдера и настроитNodePort иClusterIP для вашей службы, на которые будут перенаправляться внешние запросы.

  • ExternalName: этот тип службы позволяет сопоставить службу Kubernetes с записью DNS. Его можно использовать для доступа к внешним службам из ваших модулей с помощью Kubernetes DNS.

Обратите внимание, что создание службы типаLoadBalancer для каждого развертывания, запущенного в вашем кластере, создаст новый балансировщик облачной нагрузки для каждой службы, что может стать дорогостоящим. Для управления маршрутизацией внешних запросов к нескольким службам с использованием одного балансировщика нагрузки можно использовать Ingress Controller. Контроллеры Ingress выходят за рамки этой статьи, но чтобы узнать о них больше, вы можете обратиться к Kubernetesdocumentation. Популярным простым контроллером Ingress являетсяNGINX Ingress Controller.

Вот простой файл конфигурации службы для примера Flask, который используется в модулях Pods и Deploymentssectionэтого руководства:

flask_app_svc.yaml

apiVersion: v1
kind: Service
metadata:
  name: flask-svc
spec:
  ports:
  - port: 80
    targetPort: 8080
  selector:
    app: flask-app
  type: LoadBalancer

Здесь мы решили раскрыть развертываниеflask-app с помощью этой службыflask-svc. Мы создаем облачный балансировщик нагрузки для маршрутизации трафика от порта балансировщика нагрузки80 к открытому контейнерному порту8080.

Чтобы узнать больше о сервисах Kubernetes, обратитесь к разделуServices документации Kubernetes.

Регистрация и мониторинг

Анализ отдельных контейнеров и журналов Pod с использованиемkubectl logs иdocker logs может стать утомительным по мере роста числа запущенных приложений. Чтобы помочь вам отладить проблемы приложения или кластера, вы должны реализовать централизованное ведение журнала. На высоком уровне он состоит из агентов, работающих на всех рабочих узлах, которые обрабатывают файлы журналов и потоки Pod, обогащают их метаданными и пересылают журналы на серверную часть, напримерElasticsearch. Отсюда данные журнала можно визуализировать, отфильтровать и упорядочить с помощью инструмента визуализации, такого какKibana.

В разделе ведения журнала на уровне контейнера мы обсудили рекомендуемый подход Kubernetes, заключающийся в том, чтобы приложения в контейнерах регистрировали потоки stdout / stderr. Мы также кратко обсудили ведение журналов в контейнерах с коляской, которые могут предоставить вам большую гибкость при входе в систему из вашего приложения. Вы также можете запускать агенты журналирования непосредственно в ваших модулях, которые собирают данные локального журнала и перенаправляют их непосредственно в бэкэнд журналирования. У каждого подхода есть свои плюсы и минусы и компромиссы в использовании ресурсов (например, запуск контейнера агента журналирования внутри каждого модуля может стать ресурсоемким и быстро перегрузить ваш бэкэнд журналирования). Чтобы узнать больше о различных архитектурах журналирования и их компромиссах, обратитесь к Kubernetesdocumentation.

В стандартной настройке каждый узел запускает агент ведения журнала, напримерFilebeat илиFluentd, который собирает журналы контейнеров, созданные Kubernetes. Напомним, что Kubernetes создает файлы журнала JSON для контейнеров на узле (в большинстве случаев их можно найти в/var/lib/docker/containers/). Они должны вращаться с помощью инструмента, такого как logrotate. Агент ведения журнала узла должен запускаться какDaemonSet Controller, тип рабочей нагрузки Kubernetes, который гарантирует, что каждый узел запускает копию модуля DaemonSet Pod. В этом случае Pod будет содержать агент ведения журнала и его конфигурацию, которая обрабатывает журналы из файлов и каталогов, смонтированных в модуле регистрации DaemonSet.

Подобно узкому месту при использованииkubectl logs для отладки проблем с контейнерами, в конечном итоге вам может потребоваться рассмотреть более надежный вариант, чем простое использованиеkubectl top и панели инструментов Kubernetes для мониторинга использования ресурсов Pod в вашем кластере. Мониторинг на уровне кластера и приложений можно настроить с помощью системы мониторингаPrometheus и базы данных временных рядов, а также панели показателейGrafana. Prometheus работает с использованием модели «вытягивания», которая периодически очищает конечные точки HTTP (например,/metrics/cadvisor на узлах или конечные точки REST API приложения/metrics) на предмет метрических данных, которые затем обрабатываются и сохраняются. Затем эти данные можно анализировать и визуализировать с помощью панели инструментов Grafana. Prometheus и Grafana могут быть запущены в кластер Kubernetes, как и любое другое развертывание и служба.

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

Заключение

Миграция и модернизация приложения для его эффективного запуска в кластере Kubernetes часто включает в себя нетривиальные этапы планирования и разработки архитектуры программного обеспечения и изменений инфраструктуры. После внесения этих изменений владельцы сервисов могут постоянно развертывать новые версии своих приложений и легко масштабировать их по мере необходимости, с минимальным количеством ручного вмешательства. Такие шаги, как вывод конфигурации из вашего приложения, настройка правильной регистрации и публикации метрик, а также настройка проверок работоспособности, позволяют в полной мере воспользоваться преимуществами парадигмы Cloud Native, которая была разработана Kubernetes. Создавая переносимые контейнеры и управляя ими с помощью объектов Kubernetes, таких как Deployments and Services, вы можете в полной мере использовать доступную вычислительную инфраструктуру и ресурсы разработки.

Related