no image
Kubernetes with Docker Desktop 환경 설정
Motivation 맥북을 개발용 컴퓨터로 두고 나니 쿠버네티스를 돌리기에는 컴퓨터 사양이 너무 부족했습니다. 그래서 몇 달 전 10만원 언저리로 쿠버네티스용 윈도우 컴퓨터를 구매 후 kubeadm 통해 가상 머신 위에서 멀티 노트 클러스터를 구축하여 이래저래 잘 사용해왔습니다만 그만 클러스터가 복구 불가능할 정도로 망가져버리고 말았습니다. virtualbox로 가상머신 3대를 통해 노드 3대를 만들었는데, 가상 머신 이미지도 맛이 가버린 상황에서 다시 처음부터 설치를 하려니 도저히 손이 안가더라구요. 이럴꺼면 잘 좀 정리해놓을껄 싶은 순간이였습니다. 그런데 여기서 생각해보니, 굳이 여러 노드로 돌려야 하는지는 의문이 들더군요. 물론 업무를 한다면 여러 노드를 돌리는게 재현성이 훨씬 좋겠지만, 제가 쿠버..
2023.06.18
no image
gchr.io를 활용한 도커 이미지 관리
Motivation Docker Repository를 Container Registry로 잘 사용하고 있었으나 무료 플랜의 경우 해당 이미지를 Private으로 설정할 수 없음. ghcr(Github Container Registry)의 경우 Private 저장소를 500MB 까지 무료로 사용 가능 Public으로 관리할 수 있는 도커 이미지의 경우, Docker Repository를 활용해도 무방하나 ssh Token 혹은 Github Access Token등의 Credential이 포함된 경우 gchr을 통해 이미지를 관리하고자 함. 도커와 gchr 연동 먼저, gchr을 이미지 저장소로 활용하기 위해 Github Access Token을 발급받는다. 이는 본인 계정의 Settings > Develo..
2023.05.22
Kafka 환경에서의 Zookeeper의 Cluster Coordination
Cluster coordination은 Zookeeper가 제공하는 기능 중의 하나로서 분산 환경을 구성하는데 핵심이 되는 기능이다. coordination은 다양한 노드가 상호작용을 통해 작업을 진행할 경우 각 노드들의 상태를 관리하고, 클러스터 전체에 걸쳐 동기화시킴으로서 작업들이 적절하게 진행될 수 있도록 만들어준다. Kafka에서는 대표적으로 다음의 작업 시에 Zookeeper의 Coordination을 활용하게 된다. Topic 생성 Broker 추가 Broker의 실패 그 중에서도 해당 글에서는 Topic의 생성에 초점을 두고 Zookeeper의 Coordination이 이뤄지는 맥락에 대해서 이야기해보고자 한다. Topic Creation 새로운 Kafka Topic이 생성되는 경우 다음의 ..
2023.05.18
Kafka와 Zookeeper의 상호작용
Apache Kafka는 실시간 데이터를 fault-tolerant한 방식으로 다룰 수 있게 도와주는 분산 스트리밍 데이터 플랫폼이다. 분산 아키텍처로서 Kafka 클러스터는 단일 머신으로 구성되기보다 여러 머신을 붙여서 사용하는 것이 일반적이다. 이때, 분산 환경을 관리하는 플랫폼인 Apache Zookeeper가 등장한다. Zookeeper와 Kafka는 다음과 같은 상호작용을 수행한다. 1. Broker Registration: Kafka 브로커가 시작되는 경우 Zookeeper에 본인을 등록한다. 해당 정보는 Zookeeper 내부의 ephemeral node 내부에 등록되며, 등록이 완료되었다면 Zookeeper를 통해 해당 브로커가 Kafka 클러스터 내부에 존재하게 된다. 2. Broker ..
2023.05.18
Zookeeper의 znode
Apache Zookeeper 내부에서 데이터는 hierarchical namespace로 관리되며, 이는 마치 OS의 파일 시스템 혹은 트리 데이터 구조처럼 표현된다. 이때, z node란 Zookeeper에서 데이터가 저장되는 단위로서, 데이터가 저장되는 트리의 노드라고 생각할 수 있겠다. znode는 두 가지 형태로 분류된다. 1. Persistent znode: 명시적으로 제거되기 전에는 계속해서 zookeeper의 데이터 트리에 남아있는 데이터 노드이다. 클라이언트(ex. kafka)가 연결을 종료하거나 예상치 못하게 세션이 만료되어도 남아있게 된다. 주요 사용 시나리오로는 클러스터의 구성 정보를 저장하거나, 시스템의 상태를 저장하는 등의 client와의 세션 연결에 종속되지 않는 데이터를 저장..
2023.05.18
[Leetcode] 1572. Matrix Diagonal Sum
🗓️ Daily LeetCoding Challenge May, Day 8 class Solution: def diagonalSum(self, mat: List[List[int]]) -> int: n = len(mat) answer = 0 for i in range(n): answer += mat[i][i] + mat[i][n - i - 1] if n % 2 != 0: answer -= mat[n // 2][n // 2] return answer
2023.05.09
[Leetcode] 34. Find First and Last Position of Element in Sorted Array
🗓️ Daily LeetCoding Challenge May, Day 7 import bisect class Solution: def longestObstacleCourseAtEachPosition(self, obstacles: List[int]) -> List[int]: lis = [] result = [] for obstacle in obstacles: idx = bisect.bisect_right(lis, obstacle) if idx == len(lis): lis.append(obstacle) else: lis[idx] = obstacle print(idx, lis, obstacle) result.append(idx+1) return result
2023.05.09
no image
[Leetcode] 1498. Number of Subsequences That Satisfy the Given Sum Condition
🗓️ Daily LeetCoding Challenge May, Day 6 class Solution: def numSubseq(self, nums: List[int], target: int) -> int: answer = 0 nums.sort() # O(NlogN) left, right = 0, len(nums) - 1 while left
2023.05.06

Motivation

맥북을 개발용 컴퓨터로 두고 나니 쿠버네티스를 돌리기에는 컴퓨터 사양이 너무 부족했습니다. 그래서 몇 달 전 10만원 언저리로 쿠버네티스용 윈도우 컴퓨터를 구매 후 kubeadm 통해 가상 머신 위에서 멀티 노트 클러스터를 구축하여 이래저래 잘 사용해왔습니다만 그만 클러스터가 복구 불가능할 정도로 망가져버리고 말았습니다.

 

virtualbox로 가상머신 3대를 통해 노드 3대를 만들었는데, 가상 머신 이미지도 맛이 가버린 상황에서 다시 처음부터 설치를 하려니 도저히 손이 안가더라구요. 이럴꺼면 잘 좀 정리해놓을껄 싶은 순간이였습니다.

 

그런데 여기서 생각해보니, 굳이 여러 노드로 돌려야 하는지는 의문이 들더군요. 물론 업무를 한다면 여러 노드를 돌리는게 재현성이 훨씬 좋겠지만, 제가 쿠버네티스를 윈도우에서 돌리는 이유는 결국 학습이니까요. 학습을 위해서라면 단일 노드 환경도 문제가 없겠다는 판단하에, minikube와 docker-desktop을 활용해야겠다는 결론을 내렸고 그 중에서 docker-desktop을 통해 상호작용이 가능한 docker-desktop 버전의 쿠버네티스를 사용해야겠다고 최종적인 방향을 정했습니다.

 

Docker Desktop 설치

먼저 Docker Desktop을 설치해야겠죠. Docker Desktop은 다음의 웹페이지에서 쉽게 설치가 가능합니다.

 

https://www.docker.com/products/docker-desktop

 

Download Docker Desktop | Docker

Docker Desktop is available to download for free on Mac, Windows, or Linux operating systems. Get started with Docker today!

www.docker.com

 

 

설치가 완료됐다면 이후 로그인 완료하시고 접속해주시면 되겠습니다.

 

Docker 실행 후 1시 방향의 톱니바퀴를 눌러주시면 Kubernetes 메뉴가 옆에 있습니다. Enable Kuberntes를 체크하고 Apply & Restart를 누르면 Kubernetes 설치가 진행됩니다.

 

 

설치하는데 생각보다 시간이 많이 걸리니 (제 경우 10분 걸렸습니다) 차분하게 완료가 될때까지 기다려주세요. 이후 정상적으로 설치가 완료되었다면 다음과 같이 쿠버네티스 아이콘이 활성화됩니다.

 

쿠버네티스 사용하기

쿠버네티스는 kubectl이라는 cli 명령어 인터페이스로 관리가 됩니다. 말 나온김에 쿠버네티스 구조를 살펴보면 다음과 같습니다.

 

출처:https://www.simplilearn.com/tutorials/kubernetes-tutorial/kubernetes-architecture

 

일반적으로 고가용성을 보장하는 쿠버네티스는 Multi-node 환경으로 구성이 되어 있습니다. 그림에서 볼 수 있는 것처럼 Master가 중앙에서 각 Worker들에게 명령을 내리는 구조이구요. 이때 kubectl은 Master에게 kubeapi server와 통신하며 본인의 요구사항을 전달하는 매개가 됩니다. 

 

쿠버네티스 구조를 설명하는 글은 아니기 때문에 해당 주제는 여기서 줄이도록 하고 계속해서 클러스터를 살펴보도록 하겠습니다.

 

간단히 몇 가지 명령어를 실행해보겠습니다.

 

# kubectl 의 버전 확인 
# Client 및 Server 의 버전을 확인할 수 있습니다. 
$> kubectl version --short
Client Version: v1.25.4
Kustomize Version: v4.5.7
Server Version: v1.25.4

# 클러스터 정보 확인 
# k8s의 컨트롤 패널의 정보를 가져올 수 있는 endpoint를 알려줍니다. 
# kubernetes.docker.internal 은 k8s 설치시 hosts 파일에 자동으로 등록된 정보입니다. 127.0.0.1 kubernetes.docker.internal
$> kubectl cluster-info
Kubernetes control plane is running at https://kubernetes.docker.internal:6443
CoreDNS is running at https://kubernetes.docker.internal:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

# 노드 정보 확인
$> kubectl get nodes
NAME             STATUS   ROLES           AGE     VERSION
docker-desktop   Ready    control-plane   9m10s   v1.25.4

 

만약 kubectl의 서버가 뜨지 않았다면, 가장 먼저 root 유저로 해당 명령어를 입력하지는 않았는지 확인해주시기 바랍니다. kubectl config 파일을 별도로 root 유저 쪽에도 설정해주지 않으셨다면, kubectl을 root 유저에서 실행해도 server에 요청이 전달이 되지 않았습니다.

 

그럼에도 문제가 된다면 Docker Desktop의 쿠버네티스가 정상적으로 설치되지 않았을 확률이 높습니다. 다양한 경우의 수가 있지만, 계속해서 문제가 된다면 대신 minikube를 활용해보시는 것을 추천드립니다. 

 

Kubernetes Dashboard 설치

물론 CLI만을 활용해서 쿠버네티스를 다루는 것도 좋지만, GUI를 통해서도 접근이 가능하다면 훨씬 훌륭하겠죠.

 

해당 글에서는 쿠버네티스 공식 문서에서 제공하는 웹 대시보드를 설치해보려고 합니다.

 

https://kubernetes.io/ko/docs/tasks/access-application-cluster/web-ui-dashboard/

 

 

쿠버네티스 대시보드를 배포하고 접속하기

웹 UI(쿠버네티스 대시보드)를 배포하고 접속한다.

kubernetes.io

 

설치는 쿠버네티스 공식 github에서 제공하는 yaml을 사용합니다.

 

kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.4.0/aio/deploy/recommended.yaml

 

설치가 완료되었다면 다음의 명령어를 통해 확인해줍니다.

 

$> kubectl get deployment -n kubernetes-dashboard
NAME                        READY   UP-TO-DATE   AVAILABLE   AGE
dashboard-metrics-scraper   1/1     1            1           44s
kubernetes-dashboard        1/1     1            1           45s

 

설치가 완료되었지만 대시보드에 접속하기 위해서는 proxy 설정이 필요합니다.

$> kubectl proxy

 

혹 쿠버네티스의 proxy가 생소하신 분들은 쿠버네티스의 공식 문서을 참조해보시는 것을 추천드립니다.

 

해당 문서에서는 다양한 쿠버네티스에서 활용되는 다양한 proxy를 설명하고 있으며, 우리가 활용하고자 하는 kubectl proxy에 대한 특징을 다음과 같이 설명합니다.

 

✅ 사용자의 데스크탑이나 파드 안에서 실행한다.
✅ 로컬 호스트 주소에서 쿠버네티스의 API 서버로 프락시한다.
✅ 클라이언트로 프락시는 HTTP를 사용한다.
✅ API 서버로 프락시는 HTTPS를 사용한다.
✅ API 서버를 찾는다.인증 헤더를 추가한다.

 

로컬 호스트의 주소를 쿠버네티스의 적절한 파드로 넘겨준다 정도로 이해하고 넘어가주시면 되겠습니다.

 

성공적으로 proxy가 동작한다면 다음의 주소로 접속해봅시다. 

 

http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/#/login

 

문제가 없다면 다음과 같이 정상적으로 접속이 가능한 것을 확인할 수 있습니다.

 

 

대시보드가 성공적으로 동작하지만, 아쉽게도 대시보드가 쿠버네티스를 자동으로 추적하지는 못합니다. 때문에 추가적인 인증 과정을 거쳐 대시보드에 쿠버네티스를 등록해주는 과정이 필요합니다.

 

해당글에서는 토큰을 기반으로 하는 인증을 수행할 예정입니다. 이러한 인증 과정은 Bearer Authentication이라고 불리기도 합니다.

 

쿠버네티스에서 Bearer 토큰을 발급하기 위해서는 다음의 과정을 거쳐야 합니다.

 

먼저, 해당 토큰을 발급할 주체인 Service Account를 생성해줍시다.

 

생성을 위해서는 아래의 텍스트를 yaml 파일로 저장합니다. 

 

apiVersion: v1
kind: ServiceAccount
metadata:
  name: dashboard-admin
  namespace: kubernetes-dashboard

 

이후 적용하여 생성합니다. 파일 이름의 경우 유동적으로 설정해주시면 됩니다.

 

$> kubectl apply -f 0.svc.yaml
serviceaccount/admin-user created

 

생성이 완료되었다면, 앞서 생성한 Service Account에 권한을 부여해줘야 합니다.

 

쿠버네티스에서는 권한을 Role로 관리해주게 되는데, 이러한 Role을 부여해주는 행위를 Role Binding이라고 합니다.

 

아래의 yaml 파일은 쿠버네티스 클러스터에 대한 권한을 Service Account에게 부여해주는 스크립트입니다. 쿠버네티스에는 기본적으로 제공하는 여러 Role이 있으며 그중에서 Cluster Role은 Cluster에 대한 권한을 의미하는 특별한 의미의 Role 입니다. 그 중에서 우리는 cluster-admin이라는 역할을 앞서 생성한 Service Account에 부여해주려고 합니다.

 

마찬가지로 파일로 먼저 생성한 이후에 적용하여 생성해줍시다.

 

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: dashboard-admin
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: dashboard-admin.yaml
  namespace: kubernetes-dashboard

 

$> kubectl apply -f 1.cluster-role-binding.yaml
clusterrolebinding.rbac.authorization.k8s.io/dashboard-admin created

 

최종적으로 다음의 명령을 통해 Bearer Token을 발급받을 수 있습니다.

 

$> kubectl -n kubernetes-dashboard create token dashboard-admin

 

토큰이 발급되었다면 콘솔창에 출력된 긴 문자를 복사 후에 아까 띄워두었던 웹페이지에 붙여넣어 줍니다. 이후 아래의 화면이 띄게 되면 모든 설정이 완료되게 됩니다. 

 

 

아직은 클러스터 내에 아무런 앱이 떠 있지 않기 때문에 볼 수 있는게 없지만 배포 이후에는 다음의 대시보드 형태를 통해 클러스터 내의 리소스를 GUI 형태로 모니터링할 수 있게 됩니다.

 

 

k9s 설치하기

마지막으로 kubectl 대신 kubenetes를 더욱 쉽게 관리하도록 도와주는 k9s를 설치하고 해당 글을 마무리하려고 합니다.

 

설치 자체는 간단합니다.

 

제가 작업하는 리눅스(wsl)의 경우에는 다음의 명령을 통해 설치합니다. 명령어가 복잡해보이지만 사실 설치하고 압축 해제하는 것 말고는 없습니다.

 

curl -s -L https://github.com/derailed/k9s/releases/download/v0.26.7/k9s_Linux_x8
6_64.tar.gz -o k9s && tar -xvf k9s && chmod 755 k9s && rm LICENSE README.md  && sudo mv k9s /usr/local/bin

 

맥에서는 더욱 간단합니다. 패키지 관리자인 brew에 이미 k9s가 포함되어 있기 때문에 바로 설치가 가능합니다.

 

brew install k9s

 

최종적으로 설치가 완료되었다면 k9s를 실행해봅시다. 주의할 점은 root가 아닌 쿠버네티스 context가 존재하는 user에서 실행해주셔야 합니다.

 

다음의 명령어를 통해 실행해주시면 아래와 같은 텍스트 기반 GUI를 확인하실 수 있습니다.

 

k9s

Motivation

 

  • Docker Repository를 Container Registry로 잘 사용하고 있었으나 무료 플랜의 경우 해당 이미지를 Private으로 설정할 수 없음.

  • ghcr(Github Container Registry)의 경우 Private 저장소를 500MB 까지 무료로 사용 가능

  • Public으로 관리할 수 있는 도커 이미지의 경우, Docker Repository를 활용해도 무방하나 ssh Token 혹은 Github Access Token등의 Credential이 포함된 경우 gchr을 통해 이미지를 관리하고자 함.

 

도커와 gchr 연동

 

먼저, gchr을 이미지 저장소로 활용하기 위해 Github Access Token을 발급받는다.

 

이는 본인 계정의 Settings > Developer Settings에서 발급이 가능하다. 이때 허용해줘야 하는 권한에는 다음이 있다.

 

  1. read:packages

  2. write:packages

  3. delete:packages

 

토큰이 발급되었다면 이를 통해 docker login 명령어를 통해 gchr.io에 로그인 해야한다.

 

이는 다음과 같이 수행이 가능하다.

 

이때 username 부분에 본인의 Github username을, Access Token Value 부분에 앞서 발급받은 키를 넣어준다.

docker login ghcr.io -u username --password "Access Token Value"



또는 다음과 같이 로그인한다.

 

echo "Access Token Value" | docker login ghcr.io -u username --password-stdin

 


로그인이 완료되었다면 이제 앞서 생성한 도커 이미지를 gchr에 업로드해보자. 먼저 해당 이미지가 gchr에 push 되기 위해서는 해당 이미지에 대한 태그를 통해 이를 표현해줘야 한다.

 

이는 다음과 같이 수행한다.

 

username에 본인의 github username을, container-name:tag에 push 하고자 하는 적절한 로컬 이미지를 입력해주면 된다.

 

이때 “ghcr.io/username/container-name:tag”에서 마지막에 위치한 container-name:tag가 github에서 볼 수 있는 해당 이미지의 이름이 된다.

 

docker tag container-name:tag ghcr.io/username/container-name:tag

 


이제 gchr에 push만을 남겨두고 있다.

 

push는 기존 Docker Registry에서 사용하는 것과 동일하되, 앞서 지정한 태그를 활용한다.

 

docker push ghcr.io/username/container-name:tag

 


업로드된 이미지는 Github Package에서 확인이 가능하다.

Cluster coordination은 Zookeeper가 제공하는 기능 중의 하나로서 분산 환경을 구성하는데 핵심이 되는 기능이다. coordination은 다양한 노드가 상호작용을 통해 작업을 진행할 경우 각 노드들의 상태를 관리하고, 클러스터 전체에 걸쳐 동기화시킴으로서 작업들이 적절하게 진행될 수 있도록 만들어준다.

Kafka에서는 대표적으로 다음의 작업 시에 Zookeeper의 Coordination을 활용하게 된다.

  • Topic 생성

  • Broker 추가

  • Broker의 실패

그 중에서도 해당 글에서는 Topic의 생성에 초점을 두고 Zookeeper의 Coordination이 이뤄지는 맥락에 대해서 이야기해보고자 한다.

Topic Creation

새로운 Kafka Topic이 생성되는 경우 다음의 작업이 필요하다.

  • 새로운 Topic이 생성되고, 해당 Topic의 구성이 어떻게 이뤄지는 지를 모든 Broker가 볼 수 있어야 한다.

  • 새로운 Topic을 담당하는 Partition들을 클러스터 내부의 Broker에 할당시켜야 한다. (이를 Partition assignment라고 한다)

  • 각 파티션을 담당하는 Leader broker가 선출되어야 한다.

Zookeeper는 이러한 작업들을 도와준다.

새로운 Topic이 생성되는 경우, 해당 Topic의 구성과 Partition assignment는 Zookeeper의 znode에 기록된다.

이처럼 Broker의 세션에 종속되지 않는 데이터는 persistent znode에 기록되어, Broker의 연결이 종료되더라도 zookeeper가 남아있다면 데이터가 유지되게 된다.

좀 더 깊게 들어가보자

Zookeeper의 znode는 트리 구조의 파일 계층처럼 데이터가 저장되는 데이터 모델이다.

각 kafka의 컨텍스트에서 znode는 /brokers/topics 라는 부모 znode 아래에서 각 Topic을 위한 데이터가 저장된다.

이때 저장되는 데이터는 Topic의 구성 정보와 partition assignment에 대한 정보이다.

샘플 Topic을 만들어보고, 해당 Topic을 통해 zookeeper의 역할을 알아보자.

먼저 여러 Broker로 구성된 클러스터라고 할 때 특정 Broker에서 Topic를 생성한다고 가정하자. 해당 Topic은 다른 모든 Broker들에게 전파되어야 한다.

이제 실제로 그런지 실습해보자.

먼저 1번 노드(kafka1)에서 Topic을 생성한다.

(현재 Docker Compose로 총 3개의 노드가 하나의 도커 네트워크에 묶여있다. 때문에 Hostname이 지정되어 있어 IP 주소 대신 Hostname이 사용되는 환경이다.)

sh-4.4$ kafka-topics.sh --create --bootstrap-server kafka1:9092 --replication-factor 1 --partitions 1 --topic sample-topic


bootstrap-server는 Kafka의 클라이언트(producer 또는 consumer)와 연결되는 최초의 Broker를 의미한다. 해당 세션 연결을 통해 클라이언트는 Kafka 클러스터에 대한 메타데이터를 획득한다

  • boostrap-server에 대한 더 자세한 설명해당 과정을 거치기 위해 클라이언트는 적어도 하나의 Broker와 연결되어야 한다. 최초의 연결이 이뤄진 후에야 비로소 클라이언트는 해당 Kafka 클러스터에 대한 메타데이터를 받아올 수 있다.

  • 클라이언트가 메타데이터를 받았다면, 해당 토픽을 처리할 준비가 완료되었다. 이후 Topic을 구성하는 Partition들을 처리하는 Leader Broker와 세션을 구성한다. 이때 bootstrap-server는 메타데이터 반환이라는 역할을 다했기 때문에 연결이 꼭 유지되어야 하는 것은 아니다. 만약 해당 Broker가 요구되는 Partition을 처리하는데 필요하지 않다면 (Leader가 아니라면), 세션은 종료될 것이다.

  • Kafka 클라이언트가 Topic을 경유하여 메세지를 보내거나 받는 경우, 먼저 해당 Kafka 클러스터 내부에 어떤 Broker들이 있는지 알아야 함과 동시에 해당 Topic의 Partition들을 처리하는 Leader Broker에 대해서 알고 있어야 한다. 이러한 정보는 각 Kafka Broker를 통해 획득되어지는데, 이는 결국 Zookeeper의 Persistant znode에 저장되어 전파되는 정보들이다.

해당 명령이 성공적이었다면 다음과 같은 결과를 얻을 수 있다.

Created topic sample-topic.


계속해서 1번 노드에서 zookeeper의 znode에 해당 Topic 정보가 저장되었는지 확인하려고 한다. 다음의 명령을 통해 zookeeper-shell에 접속한다.

sh-4.4$ zookeeper-shell zookeeper:2181


*파라미터 값으로 Zookeeper의 주소를 넣어준다.

연결이 성공적이었다면 다음의 결과를 얻는다.

Connecting to zookeeper:2181
Welcome to ZooKeeper!
JLine support is disabled

WATCHER::

WatchedEvent state:SyncConnected type:None path:null


Zookeeper shell 내부에서 ls 명령을 통해 topic이 잘 생성되었는지 확인한다.

ls /brokers/topics
[sample-topic, sample-topice]


*하나는 원래 있던 Topic으로 sample-topic의 오타이다. 삭제를 고민하다 여러 Topic이 있는 경우 전달하는 바가 좀 더 명확할 것 같아 남겨두었다.

Topic이 잘 생성되었다면, 다른 Broker로 이동하여 해당 Broker에서도 Topic이 잘 기록되어 있는지 확인해보자.

2번 Broker로 이동하여 마찬가지로 Zookeeper shell 내부에서 ls 명령어를 수행하였다.

sh-4.4$ zookeeper-shell zookeeper:2181
Connecting to zookeeper:2181
Welcome to ZooKeeper!
JLine support is disabled

WATCHER::

WatchedEvent state:SyncConnected type:None path:null
ls /brokers/topics
[sample-topic, sample-topice]


마찬가지로 동일한 Topic 목록을 확인할 수 있다. 사실 동일한 Zookeeper를 참조하므로 당연한 결과이다. 이를 통해 zookeeper의 역할을 잘 이해할 수 있으리라 생각한다.

추가적으로 zookeeper-shell 에는 해당 znode의 정보를 확인할 수 있는 get 명령어가 존재하는데 이를 통해 topic의 메타데이터에 해당하는 znode를 확인할 수 있다.

sh-4.4$ echo "get /brokers/topics/sample-topic" | zookeeper-shell zookeeper:2181


*get /brokers/topics/sample-topic을 zookeeper-shell 내부적으로 실행

성공했다면 다음과 같은 정보를 얻을 수 있다.

{"partitions":{"0":[3]},"topic_id":"UWF0HcJqT8ugeis0q8yDRQ","adding_replicas":{},"removing_replicas":{},"version":3}

 


각 필드는 다음을 의미한다.

  • partitions: Topic이 1개의 partition(”0”)을 가지고 있고, 해당 Partition에 연결된 Broker는 3번 Broker이다.

  • topic_id: 해당 Topic을 구분하는 고유한 구별자이다. Kafka는 Topic을 내부적으로 관리할 때 이를 사용한다.

  • adding_replicas/removing_replicas: 해당 필드는 Kafka의 replication factor가 증가하는 상태이거나 감소하는 상태임을 의미한다. 만약 partition에 대한 재할당이 이뤄지는 경우, 해당 필드는 재할당이 이뤄지는 partition에 대한 정보와 이와 관련된 broker의 id를 담게 될 것이다.

  • version: topic 구성 형식의 버전을 의미한다. 새로운 형태의 kafka에서는 더 높은 숫자의 version이 적힐 것이다.

  • 유사하게 znode 중에는 각 Broker의 세션 연결 상태를 관리하는 /brokers/ids 가 존재한다. host와 port 정보 그리고 해당 세션이 연결되어 있는지 여부 등이 저장된다.

replica와 partition이 추가된다면 znode에 저장된 메타데이터는?

앞선 예시는 별도의 replica와 partition을 생성하지 않아 topic의 메타데이터가 제공하는 정보가 다소 부실했다. 과연 replica와 partition이 추가된다면 메타데이터는 어떻게 변하게 될까?

이번에는 replication-factor를 2, partition을 2로 설정하여 데이터가 들어오면 2개의 partition에 나뉘어 저장되게 설정함과 동시에, 2개의 replica가 생성되도록 구성해보자. (leader가 각 partition을 담당하게 될 것이며, 각 partition은 2개의 broker에 replica를 생성할 것이다)

앞서 설명한대로 kafka-topic을 생성한다. (--replication-factor 2 --partitions 2)

kafka-topics --create --bootstrap-server kafka1:9092 --replication-factor 2 --partitions 2 --topic sample-topic-replica


해당 topic의 메타데이터를 살펴보면 다음의 결과를 얻는다.

  • partitions: 현재 0번 partition에 대해서, 2번과 1번 Broker에 replica가 생성되었다.

  • topic_id: (위의 설명 참고)

  • adding_replicas/removing_replicas: (위의 설명 참고)

  • version: (위의 설명 참고)

만약 zookeeper의 znode로 Topic을 제거한다면?

마지막으로 궁금증이 생긴다. 만약 Kafka 관리 명령어가 아니라 zookeeper의 znode를 지우면 topic이 사라지는 걸까?

일단 여러 많은 참고자료에서 kafka의 topic을 제거하기 위해서는 Kafka 관리 도구를 써야하며, zookeeper의 znode를 바로 지우는 것은 의도하지 않은 동작이라는 서술을 볼 수 있었다.

해당 Topic을 zookeeper 내부에서 지우기 위해 zookeeper-shell의 delete 명령어를 활용했다. delete 명령어는 안전을 위해 child znode가 존재한다면 삭제가 되지 않는다. 하지만 해당 예시는 의도하지 않은 작업을 시도하는 것이기 때문에, child node부터 쭉 타고 올라가서 하나의 topic 정보를 모두 제거해줄 것이다.

delete /brokers/topics/sample-topic-replica/partitions/0/state
delete /brokers/topics/sample-topic-replica/partitions/0
delete /brokers/topics/sample-topic-replica/partitions/1/state
delete /brokers/topics/sample-topic-replica/partitions/1
delete /brokers/topics/sample-topic-replica/partitions        
delete /brokers/topics/sample-topic-replica


znode 상에는 Topic 정보가 남아있지 않다. 과연 kafka에서 topic 리스트를 뽑으면 어떻게 될까?

다음의 명령을 통해 kafka의 topic 리스트를 뽑아보자. (현재 여전히 2번 노드에서 명령이 이뤄지고 있다)

sh-4.4$ kafka-topics --bootstrap-server kafka2:9093 --list


다음의 결과를 얻을 수 있다. 여전히 sample-topic-replica가 남아있다.

sample-topic
sample-topic-replica
sample-topice


topic이 저장된 위치를 찾아서

해당 명령을 수행한 이후 kafka 내부에 topic들의 위치가 저장되는 경로가 있지 않을까라는 막연한 추측을 할 수 있었다.

경로를 뒤져가며 /etc/kafka라는 경로를 통해 server.properties라는 kafka의 설정 파일을 찾을 수 있었고 해당 설정 파일의 중간 쯔음에는 log 정보가 저장되는 경로가 설정되어 있다.

  ############################# Log Basics #############################

  # A comma separated list of directories under which to store log files
  log.dirs=/var/lib/kafka


해당 경로에서 ls를 수행하였더니 다음과 같은 결과를 얻을 수 있었다.

sh-4.4$ ls /var/lib/kafka
cleaner-offset-checkpoint    meta.properties                   replication-offset-checkpoint  sample-topic-replica-1
log-start-offset-checkpoint  recovery-point-offset-checkpoint  sample-topic-replica-0


각 폴더에 대한 설명

  • cleaner-offset-checkpoint: log cleaner에 의해 삭제된 메세지들의 offset을 저장한다.

  • log-start-offset-checkpoint: 각 topic partition의 첫 번째 메세지의 offset을 저장한다.

  • recovery-point-offset-checkpoint: 각 topic patition의 마지막으로 완벽하게 써진 메세지를 저장한다. 이는 이후 broker가 실패하는 경우, 해당 지점에서부터 쓰기 작업을 실행하는데에 활용된다.

  • meta.properties: 해당 broker의 ID, host, port와 같은 메타데이터가 저장된다.

  • replication-offset-checkpoint: 각 topic partition의 마지막으로 복제된 메세지의 offset을 저장한다. 이는 이후 follower broker가 실패하는 경우 복제를 해당 지점부터 다시 수행할 때 활용된다.

  • sample-topic-replica-0 / sample-topic-replica-1: sample-topic-replica에 대한 partition들이다. 각 partition은 고유한 폴더를 갖게 되며, 클라이언트로부터 실제로 읽고 쓰여지는 실제 메세지에 해당하는 데이터 파일들을 하위 경로에 보유하게 된다.

추가) sample-topic-replica-0의 하위 파일들 

