Set up a streams for Apache Kafka cluster with Ansible

Set up a streams for Apache Kafka cluster with Ansible

posted Originally published at developers.redhat.com 10 min read

Based on the project Apache Kafka, streams for Apache Kafka is a powerful, innovative distributed event streaming platform. Using a fully distributed architecture, a streams for Apache Kafka cluster requires you to setup and deploy numerous services, ensure they are all properly configured and, even more importantly, that each component is functional and can communicate with each other.

For all these reasons, applying an automation solution such as Red Hat Ansible Automation Platform to deploy and manage streams for Apache Kafka makes a lot of sense, especially as Ansible now has a collection dedicated for the product.

Overview

As mentioned, a typical implementation of streams for Apache Kafka includes several components, each deployed on numerous target systems. Depending on the organization of the servers, some of those components may actually be collocated.

At the core, however, there are two main parts: Zookeepers instances, often abbreviated zk, in charge of the orchestration of the cluster, and the brokers, responsible of processing the request.

Beyond those foundational elements, a streams for Apache Kafka deployment can include other services, such as Kafka Connect, to help integrate with numerous systems. We'll first tackle the deployment of the cluster itself before discussing this extra service.

Throughout this article, we will document step-by-step how to deploy each of those components using Ansible. To be thorough and easy to follow, however, we are going to first describe the setup of the Ansible controller itself to ensure the reader has all the information necessary to properly reproduce this demonstration.

Preparing the Ansible controller

Install Ansible

In Ansible lexicon, the system executing the automation tool itself is named the "controller". The machines handled by the controller are designated under the term "targets".

To set up a controller on Red Hat Enterprise Linux 9 (RHEL 9), the very first step is to enable the Ansible Automation Platform subscription on the system:

# subscription-manager repos --enable=ansible-automation-platform-2.4-for-rhel-9-x86_64-rpms

Then, Ansible can be installed simply by using dnf:

# dnf install -y ansible-core

Configure Ansible to use Ansible automation hub

As mentioned above, to smoothly set up and manage our streams for Apache Kafka cluster, we are going to leverage an Ansible collection dedicated to the product. This extension for the automation engine is available on the Ansible automation hub. This means that, to be able to connect to this repository of content, Ansible needs to be configured to utilize it (instead or on top of Ansible Galaxy). This configuration lives in the ansible.cfg configuration file:

[defaults]
...
[inventory]
...
[galaxy]
server_list = automation_hub, galaxy
[galaxy_server.galaxy]
url=https://galaxy.ansible.com/
[galaxy_server.automation_hub]
url=https://cloud.redhat.com/api/automation-hub/
auth_url=https://sso.redhat.com/auth/realms/redhat-external/protocol/openid-connect/token
token=<insert-your-token-here>

Please refer to this documentation on how to configure Ansible to Automation Hub and then execute the following command to install the Red Hat provided collection for streams for Apache Kafka.

Once you have properly configured Ansible to utilize automation hub, you can simply run the following command to install the Ansible collection for streams for Apache Kafka:

$ ansible-galaxy collection install redhat.amq_streams

Note: Execute this command as the same system user used to run the playbook later on. Indeed, another user, on the same machine, won't have access to the installed collection.

Prepare targets

For any systems managed by Ansible, SSHD must be running and a SSH pubkey, associated to root user needs to have been deployed to ensure that Ansible can connect to it. For RHEL 9 system, the required subscription for streams for Apache Kafka (and RHEL) must be enabled. Please refer to this documentation to learn the necessary steps to achieve this.

An inventory of the targets needs to be provided to Ansible. The simplest way to achieve this is using a plain inventory file, such as this one:

[all]

[brokers]
broker*.example.com

[zookeepers]
zk*.example.com

[topic_writer]
broker1.example.com

For testing purpose, you can use localhost, which also removes the need to set up SSHD (as there will be no SSH connection between Ansible and localhost):

[all]
localhost ansible_connection=local

[brokers]
localhost ansible_connection=local

[zookeepers]
localhost ansible_connection=local

[topic_writer]
localhost ansible_connection=local

Each of the components of the streams for Apache Kafka cluster used different ports, so they can be all deployed on one single system. Obviously, this is not recommended for production. It allows you, however, to run the playbooks provided in this article with only one machine (physical, virtual, or containerized).

Note: Execute twice every playbook in this demonstration to verify that they are indeed idempotent (meaning, the second time does not perform any change).

Automating the deployment of the Zookeepers instances

Now that we have a working Ansible controller and an assortment of target machines ready to be provisioned, we can start the setup of our cluster. The first step will be, obviously, to deploy the zk instances. Indeed, they are being orchestrating the cluster, and having them already running before the brokers make sense.

