🚀 告别繁琐配置!Apache Kafka 一键安装脚本全攻略(支持 KRaft 无 Zookeeper 模式)

97 阅读18分钟

📖 前言

在微服务架构和大数据处理中,Apache Kafka 已经成为消息队列和流处理的事实标准。然而,传统的 Kafka 安装配置过程繁琐,需要安装 Zookeeper、配置各种参数、创建 systemd 服务等,对新手极不友好。

本文提供一个生产级别的 Kafka 一键安装 Python 脚本,支持最新的 KRaft 模式(无需 Zookeeper)和传统的 Zookeeper 模式,让你在 5 分钟内完成 Kafka 的安装部署!

✨ 核心特性

🎯 功能亮点

  • 一键安装:自动完成所有安装配置步骤
  • KRaft 模式:支持 Kafka 3.x 最新的 KRaft 模式(无需 Zookeeper)
  • 传统模式:同时支持 Zookeeper 模式,向下兼容
  • 自动依赖:自动检测和安装 Java 环境
  • 智能下载:内置多个国内外镜像源,自动切换
  • 生产就绪:自动配置 systemd 服务、开机自启
  • 完整验证:安装后自动验证服务状态和功能
  • 美观输出:彩色终端输出,清晰易懂

🆚 KRaft vs Zookeeper 模式对比

特性KRaft 模式Zookeeper 模式
架构复杂度⭐⭐ 简单⭐⭐⭐⭐ 复杂
启动速度⚡ 快速🐢 较慢
资源占用💚 低🔴 高
运维成本💰 低💰💰 高
元数据管理🚀 高效📦 相对低效
未来支持✅ 推荐⚠️ 将被废弃

推荐使用 KRaft 模式! Kafka 社区已明确表示将在未来版本移除 Zookeeper 依赖。

📋 系统要求

  • 操作系统:Debian 10+、Ubuntu 18.04+ 或其他 Debian 系 Linux
  • 权限:需要 root 权限(sudo)
  • 网络:需要访问外网下载安装包
  • 磁盘空间:至少 500MB 可用空间
  • 内存:建议 2GB+ RAM

🎬 快速开始

1. 下载脚本

# 使用 wget
wget https://your-domain.com/install_kafka.py

# 或使用 curl
curl -O https://your-domain.com/install_kafka.py

2. 一键安装(KRaft 模式 - 推荐)

# 使用默认配置(Kafka 3.6.1 + KRaft 模式)
sudo python3 install_kafka.py

就这么简单!脚本会自动完成以下所有步骤:

  1. ✅ 检查并安装 Java 17
  2. ✅ 下载 Kafka 安装包
  3. ✅ 解压并配置 Kafka
  4. ✅ 创建 systemd 服务
  5. ✅ 启动服务并设置开机自启
  6. ✅ 配置防火墙规则
  7. ✅ 验证安装是否成功

3. 自定义安装

# 指定 Kafka 版本
sudo python3 install_kafka.py 3.7.0

# 指定安装目录和版本
sudo python3 install_kafka.py 3.6.1 /usr/local/kafka

# 使用传统 Zookeeper 模式
sudo python3 install_kafka.py 3.6.1 /opt/kafka zookeeper

# 完整参数示例
sudo python3 install_kafka.py [Kafka版本] [安装目录] [kraft|zookeeper]

📝 详细说明

脚本架构

脚本采用模块化设计,主要包含以下核心函数:

1. 环境检查模块

  • check_root() - 检查 root 权限
  • check_java() - 检查 Java 环境
  • install_java() - 自动安装 Java 17

2. 下载模块

  • download_kafka() - 智能多源下载
    • 🇨🇳 清华大学镜像
    • 🇨🇳 阿里云镜像
    • 🇨🇳 华为云镜像
    • 🇨🇳 中科大镜像
    • 🇨🇳 网易镜像
    • 🌍 Apache 官方源
    • 🌏 Apache 亚洲镜像

3. 配置模块

  • configure_kafka_kraft() - KRaft 模式配置
  • configure_kafka_zookeeper() - Zookeeper 模式配置

4. 服务管理模块

  • create_systemd_service_kraft() - 创建 KRaft 服务
  • create_systemd_service_zookeeper() - 创建 Zookeeper 服务
  • start_services_kraft() - 启动 KRaft 服务
  • start_services_zookeeper() - 启动 Zookeeper 服务

5. 验证模块

  • verify_installation() - 验证安装
  • configure_firewall() - 配置防火墙

KRaft 模式配置详解

脚本在 KRaft 模式下会自动完成以下配置:

# Node ID
node.id=1

# Controller 配置
controller.quorum.voters=1@localhost:9093

# 监听地址(允许外网访问)
listeners=PLAINTEXT://0.0.0.0:9092,CONTROLLER://0.0.0.0:9093

# 广播地址(客户端连接地址)
advertised.listeners=PLAINTEXT://your-server-ip:9092

# 数据存储目录
log.dirs=/var/lib/kafka/kraft-combined-logs

Zookeeper 模式配置详解

传统模式下的配置:

# 监听地址
listeners=PLAINTEXT://0.0.0.0:9092