sh-4.4$ ls sample-topic-replica-1
00000000000000000000.index  00000000000000000000.timeindex  partition.metadata
00000000000000000000.log    leader-epoch-checkpoint

 

'Data Engineering' 카테고리의 다른 글

Zero Copy는 어떻게 Kafka의 속도를 높일까  (0) 2023.08.08
Kafka와 Zookeeper의 상호작용  (0) 2023.05.18
Zookeeper의 znode  (0) 2023.05.18
Coursera 데이터 엔지니어링 강의 목록  (0) 2022.12.08
Spark  (0) 2022.11.20

Apache Kafka는 실시간 데이터를 fault-tolerant한 방식으로 다룰 수 있게 도와주는 분산 스트리밍 데이터 플랫폼이다.

분산 아키텍처로서 Kafka 클러스터는 단일 머신으로 구성되기보다 여러 머신을 붙여서 사용하는 것이 일반적이다. 이때, 분산 환경을 관리하는 플랫폼인 Apache Zookeeper가 등장한다.

Zookeeper와 Kafka는 다음과 같은 상호작용을 수행한다.

1. Broker Registration: Kafka 브로커가 시작되는 경우 Zookeeper에 본인을 등록한다. 해당 정보는 Zookeeper 내부의 ephemeral node 내부에 등록되며, 등록이 완료되었다면 Zookeeper를 통해 해당 브로커가 Kafka 클러스터 내부에 존재하게 된다.

