Kafka в Kubernetes с чартом Bitnami

Published: 2026-02-23

Чарт kafka от Bitnami — один из самых полных вариантов для запуска Kafka в Kubernetes. Он включает ZooKeeper как зависимый sub-chart, JMX exporter в виде sidecar-контейнера и из коробки генерирует манифесты NetworkPolicy, ServiceMonitor и PrometheusRule. Рассмотрим ключевые настройки для продакшен-деплоя.


Настройка чарта

yaml# Chart.yaml (wrapper chart)
apiVersion: v2
name: kafka
version: 0.1.0
dependencies:
  - name: kafka
    version: "22.x.x"
    repository: "oci://registry-1.docker.io/bitnamicharts"
  - condition: zookeeper.enabled
    name: zookeeper
    version: "11.x.x"
    repository: "oci://registry-1.docker.io/bitnamicharts"

Версию чарта стоит зафиксировать в Chart.lock и закоммитить его. Плавающая версия 22.x.x подходит для helm dependency update, но воспроизводимые деплои обеспечивает именно lock-файл.


Параметры StatefulSet

yamlreplicaCount: 3

heapOpts: "-Xmx2g -Xms2g"

persistence:
  enabled: true
  storageClass: "yc-network-ssd"
  size: 50Gi

podManagementPolicy: Parallel

resources:
  requests:
    cpu: 500m
    memory: 3Gi
  limits:
    cpu: 2
    memory: 4Gi

affinity:
  podAntiAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchLabels:
            app.kubernetes.io/name: kafka
        topologyKey: kubernetes.io/hostname

Политика управления подами Parallel безопасна для Kafka — брокеры не образуют кворум при запуске (это делает ZooKeeper). Она заметно ускоряет rolling-restart на кластерах с тремя и более брокерами.

Heap JVM — примерно 75 % от лимита памяти контейнера. При лимите 4Gi настройка -Xmx2g -Xms2g оставляет 2Gi под page cache ОС, который Kafka активно использует при чтении.

podAntiAffinity гарантирует, что каждый брокер попадёт на отдельный узел. Без этого два брокера на одном узле означают потерю кворума при отказе узла.


Listeners и аутентификация

Для внутрикластерного взаимодействия используем PLAINTEXT (шифруется на сетевом уровне Cilium). Для клиентов вне namespace принудительно SASL/SCRAM:

yamlauth:
  clientProtocol: sasl
  interBrokerProtocol: plaintext
  sasl:
    mechanisms: scram-sha-256
    interBrokerMechanism: plain
    jaas:
      clientUsers:
        - app-user
        - monitoring-user
      clientPasswords:
        - ""   # подставляется через ESO; оставляем пустым
        - ""

Пароли монтируются из Kubernetes Secret, создаваемого External Secrets Operator из Vault. Чарт ищет Secret kafka-jaas в том же namespace — заполняем только секрет, не values-файл.


JMX exporter sidecar

Чарт поставляет sidecar bitnami/jmx-exporter и jmx-configmap.yaml. При включении метрики брокера публикуются на порту 5556 в формате Prometheus:

yamlmetrics:
  jmx:
    enabled: true
    resources:
      requests:
        cpu: 50m
        memory: 128Mi
      limits:
        cpu: 200m
        memory: 256Mi
  serviceMonitor:
    enabled: true
    namespace: monitoring
    labels:
      release: kube-prometheus-stack

Ключевые метрики для мониторинга:

  • kafka_server_replicamanager_underreplicatedpartitions — должно быть 0
  • kafka_controller_kafkacontroller_activecontrollercount — ровно 1 в кластере
  • kafka_network_requestmetrics_requestspersec — паттерны трафика по типу запросов
  • kafka_server_brokertopicmetrics_messagesinpersec — пропускная способность по топикам

PrometheusRule

yamlmetrics:
  prometheusRule:
    enabled: true
    rules:
      - alert: KafkaUnderReplicatedPartitions
        expr: kafka_server_replicamanager_underreplicatedpartitions > 0
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "Under-replicated partitions on {{ $labels.pod }}"
      - alert: KafkaOfflinePartitions
        expr: kafka_controller_kafkacontroller_offlinepartitionscount > 0
        for: 5m
        labels:
          severity: critical
      - alert: KafkaActiveControllers
        expr: sum(kafka_controller_kafkacontroller_activecontrollercount) != 1
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "Неправильное количество активных контроллеров Kafka"

NetworkPolicy

Чарт генерирует NetworkPolicy при networkPolicy.enabled: true. Расширяем её через additionalRules — до порта 9092 могут достучаться только нужные неймспейсы:

yamlnetworkPolicy:
  enabled: true
  allowExternal: false
  additionalRules:
    - from:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: orders
      ports:
        - port: 9092
          protocol: TCP

Создание топиков (provisioning)

Чарт содержит Job provisioning, который запускается после готовности брокеров:

yamlprovisioning:
  enabled: true
  numPartitions: 6
  replicationFactor: 3
  topics:
    - name: orders.created
      partitions: 6
      replicationFactor: 3
      config:
        retention.ms: "604800000"   # 7 дней
        cleanup.policy: delete
    - name: payments.completed
      partitions: 3
      replicationFactor: 3
      config:
        retention.ms: "2592000000"  # 30 дней

Выбор количества партиций

Нагрузка Партиции
< 10 МБ/с 3
10–50 МБ/с 6
> 50 МБ/с 12–24

PodDisruptionBudget

yamlpdb:
  create: true
  minAvailable: 2

При трёх брокерах и minAvailable: 2 Kubernetes не позволит одновременно дрейнить больше одного узла с брокером. В сочетании с offsets.topic.replication.factor=3 это гарантирует сохранность офсетов consumer groups при последовательном дрейне узлов.


Sub-chart ZooKeeper

yamlzookeeper:
  enabled: true
  replicaCount: 3
  persistence:
    enabled: true
    storageClass: "yc-network-ssd"
    size: 8Gi
  resources:
    requests:
      cpu: 100m
      memory: 512Mi
    limits:
      cpu: 500m
      memory: 1Gi

ZooKeeper должен выбрать лидера до того, как брокеры Kafka начнут регистрироваться. Если брокеры при запуске падают в CrashLoopBackOff — сначала проверьте логи ZooKeeper.


Flux HelmRelease

yamlapiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
  name: kafka
  namespace: kafka
spec:
  interval: 10m
  chart:
    spec:
      chart: ./kafka
      sourceRef:
        kind: GitRepository
        name: infra-repo
  valuesFrom:
    - kind: Secret
      name: kafka-jaas
      valuesKey: jaasPasswords
      targetPath: auth.sasl.jaas.clientPasswords
  values:
    replicaCount: 3

valuesFrom подставляет SASL-пароли из Secret во время реконсиляции. В values-файле реального пароля нет.


Отладка

bash# Логи брокера
kubectl logs -n kafka kafka-0 -f

# Список топиков
kubectl exec -n kafka kafka-0 -- \
  kafka-topics.sh --bootstrap-server localhost:9092 --list

# Under-replicated партиции
kubectl exec -n kafka kafka-0 -- \
  kafka-topics.sh --bootstrap-server localhost:9092 \
  --describe --under-replicated-partitions

# Лаг consumer group
kubectl exec -n kafka kafka-0 -- \
  kafka-consumer-groups.sh --bootstrap-server localhost:9092 \
  --describe --group orders-consumer

Kafka в Kubernetes работает хорошо, если относиться к ней как к stateful-зверю: зафиксированный storage class, PDB, консервативные resource limits, pod anti-affinity и JMX-мониторинг с первого дня.