• kube-proxy

쿠버네티스의 네트워크 프록시는 각 노드에서 실행되며, 쿠버네티스 API에 정의 된 서비스를 반영하는 역할을 수행한다.




⇒ netfilter는 rule-based 패킷 처리 엔진으로, kernel space에 위치하여 모든 오고가는 패킷의 생명주기를 관찰한다. 그리고 규칭에 매칭되는 패킷을 발견하면 미리 정의된 action을 수행한다. iptables는 이러한 netfilter를 사용해서 chain rule이라는 규칙을 지정하여 패킷을 포워딩하게 한다.

kube-proxy가 위 작업을 직접하게 되면 user space에서 kernel space로 변환이 요구되어 비용이 많이 발생하게 된다. 그래서 iptables 프록시 모드에서는 이러한 역할을 netfilter에게 맡기고 kube-proxy는 netfilter의 규칙을 수정하는 역할만 담당한다.



출처

쿠버네티스(Kubernetes) 네트워크 정리

'DevOps > Kubernetes' 카테고리의 다른 글

kops - 설치 방법 (kubernetes in aws)  (0) 2023.05.14
Controller, 넌 도대체 뭐냐?  (0) 2023.05.13

이 글은 2021년도에 작성했던 글을 다시 업로드하는 글입니다.

 

  • AWS의 EKS를 사용하면 이미 구축된 쿠버네티스 환경을 손쉽게 사용할 수 있다. 하지만 이미 구축된 쿠버네티스 환경은 기본 설정으로 구성되어 있으며, 새로운 설정을 바탕으로 실험을 진행하고 싶을 때는 적절하지 못하다.
  • 그렇다고 EC2를 사용해서 인스턴스를 켜둔 상태로 유지하고, 실험을 수행하기에는 유지 비용이 너무 많이 들고, 매번 쿠버네티스 클러스터링을 수행해야하는 것이 번거롭다.
  • 그래서 번거로움을 해소하기 위해 EC2에서 쿠버네티스 환경 구성을 도와주는 kops를 사용해서 환경을 구축하고자 한다.

1. 실습 준비

  • kops를 사용할 인스턴스 생성 : t2.micro (프리티어)
  • IAM 계정 및 권한 설정
AmazonEC2FullAccess
AmazonRoute53FullAccess
AmazonS3FullAccess
IAMFullAccess
AmazonVPCFullAccess

2. 쿠버네티스 클러스터 구축

  1. kops 및 kubectl 설치
  2. 쿠버네티스 클러스터 구축을 위한 환경 설정
  3. kops를 사용한 쿠버네티스 클러스터 구축

2-1. kops 및 kubectl 설치

  • kops 설치 명령어
wget -O kops <https://github.com/kubernetes/kops/releases/download/$>(curl -s <https://api.github.com/repos/kubernetes/kops/releases/latest> | grep tag_name | cut -d '"' -f 4)/kops-linux-amd64
chmod +x ./kops
sudo mv ./kops /usr/local/bin/
  • kubectl 설치 명령어
wget -O kubectl <https://storage.googleapis.com/kubernetes-release/release/$>(curl -s <https://storage.googleapis.com/kubernetes-release/release/stable.txt>)/bin/linux/amd64/kubectl
chmod +x ./kubectl
sudo mv ./kubectl /usr/local/bin/
  • 설치 확인

2-2. 쿠버네티스 클러스터 구축을 위한 환경 설정

aws configure 으로 IAM 계정정보를 입력, access key id와 secret access key는 계정을 생성할 때, csv 파일로 받아뒀다.

  • 클러스터 정보를 저장할 S3 버킷 생성 및 버저닝 기능 활성화
aws s3api create-bucket \\
--bucket wkd3475-k8s-bucket \\
--region ap-northeast-2 \\
--create-bucket-configuration LocationConstraint=ap-northeast-2

aws s3api put-bucket-versioning  \\
--bucket wkd3475-k8s-bucket  \\
--versioning-configuration Status=Enabled

  • kops로 설치할 때 필요한 환경 변수 설정

~/.bash_profile 에 아래 코드를 추가

export AWS_ACCESS_KEY_ID=$(aws configure get aws_access_key_id)
export AWS_SECRET_ACCESS_KEY=$(aws configure get aws_secret_access_key)
export NAME=wkd3475cluster.k8s.local
export KOPS_STATE_STORE=s3://wkd3475-k8s-bucket

.bash_profile 적용하기

. ~/.bash_profile
  • SSH Key Pair 생성
