본문 바로가기

IT

쿠버네티스 아키텍쳐 & 컴포넌트

728x90

쿠버네티스의 기본 개념은 desired state(원하는 상태)라고 보면 될 것 같다. 정확히는 원하는 상태, 선언되어 있는 상태에 현재 상태와 비교하고 다르다면 맞추기 위해 계속해서 도전한다. 

 

즉 쿠버테니스는 항상 우리가 선언해놓은, 원하는 상태로 현재 상태를 계속 유지해나가려 한다.

쿠버네티스의 구조는 Master(마스터 노드)와 Node(워커노드)라는 구조로 이루어져있다.

 

크게보면 Master(마스터 노드)는 Controller-Manager, API Server, Scheduler, etcd로 구성되어있으며 Node(워커노드)는 Kubelet, Kube-proxy, Container Runtime, CAdvisor 정도로 구분할 수 있을 것 같다.

쿠버네티스 마스터, 노드 구조

 

구성 컴포넌트를 하나씩 확인해보자.

Master(master node)

Controller Manager )

Controlle Manager

쿠버네티스 클러스에서 실행되는 여러 개의 컨트롤러를 통합 관리하는 프로세스이다. 컨트롤러는 클러스터 내의 리소스 상태를 감시하고, 사용자가 정의한 상태로 유지하기 위한 조치를 취한다. 

주요 컨트롤러로 Replication Controller(사용자가 지정한 파드 복제 수를 유지하는 역할) Node Controller 등이 있다.

컨트롤러의 역할은 크게 Watch Status & Remediate Situation(상태 해결 및 복구)으로 나뉜다.

  1. Watch Status
  2. node-controller는 5초마다 node의 상태를 모니터링한다.
  3. Remediate Situationnode에 unreachable marking을 한다. marking 후 5분 동안 come back할 시간을 주고, 그럼에도 장애가 있다면 클러스터에서 노드를 제거한다.
  4. node에 장애가 발생하고 40초 후에도 node가 unreachable하다면 

이런식으로 계속해서 desired functioning state를 유지하게 한다.

API Server )

쿠버네티스 안의(내, 외부) 모든 요청(데이터의 상태를 조회하거나 변경하는 모든 요청)은 API Server를 통해 요청, 응답 받는다고 생각하면 될 것 같다. 흔히 허브 앤 스포크(hub-and-spoke) API 패턴을 가지고 있다. (Hub가 통신의 중심이 되며 각 Spoke는 Hub와 연결되어 Hub의 변경 사항을 리스닝 하고 각 역할을 수행하는 패턴)

Hub & Spoke 패턴

 

EKS 등을 사용할 때 사용자가 Bastion을 통해 통신하는 것이 이 API Server이다. API Server는 클러스터의 상태를 etcd라는 분산 키-값 저장소에 저장하는 역할을 하며, 인증 및 인가를 통해 클러스터 리소스에 대한 접근 제어를 수행하기도 한다.

이러한 패턴은 etcd를 통해서만 데이터 변경이 가능하기에 데이터 일관성이 유지되고, 새로운 서비스(Spoke)를 추가하는 것이 편리한 확장성을 제공한다.

etcd )

etcd는 분산 키-값 저장소이다. etcd는 클러스터의 모든 리소스 정보(파드, 서비스, 노드 등)를 포함하여 클러스터의 설정 정보및 메타데이터를 저장하고 모든 노드가 이를 참조하여 상태를 동기화한다. 

그렇기 때문에 백업은 필수이며 고가용성과 일관성을 유지해야 한다. 

etcd는 Raft Consensus Algorithm(Raft: 분산 합의 알고리즘)을 활용하여 설계되었기에 노드가 여러 대일 경우에도 분산된 방식으로 상태를 관리한다고 한다. 따라서 하나의 노드가 실패하더라도 다른 노드가 자동으로 복구하여 시스템이 지속적으로 동작할 수 있다. 

