본문 바로가기
MLOps

# 0. 쿠버네티스 환경의 서비스 디스커버리

by Bebsae 2024. 5. 26.

서비스의 규모가 커질수록 모놀리식 아키텍처로 관리하는 것은 점점 한계가 존재한다. 도메인 혹은 기능간의 의존성이 생기고 이를 서버 혹은 컨테이너 단위로 분리해야 의존성을 제거할 수 있다. 이러한 형태를 MSA(Micro Service Architecture)라고 한다. 그러나, MSA는 사실 이상적인 형태로써, DDD(Domain-Driven Development)가 제대로 수행되어야 실질적으로 서비스 간의 의존성을 깨끗하게 정리할 수 있다.
 
그리고, 아무리 MSA라 하더라도, 다양한 요소로 인해 전반적인 아키텍처는 훨씬 복잡해질 수 있다. 예를 들면, 로드 밸런싱(Load Balancing)의 문제이다. 만약 단일 서버 혹은 컨테이너가 비동기적으로 오는 클라이언트의 무수한 요청을 받기 위해 노력하고 있는 상황이라 가정해보자. 프로덕션 환경에 배포하기 이전, 부하 테스트를 충분히 수행하더라도, 언제나 예상 밖의 케이스가 발생할 수 있다. 고로, 고가용성(High Availability)를 보장하고자 동일한 기능을 가진 서버 혹은 컨테이너를 Replication해야 한다. 이로써, 특정 서버 혹은 컨테이너에 장애가 발생하더라도, 다른 Replica에서 요청을 나눠 갖거나 무중단 서비스를 제공할 수 있다.
 
사실 여기까지는 그리 어려운 부분이 아니다. 그렇다면, 그 다음 단계는 무엇일까? 쿠버네티스는 컨테이너 오케스트레이션 플랫폼이다. 너무 많은 컨테이너들을 조율하듯이 일관성 있게 관리하기 위함이다. 쿠버네티스를 접하다보면 서비스 메시, 사이드카 패턴, 컨트롤러 패턴, 오퍼레이터 패턴 등.. 다양한 내용들을 접할 수 있다. 그러나, 이러한 복잡한 개념을 숙지하기 이전에, 가장 기본적인 서비스 디스커버리(Service Discovery)에 대해서 알아야 한다고 생각한다. 내가 정의하는 서비스 디스커버리는 결국, 말 그대로 수 많은 서비스 중에서 내가 원하는 항목만 찾는 것을 의미한다. 쿠버네티스에서 서비스 디스커버리를 예시로 들어보자.
 

 
위 스크린샷에서 보다시피 kube-dns라는 Service 객체가 존재한다. 그리고, kube-dns 서비스 객체는 coredns Pod를 정확하게 프록시 하고 있다. 쿠버네티스를 접하면 알다시피 Pod 객체의 IP 주소는 유동적이고, (CNI에 따라 방식은 조금씩 다르겠지만) 보통은 노드간에 다른 네트워크를 구성함에도 불구하고, Pod가 어느 노드에 스케줄링되든 Service 객체는 자신이 바라봐야할 Pod들을 정확하게 찾아간다.
 
이러한 과정이 가능한 것은 레이블 셀렉터(Label Selector)를 통해 Service 객체가 바라봐야할 Pod를 찾는 것이다. 그 밖의 서비스 디스커리 과정으로는, 다른 네임스페이스에 존재하는 리소스지만, 클러스터 내부에서 찾아갈 수 있는 방법이 CoreDNS를 통해 찾아갈 수 있다. 예를 들면, test 네임스페이스에 존재하는 test-svc Service를 찾아갈 경우, CoreDNS 파드의 53번 포트로 접근하여 test-svc.test.svc.cluster.local(<Service 객체 이름>.<네임스페이스>.svc.cluster.local) 이라는 도메인 네임을 얻을 수 있다. NodePort 타입의 Service 객체를 생성(노드마다 VM을 생성하고, 마스터 노드를 거쳐 전 노드를 순회하기 때문에 네트워크 비용 발생)하지 않더라도, ClusterIP 타입의 Service 객체만으로 클러스터 내부간에 소통을 자유롭게 할 수 있고, 네트워크 성능 또한 일부 최적화가 가능하다.
 

 
아무튼 .. 각설하고, Service 객체가 생성되면 Endpoints 객체가 같이 생성된다. 지금 보는 것은 kube-dns Service 객체에 대응되는 Endponits 객체의 명세이다. 10.244.169.19, 10.244.240.214은 아까 위에서 본 coredns Pod들의 유동적인 IP 주소이다. k8s-app=kube-dns 레이블이 있음을 인지하자.
 

 
Service 객체로 돌아와보자. 레이블 셀렉터를 통해 k8s-app=kube-dns 레이블이 존재하는 Pod를 가르키도록 구성되어 있다.
 

 
coredns Pod에는 k8s-app=dns Pod가 있는 것이 확인되었다! 그럼, 쿠버네티스 계층에서 추상적으로(그리고, 직관적으로) 유동적인 Pod의 IP 주소를 가르키는 방법에 대해서는 알았으나, 라우팅을 실제로 어떻게 할까?
 

 

Chain INPUT (policy ACCEPT)
target     prot opt source               destination         

Chain FORWARD (policy DROP)
target     prot opt source               destination         
DOCKER-USER  all  --  anywhere             anywhere            
DOCKER-ISOLATION-STAGE-1  all  --  anywhere             anywhere            
ACCEPT     all  --  anywhere             anywhere             ctstate RELATED,ESTABLISHED
DOCKER     all  --  anywhere             anywhere            
ACCEPT     all  --  anywhere             anywhere            
ACCEPT     all  --  anywhere             anywhere            
ACCEPT     all  --  anywhere             anywhere             ctstate RELATED,ESTABLISHED
DOCKER     all  --  anywhere             anywhere            
ACCEPT     all  --  anywhere             anywhere            
ACCEPT     all  --  anywhere             anywhere            
ACCEPT     all  --  anywhere             anywhere             ctstate RELATED,ESTABLISHED
DOCKER     all  --  anywhere             anywhere            
ACCEPT     all  --  anywhere             anywhere            
ACCEPT     all  --  anywhere             anywhere            

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         

Chain DOCKER (3 references)
target     prot opt source               destination         
ACCEPT     tcp  --  anywhere             172.23.0.2           tcp dpt:http-alt

Chain DOCKER-ISOLATION-STAGE-1 (1 references)
target     prot opt source               destination         
DOCKER-ISOLATION-STAGE-2  all  --  anywhere             anywhere            
DOCKER-ISOLATION-STAGE-2  all  --  anywhere             anywhere            
DOCKER-ISOLATION-STAGE-2  all  --  anywhere             anywhere            
RETURN     all  --  anywhere             anywhere            

Chain DOCKER-ISOLATION-STAGE-2 (3 references)
target     prot opt source               destination         
DROP       all  --  anywhere             anywhere            
DROP       all  --  anywhere             anywhere            
DROP       all  --  anywhere             anywhere            
RETURN     all  --  anywhere             anywhere            

Chain DOCKER-USER (1 references)
target     prot opt source               destination         
RETURN     all  --  anywhere             anywhere

 
정답은 iptables를 활용한다. 쿠버네티스에서는 Service 객체가 생성될 때마다 OS의 User space에 존재하는 iptables 규칙을 동적으로 수정한다. iptables 규칙을 통해 인바운드, 아웃바운드, 포워드 트래픽을 관리할 수 있다. 위 결과물은 iptables --list 커맨드 수행 예시이다. (쿠버네티스 노드에서 수행한 것은 아님) User space에 존재하는 iptables는 단순히 라우팅 규칙으로 실제로 Kernel space에서 라우팅을 수행하는 것은 netfilter이다. 직관적으로는 Nginx Ingress Controller를 비유할 수 있다. (Ingress는 프록시를 위한 규칙, 실질적으로 프록시하는 주체는 Nginx)
 
요약하자면, 아래와 같다.

  • (from) Service -- Label Selector --> Endpoints --> Pod (to)
  • 내부적으로 발생하는 일 : Service 객체를 생성 --> iptables 규칙 생성 --> netfilter 라우팅 수행

 
 
이번 내용을 다루면서, 앞으로 Prometheus에서 Kubernetes로의 서비스 디스커버리, 쿠버네티스에서의 라우팅 모드(userspace / iptables / ipvs) 등 다양한 내용들을 다룰 기반이 준비되었다.

댓글