{"id":23086808,"url":"https://github.com/papirosko/kafka-mm2-demo","last_synced_at":"2025-04-30T18:04:34.839Z","repository":{"id":186175085,"uuid":"674662304","full_name":"papirosko/kafka-mm2-demo","owner":"papirosko","description":"Example of deploying mirror maker 2 on kafka connect cluster","archived":false,"fork":false,"pushed_at":"2023-08-04T18:32:19.000Z","size":17,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-30T18:04:23.235Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/papirosko.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2023-08-04T13:30:33.000Z","updated_at":"2024-11-23T07:32:42.000Z","dependencies_parsed_at":null,"dependency_job_id":"a702f5da-8a5d-45cf-9449-526247919b5a","html_url":"https://github.com/papirosko/kafka-mm2-demo","commit_stats":null,"previous_names":["papirosko/kafka-mm2-demo"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/papirosko%2Fkafka-mm2-demo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/papirosko%2Fkafka-mm2-demo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/papirosko%2Fkafka-mm2-demo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/papirosko%2Fkafka-mm2-demo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/papirosko","download_url":"https://codeload.github.com/papirosko/kafka-mm2-demo/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251758162,"owners_count":21638988,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-12-16T19:31:14.147Z","updated_at":"2025-04-30T18:04:34.816Z","avatar_url":"https://github.com/papirosko.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Preface\n\nWhen your application has customers all over the world and you want to decrease the latency for them, usually you \ndeploy instances of your application in different DC.\n\nWe also do so, we have clusters deployed in US and UK.\n\nEach of our clusters has its own kafka cluster. We use it to collect realtime logs from all nodes. Later logs are \nconsumed by Graylog, analytics processors and alerting system.\n\nThe tricky part here is to manage all the clusters in each application, that consumes the messages (so, graylog will \nbe aware of UK and US clusters and will connect to both). No one wants that. The much better idea is to have \ncentralized kafka cluster, where we can get logs from all remote DC.\n\nThis is where mirror maker comes in play. The idea of this application is to read the messages from remote \nkafka cluster and write them into local cluster.\n\nPreviously we used mirror maker 1, which literally consisted of consumers per each source cluster and producers \nper each target cluster. It works pretty well, and even has some advantages over mirror maker 2 (e.g., you can \ncontrol the number of partitions in target cluster). But time goes on, we decided to migrate to mm2.\n\nMM2 can run in 3 main modes (https://cwiki.apache.org/confluence/display/KAFKA/KIP-382%3A+MirrorMaker+2.0#KIP382:MirrorMaker2.0-Walkthrough:RunningMirrorMaker2.0):\n- Legacy mode (much the same as mm1)\n- Standalone/distributed mode with some limitations:\n    https://cwiki.apache.org/confluence/display/KAFKA/KIP-710%3A+Full+support+for+distributed+mode+in+dedicated+MirrorMaker+2.0+clusters\n- Inside kafka connect cluster\n\nWe are going to deploy mm2 inside kafka connect cluster. We will create configuration for the worker, \nmonitor workers using kafka.\n\n\n# Initial setup\n\nLet’s start with 2 clusters and helper application:\n- Source cluster (2 nodes)\n- Target cluster (2 nodes)\n- Kafka ui\n- Kafka exporter\n- Prometheus\n- Grafana\n\nFor the details of this deployment, please read my previous article:\nhttps://medium.com/@penkov.vladimir/kafka-cluster-with-ui-and-metrics-easy-setup-d12d1b94eccf.\n\nThe `docker-compose.yml` config will be like this:\n\n```yaml\nversion: '3.8'\n\nx-kafka-common: \u0026kafka-common\n  image: 'bitnami/kafka:latest'\n  ports:\n    - \"9092\"\n  networks:\n    - kafka\n  healthcheck:\n    test: \"bash -c 'printf \\\"\\\" \u003e /dev/tcp/127.0.0.1/9092; exit $$?;'\"\n    interval: 5s\n    timeout: 10s\n    retries: 3\n    start_period: 30s\n  restart: unless-stopped\n\n\n\n\nx-kafka-source-env-common: \u0026kafka-source-env-common\n  ALLOW_PLAINTEXT_LISTENER: 'yes'\n  KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE: 'true'\n  KAFKA_CFG_CONTROLLER_QUORUM_VOTERS: 0@kafka-source-0:9093,1@kafka-source-1:9093\n  KAFKA_KRAFT_CLUSTER_ID: abcdefghijklmnopqrstuv\n  KAFKA_CFG_PROCESS_ROLES: controller,broker\n  KAFKA_CFG_CONTROLLER_LISTENER_NAMES: CONTROLLER\n  KAFKA_CFG_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093\n  EXTRA_ARGS: \"-Xms128m -Xmx256m -javaagent:/opt/jmx-exporter/jmx_prometheus_javaagent-0.19.0.jar=9404:/opt/jmx-exporter/kafka-2_0_0.yml\"\n\n\nx-kafka-target-env-common: \u0026kafka-target-env-common\n  ALLOW_PLAINTEXT_LISTENER: 'yes'\n  KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE: 'true'\n  KAFKA_CFG_CONTROLLER_QUORUM_VOTERS: 0@kafka-target-0:9093,1@kafka-target-1:9093\n  KAFKA_KRAFT_CLUSTER_ID: abcdef123jklmnopqrstuv\n  KAFKA_CFG_PROCESS_ROLES: controller,broker\n  KAFKA_CFG_CONTROLLER_LISTENER_NAMES: CONTROLLER\n  KAFKA_CFG_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093\n  EXTRA_ARGS: \"-Xms128m -Xmx256m -javaagent:/opt/jmx-exporter/jmx_prometheus_javaagent-0.19.0.jar=9404:/opt/jmx-exporter/kafka-2_0_0.yml\"\n\n\nservices:\n\n  kafka-source-0:\n    \u003c\u003c: *kafka-common\n    environment:\n      \u003c\u003c: *kafka-source-env-common\n      KAFKA_CFG_NODE_ID: 0\n    volumes:\n      - kafka_0_data:/bitnami/kafka\n      - ./jmx-exporter:/opt/jmx-exporter\n\n  kafka-source-1:\n    \u003c\u003c: *kafka-common\n    environment:\n      \u003c\u003c: *kafka-source-env-common\n      KAFKA_CFG_NODE_ID: 1\n    volumes:\n      - kafka_1_data:/bitnami/kafka\n      - ./jmx-exporter:/opt/jmx-exporter\n\n  kafka-target-0:\n    \u003c\u003c: *kafka-common\n    environment:\n      \u003c\u003c: *kafka-target-env-common\n      KAFKA_CFG_NODE_ID: 0\n    volumes:\n      - kafka_2_data:/bitnami/kafka\n      - ./jmx-exporter:/opt/jmx-exporter\n\n  kafka-target-1:\n    \u003c\u003c: *kafka-common\n    environment:\n      \u003c\u003c: *kafka-target-env-common\n      KAFKA_CFG_NODE_ID: 1\n    volumes:\n      - kafka_3_data:/bitnami/kafka\n      - ./jmx-exporter:/opt/jmx-exporter\n\n\n  kafka-ui:\n    container_name: kafka-ui\n    image: provectuslabs/kafka-ui:latest\n    volumes:\n      - ./kafka-ui/config.yml:/etc/kafkaui/dynamic_config.yaml\n    environment:\n      DYNAMIC_CONFIG_ENABLED: 'true'\n    depends_on:\n      kafka-source-0:\n        condition: service_healthy\n      kafka-source-1:\n        condition: service_healthy\n      kafka-target-0:\n        condition: service_healthy\n      kafka-target-1:\n        condition: service_healthy\n    networks:\n      - kafka\n    ports:\n      - '8080:8080'\n    healthcheck:\n      test: wget --no-verbose --tries=1 --spider localhost:8080 || exit 1\n      interval: 5s\n      timeout: 10s\n      retries: 3\n      start_period: 30s\n\n  prometheus:\n    image: prom/prometheus\n    container_name: prometheus\n    command:\n      - '--config.file=/etc/prometheus/prometheus.yml'\n    ports:\n      - 9090:9090\n    volumes:\n      - ./prometheus:/etc/prometheus\n      - prom_data:/prometheus\n    networks:\n      - kafka\n    healthcheck:\n      test: wget --no-verbose --tries=1 --spider localhost:9090 || exit 1\n      interval: 5s\n      timeout: 10s\n      retries: 3\n      start_period: 5s\n\n\n  kafka-exporter:\n    image: docker.io/bitnami/kafka-exporter:latest\n    depends_on:\n      kafka-source-0:\n        condition: service_healthy\n      kafka-source-1:\n        condition: service_healthy\n      kafka-target-0:\n        condition: service_healthy\n      kafka-target-1:\n        condition: service_healthy\n    ports:\n      - \"9308:9308\"\n    networks:\n      - kafka\n    command: --kafka.server=kafka-source-0:9092 --kafka.server=kafka-source-1:9092 --kafka.server=kafka-target-0:9092 --kafka.server=kafka-target-1:9092\n    healthcheck:\n      test: \"bash -c 'printf \\\"\\\" \u003e /dev/tcp/127.0.0.1/9308; exit $$?;'\"\n      interval: 5s\n      timeout: 10s\n      retries: 3\n      start_period: 5s\n\n  grafana:\n    image: grafana/grafana\n    container_name: grafana\n    ports:\n      - \"3000:3000\"\n    environment:\n      - GF_SECURITY_ADMIN_USER=admin\n      - GF_SECURITY_ADMIN_PASSWORD=grafana\n    volumes:\n      - ./grafana/provisioning:/etc/grafana/provisioning\n      - ./grafana/dashboards:/var/lib/grafana/dashboards\n    networks:\n      - kafka\n    healthcheck:\n      test: curl --fail localhost:3000\n      interval: 5s\n      timeout: 10s\n      retries: 3\n      start_period: 10s\n\n\n\nnetworks:\n  kafka:\n\nvolumes:\n  kafka_0_data:\n    driver: local\n  kafka_1_data:\n    driver: local\n  kafka_2_data:\n    driver: local\n  kafka_3_data:\n    driver: local\n  prom_data:\n    driver: local\n```\n\n`kafka-ui/config.yml` should contain:\n\n```yaml\nkafka:\n  clusters:\n    - bootstrapServers: kafka-source-0:9092,kafka-source-1:9092\n      name: kafka-source\n      metrics:\n        type: JMX\n        port: 9404\n\n    - bootstrapServers: kafka-target-0:9092,kafka-target-1:9092\n      name: kafka-target\n      metrics:\n        type: JMX\n        port: 9404\n```\n\n`prometheus/prometheus.yml` should contain:\n\n```yaml\n- job_name: kafka-source-exporter\n  honor_timestamps: true\n  scrape_interval: 15s\n  scrape_timeout: 10s\n  metrics_path: /metrics\n  scheme: http\n  static_configs:\n    - targets:\n        - kafka-source-0:9404\n        - kafka-source-1:9404\n- job_name: kafka-target-exporter\n  honor_timestamps: true\n  scrape_interval: 15s\n  scrape_timeout: 10s\n  metrics_path: /metrics\n  scheme: http\n  static_configs:\n    - targets:\n        - kafka-target-0:9404\n        - kafka-target-1:9404\n```\n\n\nLet’s start the services:\n```shell\ndocker-compose up\n```\n\n\nVisit Kafka UI at (http://localhost:8080 with credentials  `admin`:`admin`. now we have 2 clusters online. \n\nNow we need to add new kafka connect cluster.\n\n\n# Kafka connect\n\nOur connect cluster will consist of 2 nodes. First we define common environment variables:\n```yaml\nx-kafka-connect-env-common: \u0026kafka-connect-env-common\n  KAFKA_HEAP_OPTS: \"-Xmx256m -Xms128M -javaagent:/opt/jmx-exporter/jmx_prometheus_javaagent-0.19.0.jar=9404:/opt/jmx-exporter/kafka-2_0_0.yml\"\n```\n\nWe will use kafka-target cluster for storing all kafka connect internal data. This makes sense, because \ndeployment mm2 close to the target cluster will improve network latency.\n\nNext common properties:\n```yaml\nx-kafka-connect-common: \u0026kafka-connect-common\n  image: 'bitnami/kafka:latest'\n  networks:\n    - kafka\n  volumes:\n    - ./jmx-exporter:/opt/jmx-exporter\n    - ./kafka-connect/connect-distributed.properties:/opt/bitnami/kafka/config/connect-distributed.properties\n  depends_on:\n    kafka-target-0:\n      condition: service_healthy\n    kafka-target-1:\n      condition: service_healthy\n  command: /opt/bitnami/kafka/bin/connect-distributed.sh /opt/bitnami/kafka/config/connect-distributed.properties\n  healthcheck:\n    test: \"bash -c 'printf \\\"\\\" \u003e /dev/tcp/127.0.0.1/8083; exit $$?;'\"\n    interval: 5s\n    timeout: 10s\n    retries: 3\n    start_period: 30s\n  restart: unless-stopped\n  environment:\n    \u003c\u003c: *kafka-connect-env-common\n```\n\n\nCreate configuration file for kafka connect:\n```shell\nmkdir -p kafka-connect\ntouch kafka-connect/connect-distributed.properties\n```\n\nuse the following:\n```properties\nbootstrap.servers=kafka-target-0:9092,kafka-target-1:9092\ngroup.id=connect-cluster\nkey.converter=org.apache.kafka.connect.json.JsonConverter\nvalue.converter=org.apache.kafka.connect.json.JsonConverter\nkey.converter.schemas.enable=true\nvalue.converter.schemas.enable=true\ninternal.key.converter=org.apache.kafka.connect.json.JsonConverter\ninternal.value.converter=org.apache.kafka.connect.json.JsonConverter\ninternal.key.converter.schemas.enable=false\ninternal.value.converter.schemas.enable=false\noffset.storage.topic=connect-offsets\noffset.storage.replication.factor=1\n#offset.storage.partitions=25\nconfig.storage.topic=connect-configs\nconfig.storage.replication.factor=1\nstatus.storage.topic=connect-status\nstatus.storage.replication.factor=1\noffset.flush.interval.ms=10000\nrest.port=8083\n```\n\n\nNow let’s add 2 kafka connect nodes:\n\n```yaml\nkafka-connect-0:\n  \u003c\u003c: *kafka-connect-common\n  hostname: kafka-connect-0\n  ports:\n    - '8083:8083'\n\nkafka-connect-1:\n  \u003c\u003c: *kafka-connect-common\n  hostname: kafka-connect-1\n```\n\n\nWe open `8083` port on 1st instance to be able to deploy connectors configurations with rest endpoints.\n\nAdd both nodes to kafka UI in the kafka-target cluster:\n```yaml\n    - bootstrapServers: kafka-target-0:9092,kafka-target-1:9092\n      name: kafka-target\n      kafkaConnect:\n        - address: http://kafka-connect-0:8083\n          name: kafka-connect-0\n        - address: http://kafka-connect-1:8083\n          name: kafka-connect-1\n      metrics:\n        type: PROMETHEUS\n        port: 9404\n```\n\n\nAdd jmx exporters to Prometheus:\n\n```yaml\n  - job_name: kafka-connect-exporter\n    honor_timestamps: true\n    scrape_interval: 15s\n    scrape_timeout: 10s\n    metrics_path: /metrics\n    scheme: http\n    static_configs:\n      - targets:\n          - kafka-connect-0:9404\n          - kafka-connect-1:9404\n```\n\n\nRestart docker compose.\n\n\n\nBefore we starts actual mirror maker 2 connectors, we need to create topic `test` and produce some messages:\n```shell\ndocker compose exec kafka-source-0 /opt/bitnami/kafka/bin/kafka-topics.sh --create \\      \n   --bootstrap-server kafka-source-0:9092,kafka-source-1:9092 --replication-factor 2 \\\n   --partitions 5 --topic test\ndocker compose exec kafka-source-0 /opt/bitnami/kafka/bin/kafka-console-producer.sh \\\n   --bootstrap-server kafka-source-0:9092,kafka-source-1:9092 \\\n   --producer.config /opt/bitnami/kafka/config/producer.properties --topic test\n```\n\n\n# Mirror Maker 2 Connectors\n\nBasically we need 3 configuration files:\n- The `MirrorHeartbeatConnector` provides heartbeats, monitoring of replication flows, and \n  client discovery of replication topologies (which can be more complex than for the original MirrorMaker).\n- The `MirrorCheckpointConnector` manages consumer offset synchronization, emits checkpoints, and enables failover.\n- The `MirrorSourceConnector` replicates records from local to remote clusters and enables offset synchronization.\n     \nMore details here: https://www.instaclustr.com/blog/kafka-mirrormaker-2-theory/\n\nWe will store the 3 files per each source cluster in the folder mm2 in the format:\n- `mm2-cpc-\u003ccluster\u003e.json`\n- `mm2-hbc-\u003ccluster\u003e.json`\n- `mm2-msc-\u003ccluster\u003e.json`\n\n\nFor example, we have cluster `source-0`, so the files will be:\n- `mm2-cpc-source-0.json`\n- `mm2-hbc-source-0.json`\n- `mm2-msc-source-0.json`\n\nHere are the contents:\n\n`mm2-cpc-source-0.json`:\n```json\n{\n  \"name\": \"mm2-cpc-source-0\",\n  \"connector.class\": \"org.apache.kafka.connect.mirror.MirrorCheckpointConnector\",\n  \"key.converter\": \"org.apache.kafka.connect.converters.ByteArrayConverter\",\n  \"value.converter\": \"org.apache.kafka.connect.converters.ByteArrayConverter\",\n  \"source.cluster.alias\": \"source-0\",\n  \"source.cluster.bootstrap.servers\": \"kafka-source-0:9092,kafka-source-1:9092\",\n  \"target.cluster.alias\": \"target-0\",\n  \"target.cluster.bootstrap.servers\": \"kafka-target-0:9092,kafka-target-1:9092\"\n}\n```\n\n\n`mm2-hbc-source-0.json`:\n```json\n{\n  \"name\": \"mm2-hbc-source-0\",\n  \"connector.class\": \"org.apache.kafka.connect.mirror.MirrorHeartbeatConnector\",\n  \"key.converter\": \"org.apache.kafka.connect.converters.ByteArrayConverter\",\n  \"value.converter\": \"org.apache.kafka.connect.converters.ByteArrayConverter\",\n  \"source.cluster.alias\": \"source-0\",\n  \"source.cluster.bootstrap.servers\": \"kafka-source-0:9092,kafka-source-1:9092\",\n  \"target.cluster.alias\": \"target-0\",\n  \"target.cluster.bootstrap.servers\": \"kafka-target-0:9092,kafka-target-1:9092\"\n}\n```\n\n\n`mm2-msc-source-0.json`:\n```json\n{\n  \"name\": \"mm2-msc-source-0\",\n  \"connector.class\": \"org.apache.kafka.connect.mirror.MirrorSourceConnector\",\n  \"topics\": \"test\",\n  \"sync.topic.acls.enabled\": false,\n  \"sync.topic.configs.enabled\": false,\n  \"key.converter\": \"org.apache.kafka.connect.converters.ByteArrayConverter\",\n  \"value.converter\": \"org.apache.kafka.connect.converters.ByteArrayConverter\",\n  \"source.cluster.alias\": \"source-0\",\n  \"source.cluster.bootstrap.servers\": \"kafka-source-0:9092,kafka-source-1:9092\",\n  \"target.cluster.alias\": \"target-0\",\n  \"target.cluster.bootstrap.servers\": \"kafka-target-0:9092,kafka-target-1:9092\",\n  \"replication.factor\": \"1\",\n  \"replication.policy.class\": \"org.apache.kafka.connect.mirror.IdentityReplicationPolicy\",\n  \"tasks.max\": \"2\"\n}\n```\n\n\nHere I disable `sync.topic.acls.enabled` and `sync.topic.configs.enabled`. The reason was explained here: \nhttps://medium.com/@penkov.vladimir/mirrormaker2-and-topics-configuration-ee56cb77affd\n\nAll possible properties can be found here: \nhttps://cwiki.apache.org/confluence/display/KAFKA/KIP-382%3A+MirrorMaker+2.0#KIP382:MirrorMaker2.0-ConnectorConfigurationProperties\n\nNow we need to deploy these conf files to kafka connect. I wrote a simple sh script, \nwhich allows me to deploy/redeploy them by the source cluster name:\n\n`.mm2.sh`\n```shell\nCONF_FOLDER=\"mm2\"\nTARGET_HOST=\"localhost:8083\"\n\nfunction validate_cluster_files() {\n  cluster=$1\n\n  if [ -z \"$cluster\" ]; then\n    echo \"Cluster is empty\"\n    exit 1\n  fi\n\n  res=1\n  if [ ! -f \"${CONF_FOLDER}/mm2-cpc-${cluster}.json\" ]\n  then\n    echo \"Checkpoints conf is missing. Create file kafka-connect/mm2-cpc-${cluster}.json\"\n    res=2\n  fi\n  if [ ! -f \"${CONF_FOLDER}/mm2-hbc-${cluster}.json\" ]\n  then\n    echo \"Heartbeat conf is missing. Create file kafka-connect/mm2-hbc-${cluster}.json\"\n    res=2\n  fi\n  if [ ! -f \"${CONF_FOLDER}/mm2-msc-${cluster}.json\" ]\n  then\n    echo \"Source conf is missing. Create file kafka-connect/mm2-msc-${cluster}.json\"\n    res=2\n  fi\n\n  if [ $res == 2 ]\n  then\n    exit 1\n  fi\n}\n\n\nfunction deploy_cluster() {\n  cluster=$1\n\n\n  CPC=$(curl -s http://${TARGET_HOST}/connectors/mm2-cpc-${cluster}/status | jq \".connector.state\")\n  if [ \"$CPC\" == '\"RUNNING\"' ]\n  then\n    echo \"Checkpoints connector already deployed\"\n  else\n    echo \"Deploying checkpoints connector\"\n    curl -X PUT -H \"Content-Type: application/json\" \\\n      --data @${CONF_FOLDER}/mm2-cpc-${cluster}.json \\\n      -s -f http://${TARGET_HOST}/connectors/mm2-cpc-${cluster}/config \u003e\u003e /dev/null || \\\n      echo \"ERROR: Failed to deploy checkpoints connector\"\n  fi\n\n\n  HBC=$(curl -s http://${TARGET_HOST}/connectors/mm2-hbc-${cluster}/status | jq \".connector.state\")\n  if [ \"$HBC\" == '\"RUNNING\"' ]\n  then\n    echo \"Heartbeat connector already deployed\"\n  else\n    echo \"Deploying heartbeats connector\"\n    curl -X PUT -s -f -H \"Content-Type: application/json\" \\\n      --data @${CONF_FOLDER}/mm2-hbc-${cluster}.json \\\n      http://${TARGET_HOST}/connectors/mm2-hbc-${cluster}/config \u003e\u003e /dev/null || \\\n      echo \"ERROR: Failed to deploy heartbeats connector\"\n  fi\n\n\n  MSC=$(curl -s http://${TARGET_HOST}/connectors/mm2-msc-${cluster}/status | jq \".connector.state\")\n  if [ \"$MSC\" == '\"RUNNING\"' ]\n  then\n    echo \"Source connector already deployed\"\n  else\n    echo \"Deploying source connector\"\n    curl -X PUT -s -f -H \"Content-Type: application/json\" \\\n      --data @${CONF_FOLDER}/mm2-msc-${cluster}.json \\\n      http://${TARGET_HOST}/connectors/mm2-msc-${cluster}/config \u003e\u003e /dev/null || \\\n      echo \"ERROR: Failed to deploy source connector\"\n  fi\n}\n\nfunction undeploy_cluster() {\n  cluster=$1\n  curl -X DELETE -s -f http://${TARGET_HOST}/connectors/mm2-cpc-${cluster} || echo \"WARN: checkpoints connector not found\"\n  echo \"Checkpoints connector undeployed\"\n  curl -X DELETE -s -f http://${TARGET_HOST}/connectors/mm2-hbc-${cluster} || echo \"WARN: heartbeats connector not found\"\n  echo \"Heartbeats connector undeployed\"\n  curl -X DELETE -s -f http://${TARGET_HOST}/connectors/mm2-msc-${cluster} || echo \"WARN: source connector not found\"\n  echo \"Source connector undeployed\"\n\n}\n\n\n\naction=$1\ncluster=$2\ncase $action in\n\ndeploy)\n  validate_cluster_files \"$cluster\"\n  echo \"Deploying cluster $cluster\"\n  deploy_cluster \"$cluster\"\n  ;;\n\nundeploy)\n  echo \"Undeploying cluster $cluster\"\n  undeploy_cluster \"$cluster\"\n  ;;\n\nredeploy)\n  validate_cluster_files \"$cluster\"\n  echo \"Redeploying cluster $cluster\"\n  undeploy_cluster \"$cluster\"\n  deploy_cluster \"$cluster\"\n  ;;\n\nstatus)\n  # validate_cluster_files \"$cluster\"\n  CPC=$(curl -s http://${TARGET_HOST}/connectors/mm2-cpc-${cluster}/status | jq \".connector.state\")\n  HBC=$(curl -s http://${TARGET_HOST}/connectors/mm2-hbc-${cluster}/status | jq \".connector.state\")\n  MSC=$(curl -s http://${TARGET_HOST}/connectors/mm2-msc-${cluster}/status | jq \".connector.state\")\n  echo \"Checkpoint connector: $CPC\"\n  echo \"Heartbeat connector: $HBC\"\n  echo \"Source connector: $MSC\"\n  ;;\n\n*)\n  echo \"usage: mm2.sh [deploy | undeploy | redeploy | status] cluster\"\n  ;;\nesac\n```\n\n\n\nRun:\n```shell\n./mm2.sh deploy source-0\n```\nOpen kafka-ui and check, that\n1.\tKafka connect contains connectors\n2.\tTopic was replicated in target-0 cluster\n\n# Conclusion\nWe can see that the replication is working. All messages appeared in target cluster.\n\n# Links\n1. KIP-382: MirrorMaker 2.0 https://cwiki.apache.org/confluence/display/KAFKA/KIP-382%3A+MirrorMaker+2.0\n2. Apache Kafka MirrorMaker 2 (MM2) Part 1: Theory  https://www.instaclustr.com/blog/kafka-mirrormaker-2-theory/\n3. MirrorMaker 2 on Kafka Connect  \n   https://catalog.workshops.aws/msk-labs/en-US/migration/mirrormaker2/usingkafkaconnectgreaterorequal270/customreplautosync/setupmm2\n\n\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpapirosko%2Fkafka-mm2-demo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpapirosko%2Fkafka-mm2-demo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpapirosko%2Fkafka-mm2-demo/lists"}