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

Мы рассмотрим «классическое» решение этой задачи штатными средствами, а затем — простой пример, как 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. Она позволяет сэкономить немного времени при выполнении таких задач, а также перенести нагрузку, с ними связанную, в кластер.

С вопросами и предложениями ждем вас в комментариях к статье, а также в Telegram-каналe werf_ru, где 700+ участников и всегда готовы помочь. Также мы всегда рады правкам и улучшениям для проекта в виде Pull Request’ов для GitHub-репозитория werf.

P.S.

Читайте также в нашем блоге:

Комментарии (0)