개발놀이터

쿠버네티스 이론 : 아키텍처 (Component, Network, Logging / Monitoring) 본문

배포/kubernetes

쿠버네티스 이론 : 아키텍처 (Component, Network, Logging / Monitoring)

마늘냄새폴폴 2024. 9. 8. 21:43

이번 포스팅에선 쿠버네티스 아키텍처에 대해서 Component와 Network, Logging / Monitoring의 관점에서 공부한 내용을 정리해보도록 하겠습니다. 

 

쿠버네티스의 아키텍처를 공부해보니까 각각의 기능들이 어떻게 돌아가는지 조금 더 잘 보이고 앞으로 공부할 때도 더 좋을 것 같네요. 

 

2024-09-08 초안

우선 아키텍처에 대한 글을 쓰고 그림은 나중에 추가할 예정입니다. 

 

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

쿠버네티스의 컴포넌트들은 kube-apiserver, etcd, kube-scheduler, controller manager, kubelet, kube-proxy등이 있습니다. 사실 컴포넌트에 더 많은 오브젝트들이 있는데 일단 이것만 짚고 넘어가겠습니다. 

 

  • kube-apiserver : kube-apiserver는 유저가 kubectl로 날리는 API와 ReplicaSet같은 오브젝트들이 replicas를 늘려 파드를 생성할 때에도 kube-apiserver에 요청합니다. 모든 명령의 가운데서 명령을 받고 이행하는 존재입니다. 
  • etcd : etcd는 kube-apiserver로 들어온 API가 어떤 내용을 포함하고 있는지 쿠버네티스 내부적으로 저장해놓는 데이터베이스입니다. 예를 들어서 CPU와 Memory를 어느정도로 요청하는 파드인지 저장해 놓고 있다가 kubelet에게 전달해주면서 kubelet이 파드를 만들 때 어떻게 파드를 만들어야하는지 참고하는 용도로 쓰입니다. 
  • kube-scheduler : 노드의 자원 상태를 모니터링하고 있는 kube-scheduler는 자신이 수집한 메트릭을 보고 쿠버네티스에 어떤 명령을 내려야하는지 관장합니다. 
  • controller manager : controller manager는 컨트롤러와 관련된 다양한 오브젝트들을 관리하고 있습니다. ReplicaSet이나 StatefulSet, DaemonSet, Deployment, HPA, VPA, CA등등을 관리하죠. controller manager는 kube-scheduler가 수집한 메트릭을 보고 replicas를 늘릴지 말지 결정하고 늘려야한다면 파드를 늘리기위해 kube-apiserver로 요청하는 역할을 수행합니다. 
  • kubelet : kubelet은 워커노드에 포함되어있는 스펙이고 kube-apiserver에 들어온 파드를 늘려달라는 명령을 받아서 etcd에 있는 정보를 바탕으로 파드를 생성해주는 역할을 합니다. 이 과정에서 파드는 쿠버네티스에 있는 개념이지 실제로는 컨테이너로 동작하기 때문에 도커와 같은 런타임에 컨테이너를 관리해주는 플러그인을 이용해서 kubelet이 받은 명령을 도커로 전달해서 컨테이너를 생성합니다. 
  • kube-proxy : kube-proxy는 도커가 만든 컨테이너를 쿠버네티스 네트워크와 연결하는 역할을 수행합니다. 이렇게 만들어진 컨테이너들은 파드의 형태로 쿠버네티스의 관리를 받게되고 쿠버네티스의 네트워크인 Service나 파드간 연결등에 사용할 수 있는 네트워크를 구성해줍니다. 

쿠버네티스의 컴포넌트들에 대해서 대충 알게 되었으니 이제 흐름에 대해서 언급해볼까합니다. 

 

CASE1. 유저가 kubectl 명령어로 파드를 만드는 경우

  1. 파드를 만들라는 API를 kube-apiserver로 보낸다.
  2. etcd가 kube-apiserver를 모니터링하고 있다가 파드를 만들겠다는 명령을 보고 파드를 어떻게 만들 것인지 확인하고 그에 걸맞는 파드 정보를 저장한다.
  3. etcd가 kube-apiserver에 파드를 만들 준비가 되었다고 알려주면 (편의상 준비됐다고 알려준다는 표현을 사용했습니다.) 그 요청을 kubelet이 모니터링하고 있다가 etcd의 설정을 기반으로 파드를 만들고 내부의 컨테이너를 만들기위해 도커에게 명령을 내린다. 
  4. 도커는 kubelet이 내린 명령을 받고 컨테이너를 만든다.
  5. 만들어진 컨테이너는 kube-proxy에 의해 쿠버네티스 클러스터 네트워크의 일부분이 된다. 

