CMAK与Helm集成:Kubernetes部署包开发指南
引言:解决Kafka集群管理的部署痛点
你是否还在为Kafka集群管理工具CMAK的部署流程繁琐而烦恼?手动配置、复杂的环境依赖、难以维护的部署脚本,这些问题是否让你望而却步?本文将为你提供一站式解决方案,通过Helm这一强大的Kubernetes包管理工具,轻松实现CMAK的部署与管理。
读完本文,你将能够:
- 理解CMAK与Helm集成的优势
- 掌握Helm Chart的结构与编写方法
- 学会自定义CMAK的Kubernetes部署配置
- 实现CMAK的高可用部署与升级
- 解决常见的部署问题与挑战
CMAK简介
CMAK(Cluster Manager for Apache Kafka)是由Yahoo贡献的一款功能强大的Kafka集群管理工具,前身为Kafka Manager。它提供了直观的Web界面,用于部署、管理和监控Apache Kafka集群。
CMAK支持以下核心功能:
- 管理多个Kafka集群
- 检查集群状态(主题、消费者、偏移量、 broker、副本分布、分区分布)
- 运行首选副本选举
- 生成分区分配并选择要使用的broker
- 运行分区重分配
- 创建、删除和更新主题
- 可选启用JMX轮询以获取broker级别和主题级别的指标
Helm简介
Helm是Kubernetes的包管理工具,它简化了Kubernetes应用的部署和管理。Helm使用Chart格式来打包应用,每个Chart包含了应用的所有Kubernetes资源定义和配置。
Helm的主要优势:
- 简化部署流程,减少手动操作
- 提供版本控制,便于应用的升级和回滚
- 支持自定义配置,适应不同环境需求
- 便于分享和重用应用部署配置
CMAK与Helm集成的优势
将CMAK与Helm集成,能够带来以下好处:
- 简化部署流程:通过Helm Chart,一键部署CMAK,无需手动创建各种Kubernetes资源
- 环境隔离:使用Helm的values文件,可以为不同环境(开发、测试、生产)定义不同的配置
- 版本管理:轻松管理CMAK的版本,支持一键升级和回滚
- 可扩展性:通过Helm的模板功能,可以灵活定制CMAK的部署配置
- 便于维护:集中管理CMAK的所有Kubernetes资源定义
准备工作
在开始之前,请确保你已经安装了以下工具:
- Kubernetes集群(1.16+)
- Helm 3.x
- kubectl命令行工具
- Git
克隆CMAK仓库
git clone https://gitcode.com/gh_mirrors/cm/CMAK.git
cd CMAK
Helm Chart结构设计
一个典型的Helm Chart结构如下:
cmak-helm/
├── Chart.yaml
├── values.yaml
├── templates/
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ ├── configmap.yaml
│ ├── secret.yaml
│ └── ...
├── charts/
└── README.md
我们将为CMAK设计类似的Chart结构,重点关注以下几个文件:
- Chart.yaml:包含Chart的基本信息,如名称、版本、描述等
- values.yaml:包含CMAK部署的默认配置值
- templates/deployment.yaml:CMAK的Deployment资源定义
- templates/service.yaml:CMAK的Service资源定义
- templates/configmap.yaml:CMAK的配置文件
- templates/ingress.yaml:(可选)CMAK的Ingress资源定义
创建Helm Chart
初始化Chart
helm create cmak-chart
cd cmak-chart
修改Chart.yaml
apiVersion: v2
name: cmak
description: A Helm chart for CMAK (Cluster Manager for Apache Kafka)
type: application
version: 0.1.0
appVersion: "3.0.0.7"
keywords:
- kafka
- cmak
- kafka-manager
home: https://github.com/yahoo/CMAK
sources:
- https://gitcode.com/gh_mirrors/cm/CMAK
maintainers:
- name: Your Name
email: your.email@example.com
配置values.yaml
# Default values for cmak.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
replicaCount: 1
image:
repository: cmak
tag: 3.0.0.7
pullPolicy: IfNotPresent
nameOverride: ""
fullnameOverride: ""
serviceAccount:
# Specifies whether a service account should be created
create: true
# Annotations to add to the service account
annotations: {}
# The name of the service account to use.
# If not set and create is true, a name is generated using the fullname template
name: ""
podAnnotations: {}
podSecurityContext: {}
# fsGroup: 2000
securityContext: {}
# capabilities:
# drop:
# - ALL
# readOnlyRootFilesystem: true
# runAsNonRoot: true
# runAsUser: 1000
service:
type: ClusterIP
port: 80
ingress:
enabled: false
annotations: {}
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
hosts:
- host: cmak.example.com
paths: []
tls: []
# - secretName: cmak-tls
# hosts:
# - cmak.example.com
resources: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 100
targetCPUUtilizationPercentage: 80
# targetMemoryUtilizationPercentage: 80
nodeSelector: {}
tolerations: []
affinity: {}
# CMAK specific configuration
cmak:
# Zookeeper hosts for CMAK's own state
zkHosts: "zookeeper:2181"
# Application features to enable
features: ["KMClusterManagerFeature","KMTopicManagerFeature","KMPreferredReplicaElectionFeature","KMReassignPartitionsFeature", "KMScheduleLeaderElectionFeature"]
# HTTP port
httpPort: 9000
# JVM options
jvmOptions: "-Xms512m -Xmx1g"
# Basic authentication configuration
basicAuth:
enabled: false
username: "admin"
password: "password"
realm: "Kafka-Manager"
# LDAP authentication configuration
ldapAuth:
enabled: false
server: "ldap.example.com"
port: 389
username: "cn=admin,dc=example,dc=com"
password: "adminpassword"
searchBaseDn: "dc=example,dc=com"
searchFilter: "(uid=$capturedLogin$)"
groupFilter: ""
connectionPoolSize: 10
ssl: false
sslTrustAll: false
starttls: false
# Additional environment variables
extraEnv: []
创建ConfigMap模板
创建templates/configmap.yaml文件:
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "cmak.fullname" . }}
labels:
{{- include "cmak.labels" . | nindent 4 }}
data:
application.conf: |
# CMAK configuration
play.crypto.secret="{{ .Values.cmak.basicAuth.password | default "changeme" }}"
play.http.session.maxAge="1h"
play.i18n.langs=["en"]
play.http.requestHandler = "play.http.DefaultHttpRequestHandler"
play.http.context = "/"
play.application.loader=loader.KafkaManagerLoader
# Zookeeper hosts for CMAK's own state
cmak.zkhosts="{{ .Values.cmak.zkHosts }}"
cmak.zkhosts=${?ZK_HOSTS}
pinned-dispatcher.type="PinnedDispatcher"
pinned-dispatcher.executor="thread-pool-executor"
application.features=[{{ range $index, $feature := .Values.cmak.features }}{{ if $index }},{{ end }}"{{ $feature }}"{{ end }}]
akka {
loggers = ["akka.event.slf4j.Slf4jLogger"]
loglevel = "INFO"
}
akka.logger-startup-timeout = 60s
basicAuthentication.enabled={{ .Values.cmak.basicAuth.enabled }}
basicAuthentication.enabled=${?KAFKA_MANAGER_AUTH_ENABLED}
basicAuthentication.ldap.enabled={{ .Values.cmak.ldapAuth.enabled }}
basicAuthentication.ldap.enabled=${?KAFKA_MANAGER_LDAP_ENABLED}
basicAuthentication.ldap.server="{{ .Values.cmak.ldapAuth.server }}"
basicAuthentication.ldap.server=${?KAFKA_MANAGER_LDAP_SERVER}
basicAuthentication.ldap.port={{ .Values.cmak.ldapAuth.port }}
basicAuthentication.ldap.port=${?KAFKA_MANAGER_LDAP_PORT}
basicAuthentication.ldap.username="{{ .Values.cmak.ldapAuth.username }}"
basicAuthentication.ldap.username=${?KAFKA_MANAGER_LDAP_USERNAME}
basicAuthentication.ldap.password="{{ .Values.cmak.ldapAuth.password }}"
basicAuthentication.ldap.password=${?KAFKA_MANAGER_LDAP_PASSWORD}
basicAuthentication.ldap.search-base-dn="{{ .Values.cmak.ldapAuth.searchBaseDn }}"
basicAuthentication.ldap.search-base-dn=${?KAFKA_MANAGER_LDAP_SEARCH_BASE_DN}
basicAuthentication.ldap.search-filter="{{ .Values.cmak.ldapAuth.searchFilter }}"
basicAuthentication.ldap.search-filter=${?KAFKA_MANAGER_LDAP_SEARCH_FILTER}
basicAuthentication.ldap.group-filter="{{ .Values.cmak.ldapAuth.groupFilter }}"
basicAuthentication.ldap.group-filter=${?KAFKA_MANAGER_LDAP_GROUP_FILTER}
basicAuthentication.ldap.connection-pool-size={{ .Values.cmak.ldapAuth.connectionPoolSize }}
basicAuthentication.ldap.connection-pool-size=${?KAFKA_MANAGER_LDAP_CONNECTION_POOL_SIZE}
basicAuthentication.ldap.ssl={{ .Values.cmak.ldapAuth.ssl }}
basicAuthentication.ldap.ssl=${?KAFKA_MANAGER_LDAP_SSL}
basicAuthentication.ldap.ssl-trust-all={{ .Values.cmak.ldapAuth.sslTrustAll }}
basicAuthentication.ldap.ssl-trust-all=${?KAFKA_MANAGER_LDAP_SSL_TRUST_ALL}
basicAuthentication.ldap.starttls={{ .Values.cmak.ldapAuth.starttls }}
basicAuthentication.ldap.starttls=${?KAFKA_MANAGER_LDAP_STARTTLS}
basicAuthentication.username="{{ .Values.cmak.basicAuth.username }}"
basicAuthentication.username=${?KAFKA_MANAGER_USERNAME}
basicAuthentication.password="{{ .Values.cmak.basicAuth.password }}"
basicAuthentication.password=${?KAFKA_MANAGER_PASSWORD}
basicAuthentication.realm="{{ .Values.cmak.basicAuth.realm }}"
basicAuthentication.excluded=["/api/health"]
创建Deployment模板
创建templates/deployment.yaml文件:
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "cmak.fullname" . }}
labels:
{{- include "cmak.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "cmak.selectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "cmak.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "cmak.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
command: ["/bin/bash", "-c"]
args: ["bin/cmak -Dconfig.file=/opt/cmak/conf/application.conf -Dhttp.port={{ .Values.cmak.httpPort }}"]
ports:
- name: http
containerPort: {{ .Values.cmak.httpPort }}
protocol: TCP
livenessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 60
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 30
periodSeconds: 5
resources:
{{- toYaml .Values.resources | nindent 12 }}
env:
- name: ZK_HOSTS
value: {{ .Values.cmak.zkHosts | quote }}
- name: JAVA_OPTS
value: {{ .Values.cmak.jvmOptions | quote }}
{{- if .Values.cmak.basicAuth.enabled }}
- name: KAFKA_MANAGER_AUTH_ENABLED
value: "true"
- name: KAFKA_MANAGER_USERNAME
valueFrom:
secretKeyRef:
name: {{ include "cmak.fullname" . }}-secret
key: username
- name: KAFKA_MANAGER_PASSWORD
valueFrom:
secretKeyRef:
name: {{ include "cmak.fullname" . }}-secret
key: password
{{- end }}
{{- if .Values.cmak.ldapAuth.enabled }}
- name: KAFKA_MANAGER_LDAP_ENABLED
value: "true"
- name: KAFKA_MANAGER_LDAP_SERVER
value: {{ .Values.cmak.ldapAuth.server | quote }}
- name: KAFKA_MANAGER_LDAP_PORT
value: {{ .Values.cmak.ldapAuth.port | quote }}
- name: KAFKA_MANAGER_LDAP_USERNAME
valueFrom:
secretKeyRef:
name: {{ include "cmak.fullname" . }}-secret
key: ldap-username
- name: KAFKA_MANAGER_LDAP_PASSWORD
valueFrom:
secretKeyRef:
name: {{ include "cmak.fullname" . }}-secret
key: ldap-password
- name: KAFKA_MANAGER_LDAP_SEARCH_BASE_DN
value: {{ .Values.cmak.ldapAuth.searchBaseDn | quote }}
- name: KAFKA_MANAGER_LDAP_SEARCH_FILTER
value: {{ .Values.cmak.ldapAuth.searchFilter | quote }}
- name: KAFKA_MANAGER_LDAP_GROUP_FILTER
value: {{ .Values.cmak.ldapAuth.groupFilter | quote }}
- name: KAFKA_MANAGER_LDAP_CONNECTION_POOL_SIZE
value: {{ .Values.cmak.ldapAuth.connectionPoolSize | quote }}
- name: KAFKA_MANAGER_LDAP_SSL
value: {{ .Values.cmak.ldapAuth.ssl | quote }}
- name: KAFKA_MANAGER_LDAP_SSL_TRUST_ALL
value: {{ .Values.cmak.ldapAuth.sslTrustAll | quote }}
- name: KAFKA_MANAGER_LDAP_STARTTLS
value: {{ .Values.cmak.ldapAuth.starttls | quote }}
{{- end }}
{{- with .Values.cmak.extraEnv }}
{{- toYaml . | nindent 12 }}
{{- end }}
volumeMounts:
- name: config-volume
mountPath: /opt/cmak/conf/application.conf
subPath: application.conf
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
volumes:
- name: config-volume
configMap:
name: {{ include "cmak.fullname" . }}
创建Service模板
创建templates/service.yaml文件:
apiVersion: v1
kind: Service
metadata:
name: {{ include "cmak.fullname" . }}
labels:
{{- include "cmak.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "cmak.selectorLabels" . | nindent 6 }}
创建Secret模板
创建templates/secret.yaml文件:
{{- if or .Values.cmak.basicAuth.enabled .Values.cmak.ldapAuth.enabled }}
apiVersion: v1
kind: Secret
metadata:
name: {{ include "cmak.fullname" . }}-secret
labels:
{{- include "cmak.labels" . | nindent 4 }}
type: Opaque
data:
{{- if .Values.cmak.basicAuth.enabled }}
username: {{ .Values.cmak.basicAuth.username | b64enc | quote }}
password: {{ .Values.cmak.basicAuth.password | b64enc | quote }}
{{- end }}
{{- if .Values.cmak.ldapAuth.enabled }}
ldap-username: {{ .Values.cmak.ldapAuth.username | b64enc | quote }}
ldap-password: {{ .Values.cmak.ldapAuth.password | b64enc | quote }}
{{- end }}
{{- end }}
创建Ingress模板(可选)
创建templates/ingress.yaml文件:
{{- if .Values.ingress.enabled -}}
{{- $fullName := include "cmak.fullname" . -}}
{{- $svcPort := .Values.service.port -}}
{{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
apiVersion: networking.k8s.io/v1beta1
{{- else -}}
apiVersion: extensions/v1beta1
{{- end }}
kind: Ingress
metadata:
name: {{ $fullName }}
labels:
{{- include "cmak.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ . }}
backend:
serviceName: {{ $fullName }}
servicePort: {{ $svcPort }}
{{- end }}
{{- end }}
{{- end }}
构建CMAK Docker镜像
根据项目中的build.sbt文件,CMAK已经包含了Docker构建配置。我们可以使用以下命令构建Docker镜像:
# 在CMAK项目根目录执行
./sbt clean dist docker:publishLocal
这将构建一个名为cmak:3.0.0.7的本地Docker镜像。
部署CMAK到Kubernetes
安装Helm Chart
# 返回到Helm Chart目录
cd cmak-chart
# 安装Chart
helm install cmak . --namespace kafka --create-namespace
自定义部署配置
如果需要自定义部署配置,可以创建一个values文件,例如production-values.yaml:
replicaCount: 2
image:
repository: cmak
tag: "3.0.0.7"
pullPolicy: IfNotPresent
service:
type: NodePort
port: 80
cmak:
zkHosts: "zookeeper-0.zookeeper-headless.kafka.svc.cluster.local:2181,zookeeper-1.zookeeper-headless.kafka.svc.cluster.local:2181,zookeeper-2.zookeeper-headless.kafka.svc.cluster.local:2181"
jvmOptions: "-Xms2g -Xmx4g"
basicAuth:
enabled: true
username: "cmak-admin"
password: "secure-password-here"
httpPort: 9000
resources:
limits:
cpu: 2
memory: 4Gi
requests:
cpu: 1
memory: 2Gi
ingress:
enabled: true
annotations:
kubernetes.io/ingress.class: nginx
cert-manager.io/cluster-issuer: "letsencrypt-prod"
hosts:
- host: cmak.example.com
paths: ["/"]
tls:
- secretName: cmak-tls
hosts:
- cmak.example.com
然后使用这个文件进行部署:
helm install cmak . --namespace kafka --create-namespace -f production-values.yaml
验证部署
# 检查Deployment
kubectl get deployments -n kafka
# 检查Pod
kubectl get pods -n kafka
# 检查Service
kubectl get services -n kafka
# 查看日志
kubectl logs -f <cmak-pod-name> -n kafka
CMAK配置与使用
访问CMAK Web界面
如果使用NodePort服务类型:
# 获取NodePort
kubectl get service cmak -n kafka -o jsonpath='{.spec.ports[0].nodePort}'
# 通过NodeIP:NodePort访问
如果使用Ingress:
# 通过配置的域名访问
open http://cmak.example.com
添加Kafka集群
- 登录CMAK Web界面
- 点击"Cluster" -> "Add Cluster"
- 填写集群信息:
- Cluster Name: 集群名称
- Cluster Zookeeper Hosts: Kafka集群的Zookeeper地址
- Kafka Version: Kafka集群版本
- 点击"Save"
监控Kafka集群
添加集群后,CMAK会自动开始收集Kafka集群的信息。你可以在Web界面上查看:
- 集群概览
- Broker列表与状态
- 主题列表与详细信息
- 消费者组信息
- 分区分布
- 副本状态
高可用部署
为了实现CMAK的高可用部署,可以采取以下措施:
多副本部署
通过设置replicaCount: 2或更高,部署多个CMAK实例。
使用外部数据库
CMAK默认使用Zookeeper存储其状态信息。对于生产环境,建议使用外部的、高可用的Zookeeper集群。
负载均衡
通过Kubernetes的Service或Ingress,实现多个CMAK实例之间的负载均衡。
升级与回滚
升级CMAK
- 更新Helm Chart中的
appVersion和image.tag - 执行升级命令:
helm upgrade cmak . --namespace kafka
回滚到之前版本
# 查看版本历史
helm history cmak -n kafka
# 回滚到指定版本
helm rollback cmak <revision-number> -n kafka
常见问题与解决方案
问题1:CMAK无法连接到Zookeeper
解决方案:
- 检查Zookeeper服务是否正常运行
- 验证
cmak.zkHosts配置是否正确 - 检查网络连接,确保CMAK Pod可以访问Zookeeper
问题2:CMAK启动失败,报内存不足
解决方案:
- 增加JVM内存配置:
cmak.jvmOptions: "-Xms1g -Xmx2g" - 调整资源限制:
resources.limits.memory: "2Gi"
问题3:无法登录CMAK,认证失败
解决方案:
- 检查基本认证或LDAP配置是否正确
- 查看CMAK日志,寻找认证相关错误
- 通过
kubectl exec进入Pod,检查配置文件是否正确挂载
问题4:CMAK界面显示的Kafka集群信息不更新
解决方案:
- 检查CMAK与Kafka集群的网络连接
- 验证Kafka集群的Zookeeper地址是否正确
- 重启CMAK Pod
总结
通过本文,我们详细介绍了如何将CMAK与Helm集成,实现Kafka集群管理工具的容器化部署。我们从Chart设计、模板编写到实际部署,全面覆盖了CMAK的Helm集成过程。
使用Helm部署CMAK带来了诸多好处,包括简化部署流程、支持环境隔离、提供版本管理等。这不仅提高了部署效率,也为CMAK的日常运维带来了便利。
下一步
- 探索CMAK的高级功能,如JMX指标收集和告警配置
- 学习Helm的高级特性,如Chart依赖管理和钩子
- 研究CMAK的数据备份和恢复策略
- 探索CMAK与其他监控工具(如Prometheus、Grafana)的集成
希望本文能帮助你更好地管理Kafka集群,提高工作效率!
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



