[doik2] 5주차 - Kafka & Strimzi Operator
Published: 2023-11-12 11:05:46

# Overview
CloudNet@ 에서 운영하고 있는 DOIK (Database Operator In Kubernetes) 5주차 스터디에서 Strimzi Operator를 활용하여 Kafka을 쿠버네티스에서 운영하는 실습을 진행하고 학습한 내용을 정리하였습니다.

## Kafka 소개

Apache Kafka 란 빠르고 확장 가능한 작업을 위해 데이터 피드의 분산 스트리밍, 파이프 라이닝 및 재생을 위한 실시간 스트리밍 데이터를 처리하기 위한 목적으로 설계된 오픈 소스 분산형 게시-구독 메시징 플랫폼.

- 서버 클러스터 내에서 데이터 스트림을 레코드로 유지하는 방식으로 작동하는 브로커 기반 솔루션
- 여러 데이터 센터, 여러 서버 인스턴스에 걸쳐 (메시지) 레코드 스트림을 토픽으로 저장하여 데이터 지속성을 제공
- 토픽은 레코드 또는 메시지를 키, 값 및 타임스템프로 구성된 Immutable한 Python 객체 시퀀스로 저장한다. 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/88v475grmujlsqfv2vri.png)

서비스가 확장되면 서비스 구조가 복잡해짐
-> 통신 프로토콜의 파편화가 심해짐
-> 배포나 장애 대응이 어려워지고 유지보수가 힘듬
Kafka 는 이런 문제를 해결하기 위해 모든 데이터 흐름을 중앙집중화 시키기 위한 솔루션.

### 용어 정리

**event**
- 서비스(비즈니스)에서 어떤 일이 일어났다는 사실을 기록 (메시지)
- 데이터를 읽거나 쓸 때 이벤트의 형태로 이를 수행
- 키, 값, 타임스탬프, 메타데이터, 헤더 등이 있음.

_이벤트의 예시_
```
Event key: "Alice"
Event value: "Made a payment of $200 to Bob"
Event timestamp: "Jun. 25, 2020 at 2:06 p.m."
```

**Producers(생산자)와 Consumers(소비자)**
- 카프카에 이벤트를 게시하는 클라이언트 애플리케이션
- Consumers(소비자)는 이러한 이벤트를 읽기 및 처리하는 애플리케이션
- 생산자와 소비자는 완전히 분리되어 서로에 대해 독립적 -> 확장성

**Topics**
- 모든 이벤트는 토픽에 영구적으로 저장됨.
- 토픽은 파일 시스템의 폴더와 비슷한 개념
- 토픽은 항상 다중 생산자 및 다중 구독자를 보유
- 토픽의 이벤트는 필요한 만큼 자주 읽을 수 있으며, 기존 메시징 시스템과 달리 이벤트 소비 후 삭제되지 않음

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lfzsbkmyw0erkvzrdjbw.png)


**Partition**
- 토픽을 분산 시키기 위한 파일 시스템 구조
- 서로 다른 카프카 브로커에 있는 여러 개의 버킷에 분산되어 토픽을 저장
- 클라이언트 애플리케이션이 동시에 여러 브로커에서 데이터를 읽고 쓸 수 있기에 확장성이 높아짐.
- 새 이벤트가 토픽에 게시되면 실제로는 토픽의 파티션 중 하나에 추가됨.
- 동일한 이벤트 키를 가진 이벤트는 동일한 파티션에 기록되며, 카프카는 특정 토픽 파티션의 모든 소비자가 항상 해당 파티션의 이벤트를 기록된 순서와 정확히 동일한 순서로 읽을 수 있도록 보장

### Kafka의 특징
- 마이크로서비스 및 기타 애플리케이션이 매우 높은 처리량과 짧은 지연 시간으로 데이터를 공유할 수 있습니다.
- 메시지 순서 보장
- 데이터 저장소에서 메시지 되감기/재생을 통해 애플리케이션 상태 재구성
- 키-값 로그 사용 시 오래된 레코드를 제거하기 위한 메시지 압축
- 클러스터 구성의 수평적 확장성
- 내결함성을 제어하기 위한 데이터 복제
- 즉각적인 액세스를 위한 대용량 데이터 보존