ssh-keygen

2-3. kops를 사용한 쿠버네티스 클러스터 구축

  • 인스턴스 선정에 대한 고찰

t4g.medium 0.0336 USD 2 4GiB EBS 전용 최대 5기가비트

a1.xlarge 0.102 USD 4 8GiB EBS 전용 최대 10기가비트

t4g.xlarge 0.1344 USD 4 16GiB EBS 전용 최대 5기가비트

t3.medium 0.0416 USD 2 4GiB EBS 전용 최대 5기가비트

t3.xlarge 0.1664 USD 4 16GiB EBS 전용 최대 5기가비트

t3a.xlarge 0.1504 USD 4 16GiB EBS 전용 최대 5기가비트

t2.xlarge 0.1856 USD 4 16GiB EBS 전용 중간

m5.xlarge 0.192 USD 4 16GiB EBS 전용 최대 10기가비트

a1.xlarge를 정말 정말 쓰고 싶었지만, 지원하지 않는 듯 하다.

그 다음으로 저렴한 t4g.xlarge로 설정한다.

  • 클러스터 생성을 위한 설정
kops create cluster \\
--master-zones ap-northeast-2a \\
--zones ap-northeast-2c \\
--state s3://wkd3475-k8s-bucket \\
--topology public \\
--networking calico \\
--master-size t4g.xlarge \\
--master-count 1 \\
--node-size t4g.xlarge \\
--node-count 1 \\
--cloud aws  \\
$NAME
  • SSH Key Pair 등록
kops create secret --name $NAME sshpublickey admin -i ~/.ssh/id_rsa.pub
  • 기타 명령어
#클러스터 리스트 확인
kops get cluster --name $NAME

#클러스터의 설정 및 수정
kops edit cluster --name $NAME

#클러스터 내 인스턴스그룹(ig) 리스트 확인
kops get ig --name $NAME

#클러스터 내 인스턴스그룹(ig) 설정 및 수정
kops edit ig --name $NAME [인스턴스그룹명]
  • 실제 클러스터 생성
#어떤 리소스가 변경될지 화면에 출력만 됨
kops update cluster --name

#실제 클러스터 반영(생성)
kops update cluster --state ${KOPS_STATE_STORE} --name $NAME --yes --admin

아래와 같이 실제로 인스턴스가 생성된 것을 확인할 수 있다.

  • 클러스터 생성 완료 체크
kops validate cluster --name $NAME
watch -n 5 kops validate cluster --name $NAME

그런데 여기서 아래와 같은 문제가 발생한다.

이를 해결하기 위해 아래와 같은 명령을 수행해준다.

kops export kubecfg --admin

  • kubectl 명령 사용해보기

warning! 아래 내용은 수행해도 작동이 되지 않는 상태 → 해결했음

  • ssh key를 ssh agent에 등록하기
eval `ssh-agent -s`
ssh-add ~/.ssh/id_rsa

  • master 노드에 접속하기
ssh admin@<master public ip>

하지만 접속이 안된다!!!!

이건 시간이 좀 걸릴 듯 해서 다른 방법을 찾아봐야할 것 같다.

issue : kops - master로 ssh로 접속이 안됨

해결!

 

  • 클러스터 삭제
kops delete cluster --name $NAME --yes

삭제를 하기 위해서는 SQS에서 sqs:ListQueues와 events:ListRules이 필요한 듯 하다.

그래서 AmazonSQSFullAccess를 추가해주고, events:ListRules는 정책을 생성해서 추가했다.

(sqs:ListQueues만 추가하면 삭제가 되지 않았다)

S3는 버저닝을 활성화했기 때문에 자동으로 지워지지 않는다.

콘솔로 들어가서 직접 삭제해줘야한다.

 

Reference

Welcome - kOps - Kubernetes Operations

AWS에 kops로 쿠버네티스 클러스터 구축하기

'DevOps > Kubernetes' 카테고리의 다른 글

kubernetes - kube-proxy proxy-mode  (0) 2023.05.14
Controller, 넌 도대체 뭐냐?  (0) 2023.05.13

Intro.

회사에서 맥북을 받게 되면서 기존에 사용하던 맥북을 동생한테 줘버렸고... 그렇게 회사 맥북과 Windows 데스크탑만 있게 되었습니다.

Windows 데스크탑은 나름 WSL을 사용해서 어느정도 편하게 쓸만 했지만, 집에서만 쓸 수 있다는 것은 역시 너무 불편했고, 회사 맥북으로 하자니 회사 github account로 설정이 되어있어서 매번 개인 계정으로 바꿔줘야하는 것은 불편해보였습니다.

