---
title: "Установка: kafka-go и локальный брокер"
description: "Установка kafka-go через go get и локальный брокер Kafka в docker compose: первый топик через CreateTopics, консольные утилиты и проверка связи за вечер."
course: "Kafka в Go: продюсеры и консьюмеры на kafka-go"
url: "https://golangmaster.ru/courses/kafka-go/lessons/03-ustanovka-brokera/"
published: "2026-09-19"
updated: "2026-09-29"
minutes: 18
---

# Установка: kafka-go и локальный брокер

*Курс «Kafka в Go: продюсеры и консьюмеры на kafka-go», урок 3 из 25. ~18 минут. Обновлено 29.09.2026. Источник: https://golangmaster.ru/courses/kafka-go/lessons/03-ustanovka-brokera/*

Установка kafka-go занимает одну команду, а вот локальный брокер — половину вечера, если идти без карты: advertised listeners, KRaft и флаги консольных утилит имеют свойство съедать время новичка. В этом уроке соберём рабочую среду целиком: go get для клиента, docker compose для брокера, первый топик через CreateTopics и консольную проверку, что сообщения ходят.

Kafka — единственный инструмент курса, который нельзя почувствовать в песочнице: клиент говорит по TCP с живым брокером, поэтому без окружения всё сведётся к чтению. Хорошая новость в том, что среда собирается один раз и живёт годами: библиотека подключается к проекту одной командой, а брокер — это один Docker-контейнер, который стартует за полминуты и не просит конфигов, если знать три переменные. В этом уроке пройдём весь путь от пустой папки до первого сообщения в собственном топике: go mod init и go get, docker-compose.yml с современным single-node брокером без ZooKeeper, создание топика из Go и из консоли, проверка продюсером и консьюмером. Каждая команда урока копируется как есть — к концу у вас будет ровно та площадка, на которой построены все двадцать пять уроков курса. Сверх этого ничего не понадобится: ни кластера, ни ZooKeeper, ни ручных конфигов — несколько переменных окружения закрывают весь KRaft-режим для одиночной ноды.

### go get: установка kafka-go в проект

Установка kafka-go выглядит стандартно для Go-модулей: создаём модуль командой go mod init и добавляем зависимость github.com/segmentio/kafka-go через go get — импорт-путь совпадает с репозиторием, никаких спецдвижений. Библиотека написана на чистом Go: внутри нет ни строчки cgo, поэтому сборка не требует GCC и системных заголовков, кросс-компиляция из macOS в Linux работает из коробки, а итоговый бинарник — обычный статичный файл, как у любого сервиса на стандартной библиотеке. Это не мелочь: альтернативный клиент confluent-kafka-go тянет за собой C-библиотеку librdkafka, и его сборка в Alpine-образе превращается в отдельный проект. Версия фиксируется в go.mod как обычно; библиотека живёт по классическому тегированию v1 и спокойно обновляется между минорами. Если рядом в проекте уже лежат модули из курсов по [базовому Go](/courses/go-basics/), ничего перенастраивать не нужно — kafka-go встанет в тот же go.mod и тот же кэш модулей. Проверка сборки проста: импортируйте пакет в любой файл и выполните go build — компилятор подтянет модуль и честно покажет ошибку, если версия не сошлась. Всё это делает kafka-go особенно удобной для CI: сборка тестового образа не тянет ни C-инструментарий, ни системных зависимостей.

### Три команды для нового проекта

```go
mkdir kafka-demo && cd kafka-demo

go mod init kafka-demo

go get github.com/segmentio/kafka-go

# проверить, что зависимость легла в go.mod
cat go.mod
```

go mod init создаёт модуль с именем kafka-demo, go get подтягивает последнюю версию библиотеки и пишет её в go.mod вместе с контрольными суммами в go.sum. Отдельного клиента ставить не нужно: kafka-go — единственная зависимость курса, без librdkafka и системных пакетов. Дальше импорт в коде выглядит как github.com/segmentio/kafka-go и сокращается до kafka.

### Локальный брокер kafka в Docker Compose