### Kafka의 사용사례
- 이벤트 중심 아키텍처
애플리케이션 상태의 변경 사항을 이벤트 로그로 캡처하는 이벤트 소싱
- 메시지 브로커링
- 웹사이트 활동 추적
- 메트릭을 통한 운영 모니터링
- 로그 수집 및 집계
- 분산 시스템을 위한 커밋 로그
- 애플리케이션이 실시간으로 데이터에 응답할 수 있도록 스트림 처리

## Strimzi Operator 소개

Strimzi 는 Kubernetes 클러스터에서 Apache Kafka 를 실행하는 프로세스를 간소화하는 오퍼레이터

Strimzi를 통해 아래의 Kafka의 구성 요소를 쿠버네티스에 배포할 수 있도록 돕는다.
- 브로커 노드의 카프카 클러스터
- 복제된 ZooKeeper 인스턴스의 ZooKeeper 클러스터
- 외부 데이터 연결을 위한 Kafka Connect 클러스터
- 보조 클러스터에서 Kafka 클러스터를 미러링하기 위한 Kafka MirrorMaker 클러스터
- 모니터링을 위한 추가 Kafka 메트릭 데이터 추출을 위한 Kafka Exporter
- Kafka 클러스터에 HTTP 기반 요청을 하기 위한 Kafka Bridge
- 브로커 노드 간에 토픽 파티션의 균형을 재조정하는 Cruise Control

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wtjetukbqd77iljp7e84.png)

### Strimzi 로 배포되는 Kafka 구성요소

**Apache ZooKeeper**
Apache ZooKeeper는 클러스터 조정 서비스를 제공하고 브로커와 소비자의 상태를 저장하고 추적하기 때문에 Kafka의 핵심 종속 요소 ZooKeeper는 컨트롤러 선출에도 사용된다.

**Kafka Connect**
Kafka Connect는 커넥터 플러그인을 사용해 Kafka 브로커와 다른 시스템 간에 데이터를 스트리밍하기 위한 통합 툴킷. Kafka Connect는 커넥터를 사용하여 데이터를 가져오거나 내보내기 위해 데이터베이스와 같은 외부 데이터 소스 또는 대상과 Kafka를 통합하기 위한 프레임워크를 제공. 커넥터는 필요한 연결 구성을 제공하는 플러그인.

**Kafka MirrorMaker**
Kafka MirrorMaker는 데이터센터 내 또는 데이터센터 간에 두 개의 Kafka 클러스터 간에 데이터를 복제.
MirrorMaker는 소스 카프카 클러스터에서 메시지를 가져와서 대상 카프카 클러스터에 씀.

**Kafka Bridge**
HTTP 기반의 클라이언트와 Kafka 클러스터를 통합하기 위한 API를 제공

**Kafka Exporter**
주로 오프셋, 소비자 그룹, 소비자 지연 및 토픽과 관련된 데이터를 Prometheus 메트릭으로 분석하기 위해 데이터를 추출함. 소비자 지연은 파티션에 마지막으로 기록된 메시지와 현재 소비자가 해당 파티션에서 수신 중인 메시지 사이의 지연시간.

## Strimzi Operator 설치 및 Kafka 클러스터 설치

아래 명령을 통해 Strimzi Operator를 설치합니다.
```
kubectl create namespace kafka
helm repo add strimzi https://strimzi.io/charts/
helm install kafka-operator strimzi/strimzi-kafka-operator --version 0.38.0 --namespace kafka
```

아래 명령을 입력하여 Strimzi Operator 가 지원하는 Kafka 버전 확인
`kubectl describe deploy -n kafka | grep KAFKA_IMAGES: -A3`

커맨드를 통해 확인된 버전은 아래와 같다.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m4qqut6cy20goqq4twsc.png)

### Kafka 클러스터 설치
기존 Statefulsets 대신 → StrimziPodSets

