KAFKA КЛАСТЕР В DOCKER

Thank you for reading this post, don't forget to subscribe! 

Мы созда­дим кла­стер из несколь­ких бро­ке­ров, доба­вим туда раз­би­тый на раз­де­лы топик и вклю­чим его резерв­ное копи­ро­ва­ние. При такой кон­фи­гу­ра­ции нуж­но будет очень поста­рать­ся, что­бы с кла­сте­ром слу­чи­лось что-то неприятное.

Создаём кластер

Думаю, для сего­дняш­не­го экс­пе­ри­мен­та трёх каф­ки­ан­ских хостов будет доста­точ­но. В иде­а­ле, конеч­но, сто­и­ло бы кло­ни­ро­вать и ZooKeeper — для надёж­но­сти. Но сего­дня я поче­му-то уве­рен, что ему ниче­го не гро­зит, так что обой­дём­ся и одним.

Что­бы сэми­ти­ро­вать изо­ли­ро­ван­ные хосты, я поме­щу  ZooKeeper и всех Каф­ка-бро­ке­ров в соб­ствен­ные Docker кон­тей­не­ры и запу­щу их через docker-compose. Послед­ний и упро­сит кон­фи­гу­ра­цию, и поза­бо­тит­ся о настрой­ке сети.

Dockerfile

Что­бы создать обра­зы кон­тей­не­ров для Kafka и ZooKeeper нуж­но все­го две вещи: JDK и Kafka инстал­лер. Обра­зы будут настоль­ко похо­жи, что, в прин­ци­пе, мож­но обой­тись одним Dockerfile и для бро­ке­ров, и для координатора.

Бро­ке­ров к тому же нуж­но еще настро­ить. Напри­мер, по-умол­ча­нию Kafka под­клю­ча­ет­ся к ZooKeeper через localhost:2181, кото­рый внут­ри кон­тей­не­ра ведёт в нику­да. С дру­гой сто­ро­ны, если дать кон­тей­не­ру с ZooKeeper внят­ное назва­ние, напри­мер — zookeeper, то тогда к нему мож­но под­клю­чать­ся по zookeeper:2181. Нуж­но про­сто немно­го под­ре­дак­ти­ро­вать кон­фи­гу­ра­цию брокера

Вто­рой момент: у каж­до­го бро­ке­ра внут­ри кла­сте­ра дол­жен быть уни­каль­ный broker id. Он зада­ёт­ся в server.properties, и в нашем слу­чае его мож­но пере­дать пря­мо из docker-compose в Dockerfile через ARG инструк­цию, и потом уже доба­вить в кон­фи­гу­ра­цию, напри­мер, через  sed . Вот в ито­ге что у меня получилось:

vim Dockerfile

[codesyntax lang="php" blockstate="collapsed"]

[/codesyntax]

 

В обра­зе openjdk есть JDK, а ADD инструк­ция доба­вит и рас­па­ку­ет архив с Каф­кой, кото­рый я преду­смот­ри­тель­но ска­чал до это­го. Потом в RUN пере­име­но­вы­ва­ем пап­ку с Каф­кой и обнов­ля­ем server.properties. Нако­нец, EXPOSE оста­вит в кон­тей­не­ре два откры­тых пор­та: 2181 (ZooKeeper) и 9092 (Kafka).

docker-compose.yml

docker-compose.yml для запус­ка зоо­пар­ка кон­тей­не­ров будет послож­нее. Ведь что­бы полу­чить внят­ный и функ­ци­о­ни­ру­ю­щий кла­стер, пона­до­бят­ся кон­тей­не­ры с ZooKeeper, тре­мя Kafka сер­ве­ра­ми, и про­дю­се­ром и кон­сью­ме­ром сооб­ще­ний (ну что­бы был хоть какой поток дан­ных). Ито­го — шесть кон­тей­не­ров. Это раз в шесть боль­ше, чем обычно.

ZooKeeper

Кон­фи­гу­ра­ция кон­тей­не­ра с ZooKeeper самая простая:

[codesyntax lang="php" blockstate="collapsed"]

[/codesyntax]

 

