Как мы создали браузерный Kubernetes Experience

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

Самый быстрый способ создания отличной инфраструктуры

Plural дает вам возможность создавать и поддерживать облачно-нативную и готовую к производству инфраструктуру с открытым исходным кодом на Kubernetes.

🔨☁️

Plural развернет приложения с открытым исходным кодом на Kubernetes в вашем облаке, используя такие общие стандарты, как Helm и Terraform.

Платформа Plural обеспечивает следующее:

  • Управление зависимостями между модулями Terraform/Helm с развертыванием и обновлением с учетом зависимостей.
  • Аутентифицированный реестр docker и прокси chartmuseum для каждого репозитория.
  • Шифрование секретов с использованием AES-256 (чтобы вы могли хранить весь рабочий процесс в git).

Кроме того, Plural также обрабатывает:

  • Выдачу сертификатов.
  • Настройка службы DNS для регистрации полностью определенных доменов под onplural.sh, чтобы избавить пользователей от необходимости регистрации DNS.
  • Быть провайдером OIDC, чтобы обеспечить безопасность входа «без касания» для всех приложений Plural.

Платформа plural получает все артефакты развертывания, необходимые для развертывания…

Посмотреть на GitHub

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

В нашем случае мы использовали следующие инструменты:

  • Helm
  • Terraform
  • Kubectl
  • git
  • любой облачный CLI (например, AWS, GCP, AZURE)
  • и Plural CLI

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

Как видите, это определенно трудоемкая задача и требует хотя бы базовых знаний Helm и Terraform для правильного развертывания и управления приложениями.

Чему мы научились у наших первых пользователей

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

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

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

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

Первое решение заключалось в том, чтобы просто предоставить пользователям образ Docker, который был бы уже установлен и готов к работе. Однако заставить контейнер Docker работать с локальным репозиторием git и учетными данными локального облака — задача нетривиальная. Мы вернулись к исходной точке.

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

Кажется, все достаточно просто, верно? Ну, вроде как, но не совсем.

Базовая архитектура облачной оболочки Plural

Использование API оказалось немного сложнее, чем мы предполагали. Мы смогли преодолеть ее, следуя общей схеме системы.

  • У нас есть набор API graphql для управления созданием, опросом и перезапуском экземпляра облачной оболочки. API создает ссылку на экземпляр оболочки в нашей базе данных и создает экземпляр в кластере k8s.
  • Мы размещаем все shell pods в выделенной группе узлов k8s, изолированных от сети, и запускаем экземпляры на месте, чтобы максимально снизить затраты.
  • У нас есть оператор, который отслеживает метку pod, объявляющую истечение срока действия, который удаляет экземпляры оболочки через шесть часов. Затем у нас есть механизм изящного завершения работы, который гарантирует, что незафиксированные git-изменения будут отправлены вверх по течению, чтобы убедиться, что мы не разрушаем состояние бездумно.
  • Наш фронтенд представлен WebSocket API, который позволяет нам передавать и получать эквивалент stdin и stdout браузера, все b64 закодировано и представлено в xterm.js в нашем приложении react.

Это не безумно сложно, но дьявол всегда кроется в деталях.

Работа с API Kubernetes pods/exec

Первая проблема, с которой нам пришлось столкнуться, заключалась в том, чтобы понять, как работает Kubernetes pod exec API. В общем, наш сервер должен был стоять между клиентом браузера и Kubernetes pod, обеспечивая auth+authz и обрабатывая логику восстановления pod. Это означало, что нам нужно как-то проксировать формат, который предоставляет API, и преобразовать его в ответ WebSocket канала elixir phoenix.

Как оказалось, API довольно плохо документирован, но на самом деле довольно прост в использовании. Хотя в документации Kubernetes нет никаких подробностей о нем, мы смогли понять, как он используется, погрузившись в код официальной приборной панели Kubernetes.

Вам просто нужно подключиться через wss к пути типа:

/api/v1/namespaces/{namespace}/pods/{name}/exec,

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

0 — это канал для stdin.
1-3 — каналы вывода, соответствующие stdout, stderr и т.д.
4 отводится под сообщение JSON, предназначенное для изменения размера экрана терминала.
Мы смогли очень элегантно обработать это с помощью бинарного шаблона elixir, например:

«def command(client, message) do
WebSockex.send_frame(client, {:binary, <<0>> <> message})
end

def resize(client, cols, rows) do
resize = Jason.encode!(%{Width: cols, Height: rows})
WebSockex.send_frame(client, {:binary, <<4>> <> resize})
end