# 广播地址
advertised.listeners=PLAINTEXT://your-server-ip:9092

# 数据存储目录
log.dirs=/var/lib/kafka/logs

# Zookeeper 连接
zookeeper.connect=localhost:2181

🎮 使用示例

启动后的验证

安装完成后,脚本会自动进行验证测试:

# 检查服务状态
systemctl status kafka

# 查看日志
journalctl -u kafka -f

# 检查端口
netstat -tulpn | grep 9092

创建主题

# 创建一个测试主题
/opt/kafka/bin/kafka-topics.sh --create \
  --topic my-first-topic \
  --bootstrap-server localhost:9092 \
  --partitions 3 \
  --replication-factor 1

# 查看所有主题
/opt/kafka/bin/kafka-topics.sh --list \
  --bootstrap-server localhost:9092

# 查看主题详情
/opt/kafka/bin/kafka-topics.sh --describe \
  --topic my-first-topic \
  --bootstrap-server localhost:9092

生产和消费消息

# 启动生产者(终端 1)
/opt/kafka/bin/kafka-console-producer.sh \
  --topic my-first-topic \
  --bootstrap-server localhost:9092

# 启动消费者(终端 2)
/opt/kafka/bin/kafka-console-consumer.sh \
  --topic my-first-topic \
  --from-beginning \
  --bootstrap-server localhost:9092

在生产者终端输入消息,消费者终端会实时显示!

服务管理命令

# KRaft 模式
systemctl start kafka      # 启动
systemctl stop kafka       # 停止
systemctl restart kafka    # 重启
systemctl status kafka     # 查看状态
systemctl enable kafka     # 开机自启(已自动配置)

# Zookeeper 模式
systemctl start zookeeper && systemctl start kafka    # 启动
systemctl stop kafka && systemctl stop zookeeper      # 停止

🔧 高级配置

性能优化

编辑配置文件 /opt/kafka/config/kraft/server.properties/opt/kafka/config/server.properties

# 增加分区数(提高并发)
num.partitions=6

# 调整日志保留时间(默认 7 天)
log.retention.hours=168

# 调整日志段大小
log.segment.bytes=1073741824

# 增加网络线程数
num.network.threads=8

# 增加 I/O 线程数
num.io.threads=16

# 调整 Socket 缓冲区
socket.send.buffer.bytes=102400
socket.receive.buffer.bytes=102400

修改后重启服务:

systemctl restart kafka

多节点集群配置

KRaft 集群模式(3 节点示例):

在每个节点上修改 server.properties

# 节点 1
node.id=1
controller.quorum.voters=1@node1-ip:9093,2@node2-ip:9093,3@node3-ip:9093
advertised.listeners=PLAINTEXT://node1-ip:9092

# 节点 2
node.id=2
controller.quorum.voters=1@node1-ip:9093,2@node2-ip:9093,3@node3-ip:9093
advertised.listeners=PLAINTEXT://node2-ip:9092

# 节点 3
node.id=3
controller.quorum.voters=1@node1-ip:9093,2@node2-ip:9093,3@node3-ip:9093
advertised.listeners=PLAINTEXT://node3-ip:9092

安全配置(SASL/SSL)

生产环境强烈建议启用认证!

  1. 生成 SSL 证书
  2. 配置 SASL 认证
  3. 修改 listeners 为 SASL_SSL
  4. 配置客户端认证信息

详细配置参考 Kafka 官方文档

🐛 常见问题

Q1: 端口 9092 已被占用

# 查看占用进程
sudo lsof -i:9092

# 停止旧的 Kafka 进程
sudo systemctl stop kafka

# 或手动杀死进程
sudo kill -9 <PID>

Q2: Java 版本不兼容

# 检查 Java 版本
java -version

# Kafka 3.x 需要 Java 11+,推荐 Java 17
sudo apt install openjdk-17-jdk

Q3: 服务启动失败

# 查看详细错误日志
journalctl -u kafka -n 100 --no-pager

# 检查配置文件
cat /opt/kafka/config/kraft/server.properties

# 检查日志目录权限
ls -la /var/lib/kafka/

Q4: 磁盘空间不足

# 清理旧日志
/opt/kafka/bin/kafka-log-dirs.sh --describe \
  --bootstrap-server localhost:9092

# 修改日志保留策略
# 编辑 server.properties
log.retention.hours=24  # 保留 1 天
log.retention.bytes=1073741824  # 最大 1GB

Q5: 无法从外网访问

# 检查防火墙
sudo ufw status
sudo ufw allow 9092/tcp

# 检查 advertised.listeners 配置
grep advertised.listeners /opt/kafka/config/kraft/server.properties

# 确保配置为实际 IP 而非 localhost
advertised.listeners=PLAINTEXT://your-public-ip:9092

📊 性能测试

安装完成后,可以使用 Kafka 自带的性能测试工具:

生产者性能测试

/opt/kafka/bin/kafka-producer-perf-test.sh \
  --topic test-perf \
  --num-records 1000000 \
  --record-size 1024 \
  --throughput -1 \
  --producer-props bootstrap.servers=localhost:9092

消费者性能测试

/opt/kafka/bin/kafka-consumer-perf-test.sh \
  --bootstrap-server localhost:9092 \
  --topic test-perf \
  --messages 1000000 \
  --threads 1