Современный Kafka не требует ZooKeeper: со версии 2.8 доступен режим KRaft, где кворум метаданных живёт внутри самих брокеров, а с третьей версии он стал основным путём. Для разработки берём single-node конфиг: один контейнер одновременно и брокер, и контроллер. Минимальный docker-compose.yml для образа bitnami/kafka выглядит так, как в блоке ниже, и в нём важно понимать три переменные. PROCESS_ROLES и CONTROLLER_QUORUM_VOTERS включают KRaft-режим для одиночного узла. LISTENERS описывает, на каких портах брокер принимает соединения: 9092 — клиентский, 9093 — внутренний для контроллера. А ADVERTISED_LISTENERS — адрес, который брокер сообщает клиентам в ответе «я здесь»: если написать там имя контейнера, ваш Go-код с хоста получит недостижимый адрес — классическая ловушка, ниже отдельный коллаут. Тома наружу выносим и порт 9092; данные живут в именованном томе kafka_data, поэтому рестарты и пересоздание контейнера учебный топик не стирают. Файл целиком копируется в проект без правок: все переменные обязательны, и их сокращение — самый быстрый способ получить брокер, который поднимается, но не отвечает.

### docker-compose.yml одного брокера

```go
services:
  kafka:
    image: bitnami/kafka:3.7
    container_name: kafka
    ports:
      - "9092:9092"
    environment:
      - KAFKA_CFG_NODE_ID=0
      - KAFKA_CFG_PROCESS_ROLES=controller,broker
      - KAFKA_CFG_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093
      - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://localhost:9092
      - KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER
      - KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=0@kafka:9093
    volumes:
      - kafka_data:/bitnami/kafka

volumes:
  kafka_data:
```

Одна услуга, один образ, шесть переменных: KRaft-режим без ZooKeeper, клиентский слушатель на 9092, контроллерный на 9093. Advertised-адрес localhost:9092 — то, что брокер скажет вашему Go-коду с хоста, поэтому он отличается от внутреннего адреса слушателя. Именованный том kafka_data держит данные лога между рестартами: без него пересоздание контейнера стирает все топики.

> **Advertised listeners: ловушка первого дня.** Типичная картина: compose поднялся, kafka-topics внутри контейнера работает, а Go-код висит или падает с io timeout. Причина — advertised.listeners с адресом вроде kafka:9092: клиент получает метаданные и честно идёт по указанному адресу, а имя kafka резолвится только внутри сети Docker, не с хоста. Симптом особый: соединение установлено, но дальше ничего — вы стучитесь в контейнер, которого снаружи нет. Лечится одним словом localhost в ADVERTISED_LISTENERS, как в конфиге выше. Правило на память: advertised-адрес — то, куда брокер отправляет клиента в ответе, а не то, куда клиент стучится изначально.

### Первый топик: kafka.Client и консоль

Топик в Kafka не создаётся автоматически первой записью — по умолчанию брокер отвечает ошибкой на запись в несуществующий канал, и это осознанная защита от опечаток. Создать топик можно двумя путями. Первый — консольная утилита kafka-topics.sh внутри контейнера: она нужна один раз, чтобы поднять топик orders с тремя партициями и фактором репликации 1. Второй — из кода, и это то, что делают сервисы при старте: kafka-go открывает Admin API через структуру kafka.Client с адресом кластера в поле Addr, а создание топиков — её метод CreateTopics. Он принимает контекст и CreateTopicsRequest со срезом TopicConfig; в каждом конфиге важны три поля: имя, NumPartitions и ReplicationFactor — для локального брокера реплика всегда одна, потому что брокер один. Полное управление топиками из Go — конфиги, изменение числа партиций, удаление — отдельный урок про Admin API, а здесь Client.CreateTopics выполняет роль init-миграции: сервис стартует, гарантирует нужный топик и работает дальше. В командной работе создание топиков обычно выносят в скрипт деплоя, чтобы код и инфраструктура не спорили, кто автор канала. В учебном проекте проще держать клиента и запрос в main.go перед стартом продюсера: против чистого брокера сервис сам себя готовит, а против уже настроенного просто повторяет знание о топике.

### Создание топика из Go

```go
client := &kafka.Client{Addr: kafka.TCP("localhost:9092")}

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

res, err := client.CreateTopics(ctx, &kafka.CreateTopicsRequest{
        Topics: []kafka.TopicConfig{
                {
                        Topic:             "orders",
                        NumPartitions:     3,
                        ReplicationFactor: 1,
                },
        },
})
if err != nil {
        log.Fatal(err)
}

for topic, terr := range res.Errors {
        fmt.Println(topic, terr)
}
```