CASE2. 파드의 자원이 부족해서 replicas를 늘려야하는 상황

  1. 파드의 자원이 부족해진다.
  2. kube-scheduler가 각 노드의 상황을 모니터링하고 있다가 파드의 자원이 부족한 것을 보고 replicas를 늘려달라고 kube-apiserver에게 요청한다.
  3. replicas에 대한 요청을 모니터링하고 있던 ReplicaSet이 파드를 만들어달라고 kube-apiserver에게 요청한다. 
  4. 그 요청을 보고 있는 etcd는 어떤 파드를 생성해야할지에 대한 정보를 저장한다.
  5. kube-apiserver가 kubelet에게 파드를 생성하라는 명령을 내린다.
  6. kubelet이 파드를 생성하고 필요한 컨테이너를 만들기위해 도커에게 명령을 내린다.
  7. 도커가 컨테이너를 생성한다.
  8. 생성된 컨테이너를 쿠버네티스 네트워크에 편입시키기 위해 kube-proxy가 명령을 수행한다. 

 

쿠버네티스 네트워크 아키텍처

파드가 하나 생성되고 kube-proxy에 의해 네트워크에 편입되게 되면 이 다음 네트워크는 어떻게 구축되고 서로 연결이 어떻게 되어있을까요? 

 

이번 섹션에선 쿠버네티스의 네트워크 아키텍처를 공부해보고 정리해보려합니다. 

 

※ 주의 CS지식인 네트워크 지식을 다루지 않습니다! 

 

Pod Network

파드가 생성되면 Pause Container가 컨테이너 형태로 생성이 되고 이 컨테이너는 자신만의 NIC을 가지고 있고, 자신만의 IP를 가지고 있습니다. 그리고 이 컨테이너는 파드 내부의 네임스페이스에 들어있는 다른 컨테이너들과 연결되어있습니다. 

 

이때 워커노드의 호스트 네트워크에 가상 네트워크를 만들고 이 가상 네트워크가 Pause Container와 연결되어 있어 파드 외부에서 해당 파드에 연결될 수 있습니다. 

 

Pause Container는 본인이 포함된 파드 내부의 컨테이너를 연결시킬 때 포트로 연결시켜준다는 특징이 있습니다. 

 

즉, Pause Container의 IP가 20.111.156.66이고 파드 내부 컨테이너들이 8080, 8081이라면 20.111.156.66:8080 이렇게 연결될 수 있는 것입니다. 

 

위에서 언급한 내용은 같은 노드에서 파드끼리 통신하는 내용이고 만약 노드가 다르다면 어떻게 될까요? 

 

kubenet이라는 쿠버네티스에서 기본적으로 제공해주는 네트워크 플러그인은 워커노드에 Pause Container로 NIC이 만들어지면 호스트 노드의 가상 NIC을 만들어서 모든 파드끼리 묶습니다. 

 

이 가상 NIC들은 Bridge 네트워크로 묶이게 되고 이렇게 묶은 Bridge네트워크는 묶었던 파드보다 더 낮은 대역의 CIDR를 가지고 있습니다. 

 

이 Bridge네트워크는 파드 네트워크의 대역을 참고해서 파드에 IP를 부여해주는데 이때 한 노드에 255개 밖에 만들어지지 않는다는 특징이 있습니다. 이것이 kubenet의 가장 큰 단점이라고 볼 수 있죠. 

 

그리고 kubenet은 Bridge네트워크 위에 라우터 레이어를 만드는데 이 라우터 층에서는 NAT이 파드로 들어오는 네트워크면 Bridge로 내려주고 그 외의 네트워크는 위로 올려줍니다. 

 

Pod Network : Calico