Raft 알고리즘은 하나의 리더 노드가 데이터를 기록하고 변경하며 팔로우 노드에 이 값들이 복제된다. 자세한 알고리즘은 해당 설명을 참고하면 좋을 것 같다.

https://yoongrammer.tistory.com/50

 

Raft Consensus Algorithm 알아보기

목차Raft Consensus Algorithm 알아보기Raft는 이해하기 쉽도록 설계된 분산 합의 알고리즘입니다.여러 서버들 중 일부 서버에 장애가 발생하더라도 기능을 유지하도록 하는 내결함성을 가지고 있습니

yoongrammer.tistory.com

 

Scheduler )

스케줄러는 새로운 파드를 적절한 노드에 할당하기 위해 파드의 배치를 결정한다.(어떤 워커노드에 파드를 올릴지) 

클러스터의 리소스를 효율적으로 활용하기 위해 현재 노드의 리소스(CPU, Mem, 디스크I/O, 네트워크 사용량 등)을 파악함과 동시에 파드의 요구사항을 참고하여 적절한 노드를 선택한다. 

조금 더 자세하게 설명하자면 파드의 요구사항이 CPU, 메모리, 스토리지가 될 수 있기에 이를 충족하는 노드를 찾아야하고 워커노드의 리소스 상황을 평가하여 클러스터의 자원을 균형 있게 사용하는 방향으로 스케쥴링 해야한다. 이때 스케쥴러도 API Server를 통해 리소스를 확인하는데 Kubelet이 CAdvisor를 통해 Node의 자원 사용량을 모니터링하여 응답한다.

또한 정책 기반 스케쥴링을 우선 순위로 진행하게 되는데 Affinity/Anti-affinity, Taints/Tolerations, NodeSelector 등의 규칙을 사용하여 파드가 특정 조건에 맞는 노드에 스케줄링될 수 있게 한다. 

특정 노드의 장애로 파드가 제거될 경우, 새로운 노드에 파드를 재배치하여 서비스 가용성을 유지하기도 한다.

  1. 파드 수집: API Server에서 스케줄링되지 않은 파드(즉, 특정 노드에 할당되지 않은 파드)를 수집한다.
  2. 노드 필터링(필터링 단계): 파드의 요구 조건에 맞지 않는 노드를 제거하여 후보 노드 목록을 만듭니다. 이 단계에서 주로 아래 조건들이 고려됩니다.  NodeSelector, Node Affinity: 파드가 특정 노드에 배치되도록 설정된 조건3) 노드 스코어링(점수화 단계): 후보 노드 목록에서 가중치(Scoring)**를 계산하여 가장 적합한 노드를 선정한다. 스케쥴링 프로파일을 구현하면 스케쥴링 단계를 구현하는 다른 플러그인을 구성할 수 있다.
  3. https://kubernetes.io/ko/docs/concepts/scheduling-eviction/kube-scheduler/
  4.   Taints and Tolerations: 노드가 특정 파드에 대해 "기피"하는 조건이 있지만, 이를 파드가 허용할 수 있는지 여부
  5.   리소스 요구 사항: 파드가 필요한 CPU, 메모리 등을 제공할 수 있는지 여부
  6. 노드 선택 및 배치: 최적의 가중치를 받은 노드를 선택하여 파드를 할당한다.

스케쥴러가 어떤 노드에 배치할 지 결정하는 부분이 중요하면서 쿠버네티스의 재밌는 요소인 거 같다. 

Affinity/Anti-affinity, Taints/Tolerations, NodeSelector 등의 예제는 https://devocean.sk.com/blog/techBoardDetail.do?ID=163909 블로그를 참고하자.

이를 잘 활용하면 노드(워커 노드)의 리소스를 잘 활용하는데도 도움이 될 것이고 서비스를 다른 노드로 배치하거나 반대로 동일한 노드로 배치함으로서 안전하게 운영할 수 있는 방법이 되지 않을까 생각한다.

 

Node(worker node)

Kubelet )

각 노드에서 실행되는 에이전트로 파드를 실행하고 관리하는 역할을 한다.

