Запуск одноразовых задач и отладка образов прямо в Kubernetes-кластере с помощью werf

Kate

Administrator
Команда форума
Какие задачи пользователю нужно выполнять в рамках CI-пайплайна или при локальной разработке? Среди них может быть что угодно, но самое очевидное — это, наверное, запуск линтеров, всевозможных unit-тестов и получение покрытия и других отчетов по результатам выполнения команды. Также при разработке и отладке может быть полезен интерактивный режим, который позволит быстрее разобраться в проблеме или проверить гипотезу.

b93a687e2969e4bdcd178d5d22851cf0.png

Мы рассмотрим «классическое» решение этой задачи штатными средствами, а затем — простой пример, как Open Source-утилита werf помогает сократить трудозатраты на выполнение этих действий. Такой подход позволяет перенести нагрузку со сборочной или локальной машины в кластер Kubernetes, что дает возможность упростить масштабирование и обслуживание инфраструктуры, а также избавиться от зависимости от Docker.

Традиционный подход с использованием kubectl run​

Попробуем протестировать приложение в контейнере Docker с ручным запуском. Для этого создадим простое приложение на Go, выполняющее одну задачу — вычисление площади прямоугольника:

package square

import (
"fmt"
"strconv"
)

// Функция, вычисляющая площадь прямоугольника.
func getArea(x, y int) (res int) {
return x * y
}

func main() {
fmt.Println("Площадь прямоугольника: " + strconv.Itoa(getArea(10, 10)))
}
Убедимся, что все работает. Скомпилируем программу и запустим:

% go build main.go

% ./main
Площадь прямоугольника: 100
Теперь добавим простой тест, проверяющий, что функция подсчета площади работает верно. Создадим файл main_test.go:

package square

import "testing"

func testGetArea(t *testing.T) {

got := getArea(3, 2)
want := 6

if got != want {
t.Errorf("Ожидалось %q, получено %q", got, want)
}
}
Запустим тест, чтобы убедиться, что все работает корректно:

% go test
PASS
ok square 0.448s
Теперь соберем из нашего тестового приложения Docker-контейнер. Для этого добавим Dockerfile в корень проекта:

FROM golang:1.18-alpine
WORKDIR /app
ADD . /app/
RUN go build -o main .
RUN chmod +x ./main
CMD ./main
Соберем контейнер и убедимся, что все работает:

% docker build .

% docker run 70df7f451a8b
Площадь прямоугольника: 100
Исходные коды приложения можно найти в репозитории.
За'push'им собранный контейнер в container registry:

docker tag 70df7f451a8b <ИМЯ ПОЛЬЗОВАТЕЛЯ DOCKER HUB>/werf-kuberun-app
docker push <ИМЯ ПОЛЬЗОВАТЕЛЯ DOCKER HUB>/werf-kuberun-app
Создадим в Kubernetes-кластере отдельное пространство имен и Secret для доступа к container registry:

% kubectl create namespace werf-kuberun-app
namespace/werf-kuberun-app created

% kubectl config set-context minikube --namespace=werf-kuberun-app
Context "minikube" modified.

kubectl create secret docker-registry registrysecret \
--docker-server='https://index.docker.io/v1/' \
--docker-username='<ИМЯ ПОЛЬЗОВАТЕЛЯ DOCKER HUB>' \
--docker-password='<ПАРОЛЬ ПОЛЬЗОВАТЕЛЯ DOCKER HUB>'
secret/registrysecret created
Развернем созданный контейнер в кластере и выполним тесты приложения:

% kubectl run gotest --image=<ИМЯ ПОЛЬЗОВАТЕЛЯ DOCKER HUB>/werf-kuberun-app:latest --command -- go test
pod/gotest created


% kubectl get pods
NAME READY STATUS RESTARTS AGE
gotest 0/1 Completed 0 5s


% kubectl logs gotest
testing: warning: no tests to run
PASS
ok square 0.002s
Контейнер запустился в Pod’е, выполнил тесты и до сих пор висит в состоянии Completed. Удалим его:

% kubectl delete pod gotest
pod "gotest" deleted
Итак, мы собрали контейнер с приложением, создали пространство имен и Secret с доступами к container registry в нём, затем, используя все данные с предыдущих шагов, запустили Pod с нашим образом и командой запуска тестов. Последним шагом удалили оставшийся Pod.

Теперь давайте сделаем то же самое с помощью werf.

Запуск разовой задачи с помощью werf kube-run​

Для этого воспользуемся новой командой werf – werf kube-run. Она отчасти аналогична уже знакомой пользователям утилиты команде werf run, но в отличие от последней создаёт Pod в K8s-кластере, а не запускает локальный контейнер.

