前言

我是一名Go后端开发工程师,不熟悉运维工作。这篇文章记录的是我零运维基础,通过SSH直接连接裸服务器进行Kafka部署的全过程。

我们购买的是纯裸服务器,没有集成Docker、K8s等任何开箱即用的云端工具,所有环境都需要自己手动搭建。这是在测试环境进行的部署,目的是尽量和生产环境保持一致的Kafka环境(生产环境使用的是云服务器厂商提供的托管Kafka产品)。

本文完整记录了从0到1的部署步骤,以及遇到的4类核心问题、一步步的排查思路和最终解决方案,哪怕是和我一样没有运维基础的开发人员,也能跟着本文完成部署。


一、部署背景与前期规划

1. 需求说明

本次部署的核心要求:

  • 测试环境指定Kafka版本:2.3.0(与生产环境托管版本对齐)
  • 部署方式:Docker + Docker Compose,方便后续运维管理
  • 安全要求:双端口隔离(内网服务免密访问、外网客户端强制账号密码认证)
  • 配套工具:Kafdrop可视化管理面板,方便Topic、消息的可视化运维
  • 目录规范:和本地开发环境的目录结构对齐,降低维护成本

2. 服务器与环境说明

  • 服务器:Linux云服务器(裸机,无预装环境),公网访问地址为【你的服务器公网IP】
  • Docker版本:24.0.9(手动安装)
  • 部署目录:/opt/docker/kafka(符合Linux第三方应用目录规范)
  • 镜像选型:业界通用的wurstmeister/kafka(兼容性强、配置简单,适配指定的2.3.0版本)

3. 目录结构规划

提前规划好持久化目录,避免容器重启后数据丢失,同时和本地开发结构对齐:

/opt/docker/kafka/
├── docker-compose.yml          # 核心编排文件
├── kafka_server_jaas.conf      # SASL账号密码认证配置
├── config/                     # (可选) 额外配置文件目录
├── data/                       # Kafka数据持久化目录
├── logs/                       # Kafka运行日志目录
├── zookeeper-data/             # Zookeeper数据目录
└── zookeeper-logs/             # Zookeeper日志目录

二、完整部署步骤

1. 服务器目录创建与权限配置

通过SSH登录服务器,进入目标目录后执行以下命令创建所有需要的子目录:

# 进入目标根目录
cd /opt/docker/kafka

# 直接在当前目录下创建所有子目录
sudo mkdir -p config data logs zookeeper-data zookeeper-logs

# 配置目录权限(wurstmeister镜像默认使用1001用户运行,必须开放对应权限)
sudo chown -R 1001:1001 /opt/docker/kafka

2. 编写认证配置文件

为了实现外网强制账号密码认证,需要创建JAAS认证配置文件kafka_server_jaas.conf,放在/opt/docker/kafka目录下:

KafkaServer {
    org.apache.kafka.common.security.plain.PlainLoginModule required
    serviceName="kafka"
    username="【管理员账号】"
    password="【管理员密码】"
    user_【管理员账号】="【管理员密码】"
    user_【应用接入账号】="【应用接入密码】";
};

配置说明:

  • 第一组账号密码为Kafka集群间通信的管理员账号
  • 格式规范:user_用户名="密码",可按需新增多个业务接入账号

3. 编写docker-compose.yml核心编排文件

最终版编排文件实现了双端口安全隔离:

  • 内网端口9093:容器内部/服务器本地免密访问,给Kafdrop、同服务器的业务服务使用
  • 外网端口9092:强制SASL账号密码认证,给远程客户端、本地开发环境使用

完整docker-compose.yml内容如下:

version: '2'

# 自定义桥接网络,所有服务通过服务名直接通信
networks:
  kafka-network:
    driver: bridge

services:
  # Zookeeper:Kafka依赖的元数据协调服务
  zookeeper:
    image: wurstmeister/zookeeper
    container_name: zookeeper
    restart: always
    ports:
      - "2181:2181"
    networks:
      - kafka-network
    volumes:
      - ./zookeeper-data:/data
      - ./zookeeper-logs:/datalog

  # Kafka Broker核心服务,固定2.3.0版本和生产环境对齐
  kafka:
    image: wurstmeister/kafka:2.12-2.3.0
    container_name: kafka
    restart: always
    ports:
      - "9092:9092"   # 外网认证端口
    environment:
      # 核心监听配置:区分内网免密、外网认证
      KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9093,SASL_PLAINTEXT://0.0.0.0:9092
      # 对外公布的连接地址,必须替换为自己的服务器公网IP
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9093,SASL_PLAINTEXT://【你的服务器公网IP】:9092
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,SASL_PLAINTEXT:SASL_PLAINTEXT
      KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT

      # Zookeeper连接地址,通过服务名直接通信
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181

      # 生产基础配置
      KAFKA_DEFAULT_REPLICATION_FACTOR: 1
      KAFKA_NUM_PARTITIONS: 3
      KAFKA_MESSAGE_MAX_BYTES: 20000000

      # 开启SASL账号密码认证
      KAFKA_SASL_ENABLED_MECHANISMS: PLAIN
      KAFKA_OPTS: -Djava.security.auth.login.config=/kafka/config/kafka_server_jaas.conf

    # 数据持久化挂载
    volumes:
      - ./data:/kafka
      - ./logs:/opt/kafka/logs
      - ./kafka_server_jaas.conf:/kafka/config/kafka_server_jaas.conf
    networks:
      - kafka-network
    depends_on:
      - zookeeper

  # Kafdrop可视化管理面板,通过内网免密端口连接Kafka
  kafdrop:
    image: obsidiandynamics/kafdrop
    container_name: kafdrop
    restart: always
    ports:
      - "9000:9000"
    environment:
      KAFKA_BROKERCONNECT: kafka:9093
      SERVER_PORT: 9000
    networks:
      - kafka-network
    depends_on:
      - kafka