2. Broker Discovery: Kafka 클라이언트(producer, consumer)는 Zookeeper를 통해 사용가능한 Kafka 브로커가 있음을 알 수 있다. 클라이언트가 zookeeper에게 쿼리를 날려 현재 활성화된 브로커의 리스트와 그들의 메타데이터를 확인하는 방식으로 수행된다.

3. Leader Election: Kafka에서 토픽을 구성하는 각 파티션은 해당 파티션을 처리하는 Leader 브로커가 존재한다. 이어서 해당 Kafka 클러스터를 구성하는 다른 브로커 노드는 Follower 브로커로서 해당 Leader를 따른다. leader는 해당 파티션에 해당하는 메세지를 읽고 쓰는 작업을 다루게 되며, Follower는 해당 데이터를 복제하여 가진다. 이때 Zookeeper는 Leader가 실패하는 경우를 감지하고 새로운 리더를 선출(Elect)한다.

4. Configuration Management: Kafka는 Zookeeper를 활용하여 topic을 저장하거나 할당량을 지정하여, 이를 다른 브로커들에게 전파한다. configuration 상에 변화가 발생하면 kafka는 이를 Zookeeper에 기록하고 이는 다른 모든 브로커에게도 전파된다.

5. Cluster Coordination
: Zookeeper는 서로 다른 여러 브로커가 서로 협력해야 하는 경우 동기화 작업을 제공하는 방법 중 하나이다. 예를 들어 토픽이 생성되거나 브로커가 클러스터에 추가되는 경우, 동기화 작업이 요구된다. Cluster Coordination를 좀 더 알아보고 싶다면, 이어지는 글에 추가적인 설명을 적어놓았으니 참고바란다.

 