Она про­сто собе­рет Dockerfile, создан­ный на преды­ду­щем эта­пе, запу­стит ZooKeeper внут­ри кон­тей­не­ра и оста­вит порт 2181 откры­тым для хоста. Вдруг нам захо­чет­ся с ним пообщаться.

Kafka сервера

Кон­фи­гу­ра­ция для Kafka сер­ве­ров чуть-чуть слож­нее, но всё еще вме­ня­е­мая. Кон­фи­гу­ра­ций будет три, и для сер­ве­ра под номе­ром 2 она будет такой:

[codesyntax lang="php" blockstate="collapsed"]

[/codesyntax]

 

depends_on  помо­жет запу­стить кон­тей­нер с Каф­кой обя­за­тель­но после кон­тей­не­ра с ZooKeeper, а  brokerId: 2  пой­дёт пря­ми­ком в Dockerfile, отку­да пере­ко­чу­ет в server.properties.

Продюсер

Про­дю­сер — это малень­кий фили­ал ада. И всё из-за коман­ды  command . Ведь что­бы запу­стить про­дю­сер, отправ­ля­ю­щий сооб­ще­ния, нуж­но всего-то:

[codesyntax lang="php" blockstate="collapsed"]

[/codesyntax]

 

Что в пере­во­де на кошер­ный рус­ский означает:

  • подо­жди 4 секун­ды (решать race condition через задерж­ки — пло­хой тон, но с вос­пи­та­ни­ем в нашу эпо­ху дей­стви­тель­но не очень),
  • создай топик под назва­ни­ем ‘dates’, раз­де­ли его на два раз­де­ла (partitions) и хра­ни по три копии каждого,
  • раз в секун­ду отправ­ляй в топик сооб­ще­ние — теку­щую дату.

Про­сто ведь. Но если ужать это в одну строч­ку, то полу­чит­ся вот что:

[codesyntax lang="php" blockstate="collapsed"]

[/codesyntax]

 

Консьюмер

По срав­не­нию с про­дю­се­ром, кон­сью­мер прак­ти­че­ски не уродлив:

[codesyntax lang="php" blockstate="collapsed"]

[/codesyntax]

 

И про­дю­сер, и кон­сю­мер полу­чи­ли в каче­стве пара­мет­ров спис­ки всех Kafka сер­ве­ров. Это нуж­но для того, что­бы когда кого-то из бро­ке­ров не ста­нет, у сер­ви­сов был запас­ной вариант.

Результат

Вот на что похож docker-compose.yml целиком:

[codesyntax lang="php" blockstate="collapsed"]

[/codesyntax]

 

Запускаем кластер

Всё про­сто. Запус­ка­ем  docker-compose up  и через деся­ток секунд моно­ло­гов от очень раз­го­вор­чи­вых кон­тей­не­ров насту­пит тиши­на, ино­гда пре­ры­ва­е­мая сооб­ще­ни­я­ми от консьюмера:

# consumer_1 | Tue Dec 13 05:23:31 UTC 2018
# consumer_1 | Tue Dec 13 05:23:34 UTC 2018
# consumer_1 | Tue Dec 13 05:23:37 UTC 2018
# consumer_1 | Tue Dec 13 05:23:39 UTC 2018
# consumer_1 | Tue Dec 13 05:23:43 UTC 2018
# consumer_1 | Tue Dec 13 05:23:46 UTC 2018

Кажет­ся, оно дей­стви­тель­но запустилось.

Так как 2181-й порт у ZooKeeper остал­ся открыт, мож­но запро­сить у него ста­ти­сти­ку по топикам:

./bin/kafka-topics.sh --describe --topic dates --zookeeper 127.0.0.1:2181
# Topic: dates Partition: 0 Leader: 3 Replicas: 3,1,2 Isr: 3,1,2
# Topic: dates Partition: 1 Leader: 1 Replicas: 1,2,3 Isr: 1,2,3

Ответ ZooKeeper’а весь­ма инте­рес­ный: у топи­ка ‘dates’ есть два раз­де­ла — 0 и 1, а их лиде­ры — бро­ке­ры 3 и 1 соот­вет­ствен­но. Лидер раз­де­ла — это бро­кер, через кото­рый похо­дит чте­ние и запись, и кото­рый отве­ча­ет за син­хро­ни­за­цию его реплик. В нашем слу­чае репли­ки раз­де­лов хра­нят­ся у бро­ке­ров 3,1,2 и 1,2,3, и все они син­хро­ни­зи­ро­ва­ны (Isr — in-sync replica), то есть все резерв­ные копии на месте.

Боль и страдания Kafka кластера

Когда все бро­ке­ры в кла­сте­ре рабо­та­ют, кла­сте­ру, разу­ме­ет­ся, хоро­шо. Что будет, если како­го-то из бро­ке­ров не ста­нет? Выяс­ня­ем ID кон­тей­не­ра kafka2 (docker-compose ведь изме­нил имя) через  docker ps , оста­нав­ли­ва­ем его, и смот­рим, что получилось:

docker stop 12bda0311443
# 12bda0311443

Что здо­ро­во, consumer-кон­тей­нер как писал в кон­соль свои сооб­ще­ния, так и про­дол­жил без пау­зы. Но если посмот­реть ста­ти­сти­ку топи­ка, то кла­стер изменился:

./kafka/bin/kafka-topics.sh --describe --topic dates --zookeeper 127.0.0.1:2181
#Topic:dates PartitionCount:2 ReplicationFactor:3 Configs:
# Topic: dates Partition: 0 Leader: 3 Replicas: 3,1,2 Isr: 3,1
# Topic: dates Partition: 1 Leader: 1 Replicas: 1,2,3 Isr: 1,3

Лиде­ры раз­де­лов оста­лись теми же, но репли­ка с номе­ром 2 пропала.

А что будет, если ещё кого-нибудь прибить?

Если оста­но­вить kafka3, то с пото­ком сооб­ще­ний всё оста­нет­ся в поряд­ке, но лидер нуле­во­го раз­де­ла поменяется:

./bin/kafka-topics.sh --describe --topic dates --zookeeper 127.0.0.1:2181
#Topic:dates PartitionCount:2 ReplicationFactor:3 Configs:
# Topic: dates Partition: 0 Leader: 1 Replicas: 3,1,2 Isr: 1
# Topic: dates Partition: 1 Leader: 1 Replicas: 1,2,3 Isr: 1

Теперь остав­ший­ся бро­кер kafka1 — лидер все­го. Репли­ка 3, разу­ме­ет­ся, пропала.

Нако­нец, что будет, если сно­ва вер­нуть kafka2 и kafka3 в строй?

./bin/kafka-topics.sh --describe --topic dates --zookeeper 127.0.0.1:2181
#Topic:dates PartitionCount:2 ReplicationFactor:3 Configs:
# Topic: dates Partition: 0 Leader: 1 Replicas: 3,1,2 Isr: 1,3,2
# Topic: dates Partition: 1 Leader: 1 Replicas: 1,2,3 Isr: 1,3,2

ZooKeeper их заме­тил. Лидер раз­де­лов остал­ся ста­рым, но про­пу­щен­ные репли­ки пере­со­зда­лись и уют­но рас­по­ло­жи­лись в «новых» бро­ке­рах. Кла­стер вер­нул­ся в сба­лан­си­ро­ван­ное состояние.

Мораль

Мы созда­ли кла­стер из трёх хостов с фраг­мен­ти­ро­ван­ным топи­ком, а затем уби­ли два хоста из трёх, и это никак не повли­я­ло на нашу воз­мож­ность пуб­ли­ко­вать и полу­чать сооб­ще­ния. Когда хосты вер­ну­лись назад, кла­стер пере­груп­пи­ро­вал­ся и про­дол­жил рабо­тать, как ни в чём не бывало.

Дан­ный при­мер полу­чил­ся намно­го слож­нее, чем преды­ду­щий, но вся слож­ность при­шла от Доке­ра. Един­ствен­ное изме­не­ние в кон­фи­гу­ра­ции Каф­ка-сер­ве­ра по срав­не­нию с кла­сте­ром из одно­го хоста — доба­вил­ся broker id. То есть изме­нив все­го один чис­лен­ный пара­метр мы можем добав­лять сер­ве­ра в кла­стер пачками.