🔒 安全最佳实践

  1. 启用认证:生产环境必须配置 SASL 或 SSL
  2. 网络隔离:使用防火墙限制访问 IP
  3. 权限控制:配置 Kafka ACL(访问控制列表)
  4. 数据加密:启用 SSL 加密传输
  5. 日志审计:开启操作日志审计
  6. 定期备份:备份重要主题数据
  7. 监控告警:配置 Prometheus + Grafana 监控

📈 监控方案

使用 JMX 监控

Kafka 内置 JMX 指标,可以集成到监控系统:

# 启用 JMX(修改 kafka.service)
Environment="JMX_PORT=9999"
Environment="KAFKA_JMX_OPTS=-Dcom.sun.management.jmxremote ..."

关键监控指标

  • Broker 指标

    • kafka.server:type=BrokerTopicMetrics,name=MessagesInPerSec
    • kafka.server:type=BrokerTopicMetrics,name=BytesInPerSec
    • kafka.network:type=RequestMetrics,name=TotalTimeMs
  • 主题指标

    • 分区数量
    • ISR(In-Sync Replicas)状态
    • Lag(消费延迟)
  • 系统资源

    • CPU 使用率
    • 内存使用
    • 磁盘 I/O
    • 网络带宽

🎯 生产环境部署建议

  1. 硬件配置

    • CPU: 8 核+
    • 内存: 16GB+
    • 磁盘: SSD(RAID 10)
    • 网络: 万兆网卡
  2. 系统优化

    # 调整文件描述符限制
    echo "* soft nofile 100000" >> /etc/security/limits.conf
    echo "* hard nofile 100000" >> /etc/security/limits.conf
    
    # 调整内核参数
    cat >> /etc/sysctl.conf << EOF
    vm.swappiness=1
    vm.dirty_ratio=80
    vm.dirty_background_ratio=5
    net.core.wmem_default=262144
    net.core.rmem_default=262144
    EOF
    
    sysctl -p
    
  3. 集群规划

    • 最小 3 节点
    • 奇数个节点(3、5、7)
    • 跨机架或跨可用区部署
  4. 备份策略

    • 使用 Kafka MirrorMaker 2.0
    • 定期快照重要主题
    • 配置多数据中心复制

📚 相关资源

🎉 总结

通过这个一键安装脚本,你可以在几分钟内完成 Kafka 的部署,无需手动配置繁琐的参数。脚本支持最新的 KRaft 模式,让你的 Kafka 集群更简单、更高效!

主要优势:

  • ⚡ 5 分钟快速部署
  • 🎯 零配置开箱即用
  • 🔧 支持自定义参数
  • 🛡️ 生产级别配置
  • 📊 自动验证测试
  • 🌐 内置国内镜像源

📄 完整脚本代码

以下是完整的 install_kafka.py 脚本源代码,可直接使用:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Kafka 一键安装脚本(支持 KRaft 模式,无需 Zookeeper)
使用方法:
    sudo python3 install_kafka.py [Kafka版本] [安装目录] [kraft|zookeeper]
    
示例:
    sudo python3 install_kafka.py                              # 默认:3.6.1 + KRaft
    sudo python3 install_kafka.py 3.6.1                        # 指定版本 + KRaft
    sudo python3 install_kafka.py 3.6.1 /opt/kafka kraft       # KRaft 模式(推荐)
    sudo python3 install_kafka.py 3.6.1 /opt/kafka zookeeper   # Zookeeper 模式