그러다가 gitconfig 파일을 사용해서 여러 계정을 하나의 호스트 머신에서 사용하는 방법을 알게 되어서 적용해보았습니다.

Step 1: 작업 환경 분리하기

/company
/personal

위와 같이 company와 personal로 프로젝트 환경을 분리를 해줍니다.

그리고 각각의 디렉토리에 .gitconfig 파일을 생성해서 아래와 같이 정보를 넣어줍니다.

[user]
    name = <name>
    email = <email>

company에서는 회사 계정의 정보를, personal에는 개인 계정의 정보를 넣어주시면 됩니다.

Step 2: ~/.gitconfig 파일 수정

[includeIf "gitdir:~/workspace/company/"]
    path = ~/workspace/company/.gitconfig

[includeIf "gitdir:~/workspace/personal/"]
    path = ~/workspace/personal/.gitconfig

각각의 디렉토리에서 git config user.name을 했을 때 지정한 이름이 잘 나오는지를 통해 잘 적용되었는지 확인할 수 있습니다.

Step 3: SSH Key 생성

company 용 ssh key(company.pri)와 personal 용 ssh key(personal.pri)를 각각 생성해준 다음, github에 등록해두고, ~/.ssh/config에서 아래와 같이 수정해줍니다.

Host company-github.com
    Hostname github.com
    User git
    Port 22
    IdentityFile ~/.ssh/company.pri
Host personal-github.com
    Hostname github.com
    User git
    Port 22
    IdentityFile ~/.ssh/personal.pri

이제 회사 원격 저장소를 쓸 때는 git@company-github.com:~ 과 같은 형태로, 개인 원격 저장소를 쓸 때는 git@personal-github.com:~ 과 같은 형태로 사용해주시면 됩니다.

끝.

Outro.

역시 필요한 기능은 대부분 이미 존재합니다...

gitconfig 파일의 존재를 알게 되었고, 회사 맥북의 좋은 툴(?)을 사용해서 개인 개발이 가능해져서 너무 행복합니다. 🎉

Controller, 넌 도대체 뭐냐?

컨트롤러가 뭐냐고 물어보면 뭐라고 할까요? Pod를 관리하는 컴포넌트? 의도한 상태를 유지할 수 있도록 도와주는 컨트롤 루프? 막상 제대로 답하려니 너무 막연하게 표현하게 되는 컨트롤러... 이제는 제대로 답할 수 있도록 제대로 한번 파보도록 하겠습니다.

 

Controller, 넌 도대체 뭐냐?

불신의 이모티콘

Background

쿠버네티스 설계 원칙 : 선언적 API

쿠버네티스의 동작을 이해하기 위해서는 선언적 API의 의미를 이해해야합니다. 선언적 API라는 개념과 대비되는 개념으로 명령적 API이 있는데, 명령적 API는 말 그대로 어떠한 행위를 수행하도록 명령을 하는 것입니다. 그리고 선언적 API는 특정 상태이길 원하는 상태를 정의함으로써, 원하는 상태가 되도록 지속적으로 맞춰가게 유도합니다. 쿠버네티스는 이러한 선언적 API를 사용함으로써 관리자가 직접 모니터링하며 원하는 상태를 맞춰주기 위해 명령을 내릴 필요 없다는 장점을 얻을 수 있습니다.

출처 : https://chacha95.github.io/2020-08-29-Docker_Kubernetes5/


쿠버네티스 오브젝트 vs 쿠버네티스 컨트롤러

쿠버네티스는 크게 오브젝트와 오브젝트를 관리하는 컨트롤러로 구분이 됩니다. 그러니 컨트롤러를 제대로 알려면 오브젝트를 제대로 알아야겠죠? 문서에 따르면 쿠버네티스는 클러스터의 상태를 나타내기 위해서 오브젝트를 이용한다고 서술하고 있습니다.

  • 어떤 컨테이너화된 애플리케이션이 동작 중인지 (그리고 어느 노드에서 동작 중인지) ⇒ status
  • 그 애플리케이션이 이용할 수 있는 리소스

💡 그 애플리케이션이 이용할 수 있는 리소스... 도대체 무슨 말일까?

  • 그 애플리케이션이 어떻게 재구동 정책, 업그레이드, 그리고 내고장성과 같은 것에 동작해야 하는지에 대한 정책 ⇒ spec

