Стоит попробовать Mizu – это ПО для мониторинга Kubernetes-трафика. Программа может сильно упростить ежедневную диагностику сетей, да и жизнь в целом.
Одна из наиболее частых задач, с которыми сталкиваются администраторы Kubernetes по ходу тестирования и дебаггинга – проверка коммуникаций между компонентами внутри сети.
Если вы хоть как-то взаимодействуете c Kubernetes по работе, то наверняка частенько проверяете входящий трафик, чтобы изучить входящие запросы и т.п. Обычно задачи подобного рода решаются при помощи утилиты tcpdump, установленной в конкретный контейнер. Таким образом, проверяются некоторые сетевые аспекты вне контейнеров, но иногда такой подход оказывается довольно сложным из-за специфики окружения или конфигурации системы.
Чтобы не натыкаться на кучу проблем по ходу мониторинга сети, советую использовать утилиту Mizu. Многие программисты, работающие с Kubernetes, мечтали бы найти подобный инструмент раньше.
Mizu можно описать как простой, но при этом мощный инструмент для отслеживания трафика в Kubernetes. Он позволяет отслеживать все API-коммуникации между микросервисами независимо от используемого протокола и упрощает процесс дебаггинга соединений.
Установка Mizu
Процесс установки довольно простой. Все что нужно – загрузить бинарный файл Mizu и настроить соответствующие разрешения в системе. Выбор бинарного файла (установщика) зависит от архитектуры устройства. Например, чтобы установить Mizu на компьютер Apple с чипом Intel нужно ввести в терминал команду:
Обычно этого достаточно. Загруженный бинарный файл теперь можно использовать для подключения к кластеру Kubernetes и работы с Kubernetes API, но для этого нужно внести несколько изменений в настройки Докера.
Вот как это может выглядеть в случае с базовым nginx-сервером. Начать стоит с команды:
kubectl run simple-app --image=nginx --port 3000
После развертки базового приложения на основе nginx переходим непосредственно к запуску Mizu. Делается это всего одной командой:
mizu tap
Через пару секунд на экране появится веб-страница с интерфейсом Mizu. Здесь и отображается весь трафик на выбранном сервере. Но для настройки такого поведения необходимо внести изменения в параметры портов. К примеру, если ваше приложение называется ‘my-app’, команда будет выглядеть так:
kubectl expose pod/my-app
После этого деплоим еще один временный сервер, используя готовый образ (при помощи следующей команды):
kubectl run -it --rm --image=curlimages/curl curly -- sh
Теперь можно использовать утилиту curl для отправки запросов непосредственно на nginx-сервер. Например, так:
curl -vvv http://my-app:3000
Уже после первых нескольких запросов вы увидите большой объем полезной информации, отображающейся в интерфейсе Mizu. В первую очередь стоит отметить то, насколько детально описываются все процессы, происходящие внутри Kubernetes. Но что еще важнее, в Mizu есть диаграммы, наглядно показывающие зависимости между запросами с учетом используемого протокола и другой полезной информации.
Конечно, Mizu не сможет заменить более сложные продукты и полноценные системы наблюдения за сетью. Но это довольно удобный инструмент, который поможет вам исправить немало багов по ходу работы с Kubernetes, и его точно стоит иметь под рукой.
Контейнеры Docker обычно считаются неизменяемыми после начала их работы.
Однако некоторые параметры конфигурации можно изменять динамически, например, имя контейнера и лимиты его аппаратных ресурсов.
В этом руководстве мы покажем вам, как использовать встроенные команды Docker для изменения выбранных параметров запущенных контейнеров.
Мы также рассмотрим, что не следует изменять, и обходные пути, которые можно использовать, если вы считаете, что это необходимо.
Переименование контейнера
Самым простым изменением является переименование созданного контейнера. Имена присваиваются с помощью флага –name для команды docker run.
Если имя не указано, демон Docker присваивает случайное имя.
Вы можете использовать имена для ссылок на контейнеры в командах Docker CLI; выбор подходящего запоминающегося имени позволяет избежать выполнения команды docker ps для поиска автоматически назначенного имени или ID контейнера.
Команда docker rename используется для изменения имен контейнеров после создания.
Она принимает два аргумента: ID или текущее имя целевого контейнера и новое имя, которое нужно присвоить:
# docker rename <target ID or name> <new name>
docker rename old_name new_name
Изменение политики перезапуска
Restart policies определяют, должны ли контейнеры запускаться автоматически после перезагрузки хоста или запуска демона Docker.
Четыре доступные политики позволяют принудительно запустить контейнер, заставить его оставаться остановленным или условно запустить на основе предыдущего кода выхода или состояния работы контейнера.
Docker поддерживает изменение политик перезапуска “на лету”.
Это полезно, если вы планируете перезагрузить хост или демон Docker и хотите, чтобы определенный контейнер оставался остановленным – или автоматически запускался – после определенного события.
Приведенный выше пример изменяет политику перезапуска demo_container на unless-stopped.
Эта политика заставляет контейнер запускаться вместе с демоном, если он не был остановлен вручную перед последним выходом демона.
Изменение лимитов аппаратных ресурсов
Команда docker update также может быть использована для изменения ограничений ресурсов, применяемых к контейнерам.
Вы должны передать один или несколько идентификаторов или имен контейнеров, а также список флагов, определяющих ограничения, которые необходимо установить для этих контейнеров.
Флаги доступны для всех ограничений ресурсов, поддерживаемых docker run.
Вот краткий список опций, которые вы можете использовать:
–blkio-weight – Изменить относительный вес Block IO контейнера.
–cpus – Установить количество процессоров, доступных контейнеру.
–cpu-shares – Установить относительный вес доли ЦП.
–memory – Изменить лимит памяти контейнера (например, 1024M).
–memory-swap – настройка объема памяти, который контейнер может обменять на диск; используйте размер, например 1024M, чтобы установить определенный лимит, или -1 для неограниченной замены.
–kernel-memory – Изменить лимит памяти ядра контейнера. Контейнеры, испытывающие недостаток памяти ядра, могут негативно влиять на другие рабочие нагрузки на хост-машине.
–pids-limit – настройка максимального количества идентификаторов процессов, разрешенных внутри контейнера, что ограничивает количество процессов, которые могут быть запущены.
Вот пример использования docker update для изменения лимита памяти и количества CPU для двух контейнеров
Все доступные флаги, кроме –kernel-memory, можно использовать с запущенными контейнерами Linux.
Чтобы изменить лимит памяти ядра, необходимо сначала остановить контейнер с помощью docker stop.
Имейте в виду, что ни один из этих флагов в настоящее время не поддерживается для контейнеров на базе Windows.
Однако они будут работать с контейнерами Linux, запущенными на хост-машине Windows.
Когда не следует использовать эти команды?
Команды docker update и docker rename следует использовать с контейнерами, которые вы создали вручную с помощью команды docker run.
Опасайтесь использовать их для контейнеров, созданных с помощью других инструментов, таких как docker-compose.
Изменение имени контейнера может сделать его необнаружимым для исходного инструмента, что может привести к поломке других компонентов вашего стека.
Кроме того, если вы декларативно определяете ограничения на ресурсы в файле docker-compose.yml, повторное выполнение команды docker-compose up приведет к повторному применению этих исходных ограничений к вашему контейнеру.
Поэтому вам следует придерживаться существующего решения для управления контейнерами, если вы его используете.
Для Compose это означает изменение имен контейнеров и лимитов ресурсов в файле docker-compose.yml, а затем выполнение команды docker-compose up -d для автоматического применения изменений.
Это позволит избежать непреднамеренного сиротства контейнеров или возникновения непреднамеренных побочных эффектов.
Что касается других свойств (образа/порты/тома)?
Аппаратные ограничения, политики ресурсов и имена контейнеров – это единственные параметры конфигурации, которые позволяет изменять Docker CLI.
Вы не можете изменить образ запущенного контейнера; вы также не можете легко изменить другие параметры, такие как привязка портов и тома.
Вам следует создать другой контейнер, если эти значения устарели.
Уничтожьте текущий экземпляр и с помощью команды docker run запустите новый контейнер с новым образом и исправленными настройками.
Поскольку контейнеры должны быть без статических данных и эфемерными, вы должны иметь возможность заменить их в любое время.
Используйте тома для хранения постоянных данных контейнера; этот механизм позволяет повторно подключить файлы с состоянием к новому контейнеру, повторив флаги -v, переданные исходной команде docker run:
docker run -v config-volume:/usr/lib/config --name demo example-image:v1
docker rm demo
# Существующие данные в /usr/lib/config сохранены
docker run -v config-volume:/usr/lib/config --name demo2 example-image:v2
Опасная зона: Изменение других свойств контейнера
Хотя вы должны стремиться заменять контейнеры везде, где это возможно, можно изменять свойства существующих контейнеров путем прямого редактирования конфигурационных файлов Docker.
Будьте осторожны при использовании этого метода: он совершенно не поддерживается, и неправильное изменение может привести к поломке вашего контейнера.
Хотя эта опция предоставляет возможность произвольно редактировать существующие контейнеры, она не будет работать во время их работы.
Используйте команду docker stop my-container, чтобы остановить контейнер, который вы хотите отредактировать, а затем продолжайте вносить свои изменения.
Конфигурационные файлы контейнеров имеют следующий путь
Вам нужно знать полный ID контейнера, а не усеченную версию, которую показывает docker ps.
Для этого можно использовать команду docker inspect:
docker inspect <short id or name> | jq | grep Id
Добравшись до config.v2.json контейнера, вы можете открыть его в текстовом редакторе, чтобы внести необходимые изменения.
В JSON хранится конфигурация контейнера, созданная при запуске docker run.
Вы можете изменить его содержимое, чтобы изменить такие свойства, как привязка портов, переменные окружения, тома, а также точку входа контейнера и команду.
Чтобы добавить привязку к порту, найдите в файле ключ PortBindings, затем вставьте новый элемент в объект:
После завершения редактирования перезапустите службу Docker и ваш контейнер:
sudo service docker restart
docker start my-container
Заключение
Контейнеры Docker должны быть эфемерными единицами, которые вы заменяете, когда их конфигурация устаревает.
Несмотря на это намерение, существуют сценарии, когда необходимо изменить существующий контейнер.
Docker обрабатывает наиболее распространенные случаи – изменение имени и корректировка лимита ресурсов в реальном времени – с помощью встроенных команд CLI, таких как docker update.
Когда вы хотите изменить другое свойство, всегда старайтесь в первую очередь заменить контейнер.
Это минимизирует риск поломки и соответствует модели неизменяемости Docker.
Если вы все же попали в ситуацию, когда необходимо изменить существующий контейнер, вы можете вручную изменить внутренние конфигурационные файлы Docker, как описано выше.
Наконец, помните, что контейнеры, управляемые другими инструментами экосистемы, такими как Docker Compose, должны быть изменены с помощью этих механизмов.
В противном случае вы можете обнаружить, что контейнеры неожиданно осиротеют или будут перезаписаны, если инструмент не знает о внесенных вами изменениях.
Kubernetes поставляется в комплекте с выдающимся CLI.
Для основных операций это работает чудесно.
Увы, когда нужно что-то сделать быстро, сложность возрастает.
Сообщество Kubernetes создало все виды веб-инструментов для мониторинга вашего кластера – kube ops, grafana и т. д.
Однако наличие полностью настроенного терминала быстро сократит время, необходимое для поиска причины проблемы.
Это основная часть вашего швейцарского армейского ножа.
Ниже приведен очень короткий список инструментов с открытым исходным кодом, которые можно применить в своем терминале.
При совместном использовании они позволяют управлять кластером kubernetes, быстро устранять неполадки и отслеживать поведение.
Предпосылки
Прежде чем приступить к изучению этих инструментов, я настоятельно рекомендую установить zsh.
Это выдающаяся оболочка с открытым исходным кодом для стандартного терминала OSX.
Он более многофункциональный и интуитивно понятный, а плагины, которые вы можете установить, просто фантастические.
Некоторые из перечисленных инструментов предполагают, что у вас установлен ZSH.
Лучшие инструменты для управления Kubernetes
k9s
Я начинаю с самого мощного.
K9s – это основа CLI для кластера kubernetes.
Вы можете проваливаться по SSH прямо в поды одним нажатием клавиши, просматривать журналы, удалять ресурсы и многое другое.
Он обеспечивает выдающийся доступ к наиболее распространенным операциям, которые вы будете выполнять.
Это основной продукт для любого инженера, использующего kubernetes.
kubectx
Очень редко у нас будет только один кластер.
Переключение между ними можно так же просто осуществлять:
kubectl config use-context my-context
Но при этом есть некоторые предпосылки:
Вам нужно знать имя кластера, прежде чем запускать команду.
Есть другая, похожая команда set-context, которая может сбить вас с толку.
kubectx представляет более простую альтернативу этому варианту.
Если вы запустите kubectx самостоятельно, он перечислит все контексты в вашем файле .kube/config.
Затем вы можете указать название интересующего вас контекста:
kubectx my-context
Не нужно запоминать все контексты, не нужно вручную проверять файлы и нет возможности ввести неправильную команду.
Красиво и просто.
В сочетании с K9s, этот набор обеспечивает классную навигацию из вашего CLI с минимальными нажатиями клавиш.
kubens
Как только вы переключаетесь между контекстами, вы можете долго копаться в определенном пространстве имен.
Еще раз, очень часто в вашем кластере имеется несколько пространств имен.
Короче, в двух словах это то же самое, что kubectx, только для пространств имен.
kubens kube-system
Теперь все ваши команды по умолчанию выполняются в пространстве имен системы kube-system.
Вы также можете запустить Kubens без флагов, чтобы увидеть список ваших пространств имен.
kube-ps1
Таким образом, вы можете переключаться между контекстами и пространствами имен.
Но как узнать, на кого вы сейчас нацелены?
Каждый раз постоянно это проверять?
На данный момент, чтобы узнать, вам нужно запустить:
kubens
kubectx
kubectl <my-command>
Чтобы не делать этого, ps1 является плагином zsh, который автоматически покажет вам ваш текущий контекст и пространство имен:
Теперь вы можете увидеть, на какое пространство имен и контекст вы указываете, не выполняя ни одной команды.
Он также очень настраиваемый – вы можете отключить пространство имен или контекст, если вас интересует только что-то одно из них, или вы можете использовать kubeoff, чтобы полностью отключить все это.
popeye
Popeye запускает автоматическое сканирование ресурсов в вашем хранилище и выявляет очевидные проблемы.
Это новый инструмент, который я нашел очень полезным.
Если вы затеяли генеральную уборку в кластере, начните с popeye, и вы получите четкие указания о том, что нужно исправить.
Stern
Вы когда-нибудь использовали логи kubectl?
Заметили, что вы можете следить только за журналами с одного пода одновременно?
Не беспокойтесь больше об этом!
Stern – это инструмент, который позволяет вам извлекать логи из нескольких подов, основываясь на очень гибком запросе.
K9s предоставляет пользовательский интерфейс терминала для взаимодействия с кластерами Kubernetes. Цель этого Open Source-проекта — облегчить удобную навигацию по приложениям в K8s, наблюдение за ними и управление ими. K9s постоянно следит за изменениями в Kubernetes и предлагает быстрые команды для работы с наблюдаемыми ресурсами.
Проект написан на Go, существует уже более полутора лет: первый коммит был сделан 1 февраля 2019 года. На момент написания статьи насчитывается 9000+ звезд на GitHub и около 80 контрибьюторов. Посмотрим, что умеет k9s?
Установка и запуск
Это клиентское (по отношению к кластеру Kubernetes) приложение, которое проще всего запустить как Docker-образ:
docker run --rm -it -v $KUBECONFIG:/root/.kube/config quay.io/derailed/k9s
Для некоторых Linux-дистрибутивов и других ОС также есть готовые для установки пакеты. В общем случае для Linux-систем можно установить бинарный файл:
Каких-то специфических требований к самому кластеру K8s нет. Судя по отзывам, приложение работает и с такими старыми версиями Kubernetes, как 1.12.
Приложение запускается, используя стандартный конфиг .kube/config — аналогичному тому, как это делает kubectl.
Навигация
По умолчанию открывается окно со стандартным namespace, который указан для контекста. То есть, если вы прописали kubectl config set-context --current --namespace=test, то и откроется namespace test. (О смене контекстов/пространств имён см. ниже.)
Переход в режим команд осуществляется нажатием на «:». После этого можно управлять работой k9s с помощью команд — например, для просмотра списка StatefulSets (в текущем пространстве имен) можно ввести :sts.
Для некоторых других ресурсов Kubernetes:
:ns — Namespaces;
:deploy — Deployments;
:ing — Ingresses;
:svc — Services.
Чтобы вывести полный список типов ресурсов, доступных для просмотра, есть команда :aliases.
Удобно просматривать и список команд, доступных по горячим комбинациям клавиш в рамках текущего окна: для этого достаточно нажать на «?».
Также в k9s есть режим поиска, для перехода в который достаточно ввести «/». С ним осуществляется поиск по содержимому текущего «окна». Допустим, если вы до этого ввели :ns, у вас открыт список пространств имён. Если их слишком много, то, чтобы не скроллить долго вниз, достаточно в окне с namespaces ввести /mynamespace.
Для поиска по лейблам можно выбрать все pod’ы в нужном пространстве имён, после чего ввести, например, / -l app=whoami. Мы получим список pod’ов с этим лейблом:
Поиск работает во всех видах окон, включая логи, просмотр YAML-манифестов и describe для ресурсов — подробнее об этих возможностях см. ниже.
Как в целом выглядит последовательность действий для навигации?
С помощью команды :ctx можно выбрать контекст:
Для выбора namespace’а есть уже упоминавшаяся команда :ns, а далее можно воспользоваться поиском для нужного пространства: /test.
Если теперь выбрать интересующий нас ресурс (например, всё тот же StatefulSet), для него покажется соответствующая информация: сколько запущено pod’ов с краткими сведениями о них.
Могут быть интересны только pod’ы — тогда достаточно ввести :pod. В случае с ConfigMap’ами (:cm — для списка этих ресурсов) можно выбрать интересующий объект и нажать на «u», после чего K9s подскажет, кто конкретно его (этот CM) использует.
Ещё одна удобная фича для просмотра ресурсов — их «рентген» (XRay view). Такой режим вызывается командой :xray RESOURCE и… проще показать, как он работает, чем объяснять. Вот иллюстрация для StatefulSets:
(Каждый из этих ресурсов можно редактировать, изменять, делать describe.)
А вот Deployment с Ingress:
Работа с ресурсами
О каждом ресурсе можно получить информацию в YAML или его describe нажатием на соответствующие клавиатурные сочетания («y» и «d» соответственно). Базовых операций, конечно, ещё больше: их список и клавиатурные сочетания всегда на виду благодаря удобной «шапке» в интерфейсе (скрывается нажатием на Ctrl + e).
При редактировании любого ресурса («e» после его выбора) открывается текстовый редактор, определённый в переменных окружения (export EDITOR=vim).
А вот как выглядит подробное описание ресурса (describe):
Такой вывод (или вывод просмотр YAML-манифеста ресурса) можно сохранить с помощью привычного сочетания клавиш Ctrl + s. Куда он сохранится, будет известно из сообщения K9s:
Из созданных файлов-бэкапов можно и восстанавливать ресурсы, предварительно убрав системные лейблы и аннотации. Для этого потребуется перейти в директорию с ними (:dir /tmp), после чего выбрать нужный файл и применить apply.
К слову, в любой момент можно откатиться и на прошлый ReplicaSet, если с текущим есть проблемы. Для этого надо выбрать нужный RS (:rs для их списка):
… и выполнить rollback с помощью Ctrl + l. Мы должны получить уведомление, что все прошло успешно:
k9s/whoami-5cfbdbb469 successfully rolled back
А чтобы масштабировать реплики, достаточно нажать на «s» (scale) и выбрать нужное количество экземпляров:
В любой из контейнеров можно зайти с помощью shell: для этого перейдите к нужному pod’у, нажмите на «s» (shell) и выберите контейнер.
Другие возможности
Конечно, поддерживается и просмотр логов («l» для выбранного ресурса). А чтобы смотреть новые логи, нет необходимости постоянно нажимать Enter: достаточно сделать маркировку («m»), после чего отслеживать только новые сообщения.
Также в этом же окне можно выбрать временной диапазон для вывода логов:
клавиша «1» — за 1 минуту;
«2» — 5 минут;
«3» — 15 минут;
«4» — 30 минут;
«5» — 1 час;
«0» — за все время жизни pod’а.
Специальный режим работы Pulse (команда :pulse) показывает общие сведения о Kubernetes-кластере:
В нем можно увидеть количество ресурсов и их состояние (зеленым показываются те, что имеют статус Running).
Еще одна интересная функция K9s называется Popeye. Она проверяет все ресурсы на определённые критерии корректности и выводит получившийся «рейтинг» с пояснениями. Например, можно увидеть, что не хватает проб или лимитов, а какой-то контейнер может запускаться под root…
Имеется базовая поддержка Helm. Например, так можно посмотреть релизы, задеплоенные в кластер:
:helm all # все
:helm $namespace # в конкретном пространстве имен
Benchmark
В K9s встроили даже hey — это простой генератор нагрузки на HTTP-сервер, альтернатива более известному ab (ApacheBench).
Чтобы включить его, потребуется активация port-forward в pod’е. Для этого выбираем pod и нажимаем на Shift + f, переходим в подменю port-forward с помощью алиаса «pf».
После выбора порта и нажатия на Ctrl + b запустится сам benchmark. Результаты его работы сохраняются в /tmp и доступны для последующего просмотра в K9s.
Для изменения конфигурации benchmark’а нужно создать файл $HOME/.k9s/bench-<my_context>.yml (определяется для каждого кластера).
NB: Важно, чтобы расширение всех YAML-файлов в директории .k9s было именно .yml (.yaml не работает корректно).
Пример конфигурации:
benchmarks:
defaults:
# Количество потоков
concurrency: 2
# Количество запросов
requests: 1000
containers:
# Настройки для контейнера с бенчмарком
# Контейнер определяется как namespace/pod-name:container-name
default/nginx:nginx:
concurrency: 2
requests: 10000
http:
path: /
method: POST
body:
{"foo":"bar"}
header:
Accept:
- text/html
Content-Type:
- application/json
services:
# Можно проводить бенчмарк на сервисах типа NodePort и LoadBalancer
# Синтаксис: namespace/service-name
default/nginx:
concurrency: 5
requests: 500
http:
method: GET
path: /auth
auth:
user: flant
password: s3cr3tp455w0rd
Интерфейс
Вид столбцов для списков ресурсов модифицируется созданием файла $HOME/.k9s/views.yml. Пример его содержимого:
k9s:
views:
v1/pods:
columns:
- AGE
- NAMESPACE
- NAME
- IP
- NODE
- STATUS
- READY
v1/services:
columns:
- AGE
- NAMESPACE
- NAME
- TYPE
- CLUSTER-IP
Правда, не хватает колонки по лейблам, на что есть issue в проекте.
Сортировка по столбцам осуществляется клавиатурными сочетаниями:
Shift + n — по имени;
Shift + o — по узлам;
Shift + i — по IP;
Shift + a — по времени жизни контейнера;
Shift + t — по количеству рестартов;
Shift + r — по статусу готовности;
Shift + c — по потреблению CPU;
Shift + m — по потреблению памяти.
Если же кому-то не нравится цветовое оформление по умолчанию, в K9s даже поддерживаются скины. Готовые примеры (7 штук) доступны здесь. Вот пример одного из таких скинов (in the navy):
Плагины
Наконец, плагины позволяют расширять возможности K9s. Сам я в работе использовал только один из них — kubectl get all -n $namespace.
Выглядит это следующим образом. Создаем файл $HOME/.k9s/plugin.yml с таким содержимым:
plugin:
get-all:
shortCut: g
confirm: false
description: get all
scopes:
- all
command: sh
background: false
args:
- -c
- "kubectl -n $NAMESPACE get all -o wide | less"
Теперь можно перейти в пространство имён и нажать на «g» для выполнения с соответствующей команды:
Среди плагинов есть, например, интеграции с kubectl-jq и утилитой для просмотра логов stern.
Заключение
На мой вкус, K9s оказалась очень удобна в работе: с ней довольно быстро привыкнуть искать всё нужное без использования kubectl. Порадовал просмотр логов и их сохранение, быстрое редактирование ресурсов, скорость работы в целом*, оказался полезным режим Popeye. Отдельного упоминания стоят возможности создавать плагины и дорабатывать приложение под свои нужды.
* Хотя при большом объеме логов замечал также медленную работу K9s. В такие моменты утилита «съедала» 2 ядра у Intel Xeon E312xx и могла даже зависать.
Чего не хватает в настоящий момент? Быстрого отката на предыдущую версию (речь не про RS) без перехода в директорию. К тому же, восстановление происходит только для всего ресурса: если вы удалили аннотацию или лейбл, придется удалить и восстановить весь ресурс (здесь и понадобится переходить в директорию). Другая мелочь — не хватает даты таких сохраненных «бэкапов».
За последние несколько лет я очень полюбил GitLab CI. В основном за его простоту и функциональность. Достаточно просто создать в корне репозитория файл .gitlab-ci.yml , добавить туда несколько строчек кода и при следующем коммите запустится пайплайн с набором джобов, которые будут выполнять указанные команды.
А если добавить к этому возможности include и extends, можно делать достаточно интересные вещи: создавать шаблонные джобы и пайплайны, выносить их в отдельные репозитории и повторно использовать в разных проектах без копирования кода.
Но к сожалению, не всё так радужно, как хотелось бы. Инструкция script в GitLab CI очень низкоуровневая. Она просто выполняет те команды, которые ей переданы в виде строк. Писать большие скрипты внутри YAML не очень удобно. По мере усложнения логики количество скриптов увеличивается, они перемешиваются с YAML делая конфиги нечитаемыми и усложняя их поддержку.
Мне очень не хватало какого-то механизма, который бы упростил разработку больших скриптов. В результате у меня родился микрофреймворк для разработки GitLab CI, про который я и хочу рассказать в этой статье (на примере простого пайплайна для сборки docker-образов).
Пример: Сборка docker-образов в GitLab
Я хочу рассмотреть процесс создания пайплайна на примере простой задачи, которую часто встречал у разных команд: создание базовых docker-образов в GitLab для их повторного использования.
Например, команда пишет микросервисы и хочет для них для всех использовать свой собственный базовый образ с набором предустановленных утилит для отладки.
Или другой пример, команда пишет тесты и хочет используя services прямо в GitLab создавать для них временную базу данных (или очередь, или что-то ещё) используя свой собственный образ.
В целом, создать docker-образ из отдельного докерфайла в GitLab достаточно просто. Например можно сделать это с помощью такого .gitlab-ci.yml:
Здесь мы создаём пайплайн с одним джобом Build, который на каждый коммит логинится в GitLab Container Registry, собирает образ из файла докерфайла в корне репозитория и пушит полученный образ. В качестве тега образа используется название ветки или тега, где происходит сборка (возможно не самая лучшая схема, но это сделано для примера).
Но предположим, что нам необходимо создать чуть более сложный пайплайн, который будет работать следующим образом:
В репозитории может находиться несколько докерфайлов, которые будут располагаться в подпапке dockerfiles.
На каждый коммит должен запускаться пайплайн с двумя джобами:
Build All — будет находить и пересобирать все докерфайлы в подпапке dockerfiles. Этот джоб будет ручным (запускается по кнопке из интерфейса).
Build Changed— будет находить и пересобирать все докерфайлы в подпапке dockerfiles, которые были изменены в последнем коммите. Этот джоб будет автоматическим (запускается сразу при коммите) и будет появляться только при изменении файлов.
Подобный пайплайн должен достаточно хорошо работать. Он будет быстро пересобирать все изменённые докерфайлы при каждом коммите. При этом у разработчиков останется возможность вручную пересобрать сразу всё, если это потребуется.
Структура репозитория для такого пайплайна может выглядеть следующим образом:
Содержимое и название докерфайлов особой роли не играет, но в данном случае это докерфайлы для разных версий .NET.
В интерфейсе гитлаба подобный пайплайн может выглядеть следующим образом:
Результатом работы такого пайплайна станет набор docker-образов, которые можно будет найти в Container Registry проекта:
А теперь попробуем создать пайплайн, который бы решал нашу задачу.
Шаг 1: Решение «в лоб»
Начнём самого простого и очевидного решения. Мы можем поместить весь код в файл .gitlab-ci.yml. В таком случае он будет выглядеть следующим образом:
services:
- docker:dind
stages:
- Build
Build All:
stage: Build
image: docker
when: manual
script:
- |
dockerfiles=$(find "dockerfiles" -name "*.Dockerfile" -type f)
docker login "$CI_REGISTRY"
--username "$CI_REGISTRY_USER"
--password "$CI_REGISTRY_PASSWORD"
for dockerfile in $dockerfiles; do
path=$(echo "$dockerfile" | sed 's/^dockerfiles///' | sed 's/.Dockerfile$//')
tag="$CI_REGISTRY/$CI_PROJECT_PATH/$path:$CI_COMMIT_REF_SLUG"
echo "Building $dockerfile..."
docker build --file "$dockerfile" --tag "$tag" .
echo "Pushing $tag..."
docker push "$tag"
done
Build Changed:
stage: Build
image: docker
only:
changes:
- 'dockerfiles/*.Dockerfile'
- 'dockerfiles/**/*.Dockerfile'
script:
- |
apk update
apk add git # Вообще говоря, так не очень хорошо делать, но для примера можно...
dockerfiles=$(git diff --name-only HEAD HEAD~1 -- 'dockerfiles/***.Dockerfile')
docker login "$CI_REGISTRY"
--username "$CI_REGISTRY_USER"
--password "$CI_REGISTRY_PASSWORD"
for dockerfile in $dockerfiles; do
path=$(echo "$dockerfile" | sed 's/^dockerfiles///' | sed 's/.Dockerfile$//')
tag="$CI_REGISTRY/$CI_PROJECT_PATH/$path:$CI_COMMIT_REF_SLUG"
echo "Building $dockerfile..."
docker build --file "$dockerfile" --tag "$tag" .
echo "Pushing $tag..."
docker push "$tag"
done
Запускается вручную, т.к. содержит настройку when: manual.
Выполняет поиск всех докерфайлов при помощи команды:
find "dockerfiles" -name "*.Dockerfile" -type f
Создаём джоб Build Changed, который:
Создаётся только при изменении докерфайлов, т.к. содержит настройку only:changes.
Выполняет поиск всех докерфайлов, изменённых в последнем коммите при помощи команды:
git diff --name-only HEAD HEAD~1 -- 'dockerfiles/***.Dockerfile'
В остальном оба джоба работают одинаково, после поиска докерфайлов, они проходят по ним циклом, вырезают из названия каждого докерфайла префикс dockerfiles/ и суффикс .Dockerfile, после чего собирают его и пушат в GitLab Container Registry.
Данный пайплайн работает но есть очевидные проблемы:
Скрипты обоих джобов практически полностью дублируются (за исключением поиска докерфайлов)
Скрипты сильно загромождают конфиг, усложняя его понимание.
В этом месте появляется тот микрофреймворк для разработки GitLab CI про который я упоминал в самом начале.
GitLab CI Bootstrap
GitLab CI Bootstrap — это микрофреймворк для разработки GitLab CI. Его основные цели:
Разделить описание пайплайна (файл .gitlab-ci.yml) и его скрипты (bash или shell), чтобы оставить YAML более декларативным.
Дать возможность разбивать большие скрипты на более мелкие и подключать одни скрипты к другим, чтобы улучшить структурирование кода и упростить его поддержку.
Дать возможность выносить скрипты и джобы (со скриптами) в отдельные репозитории, чтобы повторно использовать их в разных проектах.
GitLab CI Bootstrap состоит из одного единственного файла bootstrap.gitlab-ci.yml, который необходимо подключить в файле .gitlab-ci.yml при помощи include. Сделать это можно несколькими способами. Проще всего скопировать файл в свой проект и подключить его через include:local:
include:
- local: 'bootstrap.gitlab-ci.yml'
В этом файле находится один единственный скрытый джоб .bootstrap, который имеет следующий вид:
.bootstrap:
before_script:
- |
...
Другие джобы могут расширять джоб .bootstrap, используя extends:
example:
extends: '.bootstrap'
script:
- '...'
В таких джобах будет включаться механизм загрузки скриптов (который описан ниже), который позволяет загружать скрипты из текущего репозитория или из любого другого репозитория внутри того же экземпляра GitLab.
При этом для загрузки скриптов не нужны специальные токены доступа. Загружать скрипты можно даже из внутреннего или приватного репозиториев. Главное, чтобы у разработчика, запустившего пайплайн был доступ на чтение к этим репозиториям.
Единственным требованием для загрузчика является наличие bash или shell. Также желательно наличие git, однако при его отсутствии он будет установлен автоматически. Благодаря этому джоб .bootstrap можно использовать практически с любыми официальными docker-образами и он будет там корректно работать.
Итак, давайте посмотрим, как это поможет нам упростить наш пайплайн для сборки docker-образов.
Шаг 2: Выносим все скрипты в отдельный файл
Все джобы, использующие джоб .bootstrap автоматически проверяют наличие в репозитории специального файла .gitlab-ci.sh. При его наличии этот файл автоматически загружается и все переменные и функции, которые в нём определены становятся доступными на этапе выполнения скриптов джоба.
Поэтому мы можем вынести все скрипты в этот файл и разбить их на функции, чтобы избежать дублирования, а затем вызвать эти функции из файла .gitlab-ci.yml:
Теперь все скрипты у нас вынесены в отдельный файл gitlab-ci.sh и разбиты на функции, которые вызываются из файла .gitlab-ci.yml. Функции search_all_dockerfiles_task и search_changed_dockerfiles_task заполняют переменную DOCKERFILES, которая позже используется функцией build_and_push_dockerfiles_task для сборки докерфайлов.
Шаг 3: Переносим пайплайн в отдельный репозиторий
Мы сделали работающий пайплайн и вынесли его скрипты в отдельный файл, но пока этот пайплайн находится в том же репозитории, что и докерфайлы. Это означает, что если другая команда захочет использовать его для сборки своих докерфайлов, им придётся скопировать все скрипты. А это в свою очередь означает, что если нам необходимо будет внести в пайплайн какие-нибудь изменения, то потом их нужно будет внести и всем командам, которые скопировали себе пайплайн.
Вместо этого мы можем вынести пайплайн в отельный репозиторий и предложить нескольким командам подключить его через include.
Однако для этого придётся внести в пайплайн небольшое изменение. Дело в том, что поскольку скрипты теперь будут фактически расположены не в том же репозитории, где запускается пайплайн, джоб должен как-то это понять и определить, откуда загрузить скрипты. Это делается при помощи специальных переменных, которые добавляются в пайплайн.
Если в джобе, использующей джоб .bootstrap определена переменная CI_IMPORT, то этот джоб проверит наличие трёх переменных: CI_{module}_PROJECT, CI_{module}_REF и CI_{module}_FILE, где {module} — это нормализованное значение переменной CI_IMPORT (приведённое в верхнему регистру с заменой дефисов на подчёркивания).
Если все три переменные существуют, джоб перед загрузкой скриптов из файла .gitlab-ci.sh сначала загрузит файл, указанный в этих переменных.
Наш репозиторий с докерфайлами теперь будет выглядеть так:
Оба наших джоба Build All и Build Changed содержат переменную CI_IMPORT со значением dockerfiles. Поэтому загрузчик при старте джоба также проверит наличие трёх переменых: CI_DOCKERFILES_PROJECT, CI_DOCKERFILES_REF и CI_DOCKERFILES_FILE, которые также определены в пайплайне.
Благодаря этим переменным загрузчик понимает, что должен загрузить скрипты из файла dockerfiles.gitlab-ci.sh, который находится в репозитории $CI_PROJECT_NAMESPACE/dockerfiles-example-ci, в ветке step3.
Кроме того, мы убрали из скриптов функцию install_git_task. Явная установка git больше не нужна, т.к. загрузчик сам по себе требует git для загрузки скриптов из другого репозитория и поэтому устанавливает git самостоятельно.
Шаг 4: Разбиваем большие скрипты на отдельные файлы
Мы вынесли пайплайн в отдельный репозиторий и теперь другие команды могут подключать его в свои проекты простым инклюдом. Изменения, которые мы будем вносить в пайплайн будут автоматически отражаться на всех командах и нам не придётся ходить по командам с просьбами обновиться на последнюю версию.
Однако сейчас все скрипты у нас находятся в одном файле dockerfiles.gitlab-ci.sh. По мере добавления функциональности, количество кода в нём будет расти и ориентироваться в нём будет всё сложнее и сложнее.
К счастью, если в процессе загрузки скрипта в нём встречается вызов функции include, файл, указанный в аргументе этой функции также загружается. При этом не важно, в каком репозитории находится загружаемый скрипт. Функция include всегда загружет файл из того же репозитория, из которого она вызвана.
Также хочу отметить, что эта функция всегда загружает каждый файл только один раз, даже если вызов функции include с этим файлом встретится несколько раз.
Поэтому мы можем разбить наш файл dockerfiles.gitlab-ci.sh на несколько более мелких:
Файл dockerfiles.gitlab-ci.sh:
include "tasks/search.sh"
include "tasks/docker.sh"
В данной статье мы прошли по шагам создания переиспользуемого пайплайна для GitLab CI с использованием bash и GitLab CI Bootstrap.
Чтобы не увеличивать статью я описал только самые основные возможности своего микрофреймворка. Кроме этого он также позволяет:
Подключать скрипты из нескольких репозиториев по цепочке. Т.е. общий пайплайн, вынесенный в отдельный репозиторий сам по себе может также подключать скрипты из ещё нескольких репозиториев. Это создаёт большой простор для создания целых библиотек для GitLab CI.
Получать доступ к произвольным файлам в подключаемом репозитории. Это можно использовать для написания скриптов на других языках, таких как Python или C#.
Делать хуки на определённые функции. Это позволяет разработчикам, использующим общий пайплайн расширять его логику при необходимости.
Если нужно, я могу рассказать про эти возможности в комментариях.
Я пока не могу сказать, что мой микрофреймворк близок к версии 1.0. Ещё есть вещи, над которыми стоит поработать. Тем не менее, ранние его версии я успешно использовал в нескольких проектах и это действительно сильно упростило мне разработку.
Интересно, сколько места занимает Docker в вашей системе Linux?
В основном, все образы Docker, контейнеры и другие связанные с ними сущности находятся в каталоге /var/lib/docker.
Вы можете проверить размер этого каталога и получить общее дисковое пространство, используемое Docker:
$ sudo du -sh /var/lib/docker
4.9G /var/lib/docker
Но это не очень подробно, и вам, возможно, придется углубиться в этот каталог, чтобы увидеть, какой компонент использует какое пространство.
К счастью, Docker предоставил инструменты для получения этой информации в более полезном виде.
Проверка использования дискового пространства Docker
Самый простой, “докеровский” способ узнать, сколько места занимают образы, контейнеры, локальные тома или кэш сборки:
docker system df
При выполнении этой команды (при необходимости используйте sudo) вы получите всю информацию об использовании диска, сгруппированную по компонентам Docker.
$ docker system df
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 4 4 1.065GB 0B (0%)
Containers 4 4 5.705kB 0B (0%)
Local Volumes 7 7 1.108GB 0B (0%)
Build Cache 0 0 0B 0B
Это определенно лучше, чем смотреть на общий размер /var/lib/docker.
Вы можете увидеть, сколько места занимают образы, контейнеры и тома.
Однако это все еще не дает четкого представления о том, какой образ или том занимает больше места.
На самом деле, это так.
Команда df системы docker имеет опцию verbose -v, которая предоставляет все эти детали.
docker system df -v
Вот подробный вывод:
$ docker system df -v
Images space usage:
REPOSITORY TAG IMAGE ID CREATED SIZE SHARED SIZE UNIQUE SIZE CONTAINERS
ghost 4.32.0 b40265427368 8 weeks ago 468.8MB 0B 468.8MB 1
jrcs/letsencrypt-nginx-proxy-companion latest 037cc4751b5a 13 months ago 24.35MB 0B 24.35MB 1
jwilder/nginx-proxy latest 509ff2fb81dd 15 months ago 165MB 0B 165MB 1
mariadb 10.5.3 f5d2bcaf057b 20 months ago 407MB 0B 407MB 1
Containers space usage:
CONTAINER ID IMAGE COMMAND LOCAL VOLUMES SIZE CREATED STATUS NAMES
899cc90e85d9 ghost:4.32.0 "docker-entrypoint.s…" 1 0B 8 weeks ago Up 8 weeks ghost_ghost_6
17b58fdafbce jrcs/letsencrypt-nginx-proxy-companion "/bin/bash /app/entr…" 4 571B 3 months ago Up 2 months letsencrypt-proxy-companion
58f99f46ee03 jwilder/nginx-proxy "/app/docker-entrypo…" 5 5.13kB 3 months ago Up 2 months jwilder-nginx-proxy
fb907286b60e mariadb:10.5.3 "docker-entrypoint.s…" 1 2B 3 months ago Up 2 months ghost_db_1
Local Volumes space usage:
VOLUME NAME LINKS SIZE
ghostdb 1 434.7MB
jwilder-nginx-with-ssl_acme 2 36.09kB
jwilder-nginx-with-ssl_certs 2 25.12kB
jwilder-nginx-with-ssl_dhparam 1 1.525kB
jwilder-nginx-with-ssl_html 2 1.106kB
jwilder-nginx-with-ssl_vhost 2 556B
ghost 1 674MB
Build cache usage: 0B
CACHE ID CACHE TYPE SIZE CREATED LAST USED USAGE SHARED
Проверка размеров образов docker
Если вы просто хотите посмотреть образы Docker и их размеры, вы также можете использовать эту команду:
docker ps --size
Вы должны увидеть столбец SIZE, добавленный к выводу команды:
$ docker ps --size
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES SIZE
1171dcfb7e06 alpine "sleep 10" 10 months ago Up 9 seconds always-policy 0B (virtual 5.61MB)
Проверка размеров запущенных контейнеров
Аналогично, если вы хотите узнать размер запущенных контейнеров Docker, вы можете использовать команду docker ps:
docker ps --size
Вы должны увидеть столбец SIZE, добавленный к выводу команды:
$ docker ps --size
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES SIZE
1171dcfb7e06 alpine "sleep 10" 10 months ago Up 9 seconds always-policy 0B (virtual 5.61MB)
Вы заметили, как вывод говорит 0B, а затем показывает виртуальный размер 5,61 МБ?
Виртуальный размер включает общий базовый образ.
Давайте вернемся к подходу к Linux более конкретно, на примере образа и контейнера Alpine в качестве практического примера.
Использование стандартных команд Linux для анализа использования дискового пространства Docker
Всякий раз, когда вы используете команду docker pull или запускаете команду docker-compose up -d для подготовки запуска приложений, вот как вы смотрите на использование пространства образа, фактически хранящегося на сервере Ubuntu 20.04:
sudo du -sh /var/lib/docker/overlay2/<hash-named-directory>/
Здесь Overlay2 является драйвером хранилища Docker по умолчанию в Ubuntu.
Вы можете подтвердить это, выполнив команду docker info и поискав Storage Driver:
Storage Driver: overlay2
Если оно отличается от вашего, значит, вы используете другой драйвер хранения для Docker.
Аналогично, расположение каталога будет названо в соответствии с тем же драйвером хранения.
Доступность драйвера хранилища зависит от поддержки ядра.
Использование диска конкретного образа
Если вы ищете местоположение конкретных образов, вы можете использовать команду inspect в Docker для извлеченного образа.
Например, я извлек образ alpine с помощью команды docker pull alpine.
Выполните следующую команду для его проверки:
$ docker inspect alpine
После выполнения команды вы заметите три поля в подразделе Data в разделе GraphDriver:
Исходя из вышеприведенной информации, вы можете видеть, что (упомянутый ранее в синтаксисе команды du) в данном случае равен 64c9c0cf8c9cfb0e2168071df0652a317d49f58a68fe86e4a9a525ab9e365e.
Здесь вы можете выполнить следующую команду, чтобы узнать объем пространства, используемого изображением Alpine:
$ sudo du -sh /var/lib/docker/overlay2/64c9c0cf8c9cfb0e2168071df0652a317d49f58a68fe86e4a9a9a525ab9e365e
6.0M /var/lib/docker/overlay2/64c9c0cf8c9cfb0e2168071df0652a317d49f58a68fe86e4a9a9a525ab9e365e
Подобно образам, контейнеры также хранятся в одном каталоге на базе драйвера хранилища.
/var/lib/docker/overlay2
Использование диска конкретным контейнером
Если вы ищете местоположение конкретных контейнеров, вы можете снова использовать команду inspect в Docker для запущенного контейнера.
Например, я запустил контейнер alpine с помощью команды docker run -ti -d alpine.
Запустив команду docker ps, вы увидите, что он запущен:
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
cb341d6a28fa alpine "/bin/sh" 6 seconds ago Up 5 seconds confident_banzai
Здесь контейнер был произвольно назван confident_banzai.
Давайте проверим его:
$ docker inspect confident_banzai
После выполнения вышеуказанной команды вы заметите все четыре поля, упомянутые ранее в подразделе Data в разделе GraphDriver.
В этих местах данные контейнера физически хранятся на хост-системе, как и в случае с образами:
$ sudo du -sh /var/lib/docker/overlay2/d734685e284c92bdcb6063ac292a48813f30f4b0b2dd6fa2882279c569e506a3
32K /var/lib/docker/overlay2/d734685e284c92bdcb6063ac292a48813f30f4b0b2dd6fa2882279c569e506a3
Использование диска конкретного тома
В данном случае существует два типа томов.
Первый – это обычные тома Docker, а второй – bind mounts.
Тома Docker
Если вы ищете местоположение определенных томов, вы можете сначала использовать команду docker volume ls и проверить имя или ID тома.
Скажем, например, я запустил контейнер alpine следующей командой с томом:
docker run -ti -d --name alpine-container -v test-data:/var/lib/app/content alpine
Теперь автоматически будет создан том с именем test-data.
Теперь давайте создадим файл test.md в этом месте:
$ docker exec alpine-container sh -c "touch /var/lib/app/content/test.md"
Убедитесь, что файл действительно был создан:
$ docker exec -ti alpine-container sh
/ # ls /var/lib/app/content/
test.md
/ # exit
Когда вы запустите docker volume ls, в списке появится том с именем test-data:
$ docker volume ls
DRIVER VOLUME NAME
local d502589845f7ae7775474bc01d8295d9492a6c26db2ee2c941c27f3cac4449d1
local e71ee3960cfef0a133d323d146a1382f3e25856480a727c037b5c81b5022cb1b
local test-data
Наконец, вы можете подтвердить фактическое расположение файла на вашей хост-системе:
$ sudo ls -l /var/lib/docker/volumes/test-data/_data
total 0
-rw-r--r-- 1 root root 0 Oct 6 23:20 test.md
Поэтому путь для смонтированного тома всегда находится в каталоге с именем _data внутри соответствующего каталога тома.
Таким образом, вы можете использовать команду du здесь снова для определенных томов!
$ sudo du -sh /var/lib/docker/volumes/test-data/_data
4.0K /var/lib/docker/volumes/test-data/_data
Всегда помните, что нужно записывать имя тома каждый раз, когда вы хотите узнать, сколько места он занимает.
Bind Mounts
Это единственное исключение в Docker, где необходимо использовать подход Linux для мониторинга использования дискового пространства.
В то же время, всегда предпочтительнее сначала остановить работающие контейнеры.
В этом случае смонтированный том с именем test-data станет доступен на стороне контейнера как /var/lib/app/content.
$ sudo du -sh /home/avimanyu/test-data
4.0K /home/avimanyu/test-data
То же самое можно проверить и внутри контейнера:
$ sudo docker exec -ti alpine-container sh
/ # du -sh /var/lib/app/content
4.0K /var/lib/app/content
Как вы можете видеть, оба указанных выше размера одинаковы!
Логи Docker на хосте всегда хранятся в томах.
Используя описанный в этом разделе способ, пользователи также могут сориентироваться и выяснить это, посмотрев на использование дискового пространства томов docker.
Это зависит от приложения и расположения файлов логов в томах приложения.
Бонусные советы
Исходя из того, что вы узнали до сих пор, вполне очевидно, что вы также можете использовать следующую команду для получения данных об использовании диска образами и контейнерами вместе:
sudo du -sh /var/lib/docker/overlay2
Логи Docker на хосте всегда хранятся в томах.
Обычно большой объем Docker, скорее всего, указывает на то, что логи накапливаются и управляются неэффективно.
Используя способ, описанный в разделе “Тома” этой статьи, пользователи также могут сориентироваться и смягчить ситуацию, посмотрев на использование дискового пространства в томах docker.
Это зависит от приложения и расположения файлов журналов в томах приложения.
Резюме
В этом руководстве я использовал общий подход, основанный на Linux, чтобы показать вам, как узнать занятость дискового пространства образов Docker, контейнеров и томов, расположенных на вашем Linux-сервере на уровне хоста.
Вы также узнали, как сделать это предпочтительным (Docker) способом.
Если вы хотите поделиться отзывами, комментариями или предложениями по поводу этого подхода, пожалуйста, оставьте свои мысли в разделе комментариев ниже