Kafka를 설치한다.
- 메시지 브로커 3개
- 주키퍼 3개
- entityOperator 디플로이먼트

아래 커맨드를 통해 Kafka 클러스터를 설치한다.
```
curl -s -O https://raw.githubusercontent.com/gasida/DOIK/main/strimzi/kafka-1.yaml
kubectl apply -f kafka-1.yaml -n kafka
```

_설치과정을 모니터링_
```
2023-11-18 23:15:12 INFO  ClusterOperator:142 - Triggering periodic reconciliation for namespace kafka
2023-11-18 23:16:39 INFO  AbstractOperator:511 - Reconciliation #1(watch) Kafka(kafka/my-cluster): Kafka my-cluster in namespace kafka was ADDED
2023-11-18 23:16:39 INFO  AbstractOperator:265 - Reconciliation #1(watch) Kafka(kafka/my-cluster): Kafka my-cluster will be checked for creation or modification
2023-11-18 23:16:39 INFO  AbstractOperator:511 - Reconciliation #2(watch) Kafka(kafka/my-cluster): Kafka my-cluster in namespace kafka was MODIFIED
2023-11-18 23:16:39 INFO  CrdOperator:131 - Reconciliation #1(watch) Kafka(kafka/my-cluster): Status of Kafka my-cluster in namespace kafka has been updated
2023-11-18 23:16:39 INFO  Ca:988 - Reconciliation #1(watch) Kafka(kafka/my-cluster): Generating CA with subject=Subject(organizationName='io.strimzi', commonName='cluster-ca v0', dnsNames=[], ipAddresses=[])
2023-11-18 23:16:40 INFO  Ca:988 - Reconciliation #1(watch) Kafka(kafka/my-cluster): Generating CA with subject=Subject(organizationName='io.strimzi', commonName='clients-ca v0', dnsNames=[], ipAddresses=[])
2023-11-18 23:16:42 INFO  Ca:417 - Reconciliation #1(watch) Kafka(kafka/my-cluster): Generating certificate Subject(organizationName='io.strimzi', commonName='cluster-operator', dnsNames=[], ipAddresses=[]), signed by CA cluster-ca
2023-11-18 23:16:42 INFO  Ca:417 - Reconciliation #1(watch) Kafka(kafka/my-cluster): Generating certificate Subject(organizationName='io.strimzi', commonName='my-cluster-zookeeper', dnsNames=[my-cluster-zookeeper-client.kafka.svc, *.my-cluster-zookeeper-client.kafka.svc.cluster.local, my-cluster-zookeeper-client.kafka.svc.cluster.local, my-cluster-zookeeper-0, my-cluster-zookeeper-0.my-cluster-zookeeper-nodes.kafka.svc.cluster.local, *.my-cluster-zookeeper-nodes.kafka.svc, *.my-cluster-zookeeper-nodes.kafka.svc.cluster.local, my-cluster-zookeeper-0.my-cluster-zookeeper-nodes.kafka.svc, *.my-cluster-zookeeper-client.kafka.svc, my-cluster-zookeeper-client, my-cluster-zookeeper-client.kafka], ipAddresses=[]), signed by CA cluster-ca
2023-11-18 23:16:43 INFO  Ca:417 - Reconciliation #1(watch) Kafka(kafka/my-cluster): Generating certificate Subject(organizationName='io.strimzi', commonName='my-cluster-zookeeper', dnsNames=[my-cluster-zookeeper-client.kafka.svc, *.my-cluster-zookeeper-client.kafka.svc.cluster.local, my-cluster-zookeeper-client.kafka.svc.cluster.local, my-cluster-zookeeper-1, *.my-cluster-zookeeper-nodes.kafka.svc, *.my-cluster-zookeeper-nodes.kafka.svc.cluster.local, my-cluster-zookeeper-1.my-cluster-zookeeper-nodes.kafka.svc.cluster.local, my-cluster-zookeeper-1.my-cluster-zookeeper-nodes.kafka.svc, *.my-cluster-zookeeper-client.kafka.svc, my-cluster-zookeeper-client, my-cluster-zookeeper-client.kafka], ipAddresses=[]), signed by CA cluster-ca
2023-11-18 23:16:43 INFO  Ca:417 - Reconciliation #1(watch) Kafka(kafka/my-cluster): Generating certificate Subject(organizationName='io.strimzi', commonName='my-cluster-zookeeper', dnsNames=[my-cluster-zookeeper-client.kafka.svc, my-cluster-zookeeper-2.my-cluster-zookeeper-nodes.kafka.svc, *.my-cluster-zookeeper-client.kafka.svc.cluster.local, my-cluster-zookeeper-client.kafka.svc.cluster.local, my-cluster-zookeeper-2, my-cluster-zookeeper-2.my-cluster-zookeeper-nodes.kafka.svc.cluster.local, *.my-cluster-zookeeper-nodes.kafka.svc, *.my-cluster-zookeeper-nodes.kafka.svc.cluster.local, *.my-cluster-zookeeper-client.kafka.svc, my-cluster-zookeeper-client, my-cluster-zookeeper-client.kafka], ipAddresses=[]), signed by CA cluster-ca
2023-11-18 23:16:43 INFO  StrimziPodSetController:378 - Reconciliation #3(watch) StrimziPodSet(kafka/my-cluster-zookeeper): StrimziPodSet will be reconciled
2023-11-18 23:16:44 INFO  StrimziPodSetController:414 - Reconciliation #3(watch) StrimziPodSet(kafka/my-cluster-zookeeper): reconciled
2023-11-18 23:16:44 INFO  StrimziPodSetController:378 - Reconciliation #4(watch) StrimziPodSet(kafka/my-cluster-zookeeper): StrimziPodSet will be reconciled
2023-11-18 23:16:44 INFO  StrimziPodSetController:414 - Reconciliation #4(watch) StrimziPodSet(kafka/my-cluster-zookeeper): reconciled
2023-11-18 23:16:44 INFO  StrimziPodSetController:378 - Reconciliation #5(watch) StrimziPodSet(kafka/my-cluster-zookeeper): StrimziPodSet will be reconciled
2023-11-18 23:16:44 INFO  StrimziPodSetController:414 - Reconciliation #5(watch) StrimziPodSet(kafka/my-cluster-zookeeper): reconciled
2023-11-18 23:17:12 INFO  ClusterOperator:142 - Triggering periodic reconciliation for namespace kafka
2023-11-18 23:17:29 INFO  StrimziPodSetController:378 - Reconciliation #7(watch) StrimziPodSet(kafka/my-cluster-zookeeper): StrimziPodSet will be reconciled
2023-11-18 23:17:39 INFO  AbstractOperator:400 - Reconciliation #1(watch) Kafka(kafka/my-cluster): Reconciliation is in progress
2023-11-18 23:17:46 INFO  StrimziPodSetController:378 - Reconciliation #13(watch) StrimziPodSet(kafka/my-cluster-zookeeper): StrimziPodSet will be reconciled
```