version: '3'

services:
  zookeeper:
    image: confluentinc/cp-zookeeper:latest
    volumes:
      - zookeeper-data:/var/lib/zookeeper
    environment:
      ZOOKEEPER_CLIENT_PORT: 2181
      ZOOKEEPER_TICK_TIME: 2000

  kafka1:
    image: confluentinc/cp-kafka:latest
    depends_on:
      - zookeeper
    environment:
      KAFKA_BROKER_ID: 1
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka1:9092
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1

  kafka2:
    image: confluentinc/cp-kafka:latest
    depends_on:
      - zookeeper
    environment:
      KAFKA_BROKER_ID: 2
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka2:9093
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1

 


KAFKA_ADVERTISED_LISTENERS
 파라미터는 Kafka가 자신의 IP/포트를 export하는 방법을 지정하여 다른 Kafka broker, client(producer 또는 consumer) 및 Zookeeper와 같은 다른 요소가 연결할 수 있도록 한다. 특히 Docker 내에서 Kafka를 실행할 때 중요한데, 기본적으로 docker로 Kafka 환경을 구성한다면, 각 컨테이너는 Docker 내부 IP를 발행하므로 이를 연결하기 위해서는 도커 네트워크를 명시적으로 생성하거나 또는 docker compose를 통해 자동으로 구성된 도커 네트워크를 사용해야 한다.

 