kafka.Client — вход в Admin API: структура с адресом кластера и Transport'ом по умолчанию, потокобезопасная и долгоживущая. Метод CreateTopics принимает CreateTopicsRequest со срезом TopicConfig и возвращает ответ: err означает сбой запроса целиком, а судьба каждого топика приходит в карте Errors. Для существующего топика там лежит TopicAlreadyExists — её отфильтровывают через errors.Is, свежесозданный топик даёт nil. kafka.TCP превращает строку адреса в net.Addr.

### Чек-лист рабочей среды

- go mod init и go get выполнены, kafka-go видна в go.mod.
- docker compose up -d прошёл, контейнер kafka отвечает на docker ps со статусом Up.
- ADVERTISED_LISTENERS указывает на localhost:9092 — иначе Go-код не дозвонится с хоста.
- Топик orders создан с тремя партициями и виден в списке kafka-topics.sh --list.
- Данные лежат в именованном томе kafka_data, а не в файловой системе контейнера.
- Порт 9092 свободен на хосте: локальный конфликт портов — второй по частоте сюрприз после advertised listeners.

> **down против down -v.** docker compose down останавливает и удаляет контейнер, но оставляет том kafka_data: после docker compose up -d топик и сообщения на месте. Команда с флагом -v стирает и том — полный сброс окружения до чистого брокера. Первое пригодится при ежедневной работе, второе — когда хочется убедиться, что сервис умеет стартовать с нуля и пересоздавать свои топики. Разницу стоит запомнить на уровне пальцев: первый — ежедневная работа, второй — кнопка аварийного сброса.

### Проверка связи: консольные продюсер и консьюмер

Прежде чем писать Go-код, стоит убедиться, что канал жив, — и для этого в образе есть консольные утилиты, которые сами же и пригодятся при отладке весь курс. kafka-console-producer.sh читает строки из стандартного ввода и отправляет каждую как сообщение: набрали текст, нажали Enter — уехало. kafka-console-consumer.sh с флагом --from-beginning вычитывает топик с самого старого живого оффсета и печатает сообщения в терминал, оставаясь висеть и показывать новые. Запустив оба в двух терминалах, вы увидите круговорот: строка ушла из продюсера — появилась у консьюмера. Эта пара — быстрее любой диагностики: если консольные утилиты внутри контейнера ходят, а Go-код нет, проблема в адресе с хоста, то есть в advertised listeners; если и консоль молчит, смотрите, жив ли контейнер и существует ли топик. Утилиты принимают те же --bootstrap-server, что и клиент: единственная точка правды — адрес брокера. Оба инструмента живут в том же образе, что и брокер, поэтому работают даже там, где нет ни Go, ни вашего кода: удобно проверять окружение до репродукции бага. Если консьюмер печатает ваши строки — канал исправен, и любую следующую проблему стоит искать в коде, а не в окружении.

### Команды урока одним столом

| Действие | Команда |
| --- | --- |
| Поднять брокер | docker compose up -d |
| Статус окружения | docker compose ps |
| Создать топик | docker exec kafka kafka-topics.sh --create --bootstrap-server localhost:9092 --topic orders --partitions 3 |
| Список топиков | docker exec kafka kafka-topics.sh --bootstrap-server localhost:9092 --list |
| Прочитать топик с начала | docker exec kafka kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic orders --from-beginning |
| Полный сброс с данными | docker compose down -v |

### Почему держать брокер под рукой до конца курса

Код этого курса — сетевой по определению, и честная оговорка будет звучать одинаково в каждом уроке: песочница сайта не умеет сеть и TCP-сокеты, поэтому kafka-код запускается локально против брокера в Docker, а интерактивы песочницы показывают тематические программы на чистом Go, обычно эмулирующие нужную механику — от хэша ключа до батчей. Такой формат не потеря: механики партиционирования и адресации удобнее щупать интерактивами, а ход сообщений в живом брокере — это десять минут подъёма окружения один раз. С этого урока у вас есть всё: клиент в go.mod, брокер на localhost:9092, топик с тремя партициями и утилиты, чтобы заглянуть внутрь в любую секунду. Дальше каждый урок будет давать код, который стоит против этой площадки: скопировали — запустили — увидели своими глазами. Именно на этом цикле Kafka перестаёт быть набором терминов и становится инструментом, который вы читаете и отлаживаете без страха. К этому же уроку стоит вернуться при смене машины: чек-лист выше — самодостаточная инструкция восстановления окружения за пять минут.