4. 启动服务

编排文件和认证文件准备完成后,执行启动命令。

三、部署过程中的问题排查与解决

本次部署过程中遇到了4类典型问题,以下是完整的排查思路、根因定位和解决方法,也是Kafka Docker部署中90%的人都会踩的坑。

问题1:docker-compose命令不存在,Docker已正常安装

现象

执行docker-compose up -d启动服务时,报错:

Command 'docker-compose' not found, but can be installed with:
apt install docker-compose

但执行docker --version,能正常输出版本号Docker version 24.0.9, build 2936816。

排查思路与根因定位

因为是裸服务器,只手动安装了Docker核心引擎,没有安装docker-compose编排工具。虽然Docker 20.10+版本内置了V2版本的插件(命令为docker compose),但系统默认没有安装,因此无法识别旧版的docker-compose命令。

解决方法

根据系统提示,直接使用apt包管理器安装旧版独立二进制docker-compose:

apt update
apt install docker-compose

安装完成后,用docker-compose --version验证,输出版本号即安装成功。

最终效果

执行docker-compose up -d,成功拉取镜像、启动容器。


问题2:Go客户端连接报错:client has run out of available brokers to talk to

现象

使用Go语言sarama客户端编写的初始化代码,连接服务器Kafka时,报错:

初始化Kafka客户端失败: 连接Kafka失败: kafka: client has run out of available brokers to talk to (Is your cluster reachable?)

原始代码核心配置如下:

var defaultKafkaConfig = &KafkaConfig{
	Brokers: []string{"【你的服务器公网IP】:9092"},
	Version: "0.10.2.0",
}
排查思路

Kafka客户端报这个错误,核心原因只有3类,按优先级排查:

  1. 网络链路不通,客户端根本无法到达服务器的9092端口
  2. Kafka服务端配置错误,监听器/公布地址配置不当
  3. 客户端配置错误,比如缺少认证配置、协议不匹配

我们先从最基础的网络链路开始排查,这是最容易被忽略的点。


问题3:本地端口连通性测试失败,Ping服务器正常

现象

在本地Windows电脑的PowerShell中,执行端口连通性测试命令:

Test-NetConnection 【你的服务器公网IP】 -Port 9092

输出结果:

警告: TCP connect to (【你的服务器公网IP】 : 9092) failed
PingSucceeded: True
TcpTestSucceeded: False

可以Ping通服务器IP,说明三层网络正常,但TCP端口连接失败,说明四层端口被拦截。

排查步骤1:验证服务器本地端口是否正常监听

登录服务器,执行命令查看9092端口的监听状态:

netstat -tlnp | grep 9092

输出结果:

tcp  0  0 0.0.0.0:9092  0.0.0.0:*  LISTEN  943/dockerd

这个结果说明:

  • 9092端口正在监听0.0.0.0(所有网卡,包括公网网卡)
  • 端口由Docker守护进程正常转发,Kafka容器的端口映射完全正常
  • 服务端本身没有问题,问题出在服务器外部的网络访问拦截
排查步骤2:定位拦截点

Ping通、TCP不通,99%的原因是云服务器的安全组/防火墙没有放行9092端口。
云服务器默认只放行22(SSH)、3389(远程桌面)等基础端口,自定义的9092端口会被默认拦截,外部流量无法进入。

解决方法

针对的是华为云(其他的云服务器厂商设置端口规则,大同小异)

  1. 登录云服务器控制台,进入实例的「更多」→「网络/安全组」→「安全组规则配置」
  2. 进入入方向规则页面,点击「添加入方向规则」,按以下配置填写:
    • 优先级:1
    • 策略:允许
    • 类型:IPv4
    • 协议端口:自定义TCP,端口号9092
    • 源地址:0.0.0.0/0(测试用,生产环境建议改为本地公网IP,更安全)
    • 描述:Kafka外网访问端口
  3. 点击确定保存,等待1-3分钟规则生效。

在这里插入图片描述

在这里插入图片描述

最终效果

再次在本地执行端口测试命令:

Test-NetConnection 【你的服务器公网IP】 -Port 9092

输出TcpTestSucceeded: True,端口连通性问题彻底解决。


问题4:端口通了,Go客户端还是连接失败

现象