오브젝트는 spec 이라는 필드에 desired status를 담게 됩니다. 즉, 쿠버네티스는 오브젝트의 생성을 보장하기 위해 작동하고, 오브젝트가 생성되면 spec 필드를 참고하여 desired status와 status(현재 상태)를 비교하여 status가 desired status가 되도록 지속적으로 관리합니다.

그런데 desired status가 되기 위한 관리를 누가 하고 있을까요!? 누군가가 주기적으로 desired status와 status를 비교하며 적절한 작업을 수행해주고 있는 것일까? 맞습니다. 바로 그 역할을 수행하는 것이 컨트롤러입니다.


쿠버네티스 오브젝트 필드

  • apiVersion : 쿠버네티스 API 버전 명세
  • kind : 오브젝트 종류
  • metadata : name, UID, namespace
  • spec : 원하는 상태
# application/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2 # tells deployment to run 2 pods matching the template
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80

쿠버네티스 오브젝트 이해하기


kube-apiserver

kube-apiserver는 다음과 같은 특징을 가집니다.

  1. 오브젝트에 대한 CRUD(Create, Read, Update, Delete) 요청을 받고 수행하는 역할을 수행 (정확히는 kube-apiserver를 통해서만 etcd에 접근하여 오브젝트에 대한 CRUD를 수행)
  2. 쿠버네티스의 컴포넌트 간의 통신도 kube-apiserver를 거처 이루어짐
  3. 마스터 노드에 위치한 컨트롤 플레인에 속하는 컴포넌트

쿠버네티스 클러스터 아키텍처

그림을 보면 controller-manager와 kube-apiserver가 통신을 하는 것을 확인할 수 있습니다. controller가 kube-apiserver에 1) 어떤 경우에, 2) 어떤 방식으로 요청을 보내는지 궁금해진다면 당신은 이미 쿠버네티스 덕후입니다.


Controller란?

역할

컨트롤러

  • current status가 desired status로 될 수 있도록 관리
  • kube-apiserver를 통해 클러스터 status를 감시하는 컨트롤 루프
  • 공통적인 구조로 Informer, SharedInformer, Workqueue를 가짐

출처 : https://www.reddit.com/r/kubernetes/comments/darsgg/inside_kubernetes_controller_deep_dive/

💡 Deployment Type에 대한 yaml 파일을 apply하게 되면, Deployment Controller와 ReplicaSet Controller가 관리해야하는 리소스별로 분리되어 생성될까 하나로 생성될까...? 예상은 recursive한 구조로...?

종류

  • replication controller
  • replicaset
  • deployment
  • daemonset
  • statefulset
  • horizontal pod autoscaling
  • 쿠버네티스 내장 컨트롤러들이 어디서 관리되고 있을까?
    func NewControllerInitializers(loopMode ControllerLoopMode) map[string]InitFunc {
    	controllers := map[string]InitFunc{}
    	controllers["endpoint"] = startEndpointController
    	controllers["endpointslice"] = startEndpointSliceController
    	controllers["endpointslicemirroring"] = startEndpointSliceMirroringController
    	controllers["replicationcontroller"] = startReplicationController
    	controllers["podgc"] = startPodGCController
    	controllers["resourcequota"] = startResourceQuotaController
    	controllers["namespace"] = startNamespaceController
    	controllers["serviceaccount"] = startServiceAccountController
    	controllers["garbagecollector"] = startGarbageCollectorController
    	controllers["daemonset"] = startDaemonSetController
    	controllers["job"] = startJobController
    	controllers["deployment"] = startDeploymentController
    	controllers["replicaset"] = startReplicaSetController
    	controllers["horizontalpodautoscaling"] = startHPAController
    	controllers["disruption"] = startDisruptionController
    	controllers["statefulset"] = startStatefulSetController
    	controllers["cronjob"] = startCronJobController
    	controllers["csrsigning"] = startCSRSigningController
    	controllers["csrapproving"] = startCSRApprovingController
    	controllers["csrcleaner"] = startCSRCleanerController
    	controllers["ttl"] = startTTLController
    	controllers["bootstrapsigner"] = startBootstrapSignerController
    	controllers["tokencleaner"] = startTokenCleanerController
    	controllers["nodeipam"] = startNodeIpamController
    	controllers["nodelifecycle"] = startNodeLifecycleController
    	if loopMode == IncludeCloudLoops {
    		controllers["service"] = startServiceController
    		controllers["route"] = startRouteController
    		controllers["cloud-node-lifecycle"] = startCloudNodeLifecycleController
    		// TODO: volume controller into the IncludeCloudLoops only set.
    	}
    	controllers["persistentvolume-binder"] = startPersistentVolumeBinderController
    	controllers["attachdetach"] = startAttachDetachController
    	controllers["persistentvolume-expander"] = startVolumeExpandController
    	controllers["clusterrole-aggregation"] = startClusterRoleAggregrationController
    	controllers["pvc-protection"] = startPVCProtectionController
    	controllers["pv-protection"] = startPVProtectionController
    	controllers["ttl-after-finished"] = startTTLAfterFinishedController
    	controllers["root-ca-cert-publisher"] = startRootCACertPublisher
    	controllers["ephemeral-volume"] = startEphemeralVolumeController
    	if utilfeature.DefaultFeatureGate.Enabled(genericfeatures.APIServerIdentity) &&
    		utilfeature.DefaultFeatureGate.Enabled(genericfeatures.StorageVersionAPI) {
    		controllers["storage-version-gc"] = startStorageVersionGCController
    	}
    
    	return controllers
    }
    
  • cmd/kube-controller-manager/app/controllermanager.go

