개요

현재 회사는 Strimzi를 활용하여 Kubernetes 환경에서 Kafka Cluster를 성공적으로 구축하여 상용 서버 및 개발 서버에서 운영하고 있습니다. 그러나 팀원 다 같이 사용하는 실제 운영 환경에서 카프카 설정을 변경하는 테스트나 다양한 실험을 진행하기에는 제약이 따르게 됩니다. 공유된 리소스 상에서의 실험은 다른 팀원들에게 영향을 줄 수 있으므로, 실제 작업 환경에서의 실험은 신중히 이루어져야 합니다. 이러한 상황에서는 개인 Kafka Cluster를 구축하여 필요한 테스트나 실험을 자유롭게 진행할 수 있는 환경이 필요하다는 것을 느끼게 되는데요. 이번 문서에서는 바로 그러한 필요성으로 인하여 Docker Compose를 사용하여 개인 Kafka Cluster를 구축하는 방법에 대해서 간단하게 정리하였습니다.
컨테이너 기술을 활용하여 Kafka Cluster를 구축한 경우 관리 및 유지보수의 효율성이  향상됩니다. 특히 컨테이너를 사용하면 Kafka Cluster의 삭제 및 재생성 작업을 매우 간단하고 빠르게 수행할 수 있으며, 컨테이너화된 환경이 제공하는 격리와 독립성 덕분에 컨테이너에서 발생하는 변경사항이 다른 컨테이너에 영향을 주지 않습니다. 따라서, Kafka Cluster를 구성하는 개별 컨테이너들을 쉽게 종료하고, 필요에 따라 새로운 설정으로 다시 시작할 수 있습니다. 이러한 유연성은 개발과 테스트 과정에서 특히 유용하며, 다양한 설정과 구성을 실험하고 최적화하는데 아주 편리합니다. (이번 실습에서는 Kafka Cluster를 구축하는 것을 목표로 하고 있기 때문에 docker-compose 및 docker 사용법에 대해서는 자세히 다루지 않습니다.)

 

이번 실습 목표

이번 실습의 목표는 개인 컴퓨터에 docker-compose를 이용하여 개인 전용 개발환경 kraft mode인 Kafka Cluster를 구축하는 것이기 때문에 최소한의 자원으로 Kafka Cluster를 구축하는 것을 목표로 하고 있습니다. (kraft mode는 zookeeper를 제거한 kafka cluster 입니다.)

  • kafka broker : 3대
  • kafka ui : 1대

docker-compose.yml 파일 작성

작업하는 위치에 다음과 같이 docker-compose.yml 파일을 생성합니다.

networks:
  kafka_network:

volumes:
  Kafka00:
  Kafka01:
  Kafka02:

services:
  ### Kafka00
  kafka00:
    image: bitnami/kafka:3.7.0
    restart: unless-stopped
    container_name: kafka00
    ports:
      - '10000:9094'
    environment:
      - KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE=true
      # KRaft settings
      - KAFKA_CFG_BROKER_ID=0
      - KAFKA_CFG_NODE_ID=0
      - KAFKA_KRAFT_CLUSTER_ID=HsDBs9l6UUmQq7Y5E6bNlw
      - KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=0@kafka00:9093,1@kafka01:9093,2@kafka02:9093
      - KAFKA_CFG_PROCESS_ROLES=controller,broker
      # Listeners
      - ALLOW_PLAINTEXT_LISTENER=yes
      - KAFKA_CFG_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093,EXTERNAL://:9094
      - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://kafka00:9092,EXTERNAL://127.0.0.1:10000
      - KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,EXTERNAL:PLAINTEXT,PLAINTEXT:PLAINTEXT
      - KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER
      - KAFKA_CFG_INTER_BROKER_LISTENER_NAME=PLAINTEXT
      # Clustering
      - KAFKA_CFG_OFFSETS_TOPIC_REPLICATION_FACTOR=3
      - KAFKA_CFG_TRANSACTION_STATE_LOG_REPLICATION_FACTOR=3
      - KAFKA_CFG_TRANSACTION_STATE_LOG_MIN_ISR=2
    networks:
      - kafka_network
    volumes:
      - "Kafka00:/bitnami/kafka"
  ### Kafka01
  kafka01:
    image: bitnami/kafka:3.7.0
    restart: unless-stopped
    container_name: kafka01
    ports:
      - '10001:9094'
    environment:
      - KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE=true
      # KRaft settings
      - KAFKA_CFG_BROKER_ID=1
      - KAFKA_CFG_NODE_ID=1
      - KAFKA_KRAFT_CLUSTER_ID=HsDBs9l6UUmQq7Y5E6bNlw
      - KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=0@kafka00:9093,1@kafka01:9093,2@kafka02:9093
      - KAFKA_CFG_PROCESS_ROLES=controller,broker
      # Listeners
      - ALLOW_PLAINTEXT_LISTENER=yes
      - KAFKA_CFG_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093,EXTERNAL://:9094
      - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://kafka01:9092,EXTERNAL://127.0.0.1:10001
      - KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,EXTERNAL:PLAINTEXT,PLAINTEXT:PLAINTEXT
      - KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER
      - KAFKA_CFG_INTER_BROKER_LISTENER_NAME=PLAINTEXT
      # Clustering
      - KAFKA_CFG_OFFSETS_TOPIC_REPLICATION_FACTOR=3
      - KAFKA_CFG_TRANSACTION_STATE_LOG_REPLICATION_FACTOR=3
      - KAFKA_CFG_TRANSACTION_STATE_LOG_MIN_ISR=2
    networks:
      - kafka_network
    volumes:
      - "Kafka01:/bitnami/kafka"
  ## Kafka02
  kafka02:
    image: bitnami/kafka:3.7.0
    restart: unless-stopped
    container_name: kafka02
    ports:
      - '10002:9094'
    environment:
      - KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE=true
      # KRaft settings
      - KAFKA_CFG_BROKER_ID=2
      - KAFKA_CFG_NODE_ID=2
      - KAFKA_KRAFT_CLUSTER_ID=HsDBs9l6UUmQq7Y5E6bNlw
      - KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=0@kafka00:9093,1@kafka01:9093,2@kafka02:9093
      - KAFKA_CFG_PROCESS_ROLES=controller,broker
      # Listeners
      - ALLOW_PLAINTEXT_LISTENER=yes
      - KAFKA_CFG_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093,EXTERNAL://:9094
      - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://kafka02:9092,EXTERNAL://127.0.0.1:10002
      - KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,EXTERNAL:PLAINTEXT,PLAINTEXT:PLAINTEXT
      - KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER
      - KAFKA_CFG_INTER_BROKER_LISTENER_NAME=PLAINTEXT
      # Clustering
      - KAFKA_CFG_OFFSETS_TOPIC_REPLICATION_FACTOR=3
      - KAFKA_CFG_TRANSACTION_STATE_LOG_REPLICATION_FACTOR=3
      - KAFKA_CFG_TRANSACTION_STATE_LOG_MIN_ISR=2
    networks:
      - kafka_network
    volumes:
      - "Kafka02:/bitnami/kafka"

  kafka-ui:
    image: provectuslabs/kafka-ui:latest
    restart: unless-stopped
    container_name: kafka-ui
    ports:
      - '8080:8080'
    environment:
      - KAFKA_CLUSTERS_0_NAME=Local-Kraft-Cluster
      - KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS=kafka00:9092,kafka01:9092,kafka02:9092
      - DYNAMIC_CONFIG_ENABLED=true
      - KAFKA_CLUSTERS_0_AUDIT_TOPICAUDITENABLED=true
      - KAFKA_CLUSTERS_0_AUDIT_CONSOLEAUDITENABLED=true
      #- KAFKA_CLUSTERS_0_METRICS_PORT=9999
    depends_on:
      - kafka00
      - kafka01
      - kafka02
    networks:
      - kafka_network

컨테이너 실행하기

docker-compose.yml 파일을 잘 작성하였다면 해당 디렉토리에서 다음 명령어를 통하여 컨테이너를 실행합니다.

docker-compose up -d

정상 동작 확인

이번 실습에서는 클러스터 및 토픽 현황을 파악하기 쉽게 하기 위해 kafka-ui 컨테이너를 같이 동작하게 되어있습니다. kafka-ui를 통해 현재 카프카 클러스터가 정상적으로 동작하는지 브라우저에서 확인할 수 있습니다.
http://localhost:8080/ui/clusters/local/brokers

 

참조

https://github.com/ArminShoeibi/KafkaDockerCompose/blob/main/docker-compose-cluster.yml

 

'Kafka > kafka 기본' 카테고리의 다른 글

kcat(kafkacat) 사용법  (2) 2024.03.05
Docker compose를 이용한 Kafka Cluster 구축 (with Zookeeper)  (0) 2024.03.03

컨테이너를 실행할 때, 여러 옵션을 사용하여 실행 중인 컨테이너에 리소스 제한을 설정할 수 있습니다. 이를 통해 컨테이너가 사용할 수 있는 CPU, 메모리, 디스크 공간 등과 같은 리소스 사용을 세밀하게 제어할 수 있습니다. Docker는 일반적으로 리소스를 자동으로 관리하지만, 경우에 따라 직접 리소스 제한을 설정하는 것이 필요할 수 있습니다. 이를 통해 컨테이너의 성능을 최적화하고, 다른 컨테이너나 호스트 시스템과의 리소스 경쟁을 관리할 수 있습니다. 예를 들어, CPU 제한을 설정하여 컨테이너가 특정 CPU 점유율을 초과하지 못하도록 할 수 있고, 메모리 제한을 설정하여 컨테이너가 지정된 메모리 양을 초과하지 않도록 할 수 있습니다. 이러한 리소스 제한 옵션을 사용하면 컨테이너의 안정성과 성능을 개선할 수 있습니다.

 

현재 동작하는 컨테이너의 resource 사용량 확인하기

docker stats 명령어를 사용하면 실행 중인 모든 컨테이너의 실시간 리소스 사용량을 확인할 수 있습니다.

docker stats 컨테이너아이디1, 컨테이너아이디2 ...

 

컨테이너 resource 사용 제한하기

--cpus 옵션을 사용하여 컨테이너에 할당할 CPU 코어의 개수를 지정할 수 있습니다.

 docker run -d --cpus=1 --name test-cpus nginx:latest

 

--cpu-shares 옵션은 Docker에서 CPU 리소스를 컨테이너 간에 분배하는 데 사용되는 상대적인 가중치를 설정하는 데 사용됩니다. 아래 설정은 기본값인 1024보다 낮은 가중치를 의미하므로, 다른 컨테이너에 비해 상대적으로 적은 CPU 리소스를 할당받을 수 있습니다.

docker run -d --cpu-shares=512 --name test-cpu-share nginx:latest

 

--memory 옵션을 사용하여 컨테이너에 할당할 메모리 양을 지정할 수 있습니다.

docker run -d --memory=1g --name test-memory nginx:latest

 

위에서 설정한 리소스 사용량 확인

 

기존 컨테이너 리소스 제한하기

앞에서는 컨테이너를 실행할 때 리소스를 제한하는 법에 대해서 알아봤지만 docker update를 이용하여 기존에 동작하고 있던 컨테이너에서도 리소스를 제한할 수 있습니다.

# 테스트용 컨테이너 생성
$ docker run -d --name test-nginx nginx:latest

# 테스트용 컨테이너 상태 확인
$ docker stats --no-stream test-nginx

 

docker update 명령어를 통해 리소스 제한 업데이트

# 리소스 설정 변경
$ docker update --memory-swap 1g --memory 1g test-nginx

# 테스트용 컨테이너 상태 확인
$ docker stats --no-stream test-nginx

'Docker' 카테고리의 다른 글

Docker Volume & BindMount  (0) 2023.07.25
컨테이너 HEALTHCHECK  (0) 2023.07.23

도커 컨테이너 파일 시스템의 생명 주기는 컨테이너의 생명 주기와 같습니다. 그렇기 때문에 컨테이너가 지워지면 컨테이너에서 기록한 데이터도 같이 지워지는데요. 이런 경우를 방지하기 위해 컨테이너의 데이터를 유지하고 관리할 수 있어야 합니다. 이런 기능을 제공하는 것이 대표적으로 2가지가 있는데 바로 volume과 bind mount입니다. 이번에는 volume과 bind mount에 대해서 정리하였습니다.

 

Docker volume

volume 생성하기

volume 명령어를 사용하여 직접적으로 volume을 생성하게 되면 /var/lib/docker/volumes/볼륨명으로 해당 볼륨의 디렉토리가 생성됩니다. 컨테이너는 해당 디렉토리를 컨테이너 경로로 마운트 하여 데이터를 읽고 쓰게 되면서 컨테이너가 기록하는 데이터를 유지할 수 있습니다.

$ docker volume create 볼륨명

volume 확인하기

생성된 volume을 `docker volume ls` 를 통하여 확인할 수 있습니다.

$ docker volume ls

volume 컨테이너에 마운트 하기

생성한 볼륨을 컨테이너에 마운트 합니다.

$ docker run 이미지명 --name 컨테이너명 -d -v <볼륨명>:<컨테이너 경로>

volume 삭제하기

`docker volume rm 볼륨명`을 통하여 볼륨을 삭제할 수 있는데 만약 해당 볼륨을 컨테이너가 사용 중이라면 해당 컨테이너부터 삭제하고 볼륨을 삭제해야 합니다.

$ docker volume rm 볼륨명

사용하지 않은 volume 전부 삭제하기

볼륨을 무작위로 생성하다 보면 사용하지 않는 볼륨들이 많이 존재하게 되는데 다음과 같은 명령어를 통하여 사용하지 않는 볼륨을 한 번에 삭제할 수 있습니다.

$ docker volume prune --all --force

dockerfile로 볼륨 생성하기

도커파일에서 생성한 볼륨은 호스트 경로의 /var/lib/docker/volumes/볼륨해쉬에 만들어집니다. Dockerfile로 생성한 volume은 매번 컨테이너를 생성할 때마다 무작위로 생성되기 때문에 사용할 때 주의해야 하며 나중에 사용 안 하는 볼륨을 지워줘야 한다.

FROM ubuntu:latest

# 볼륨을 마운트할 디렉토리 생성
RUN mkdir /app

# 볼륨 마운트 지정
VOLUME /app

# 컨테이너 내부의 파일 생성
RUN echo "Hello, Docker Volume!" > /app/data.txt

--volume-from 옵션 사용하기

Docker 컨테이너에게 다른 컨테이너에서 마운트 된 볼륨을 공유하도록 지시하는 명령어입니다. 이를 통해 볼륨을 한 컨테이너에서 생성하고 다른 컨테이너에서 사용할 수 있습니다.

$ docker volume create my-volume
$ docker run -d --name test-1 ubuntu:20.04 -v my-volume:/data
$ docker run -d --name test-2 ubuntu:20.04 --volume-from test-1

위 명령어를 이용하면 test-1에서 사용 중인 my-volume을 test-2에서도 사용할 수 있습니다.

 

Bind Mount

바인드 마운트는 호스트 시스템의 특정 경로를 컨테이너 내부 경로에 직접 연결을 하기 때문에 호스트와 컨테이너 간에 파일이 실시간으로 동기화할 수 있습니다. 저 같은 경우 개발을 한 애플리케이션의 로그를 확인하기 쉽게 하기 위해 로그 파일의 경로를 바인트 마운트 경로로 잡아서 사용하는 경우가 많습니다.

$ docker run -v 호스트경로:컨테이너경로 이미지:태그

또한 --mount 옵션을 이용하여 Bind Mount를 수행할 수 있는데 --mount를 이용하면 보다 섬세한 설정을 할 수 있습니다.

아래를 --mount 옵션을 이용하여 readonly 설정을 통해 컨테이너는 해당 파일 시스템에 readonly 권한으로 밖에 접근할 수 없도록 설정한 예시입니다.

$ docker run --mount type=bind,source=~/data,target=/data,readonly -d 이미지:태그

그밖에 옵션을 통해 여러가지 형식으로 마운트를 할 수 있습니다.

$ docker run --mount type=bind,source=호스트경로,target=컨테이너경로,옵션=값,옵션=값,...

 

'Docker' 카테고리의 다른 글

Docker resource 설정  (0) 2023.07.27
컨테이너 HEALTHCHECK  (0) 2023.07.23

Container 상태는 Run 상태인데 실제 내부적인 문제로 인해 Container가 정상적으로 동작하지 않는 경우가 있다. 이런 경우 Dockerfile을 이용하여 컨테이너 이미지를 빌드할 때 HEALTHCHECK 인스트럭션을 사용하여 컨테이너의 상태를 확인할 수 있습니다. 

 

HEALTHCHECK 작성 형식

HEALTHCHECK [option] CMD 명령어
  • --interval : 헬스 체크 간격을 지정합니다. default 30s
  • --timeout : 명령어의 타임아웃을 설정합니다. default 30s
  • --start-period : 컨테이너가 실행되고 헬스 체크를 시작하기까지 대기하는 시간을 지정합니다. default 0s
  • --retries : 헬스 체크 명령어 실행 실패 시 재시도 횟수를 지정합니다. default 3

 

HEALTHCHECK 예시

FROM nginx:latest
HEALTHCHECK --interval=10s --timeout=3s CMD curl -f http://localhost/ || exit 1

curl -f http://localhost/ 명령어를 통하여 10초 간격으로 3초 이내의 결과가 나타나지 않고 3번 이상 실패 시 unhealthy 하다고 판단합니다. 상태를 확인하는 방법은 "docker ps -a"를 통해 해당 컨테이너의 상태를 확인할 수 있지만 "docker inspect 컨테이너아이디"를 통하여 보다 자세한 상태를 확인할 수 있습니다. 아래는 docker inspect 명령어를 통해 확인한 결과입니다.

(생략...)
	"State": {
            "Status": "running",
            "Running": true,
            "Paused": false,
            "Restarting": false,
            "OOMKilled": false,
            "Dead": false,
            "Pid": 32813,
            "ExitCode": 0,
            "Error": "",
            "StartedAt": "2023-07-06T12:18:15.929466969Z",
            "FinishedAt": "0001-01-01T00:00:00Z",
            "Health": {
                "Status": "healthy",
                "FailingStreak": 0,
                "Log": [
                    {
                        "Start": "2023-07-06T12:18:45.930156552Z",
                        "End": "2023-07-06T12:18:46.006446885Z",
                        "ExitCode": 0,
                        "Output": "  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current\n                                 Dload  Upload   Total   Spent    Left  Speed\n\r  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0\r100   615  100   615    0     0   637k      0 --:--:-- --:--:-- --:--:--  600k\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n\u003chead\u003e\n\u003ctitle\u003eWelcome to nginx!\u003c/title\u003e\n\u003cstyle\u003e\nhtml { color-scheme: light dark; }\nbody { width: 35em; margin: 0 auto;\nfont-family: Tahoma, Verdana, Arial, sans-serif; }\n\u003c/style\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n\u003ch1\u003eWelcome to nginx!\u003c/h1\u003e\n\u003cp\u003eIf you see this page, the nginx web server is successfully installed and\nworking. Further configuration is required.\u003c/p\u003e\n\n\u003cp\u003eFor online documentation and support please refer to\n\u003ca href=\"http://nginx.org/\"\u003enginx.org\u003c/a\u003e.\u003cbr/\u003e\nCommercial support is available at\n\u003ca href=\"http://nginx.com/\"\u003enginx.com\u003c/a\u003e.\u003c/p\u003e\n\n\u003cp\u003e\u003cem\u003eThank you for using nginx.\u003c/em\u003e\u003c/p\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n"
                    }
                ]
            }
        },
(생략...)
	"Healthcheck": {
                "Test": [
                    "CMD-SHELL",
                    "curl -f http://localhost/ || exit 1"
                ],
                "Interval": 30000000000,
                "Timeout": 3000000000
            },
(생략...)

 

HEALTHCHECK의 상태 값

healthcheck의 상태 값은 3가지 종류로 나뉩니다.

  • starting : 컨테이너가 시작되고 --start-period로 정의된 대기 시간이 경과하기 전의 초기 상태입니다. 이 시간 동안 HEALTHCHECK가 수행되지 않으며, 컨테이너는 자동으로 healthy 상태로 표시됩니다.
  • healthy : HEALTHCHECK 명령이 성공적으로 실행되고 정상적인 상태를 반환한 경우입니다. 컨테이너가 정상적으로 작동하고 있다는 의미입니다.
  • unhealthy : HEALTHCHECK 명령이 실패하거나 비정상적인 상태를 반환한 경우입니다. 이는 컨테이너가 문제가 있거나 제대로 작동하지 않고 있다는 의미입니다.

healthcheck를 통하여 컨테이너의 상태를 표시할 뿐 도커는 컨테이너가 unhealthy 상태여도 컨테이너를 자동으로 재시작하는 기능을 수행하지 않습니다. 그 이유는 컨테이너를 재시작할 경우 애플레케이션이 일시적으로 드랍되면서 서비스에 문제가 발생할 수 있기 때문입니다. 도커의 입장에서 애플리케이션의 드랍으로 더 큰 문제가 발생될 수 있기 때문에 사용자에게 상태만 보고할 뿐 재시작을 수행하지 않습니다.

'Docker' 카테고리의 다른 글

Docker resource 설정  (0) 2023.07.27
Docker Volume & BindMount  (0) 2023.07.25

+ Recent posts