Calico는 네트워크 구현체로서 기본적으로 제공해주는 kubenet보다 더 많은 기능을 가지고 있어서 쿠버네티스에서 많이 사용되는 네트워크 구현체입니다. 

 

Calico를 사용하게 되면 기본 아키텍처에서 조금 달라지게 되는데 파드 네트워크에서 Pause Container가 생성되고 가상 NIC이 호스트 네트워크로 연결되는 것은 동일합니다. 

 

그 이후에 이 가상 NIC을 라우터로 묶어주는데 이 때 라우터의 IP는 파드의 네트워크보다 더 큰 대역폭을 부여받습니다. 

 

만약 서로 다른 노드에 존재하고 있는 파드A와 파드B가 통신하려고 할 때 파드B에서 파드A로 IP요청을 보내면 이 트래픽이 가상 NIC을 통해 Calico의 네틍뤄크로 들어갑니다. 

 

그리고 이 라우터에서 자신이 담당하고 있는 파드들의 IP를 검사해서 있는지 없는지 확인하고 없으면 라우터 위의 계층인 Overlay계층으로 넘겨줍니다. 

 

이때 Overlay계층은 IP Encapsulation이 일어나면서 파드의 IP를 목적지 워커노드의 IP로 바꿔줍니다. Calico는 파드의 IP대역이 어느 노드에 들어있는지 알고 있기 때문에 이런 기능이 가능한 것입니다. 

 

이렇게 다른 노드로 이동된 트래픽이 Overlay계층에서 다시 Decapsulation이 일어나고 이 트래픽이 원하는 목적지 파드까지 연결시켜줍니다. 

 

Service Network

만약 파드와 서비스를 연결한다면 이 둘은 Endpoint라는 오브젝트에 의해 연결이 되어있습니다. 

 

이때 마스터 노드의 kube-apiserver는 Endpoint를 감시하고 있다가 Endpoint가 생성되면 이 정보를 모든 노드에 DaemonSet으로 떠있는 kube-proxy에게 이 정보를 넘겨줍니다. 이 정보에는 서비스의 IP와 파드의 IP가 적혀있습니다. 

 

그리고 워커노드의 iptables에 해당 서비스 대역으로 들어노는 모든 트래픽을 kube-proxy로 들어가라고 지정되어있고 만약 다른 파드에서 이 서비스 대역의 IP로 요청을 보내게 되면 iptables가 kube-proxy로 이 트래픽을 보내주고 kube-proxy는 서비스 IP를 파드의 IP로 바꿔주면서 파드 네트워크 영역으로 보내줍니다. 

 

하지만 이 방식의 단점은 모든 트래픽이 kube-proxy를 지나게 되어있고 kube-proxy는 모든 트래픽을 감당하기엔 성능상 좋지않아서 기본적으로 쿠버네티스는 kube-proxy를 타지 않고 곧바로 iptables에 서비스 IP와 파드 IP를 적어서 보내줍니다. 

 

리눅스에서는 IPVS라는 L4로드밸런서가 제공하는데 쿠버네티스는 설정에서 기본설정인 iptables 대신 IPVS로 설정하게 되면 iptables와 똑같은 역할을 수행해주지만 트래픽이 많아지면 많아질수록 IPVS가 iptables에 비해 더 좋은 성능을 보여준다는 차이점이 있습니다. 

 

Service Network : Calico

만약 이 상태에서 Calico를 사용하게 된다면 서비스를 파드와 연결했을 때 ClusterIP로 연결하게 되면 다른 노드에 존재하는 파드에서 해당 서비스를 요청하게 되었을 때 이 요청이 라우터 계층에 있는 NAT 게이트웨이를 지나게 되고 이 NAT에서 이 요청의 서비스IP를 파드의 IP로 바꿔서 트래픽을 전송합니다. 

 

이 다음의 흐름은 위에서 언급했던 서로 다른 노드간 파드 네트워크와 동일합니다. 

 

하지만 여기서 NodePort로 서비스를 파드와 연결하게 된다면 조금 다른 흐름이 됩니다. 

 

만약 NodePort로 서비스와 파드를 연결하면 모든 노드에 떠있는 kube-proxy가 자신의 노드에 3만번대의 포트를 부여해주고 외부에서 트래픽 요청이 들어오면 iptables가 NAT게이트웨이로 트래픽을 보내고 NAT이 패킷을 까서 어느 목적지로 가야하는지 확인한 다음 쿠버네티스 클러스터의 네트워크를 탱눠서 목적지로 트래픽을 전송합니다. 

 

 

쿠버네티스 Logging / Monitoring 아키텍처

쿠버네티스는 로그와 모니터링을 위한 아키텍처를 가지고 있습니다. 쿠버네티스에서 제공하는 플러그인으로 로깅 / 모니터링을 구현할 수도 있고 외부 플러그인을 이용해서 더 세세하게 모니터링을 구축할 수도 있습니다. 

 

이 두가지 상황을 모두 알아보도록 하겠습니다. 

 

Core Pipeline

Core Pipeline은 일반적으로 쿠버네티스에서 기본적으로 제공해주는 모니터링 시스템입니다. 

 

먼저 시작은 kube-apiserver에서 파드를 만들라는 명령을 받은 kubelet에서 시작됩니다. 

 

kubelet이 명령을 받고 파드를 만들면서 컨테이너를 만들기위해 컨테이너 런타임 플러그인인 도커에게 명령을 내립니다. 

 

이 컨테이너들은 워커 노드에 기본적으로 존재하는 자원을 사용하고 워커 노드의 디스크에 로그를 기록합니다. 

 

도커는 자원을 사용해서 컨테이너를 돌리고 디스크에 로그를 기록하면서 Resource Estimator인 cAdvisor에게 메트릭 정보를 보냅니다. 그럼 이 cAdvisor가 컨테이너 정보들을 모아서 kubelet정보로 정리합니다. (편의상 정리한다고 표현했습니다.)

 

이 kubelet에 대한 정보를 마스터 노드에 존재하는 metrics-server로 보내주고 kube-apiserver가 이 metrics-server에 접근해서 kubectl log / kubectl top 명령어로 노드와 파드의 메트릭을 볼 수 있게 됩니다. 

 

Service Pipeline

쿠버네티스가 기본적으로 제공해주는 메트릭 정보 말고 플러그인을 이용해서 더 자세한 내용을 메트릭으로 받을 수 있습니다.

 

이 플러그인들은 로그를 수집하는 Agent와 로그를 조회하고 분석하는 Metric Aggregator / Analytics가 있습니다. 그리고 이를 UI로 볼 수 있는 Web UI가 존재합니다. 

 

Agent는 도커와 노드의 자원으로부터 로그를 수집해서 Metric Aggregator로 보내고 이를 UI로 보는 것이 일반적인 흐름입니다. 

 

플러그인을 이용한 Service Pipeline은 Elastic 재단에서 나온 ELK스택과, 쿠버네티스에 맞춤형으로 갭라된 Agent인 Fluent-bit을 이용한 EFK스택, 프로메테우스를 이용한 메트릭 모니터링, 그리고 쿠버네티스에서 가장 많이 사용하는 Promtail - Loki - Grafana로 이어지는 PLG스택이 있습니다. 

 

PLG스택은 각 노드에 Promtail이 파드의 형태로 배포되어 있고 이 Promtail이 워커노드에 존재하는 파드에 대한 로그를 참조해서 이 로그들을 Loki로 푸시합니다. 

 

이 때 Loki는 파드의 형태로 존재하고 Loki와 연결된 서비스가 ClusterIP로 되어있습니다. 그리고 이 Service로 Grafana가 API콜을 하면서 메트릭 정보를 UI로 뿌려주는데 이 Grafana도 NodePort로 만들어진 Service와 연결되어 있어서 쿠버네티스 클러스터 내부망으로 이 Grafana에 접근할 수 있습니다. 

 

 

마치며

이번 포스팅에선 쿠버네티스의 아키텍처를 컴포넌트, 네트워크, 로깅 / 모니터링로 나누어 알아봤습니다. 

 

개인적으로 컴포넌트, 네트워크 아키텍처를 공부하면서 굉장히 뿌듯한 느낌이 들었습니다. 조금 더 쿠버네티스를 잘 알게 된 것 같아서 너무 좋았습니다. 

 

긴 글 읽어주셔서 감사합니다. 오늘도 즐거운 하루 되세요!