### Проверка: адрес, который видит клиент

**Вопрос:** Go-код на хосте соединяется с брокером, но запись висит. Внутри контейнера утилиты работают. Первое, что проверяем?

- ✅ Advertised listeners: брокер может сообщать недостижимый с хоста адрес
- Версию Go: kafka-go требует самый свежий компилятор
- Наличие ZooKeeper: без него Kafka не работает
- Размер диска: лог переполнен

Клиент узнаёт адрес брокера из метаданных: если advertised listeners названы именем контейнера, с хоста этот адрес не резолвится. Симптом именно такой — соединение есть, а запись не идёт. ZooKeeper в KRaft-режиме не нужен, версия Go и диск тут ни при чём.

### Проверка: что делает go get

**Вопрос:** Что происходит после go get github.com/segmentio/kafka-go?

- Библиотека ставится системным пакетом и требует перезапуска IDE
- Собирается C-библиотека librdkafka для скорости
- ✅ Версия фиксируется в go.mod, контрольные суммы — в go.sum
- Создаётся локальный брокер для разработки

go get — обычная работа модулей Go: зависимость записывается в go.mod и верифицируется в go.sum, код качается в общий кэш модулей. Никакого cgo и системных пакетов: kafka-go — чистый Go, а брокер поднимается Docker'ом отдельно.

### Что станет с топиком после рестарта?

```go
# в compose объявлен именованный том:
# volumes: kafka_data:/bitnami/kafka

docker compose down
docker compose up -d

docker exec kafka kafka-topics.sh \
  --bootstrap-server localhost:9092 --list
```

**Что выведет программа?**

- Список пуст: down удаляет контейнер вместе с данными
- ✅ Топик orders на месте: данные жили в именованном томе
- Топик на месте, но партиции обнулились до одной
- Команда упадёт: топик надо создавать заново через CreateTopics

down удаляет контейнер, но не трогает именованный том, поэтому после up список топиков прежний — вместе с сообщениями. Полный сброс делает только down -v. Именно поэтому том в compose не украшение: без него любой рестарт во время учёбы стирал бы учебные данные.

### Соберите создание топика

Допишите вход в Admin API, сборку адреса и вызов создания топика.

```go
client := &kafka.___{
        Addr: kafka.___("localhost:9092"),
}

res, err := client.___(ctx, &kafka.CreateTopicsRequest{
        Topics: []kafka.TopicConfig{
                {
                        Topic:             "orders",
                        NumPartitions:     3,
                        ReplicationFactor: 1,
                },
        },
})
```

Подсказка: Вход в Admin API — структура Client, адрес собирает kafka.TCP, а создаёт топики метод CreateTopics с запросом CreateTopicsRequest.

Правильные ответы в пропусках: `Client`, `TCP`, `CreateTopics`

### Соберите путь до первого сообщения

Расставьте шаги от пустой папки до записи: модуль, зависимость, брокер, топик, Writer, отправка, закрытие.

Правильный порядок строк:

1. `go mod init kafka-demo`
2. `go get github.com/segmentio/kafka-go`
3. `docker compose up -d`
4. `docker exec kafka kafka-topics.sh --create --bootstrap-server localhost:9092 --topic orders --partitions 3`
5. `writer := kafka.NewWriter(kafka.WriterConfig{Brokers: []string{"localhost:9092"}, Topic: "orders"})`
6. `err := writer.WriteMessages(ctx, kafka.Message{Key: []byte("order-1"), Value: []byte("hello")})`
7. `writer.Close()`

### Карточки: установка и окружение