"""

import os
import sys
import subprocess
import time
import re
import shutil
import uuid

# 颜色输出
class Colors:
    GREEN = '\033[0;32m'
    YELLOW = '\033[1;33m'
    RED = '\033[0;31m'
    BLUE = '\033[0;34m'
    CYAN = '\033[0;36m'
    NC = '\033[0m'
    BOLD = '\033[1m'

def print_step(message):
    """打印步骤信息"""
    print(f"\n{Colors.CYAN}{'='*70}{Colors.NC}")
    print(f"{Colors.GREEN}{Colors.BOLD}{message}{Colors.NC}")
    print(f"{Colors.CYAN}{'='*70}{Colors.NC}\n")

def print_success(message):
    """打印成功信息"""
    print(f"{Colors.GREEN}{message}{Colors.NC}")

def print_error(message):
    """打印错误信息"""
    print(f"{Colors.RED}{message}{Colors.NC}")

def print_warning(message):
    """打印警告信息"""
    print(f"{Colors.YELLOW}{message}{Colors.NC}")

def print_info(message):
    """打印信息"""
    print(f"{Colors.BLUE}{message}{Colors.NC}")

def run_command(command, show_output=True, check=True):
    """执行系统命令"""
    try:
        if show_output:
            result = subprocess.run(command, shell=True, check=check,
                                   stdout=subprocess.PIPE, stderr=subprocess.PIPE,
                                   text=True, encoding='utf-8')
            if result.stdout and result.stdout.strip():
                print(result.stdout)
            if result.stderr and result.returncode != 0:
                print_error(result.stderr)
            return result.returncode == 0
        else:
            result = subprocess.run(command, shell=True, check=check,
                                   stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
            return result.returncode == 0
    except subprocess.CalledProcessError as e:
        print_error(f"命令执行失败: {command}")
        return False

def check_root():
    """检查是否为 root 用户"""
    if os.geteuid() != 0:
        print_error("此脚本需要 root 权限运行!")
        print_info("请使用: sudo python3 install_kafka.py")
        sys.exit(1)

def get_server_ip():
    """获取服务器 IP 地址"""
    result = subprocess.run("hostname -I | awk '{print $1}'", shell=True,
                          capture_output=True, text=True)
    return result.stdout.strip() if result.returncode == 0 else "0.0.0.0"

def check_java():
    """检查 Java 环境"""
    print_step("步骤 1: 检查 Java 环境")
    
    result = subprocess.run("java -version", shell=True,
                          stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
    
    if result.returncode == 0:
        print_success("Java 已安装")
        print_info(result.stdout.split('\n')[0])
        return True
    else:
        print_warning("Java 未安装,正在安装...")
        return install_java()

def install_java():
    """安装 Java"""
    print_info("正在安装 OpenJDK 17...")
    
    if not run_command("apt update", show_output=False):
        print_error("更新软件包列表失败")
        return False
    
    if not run_command("apt install -y openjdk-17-jdk", show_output=True):
        print_error("Java 安装失败")
        return False
    
    result = subprocess.run("java -version", shell=True,
                          stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
    if result.returncode == 0:
        print_success("Java 安装成功")
        print_info(result.stdout.split('\n')[0])
        return True
    return False

def download_kafka(version, install_dir):
    """下载 Kafka"""
    print_step(f"步骤 2: 下载 Kafka {version}")
    
    scala_version = "2.13"
    kafka_name = f"kafka_{scala_version}-{version}"
    
    # 国内外镜像源列表(国内源优先)
    mirror_urls = [
        # 清华大学镜像
        f"https://mirrors.tuna.tsinghua.edu.cn/apache/kafka/{version}/{kafka_name}.tgz",
        # 阿里云镜像
        f"https://mirrors.aliyun.com/apache/kafka/{version}/{kafka_name}.tgz",
        # 华为云镜像
        f"https://repo.huaweicloud.com/apache/kafka/{version}/{kafka_name}.tgz",
        # 中科大镜像
        f"https://mirrors.ustc.edu.cn/apache/kafka/{version}/{kafka_name}.tgz",
        # 网易镜像
        f"https://mirrors.163.com/apache/kafka/{version}/{kafka_name}.tgz",
        # Apache 官方源(备用)
        f"https://downloads.apache.org/kafka/{version}/{kafka_name}.tgz",
        f"https://archive.apache.org/dist/kafka/{version}/{kafka_name}.tgz",
        # Apache 亚洲镜像
        f"https://dlcdn.apache.org/kafka/{version}/{kafka_name}.tgz",
    ]
    
    tmp_dir = "/tmp"
    tar_file = f"{tmp_dir}/{kafka_name}.tgz"
    
    # 检查现有文件是否完整
    if os.path.exists(tar_file):
        print_info(f"发现已下载的文件: {tar_file}")
        # 验证文件完整性
        verify_result = subprocess.run(f"tar -tzf {tar_file} > /dev/null 2>&1", shell=True)
        if verify_result.returncode == 0:
            print_success("文件验证通过,跳过下载")
            return tar_file, kafka_name
        else:
            print_warning("文件已损坏,重新下载...")
            os.remove(tar_file)
    
    # 预期文件大小(约 100MB,根据版本不同)
    expected_min_size = 50 * 1024 * 1024  # 至少 50MB
    
    for url in mirror_urls:
        print_info(f"尝试从: {url}")
        
        # 使用 wget 下载,显示进度
        result = subprocess.run(
            f"wget --progress=bar:force -c --tries=3 --timeout=60 -O {tar_file} {url}",
            shell=True
        )
        
        if result.returncode == 0 and os.path.exists(tar_file):
            # 检查文件大小
            file_size = os.path.getsize(tar_file)
            print_info(f"下载文件大小: {file_size / 1024 / 1024:.2f} MB")
            
            if file_size < expected_min_size:
                print_warning(f"文件大小异常(< 50MB),可能下载不完整")
                os.remove(tar_file)
                continue
            
            # 验证文件完整性
            print_info("验证文件完整性...")
            verify_result = subprocess.run(
                f"tar -tzf {tar_file} > /dev/null 2>&1", 
                shell=True
            )
            
            if verify_result.returncode == 0:
                print_success(f"Kafka 下载并验证成功")
                return tar_file, kafka_name
            else:
                print_warning("文件验证失败,可能已损坏,尝试下一个镜像...")
                os.remove(tar_file)
        else:
            print_warning(f"下载失败,尝试下一个镜像...")
            if os.path.exists(tar_file):
                os.remove(tar_file)
    
    print_error("所有下载源均失败!")
    return None, None

def extract_kafka(tar_file, kafka_name, install_dir):
    """解压 Kafka"""
    print_step("步骤 3: 解压 Kafka")
    
    parent_dir = os.path.dirname(install_dir)
    os.makedirs(parent_dir, exist_ok=True)
    
    extract_dir = f"/opt/{kafka_name}"
    if os.path.exists(extract_dir):
        print_warning(f"目录已存在: {extract_dir}")
        response = input("是否删除并重新解压? (y/N): ")
        if response.lower() == 'y':
            shutil.rmtree(extract_dir)
        else:
            print_info("使用现有目录")
            if os.path.exists(install_dir) and os.path.islink(install_dir):
                os.remove(install_dir)
            os.symlink(extract_dir, install_dir)
            return install_dir
    
    print_info(f"正在解压到: /opt/")
    result = subprocess.run(f"tar -xzf {tar_file} -C /opt/", shell=True,
                          capture_output=True, text=True)
    if result.returncode != 0:
        print_error("解压失败")
        print_error(f"错误信息: {result.stderr}")
        print_info("尝试验证下载文件...")
        # 检查文件是否完整
        check_result = subprocess.run(f"file {tar_file}", shell=True,
                                     capture_output=True, text=True)
        print_info(f"文件类型: {check_result.stdout.strip()}")
        
        # 尝试验证 tar 文件
        verify_result = subprocess.run(f"tar -tzf {tar_file} > /dev/null 2>&1", shell=True)
        if verify_result.returncode != 0:
            print_error("下载的文件已损坏,正在删除...")
            os.remove(tar_file)
            print_info("请重新运行脚本")
        return None
    
    if os.path.exists(install_dir) and os.path.islink(install_dir):
        os.remove(install_dir)
    
    os.symlink(extract_dir, install_dir)
    print_success(f"Kafka 解压成功: {extract_dir}")
    print_success(f"创建符号链接: {install_dir} -> {extract_dir}")
    
    return install_dir

def configure_kafka_kraft(install_dir, server_ip):
    """配置 Kafka KRaft 模式(无需 Zookeeper)"""
    print_step("步骤 4: 配置 Kafka(KRaft 模式)")
    
    server_config = f"{install_dir}/config/kraft/server.properties"
    
    # 备份原配置
    if not os.path.exists(f"{server_config}.backup"):
        shutil.copy(server_config, f"{server_config}.backup")
        print_info("已备份 server.properties")
    
    # 生成 Cluster ID
    result = subprocess.run(
        f"{install_dir}/bin/kafka-storage.sh random-uuid",
        shell=True, capture_output=True, text=True
    )
    cluster_id = result.stdout.strip()
    print_success(f"生成 Cluster ID: {cluster_id}")
    
    # 读取配置文件
    with open(server_config, 'r') as f:
        config_content = f.read()
    
    # 修改配置
    modifications = {
        # Node ID
        r'node\.id=.*': 'node.id=1',
        
        # Controller 配置
        r'controller\.quorum\.voters=.*': 'controller.quorum.voters=1@localhost:9093',
        
        # 监听地址
        r'#?listeners=.*': f'listeners=PLAINTEXT://0.0.0.0:9092,CONTROLLER://0.0.0.0:9093',
        
        # 广播地址
        r'#?advertised\.listeners=.*': f'advertised.listeners=PLAINTEXT://{server_ip}:9092',
        
        # 日志目录
        r'log\.dirs=.*': 'log.dirs=/var/lib/kafka/kraft-combined-logs',
    }
    
    for pattern, replacement in modifications.items():
        if re.search(pattern, config_content, re.MULTILINE):
            config_content = re.sub(pattern, replacement, config_content, flags=re.MULTILINE)
        else:
            config_content += f"\n{replacement}\n"
    
    # 写回配置文件
    with open(server_config, 'w') as f:
        f.write(config_content)
    
    print_success("Kafka KRaft 配置完成")
    print_info(f"监听地址: 0.0.0.0:9092")
    print_info(f"外部访问地址: {server_ip}:9092")
    print_info(f"Controller 端口: 9093")
    print_info(f"日志目录: /var/lib/kafka/kraft-combined-logs")
    
    # 创建日志目录
    os.makedirs("/var/lib/kafka/kraft-combined-logs", exist_ok=True)
    
    # 格式化存储目录
    print_info("格式化 KRaft 存储目录...")
    result = subprocess.run(
        f"{install_dir}/bin/kafka-storage.sh format -t {cluster_id} -c {server_config}",
        shell=True, capture_output=True, text=True
    )
    
    if result.returncode == 0:
        print_success("存储目录格式化成功")
    else:
        print_error("存储目录格式化失败")
        print_error(result.stderr)
        return False, None
    
    return True, cluster_id

def configure_kafka_zookeeper(install_dir, server_ip):
    """配置 Kafka Zookeeper 模式(传统方式)"""
    print_step("步骤 4: 配置 Kafka(Zookeeper 模式)")
    
    server_config = f"{install_dir}/config/server.properties"
    zookeeper_config = f"{install_dir}/config/zookeeper.properties"
    
    # 备份配置
    for config in [server_config, zookeeper_config]:
        if not os.path.exists(f"{config}.backup"):
            shutil.copy(config, f"{config}.backup")
    
    # 修改 Kafka 配置
    with open(server_config, 'r') as f:
        config_content = f.read()
    
    modifications = {
        r'#?listeners=.*': f'listeners=PLAINTEXT://0.0.0.0:9092',
        r'#?advertised\.listeners=.*': f'advertised.listeners=PLAINTEXT://{server_ip}:9092',
        r'log\.dirs=.*': 'log.dirs=/var/lib/kafka/logs',
        r'zookeeper\.connect=.*': 'zookeeper.connect=localhost:2181',
    }
    
    for pattern, replacement in modifications.items():
        config_content = re.sub(pattern, replacement, config_content, flags=re.MULTILINE)
    
    with open(server_config, 'w') as f:
        f.write(config_content)
    
    print_success("Kafka Zookeeper 配置完成")
    
    # 创建日志目录
    os.makedirs("/var/lib/kafka/logs", exist_ok=True)
    
    return True, None

def create_systemd_service_kraft(install_dir):
    """创建 KRaft 模式的 systemd 服务"""
    print_step("步骤 5: 创建 systemd 服务(KRaft 模式)")
    
    kafka_service = f"""[Unit]