defp deliver_frame(<<1, frame::binary>>, pid),
do: send_frame(pid, frame)
defp deliver_frame(<<2, frame::binary>>, pid),
do: send_frame(pid, frame)
defp deliver_frame(<<3, frame::binary>>, pid),
do: send_frame(pid, frame)
defp deliver_frame(frame, pid), do: send_frame(pid, frame)`.

В Elixir также есть довольно простой WebSocket-клиент на основе GenServer, который мы использовали для установления устойчивого соединения с API k8s. Мы связали генсервер WebSocket с нашим канальным процессом, чтобы обеспечить надлежащий перезапуск в случае потери соединения с k8s.

Весь код для управления pod exec WebSocket можно найти здесь, а инкапсуляция этого кода в генсервер позволила нам создать довольно тонкий канал для управления передачей stdout и stdin в браузер и из него:

defmodule RtcWeb.ShellChannel do
use RtcWeb, :channel
alias Core.Services.{Shell.Pods, Shell}
alias Core.Shell.Client
alias Core.Schema.CloudShell

require Logger

def join(«shells:me», _, socket) do
send(self(), :connect)
{:ok, socket}
end

def handle_info(:connect, socket) do
with %CloudShell{pod_name: name} = shell <- Shell.get_shell(socket.assigns.user.id),
{:ok, _} <- Client.setup(shell),
url <- Pods.PodExec.exec_url(name),
{:ok, pid} <- Pods.PodExec.start_link(url, self()) do
{:noreply, assign(socket, :wss_pid, pid)}
else
err ->
Logger.info «failed to exec pod with #{inspect(err)}»
{:stop, {:shutdown, :failed_exec}, socket}
end
end

def handle_info({:stdo, data}, socket) do
push(socket, «stdo», %{message: Base.encode64(data)})
{:noreply, socket}
end

def handle_in(«command», %{«cmd» => cmd}, socket) do
Pods.PodExec.command(socket.assigns.wss_pid, fmt_cmd(cmd))
{:reply, :ok, socket}
end

def handle_in(«resize», %{«width» => w, «height» => h}, socket) do
Pods.PodExec.resize(socket.assigns.wss_pid, w, h)
{:reply, :ok, socket}
end

defp fmt_cmd(cmd) when is_binary(cmd), do: cmd
defp fmt_cmd(cmd) when is_list(cmd), do: Enum.join(cmd, » «)
end`

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

Устойчивость оболочки

Одним из недостатков использования Kubernetes pod execs является то, что если WebSocket прерывается по какой-либо причине, то процесс оболочки, с помощью которого вы зашли в этот pod, прерывается на стороне pod. Это означает, что каждый раз, когда кто-то обновляет страницу или перемещается в браузере, его сессия полностью уничтожается.

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

Однако в нашем случае оболочка выполняет длительные команды terraform и helm, которые при преждевременном завершении могут привести к повреждению состояния и зависанию ресурсов. Это стало огромной проблемой и почти убило проект.

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

К счастью, мы нашли решение; мультиплексирование терминала.

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

Для управления одной сессией tmux в наших shell pods мы написали простой bash-скрипт:

`#!/bin/sh

session=»workspace»
tmux start
tmux has-session -t $session 2>/dev/null

if [ $? != 0 ]; then
tmux new-session -c ~/workspace -s $session zsh
fi

Присоединитесь к созданной сессии
tmux attach-session -d -t $session`

Это гарантирует наличие долговечной сессии tmux, к которой браузер всегда подключается при WebSocket-соединении, а также делает нашу оболочку во многих отношениях более предпочтительной для запуска локально, поскольку сеть ноутбука будет гораздо менее надежной, чем центр обработки данных AWS, в котором работают наши оболочки. Это также позволяет вам иметь рабочий процесс «запустил и забыл» для более длительных настроек terraform, особенно для процесса создания кластера k8s.

Я не ожидал, что в этом проекте мне придется стирать пыль с моих навыков работы с tmux, но было забавно иметь возможность использовать его в нашем конечном решении.

Реализация React

Последняя часть головоломки, которую нам предстояло решить, — как отобразить терминал в браузере.

Очевидно, что в природе существует достаточное количество инструментов, которые делают это. GCP и Azure имеют облачную оболочку, а OSS-проект Kubernetes dashboard также встраивает ее в свой просмотрщик стручков. Так что предшествующий уровень техники явно был.

Немного покопавшись, наша команда наткнулась на проект xtermjs, который представляет собой полную реализацию POSIX-терминала на javascript, совместимом с браузером. Существует также обёртка react для xterm-for-react, которая, по сути, просто монтирует xterm в родительский узел HTML и управляет некоторым состоянием настроек совместимым с react способом.

Пришлось немного повозиться, чтобы заставить канал phoenix читать и писать из xtermjs, но в итоге все оказалось довольно просто. Если кому-то интересно, как все это сочетается на переднем плане, вы можете посмотреть исходный код нашего терминального компонента здесь.

Подведение итогов

В целом, мы получили массу удовольствия от создания этой функции. Она позволила нам углубиться в часто неисследованную область API Kubernetes, и я искренне рад, что нам удалось ее изучить. Этот проект также сделал неожиданный поворот в использовании tmux и познакомил нас с поистине умопомрачительным проектом xtermjs (я потрясен, что у сообщества хватило терпения написать полноценную оболочку на javascript!)

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

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

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

Если вам нравится то, что мы делаем, и вы хотите внести свой вклад в наш продукт с открытым исходным кодом, загляните в наш репозиторий Github для получения дополнительной информации.

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

Оцените статью
devanswers.ru
Добавить комментарий