또 중요한 점으로 Zookeeper 인스턴스는 Kafka 노드를 적극적으로 감지하지 않는다는 점이다. 대신 Kafka 노드 측에서는 시작할 때 Zookeeper에 자신을 등록하는 형태로 Zookeeper ephemeral node에 본인을 등록한다. 각 Kafka broker는 Zookeeper 세션을 유지하며, 만약 세션이 만료된다면 (ex. Broker가 충돌하거나, Zookeeper와 통신이 끊어지는 경우), Zookeeper는 브로커를 더 이상 존재하지 않는 것으로 간주하고 Broker registry에서 제거한다.

 

일단 Kafka Broker가 Zookeeper에 등록되면 연결 정보가 Zookeeper의 /brokers 하위 노드에 저장되고 이는 다른 Broker, Consumer 및 Producer가 해당 Broker에 연결을 구성할 때 사용된다.

 

Zookeeper 인스턴스는 Kafka 노드를 적극적으로 "감지"하지 않습니다. 대신, Kafka 노드는 시작할 때 Zookeeper에 자신을 등록합니다. 각 Kafka 브로커는 Zookeeper 세션을 유지하며, 세션이 만료되면 (예: Kafka 브로커가 충돌하거나 Zookeeper 앙상블과 통신할 수 없는 경우), Zookeeper는 브로커를 더 이상 존재하지 않는 것으로 간주하고 브로커 레지스트리에서 제거합니다.

 