Informer/SharedInformer, Workqueue

Informer

: Object Event(Added, Updated, Deleted...)를 모니터링

매번 컨트롤러가 api-server에 요청해서 Object의 변화를 모니터링하려고 하면 로드가 너무 크다는 문제가 발생합니다.

⇒ 그래서 Informer가 object data를 인-메모리 캐시 형태로 저장

  • Listwatcher : 특정 네임 스페이스의 특정 리소스에 대한 list function과 watch function의 결합

⇒ 이를 통해 컨트롤러가 특정 리소스에만 집중할 수 있도록 한다.

  • Listwatcher source code
    func NewFilteredListWatchFromClient(c Getter, resource string, namespace string, optionsModifier func(options *metav1.ListOptions)) *ListWatch {
    	listFunc := func(options metav1.ListOptions) (runtime.Object, error) {
    		optionsModifier(&options)
    		return c.Get().
    			Namespace(namespace).
    			Resource(resource).
    			VersionedParams(&options, metav1.ParameterCodec).
    			Do(context.TODO()).
    			Get()
    	}
    	watchFunc := func(options metav1.ListOptions) (watch.Interface, error) {
    		options.Watch = true
    		optionsModifier(&options)
    		return c.Get().
    			Namespace(namespace).
    			Resource(resource).
    			VersionedParams(&options, metav1.ParameterCodec).
    			Watch(context.TODO())
    	}
    	return &ListWatch{ListFunc: listFunc, WatchFunc: watchFunc}
    }
    
  • client-go/tools/cache/listwatch.go

list function은 initial list를 만들기 위해 사용되고, watch function은 리소스에 대한 watch를 수행하기 위해 사용됩니다.

  • Resource Event Handler :
  • ResyncPeriod

SharedInformer

: 컨트롤러 간에 단일 공유 캐시를 위한 Informer

실제로는 Informer 보다는 Shared Informer를 사용한다고 합니다. 하나의 리소스에 대해 여러 컨트롤러가 모니터링을 수행하는 경우 Shared Informer를 사용해서 부하를 줄일 수 있습니다.

kubernetes/client-go

Workqueue

: SharedInformer에 대한 대기 및 재시도 메카니즘을 위한 큐

정리

Read from In-memory-cache. Write to api-server.

Controller Manager

쿠버네티스의 컨트롤러들은 Controller Manager가 관리하고 있습니다. 각각의 쿠버네티스 내장 컨트롤러는 논리적으로 분리되어 있지만, 실제 구현 상에서는 컨트롤러 매니저에 통합되어 하나의 바이너리로 컴파일되어 실행됩니다.

컨트롤러 매니저 실행 과정 (주의 : 별거 없음), made by me

 

참고자료

- kubernetes/client-go

'DevOps > Kubernetes' 카테고리의 다른 글

kubernetes - kube-proxy proxy-mode  (0) 2023.05.14
kops - 설치 방법 (kubernetes in aws)  (0) 2023.05.14