Description=Apache Kafka Server (KRaft mode)
Documentation=http://kafka.apache.org/documentation.html
After=network.target

[Service]
Type=simple
User=root
Environment="JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64"
ExecStart={install_dir}/bin/kafka-server-start.sh {install_dir}/config/kraft/server.properties
ExecStop={install_dir}/bin/kafka-server-stop.sh
Restart=on-abnormal
RestartSec=10

[Install]
WantedBy=multi-user.target
"""
    
    with open('/etc/systemd/system/kafka.service', 'w') as f:
        f.write(kafka_service)
    print_success("创建 Kafka 服务: /etc/systemd/system/kafka.service")
    
    run_command("systemctl daemon-reload", show_output=False)
    print_success("重新加载 systemd 配置")
    
    return True

def create_systemd_service_zookeeper(install_dir):
    """创建 Zookeeper 模式的 systemd 服务"""
    print_step("步骤 5: 创建 systemd 服务(Zookeeper 模式)")
    
    zookeeper_service = f"""[Unit]
Description=Apache Zookeeper Server
Documentation=http://zookeeper.apache.org
After=network.target

[Service]
Type=simple
User=root
Environment="JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64"
ExecStart={install_dir}/bin/zookeeper-server-start.sh {install_dir}/config/zookeeper.properties
ExecStop={install_dir}/bin/zookeeper-server-stop.sh
Restart=on-abnormal

