Я инженер по нагрузочному тестированию и не так давно работаю над проектом, где предполагается активное использование Apache Kafka. Из-за режима удаленной работы получение доступа к тестовому окружению затянулось на недели. Чтобы не терять время я решил развернуть локальный стенд в Kubernetes.
Кто работал с Apache Kafka подтвердит, что официальная документация покрывает далеко не все тонкости инсталляции и настройки. Я надеюсь, что данная пошаговая инструкция позволит вам сократить время развертывания тестового окружения. Обращаю внимание на то, что установка stateful в контейнерах — далеко не лучшая идея, поэтому данная инструкция не предназначена для развертывания промышленного стенда.
Инструкция описывает создание виртуальной машины в VirtualBox, установку и настройку операционной системы, установку Docker, Kubernetes и системы мониторинга. В Kubernetes развертывается два кластера Apache Kafka: "production" и "backup". Для репликации сообщений из production в backup используется MirrorMaker 2.0. Взаимодействие между узлами production кластера защищено TLS. К сожалению, не могу выложить в Git скрипт для генерирования сертификатов. В качестве примера можете использовать сертификаты из архива certs/certs.tar.gz. В самом конце инструкции описывается развертывание кластера Jmeter и запуск тестового сценария.
Исходники доступны в репозитории: github.com/kildibaev/k8s-kafka
Инструкция расcчитана на новичков в Kubernetes, поэтому если вы уже имеете опыт работы с контейнерами, то можете сразу перейти в раздел "12. Разворачиваем кластер Apache Kafka".
Q&A:
- Почему используется Ubuntu? Изначально я развернул Kubernetes в CentOS 7, но после одного из обновлений окружение перестало работать. К тому же я заметил, что в CentOS нагрузочные тесты, запущенные в Jmeter, ведут себя непредсказуемо. Если сталкивались, пожалуйста, напишите в комментариях возможное решение этой проблемы. В Ubuntu всё намного стабильнее.
- Почему не k3s или MicroK8s? Если коротко, ни k3s, ни MicroK8s из коробки не умеют работать с локальным Docker-репозиторием.
- Почему не оптимизированы параметры конфигурации? Я намеренно использовал параметры по умолчанию где это возможно.
- Почему Flannel? Я новичок в kubernetes и Flannel — единственный плагин, который мне удалось завести без проблем.
- Почему Docker, а не CRI-O? Мне интересен CRI-O и я планирую изучить его в будущем.
- Почему MirrorMaker 2.0 развернут в Kafka Connect? Kafka Connect позволяет редактировать параметры конфигурации MirrorMaker 2.0 "на лету" через REST API.
Оглавление
1. Создание виртуальной машины
2. Установка Ubuntu Server 20.04
6. Установка kubeadm, kubelet и kubectl
7. Разворачиваем кластер Kubernetes
9. Разрешаем запуск pod-ов на ноде control-plane
10. Добавляем алиас для команды kubectl
11. Устанавливаем Prometheus, Grafana, Alert Manager и Node Exporter
12. Разворачиваем кластер Apache Kafka
12.1. Запускаем Apache Zookeeper
13. Проверяем отправку и получение сообщений
13.1. Запускаем генератор сообщений
13.2. Запускаем получателя сообщений
14. Настраиваем репликацию сообщений с помощью MirrorMaker 2.0
14.1. Запуск MirrorMaker 2.0 как конектор в кластере Kafka Connect
14.2. Проверяем репликацию сообщений
15. Выполнение сценариев Jmeter
1. Создание виртуальной машины
Виртуальной машине должно быть доступно как минимум 2 ядра ЦПУ и 6-8 ГБ оперативной памяти. Если нет возможности выделить достаточный объем оперативной памяти для виртуальной машины, то посмотрите в сторону Rancher K3S.
2. Установка Ubuntu Server 20.04
Установка операционной системы довольно тривиальный процесс, но на всякий случай приведу скриншоты. На что нужно обратить внимание:
- IP адрес, который будет присвоен ноде (по умолчанию 10.0.2.15);
- Kubernetes требует, чтобы swap был отключен, поэтому дисковые разделы необходимо создать вручную;
- В самом конце процесса установки можно выбрать опцию "Install OpenSSH server".
3. Настройка Ubuntu
3.1. Отключить Firewall
sudo ufw disable
3.2. Отключить Swap
sudo swapoff -a
sudo sed -i 's/^\/swap/#\/swap/' /etc/fstab
3.3. Установить OpenJDK
В поставку OpenJDK входит утилита keytool, которая понадобится для генерирования сертификатов:
sudo apt install openjdk-8-jdk-headless
3.4. Авторизация по ключу (опционально)
Подробная инструкция от DigitalOcean:
https://www.digitalocean.com/community/tutorials/how-to-set-up-ssh-keys-on-ubuntu-20-04-ru
4. Установка Docker [источник]
# Switch to the root user
sudo su
# (Install Docker CE)
## Set up the repository:
### Install packages to allow apt to use a repository over HTTPS
apt-get update && apt-get install -y apt-transport-https ca-certificates curl software-properties-common gnupg2
# Add Docker’s official GPG key:
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
# Add the Docker apt repository:
add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
# Install Docker CE
apt-get update && apt-get install -y containerd.io=1.2.13-2 docker-ce=5:19.03.11~3-0~ubuntu-$(lsb_release -cs) docker-ce-cli=5:19.03.11~3-0~ubuntu-$(lsb_release -cs)
# Set up the Docker daemon
cat > /etc/docker/daemon.json <<EOF
{
"exec-opts": ["native.cgroupdriver=systemd"],
"log-driver": "json-file",
"log-opts": {
"max-size": "100m"
},
"storage-driver": "overlay2"
}
EOF
mkdir -p /etc/systemd/system/docker.service.d
# Restart Docker
systemctl daemon-reload
systemctl restart docker
# If you want the docker service to start on boot, run the following command:
sudo systemctl enable docker
5. Настройка iptables [источник]
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
sudo sysctl --system
6. Установка kubeadm, kubelet и kubectl [источник]
sudo apt-get update && sudo apt-get install -y apt-transport-https curl
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
cat <<EOF | sudo tee /etc/apt/sources.list.d/kubernetes.list
deb https://apt.kubernetes.io/ kubernetes-xenial main
EOF
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
7. Разворачиваем кластер Kubernetes
Запускаем инициализацию ноды control-plane: [источник]
# Pulling images required for setting up a Kubernetes cluster
# This might take a minute or two, depending on the speed of your internet connection
sudo kubeadm config images pull
# Initialize a Kubernetes control-plane node
sudo kubeadm init --pod-network-cidr=10.244.0.0/16
Следующие команды необходимо выполнить под обычным пользователем (не root): [источник]
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
8. Устанавливаем Flannel [источник]
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
9. Разрешаем запуск pod-ов на ноде control-plane [источник]
Так как наш кластер Kubernetes развернут в режиме standalone, то необходимо разрешить запуск pod-ов на ноде control-plane:
kubectl taint nodes --all node-role.kubernetes.io/master-
10. Добавляем алиас для команды kubectl [источник]
alias k='kubectl'
echo "alias k='kubectl'" >> ~/.bashrc
11. Устанавливаем Prometheus, Grafana, Alert Manager и Node Exporter
Устанавливаем kube-prometheus: [источник]
curl -O -L https://github.com/coreos/kube-prometheus/archive/master.zip
sudo apt install -y unzip
unzip master.zip
cd kube-prometheus-master
kubectl create -f manifests/setup
kubectl create -f manifests/
Выполните следующую команду, чтобы посмотреть процесс запуска pod-ов. Необходимо дождаться когда все pod-ы перейдут в статус Running:
kubectl get pods -w -n monitoring
Для сбора метрик Kafka и Zookeeper будем использовать JMX Exporter. Чтобы Prometheus получил доступ к экспортируемым метрикам необходимо добавить ServiceMonitor:
k apply -f https://raw.githubusercontent.com/kildibaev/k8s-kafka/master/servicemonitor/jmx-exporter-servicemonitor.yaml
Создадим сервис, чтобы получить доступ к веб-интерфейсу Grafana из виртуальной машины:
kubectl apply -f https://raw.githubusercontent.com/kildibaev/k8s-kafka/master/service/grafana-svc.yaml
После запуска сервиса Grafana будет доступна из гостевой системы по адресу http://localhost:32000
Выполним проброс порта, чтобы получить доступ к Grafana на хост машине:
Теперь веб-интерфейс Grafana доступен на хост машине по адресу http://127.0.0.1:3000
Для просмотра метрик в Grafana можете воспользоваться готовым дашбордом. Для этого перейдите на страницу http://127.0.0.1:3000/dashboard/import и в поле "Import via panel json" скопируйте содержимое файла grafana-dashboard.json
12. Разворачиваем кластер Apache Kafka
# Скачиваем содержимое репозитория
git clone https://github.com/kildibaev/k8s-kafka.git $HOME/k8s-kafka
cd $HOME/k8s-kafka
12.1. Запускаем Apache Zookeeper
Чтобы получить предсказуемые имена хостов воспользуемся Statefulset. В нашем случае кластер Apache Zookeeper будет состоять из трех инстансов: zookeeper-0.zookeeper, zookeeper-1.zookeeper и zookeeper-2.zookeeper
# Собираем образ zookeeper-base
sudo docker build -t zookeeper-base:local-v1 -f dockerfile/zookeeper-base.dockerfile .
# Запускаем сервис Zookeeper
k apply -f service/zookeeper-svc.yaml
# Запускаем кластер Apache Zookeeper
k apply -f statefulset/zookeeper-statefulset.yaml
# Перед выполнением следующих шагов необходимо дождаться когда все три pod-а перейдут в статус Running. Проверить состояние pod-ов можно следующей командой:
k get pods -w
12.2. Запускаем Apache Kafka
Кластер Apache Kafka будет состоять из двух брокеров: kafka-0.kafka и kafka-1.kafka
# Собираем образ kafka-base
sudo docker build -t kafka-base:local-v1 -f dockerfile/kafka-base.dockerfile .
# Запускаем сервис Kafka
k apply -f service/kafka-svc.yaml
# Запускаем кластер Apache Kafka
k apply -f statefulset/kafka-statefulset.yaml
# Перед выполнением следующих шагов необходимо дождаться когда оба pod-а перейдут в статус Running. Проверить состояние pod-ов можно следующей командой:
k get pods -w
13. Проверяем отправку и получение сообщений
13.1. Запускаем генератор сообщений
Запустим генератор сообщений, который будет отправлять 10 сообщений в секунду. Размер каждого сообщения составляет 100 байт. В общей сложности будет отправлено 30000 сообщений.
# Запускаем новый pod - producer
k run --rm -i --tty producer --image=kafka-base:local-v1 -- bash
# Создаем топик topicname и отправляем в него сообщения
bin/kafka-producer-perf-test.sh --topic topicname --num-records 30000 --record-size 100 --throughput 10 --producer.config /config/client.properties --producer-props acks=1 bootstrap.servers=kafka-0.kafka:9092,kafka-1.kafka:9092 buffer.memory=33554432 batch.size=8196
13.2. Запускаем получателя сообщений
# Запускаем новый pod - consumer
k run --rm -i --tty consumer --image=kafka-base:local-v1 -- bash
# Получаем сообщения из топика topicname
bin/kafka-consumer-perf-test.sh --broker-list kafka-0.kafka:9092,kafka-1.kafka:9092 --consumer.config /config/client.properties --messages 30000 --topic topicname --threads 2
14. Настраиваем репликацию сообщений с помощью MirrorMaker 2.0
14.1. Запуск MirrorMaker 2.0 как конектор в кластере Kafka Connect
Кластер Apache Kafka, который мы запустили ранее, условно назовем production. Поднимем еще один pod, в котором будет запущено три контейнера: Apache Zookeeper, Apache Kafka и Kafka Connect. Новый инстанс Apache Kafka назовем backup и настроим репликацию сообщений из production в backup.
k apply -f service/mirrormaker-svc.yaml
# Поднимем pod, в котором будет запущено три контейнера: Apache Zookeeper, Apache Kafka и Kafka Connect
k apply -f statefulset/mirrormaker-statefulset.yaml
# Необходимо дождаться когда pod с именем mirrormaker-0 перейдет в статус Running
k get pods -w
# Подключимся к контейнеру connect в pod-е mirrormaker-0 и откроем командную строку
k exec -ti mirrormaker-0 -c connect -- bash
# В кластере Kafka Connect создадим коннектор MirrorMaker 2.0
curl -X POST -H "Content-Type: application/json" mirrormaker-0.mirrormaker:8083/connectors -d '{
"name": "MirrorSourceConnector",
"config": {
"connector.class": "org.apache.kafka.connect.mirror.MirrorSourceConnector",
"source.cluster.alias": "production",
"target.cluster.alias": "backup",
"source.cluster.bootstrap.servers": "kafka-0.kafka:9092,kafka-1.kafka:9092",
"source.cluster.group.id": "mirror_maker_consumer",
"source.cluster.enable.auto.commit": "true",
"source.cluster.auto.commit.interval.ms": "1000",
"source.cluster.session.timeout.ms": "30000",
"source.cluster.security.protocol": "SSL",
"source.cluster.ssl.truststore.location": "/certs/kafkaCA-trusted.jks",
"source.cluster.ssl.truststore.password": "kafkapilot",
"source.cluster.ssl.truststore.type": "JKS",
"source.cluster.ssl.keystore.location": "/certs/kafka-consumer.jks",
"source.cluster.ssl.keystore.password": "kafkapilot",
"source.cluster.ssl.keystore.type": "JKS",
"target.cluster.bootstrap.servers": "localhost:9092",
"target.cluster.compression.type": "none",
"topics": ".*",
"rotate.interval.ms": "1000",
"key.converter.class": "org.apache.kafka.connect.converters.ByteArrayConverter",
"value.converter.class": "org.apache.kafka.connect.converters.ByteArrayConverter"
}
}'
14.2. Проверяем репликацию сообщений
Если репликация прошла успешно, то в backup будет создан топик production.topicname. Префикс ".production" MirrorMaker 2.0 добавит, чтобы избежать зацикливания, например, когда репликация настроена в режиме active-active.
# Запускаем новый pod - consumer
k exec -ti mirrormaker-0 -c kafka -- bash
# Получим список топиков
bin/kafka-topics.sh --list --bootstrap-server mirrormaker-0.mirrormaker:9092
# Получаем сообщения из топика production.topicname
bin/kafka-console-consumer.sh --bootstrap-server mirrormaker-0.mirrormaker:9092 --topic production.topicname --from-beginning
Если топик production.topicname присутствует, но сообщения из него не считываются, проверьте логи Kafka Connect:
k logs mirrormaker-0 connect
Если в логах присутствуют следующие записи
ERROR WorkerSourceTask{id=MirrorSourceConnector-0} Failed to flush, timed out while waiting for producer to flush outstanding 1 messages (org.apache.kafka.connect.runtime.WorkerSourceTask:438)
ERROR WorkerSourceTask{id=MirrorSourceConnector-0} Failed to commit offsets (org.apache.kafka.connect.runtime.SourceTaskOffsetCommitter:114)
для решения проблемы можете уменьшить значение параметра producer.buffer.memory:
k exec -ti mirrormaker-0 -c connect -- bash
curl -X PUT -H "Content-Type: application/json" mirrormaker-0.mirrormaker:8083/connectors/MirrorSourceConnector/config -d '{
"connector.class": "org.apache.kafka.connect.mirror.MirrorSourceConnector",
"source.cluster.alias": "production",
"target.cluster.alias": "backup",
"source.cluster.bootstrap.servers": "kafka-0.kafka:9092,kafka-1.kafka:9092",
"source.cluster.group.id": "mirror_maker_consumer",
"source.cluster.enable.auto.commit": "true",
"source.cluster.auto.commit.interval.ms": "1000",
"source.cluster.session.timeout.ms": "30000",
"source.cluster.security.protocol": "SSL",
"source.cluster.ssl.truststore.location": "/certs/kafkaCA-trusted.jks",
"source.cluster.ssl.truststore.password": "kafkapilot",
"source.cluster.ssl.truststore.type": "JKS",
"source.cluster.ssl.keystore.location": "/certs/kafka-consumer.jks",
"source.cluster.ssl.keystore.password": "kafkapilot",
"source.cluster.ssl.keystore.type": "JKS",
"target.cluster.bootstrap.servers": "localhost:9092",
"target.cluster.compression.type": "none",
"topics": ".*",
"rotate.interval.ms": "1000",
"producer.buffer.memory:" "1000",
"key.converter.class": "org.apache.kafka.connect.converters.ByteArrayConverter",
"value.converter.class": "org.apache.kafka.connect.converters.ByteArrayConverter"
}'
15. Выполнение сценариев Jmeter
# Собираем образ jmeter-base
sudo docker build -t jmeter-base:local-v1 -f dockerfile/jmeter-base.dockerfile .
# Запускаем сервис Jmeter
k apply -f service/jmeter-svc.yaml
# Запускаем четыре pod-а Jmeter, которые будут непосредственно генерировать нагрузку
k apply -f statefulset/jmeter-statefulset.yaml
Создадим pod jmeter-producer и выполним в нем сценарий producer.jmx
k run --rm -i --tty jmeter-producer --image=jmeter-base:local-v1 -- bash ./jmeter -n -t /tests/producer.jmx -r -Jremote_hosts=jmeter-0.jmeter:1099,jmeter-1.jmeter:1099
Создадим pod jmeter-consumer и выполним в нем сценарий consumer.jmx
k run --rm -i --tty jmeter-consumer --image=jmeter-base:local-v1 -- bash ./jmeter -n -t /tests/consumer.jmx -r -Jremote_hosts=jmeter-2.jmeter:1099,jmeter-3.jmeter:1099
16. Удаление данных
Удаляем statefulset
k delete statefulset jmeter zookeeper kafka mirrormaker
Удаляем контейнеры
sudo docker rmi -f zookeeper-base:local-v1 kafka-base:local-v1 jmeter-base:local-v1
Удаляем сервисы
k delete svc grafana jmeter kafka mirrormaker zookeeper
k delete servicemonitor jmxexporter
linkedin: kildibaev
Всем добра! Не болейте.