아래 커맨드를 통해 어떤 리소스가 설치되었는지 확인한다.
`watch kubectl get kafka,strimzipodsets,pod,svc,endpointslice,pvc -n kafka`

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/450gjbxz5focj0sc2zcc.png)

Strimzi 는 Kafka 브로커와 Zookeeper를 strimzipodsets라는 리소스 유형으로 설치한다.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7dvro8rv9xfqu7ushr09.png)

테스트를 위한 클라이언트 배포
```
curl -s -O https://raw.githubusercontent.com/gasida/DOIK/main/strimzi/myclient.yaml
VERSION=3.6 envsubst < myclient.yaml | kubectl apply -f -
```

배포된 클라이언트에 Kafka tools 이 설치되어 있는지 확인

`kubectl exec -it ds/myclient -- ls /opt/bitnami/kafka/bin`

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lm4yvkgpeckviea3995k.png)

브로커 정보를 확인
`kubectl exec -it ds/myclient -- kafka-broker-api-versions.sh --bootstrap-server $SVCDNS`

```
my-cluster-kafka-2.my-cluster-kafka-brokers.kafka.svc:9092 (id: 2 rack: null) -> ()

my-cluster-kafka-0.my-cluster-kafka-brokers.kafka.svc:9092 (id: 0 rack: null) -> ()

my-cluster-kafka-1.my-cluster-kafka-brokers.kafka.svc:9092 (id: 1 rack: null) -> ()
```