마스터 노드에서 할당받은 파드를 실행하고 파드의 상태를 유지하며 API Server와 소통한다. API Server를 통해 명령이 들어오면 Container Runtime에게 컨테이너를 띄우도록 요청한다.

실질적으로 컨테이너를 띄우는 것은 docker, containerd와 같은 Container Runtime이지만 이와 직접 통신하여 생성 및 관리를 맡고 있다.

Kubelet은 각 파드와 컨테이너의 상태를 지속적으로 확인하여, 필요한 경우 정의된 프로브(liveness, readiness, startup probe)를 사용해 컨테이너를 재시작하거나 트래픽을 막을 수 있으며 CAdvisor를 통해 전달받은 메트릭 정보를 API Server에 보고 한다.

CAdvisor )

CAdvisor는 Kubelet의 하위 프로세스로 컨테이너의 리소스 사용량과 성능 지표를 수집한다. 이 정보는 Kubelet에 보고되고, 클러스터의 상태 모니터링에 사용된다.

Kube-Proxy )

Kube-Proxy는 쿠버네티스 클러스터 내에서 네트워크 프록시 역할을 수행한다. 각 노드에서 실행되며 노드로 들어오는 네트워크 트래픽을 적절한 컨테이너로 라우팅 될 수 있도록 도운다.

쿠버네티스에서는 파드가 주기적으로 스케일링되고 여러 노드 사이를 이동하므로 특정 파드의 IP는 지속되지 않는다. 따라서 서비스(SVC)라는 추상 레이어를 만들어 파드로 접근하게 되는데, Kube-Proxy는 이 서비스 레이어에서 클러스터 IP를 통해 요청을 특정 파드로 연결한다. 쉽게 말하면 SVC에서 파드로의 트래픽을 포워딩한다고 생각해도 될 것 같다. 

새로운 서비스나 엔드포인트가 추가되거나 삭제되면 API 서버는 이러한 변화를 Kube-Proxy에 전달하고, Kube-Proxy는 이 변화를 노드의 NAT 규칙에 반영한다. NAT 규칙은 단순하게 서비스의 IP와 파드의 IP를 매핑해주고 있다고 생각하자. 파드가 여러개라면 로드밸런싱도 기능도 포함하고 있다고 한다. 

kube-porxy가 네트워크를 관리하는 3가지 모드(userspace, iptables, IPVS)가 있다고 한다.

 

기타 )

CoreDNS

쿠버네티스 클러스터에 DNS 서비스를 제공한다. 파드가 생성될 때 도메인(파드의 주소) 이름을 통해 다른 서비스나 파드의 IP 주소를 쉽게 찾을 수 있도록 도와준다. 흔히 서비스 디스커버리라고 하는데, 컨테이너에서 실행되는 애플리케이션은 IP 주소 대신 DNS 이름을 사용하여 다른 서비스를 찾고 통신할 수 있다. 서비스의 확장 또는 마이그레이션 시에도 결합도가 낮아지고 유연성이 향상된다.

CoreDNS 질의

그림을 보면 우리가 아는 DNS 서버와 크게 다르지 않다. 

도메인 기반 호출 시에 질의 수행 및  IP를 획득하고 IP 기반의 요청이 이루어진다. 외부 DNS의 경우 외부 DNS Server에게 요청이 가지만 내부 DNS, 예를 들어 쿠버네티스 클러스터안에 다른 서비스&파드(ex beta.ns-beta.svc)에 요청할 때는 CoreDNS를 통해 IP를 획득한다. 이는 파드가 배포될 때 CoreDNS는 이를 감지하고 최신 상태로 반영된다. 

CNI (Container Network Interface)

쿠버네티스의 CNI(Container Network Interface)는 컨테이너 네트워킹을 위한 표준 플러그인 인터페이스이다. CNI는 쿠버네티스 클러스터에서 컨테이너가 생성될 때 자동으로 네트워크 설정을 수행하여 컨테이너 간의 통신과 네트워크 연결을 가능하게 한다.

 