端口已经可以正常连通,但执行Go代码还是报同样的连接错误。

根因定位

经过排查,原始代码有2个致命错误,完全匹配不上服务端的配置:

  1. 缺少SASL认证配置:服务端外网9092端口强制账号密码认证,代码里完全没有开启SASL、配置账号密码
  2. 缺少安全协议配置:没有指定SASL_PLAINTEXT安全协议,客户端用默认的明文协议连接,和服务端不匹配

(注:Kafka版本是向下兼容的,代码里的0.10.2.0可以正常连接服务端的2.3.0,无需修改版本号)

解决方法:修正后的完整Go代码
package main

import (
	"fmt"
	"sync"

	"github.com/Shopify/sarama"
)

var (
	KafkaClient   sarama.Client
	KafkaProducer sarama.SyncProducer
	kafkaLock     sync.Mutex
)

// KafkaConfig 配置结构体
type KafkaConfig struct {
	Brokers []string
	Version string
}

// 正确配置:和服务端地址、认证完全匹配
var defaultKafkaConfig = &KafkaConfig{
	Brokers: []string{"【你的服务器公网IP】:9092"},
	Version: "0.10.2.0", // Kafka版本向下兼容,无需修改
}

// createKafkaConfig 创建带认证的Kafka配置
func createKafkaConfig() (*sarama.Config, error) {
	config := sarama.NewConfig()

	// 配置Kafka版本
	version, err := sarama.ParseKafkaVersion(defaultKafkaConfig.Version)
	if err != nil {
		return nil, err
	}
	config.Version = version

	// 开启SASL账号密码认证,和服务端配置一致
	config.Net.SASL.Enable = true
	config.Net.SASL.User = "【管理员账号】"           // 服务端配置的账号
	config.Net.SASL.Password = "【管理员密码】"   // 服务端配置的密码
	config.Net.SASL.Mechanism = sarama.SASLTypePlaintext

	// 生产者必填配置:开启成功/错误返回
	config.Producer.Return.Successes = true
	config.Producer.Return.Errors = true

	return config, nil
}

// InitKafkaClient 初始化Kafka客户端和生产者
func InitKafkaClient() error {
	kafkaLock.Lock()
	defer kafkaLock.Unlock()

	// 单例判断:如果客户端已存在且未关闭,直接返回
	if KafkaClient != nil && !KafkaClient.Closed() {
		return nil
	}

	// 创建配置
	config, err := createKafkaConfig()
	if err != nil {
		return fmt.Errorf("创建Kafka配置失败: %w", err)
	}

	brokers := defaultKafkaConfig.Brokers
	fmt.Printf("尝试连接到Kafka brokers: %v\n", brokers)

	// 创建客户端
	client, err := sarama.NewClient(brokers, config)
	if err != nil {
		return fmt.Errorf("连接Kafka失败: %w", err)
	}

	// 创建同步生产者
	producer, err := sarama.NewSyncProducerFromClient(client)
	if err != nil {
		client.Close()
		return fmt.Errorf("创建生产者失败: %w", err)
	}

	// 赋值全局变量
	KafkaClient = client
	KafkaProducer = producer
	fmt.Println("✅ Kafka 连接成功!")
	return nil
}

func main() {
	// 初始化Kafka
	err := InitKafkaClient()
	if err != nil {
		panic(fmt.Sprintf("Kafka初始化失败: %v", err))
	}

	// 测试发送消息
	partition, offset, err := KafkaProducer.SendMessage(&sarama.ProducerMessage{
		Topic: "test-topic",
		Value: sarama.StringEncoder("Hello Kafka! 测试环境部署成功"),
	})
	if err != nil {
		panic(fmt.Sprintf("发送消息失败: %v", err))
	}
	fmt.Printf("消息发送成功!partition: %d, offset: %d\n", partition, offset)
}
最终效果

执行代码,成功输出:

尝试连接到Kafka brokers: [【你的服务器公网IP】:9092]
✅ Kafka 连接成功!
消息发送成功!partition: 0, offset: 0

同时访问http://【你的服务器公网IP】:9000,可以通过Kafdrop面板看到创建的Topic和发送的消息,全链路部署成功。

四、零运维基础部署避坑总结

作为一名不懂运维的Go开发,本次部署踩了很多坑,这里总结5条核心避坑原则:

  1. 端口连通性优先排查:遇到客户端连接问题,先测端口通不通,再排查服务端和客户端配置,90%的问题都出在网络层面。
  2. Kafka监听器配置是核心:LISTENERS是Kafka监听的地址,ADVERTISED_LISTENERS是对外公布的地址,必须填客户端能访问到的IP/域名,这是Kafka最容易踩的坑。
  3. 安全组/防火墙配置前置:部署前先规划好需要开放的端口,提前在安全组放行,避免部署完才发现网络不通。
  4. 目录创建注意当前路径:执行mkdir命令前,确保已进入正确的目标目录(如/opt/docker/kafka),避免目录结构创建错误。
  5. Docker和Docker Compose是两个东西:裸服务器只装Docker是不够的,还需要单独安装Docker Compose编排工具。

更多推荐