https://github.com/grafana/mimir/releases/tag/mimir-2.5.0

  • Mimir 2.6.x 버전이 나옴에 따라 회사에서 2.5.0으로 버전을 올리는 작업을 진행하고 있습니다.
  • Mimir 2.5.0은 Helm Chart 4.0.x버전에서 기본으로 사용되며, Helm Chart도 겸사 겸사 4.0.1으로 올리는 작업을 진행하였습니다.
  • Mimir 2.4.x 버전으로 올리는 작업부터 mimir 관리는 제가 ownership을 가지고 진행하고 이었습니다. (전사 메트릭을 aggregate하는 모니터링 시스템 장애내본 사람 손…! ㅎㅎ)

중요 변경사항

  • Lower memory usage in store-gateway, ingester and alertmanager
    • alertmanager는 사용하지 않기 때문에 store-gateway와 ingester의 메모리 사용량을 줄이는 것에서 큰 효과를 볼 수 있을 것으로 예상되었고, 실제로 아주 조금 메모리 사용량이 줄어드는 것을 확인할 수 있었습니다.
  • Zone aware replication
    • zone-aware replication 기능은 원래 존재하였지만, 이를 helm chart에서 자체적으로 지원하는 것은 이번에 새로 추가되었습니다.

What I did?

  • Mimir Helm Chart 버전이 올라가면 주로 mimir.config 값에 변경이 생깁니다. 말그대로 각 컴포넌트에 주입되는 설정 값들이 들어가며, 기본 설정이 변경된 것을 맞춰줍니다.
  • 버전업에서 변경되는 추가적인 설정이 있는지 체크합니다. (이번에는 zone-awareness replication 외에 특별히 달라지는 것은 없었습니다)
  • 새로 추가된 zone-awareness in helm chart를 도입하기 위해 테스트를 진행하고 있습니다. (ing)

Challenge

원래 mimir는 zone-awareness replication 기능을 지원하고 있었습니다. Ingester를 예시로 설명을 하면, ingester를 여러 AZ에 고르게 분배되게 배포하여 HA를 얻어내는 설정입니다.

즉, 하나의 AZ에 장애가 발생해서 해당 AZ에 있는 Ingester가 모두 죽어도, 다른 AZ에 있는 Ingester가 살아있기 때문에 HA를 보장할 수 있는 것입니다. 물론 Ingester의 replication Factor를 잘 설정하는 것도 중요합니다.

ex) 3개의 AZ에 대해 Ingester를 최소 3개 이상 zone-aware하게 배포하고, replication factor를 3으로 두면,

  • 각 AZ에서 최소 한개의 Ingester는 series를 받게 되고,
  • 하나의 AZ가 비가용 상태가 되어도 최소 두 개의 ingester는 series를 받아서 2개의 동의를 바탕으로 합의가 이루어져 정상적으로 series를 저장할 수 있게 되어 series 손실이 발생하지 않습니다.

그래서 무엇이 문제였는가… 하면, helm chart에서 자체적으로 zone-awareness replication 기능을 제공해주지 않아서, 별도의 스크립트를 사용해서 ingester manifest를 만들고 각 az 별로 ingester-a, ingester-b, ingester-c와 같은 형태로 3개의 sts manifest를 만들어서 개별적으로 배포를 해왔습니다. 하지만 이제 helm chart 자체적으로 이를 가능하게 해주면서 충돌이 나게 되었습니다.

즉, zoneAwareReplication.enabled: true 로 설정하면 이제는 알아서 3개의 sts를 생성해주게 되었는데, 이부분이 회사에서 별도로 처리해주던 스크립트와 충돌이 나기 때문에 스크립트를 수정하는 작업을 진행할 예정입니다(글을 작성하는 시점에서는 아직 진행 중입니다)

그리고 3개의 sts에 동시에 kubectl apply가 이루어지면 문제가 발생할 수 있습니다. 3개의 AZ에 있는 ingester가 동시에 rolling되면서 죽어버려서 다운타임이 발생할 수 있습니다. 그래서 3개의 sts에서 하나의 파드만 동시에 rolling되게 보장하는 기능이 필요해지며, 이를 위해서 mimir helm chart에서는 rollout-operator를 사용합니다. rollout-operator는 group을 만들고, group 내에서 하나의 파드만 rolling되는 것을 보장시켜줍니다.


그래도 다행이었던 점은 이미 zone-awareness replication 기능을 사용하고 있었던 점입니다. 이게 아니었다면 zone-awareness replication을 사용하기 위해 별도의 migration 작업을 해야했을 것이고 이는 많게는 12시간 이상 걸리는 작업이 되었을 것입니다. 이미 이 기능을 적용해둔 앞선 작업자분들에게 진심으로 감사합니다. ㅎㅎ

 

End.