현재 토픽 리스트 확인
`kubectl exec -it ds/myclient -- kafka-topics.sh --bootstrap-server $SVCDNS --list`

```
__consumer_offsets
__strimzi-topic-operator-kstreams-topic-store-changelog
__strimzi_store_topic
```

토픽 리스트를 확인하려면 kafka 클라이언트의 툴을 통해서 확인해야하지만, Strimzi 오퍼레이터는 쿠버네티스에 커스텀리소스를 생성하여 아래와 같은 커맨드로 토픽을 조회할 수 있음.
`kubectl get kafkatopics -n kafka`

```
NAME                                                                                               CLUSTER      PARTITIONS   REPLICATION FACTOR   READY
consumer-offsets---84e7a678d08f4bd226872e5cdd4eb527fadc1c6a                                        my-cluster   50           3                    True
strimzi-store-topic---effb8e3e057afce1ecf67c3f5d8e4e3ff177fc55                                     my-cluster   1            3                    True
strimzi-topic-operator-kstreams-topic-store-changelog---b75e702040b99be8a9263134de3507fc0cc4017b   my-cluster   1            3                    True
```

### Kafka 모니터링 (UI, Grafana)

```
helm repo add kafka-ui https://provectus.github.io/kafka-ui-charts
cat <<EOF > kafkaui-values.yml
yamlApplicationConfig:
  kafka:
    clusters:
      - name: yaml
        bootstrapServers: my-cluster-kafka-bootstrap.kafka.svc:9092
  auth:
    type: disabled
  management:
    health:
      ldap:
        enabled: false
EOF
helm install kafka-ui kafka-ui/kafka-ui -f kafkaui-values.yml

kubectl patch svc kafka-ui -p '{"spec":{"type":"LoadBalancer"}}'
kubectl annotate service kafka-ui "external-dns.alpha.kubernetes.io/hostname=kafka-ui.$MyDomain"
echo -e "kafka-ui Web URL = http://kafka-ui.$MyDomain"
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q6f1x59abb2mcj257v0f.png)

프로메테우스와 그라파나 설치하기

```
git clone https://github.com/AmarendraSingh88/kafka-on-kubernetes.git
cd kafka-on-kubernetes/kafka-demo/demo3-monitoring/

kubectl apply -f prometheus-operator-deployment.yaml -n monitoring --server-side
kubectl apply -f prometheus.yaml -n monitoring
kubectl apply -f prometheus-rules.yaml -n monitoring
kubectl apply -f strimzi-pod-monitor.yaml -n monitoring
kubectl apply -f grafana/grafana.yaml -n monitoring
kubectl patch svc -n monitoring grafana -p '{"spec":{"type":"LoadBalancer"}}'
kubectl annotate service grafana -n monitoring "external-dns.alpha.kubernetes.io/hostname=grafana.$MyDomain"
```

#### 실습! - 토픽 생성 및 메시지 주고 받기

테스트를 위한 토픽 만들기
```
curl -s -O https://raw.githubusercontent.com/gasida/DOIK/main/3/mytopic.yaml
cat mytopic.yaml | yh
TOPICNAME=mytopic1 envsubst < mytopic.yaml | kubectl apply -f - -n kafka
```
Kafka UI를 통해 mytopic1 이라는 토픽이 생성되는 것을 확인

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g6wvxwvh6iszb2i9q27u.png)

테스트를 위한 새로운 토픽 생성

```
kubectl exec -it ds/myclient -- kafka-topics.sh --create --bootstrap-server $SVCDNS --topic mytopic2 --partitions 1 --replication-factor 3 --config retention.ms=172800000
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xc6767rnfvveoggo2xl1.png)


메시지 테스트를 위해 Kafka UI에서 라이브모드로 전환

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/roc3n9palen8kti825dj.png)