PLAINTEXT는 보안 설정이 되지 않는 연결 프로토콜을 의미한다. 그 외에도 SSL, SASL_PLAINTEXT, SASL_SSL 과 같은 보안 연결 프로토콜로도 세션 연결이 가능하다. 다만 이 경우에는 SSL을 구성하기 위한 일반적인 시나리오가 요구된다. (인증서/키 등을 관리할 솔루션이 추가적으로 요구)

Zookeeper의 znode

mydailylogs
|2023. 5. 18. 01:18

Apache Zookeeper 내부에서 데이터는 hierarchical namespace로 관리되며, 이는 마치 OS의 파일 시스템 혹은 트리 데이터 구조처럼 표현된다. 이때, z nodeZookeeper에서 데이터가 저장되는 단위로서, 데이터가 저장되는 트리의 노드라고 생각할 수 있겠다.

 

znode는 두 가지 형태로 분류된다.

 

1. Persistent znode: 명시적으로 제거되기 전에는 계속해서 zookeeper의 데이터 트리에 남아있는 데이터 노드이다. 클라이언트(ex. kafka)가 연결을 종료하거나 예상치 못하게 세션이 만료되어도 남아있게 된다. 주요 사용 시나리오로는 클러스터의 구성 정보를 저장하거나, 시스템의 상태를 저장하는 등의 client와의 세션 연결에 종속되지 않는 데이터를 저장하는데에 사용한다. 예를 들어 여러 애플리케이션 서버를 Zookeeper를 통해 관리한다고 했을 때 global configuration을 저장하고 이를 모든 서버에 적용한다고 했을 때, 이를 Persistent znode에 저장하며 관리할 수 있다.

 