'DevOps > Monitoring' 카테고리의 다른 글

Mimir Overview  (0) 2023.05.13

Intro

내가 다니는 회사는 mimir를 사용하고 있습니다. Grafana Labs는 mimir에 집중해서 지원하려고 하고 있으며, mimir는 cortex를 기반으로 만들었기 때문에 cortex에 대한 이해가 있다면 손쉽게 mimir로 넘어갈 수 있습니다. 회사에서 mimir를 테스트하고 전환하던 당시에는 남 일이라 생각하고 신경을 안썼지만(애초에 모든게 새로웠던 시기라서 다른 것을 보고 있었다)… 이제는 그런 생각을 버리고 뭐든 내 일이라고 생각하고 mimir를 제대로 파보고 있습니다.

생각보다 mimir는 분산 시스템을 고려해서 잘 설계된 프로젝트였고, 분산 시스템을 공부했던 내 입장에서는 옛날 생각이 나면서 재밌게 공부를 할 수 있었습니다.

Why Mimir?

Github Readme에서는 아래와 같은 점들을 장점으로 소개하고 있습니다.

  • Easy to install and maintain
  • Massive scalability
  • Global view of metrics
  • Cheap, durable metric storage
  • High availability
  • Natively multi-tenant

하지만, 좀더 느낌이 확 와닿는 장점을 추려서 소개하려고 합니다.

Prometheus HA

Mimir의 궁극적인 목표는 Prometheus의 HA를 지원하는 것 입니다. Prometheus는 자체적으로 HA가 되지 않습니다. 그래서 업그레이드할 때 다운타임이 무조건 발생하게 되고, 어떠한 이유로든 Prometheus가 죽게 되면 메트릭 수집이 완전히 멈춰버리는 문제가 발생하게 됩니다. 그러면 모니터링 시스템이 완전히 멈춰버리고, 시스템에 장애가 발생해도 알 수 없는 문제가 발생하게 됩니다. 이는 회사 전체에 큰 영향을 미칠 수 있는 사항이고, 그렇기 때문에 Prometheus의 HA는 선택이 아닌 필수 사항이라고 볼 수 있습니다.

  • How HA? (Advanced)

HA를 지원하는 방법은 간단합니다. 하나의 클러스터에 prometheus를 여러 개를 두는 것입니다. 그리고 각각의 prometheus는 동일한 metric을 수집하여 mimir distributor라는 component로 전송합니다. (prometheus의 config에서 remote_write의 url에 distributor url을 넣어주면 prometheus의 remote_write 기능을 사용해서 metric을 distributor로 보냅니다). 그런데 각각의 prometheus가 metric을 전송하기 때문에 distributor는 중복되는 metric을 받게 되며(정확히는 완전히 똑같은 metric data는 아니지만 중복되는 기간에 대한 metric을 포함할 수 있습니다), 이를 모두 저장하는 것은 불필요한 작업이며 혼란을 발생시킬 수 있습니다. 그래서 leader replica에서 보낸 metric인지를 식별해서 해당하는 metric만 수용하고, 그 외의 metric은 drop시킵니다.

자세한 내용은: Distributor HA Tracker 참고

Long-Term Storage

Prometheus는 로컬 TSDB에 metric을 저장합니다. 하지만 운영 시간이 길어질수록 metric이 계속 쌓이고 용량이 부족해지면 매번 추가적으로 PVC를 수정해서 용량을 늘려줘야하는 번거로움이 발생하게 됩니다. 사실 이렇게 지속적으로 용량이 증가하는 문제를 해결하기 위해서 prometheus에서는 retention을 설정해서 주기적으로 storage를 비워주는 기능을 제공하고 있기는 하지만, 오랜 기간 동안 metric을 저장해야하는 경우에는 결국 prometheus이 storage 사이즈를 필요에 따라 올려줘야하는 상황이 필요하게 됩니다. 하지만 mimir는 최근 metric은 in-memory에 저장하고, 주기적으로 s3로 flush하여 저장하며, 쿼리를 수행할 때는 이 둘을 동시에 사용할 수 있게 하여 Long-Term Storage를 지원합니다.

Metric Aggregation

또한, Mimir는 여러 클러스터에서 발생하는 metric을 하나로 모으는 역할도 수행합니다. 이를 통해 grafana 같은 대시보드에서 매번 data source를 바꿔가며 쿼리를 수행할 필요 없고, 여러 클러스터에 대한 쿼리를 수행할 수 있습니다. 많은 클러스터를 관리하는 기업에서는 이 기능이 매우 유용합니다(매우 매우…).

