Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Какие задачи пользователю нужно выполнять в рамках 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.
Читайте также в нашем блоге:
«Новые возможности werf: CI/CD на основе werf и Argo CD»;
«Запуск werf в GitLab CI/CD без Docker-сервера»;
«Локальная разработка в Kubernetes с помощью werf 1.2 и minikube».