- **go.mod и go.sum** — Манифест зависимостей модуля и файл контрольных сумм. kafka-go попадает туда через go get github.com/segmentio/kafka-go.
- **KRaft** — Режим Kafka без ZooKeeper: метаданные и кворум живут внутри брокеров. Для локальной среды — single-node ролью controller,broker.
- **advertised.listeners** — Адрес, который брокер сообщает клиентам в метаданных. Должен быть достижим с хоста: для локальной разработки — localhost:9092.
- **TopicConfig** — Структура kafka-go для создания топика: имя, NumPartitions и ReplicationFactor. Применяется через kafka.CreateTopics.
- **kafka-topics.sh** — Консольная утилита в образе: создать, перечислить и описать топики. Запускается внутри контейнера через docker exec.
- **Именованный том** — Docker-том вроде kafka_data, где живёт лог брокера. down его сохраняет, down -v стирает вместе со всеми топиками.

Среда готова: клиент в go.mod, брокер на localhost:9092, топик orders с тремя партициями, консольные утилиты под рукой. Дальше по курсу: в [уроке про Writer](/courses/kafka-go/lessons/04-pervyj-producer/) напишем первого продюсера и разберём WriteMessages с контекстом и ошибками; в [уроке про Reader](/courses/kafka-go/lessons/06-pervyj-consumer/) — первого консьюмера. Создание топиков из кода вернётся с полным Admin API в [уроке шестнадцатом](/courses/kafka-go/lessons/16-admin-topiki/). Домашнее задание — собрать всё из урока руками: эта площадка будет колесом всей телеги, и час, вложенный сейчас, вернётся на каждом следующем уроке. Если что-то не взлетело — начните с коллаута про advertised listeners: девять из десяти проблем подключения живут именно там.

## Коротко: что запомнить

- kafka-go ставится через go get как обычная зависимость: чистый Go, без cgo и системных пакетов.
- Локальный брокер — один контейнер bitnami/kafka в режиме KRaft: без ZooKeeper, с томом для данных.
- Advertised listeners обязаны указывать на адрес, достижимый с хоста: localhost:9092 — для локальной разработки.
- Топики создаются явно: kafka.CreateTopics из кода или kafka-topics.sh из консоли.
- Консольные продюсер и консьюмер — быстрая диагностика: если консоль ходит, а Go нет, проблема в адресе с хоста.
- Kafka без ZooKeeper — это KRaft: single-node из шести переменных окружения поднимается за полминуты.
- down сохраняет том, down -v стирает всё: разница между рестартом окружения и полным сбросом.
- Песочница сеть не поднимает: kafka-код — локальный Go и брокер в Docker, интерактивы — программы на чистом Go.

## Домашнее задание

Соберите среду по уроку и зафиксируйте её в репозитории проекта: docker-compose.yml из урока, скрипт setup.sh с go mod init, go get и созданием топика orders на три партиции. Проверьте полный цикл: compose up, CreateTopics через go run, отправка сообщения Writer'ом из четвёртого урока-тизера, чтение через kafka-console-consumer. Затем выполните docker compose down -v и прогоните скрипт заново — окружение должно восстанавливаться с нуля без ручных шагов. Сохраните compose и скрипт в репозитории: они пригодятся и при смене машины, и при переходе на новые версии брокера.

## Вопросы и ответы по уроку

### Можно ли ставить Kafka без Docker?

Можно: скачать архив с сайта Apache, распаковать и запустить bin/kafka-server-start.sh с KRaft-конфигом из папки config/kraft. Но Java-окружение, ручные конфиги и очистка логов руками делают Docker-путь практичнее для разработки: контейнер поднимается одной командой и стирается одной командой.

### Почему образ bitnami, а не Apache и не Confluent?

Все три рабочие. Bitnami даёт KRaft single-node в шести переменных окружения без ZooKeeper-контейнера, Confluent требует либо пару контейнеров, либо больше переменных, а чистый Apache — ручную правку конфигов. Для учёбы важна простота подъёма, а не производительностные флаги, поэтому курс держится bitnami; конфиги переносимы — все настройки из урока переезжают на любой образ с минимальными правками имён переменных.

### Нужен ли ZooKeeper для учёбы?

Нет. Начиная с Kafka 3.x режим KRaft — штатный путь, и single-node из урока работает на нём. Отдельный контейнер ZooKeeper нужен только старым версиям 2.x и специфическим кластерным конфигурациям; для локальной разработки и этого курса он не требуется. Практический совет: какую бы сборку вы ни выбрали, убедитесь, что её версия поддерживает KRaft — 3.x и новее.