Basic Feature

Mimir Docs: About the Grafana Mimir architecture

The Write Path

  1. 각각의 prometheus는 distributor로 메트릭을 전송합니다.
  2. distributor는 전달받은 metric이 정상적인 metric인지에 대한 vaildation을 수행합니다.
  3. distributor는 metric을 sample 단위로 나눠서 모든 ingester에 전송합니다.
  4. ingester는 sample을 in-memory와 Write-Ahead Log(WAL)에 저장합니다. (WAL을 통해서 ingester가 restart해도 WAL을 통해서 빠르게 복구할 수 있도록 합니다)
  5. ingester는 주기적으로 in-memory에 있는 sample을 block 단위로 local tsdb와 s3에 저장하고, 해당 block에 해당하는 WAL은 제거합니다.
    1. local tsdb에는 default로 24시간(blocks-storage.tsdb.retention-period) 동안 block이 저장되며, s3로 flush하는 과정에서 문제가 생겨도 복구할 수 있도록 하기 위한 용도로 사용됩니다.
    2. ingester는 각각 개별적인 block을 생성하여 저장하며, compactor가 주기적으로 s3에 있는 중복되는 block을 합쳐주어, duplicate samples를 제거합니다.

The Read Path

  1. query-frontend가 query를 분석해서 작은 단위의 query로 나눕니다.
  2. query-frontend는 캐시를 확인하고, 캐시가 없으면 in-memory 상의 큐에 query 작업을 넣어둡니다. 만약 query-scheduler를 사용한다면 query-scheduler의 큐에 query 작업을 넣어둡니다. (appVersion 2.3.0 부터는 default로 query-scheduler를 사용합니다)
    1. 캐시가 존재하면 캐시에 있는 query 결과를 client에 리턴합니다.
    2. query-scheduler를 사용함으로써 query-frontend에서 큐를 제거할 수 있으며, 큐를 제거함으로써 큐에 대응하는 worker가 생성되어야하는 문제가 해결되어 query-frontend의 스케일링이 효율적이게 됩니다. (즉, query-frontend 수를 늘리면 강제적으로 worker 수가 폭발적으로 늘어나기 때문에 비효율적이었습니다)
  3. querier는 worker 역할을 수행하며, 큐에서 query를 받아와서 실제로 query 요청을 수행시킵니다.
    1. sample은 s3 (long-term storage)와 ingester에 존재하며, 최신 데이터는 ingester를 통해서, 오래된 데이터는 store-gateway를 통해서 받아옵니다.
  4. query-frontend는 쿼리 결과를 모아서 client에 리턴합니다.

Components

Mimir Docs: Grafana Mimir Component

Basic Feature에서 설명한 내용을 이해했다면 아래 Component가 각각 어떤 역할을 수행하는지 대략적으로 알게 되었을 것이며, 자세한 내용은 문서를 확인하는게 빠릅니다.

  • Compactor
    • s3에 있는 sample을 압축하는 역할을 수행합니다.
  • Distributor
    • Rate limiting - 운영을 하면서 rate limit 때문에 요청이 drop되는 현상을 경험한 적이 있으며, distributor가 견딜 수 있는 선에서 적절한 값을 할당하는 것이 성능 효율을 결정합니다.
    • HA tracker
    • Quorum
  • Ingester
    • ingester의 데이터를 조회하는 방법, mimir component가 어떤 ingester를 사용할지를 결정하는 방법 - hash ring
    • ingester에 복구 방법 - Replication(Zone Aware Replication), WAL, Out-of-order samples ingestion
  • Querier
    • store-gateway를 사용해서 s3의 데이터에 쿼리를 수행하고, ingester를 통해서 최신 데이터에 쿼리를 수행합니다.
  • Query-frontend
    • (Optional) Caching - query-fronend.align-queries-with-step - PromQL 결과에 대한 정확도가 중요하지 않다면, 이 값을 true로 하여 Mimir의 성능을 높일 수 있습니다.
  • Query-scheduler (Optional)
    • 기존 Query-frontend에서 큐 기능을 대체합니다.
  • Store-gateway
    • long-term storage의 block에 대한 쿼리를 수행합니다.
    • blocks index-header - 로컬에 이를 저장해둠으로써 쿼리 속도를 향상시킵니다.

'DevOps > Monitoring' 카테고리의 다른 글

Mimir 2.5.0  (1) 2023.05.13

+ Recent posts