2. Ephemeral znode: 세션에 종속되어, znode를 만든 클라이언트와의 세션이 종료되면 삭제되는 일시적인 데이터 노드이다. Disconnect, Terminate, Session timeout 등 이유를 막론하고 세션이 종료되면 해당 노드는 사라진다. 주요 사용 시나리오로는 client의 상태를 저장하는 경우, client가 연결되는 경우 연결 정보를 담고 있다가 연결이 해제되는 경우 상태를 깔끔하게 날리는데 사용이 가능하다. 같은 맥락으로 해당 znode가 없다면 세션이 생성되지 않았음을 알 수 있다.

이는 Kafka 클러스터에서 브로커의 장애 상황을 자동으로 처리하는데 도움이 된다. 예를 들어, 브로커가 다운되거나 네트워크 연결이 끊기면 ZooKeeper 세션이 종료되고, 그 결과 해당 브로커와 관련된 ephemeral znode가 삭제되는데, 이를 통해 클러스터 상태가 실시간으로 업데이트되며, 다른 브로커들이 이 변화를 인지하고 적절히 대응할 수 있게 된다.

Kafka의 메타데이터 정보 중 일부는 persistent znode에 저장될 수 있다. 예를 들어, 토픽과 파티션 정보, 컨슈머 그룹의 offset 정보 등은 일반적으로 persistent znode에 저장되며, 이 정보는 ZooKeeper 세션이 종료되더라도 유지된다.