메시지 생성을 위해서는 Producer 애플리케이션을 사용해야하는데
Kafka 클라이언트 내에 kafka-console-producer.sh 라는 스크립트를 통해 콘솔 입력을 메시지로 Kafka에 보낼 수있다.

```
kubectl exec -it ds/myclient -- kafka-console-producer.sh --bootstrap-server $SVCDNS --topic mytopic1
```

이와 같이 kafka-console-producer.sh를 실행하고 콘솔에 입력하면
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e5p33j3kse1qukcocwnp.png)

Kafka UI를 통해 생성되는 메시지를 확인할 수 있다.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/58kvv41fwoajd10xtlrl.png)











#### 실습! - 장애 발생: Kafka, Zookeeper 파드 삭제

우선 아래 커맨드를 통해 모니터링을 걸어 둔다.
`watch -d kubectl get pod -owide -n kafka`

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hainth2u40nx37cfr7a8.png)

장애 테스트를 위한 실습 토픽 생성
`TOPICNAME=mytopic3 envsubst < mytopic.yaml | kubectl apply -f - -n kafka`

아래 커맨드를 입력하여 pod 상태를 확인합니다.
```
kubectl get pod -n kafka -l app.kubernetes.io/name=kafka -owide
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hnwv0ya8iohjdyjtx50v.png)


```
NODE1IP=$(kubectl get node -owide | grep 192.168.1 | awk '{print $6}')
NODEPORT=$(kubectl get svc -n kafka my-cluster-kafka-external-bootstrap -o jsonpath={.spec.ports[0].nodePort})
```

테스트 데이터를 삽입한다.
```
for ((i=1; i<=100;  i++)); do echo "failover-test1-$i" ; kubectl exec -it ds/myclient -- sh -c "echo test1-$i | kafka-console-producer.sh --bootstrap-server $SVCDNS --topic mytopic3" ; date ; done
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lj5yyvgx1ie14myt6xve.png)

카프카 브로커 서버 Pod를 삭제
```
kubectl annotate pod -n kafka my-cluster-kafka-0 strimzi.io/delete-pod-and-pvc=true && kubectl get pv -w
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ja2qyb7033rb883pamvo.png)

주키퍼 Pod를 삭제

```
kubectl annotate pod -n kafka my-cluster-zookeeper-0 strimzi.io/delete-pod-and-pvc=true && kubectl get pv -w
```

브로커서버, 주키퍼가 삭제되었어도
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vqdbt6rsml2w22fcy1mj.png)

정상적으로 메시지가 들어오는 것을 확인할 수 있다.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8qvlilvy858chpsncw0t.png)

#### 실습! - 장애 발생: 토픽 리더 파드가 있는 노드 Drain

카프카가 배포되어 있는 노드를 확인한다
`kubectl get pod -owide -n kafka  | grep kafka`

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0broaf5wnctqiy0apfaq.png)

테스트 메시지를 생성한다.
```
for ((i=1; i<=100;  i++)); do echo "failover-test2-$i" ; kubectl exec -it ds/myclient -- sh -c "echo test2-$i | kafka-console-producer.sh --bootstrap-server $SVCDNS --topic mytopic3" ; date ; done
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r1fdaqstgbhqqypns76x.png)

토픽 리더가 있는 노드를 Drain 한다.
```
NODE=ip-192-168-2-16.ap-northeast-2.compute.internal
kubectl drain $NODE --delete-emptydir-data --force --ignore-daemonsets && kubectl get node -w
```

노드가 Drain 됨에 따라 메시지브로커와 주키퍼 Pod가 삭제됨

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7ukzd62bepluleu95inl.png)

mytopic3 토픽에 메시지가 계속 적재되는 것을 확인할 수 있다.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jzprhgzkcpzns0vbjuad.png)

추가로 토픽 리더 선출에는 장애가 없음을 확인할 수 있다.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i6vg3n96kzce9fag52ni.png)

## Keda 설정하기

