Собираем и деплоим в Kubernetes приложение на Node.js с помощью werf
Огромный пошаговый гайд по сборке и деплою Kubernetes с помощью werf. В качестве примера используется приложение на Node.js.
10К открытий11К показов
В статье будет рассмотрено, как собрать Docker-контейнер Node.js-приложения и затем развернуть его в Kubernetes-кластере. Также рассмотрим, как можно легко накатывать изменения в коде и инфраструктуре, а также правильную организацию раздачи asset’ов, подняв для этого перед приложением reverse proxy-сервер.
В качестве замены K8s-кластеру воспользуемся minikube, это позволит малыми затратами подготовить локальное окружения для работы с werf.
О werf
werf — утилита командной строки, помогающая в организации всего цикла доставки ваших приложений в Kubernetes. В основе ее работы лежит едины источник истины — Git-репозиторий, в котором хранится само приложение и его конфигурация. Состояние приложения определяется коммитом, в котором оно зафиксировано. werf собирает его, заливает в container registry, при необходимости дособирая несуществующие слои конечных образов, после чего разворачивает приложение в кластере, обновляя его составный части, если они изменились. Дополнительно werf умеет удалять из container registry устаревшие артефакты. Для этого применяется уникальный алгоритм, основанный на истории Git-репозитория и политиках, настроенных пользователем.
Основная фишка werf — объединение различных инструментов, используемых DevOps-/SRE-инженерами и разработчиками в свой работе, в рамках одной утилиты. Это Git, Docker, container registry, CI-система, Helm и Kubernetes. Компоненты тесно интегрированы между собой и опираются на большой опыт разработчиков утилиты в «кубернетизации» приложений.
Предварительная настройка окружения
Для начала необходимо поставить актуальную версию werf из стабильной ветки. Инструкция по установке доступна в официальной документации.
Описанные далее действия тестировались на ОС Linux (Ubuntu 20.04.03). Утилита доступна и для других ОС (Windows или macOS), ее команды идентичны, но в случае других операционных систем могут быть небольшие особенности.
Установка зависимостей
Необходимо настроить работоспособный Kubernetes-кластер. Выполним следующие шаги:
- настроим minikube — небольшой дистрибутив K8s, предназначенный для локальной установки;
- установим в него NGINX Ingress Controller — специальный компонент, отвечающий за проброс запросов извне внутрь;
- внесем изменения в
/etc/hosts
, чтобы обращаться к приложению в кластер по понятному имени имени, а не просто IP-адресу;
- войдем под своей учетной записью на Docker Hub;
- подготовим Secret с данными для входа в учетную запись Docker Hub;
- развернем приложение в настроенном K8s-кластере.
Установка и настройка minikube
Поставьте minikube по инструкции из официальной документации. В случае, если вы его уже когда-то устанавливали, проверьте, что стоит актуальная версия.
Создайте K8s-кластер:
При каждом запуске kubectl
придется явно указывать пространство имен. Это неудобно, поэтому настроим его по умолчанию (обратите внимание, что сейчас мы только настроим имя, а само пространство имен не создается, это мы сделаем далее по тексту):
Если у вас нет kubectl
, поставить ее можно одним из этих двух способов:
- по инструкции из официальной документации;
- воспользоваться входящей в состав minikube версией утилиты, для чего выполните две команды:
Во втором случае при первом запуске kubectl нужная версия утилиты загрузится из репозиториев minikube и станет доступна для использования.
Проверим состояние кластера, взглянув на запущенные в нем Pod’ы:
Мы должны увидеть такую картину:
Нужно обратить внимание на содержание столбцов READY
и STATUS
: все Pod’ы должны находиться в статусе Running
, а их количество быть равным 1/1
(важно, чтобы левое число соответствовало правому). Если видите что-то другое, то нужно немного подождать — Pod’ы запускаются не мгновенно, поэтому до изменения их статуса на активный может пройти некоторое время.
Установка NGINX Ingress Controller
Далее установим Ingress-контроллер. Он нужен для того, чтобы запросы извне кластера пробрасывались внутрь на наше приложение.
Сделать это можно следующей командой:
Иногда установка занимает довольно много времени. Например, в моем случае процесс занял чуть больше четырех минут.
Если все установилось и активировалось, мы увидим соответствующее сообщение:
Подождем, пока он запустится, и проверим состояние кластера:
Мы должны увидеть что-то наподобие этого:
Если статус Pod’а в нижней строке должен быть Running
.
Правим /etc/hosts
Чтобы к приложению можно было обращаться по доменному имени, нужно добавить соответствующую запись в /etc/hosts
.
Добавим в файл адрес werf-first-app.test
. Посмотрим IP-адрес кластера, выполнив команду minikube ip
. Она должна отобразить правильный IP-адрес (например, 192.168.49.2
). Если вы видите что-то другое, нужно пройти предыдущие шаги заново.
Для добавления строки в hosts
запустим команду:
Чтобы проверить, что все сработало как надо, проверьте содержимое /etc/hosts
любым доступным способом, в конце должна добавиться строка 192.168.49.2 werf-first-app.test
.
Проверим, что окружение настроено правильно. Обратимся на созданный адрес:
Т.к. приложение пока не запущено, Ingress ответит ошибкой 404
:
Логинимся на Docker Hub
Собираемые образы нужно где-то хранить. Очевидное решение — приватный репозиторий на Docker Hub с именем werf-first-app
.
Теперь нужно авторизоваться на Docker Hub под своим аккаунтом:
В случае успешного входа отобразится сообщение Login Succeeded
.
Генерация Secret для доступа к registry
Чтобы у werf был доступ к созданному container registry, необходимо подготовить Secret с параметрами для входа. Располагаться он должен в одном пространстве имен с приложением.
Создадим пространство имен приложения:
В случае успешного выполнения мы увидим сообщение namespace/werf-first-app
created.
Теперь сгенерируем Secret, задав ему имя registrysecret
:
Если отобразилось secret/registrysecret created
, значит Secret создан. В случае возникновения ошибки при создании Secret удалите его kubectl delete secret registrysecret
, а затем создайте еще раз. К сожалению, возможности отредактировать созданный Secret нет.
В документации Kubernetes этот метод создания Secret’ов указан как стандартный.
Теперь у нас готово окружение, и можно начинать разработку и деплой приложения!
Базовое приложение на Node.js
Новое приложение на Node.js
Создадим новый каталог для приложения:
Перейдем в него и сгенерируем скелет приложения командой:
Выполнять эти команды и разрабатывать приложение вручную не нужно, исходные коды всех примеров доступны в репозитории.
Мы убрали из приложения все, что не потребуется на текущем этапе, и оставили только конфигурацию, необходимую для production-приложения.
Добавим в файл app.js
приложения новый endpoint /ping
:
Добавим новый контроллер, в котором будем обрабатывать запрос на созданный endpoint и возвращать ответ Hello, werfer!
. Создадим файл routes/ping.js
со следующим содержимым:
Следующим шагом создадим Dockerfile
, расположив его в корневом каталоге проекта и адаптировав под сборку приложения на Node.js:
Теперь необходимо создать манифесты для Kubernetes, описывающие ресурсы, которые будут в нем разворачиваться. Это будут привычные Helm-чарты.
Нам нужно не так много — Deployment, в котором будет разворачиваться само приложение, а также Service и Ingress, обеспечивающие доступ к приложению изнутри (между сервисами) и снаружи кластера.
Создадим каталог .helm
в корне проекта со следующим содержимым:
Теперь заполним манифесты. Deployment приложения:
Ingress:
Service:
Теперь остается последний шаг — добавить файл конфигурации werf. Это файл werf.yaml
, расположенный в корне проекта и содержащий следующее содержимое:
Приложение готово к деплою!
Деплой приложения и проверка работоспособности
werf использует Git в качестве источника истины о том, с чем работает. Поэтому исходные файлы проекта должны находиться в Git-репозитории, пусть и просто локальном. Инициализируем новый репозиторий и зафиксируем изменения:
Теперь развернем приложение в Kubernetes, выполнив команду:
Ожидаемый результат:
Проверим, что сервисы развернулись и функционируют:
В ответ должно отобразиться Hello, werfer!
.
Раздаем assets правильно
Node.js-приложение способно самостоятельно заниматься раздачей статических файлов. Например, в express для этого можно создать контроллер на базе express.static. Однако разработчики express советуют использовать для раздачи статических файлов более эффективное решение, такое как проксирующий сервер NGINX. Эта рекомендация основана на том, что reverse proxy имеет лучшую производительность и эффективность раздачи статики, нежели штатные средства Node.js.
Сделать это можно несколькими способами. Мы будем использовать достаточно распространенный хорошо масштабируемый способ:
- NGINX-контейнер поднимается перед каждым контейнером с Node.js в том же Pod’е;
- поднятый контейнер будет проксировать все запросы, кроме запросов на статические файлы;
- статические файлы хранятся в самом контейнере с NGINX и раздаются средствами последнего.
Новый endpoint /image
Теперь создадим в приложении новую страницу /image
, на которой будут использоваться статические файлы. Собирать JS-, CSS- и медиафайлы будем с помощью webpack.
Располагаться статика будет в каталоге dist
вместе с шаблоном главной страницы и страницы с изображением.
Добавим новый контроллер в routes/image.js
:
А также новый маршрут в routes/image.js
:
Теперь наше приложение умеет отдавать страницу, на которой используются статические файлы.
Обновление сборки и деплоя
Организация файлов
Сейчас у нас есть только одна главная страница, доступная по адресу /
. Ее содержимое (шаблон) лежит в каталоге /public/index.html
. Добавим к ней еще одну — image.html
, а затем реорганизуем структуру файлов в каталоге /public
таким образом, чтобы разделить их на две части — pages
и assets
:
Страницы, основанные на шаблонах image
и index
, не будут кешироваться, т.к. их содержимое может быть динамическим. Однако связанные с страницами файлы мы будем собирать так, чтобы их имена менялись в зависимости от содержимого. Это позволит надолго кешировать файлы в браузерах пользователей и безболезненно изменять их в в случае необходимости. Содержимое каталога dist
после сборки статики:
Сборка статических файлов
Рассмотрим, как достичь результата, описанного выше. Для начала установим необходимые модули:
Назначение добавленных модулей:
webpack
,webpack-cli
— непосредственно собирает статические файлы;webpack-dev-middleware
— раздает статические файлы во время разработки;html-webpack-plugin
— генерирует HTML-страницы с динамическими статическими файлами;css-loader
— задействовует CSS в сборке;mini-css-extract-plugin
— выделяет CSS в отдельные файлы, что позволяет кешировать их отдельно от JS;css-minimizer-webpack-plugin
— минифициует CSS (будет работать только при включенииmode: "production"
).
Теперь добавим в package.json
параметр "build": "webpack"
. В app.js
добавим обработчик запросов к статическим файлам. Это необходимо на случай, если приложение будет запущено не в production-окружении:
Сконфигурируем webpack в файле webpack.config.js
:
Убедимся, что все настроено правильно — запустим локально приложение и взглянем на результат, доступный на странице http://localhost:3000/image:
Переходим к деплою в кластер.
Подготовка деплоя в кластер
При деплое необходимо отделить статические файлы от самого приложения, разобрав их по разным контейнерам: приложение уйдет в контейнер с Node.js, а статические файлы в контейнер с NGINX.
Адаптируем Dockerfile
:
На последнем шаге происходит копирование файла конфигурации NGINX в контейнер. Создадим его по пути .werf/nginx.conf
:
Сейчас werf не знает о том, что мы поделили приложение на составные части. Добавим новую конфигурацию в файле werf.yaml
:
Осталось добавить контейнер с proxy в Deployment приложения:
И настроить связи между контейнерами и внешним миром, изменив Ingress и Sevice:
Приложение готово к работе и спрятано за reverse proxy.
Проверка работоспособности
Редеплоим приложение в Kubernetes, выполнив команду (не забудьте перед этим закоммитить изменения в репозитории):
Ожидаемый результат:
Проверим работоспособность: обратимся к странице http://werf-first-app.test/image и нажмем на ней кнопку «Get Image». Мы должны увидеть следующее:
Теперь посмотрим, какие запросы были выполнены и по каким адресам:
Мы превратили наше API в полноценное web-приложение, имеющее средства для эффективного управления JS- и статическими файлами. Также оно способно выдержать высокую нагрузку при большом количестве запросов к статическим файлам, и это не будет сказываться на работоспособности самого приложения.
Также можно быстро масштабировать приложение и proxy перед ним, просто увеличивая количество реплик в Deployment’е приложения.
Заключение
Мы научились собирать и деплоить в k8S-кластер простое приложение на Node.js с помощью утилиты werf. При этом мы правильно организовали раздачу ассетов, спрятав бэкенд за reverse proxy-сервером NGINX, сняв тем самым нагрузку с самого приложения.
Надеюсь, что этот материал поможет вам научиться эффективно использовать werf и Node.js, а также получить немного опыта в деплое приложений в Kubernetes!
В следующей статье мы разберемся, как правильно организовать работу с базой данных, развернув ее в K8s и инициализировав со всеми миграциями и настройками.
10К открытий11К показов