Thanks to the Ansible collection for streams for Apache Kafka, the playbook to set up the zk nodes is reduced to the bare minimum:

---
- name: "Ansible Playbook to install a Zookeeper ensemble with auth"
  hosts: zookeepers
  gather_facts: yes
  vars_files:
    - vars.yml
  collections:
    - redhat.amq_streams
  roles:
    - role: redhat.amq_streams.amq_streams_zookeeper

  post_tasks:
    - name: "Validate that Zookeeper deployment is functional."
      ansible.builtin.include_role:
        name: redhat.amq_streams.amq_streams_zookeeper
        tasks_from: validate.yml

Note that the host's value matches the name of the inventory's group associated with the machines responsible for running Zookeeper.

In essence, the playbook above just use the role redhat.amq_streams.amq_streams_zookeeper provided by the collection. In the post_tasks: segment, it utilizes as well that validate.yml, a handy playbook also supplied by the collection, to check that the zk instances deployed are indeed functional.

The specific configuration of the Zookeepers node is provided by the variables in the vars.yml:

---
amq_streams_common_download_dir: "/opt"

In our demonstration here, we chose to deploy the binaries and files of streams for Apache Kafka in the /opt folder, on the target system. As this particular configuration will also be used for the broker (see below), it has been placed into a separate vars.yml file. This way, this configuration can be shared with the playbook managing the broker instance, as we'll cover in the next section.

You can now run the playbooks to deploy zk on the targets belonging to the 'zookeepers' group:

$ ansible-playbook -i inventory -e @service_account.yml zookeepers.yml 

Note: KRaft can be employed as an alternative to Zookeepers for orchestration, however the Ansible collection for streams for Apache Kafka does not yet supports deployment using KRaft.

Automating the deployment of the brokers

Now that the zk nodes are ready to orchestrate our cluster, we can look into the deployment of the broker’s nodes. Here again, the Ansible collection for streams for Apache Kafka is doing the heavy lifting:

---
- name: "Ansible Playbook to install Kafka Brokers with authentication"
  hosts: brokers
  vars_files:
    - vars.yml
  vars:
    amq_streams_replication_factor: "{{ groups['brokers'] | length }}"
    amq_streams_broker_offsets_topic_replication_factor: "{{ amq_streams_replication_factor }}"
    amq_streams_broker_transaction_state_log_replication_factor: "{{ amq_streams_replication_factor }}"
    amq_streams_broker_transaction_state_log_min_isr: "{{ amq_streams_replication_factor }}"
  collections:
    - redhat.amq_streams
  roles:
    - name: amq_streams_broker
  post_tasks:
    - name: "Validate that Broker deployment is functional."
      ansible.builtin.include_role:
        name: amq_streams_broker
        tasks_from: validate.yml

Similar to the playbook above, we use the role provided by the streams for Apache Kafka collection in order to deploy the broker. As mentioned in the previous section, we also include the vars.yml to utilize the configuration shared between the zk instances and brokers.

A few variables were also added in order to configure the broker instances. In the example above, we used the number of instances to determine the value of the replication factor.

We can now run this playbook in order to set up our cluster:

$ ansible-playbook -i inventory -e @service_account.yml brokers.yml

Creating topics in the cluster

With our zk and brokers nodes completely provisioned, our cluster is now ready. For applications to be able to consume or publish events (or messages), topics must be created.

Here again, these changes to the setup are fully automated by a third playbook, also leveraging the capabilities of the Ansible collection for streams for Apache Kafka:

---
- name: "Ansible Playbook to ensure topics are created in Red Hat AMQ Streams cluster"
  hosts: topic_writer
  gather_facts: no
  vars_files:
    - vars.yml
  vars:
    amq_streams_broker_topic_to_create_name: "myTopic"
    amq_streams_broker_topics:
      - name: "{{ amq_streams_broker_topic_to_create_name }}"
        partitions: 1
        replication_factor: "{{ groups['brokers'] | length }}"
  collections:
    - middleware_automation.amq_streams

  tasks:
    - name: "Create topic: {{ amq_streams_broker_topic_to_create_name }}"
      ansible.builtin.include_role:
        name: amq_streams_broker
        tasks_from: topic/create.yml
      loop: "{{ amq_streams_broker_topics }}"
      loop_control:
        loop_var: topic
      vars:
        topic_name: "{{ topic.name }}"
        topic_partitions: "{{ topic.partitions }}"
        topic_replication_factor: "{{ topic.replication_factor }}"

  - name: "Describe topic: {{ amq_streams_broker_topic_to_create_name }}"
    ansible.builtin.include_role:
      name: amq_streams_broker
      tasks_from: topic/describe.yml
    loop: "{{ amq_streams_broker_topics }}"
    loop_control:
      loop_var: topic
    vars:
      topic_name: "{{ topic.name }}"