[Install]
WantedBy=multi-user.target
"""
    
    kafka_service = f"""[Unit]
Description=Apache Kafka Server (Zookeeper mode)
Documentation=http://kafka.apache.org/documentation.html
Requires=zookeeper.service
After=zookeeper.service

[Service]
Type=simple
User=root
Environment="JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64"
ExecStart={install_dir}/bin/kafka-server-start.sh {install_dir}/config/server.properties
ExecStop={install_dir}/bin/kafka-server-stop.sh
Restart=on-abnormal

[Install]
WantedBy=multi-user.target
"""
    
    with open('/etc/systemd/system/zookeeper.service', 'w') as f:
        f.write(zookeeper_service)
    print_success("创建 Zookeeper 服务")
    
    with open('/etc/systemd/system/kafka.service', 'w') as f:
        f.write(kafka_service)
    print_success("创建 Kafka 服务")
    
    run_command("systemctl daemon-reload", show_output=False)
    print_success("重新加载 systemd 配置")
    
    return True

def start_services_kraft():
    """启动 KRaft 模式服务"""
    print_step("步骤 6: 启动 Kafka 服务(KRaft 模式)")
    
    print_info("正在启动 Kafka...")
    if not run_command("systemctl start kafka", show_output=False):
        print_error("Kafka 启动失败")
        run_command("journalctl -u kafka -n 50 --no-pager")
        return False
    
    print_success("Kafka 启动成功")
    print_info("等待 Kafka 完全启动...")
    time.sleep(10)
    
    result = subprocess.run("systemctl is-active kafka", shell=True,
                          capture_output=True, text=True)
    if result.stdout.strip() != "active":
        print_error("Kafka 未正常运行")
        return False
    
    run_command("systemctl enable kafka", show_output=False)
    print_success("已设置开机自启动")
    
    return True

def start_services_zookeeper():
    """启动 Zookeeper 模式服务"""
    print_step("步骤 6: 启动 Kafka 服务(Zookeeper 模式)")
    
    print_info("正在启动 Zookeeper...")
    if not run_command("systemctl start zookeeper", show_output=False):
        print_error("Zookeeper 启动失败")
        return False
    
    print_success("Zookeeper 启动成功")
    time.sleep(5)
    
    print_info("正在启动 Kafka...")
    if not run_command("systemctl start kafka", show_output=False):
        print_error("Kafka 启动失败")
        return False
    
    print_success("Kafka 启动成功")
    time.sleep(10)
    
    run_command("systemctl enable zookeeper kafka", show_output=False)
    print_success("已设置开机自启动")
    
    return True

def configure_firewall(mode):
    """配置防火墙"""
    print_step("步骤 7: 配置防火墙")
    
    result = subprocess.run("which ufw", shell=True,
                          stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    
    if result.returncode == 0:
        print_info("检测到 UFW 防火墙")
        
        result = subprocess.run("ufw status", shell=True,
                              capture_output=True, text=True)
        
        if "inactive" not in result.stdout.lower():
            ports = [9092]
            if mode == 'zookeeper':
                ports.append(2181)
            
            for port in ports:
                run_command(f"ufw allow {port}/tcp", show_output=False)
                print_success(f"已添加防火墙规则:允许 {port} 端口")
        else:
            print_warning("UFW 防火墙未启用,跳过配置")
    else:
        print_warning("未检测到 UFW 防火墙")
    
    return True

def verify_installation(install_dir, mode):
    """验证安装"""
    print_step("步骤 8: 验证安装")
    
    # 检查服务状态
    result = subprocess.run("systemctl is-active kafka", shell=True,
                          capture_output=True, text=True)
    if result.stdout.strip() == "active":
        print_success("✓ Kafka 服务运行正常")
    else:
        print_error("✗ Kafka 服务未运行")
        return False
    
    if mode == 'zookeeper':
        result = subprocess.run("systemctl is-active zookeeper", shell=True,
                              capture_output=True, text=True)
        if result.stdout.strip() == "active":
            print_success("✓ Zookeeper 服务运行正常")
    
    # 检查端口
    result = subprocess.run("netstat -tulpn 2>/dev/null | grep 9092 || ss -tulpn 2>/dev/null | grep 9092",
                          shell=True, capture_output=True, text=True)
    if "9092" in result.stdout:
        print_success("✓ Kafka 端口 9092 监听正常")
    
    # 测试创建主题
    print_info("\n测试创建主题...")
    time.sleep(3)
    
    result = subprocess.run(
        f"{install_dir}/bin/kafka-topics.sh --create --topic test-topic "
        f"--bootstrap-server localhost:9092 --partitions 1 --replication-factor 1",
        shell=True, capture_output=True, text=True
    )
    
    if result.returncode == 0 or "already exists" in result.stderr:
        print_success("✓ 主题创建测试成功")
        
        # 列出主题
        result = subprocess.run(
            f"{install_dir}/bin/kafka-topics.sh --list --bootstrap-server localhost:9092",
            shell=True, capture_output=True, text=True
        )
        print_info(f"当前主题: {result.stdout.strip()}")
    else:
        print_warning("⚠ 主题创建测试失败(服务可能还在启动)")
    
    return True

def print_summary(install_dir, server_ip, version, mode, cluster_id=None):
    """打印安装摘要"""
    print(f"\n{Colors.GREEN}{'='*70}{Colors.NC}")
    print(f"{Colors.GREEN}{Colors.BOLD}{'  🎉 Kafka 安装配置完成!':^70}{Colors.NC}")
    print(f"{Colors.GREEN}{'='*70}{Colors.NC}\n")
    
    print(f"{Colors.BOLD}📋 安装信息:{Colors.NC}")
    print(f"  • Kafka 版本: {Colors.YELLOW}{version}{Colors.NC}")
    print(f"  • 运行模式: {Colors.YELLOW}{mode.upper()}{Colors.NC} {'(推荐,无需 Zookeeper)' if mode == 'kraft' else '(传统模式)'}")
    if cluster_id:
        print(f"  • Cluster ID: {Colors.YELLOW}{cluster_id}{Colors.NC}")
    print(f"  • 安装目录: {install_dir}")
    print(f"  • 配置文件: {install_dir}/config/{'kraft/server' if mode == 'kraft' else 'server'}.properties")
    
    print(f"\n{Colors.BOLD}🌐 连接信息:{Colors.NC}")
    print(f"  • 服务器 IP: {Colors.YELLOW}{server_ip}{Colors.NC}")
    print(f"  • Kafka 地址: {Colors.CYAN}{server_ip}:9092{Colors.NC}")
    if mode == 'zookeeper':
        print(f"  • Zookeeper 地址: {Colors.CYAN}{server_ip}:2181{Colors.NC}")
    
    print(f"\n{Colors.BOLD}🛠 服务管理:{Colors.NC}")
    if mode == 'kraft':
        print(f"  {Colors.CYAN}systemctl start kafka{Colors.NC}      # 启动")
        print(f"  {Colors.CYAN}systemctl stop kafka{Colors.NC}       # 停止")
        print(f"  {Colors.CYAN}systemctl restart kafka{Colors.NC}    # 重启")
        print(f"  {Colors.CYAN}systemctl status kafka{Colors.NC}     # 状态")
        print(f"  {Colors.CYAN}journalctl -u kafka -f{Colors.NC}     # 日志")
    else:
        print(f"  启动: systemctl start zookeeper && systemctl start kafka")
        print(f"  停止: systemctl stop kafka && systemctl stop zookeeper")
    
    print(f"\n{Colors.BOLD}📝 常用命令:{Colors.NC}")
    print(f"  # 创建主题")
    print(f"  {Colors.CYAN}{install_dir}/bin/kafka-topics.sh --create --topic my-topic \\")
    print(f"    --bootstrap-server localhost:9092 --partitions 3 --replication-factor 1{Colors.NC}")
    print(f"\n  # 列出主题")
    print(f"  {Colors.CYAN}{install_dir}/bin/kafka-topics.sh --list --bootstrap-server localhost:9092{Colors.NC}")
    print(f"\n  # 生产消息")
    print(f"  {Colors.CYAN}{install_dir}/bin/kafka-console-producer.sh --topic my-topic \\")
    print(f"    --bootstrap-server localhost:9092{Colors.NC}")
    print(f"\n  # 消费消息")
    print(f"  {Colors.CYAN}{install_dir}/bin/kafka-console-consumer.sh --topic my-topic \\")
    print(f"    --from-beginning --bootstrap-server localhost:9092{Colors.NC}")
    
    print(f"\n{Colors.BOLD}🔗 客户端配置:{Colors.NC}")
    print(f"  bootstrap.servers={Colors.CYAN}{server_ip}:9092{Colors.NC}")
    
    print(f"\n{Colors.BOLD}⚠️  安全提醒:{Colors.NC}")
    print(f"  • 已配置外网访问 (0.0.0.0:9092)")
    print(f"  • 当前未启用认证,建议生产环境配置 SASL/SSL")
    print(f"  • 建议使用防火墙限制访问 IP")
    
    if mode == 'kraft':
        print(f"\n{Colors.BOLD}💡 KRaft 优势:{Colors.NC}")
        print(f"  ✓ 无需 Zookeeper,架构更简单")
        print(f"  ✓ 启动更快,资源占用更少")
        print(f"  ✓ 元数据管理更高效")
        print(f"  ✓ Kafka 未来版本的标准模式")
    
    print(f"\n{Colors.GREEN}{'='*70}{Colors.NC}\n")

def main():
    """主函数"""
    check_root()
    
    # 解析参数
    kafka_version = sys.argv[1] if len(sys.argv) > 1 else "3.6.1"
    install_dir = sys.argv[2] if len(sys.argv) > 2 else "/opt/kafka"
    mode = sys.argv[3].lower() if len(sys.argv) > 3 else "kraft"
    
    if mode not in ['kraft', 'zookeeper']:
        print_error(f"无效的模式: {mode}")
        print_info("请使用 'kraft' 或 'zookeeper'")
        sys.exit(1)
    
    server_ip = get_server_ip()
    
    print(f"\n{Colors.BLUE}{'='*70}{Colors.NC}")
    print(f"{Colors.BOLD}{Colors.BLUE}{'  Apache Kafka 一键安装脚本':^70}{Colors.NC}")
    print(f"{Colors.BLUE}{'='*70}{Colors.NC}\n")
    
    print_info(f"Kafka 版本: {kafka_version}")
    print_info(f"安装目录: {install_dir}")
    print_info(f"运行模式: {mode.upper()} {'(推荐)' if mode == 'kraft' else '(传统)'}")
    print_info(f"服务器 IP: {server_ip}")
    
    if mode == 'kraft':
        print_warning("\n使用 KRaft 模式(无需 Zookeeper)")
    else:
        print_warning("\n使用传统 Zookeeper 模式")
    
    print()
    
    cluster_id = None
    
    try:
        # 检查 Java
        if not check_java():
            sys.exit(1)
        
        # 下载 Kafka
        tar_file, kafka_name = download_kafka(kafka_version, install_dir)
        if not tar_file:
            sys.exit(1)
        
        # 解压
        kafka_dir = extract_kafka(tar_file, kafka_name, install_dir)
        if not kafka_dir:
            sys.exit(1)
        
        # 配置
        if mode == 'kraft':
            success, cluster_id = configure_kafka_kraft(install_dir, server_ip)
        else:
            success, _ = configure_kafka_zookeeper(install_dir, server_ip)
        
        if not success:
            sys.exit(1)
        
        # 创建服务
        if mode == 'kraft':
            if not create_systemd_service_kraft(install_dir):
                sys.exit(1)
        else:
            if not create_systemd_service_zookeeper(install_dir):
                sys.exit(1)
        
        # 启动服务
        if mode == 'kraft':
            if not start_services_kraft():
                sys.exit(1)
        else:
            if not start_services_zookeeper():
                sys.exit(1)
        
        # 防火墙
        configure_firewall(mode)
        
        # 验证
        verify_installation(install_dir, mode)
        
        # 摘要
        print_summary(install_dir, server_ip, kafka_version, mode, cluster_id)
        
        # 清理
        if tar_file and os.path.exists(tar_file):
            os.remove(tar_file)
        
    except KeyboardInterrupt:
        print_error("\n\n安装已被用户中断!")
        sys.exit(1)
    except Exception as e:
        print_error(f"\n发生错误: {e}")
        import traceback
        traceback.print_exc()
        sys.exit(1)

if __name__ == "__main__":
    main()

💬 反馈与贡献

如果你在使用过程中遇到任何问题,或有改进建议,欢迎在评论区留言讨论!

觉得有用的话,请点赞收藏支持一下! 👍

🏷️ 标签

#Kafka #消息队列 #大数据 #流处理 #KRaft #运维自动化 #Python #Linux #Debian #Ubuntu


让 Kafka 部署变得简单!🚀
如有问题,欢迎交流讨论!