📖 前言
在微服务架构和大数据处理中,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
就这么简单!脚本会自动完成以下所有步骤:
- ✅ 检查并安装 Java 17
- ✅ 下载 Kafka 安装包
- ✅ 解压并配置 Kafka
- ✅ 创建 systemd 服务
- ✅ 启动服务并设置开机自启
- ✅ 配置防火墙规则
- ✅ 验证安装是否成功
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)
生产环境强烈建议启用认证!
- 生成 SSL 证书
- 配置 SASL 认证
- 修改 listeners 为 SASL_SSL
- 配置客户端认证信息
详细配置参考 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
🔒 安全最佳实践
- 启用认证:生产环境必须配置 SASL 或 SSL
- 网络隔离:使用防火墙限制访问 IP
- 权限控制:配置 Kafka ACL(访问控制列表)
- 数据加密:启用 SSL 加密传输
- 日志审计:开启操作日志审计
- 定期备份:备份重要主题数据
- 监控告警:配置 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=MessagesInPerSeckafka.server:type=BrokerTopicMetrics,name=BytesInPerSeckafka.network:type=RequestMetrics,name=TotalTimeMs
-
主题指标
- 分区数量
- ISR(In-Sync Replicas)状态
- Lag(消费延迟)
-
系统资源
- CPU 使用率
- 内存使用
- 磁盘 I/O
- 网络带宽
🎯 生产环境部署建议
-
硬件配置
- CPU: 8 核+
- 内存: 16GB+
- 磁盘: SSD(RAID 10)
- 网络: 万兆网卡
-
系统优化
# 调整文件描述符限制 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、5、7)
- 跨机架或跨可用区部署
-
备份策略
- 使用 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 部署变得简单!🚀
如有问题,欢迎交流讨论!