Чтобы werf смогла собрать контейнер и задеплоить его в кластер, нужно создать файл werf.yaml в корне проекта:

project: werf-kuberun-app
configVersion: 1

---
image: kuberun
dockerfile: Dockerfile
Здесь мы указываем название проекта, наименование создаваемого образа и Dockerfile, из которого будут браться инструкции для сборки.

Для работы werf необходимо, чтобы все файлы проекта находились в Git-репозитории. Инициализируем новый репозиторий в корне проекта:

git init
git add .
git commit -m WIP
Запустим выполнение тестов в кластере командой:

werf kube-run --repo <ИМЯ ПОЛЬЗОВАТЕЛЯ DOCKER HUB>/werf-kuberun-app -- go test
Здесь мы указываем репозиторий, в который werf за'push'ит собранный образ, и команду, которую необходимо выполнить: go test. Подробнее о команде и ее настройках (доступных флагах) можно прочитать в официальной документации.

После выполнения увидим примерно следующее:

┌ Getting client id for the http synchronization server
│ Using clientID "e100e249-066a-48eb-80e7-52b0e3e6a491" for http synchronization server at address https://synchronization.werf.io/e100e249-066a-48eb-80e7-52b0e3e6a491
└ Getting client id for the http synchronization server (1.70 seconds)

⛵ image kuberun
│ Use cache image for kuberun/dockerfile
│ name: <ИМЯ ПОЛЬЗОВАТЕЛЯ DOCKER HUB>/werf-kuberun-app:44922a164fd7eceb98659eb4e008f03730a9abc29cf750e16cdc0c99-1653648006124
│ id: 166a97e05613
│ created: 2022-05-27 13:40:04 +0300 MSK
│ size: 115.8 MiB
⛵ image kuberun (1.42 seconds)

Running pod "werf-run-1675291575958117025" in namespace "werf-kuberun-app" ...
pod/werf-run-1675291575958117025 created
Waiting for pod "werf-run-1675291575958117025" in namespace "werf-kuberun-app" to be ready ...
Execing into pod "werf-run-1675291575958117025" in namespace "werf-kuberun-app" ...
PASS
ok square 0.070s
Stopping container "werf-run-1675291575958117025" in pod "werf-run-1675291575958117025" in namespace "werf-kuberun-app" ...
Cleaning up pod "werf-run-1675291575958117025" ...
Стоит отметить, что в целях демонстрации мы используем Docker-сервер для сборки тестового образа, чтобы не усложнять статью особенностями сборки с Buildah. Однако такой режим работы тоже доступен в werf и позволяет собирать образы без Docker-сервера (подробнее про настройку окружения с Buildah можно прочитать в документации).
Все запустилось и выполнилось. Проверим, что Pod, в котором запускались тесты, удален:

% kubectl get pods
No resources found in werf-kuberun-app namespace.
Все действия werf выполняет в рамках одной команды, автоматизируя рутину и позволяя пользователю сосредоточиться на выполняемой задаче.

Как это работает​

По умолчанию команда действует по следующему алгоритму:

  • Берёт параметры доступа для указанного $WERF_REPO из ~/.docker/config.json.
  • Создает в кластере Image Pull Secret с этими параметрами (для доступа к приватным container registry).
  • Создаёт в кластере Pod с указанной командой, монтирует к нему созданный Pull Secret.
После завершения работы Pod'а удаляет созданные Image Pull Secret и Pod.

Различные сценарии использования​

Мы рассмотрели простой запуск команды во временном Pod’е. Возможны и другие, более сложные, сценарии использования werf kube-run. Взглянем на несколько таких примеров — уже без практики, а с целью лучше раскрыть ее возможности.

Запустить выполнение тестов в ранее созданном Pod’е (например, frontend_image) можно командой:

werf kube-run frontend_image --repo ghcr.io/group/project -- npm test
Запустить тесты в Pod’e, но перед выполнением команды скопировать файл с секретными переменными окружения в контейнер:

werf kube-run frontend_image --repo ghcr.io/group/project --copy-to ".env:/app/.env" -- npm run e2e-tests
Запустить тесты в Pod’е и получить отчет о покрытии:

werf kube-run frontend_image --repo ghcr.io/group/project --copy-from "/app/report:." -- go test -coverprofile report ./...
Выполнить команду по умолчанию для созданного образа в Kubernetes Pod с установленными параметрами CPU:

werf kube-run frontend_image --repo ghcr.io/group/project --overrides='{"spec":{"containers":[{"name": "%container_name%", "resources":{"requests":{"cpu":"100m"}}}]}}'

Выводы​

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

 
Сверху