네트워크 플러그인은 각 파드에 IP 주소를 할당하여 클러스터 내 파드들이 IP 기반 통신을 할 수 있도록 한다.

네트워크 정책을 지원하여 파드와 서비스 간의 트래픽 제어와 접근 제어도 가능하다. 

파드가 삭제되면 CNI 플러그인은 해당 파드의 네트워크 인터페이스와 IP 주소를 해제한다.

 

흔히 주요 플러그인은 Calico, Flannel, Weave 등이 존재하고 EKS를 활용한다면 Amazon VPC CNI 가 이 역할을 대신한다.

 

Kubernetes Architecture

한번 그려본 전반적인 Kubernetes Architecture 인데 밑에 파드 배포 순서를 보고 다시 본다면 어느정도 이해가 될 수 있을 거 같다. 트래픽에 흐름을 강조해서 그려보았다. (틀린 부분이 있을 수도 있습니다.)

 

Logging과 Metrics는 따로 포스팅해두었으니 참고하도록 하자.

쿠버네티스 파드 배포 순서

마스터와 노드를 구성하는 컴포넌트들을 요약하여 살펴보았고 

이를 바탕으로 파드를 생성하였을때의 순서를 살펴보고 쿠버네티스 아키텍쳐를 이해해보도록 하자.

 

파드 생성 처리 순서

 

1 & 2. 파드 생성을 요청하면 API Server를 통해 etcd에 변경 사항을 업데이트한다.  ex) kubectl apply -f A.yaml

 

3. 컨트롤러 매니저는 API Server로 부터 상태 변화를 감지하고, 클러스터의 현재 상태를 원하는 상태로 만들기 위한 작업을 수행한다. 배포를 위한 추가적인 객체를 준비한다.

ReplicaSet 생성 시 

  •  Kube Controller에 포함된 ReplicaSet Controller가 ReplicaSet을 감시하다가 ReplicaSet에 정의된 Label Selector 조건을 만족하는 Pod이 존재하는지 체크
  •  해당하는 Label의 Pod이 없으면 ReplicaSet의 Pod 템플릿을 보고 새로운 Pod(no assign)을 생성. 생성은 역시 API Server에 전달하고 API Server는 etcd에 저장

4. Scheduler는 할당되지 않은(no assign) Pod이 있는지 체크하여 Pod를 배포할 노드를 배정받음

 

5. kubelet은 API Server를 통해 자신의 노드에서 생성되어야할 파드가 있는지 확인함

 

6. 컨테이너 런타임에게 컨테이너(파드) 생성 요청 & 컨테이너 생성 

 

7. kubelet은 파드의 상태를 API Server에게 (주기적으로)보고하고 etcd에 상태가 저장된다.

 

파드의 생성 순서가 처음에는 낯설게 느껴졌지만 API Server를 중심으로 Hub & Spoke 방식의 API 패턴을 가지고 각각의 컴포넌트(Controller, Scheduler)들이 선언된 상태를 이루기 위해 동작하는 모습으로 바라보면 점점 눈에 들어오는 것 같았고 재밌게 다가왔다. 쿠버네티스를 이루는 여러 컴포넌트들에 대해 기본적인 부분을 다뤄보았고 ko document를 꾸준히 읽어보면 내 머릿 속에 블랙박스인 부분을 채워보면 좋은 기회가 될 거 같다.

 

 

 

참고하기 좋은 글

https://subicura.com/2019/05/19/kubernetes-basic-1.html

 

 

Kubernetes Components

https://kubernetes.io/docs/concepts/overview/components/

 

Kubernetes Components

An overview of the key components that make up a Kubernetes cluster.

kubernetes.io

 

https://kubernetes.io/ko/docs/home/

 

쿠버네티스 문서

쿠버네티스는 컨테이너화된 애플리케이션의 배포, 확장 및 관리를 자동화하기 위한 오픈소스 컨테이너 오케스트레이션 엔진이다. 오픈소스 프로젝트는 Cloud Native Computing Foundation에서 주관한다.

kubernetes.io