Введение
Образы контейнеров — это основной формат упаковки для определения приложений в Kubernetes. Изображения используются в качестве основы для модулей и других объектов и играют важную роль в эффективном использовании функций Kubernetes. Хорошо продуманные изображения безопасны, высокопроизводительны и целенаправленны. Они могут реагировать на данные конфигурации или инструкции, предоставленные Kubernetes. Они реализуют конечные точки, которые используются при развертывании для понимания внутреннего состояния приложения.
В этой статье мы познакомим вас с некоторыми стратегиями создания высококачественных изображений и обсудим несколько общих целей, которые помогут вам принять решения при контейнеризации приложений. Мы сосредоточимся на создании образов, предназначенных для запуска в Kubernetes, но многие из этих предложений в равной степени применимы и к запуску контейнеров на других платформах оркестрации или в других контекстах.
Характеристики эффективных образов контейнеров
Прежде чем мы перейдем к конкретным действиям, которые необходимо предпринять при создании образов контейнеров, мы поговорим о том, что делает образ контейнера хорошим. Каковы должны быть ваши цели при создании новых изображений? Какие характеристики и какое поведение наиболее важны?
Некоторые качества, к которым следует стремиться:
A single, well-defined purpose
Изображения контейнеров должны иметь один дискретный фокус. Не думайте об образах контейнеров как о виртуальных машинах, где имеет смысл объединить связанные функции вместе. Вместо этого обращайтесь с образами контейнеров как с утилитами Unix, уделяя особое внимание хорошему выполнению одной маленькой задачи. Приложения можно координировать за пределами отдельного контейнера, чтобы обеспечить более сложную функциональность.
Generic design with the ability to inject configuration at runtime
Образы контейнеров следует проектировать с учетом возможности повторного использования. Например, возможность настройки конфигурации во время выполнения часто требуется для выполнения основных требований, таких как тестирование образов перед развертыванием в рабочей среде. Небольшие общие изображения можно комбинировать в различных конфигурациях, чтобы изменить поведение без создания новых изображений.
Small image size
Образы меньшего размера имеют ряд преимуществ в кластерных средах, таких как Kubernetes. Они быстро загружаются на новые узлы и часто имеют меньший набор установленных пакетов, что может повысить безопасность. Урезанные образы контейнеров упрощают отладку проблем за счет минимизации количества используемого программного обеспечения.
Externally managed state
Контейнеры в кластерных средах имеют очень нестабильный жизненный цикл, включая плановые и внеплановые отключения из-за нехватки ресурсов, масштабирования или сбоев узлов. Чтобы обеспечить согласованность, помочь в восстановлении и доступности ваших сервисов, а также избежать потери данных, крайне важно хранить состояние приложения в стабильном месте вне контейнера.
Easy to understand
Важно стараться сделать образы контейнеров максимально простыми и понятными. При устранении неполадок возможность напрямую просматривать конфигурации и журналы или тестировать поведение контейнера может помочь вам быстрее найти решение. Рассматривая образы контейнеров как формат упаковки для вашего приложения, а не как конфигурацию машины, вы сможете найти правильный баланс.
Follow containerized software best practices
Изображения должны работать в рамках модели контейнера, а не противоречить ей. Избегайте применения традиционных методов системного администрирования, таких как включение полной системы инициализации и демонизация приложений. Выполните вход в stdout
, чтобы Kubernetes мог предоставлять данные администраторам вместо использования внутреннего демона журналирования. Эти рекомендации во многом расходятся с лучшими практиками для полноценных операционных систем.
Fully leverage Kubernetes features
Помимо соответствия модели контейнера, важно понимать и согласовывать инструменты, предоставляемые Kubernetes. Например, предоставление конечных точек для проверки работоспособности и готовности или корректировка операций на основе изменений в конфигурации или среде может помочь вашим приложениям использовать среду динамического развертывания Kubernetes в своих интересах.
Теперь, когда мы установили некоторые качества, определяющие высокофункциональные образы контейнеров, мы можем углубиться в стратегии, которые помогут вам достичь этих целей.
Повторное использование минимальных общих базовых слоев
Мы можем начать с изучения ресурсов, из которых создаются образы контейнеров: базовых образов. Каждый образ контейнера создается либо из родительского образа, изображения, используемого в качестве отправной точки, либо из абстрактного scratch
слоя, пустого слоя изображения без файловой системы. Базовый образ — это образ контейнера, который служит основой для будущих образов, определяя базовую операционную систему и обеспечивая основные функции. Изображения состоят из одного или нескольких слоев изображения, наложенных друг на друга и образующих окончательное изображение.
При работе непосредственно с scratch
стандартные утилиты или файловая система недоступны, а это означает, что у вас есть доступ только к крайне ограниченным функциям. Хотя изображения, созданные непосредственно с scratch
, могут быть очень простыми и минимальными, их основная цель — определение базовых изображений. Обычно вы хотите создать образы контейнеров поверх родительского образа, который устанавливает базовую среду, в которой работают ваши приложения, чтобы вам не приходилось создавать полную систему для каждого образа.
Несмотря на то, что существуют базовые образы для различных дистрибутивов Linux, лучше тщательно обдумать, какую систему вы выберете. На каждой новой машине необходимо будет загрузить родительский образ и все добавленные вами дополнительные слои. Для больших изображений это может потреблять значительную часть пропускной способности и заметно удлинять время запуска ваших контейнеров при первом запуске. Невозможно сократить образ, который используется в качестве родительского элемента в процессе сборки контейнера, поэтому хорошей идеей будет начать с минимального родительского образа.
Среды с богатым набором функций, такие как Ubuntu, позволяют вашему приложению работать в знакомой вам среде, но есть некоторые компромиссы, которые следует учитывать. Образы Ubuntu (и аналогичные обычные образы дистрибутива) имеют тенденцию быть относительно большими (более 100 МБ), а это означает, что любые образы контейнеров, созданные на их основе, унаследуют этот вес.
Alpine Linux — популярная альтернатива базовым образам, поскольку она успешно объединяет множество функций в очень небольшой базовый образ (~ 5 МБ). Он включает в себя менеджер пакетов с большими репозиториями и большинство стандартных утилит, которые можно ожидать от минимальной среды Linux.
При разработке приложений рекомендуется попытаться повторно использовать одного и того же родительского элемента для каждого изображения. Если ваши изображения имеют общий родительский слой, машины, на которых работают ваши контейнеры, загрузят родительский слой только один раз. После этого им нужно будет загрузить только те слои, которые различаются между вашими изображениями. Это означает, что если у вас есть общие функции или функции, которые вы хотите встроить в каждое изображение, хорошей идеей может быть создание общего родительского изображения для наследования. Изображения, имеющие общее происхождение, помогают минимизировать объем дополнительных данных, которые необходимо загрузить на новые серверы.
Управление уровнями контейнера
Выбрав родительский образ, вы можете определить образ контейнера, добавив дополнительное программное обеспечение, скопировав файлы, открыв порты и выбрав процессы для запуска. Определенные инструкции в файле конфигурации образа (например, Dockerfile
если вы используете Docker) добавят к вашему образу дополнительные слои.
По многим из тех же причин, упомянутых в предыдущем разделе, важно помнить о том, как вы добавляете слои к изображениям, из-за получающегося размера, наследования и сложности во время выполнения. Чтобы избежать создания больших и громоздких образов, важно хорошо понимать, как взаимодействуют уровни контейнера, как механизм сборки кэширует слои и как тонкие различия в похожих инструкциях могут оказать большое влияние на создаваемые вами изображения.
Понимание слоев изображения и кэша сборки
Docker создает новый слой изображения каждый раз, когда выполняет инструкцию RUN
, COPY
или ADD
. Если вы создадите образ еще раз, механизм сборки проверит каждую инструкцию, чтобы определить, есть ли в ней кэшированный слой изображения для этой операции. Если он находит совпадение в кеше, он использует существующий слой изображения вместо повторного выполнения инструкции и перестроения слоя.
Этот процесс может значительно сократить время сборки, но важно понимать используемый механизм, чтобы избежать потенциальных проблем. Для инструкций копирования файлов, таких как COPY
и ADD
, Docker сравнивает контрольные суммы файлов, чтобы определить, нужно ли выполнить операцию еще раз. Для инструкций RUN
Docker проверяет, имеется ли в кэше существующий слой изображения для этой конкретной командной строки.
Хотя это может быть не сразу очевидно, такое поведение может привести к неожиданным результатам, если вы не будете осторожны. Типичным примером этого является обновление локального индекса пакетов и установка пакетов в два отдельных этапа. В этом примере мы будем использовать Ubuntu, но основная предпосылка одинаково хорошо применима и к базовым образам для других дистрибутивов:
1 |
FROM ubuntu:20.04 RUN apt -y update RUN apt -y install nginx . . . |
Здесь индекс локального пакета обновляется с помощью одной инструкции RUN
( apt -y update
), а Nginx устанавливается с помощью другой операции. Это работает без проблем при первом использовании. Однако если Dockerfile будет позже обновлен для установки дополнительного пакета, могут возникнуть проблемы:
1 |
FROM ubuntu:20.04 RUN apt -y update RUN apt -y install nginx</code> php-fpm . . . |
Мы добавили второй пакет в команду установки, запускаемую второй инструкцией. Если с момента предыдущей сборки образа прошло значительное время, новая сборка может завершиться неудачно. Это связано с тем, что инструкция обновления индекса пакета ( RUN apt -y update
) не изменилась, поэтому Docker повторно использует уровень изображения, связанный с этой инструкцией. Поскольку мы используем старый индекс пакета, версия пакета php-fpm
которая есть в наших локальных записях, может больше не находиться в репозиториях, что приводит к ошибке при выполнении второй инструкции.
Чтобы избежать этого сценария, обязательно объедините все взаимозависимые шаги в одну инструкцию RUN
, чтобы Docker повторно выполнял все необходимые команды при возникновении изменений. В сценариях оболочки хорошим способом добиться этого является использование логического оператора AND &&
, который будет выполнять несколько команд в одной строке, если каждая из них успешна:
1 |
FROM ubuntu:20.04 RUN apt -y update</code> && apt -y установить nginx<mark> php-fpm</mark> . . . |
Теперь инструкция обновляет локальный кеш пакетов при каждом изменении списка пакетов. Альтернативой может быть RUN
всего сценария shell.sh
, который содержит несколько строк инструкций, но сначала должен быть доступен контейнеру.
Уменьшение размера слоя изображения путем настройки инструкций RUN
Предыдущий пример демонстрирует, как поведение кэширования Docker может подорвать ожидания, но есть и другие вещи, которые следует учитывать при взаимодействии инструкций RUN
с многоуровневой системой Docker. Как упоминалось ранее, в конце каждой инструкции RUN
Docker фиксирует изменения в виде дополнительного слоя образа. Чтобы контролировать объем создаваемых слоев изображения, вы можете очистить ненужные файлы, обращая внимание на артефакты, возникающие в результате запуска команд.
В общем, объединение команд в одну инструкцию RUN
обеспечивает больший контроль над записываемым слоем. Для каждой команды вы можете настроить состояние уровня ( apt -y update
), выполнить основную команду ( apt install -y nginx php-fpm
) и удалить все ненужные артефакты, чтобы очистить среду перед ее фиксацией. Например, многие Dockerfiles связывают rm -rf /var/lib/apt/lists/*
с концом команд apt
, удаляя индексы загруженных пакетов, чтобы уменьшить размер конечного слоя:
1 |
FROM ubuntu:20.04 RUN apt -y update && apt -y install nginx php-fpm</code> && rm -rf /var/lib/apt/lists/* . . . |
Чтобы еще больше уменьшить размер слоев изображения, которые вы создаете, может быть полезно попытаться ограничить другие непреднамеренные побочные эффекты выполняемых вами команд. Например, в дополнение к явно объявленным пакетам apt
по умолчанию также устанавливает «рекомендуемые» пакеты. Вы можете включить --no-install-recommends
в свои команды apt
, чтобы устранить это поведение. Возможно, вам придется поэкспериментировать, чтобы выяснить, полагаетесь ли вы на какую-либо функциональность, предоставляемую рекомендуемыми пакетами.
В этом разделе мы использовали команды управления пакетами в качестве примера, но те же принципы применимы и к другим сценариям. Общая идея состоит в том, чтобы создать предварительные условия, выполнить минимально жизнеспособную команду, а затем очистить все ненужные артефакты с помощью одной команды RUN
, чтобы уменьшить накладные расходы слоя, который вы будете создавать.
Использование многоэтапных сборок
Multi-stage builds были представлены в Docker 17.05, что позволяет разработчикам более жестко контролировать создаваемые ими окончательные образы среды выполнения. Многоэтапные сборки позволяют разделить Dockerfile на несколько разделов, представляющих отдельные этапы, каждый из которых имеет оператор FROM
для указания отдельных родительских образов.
В предыдущих разделах определены изображения, которые можно использовать для создания приложения и подготовки ресурсов. Они часто содержат инструменты сборки и файлы разработки, которые необходимы для создания приложения, но не являются обязательными для его запуска. Каждый последующий этап, определенный в файле, будет иметь доступ к артефактам, созданным на предыдущих этапах.
Последний оператор FROM
определяет образ, который будет использоваться для запуска приложения. Обычно это урезанный образ, в котором устанавливаются только необходимые требования среды выполнения, а затем копируются артефакты приложения, созданные на предыдущих этапах.
Эта система позволяет вам меньше беспокоиться об оптимизации инструкций RUN
на этапах сборки, поскольку эти слои контейнера не будут присутствовать в окончательном образе среды выполнения. Вам по-прежнему следует обращать внимание на то, как инструкции взаимодействуют с кэшированием слоев на этапах сборки, но ваши усилия могут быть направлены на минимизацию времени сборки, а не на окончательный размер изображения. Обращать внимание на инструкции на заключительном этапе по-прежнему важно для уменьшения размера образа, но, разделив различные этапы сборки контейнера, легче получить оптимизированные образы без особой сложности Dockerfile
.
Определение функциональности на уровне контейнера и модуля
Хотя выбор, который вы делаете в отношении инструкций по созданию контейнера, важен, более общие решения о том, как контейнеризировать ваши сервисы, часто оказывают более непосредственное влияние на ваш успех. В этом разделе мы поговорим подробнее о том, как лучше всего перевести ваши приложения из более традиционной среды на работу на контейнерной платформе.
Контейнеризация по функциям
Как правило, рекомендуется упаковывать каждую часть независимой функциональности в отдельный образ контейнера.
Это отличается от распространенных стратегий, используемых в средах виртуальных машин, где приложения часто группируются в одном образе, чтобы уменьшить размер и свести к минимуму ресурсы, необходимые для запуска виртуальной машины. Поскольку контейнеры представляют собой легкие абстракции, которые не виртуализируют весь стек операционной системы, этот компромисс менее привлекателен для Kubernetes. Таким образом, хотя виртуальная машина веб-стека может объединять веб-сервер Nginx с сервером приложений Gunicorn на одной машине для обслуживания приложения Django, в Kubernetes они могут быть разделены на отдельные контейнеры.
Разработка контейнеров, реализующих одну отдельную часть функциональности ваших сервисов, дает ряд преимуществ. Каждый контейнер можно разрабатывать независимо, если установлены стандартные интерфейсы между сервисами. Например, контейнер Nginx потенциально может использоваться для прокси-сервера к нескольким различным бэкэндам или может использоваться в качестве балансировщика нагрузки, если ему задана другая конфигурация.
После развертывания каждый образ контейнера можно масштабировать независимо, чтобы учесть различные ограничения ресурсов и нагрузки. Разделив приложения на несколько образов контейнеров, вы получаете гибкость в разработке, организации и развертывании.
Объединение изображений контейнеров в модулях
В Kubernetes pods — это наименьшая единица, которой можно напрямую управлять с плоскости управления. Поды состоят из одного или нескольких контейнеров вместе с дополнительными данными конфигурации, которые сообщают платформе, как следует запускать эти компоненты. Контейнеры внутри модуля всегда планируются на одном и том же рабочем узле кластера, и система автоматически перезапускает отказавшие контейнеры. Абстракция модуля очень полезна, но она вводит еще один уровень решений о том, как объединить компоненты вашего приложения.
Как и образы контейнеров, модули также становятся менее гибкими, когда слишком много функций объединено в одну сущность. Сами поды можно масштабировать с использованием других абстракций, но контейнерами внутри нельзя управлять или масштабировать независимо. Итак, чтобы продолжить использование нашего предыдущего примера, отдельные контейнеры Nginx и Gunicorn, вероятно, не следует объединять в один модуль. Таким образом, ими можно управлять и развертывать отдельно.
Однако существуют сценарии, в которых имеет смысл объединить функционально разные контейнеры в единое целое. В целом их можно отнести к ситуациям, когда дополнительный контейнер поддерживает или расширяет основные функции основного контейнера или помогает ему адаптироваться к среде развертывания. Вот некоторые распространенные шаблоны:
- Sidecar : Вторичный контейнер расширяет основные функциональные возможности основного контейнера, выполняя вспомогательную роль. Например, дополнительный контейнер может пересылать журналы или обновлять файловую систему при изменении удаленного репозитория. Основной контейнер по-прежнему сосредоточен на своей основной задаче, но его возможности расширяются за счет дополнительных функций.
- Ambassador : контейнер-посол отвечает за обнаружение и подключение к (часто сложным) внешним ресурсам. Основной контейнер может подключаться к постороннему контейнеру через общеизвестные интерфейсы, используя внутреннюю среду модуля. Посол абстрагирует внутренние ресурсы и прокси-трафик между основным контейнером и пулом ресурсов.
- Adaptor : Контейнер адаптера отвечает за нормализацию интерфейсов, данных и протоколов основного контейнера для согласования со свойствами, ожидаемыми другими компонентами. Основной контейнер может работать с использованием собственных форматов, а контейнер-адаптер преобразует и нормализует данные для связи с внешним миром.
Как вы могли заметить, каждый из этих шаблонов поддерживает стратегию создания стандартных, универсальных образов первичного контейнера, которые затем можно развертывать в различных контекстах и конфигурациях. Вторичные контейнеры помогают устранить разрыв между основным контейнером и конкретной используемой средой развертывания. Некоторые контейнеры с коляской также можно использовать повторно для адаптации нескольких основных контейнеров к одним и тем же условиям окружающей среды. Эти шаблоны извлекают выгоду из общей файловой системы и сетевого пространства имен, предоставляемого абстракцией модуля, но при этом допускают независимую разработку и гибкое развертывание стандартизированных контейнеров.
Проектирование конфигурации времени выполнения
Существует некоторое противоречие между желанием создавать стандартизированные, повторно используемые компоненты и требованиями, связанными с адаптацией приложений к среде их выполнения. Конфигурация во время выполнения — один из лучших способов устранить разрыв между этими проблемами. Таким образом, компоненты создаются как универсальные, а их требуемое поведение определяется во время выполнения путем предоставления дополнительных сведений о конфигурации. Этот стандартный подход работает как для контейнеров, так и для приложений.
Построение с учетом конфигурации среды выполнения требует от вас предусмотрительности как на этапах разработки приложения, так и на этапах контейнеризации. Приложения должны быть спроектированы так, чтобы считывать значения из параметров командной строки, файлов конфигурации или переменных среды при их запуске или перезапуске. Логика синтаксического анализа и внедрения конфигурации должна быть реализована в коде до контейнеризации.
При написании Dockerfile контейнер также должен быть спроектирован с учетом конфигурации времени выполнения. Контейнеры имеют ряд механизмов для предоставления данных во время выполнения. Пользователи могут монтировать файлы или каталоги с хоста как тома внутри контейнера, чтобы обеспечить настройку на основе файлов. Аналогично, переменные среды могут передаваться во внутреннюю среду выполнения контейнера при запуске контейнера. Инструкции CMD
и ENTRYPOINT
Dockerfile также можно определить таким образом, чтобы можно было передавать информацию о конфигурации времени выполнения в качестве параметров командной строки.
Поскольку Kubernetes манипулирует объектами более высокого уровня, такими как модули, вместо непосредственного управления контейнерами, существуют механизмы для определения конфигурации и внедрения ее в среду контейнера во время выполнения. Kubernetes ConfigMaps и Secrets позволяют вам определять данные конфигурации отдельно, а затем проецировать эти значения в среду контейнера во время выполнения. ConfigMaps — это объекты общего назначения, предназначенные для хранения данных конфигурации, которые могут различаться в зависимости от среды, этапа тестирования и т. д. Секреты предлагают аналогичный интерфейс, но специально разработаны для конфиденциальных данных, таких как пароли учетных записей или учетные данные API.
Понимая и правильно используя параметры конфигурации среды выполнения, доступные на каждом уровне абстракции, вы можете создавать гибкие компоненты, которые учитывают значения, предоставленные средой. Это позволяет повторно использовать одни и те же образы контейнеров в самых разных сценариях, сокращая накладные расходы на разработку за счет повышения гибкости приложения.
Реализация управления процессами с помощью контейнеров
При переходе к средам на основе контейнеров пользователи часто начинают с переноса существующих рабочих нагрузок с небольшими изменениями или без них в новую систему. Они упаковывают приложения в контейнеры, помещая уже используемые инструменты в новую абстракцию. Хотя для запуска и запуска перенесенных приложений полезно использовать обычные шаблоны, удаление предыдущих реализаций внутри контейнеров иногда может привести к неэффективному проектированию.
Отношение к контейнерам как к приложениям, а не сервисам
Проблемы часто возникают, когда разработчики реализуют важные функции управления сервисами внутри контейнеров. Например, запуск сервисов systemd внутри контейнера или демонизация веб-серверов могут считаться лучшими практиками в обычной вычислительной среде, но они часто противоречат предположениям, присущим модели контейнера.
Хосты управляют событиями жизненного цикла контейнера, отправляя сигналы процессу, работающему как PID (идентификатор процесса) 1 внутри контейнера. PID 1 — это первый запущенный процесс, который в традиционных вычислительных средах будет системой инициализации. Однако, поскольку хост может управлять только PID 1, использование обычной системы инициализации для управления процессами внутри контейнера иногда означает, что невозможно управлять основным приложением. Хост может запустить, остановить или завершить работу внутренней системы инициализации, но не может напрямую управлять основным приложением. Эти сигналы могут иногда передавать заданное поведение работающему приложению, но это все равно усложняет работу и не всегда необходимо.
В большинстве случаев лучше упростить рабочую среду внутри контейнера, чтобы PID 1 запускал основное приложение на переднем плане. В случаях, когда необходимо запустить несколько процессов, PID 1 отвечает за управление жизненным циклом последующих процессов. Некоторые приложения, такие как Apache, справляются с этим изначально, создавая и управляя рабочими процессами, обрабатывающими соединения. Для других приложений можно использовать скрипт-оболочку или очень простую систему инициализации, такую как dump-init, или включенную систему инициализации Tini. Независимо от выбранной вами реализации, процесс, работающий в контейнере с PID 1, должен соответствующим образом реагировать на сигналы TERM
, отправляемые Kubernetes, и вести себя должным образом.
Управление здоровьем контейнеров в Kubernetes
Развертывания и сервисы Kubernetes предлагают управление жизненным циклом длительно выполняющихся процессов и надежный, постоянный доступ к приложениям, даже когда базовые контейнеры необходимо перезапустить или сами реализации изменяются. Извлекая из контейнера ответственность за мониторинг и поддержание работоспособности сервисов, вы можете использовать инструменты платформы для управления работоспособными рабочими нагрузками.
Чтобы Kubernetes мог правильно управлять контейнерами, он должен понимать, работоспособны ли приложения, работающие в контейнерах, и способны ли они выполнять работу. Чтобы обеспечить это, контейнеры могут реализовывать проверки работоспособности: то есть конечные точки сети или команды, которые можно использовать для отчета о работоспособности приложения. Kubernetes будет периодически проверять определенные зонды жизнеспособности, чтобы определить, работает ли контейнер должным образом. Если контейнер не отвечает должным образом, Kubernetes перезапускает контейнер, пытаясь восстановить функциональность.
Kubernetes также предоставляет проверки готовности — аналогичную конструкцию. Вместо того, чтобы указывать, работоспособно ли приложение в контейнере, проверки готовности определяют, готово ли приложение к приему трафика. Это может быть полезно, когда в контейнерном приложении есть процедура инициализации, которая должна завершиться, прежде чем оно будет готово к получению соединений. Kubernetes использует проверки готовности, чтобы определить, следует ли добавить модуль в службу или удалить его из службы.
Определение конечных точек для этих двух типов зондов может помочь Kubernetes эффективно управлять вашими контейнерами и предотвратить влияние проблем жизненного цикла контейнеров на доступность сервисов. Механизмы ответа на эти типы запросов о работоспособности должны быть встроены в само приложение и должны быть представлены в конфигурации образа Docker.
Заключение
В этом руководстве мы рассмотрели некоторые важные моменты, которые следует учитывать при запуске контейнерных приложений в Kubernetes. Повторим, некоторые из предложений, которые мы рассмотрели, были следующими:
- Используйте минимальные родительские образы, которыми можно делиться, чтобы создавать образы с минимальным раздуванием и сокращать время запуска.
- Используйте многоэтапные сборки для разделения сред сборки контейнера и среды выполнения.
- Объедините инструкции Dockerfile, чтобы создать чистые слои изображения и избежать ошибок кэширования изображений.
- Контейнеризация путем изоляции дискретных функций для обеспечения гибкого масштабирования и управления.
- Создавайте модули так, чтобы у них была единая и целенаправленная ответственность.
- Объединение вспомогательных контейнеров для расширения функциональности основного контейнера или его адаптации к среде развертывания.
- Создавайте приложения и контейнеры, реагирующие на конфигурацию среды выполнения, чтобы обеспечить большую гибкость при развертывании.
- Запускайте приложения в качестве основных процессов в контейнерах, чтобы Kubernetes мог управлять событиями жизненного цикла.
- Разработайте конечные точки работоспособности и работоспособности внутри приложения или контейнера, чтобы Kubernetes мог отслеживать работоспособность контейнера.
На протяжении всего процесса разработки и внедрения вам придется принимать решения, которые могут повлиять на надежность и эффективность вашего сервиса. Понимание того, чем контейнерные приложения отличаются от обычных приложений, а также изучение того, как они работают в среде управляемого кластера, поможет вам избежать некоторых распространенных ошибок и позволит вам воспользоваться всеми возможностями, предоставляемыми Kubernetes.