**Keda 란?**
Keda 는 Kubernetes Event-Drived Autoscaling 으로
기존의 쿠버네티스 오토스케일러의 단점을 보완하기 위해 고안된 솔루션이다.
CPU/MEM 사용률 외에 외부 지표 가령 Queue 사이즈, Request Rate 등에 기반하여 Pod의 개수를 스케일링한다.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b7xrg694htpctjiadnno.png)

Keda는 다양한 소스로 이벤트를 받아 Pod를 Autoscaling

**Agent**
이벤트 유무에 따라 애플리케이션 Deployment 를 Activate/Deactivate 시키는 역할을 수행한다.

**Metrics**
다양한 이벤트를 제공하는 메트릭서버 역할을 수행

**Admission Webhooks**
KEDA 관련 자원들의 변경시 Validate를 자동으로 수행하는 역할을 한다.

**동작 방식**
사용자가 Keda에 관리를 위한 ScaledObject 리소스를 생성하면, Admission 에 의해 검수되고, 유효한 자원일 경우 ScaledObject 와 Keda가 관리하는 HPA자원이 자동으로 생성된다.
이때 HPA 자원의 메트릭 참조 지표는 쿠버네티스의 메트릭 서버가 아닌 keda-metrics-apiserver 를 참조하도록 설정됨.

### Keda 설치하기

```
kubectl create namespace keda
helm repo add kedacore https://kedacore.github.io/charts
helm install keda kedacore/keda --version 2.12.0 --namespace keda
```

아래 커맨드를 통해 어떤 리소스들이 설치되어있는지 확인해본다.
`kubectl get all -n keda`

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z7odtun3tffii8a5yrfk.png)

- Keda-Operator
- Keda-Metrics-Apiserver
- Keda-Admission-Webhooks

실습용 컨슈머 애플리케이션를 배포한다.
```
curl -s -O https://raw.githubusercontent.com/gasida/DOIK/main/3/keda-deploy-svc.yaml
cat keda-deploy-svc.yaml | yh
kubectl apply -f keda-deploy-svc.yaml
```

keda 로 오토스케일링을 위한 ScaledObject 리소스를 배포한다

```
curl -s -O https://raw.githubusercontent.com/gasida/DOIK/main/3/keda-scale.yaml
kubectl apply -f keda-scale.yaml
```

```
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: consumer-scaler
  namespace: keda
spec:
  scaleTargetRef:
    name: consumer
  pollingInterval: 1
  cooldownPeriod: 10
  minReplicaCount: 1
  maxReplicaCount: 10
  triggers:
    - type: kafka
      metadata:
        topic: my-topic
        bootstrapServers: my-cluster-kafka-bootstrap.kafka.svc:9092
        consumerGroup: keda-consumer
        lagThreshold: "1"
        offsetResetPolicy: earliest
        allowIdleConsumers: "1"
```

실습용 애플리케이션을 배포하면 my-topic 이라는 토픽도 생성되는데, Kafka UI에서 확인하고, 메시지 모드 전환

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oqwmukp66rojafr2oz9g.png)


`watch 'kubectl get ScaledObject,hpa,pod -n keda'`
커맨드를 통해 모니터링

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/prkctzjk7ykuzcnhn6ql.png)

데이터 삽입
```
for ((i=1; i<=100;  i++)); do echo "keda-scale-test-$i" ; kubectl exec -it ds/myclient -- sh -c "echo test1-$i | kafka-console-producer.sh --bootstrap-server $SVCDNS --topic my-topic" ; date ; done
```

my-topic 토픽에 메시지가 쌓임에 따라 컨슈머 클라이언트가 스케일 아웃된다.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/09iw7mt98z598u76ybn9.png)

Replica 2로 증가

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wkmkeltieogjoe702a4v.png)

Replica 4로 증가

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f8v1gd1b3r4g48tteet1.png)

Replica 6으로 증가

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xkfu1cbspmaw85eg96mf.png)


my-topic 에 들어온 메시지를 처리하기 위해 컨슈머 클라이언트가 keda에 의해 스케일아웃 되는 것을 확인할 수 있다.


**References**
https://devocean.sk.com/blog/techBoardDetail.do?ID=164800