CMAK与Helm集成:Kubernetes部署包开发指南

CMAK与Helm集成:Kubernetes部署包开发指南

【免费下载链接】CMAK yahoo/CMAK: CMAK是Yahoo贡献的一个Kafka集群管理工具,全称为“Cluster Management for Apache Kafka”,提供了一种便捷的方式来部署、管理和监控Apache Kafka集群。 【免费下载链接】CMAK 项目地址: https://gitcode.com/gh_mirrors/cm/CMAK

引言:解决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集成,能够带来以下好处:

  1. 简化部署流程:通过Helm Chart,一键部署CMAK,无需手动创建各种Kubernetes资源
  2. 环境隔离:使用Helm的values文件,可以为不同环境(开发、测试、生产)定义不同的配置
  3. 版本管理:轻松管理CMAK的版本,支持一键升级和回滚
  4. 可扩展性:通过Helm的模板功能,可以灵活定制CMAK的部署配置
  5. 便于维护:集中管理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结构,重点关注以下几个文件:

  1. Chart.yaml:包含Chart的基本信息,如名称、版本、描述等
  2. values.yaml:包含CMAK部署的默认配置值
  3. templates/deployment.yaml:CMAK的Deployment资源定义
  4. templates/service.yaml:CMAK的Service资源定义
  5. templates/configmap.yaml:CMAK的配置文件
  6. 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集群

  1. 登录CMAK Web界面
  2. 点击"Cluster" -> "Add Cluster"
  3. 填写集群信息:
    • Cluster Name: 集群名称
    • Cluster Zookeeper Hosts: Kafka集群的Zookeeper地址
    • Kafka Version: Kafka集群版本
  4. 点击"Save"

监控Kafka集群

添加集群后,CMAK会自动开始收集Kafka集群的信息。你可以在Web界面上查看:

  • 集群概览
  • Broker列表与状态
  • 主题列表与详细信息
  • 消费者组信息
  • 分区分布
  • 副本状态

高可用部署

为了实现CMAK的高可用部署,可以采取以下措施:

多副本部署

通过设置replicaCount: 2或更高,部署多个CMAK实例。

使用外部数据库

CMAK默认使用Zookeeper存储其状态信息。对于生产环境,建议使用外部的、高可用的Zookeeper集群。

负载均衡

通过Kubernetes的Service或Ingress,实现多个CMAK实例之间的负载均衡。

升级与回滚

升级CMAK

  1. 更新Helm Chart中的appVersionimage.tag
  2. 执行升级命令:
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的日常运维带来了便利。

下一步

  1. 探索CMAK的高级功能,如JMX指标收集和告警配置
  2. 学习Helm的高级特性,如Chart依赖管理和钩子
  3. 研究CMAK的数据备份和恢复策略
  4. 探索CMAK与其他监控工具(如Prometheus、Grafana)的集成

希望本文能帮助你更好地管理Kafka集群,提高工作效率!

【免费下载链接】CMAK yahoo/CMAK: CMAK是Yahoo贡献的一个Kafka集群管理工具,全称为“Cluster Management for Apache Kafka”,提供了一种便捷的方式来部署、管理和监控Apache Kafka集群。 【免费下载链接】CMAK 项目地址: https://gitcode.com/gh_mirrors/cm/CMAK

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值