[Перевод] Четыре важных теста для Apache Kafka CI/CD с GitHub Actions
Если вы используете GitHub для создания приложений Apache Kafka® (а на GitHub больше 70 тыс. репозиториев, относящихся к Kafka), наверняка вы захотите интегрировать Kafka в свою среду разработки и эксплуатации GitOps. Эта статья для тех, кто понимает принципы GitOps, ценность непрерывной интеграции и поставки (CI/CD) и важность промежуточных сред (staging). Если что, вот полезный ресурс. Мы поговорим о том, как применять принципы GitOps к жизненному циклу разработки клиентского приложения Kafka с помощью GitHub Actions — для тестирования в локальной среде и Confluent Cloud, со Schema Registry и без него, и для эволюции схемы.
Обычно разработчики добавляют в локальные среды фреймворки тестирования для конкретного языка (JUnit, pytest и т. д.) до возврата кода в репозиторий или тесты выполняются в облачных конвейерах CI/CD после возврата. На начальных этапах цикла разработки нам нужны простые и быстрые тесты:
Модульные тесты проверяют изолированные блоки кода без внешних зависимостей или связей с другими системами. Инструменты для модульного тестирования в Kafka:
MockProducer
MockConsumer
Rdkafka_mock
TopologyTestDriver
…
Интеграционные тесты проверяют совместимость с другими сервисами с помощью:
TestContainers
EmbeddedKafkaCluster
…
Проблема в том, что в этих тестах мы работаем с локальным брокером Kafka или имитацией. Мы не используем реальный кластер, не отправляем записи по сети, не тестируем отказоустойчивость, сложные сценарии отказа или эволюцию схемы. На более поздних этапах мы должны тестировать фактическое клиентское приложение, отправляя настоящие сообщение на настоящий удаленный кластер и проверяя все аспекты работы приложения. Что делать, если у вас нет своего кластера Kafka? Вот бы был способ тестировать приложения в реальном кластере Kafka в облаке и бесшовно интегрировать его с репозиторием GitHub…
Сообщество Kafka использует разные инструменты CI/CD: GitHub Actions, Jenkins, CircleCI, Travis CI и многие другие. В этой статье мы рассмотрим платформу GitHub Actions, с помощью которой можно создавать рабочие процессы на основе действий. Действие можно написать самому или взять на GitHub Marketplace. Мы рассмотрим только самое основное — остальное читайте в документации по GitHub Actions. Мы можем, например:
Создавать артефакты для клиентского приложения.
Тестировать код в локальном кластере разработки.
Тестировать код в облачном кластере.
Проверять совместимость новых схем.
Весь код из этой статьи можно подробно изучить на GitHub в полностью функциональном рабочем процессе:
Создание артефактов для клиентского приложения
Первое задание в примере рабочего процесса создает артефакт, доступный для последующих заданий в конвейере CI/CD. В этом примере мы создаем образ контейнера на основе загруженного в репозиторий кода клиентского приложения. В самом простом случае задание build делает следующее (конечно, все зависит от конкретной ситуации):
build:
runs-on: ubuntu-latest
permissions:
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Log in to the GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push Docker image for Kafka client app
uses: docker/build-push-action@v3
with:
context: .
push: true
tags: ghcr.io/${{ github.actor }}/kafka-app:latest
На каждом шаге используются готовые действия, которые находятся в открытом доступе на GitHub Marketplace. Нам даже не пришлось писать код! Что делает каждое действие?
actions/checkout: извлекает код из этой ветви в уникальное рабочее пространство, где у задания build будет доступ к нему. Без этого действия задание build использует ветви репозитория по умолчанию.
docker/login-action: входит в GitHub Container Registry по адресу ghcr.io (с разрешениями на push образов), хотя с тем же успехом это мог быть и Docker Hub. Здесь нужно указать имя пользователя и пароль. В этом примере мы не храним учетные данные в самом файле, а используем автоматическую аутентификацию по токенам и подстановку переменных.
docker/build-push-action: собирает, отправляет и помечает образ Docker в GitHub Container Registry. Мы можем указать дополнительные шаги для сборки в Dockerfile для приложения (пример Dockerfile для клиентского приложения, в этом случае на Python).
После сборки образа приложения и его отправки в GitHub Container Registry стоит выполнить санитарное тестирование, прежде чем передавать его дальше по конвейеру. В нашем примере на опубликованный образ Docker можно ссылаться по тегу:
ghcr.io/${{ github.actor }}/kafka-app:latest
Делаем короткий санитарный тест в симуляции кластера Kafka с имитацией rdkafka из librdkafka. В этом примере в качестве приложения мы используем клиент Confluent Python, а симуляция кластера активируется установкой параметра test.mock.num.brokers во входном файле (см. librdkafka.sanity.config). Критерии успеха санитарных тестов зависят от логики приложения.
…..
- name: Run Kafka client app unit test
run: |
docker run -t \
-v ${DEST_WORKSPACE}/configs/librdkafka.sanity.config:/etc/librdkafka.config \
--name my-kafka-app --rm ghcr.io/${{ github.actor }}/kafka-app:latest \
bash -c '/usr/bin/producer.py -f /etc/librdkafka.config -t t1'
Тестирование кода в локальном кластере разработки
После санитарного теста запускаем приложение в реальном брокере Kafka. cp-all-in-one от Confluent — это файл Docker Compose, который хранится на GitHub, содержит полноценную платформу Confluent Platorm и подходит для этой цели. На Marketplace есть соответствующее действие, cp-all-in-one-action, которое извлекает файл Docker Compose и активирует все или некоторые сервисы платформы, включая брокер.
Следующий джоб вызывает это действие в GitHub Actions, чтобы запустить брокер Kafka. На этот раз при запуске контейнера приложения Docker дополнительный аргумент --net workspace_default подключает сети контейнера Docker, чтобы клиент получил доступ к брокеру. Входной файл конфигурации (см. librdkafka.local.config) задает контейнер брокера в качестве сервера инициализации. Как и в предыдущем случае, критерии успеха зависят от логики приложения.
…
- name: Run Confluent Platform (Confluent Server)
uses: ybyzek/cp-all-in-one-action@v0.2.1
with:
service: broker
- name: Run Kafka client app to local cluster
run: |
docker run -t \
-v ${DEST_WORKSPACE}/configs/librdkafka.local.config:/etc/librdkafka.config \
--net workspace_default \
--name my-kafka-app --rm ghcr.io/${{ github.actor }}/kafka-app:latest \
bash -c '/usr/bin/producer.py -f /etc/librdkafka.config -t t1'
Тестирование кода в облачном кластере
Это особенно ценно, если вы используете подход GitOps с моделью «среда как код», в которой различные ветви GitHub содержат канонический источник истины, определяющий разные среды, и приложение можно последовательно переносить из одной среды в другую по мере прохождения проверок. Рабочие процессы запускаются определенным событием, например, создание коммита, создание или слияние пул-реквеста и т. д. Так коммит в ветвь может запустить тест в среде разработки, создание пул-реквеста в другой ветви может активировать тестирование в стейджинге, а слияние пул-реквеста может привести к развертыванию в продакшен.
В любой среде мы должны защищать чувствительную информацию, допустим, учётные данные в кластере Confluent Cloud. Никогда не храните учётные данные в кодовой базе, даже если это частный репозиторий GitHub. Храните учётные данные отдельно как зашифрованные секреты — это функция GitHub, которая использует sealed box для шифрования учётных данных ещё до взаимодействия с GitHub и хранит их зашифрованными до использования в рабочем процессе.
Чтобы клиентское приложение могло подключиться к Confluent Cloud, ему требуется ключ, секрет и bootstrap-server.
Определим секреты в разделе среды в рабочем процессе.
….
env:
CONFLUENT_BOOTSTRAP_SERVERS: ${{ secrets.CONFLUENT_BOOTSTRAP_SERVERS }}
CONFLUENT_API_KEY: ${{ secrets.CONFLUENT_API_KEY }}
CONFLUENT_API_SECRET: ${{ secrets.CONFLUENT_API_SECRET }}
Чтобы клиентское приложение Kafka могло использовать учётные данные, их нужно задать в переменных среды и указать на них во входном файле, переданном в приложение. Не забывайте, что это чувствительная информация, которую мы не храним в коде напрямую, поэтому входной файл, например, librdkafka.ccloud.config, указывает на учётные данные только по именам заданных переменных среды. Если мы просмотрим код в репозитории GitHub, увидим только имена переменных, но не их значения.
bootstrap.servers=${CONFLUENT_BOOTSTRAP_SERVERS}
security.protocol=SASL_SSL
sasl.mechanisms=PLAIN
sasl.username=${CONFLUENT_API_KEY}
sasl.password=${CONFLUENT_API_SECRET}
Во время выполнения задания действие franzbischoff/replace_envs подставляет эти переменные на месте, и раз они зарегистрированы как секреты, в логах рабочего процесса мы их не увидим.
- uses: franzbischoff/replace_envs@v1
with:
from_file: 'configs/librdkafka.ccloud.config'
to_file: 'configs/librdkafka.ccloud.config'
commit: 'false'
Для теста, который запускает приложение в Confluent Cloud, задание должно указать нужный входной файл, иначе оно будет выполняться так же, как раньше.
- name: Run Kafka client app to Confluent Cloud
run: |
docker run -t \
-v ${DEST_WORKSPACE}/configs/librdkafka.ccloud.config:/etc/librdkafka.config \
--name my-kafka-app --rm ghcr.io/${{ github.actor }}/kafka-app:latest \
bash -c '/usr/bin/producer.py -f /etc/librdkafka.config -t t1'
Когда приложение на Python создает сообщения в Confluent Cloud, мы должны проверять, что сообщения были записаны и их можно правильно десериализовать. Это было бы проще, если бы у нас было приложение-консьюмер, но иногда достаточно небольшой проверки с помощью Confluent CLI. Чтобы войти в CLI, администратор вводит адрес электронной почты и пароль, а что делает рабочий процесс CI/CD? Для автоматизации используйте context, который поддерживает учётные данные аккаунта сервиса без входа. Следующий пример запускает образ Docker с Confluent CLI, создаёт и использует новый контекст, а потом потребляет сообщение (критерии успешного выполнения снова будут зависеть от логики приложения):
docker run -t \
-e CONFLUENT_BOOTSTRAP_SERVERS=${CONFLUENT_BOOTSTRAP_SERVERS} \
-e CONFLUENT_API_KEY=${CONFLUENT_API_KEY} \
-e CONFLUENT_API_SECRET=${CONFLUENT_API_SECRET} \
--name confluent-cli confluentinc/confluent-cli:2.16.0 \
bash -c 'confluent context create context-test \
--bootstrap ${CONFLUENT_BOOTSTRAP_SERVERS} \
--api-key ${CONFLUENT_API_KEY} \
--api-secret ${CONFLUENT_API_SECRET} && \
confluent context use context-test && \
timeout 10s confluent kafka topic consume -b t1’
Проверка совместимости новых схем
Если для развёртывания требуются данные в формате Avro, JSON или Protobuf, приложение нужно проверить через Schema Registry.
Для локального тестирования на ранних этапах снова запустите действие cp-all-in-one, но укажите service: schema-registry, чтобы использовать Schema Registry. Для тестирования на поздних этапах с использованием полностью управляемого Schema Registry в Confluent Cloud добавьте в GitHub два дополнительных зашифрованных секрета: URL и учётные данные Schema Registry.
Выполняем те же шаги, что и раньше, чтобы задать переменные среды и вызвать действие для замены переменных во входном файле конфигурации. Новое задание может проверить, что приложение создает реальную схему в Schema Registry и записывает данные в правильном формате Avro, JSON или Protobuf.
А что если через пару месяцев бизнес-требования изменятся и нужно будет добавить в схему новые поля или удалить имеющиеся? Такая эволюция схемы ожидаема, это часть жизненного цикла разработки, но конвейеры CI/CD должны проверить, что новая схема совместима со старой, прежде чем переводить ее на следующий этап. Иначе контракт между продюсерами и консьюмерами может быть нарушен, и консьюмеры не смогут читать данные, записанные продюсерами.
В Schema Registry есть REST API, который удобно использовать из командной строки, но существуют и возможности для конкретных языков, которые лучше оптимизированы для разработки приложений. Например, в Python есть пакет для встроенной проверки совместимости schema_registry_client.test_compatibility (), как показано в этом коде. Это просто код Python, его можно вызывать так же, как предыдущие джобы. В Java есть схожий метод, SchemaRegistryClient.testCompatibility (), или можно использовать цель test-compatibility в maven-плагине Schema Registry, как настроено в этом pom.xml. Плагин можно вызывать в джобе:
…
- name: Set up JDK 11
uses: actions/setup-java@v2
with:
java-version: '11'
distribution: 'temurin'
cache: maven
- name: Test compatibility of a new schema
run: |
# Pass
mvn io.confluent:kafka-schema-registry-maven-plugin:test-compatibility \
"-DschemaRegistryUrl=$CONFLUENT_SCHEMA_REGISTRY_URL" \
"-DschemaRegistryBasicAuthUserInfo=$CONFLUENT_BASIC_AUTH_USER_INFO" \
"-DnewSchema=schemas/Count-new.avsc"
Этот код проверяет совместимость новой схемы (в этом примере новая схема определена локально в файле schemas/Count-new.avsc) со схемой, которая связана с уже зарегистрированным субъектом, так что требуется подключение к Schema Registry и передача действительных учетных данных. В Confluent Platform версии 7.2.0 и выше есть цель test-local-compatibility, которая позволяет проверять совместимость с локально хранящимися схемами, её также можно использовать для быстрого санитарного теста на ранних этапах цикла разработки даже без регистрации схемы.
У нас уже получился неплохой конвейер! Каждый раз, когда мы фиксируем код, рабочий процесс автоматически проверяет, что приложение работает в Confluent Cloud, и каждый раз, когда требуется эволюция схемы, рабочий процесс автоматически проверяет совместимость схемы с предыдущей версией.
Следующие действия
В этой статье мы рассмотрели, как преобразовать разработку и тестирование клиентских приложений Kafka с помощью GitHub Actions и конвейера CI/CD: мы создали образ Docker с кодом приложения, проверили его с помощью cp-all-in-one и в Confluent Cloud, а также проверили измененную схему на совместимость. Теперь можно расширять конвейер в соответствии с нашим приложением и использовать GitHub Actions для задач, которые выполняются другими ботами, для управления проблемами, для уведомлений и т. д.
Если вы используете ksqlDB и хотите узнать, как развивать выполняющиеся запросы и проверять их с помощью GitHub Actions, см. руководство Online, Managed Schema Evolution with ksqlDB Migrations.
От редакции
Если хотите больше узнать про Kafka и прокачаться в ней, у Слёрма есть 2 курса:
— Курс «Apache Kafka База»: познакомимся с технологией, научимся настраивать распределённый отказоустойчивый кластер, отслеживать метрики, равномерно распределять нагрузку.
— Видеокурс «Apache Kafka для разработчиков». Это углублённый интенсив с практикой на Java или Golang и платформой Spring+Docker+Postgres. Интенсив даёт понимание, как организовать работу микросервисов и повысить общую надежность системы.
Купить комплектом 2 курса выгоднее на 30%: https://slurm.club/3ASuADm
Ждем вас!