Kafka Streams 202 – Dockeryzacja aplikacji, czyli Kafka w kontenerze

Obrazy Dockera są łatwe w obsłudze. Nie musimy instalować konkretnej wersji środowiska, bibliotek i innych zależności. Wszystko powinno być zamknięte w abstrakcji zwanej kontenerem. Możemy je uruchamiać i skalować w Docker Swarm lub Kubernetes. W tym wpisie zajmiemy się dockeryzacją aplikacji Kafka Streams na przykładzie strumienia dla lokalizacji autobusów ZTM przedstawionym w poprzednim wpisie.

Zmiany w pom.xml

W pierwotnej wersji projektu utworzenie paczki komendą mvn package generowało thin jar-a, czyli takiego, którego użycie wymagało dostarczenia zależności. Wygodniej w naszym przypadku utworzyć fat jar, aby kompletna aplikacja znajdowała się w jednym pliku.

...
<build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>3.1.1</version>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
                <executions>
                    <execution>
                        <id>assemble-all</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
...

Zmiany w kodzie aplikacji Kafka Streams

W aplikacji są zahardkodowane nazwy topic-ów, id aplikacji i adres brokera kafki. Aby uniknąć kompilowania nowej wersji przy każdej zmianie, utworzymy mechanizm oparty o zmienne środowiskowe. Jest to dość często spotykany sposób konfigurowana kontenerów w Dockerze.

Utworzyłem klasę EnvTools, która zwraca zmienną lub podaną wartość domyślną. Trochę brakuje mi operatora ?? dostępnego w C# 😉 . Aby uniknąć literówek, utworzyłem statyczne stringi z kluczami.

public class EnvTools {
    public static final String INPUT_TOPIC = "INPUT_TOPIC";
    public static final String OUTPUT_TOPIC = "OUTPUT_TOPIC";
    public static final String APPLICATION_ID_CONFIG = "APPLICATION_ID_CONFIG";
    public static final String BOOTSTRAP_SERVERS_CONFIG = "BOOTSTRAP_SERVERS_CONFIG";

    public static String getEnvValue(String environmentKey, String defaultValue)
    {
        String envValue = System.getenv(environmentKey);
        if(envValue != null && !envValue.isEmpty())
        {
            return envValue;
        }
        return defaultValue;
    }
}

Teraz gdy będę chciał zmienić jakiś parametr, wystarczy zmienna środowiskowa.

...
    public ZtmStream() {
        inputTopic = EnvTools.getEnvValue(EnvTools.INPUT_TOPIC, "ztm-input");
        outputTopic = EnvTools.getEnvValue(EnvTools.OUTPUT_TOPIC, "ztm-output");
    }
...
private static Properties createProperties() {
        Properties props = new Properties();
        String appIdConfig = EnvTools.getEnvValue(EnvTools.APPLICATION_ID_CONFIG, "wiaderko-ztm-stream");
        String bootstrapServersConfig =  EnvTools.getEnvValue(EnvTools.BOOTSTRAP_SERVERS_CONFIG, "localhost:29092");
        props.put(StreamsConfig.APPLICATION_ID_CONFIG, appIdConfig);
        props.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServersConfig);
...

Dockerfile

Dockerfile to plik zawierający kroki/instrukcje wymagane do utworzenia obrazu dockera. Przyjrzyjmy mu się.

FROM maven:3-openjdk-8-slim AS BUILD
COPY pom.xml /tmp/
COPY src /tmp/src/
WORKDIR /tmp/
RUN mvn package

FROM openjdk:8-alpine AS RUNTIME
RUN apk update && apk add --no-cache libc6-compat
RUN ln -s /lib64/ld-linux-x86-64.so.2 /lib/ld-linux-x86-64.so.2
WORKDIR /app/
COPY --from=BUILD /tmp/target/wiadrodanych-kafka-streams-*-jar-with-dependencies.jar .
ENTRYPOINT ["java", "-cp", "*", "wiadrodanych.streams.ZtmStream"]

Pierwsza część odpowiada za zbudowanie aplikacji. Projekt wykrzystuje maven i jest napisany pod Javę 8, stąd podstawą jest maven:3-openjdk-8-slim. Kopiowane są koniecznie pliki, a następnie uruchamiana jest komenda mvn package, która tworzy plik .jar

Druga część jest to odchudzony kontener z openjdk w wersji 8. W momencie uruchamiania aplikacji nie potrzebujemy mavena, dlatego możemy zaoszczędzić na wielkości kontenera używając innej bazy pod kontener.

Wersja alpine nie posiada ld-linux-x86-64.so.2, przez co kafka streams się nie uruchomi, dlatego wykonywana jest instalacja paczki i pewien trick z linkiem. Następnie kopiowany jest utworzony wcześniej jar z poprzedniej warstwy. Kontener po odpaleniu wykona komendę java -cp * wiadrodanych.streams.ZtmStream, czyli załaduje wszystko, co znajdzie w aktualnym folderze i uruchomi klasę ZtmStream.

Zbudowanie obrazu Docker

Znajdując się w folderze gdzie jest plik Dockerfile

docker build -t ztm_stream .

Gdy zobaczymy Successfully tagged ztm_stream:latest to jesteśmy w domu. Obraz ma 133 MB, z czego czysty openjdk 105 MB. Wytrwawny wielorybiarz pewnie by coś jeszcze z tego przyciął :-).

Docker-Compose

Wrzućmy aplikację i kafkę wszystko docker-compose.yml i włączmy docker-compose up. Weź pod uwagę zmienne środowiskowe dla ztm_kafka_streams.

version: '3.3'
services:
    zookeeper:
        image: 'bitnami/zookeeper:3'
        ports:
            - '2181:2181'
        volumes:
            - 'zookeeper_data:/bitnami'
        environment:
            - ALLOW_ANONYMOUS_LOGIN=yes
    kafka:
        image: 'bitnami/kafka:2'
        ports:
            - '9092:9092'
            - '29092:29092'
        volumes:
            - 'kafka_data:/bitnami'
        environment:
            - KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181
            - ALLOW_PLAINTEXT_LISTENER=yes
            - KAFKA_CFG_LISTENERS=PLAINTEXT://:9092,PLAINTEXT_HOST://:29092
            - KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
            - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://kafka:9092,PLAINTEXT_HOST://localhost:29092
        depends_on:
            - zookeeper
    ztm_kafka_streams:
        image: "ztm_stream"
        environment:
          - APPLICATION_ID_CONFIG=awesome_overrided_ztm_stream_app_id
          - BOOTSTRAP_SERVERS_CONFIG=kafka:9092
        depends_on:
          - kafka
volumes:
    zookeeper_data:
        driver: local
    kafka_data:
        driver: local

Działanie

Jak widać poniżej, działa :-). Skrypt python, którym wrzucam rekordy, jest tym samym, którego użyłem w poprzednim wpisie.

Repozytorium

https://github.com/zorteran/wiadro-danych-kafka-streams/tree/kafka_streams_202

Branch: kafka_streams_202

Jeden komentarz do “Kafka Streams 202 – Dockeryzacja aplikacji, czyli Kafka w kontenerze”

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *