[Rocky Linux 9] 한정된 리소스 환경에서 Kubernetes 기반 Kafka 설치 및 설정하기

두 대의 노드로 구성된 홈 서버에 Kafka를 설치했더니 제한된 자원 때문에 서버가 그냥 다운되거나 Pod에 메모리 제한을 설정해도 OOMKilled 오류가 발생했습니다.

 

그래서 Pod가 아닌 컨테이너 자체의 메모리 할당을 조정하는 설정 방법을 찾게 되었습니다.

이 글에서는 Kubernetes 환경에서 Helm을 사용하여 Bitnami Kafka를 설치하고, NFS 스토리지 설정을 통해 Volume을 설정하는 방법을 설명합니다.
*Kafka는 Helm으로 쉽게 다운 가능한 Bitnami Kafka를 이용하였습니다.

 

1. 메모리 최적화 설정

한정된 메모리 환경에서 Kafka의 안정성을 유지하려면 JVM의 힙 메모리를 적절하게 제한해야 합니다.

 

이를 위해 Bitnami Kafka Helm Chart의 kafka.heapOpts를 설정하여 JVM 힙 크기를 조정할 수 있습니다.

helm install kafka bitnami/kafka \
  --set kafka.heapOpts="-Xms256M -Xmx512M" \
  --set podSecurityContext.runAsUser=1001 \
  --set podSecurityContext.fsGroup=1001 \
  --set containerSecurityContext.runAsUser=1001 \
  --set containerSecurityContext.runAsNonRoot=true

 

위 설정은 JVM이 최소 256MB, 최대 512MB의 메모리를 사용하도록 제한하여 OOMKilled 오류를 방지합니다.

  • Xms256M: JVM(Java Virtual Machine)이 시작 시 할당하는 최소 힙 메모리 크기입니다. 여기서는 256MB로 설정되어 JVM이 시작할 때 이 메모리를 즉시 예약합니다.
  • Xmx512M: JVM이 사용할 수 있는 최대 힙 메모리 크기입니다. 512MB로 설정하여 이 이상의 메모리는 할당되지 않도록 제한합니다.

Kafka는 Java와 Scala로 작성된 시스템이므로 JVM 위에서 실행되며, 이러한 설정을 통해 JVM 힙 메모리 사용량을 제어하여 서버 메모리를 효율적으로 관리할 수 있습니다.

 

2. NFS 스토리지 연결

원래는 PVC가 자동으로 생성되어야 하지만, NFS를 사용해서 그런지 Storage가 자동으로 연결되지 않았습니다.
그래서 직접 storageClass를 연결해 주어야 했습니다.

 

  1. 현재 사용 가능한 스토리지 클래스를 확인합니다.
kubectl get storageclass
  1. PVC 설정을 수정하여 storageClassName 필드에 NFS 스토리지 클래스를 추가합니다.
k edit pvc data-kafka-controller-0
k edit pvc data-kafka-controller-1
k edit pvc data-kafka-controller-2
   apiVersion: v1
   kind: PersistentVolumeClaim
   metadata:
     name: data-kafka-controller-0

     # ... 중략

   spec:
     accessModes:
       - ReadWriteOnce
     resources:
       requests:
         storage: 8Gi
     volumeMode: Filesystem
     storageClassName: nfs-storage  # 이 부분 추가: NFS 스토리지 클래스 지정

 

3. NFS 설정 및 노드 접근 허용

저의 홈서버의 경우 한정된 자원으로 인해 마스터 노드와 워커 노드 모두에서 파드가 실행될 수 있도록 설정되어 있습니다.
하지만 NFS 서버의 설정에서 마스터 노드에 떠 있는 Pod의 경우 NFS 접근이 불가능한 문제가 발생하였습니다.

 

이를 해결하기 위해 /etc/exports 파일을 확인하고 수정하여 NFS에 마스터 노드 접근을 허용했습니다.

# 파일 내용 확인
cat /etc/exports

# 연결 설정 추가
sudo vi /etc/exports

# 마스터 노드와 워커 노드에 접근 허용
/srv/nfs <마스터_노드_IP>(rw,sync,no_subtree_check,no_root_squash) <워커_노드_IP>(rw,sync,no_subtree_check,no_root_squash)

# 변경 사항 적용
sudo exportfs -ra

 

4. Kafka 포트 설정 및 Readiness Probe 문제 해결

이후 Kafka 파드가 스케줄링되었으나, Readiness probe 실패로 인해 계속 재시작을 반복하는 문제가 나타났습니다.

 Type     Reason            Age                    From               Message
  ----     ------            ----                   ----               -------
  Warning  FailedScheduling  11m                    default-scheduler  0/2 nodes are available: pod has unbound immediate PersistentVolumeClaims. preemption: 0/2 nodes are available: 2 Preemption is not helpful for scheduling..
  Warning  FailedScheduling  8m56s (x2 over 11m)    default-scheduler  0/2 nodes are available: pod has unbound immediate PersistentVolumeClaims. preemption: 0/2 nodes are available: 2 Preemption is not helpful for scheduling..
  Normal   Scheduled         8m51s                  default-scheduler  Successfully assigned kafka/kafka-controller-0 to k8s-worker01
  Normal   Pulled            8m51s                  kubelet            Container image "docker.io/bitnami/kafka:3.8.0-debian-12-r5" already present on machine
  Normal   Created           8m51s                  kubelet            Created container kafka-init
  Normal   Started           8m51s                  kubelet            Started container kafka-init
  Warning  Unhealthy         5m41s (x3 over 8m41s)  kubelet            Readiness probe failed: dial tcp 10.244.1.166:9093: connect: connection refused
  Normal   Pulled            3m55s (x4 over 8m51s)  kubelet            Container image "docker.io/bitnami/kafka:3.8.0-debian-12-r5" already present on machine
  Normal   Created           3m55s (x4 over 8m51s)  kubelet            Created container kafka
  Normal   Started           3m55s (x4 over 8m51s)  kubelet            Started container kafka
  Warning  BackOff           2m31s (x8 over 6m9s)   kubelet            Back-off restarting failed container kafka in pod kafka-controller-0_kafka(2b09863b-284b-4e9c-8bba-e292980061b4)

 

이는 Kafka 서버가 포트 9093에서 연결 요청을 처리하지 못해 발생하는 오류입니다.

 

이를 해결하기 위해 advertised.listenerslisteners 값을 설정하여 Kafka의 통신을 조정해 주었습니다.

 

helm upgrade kafka bitnami/kafka \
  --set kafka.listeners="PLAINTEXT://:9093" \
  --set kafka.advertisedListeners="PLAINTEXT://kafka-controller-0.kafka-controller-headless.kafka.svc.cluster.local:9093" \
  --set kafka.configurationOverrides.auto.create.topics.enable=true

helm upgrade kafka bitnami/kafka --set kafka.readinessProbe.port=9092

 

이 설정을 적용하면, Kafka 파드들이 정상적으로 실행되고, 아래와 같이 모두 Running 상태를 확인할 수 있습니다.

 

$ kubectl get all
NAME                     READY   STATUS    RESTARTS      AGE
pod/kafka-controller-0   1/1     Running   1 (29m ago)   3h16m
pod/kafka-controller-1   1/1     Running   1 (19m ago)   3h16m
pod/kafka-controller-2   1/1     Running   1 (15m ago)   3h16m

 

5. 인증 비활성화 설정

테스트 파드를 만들어서 Kafka에 연결을 시도하니 SASL 인증 정보 오류가 계속 발생했습니다.

2024-10-25 11:50:14,923] INFO [SocketServer listenerType=BROKER, nodeId=0] Failed authentication with /10.244.1.168 (channelId=10.244.1.167:9092-10.244.1.168:41296-7) (Unexpected Kafka request of type METADATA during SASL handshake.) (org.apache.kafka.common.network.Selector)
[2024-10-25 11:50:16,180] INFO [SocketServer listenerType=BROKER, nodeId=0] Failed authentication with /10.244.1.168 (channelId=10.244.1.167:9092-10.244.1.168:41312-8) (Unexpected Kafka request of type METADATA during SASL handshake.) (org.apache.kafka.common.network.Selector)

 

Kafka 인증을 비활성화하려면 auth.enabled=false 옵션을 사용하여 인증 요구 사항을 비활성화할 수 있다고 합니다.

 

helm upgrade kafka bitnami/kafka --set auth.enabled=false

 

이렇게 인증 불필요하게 만들려고 설정을 주어 보았지만, 인증은 계속 요구되었습니다.

 

해결을 못한 것입니다...


6. Kafka 클라이언트 테스트

다시 처음부터 돌아와 Helm으로 Kafka를 설치하고 터미널에 출력된 메시지를 확인해 보겠습니다.

 

완료 했을 때 출력된 메시지는 다음과 같았습니다.

Release "kafka" has been upgraded. Happy Helming!
NAME: kafka
LAST DEPLOYED: Fri Oct 25 21:03:04 2024
NAMESPACE: kafka
STATUS: deployed
REVISION: 6
TEST SUITE: None
NOTES:
CHART NAME: kafka
CHART VERSION: 30.1.6
APP VERSION: 3.8.0

** Please be patient while the chart is being deployed **

Kafka can be accessed by consumers via port 9092 on the following DNS name from within your cluster:

    kafka.kafka.svc.cluster.local

Each Kafka broker can be accessed by producers via port 9092 on the following DNS name(s) from within your cluster:

    kafka-controller-0.kafka-controller-headless.kafka.svc.cluster.local:9092
    kafka-controller-1.kafka-controller-headless.kafka.svc.cluster.local:9092
    kafka-controller-2.kafka-controller-headless.kafka.svc.cluster.local:9092

The CLIENT listener for Kafka client connections from within your cluster have been configured with the following security settings:
    - SASL authentication

To connect a client to your Kafka, you need to create the 'client.properties' configuration files with the content below:

security.protocol=SASL_PLAINTEXT
sasl.mechanism=SCRAM-SHA-256
sasl.jaas.config=org.apache.kafka.common.security.scram.ScramLoginModule required \
    username="user1" \
    password="$(kubectl get secret kafka-user-passwords --namespace kafka -o jsonpath='{.data.client-passwords}' | base64 -d | cut -d , -f 1)";

To create a pod that you can use as a Kafka client run the following commands:

    kubectl run kafka-client --restart='Never' --image docker.io/bitnami/kafka:3.8.0-debian-12-r5 --namespace kafka --command -- sleep infinity
    kubectl cp --namespace kafka /path/to/client.properties kafka-client:/tmp/client.properties
    kubectl exec --tty -i kafka-client --namespace kafka -- bash

    PRODUCER:
        kafka-console-producer.sh \
            --producer.config /tmp/client.properties \
            --bootstrap-server kafka.kafka.svc.cluster.local:9092 \
            --topic test

    CONSUMER:
        kafka-console-consumer.sh \
            --consumer.config /tmp/client.properties \
            --bootstrap-server kafka.kafka.svc.cluster.local:9092 \
            --topic test \
            --from-beginning

WARNING: There are "resources" sections in the chart not set. Using "resourcesPreset" is not recommended for production. For production installations, please set the following values according to your workload needs:
  - controller.resources
+info https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/

 

다시 읽어보니 설치 완료 후 테스트 방법이 모두 안내되어 있어, 이를 참고하여 다시 진행했습니다.

 

1. client.properties 파일을 생성합니다.

1.1 Secret 값 추출

kubectl get secret kafka-user-passwords --namespace kafka -o jsonpath='{.data.client-passwords}' | base64 -d | cut -d , -f 1

1.2. client.properties 파일 생성

security.protocol=SASL_PLAINTEXT
sasl.mechanism=SCRAM-SHA-256
sasl.jaas.config=org.apache.kafka.common.security.scram.ScramLoginModule required username="user1" password="<위의 명령어를 통해 나온 값 넣기>";

2. Kafka-client 파드를 만들어서 실행합니다.

kubectl run kafka-client --restart='Never' --image docker.io/bitnami/kafka:3.8.0-debian-12-r5 --namespace kafka --command -- sleep infinity
kubectl cp --namespace kafka /path/to/client.properties kafka-client:/tmp/client.properties
kubectl exec --tty -i kafka-client --namespace kafka -- bash

3. 두 개의 터미널을 이용해서 Producer와 Consumer 생성해 보겠습니다.

  • Producer 생성하기 → test라는 Topic이 생성됩니다.
kafka-console-producer.sh \
    --producer.config /tmp/client.properties \
    --bootstrap-server kafka.kafka.svc.cluster.local:9092 \
    --topic test
  • Consumer 생성하기
kafka-console-consumer.sh \
    --consumer.config /tmp/client.properties \
    --bootstrap-server kafka.kafka.svc.cluster.local:9092 \
    --topic test \
    --from-beginning

 

 

글자가 깨지는 것은 MacBook의 한글 처리 오류로 인해 발생한 현상입니다.

 

왼쪽이 Consumer, 오른쪽이 Producer이며, 통신이 잘 되는 모습을 볼 수 있습니다.