The target host for this playbook is all the members of the group topic_writer. As shown on the manifest above, this group only contains one of the brokers. Indeed, to create the topic for the entire cluster, any broker can be utilized and there is no need to repeat the operation on all the nodes.

Here again, the vars.yml is used in this playbook to give access the shared variables between all of them. In our demonstration, the folder sheltering the streams for Apache Kafka deployment on the target is the only common parameters.

Note: With a real life use case, there would be the most likely more common configuration between the components, however, for the sake of simplicity, our example only have this parameter shared.

While we only create one topic, we used a list and the associated for loop to demonstrate how this playbook can be used to ensure the availability of as many topics as needed.

Let's run this playbook to create our topic inside the cluster. We don't need to pass our service account credentials anymore, as this playbook mustn't perform any product installation and thus does not require to access the Red Hat Customer portal to retrieve the necessary zips:

$ ansible-playbook -i inventory topics.yml -e amq_streams_common_download_dir=/opt

Note that we need to specify where streams for Apache Kafka is located on the target system, because this playbook has not performed the installation; thus it does not know where the streams for Apache Kafka binaries and config files lives. If the topics deployment were part of the same playbook as the setup, this would not be necessary.

Also note that this playbook, like the previous one, is idempotent: if we run it again, no change occurs on the target system as the required topics are already created inside the cluster:

$ ansible-playbook -i inventory topics.yml

Automating the deployment of Kafka Connect

With the cluster now fully functional, we can study an additional component of the rich ecosystem of Red Hat AMQ: Kafka Connect. This supplemental service will allow the cluster to interact with numerous external systems, typically a datasource of some kind, like traditional RDMS or in-memory cache. In essence, it helps data's conversion from a remote service into messages that can be processed by the cluster. Or, the other way around, transforming messages from the cluster into records persisted or manipulated by an external system.

Here again, setting up this service on the target system is made extremely easy by the Ansible collection for streams for Apache Kafka:

---
- name: "Ansible Playbook to install a Kafka Connect ensemble with auth"
  hosts: connect
  gather_facts: yes
  vars_files:
    - vars.yml
  collections:
    - redhat.amq_streams
  tasks:
    - name: "Ensure Kafka Connect is running"
      ansible.builtin.include_role:
        name: redhat.amq_streams.amq_streams_connect
  vars:
    connectors:
      - { name: "file", path: "connectors/file.yml" }
      
  post_tasks:
    - name: "Validate that Kafka Connect deployment is functional."
      ansible.builtin.include_role:
        name: redhat.amq_streams.amq_streams_connect
        tasks_from: validate.yml

The sole configuration specific to Kafka Connect is the file connector. It is needed only because Kafka Connect can be started without any connectors. In a real-life use case, it's not an issue as, obviously, such a component would not be deployed with a set of predefined connectors matching the requirement of the applications utilizing the cluster. In our demonstration, we add this connector only for the installation to be successful:

$ ansible-playbook -i inventory connect.yml -e @service_account.yml

Note: In our demonstration we install each component on the same host, which means that the Red Hat service authentication data could be omitted in the command line above, as Kafka is already available on the target. However, in a real-life scenario, Kafka Connect will most likely run on a different system and thus require those identification information to set up the service.

Summary

Thanks to both Ansible and its collection for streams for Apache Kafka, we have deployed a fully operational and ready to be used cluster, with only a set of simple playbooks. This setup works on one machine, but could be effortlessly utilized for hundreds of systems. The only change required is to modify the inventory accordingly.

From now on, managing the cluster is also made easier by Ansible. New topics can handily be added to the configuration and deployed. Advanced features, such as authentication between components, can be implemented leveraging functionalities of the streams for Apache Kafka collection. Last but not the least, Ansible can help automate the update of the cluster. It's a difficult subject that needs to be handled according to the usage made of the system, so there is no "out of the box" solution for this, however the collection and the primitives of Ansible, provides a lot assistance to manage such critical operation.

If you read this far, tweet to the author to show them you care. Tweet a Thanks

More Posts

How to use Builder design Pattern for test data generation in automation testing

Faisal khatri - Oct 14, 2024

Integrating Kafka Test Container for Local Development Environment

Amol Gote - Jun 8, 2024

Automating With Python: A Developer’s Guide

Tejas Vaij - May 14, 2024

Streamlining Workflows Task Automation with Python

Tejas Vaij - Jun 2, 2024

Creating RESTful API Using Spring Boot for The First Time

didikts - Oct 14, 2024
chevron_left