'Data Engineering' 카테고리의 다른 글

Kafka 환경에서의 Zookeeper의 Cluster Coordination  (0) 2023.05.18
Kafka와 Zookeeper의 상호작용  (0) 2023.05.18
Coursera 데이터 엔지니어링 강의 목록  (0) 2022.12.08
Spark  (0) 2022.11.20
로그 데이터 수집  (0) 2022.11.18

class Solution:
    def diagonalSum(self, mat: List[List[int]]) -> int:
        
        n = len(mat)
        answer = 0
        
        for i in range(n):
            answer += mat[i][i] + mat[i][n - i - 1]
        
        if n % 2 != 0:
            answer -= mat[n // 2][n // 2]
        
        return answer

 

 

import bisect

class Solution:
    def longestObstacleCourseAtEachPosition(self, obstacles: List[int]) -> List[int]:
        lis = []
        result = []
        for obstacle in obstacles:
            idx = bisect.bisect_right(lis, obstacle)
            if idx == len(lis):
                lis.append(obstacle)
            else:
                lis[idx] = obstacle
            print(idx, lis, obstacle)
            result.append(idx+1)
        return result

class Solution:
    def numSubseq(self, nums: List[int], target: int) -> int:

        answer = 0

        nums.sort() # O(NlogN)

        left, right = 0, len(nums) - 1
        while left <= right:                         # O(N)
            if nums[left] + nums[right] <= target:
                if (left - right) in [0, 1]:
                    answer += 1
                else:
                    answer += pow(2, right - left)
                left += 1
            else:
                right -= 1
        
        return answer % (10**9 + 7)