본문 바로가기

Kubenetes/Kubernetes Internal

쿠버네티스 인터널 - 1 (쿠버네티스 아키텍처)

1. 아키텍처 이해

 

쿠버네티스는 아래 두 부분으로 나뉘어져 있다

 

 ㅇ 쿠버네티스 컨트롤 플레인

 ㅇ 워커노드

 

각 부분은 다음으로 구성되어 있다.

 

ㅁ 컨트롤 플레인 컴포넌트

 

 컨트롤 플레인은 전체 클러스터 기능을 제어하고 만드는 역할을 한다. 컨트롤 플레인을 이루는 컴포넌트는 다음과 같다.

 

 ㅇ etcd 분산 스토리지

 ㅇ API 서버

 ㅇ 스케줄러

 ㅇ 컨트롤 매니저

 

ㅁ 워커노드를 실행하는 컴포넌트

 

 컨테이너를 실행하는 작업은 워커노드에서 실행 중인 컴포넌트에 달려 있다.

 

 ㅇ Kubelet

 ㅇ 쿠버네티스 서비스 프록시 (kube-proxy)

 ㅇ 컨테이너 런타임(도커, rkt, 그 밖의 것들)

 

ㅁ 애드온 컴포넌트

 

 컨트롤 플레인 컴포넌트와 워커노드에 실행 중인 컴포넌트뿐만 아니라 여러 기능에 필요한 클러스터용 애드온 컴포넌트가 있다.

 

 ㅇ 쿠버네티스 DNS 서버

 ㅇ 대시보드

 ㅇ 인그레스 컨트롤러

 ㅇ 힙스터(Heapster)

 ㅇ 컨테이너 네트워크 인터페이스 플러그인

 

1.1  쿠버네티스 컴포넌트의 분산 특성 (nature) 

 

쿠버네티스 컴포넌트인 컨트롤 플레인과 워커노드

 

- 컨트롤 플레인 컴포넌트의 상태 체크

 

API 서버는 ComponentStatus라 불리는 API 리소스를 노출한다. 컴포넌트 상태 API 리소스는 각 컨트롤 플레인 컴포넌트의 건강 상태를 보여준다. Kubectl을 통해 컴포넌트와 상태를 나열해 볼 수 있다.

 

$ kubectl get componentstatuses

NAME.                        STATUS          MESSAGE                 ERROR

scheduler                   Healthy           ok

controller-manager.   Healthy.          ok

etcd-0                        Healthy.         {"health": "true"}

 

ㅁ 컴포넌트와 커뮤니케이션하는 방법

 

쿠버네티스 시스템 컴포넌트는 오직 API 서버와 통신하며 서로 직접 통신하지 않는다.

API 서버는 etcd와 통신할 수 있는 유일한 컴포넌트이다.

 

ㅁ 개별 컴포넌트의 다수의 인스턴스 실행

 

워커노드상의 컴포넌트는 모두 동일한 노드에서 실행할 것을 요구하지만 컨트롤 플레인의 컴포넌트는 다수의 서버로 쉽게 분리될 수 있다.

가용성을 보장하기 위해 각 컨트롤 플레인 컴포넌트에서 인스턴스를 한 개 이상 실행할 수 있다.

 

etcd와 API 서버에서 여러 인스턴스가 동시에 활성화 되고 동시에 작업을 수행하는 동안 스케줄러 및 컨트롤러 매니저는 단일 인스턴스만이 지정된 시간에 활성화 될 수 있으며 다른 인스턴스는 대기 보드로 활성화 된다.

 

ㅁ 컴포넌트의 실행 방법

 

- kube-proxy라 알려진 컨트롤 플레인 컴포넌트는 시스템에 직접 배포될 수도 있고 아니면 포드로서 실행될 수도 있다.

- kubelet은 정규 시스템 컴포넌트로 항상 실행할 수 있는 유일한 컴포넌트

- kubelet은 포드로서 모든 컴포넌트를 실행한다.

- 포드로서 컨트롤 플레인 컴포넌트를 실행하기 위해 Kubelet 또한 마스터에서 배포된다.

 

 ㅇ 포드로 실행하는 쿠버네티스 컴포넌트

 

$ kubectl get po -o custom-columns=POD:metadata.name, NODE:spec.nodeName --sort-by spec.nodeName -n kube-system

 

POD                                                NODE

kube-controller-manager-master.  master      # etcd, API 서버, 스케줄러, 컨트롤 매니저, DNS 서버는 마스터에서 실행된다.

kube-dns-2334855451-37d9k        master

etcd-master                                    master

kube-apiserver-master                   master

kube-scheduler-master                  master

kube-flannel-ds-tgj9k                    node1        # 세 노드는 각각 kube proxy 포드와 Flannel 네트워킹 포드를 실행한다.

kube-proxy-ny3xm                         node1

kube-flannel-ds-0eek8                  node2

kube-proxy-sp362                         node2

kube-flannel-ds-r5yf4                   node3

kube-proxy-og9ac                         node3

 

위 예제에서 보듯이 모든 컨트롤 플레인 컴포넌트는 마스터 노드에서 포드로서 실행된다.

세 개의 워커노드가 있고 워커노드는 포드의 중첩 네트워크를 제공하는 kube-proxy와 Flannel 포드를 실행한다.

 

1.2 쿠버네티스가 etcd를 사용하는 방법 

 

- 쿠버네티스는 빠르고, 분산되며, 일관된 키 값 스토리지인 etcd를 사용

- 분산돼 있으므로 둘 이상의 인스턴스를 실행해 고가용성과 우수한 성능을 모두 제공할 수 있다.

- etcd와 직접 통신하는 유일한 컴포넌트는 쿠버네티스 API 서버

- 다른 모든 컴포넌트는 API 서버를 통해 간접적으로 etcd에 데이터를 읽고 쓴다.

  ㅇ 강력한 낙관적 잠금 시스템

  ㅇ 유효성 검사

  ㅇ 스토리지 매커니즘 추상화

  ㅇ 쿠버네티스 클러스터 상태 및 메타 데이터를 저장하는 유일한 장소

 

더보기

낙관적 동시성 제어(Optimistic Concurrency Control)에 관하여

낙관적 동시성 제어(때론 낙관적 잠금(Optimistic Lock)이라 부르는)는 데이터를 잠그고 읽지 못하도록 하는 대신 잠금이 필요한 위치에서 데이터가 업데이트되면 해당 데이터 조각에 버전 번호를 포함하는 것이다. 매번 데이터가 업데이트되면 버전 번호도 증가된다. 데이터가 업데이트됐을 때 클라이언트가 데이터를 읽은 시간과 업데이트가 요청된 시간 사이에 버전 번호가 증가됐는지 체크를 한다. 증가했다면 업데이트는 거절되고 클라이언트는 새로운 데이터를 다시 읽고 업데이트를 다시 시도한다.

그 결과 두 클라이언트가 같은 데이터에 업데이트를 시도했을 때 오직 하나의 클라이언트만 성공하게 된다.

모든 쿠머네티스 리소스에는 metadata.resourceVersion 필드가 있다. 클라이언트는 오브젝트를 업데이트할 때 API 서버로 다시 전달해야 한다. 버전이 etcd에 저장된 것과 일치하는 것이 없다면 API 서버는 갱신을 거절한다.

 

ㅁ etcd에 리소스를 저장하는 방법

 

- etcd의 값 키는 다른 키를 포함하고 있는 디렉터리 이거나 대응하는 값을 가지는 정규 키일 수 있다.

- etcd v3는 디렉터리를 지원하고 있지 않지만 키 형식이 동일하게 남아 있기 때문에 디렉터리로 키를 그룹핑할 수 있다고 생각할 수 있다.

- 쿠버네티스는 /registry 밑에 etcd에 모든 데이터를 저장한다.

 

 ㅇ 쿠버네티스의 etcd에 저장된 최상단의 엔트리 목록

 

$ etcdctl ls /registry

/registry/configmaps

/registry/daemonsets

/registry/deployments

/registry/events

/registry/namespaces

/registry/pods

...

 

etcd에 있는 각 키가 각 리소스 타입에 대응한다는 것을 알 수 있다.

 

etcd v3의 API에서 ls 명령을 사용해 디렉터리를 살펴볼 수 없다. 대신 etcdctl get /registry --prefix=true 명령을 사용해 모든 키를 열거 할 수 있다.

 

 ㅇ /registry/pods 디렉터리 내용

 

$ etcdctl ls /registry/pods

/registry/pods/default

/registry/pods/kube-system

 

이름에서 알 수 있듯이 두 엔트리는 default와 kube-system 네임스페이스와 대응된다.

이 것은 포드가 네임스페이스 각각에 저장됨을 뜻한다.

 

 ㅇ default 네임스페이스에 있는 포드의 etcd 엔트리 목록

 

$ etcdctl ls /registry/pods/default

/registry/pods/default/kubia-15904137-xk9vc

/registry/pods/default/kubia-15904137-wt6ga

/registry/pods/default/kubia-15904137-hp29d

 

각 엔트리는 각 포드에 대응하며 디렉터리가 아니라 키/값 형식이다.

 

 ㅇ 포드를 나타내는 etcd 엔트리

 

$ etcdctl get /registry/pods/default/kubia-159032478-erd8d

{"kind":"Pod", "apiVersion":"v1","metadata":{"name":"kubia-1590143478-=wt56d", "generateNAme":"kubia-15901347-","namespace":"default", "selfLink":...

 

JSON 형식의 포드 정의 이상 아무것도 아님을 알 수 있다. API 서버는 etcd에 전체 JSON 형식의 리소스를 저장한다. etcd는 계층적 키 스토리지이기 때문에 파일 시스템처럼 모든 리소스가 JSON 파일로 저장된다고 생각할 수 있다.

 

ㅁ 저장된 오브젝트의 일관성과 유효성 보장

 

 - 모든 컴포넌트는 충돌을 올바르게 처리하기 위해 모두 동일한 낙관적 잠금 메커니즘을 준수해야 한다.

 - 쿠버네티스는 모든 컴트롤 플레인 컴포넌트가 API 서버를 통과하도록 요구함으로써 이를 향상 시킨다.

 - 낙관적 잠금 메커니즘은 단일 위치에서 구현되기 때문에 이러한 클러스터 상태 업데이트는 항상 일관적이다.

 

ㅁ etcd가 클러스터가 될 때 일관성 보장

 

 고가용성을 보장하기 위해 일반적으로 etcd 인스턴스를 하나 이상 실행한다.

 여러 개의 etcd 인스턴스가 일관성을 유지할 필요가 있다. 그러한 분산 시스템은 실제 상태가 무엇인지에 대한 합의에 도달해야한다.

 etcd는 이를 달성하기 위해 RAFT 합의 알고리즘을 사용

 이는

   [주어진 순간에 각 노드의 상태가 대부분의 노드가 동의한 현재의 상태이거나 이전에 합의한 상태 중 하나임을 확인]

한다.

합의 알고리즘은 클러스터가 다음 상태로 전환될 수 있도록 과반수(또는 쿼럼)를 요구한다.

그 결과 클러스터가 두 개의 연결 해제 노드 그룹으로 분할되면 두 그룹의 상태가 서로 바뀔수는 없다.

 

분할된 브레인의 경우 오직 과반수(쿼럼)을 갖는 한쪽 그룹에서만 상태 변화를 수용할 수 있다.

 

클러스터가 두 개의 연결 해제 노드 그룹으로 분할되면 두 그룹의 상태가 서로 바뀔 수는 없다. 

이전 상태에서 새로운 상태로 전환하려면 상태 변화에 참여하는 절반 이상의 노드가 필요해진다.

하나의 그룹이 모든 노드의 대부분을 포함하는 경우라면 다른 그룹은 그렇게 될 수 없다.

 

위 그림의 첫번째 그룹은 클러스터 상태를 수정할 수 있지만 다른 그룹은 수정할 수가 없다. 두 개의 그룹이 다시 연결되면 두 번째 그룹은 첫 번째 그룹의 상태를 따라 잡을 수 있다.

 

1.3 API 서버가 하는 일

 

- 쿠버네티스 API 서버는 모든 컴포넌트와 kubect 같은 클라이언트가 사용하는 중앙 컴포넌트

- RESTful API를 통해 클러스터 상태를 쿼리 및 수정할 수 있는 CRUD 인터페이스를 제공한다.

- 그 상태를 etcd 안에 저장한다.

- 객체 등을 저장하는 일관된 방법을 제공하는 것 외에도, 객체의 유효성 검사도 수행하므로 클라이언트가 잘못 구성된 객체를 저장할 수 없다.

- 검증과 함께 낙관적인 잠금도 처리하므로 동시 업데이트 시 다른 클라이언트가 객체의 변경 사항을 재정의하지 않는다.

API 서버의 동작

 

ㅁ 인증 플러그인(Authentication Plugin)을 사용한 클라이언트 인증

 

첫째로 API 서버는 요청을 보낸 클라이언트를 인증해야 한다. 이 것은 API 서버에서 환경 설정된 하나 혹은 그 이상의 인증 플러그인을 통해 수행된다.

누가 요청을 보냈는지 결정할 때까지 API 서버는 차례로 플러그인을 호출한다. HTTP 요청을 검사해 이 작업을 수행한다.

 

ㅁ 권한 승인 플러그인(Authorization Plugin)을 통한 클라이언트 승인

 

인증 플러그인과 마찬가지로 API 서버는 하나 혹은 그 이상의 권한 승인 플러그인을 사용할 수 있도록 설정한다.

API 서버의 역할은 인증된 사용자가 요청된 액션을 요청된 리소스에 대해 수행할 수 있는지 결정하는 것이다.

 

ㅁ 승인 제어 플러그인(Admission Control Plougin)을 통해 요청 받은 리소스를 확인/수정

 

요청이 리소스를 생성, 수정, 삭제한다면 요청은 승인 제어 플러그인으로 보내진다.

서버는 다수의 승인 제어 플러그인이 동작하도록 설정되며 이 플러그인은 여러 이유로 리소스를 수정할 수 있다.

이 플러그인은 리소스 스펙의 누락된 필드를 디폴트 값이나 값을 상속받아 초기화해버리기도 하며 요청과는 무관한 연관된 리소스를 수정해 버릴 수도 있다.

 

승인 제어 플러그인의 예는 다음과 같다.

 

 ㅇ AlwaysPullImage: 포드를 배포될 때마다 이미지가 Pull 되도록 포드의 imagePullPolicy를 Always로 오버라이드 한다.

 ㅇ ServerAccount:  명시적으로 설정하지 않은 포드로 기본 서비스 계정을 적용한다.

 ㅇ NamespaceLifeCycle: 존재하지 않는 네임스페이스뿐만 아니라 삭제될 네임스페이스상의 포드 생성을 방지한다.

 ㅇ ResourceQuota: 특정 네임스페이스의 포드가 네임스페이스에 할당된 만큼의 CPU 및  메모리만 사용하는지 확인

 

https://kubernetes.io/docs/admin/admission-controllers/에서 승인 제어 플러그인의 목록을 조금더 확인 가능하다.

 

Using Admission Controllers

This page provides an overview of Admission Controllers. What are they? An admission controller is a piece of code that intercepts requests to the Kubernetes API server prior to persistence of the object, but after the request is authenticated and authoriz

kubernetes.io

ㅁ 리소스 검증 및 영구 저장 (Resource Validation)

 

요청이 모든 승인 제어 플러그인을 통과하면 API 서버는 오브젝트의 유효성을 검사하고 이를 etcd에 저장한 다음 클라이언트에 응답을 전달한다.

 

1.4 API 서버가 클라이언트의 리소스 변화를 감지하는 방법

 

 API 서버는 위에서 설명한 일 외에는 아무것도 하지 않는다. API 서버는 컨트롤러에게 무엇을 해야 할지 알려 주지 않는다. 오직 하는 일은 배포된 리소스의 변화를 감지하기 위해 컨트롤러와 다른 컴포넌트를 활성화 하는 것이다.

 컨트롤 플레인 컴포넌트는 리소스가 생성, 수정, 삭제 됐을 때 통지하도록 요청할 수 있고 이렇게 해서 컴포넌트가 클러스터 메타 데이터의 변경에 따라 필요한 작업을 수행한다.

 

오브젝트가 갱신됐을 때 API 서버는 모든 감시자에게 갱신된 오브젝트를 전송한다.

 

 ㅇ 생성되고 삭제되는 포드 감시하기

 

포드가 배포됐을 때 kubetctl get pods 명령을 반복 실행 해 포드의 목록을 계속해서 얻을 필요가 없다. --watch 옵션으로 계속 감시할 수 있다.

 

 $ kubectl get pods --watch

 

또한 전체 YAML을 출력할 수 도 있다.

 

$ kubectl get pods -o yaml --watch

 

1.5 스케줄러 이해

 

스케줄러는 선택된 노드에 포드를 실행하도록 지시하지 않음. 스케줄러가 하는 것은 API 서버를 통해 포드의 정의를 업데이트하고 API 서버는 포드가 스케줄 됐다고 kubelet에게 통지한다.

대상 노드의 kubelet이 포드가 해당 노드에 스케줄됐음을 확인하자마자 포드의 컨테이너를 생성하고 실행한다.

 

ㅁ 기본 스케줄러 알고리즘

 

 ㅇ 포드가 스케줄될 수 있는 모든 노드의 목록을 얻기 위해 모든 노드의 목록을 필터링 한다.

 ㅇ 허용하는 노드를 우선순위화하고 최적의 노드를 선택한다. 다수의 노드가 가장 높은 접수를 갖는다면 포드가 공평하게 모두 배포될 수 있도록 라운드 로빈이 사용된다.

 

스케줄러는 포드를 위한 수용할 수 있는 노드를 찾는다. 그리고 나서 포드를 위한 최적의 노드를 선택한다.

 

ㅁ 수용 가능한 노드 찾기

 

포드를 수용 가능한 노드인지 결정하기 위해 스케줄러는 구성된 각 함수 목록을 통해 각 노드로 전달하며 다음과 같은 다양한 것들을 체크한다.

 ㅇ 노드가 포드의 하드웨어 리소스 요청을 이행할 수 있을까?

 ㅇ 노드에 리소스가 부족한가? (메모리나 디스크의 부족 상태를 보고 하는가?)

 ㅇ 포드가 이름으로 특정 노드에 스케줄되도록 요청하는 경우, 이것은 노드인가?

 ㅇ 노드가 포드 스펙의 노드 셀렉터에 맞는 라벨을 가질 수 있을까?(하날도 정의돼 있다면)

 ㅇ 포드가 특정 호스트 포트에 바인딩을 요청하는 경우 해당 노드에 포트가 이미 사용되고 있는가?

 ㅇ 포드가 특정 볼륨 유형을 요청하는 경우, 이 볼륨을 노드의 포드에 마운트할 수 있는가? 아니면 노드의 다른 포드에서 이미 동일한 볼륨을 사용하고 있는가?

 ㅇ 포드는 노드의 테인트(taints)를 허용하는가?

 ㅇ 포드가 노드나 포드의 치노하성과 비친화성 규칙을 설정할 수 있을까? 그렇다면 이 규칙을 어길 수 있는 노드로 포드가 스케줄 돼야 한다.

 

노드가 포드를 호스트하려면 위의 모든 사항이 통과돼야 한다.

 

ㅁ 포드를 위한 최적의 노드 선택

 

수용 가능한 노드 사이에도 우선순위를 정하여 노드를 선택할 수 있다. 이미 10개의 포드를 실행하고 있는 노드와 어떤 포드도 실행하고 있지 않은 노드 중 두번째 노드를 선호해야 한다는 것은 명백하다.

 

1.6 컨트롤러 매니저에서 실행되고 있는 컨트롤러 소개

 

API 서버는 etcd에 리소스를 저장하고 변경 사항을 클라이언트에게 통지하는 것 외에는 아무것도 하지 않는다. 따라서 API 서버를 통해 배포된 리소스에 지정된 대로 시스템의 실제 상태가 원하는 상태로 수렴되는지 확인하려면 다른 활성화된 컴포넌트가 필요하다.

이것은 컨트롤러 매니저에서 실행되는 컨트롤러에 의해 수행된다.

 

단일 컨트롤러 매니저 프로세스는 다양한 조정 작업을 수행하는 다수의 컨트롤러를 결합한다. 다수의 컨트롤러 목록은 다음과 같다.

 

 ㅇ 레플리케이션 매니저(레플리케이션컨트롤러 리소스의 컨트롤러)

 ㅇ 레플리카셋, 데몬셋, 잡 컨트롤러

 ㅇ 디플로이먼트 컨트롤러

 ㅇ 스테이트풀셋 컨트롤러

 ㅇ 노드 컨트롤러

 ㅇ 서비스 컨트롤러

 ㅇ 엔드포인트 컨트롤러

 ㅇ 네임스페이스 컨트롤러

 ㅇ 그 밖의 컨트롤러

 

ㅁ 컨트롤러가 하는 일과 동작 방식

 

모든 API 서버는 리소스의 변경을 감시하고 새로운 오브젝트를 생성하거나 갱신, 삭제 등의 변경을 위한 동작을 수행한다. 대부분의 시간 동안 이런 동작은 리소스를 생성하거나 감시하는 리소스의 갱신을 포함한다.

 

일반적으로 컨트롤러는 조정 루프를 실행해 실제 상태를 원하는 상태(리소스의 Spec 섹션에 지정된)와 조정하고 새로운 실제의 상태를 리소스의 Status 색션에 쓴다.

 

컨트롤러는 감시 매커니즘을 사용해 변경 사항을 통지하지만 감시 매커니즘을 사용한다고 컨트롤러가 이벤트를 좋치지 않는다고 보장할 수 없기 때문에 주기적으로 목록 작업을 수행해 이벤트를 놓치지 않았는지 확인한다.

 

컨트롤러는 절대로 직접 서로 통신하지 않는다. 컨트롤러는 심지어 서로의 존재도 알지 못한다. 각 컨트롤러는 API 서버에 연결하고 감시 메커니즘을 통해 컨트롤러가 책임지는 어떤 타입의 리소스의 목록에 변화가 감지됐을 때 통지해줄 것을 요청한다.

 

ㅁ Replication 매니저

 

레플리케이션 컨트롤러 리소스를 움직이게 하는 컨트롤러를 레플리케이션 매니저라고 한다.

래플리케이션 컨트롤러가 동작하는 실제 작업을 하는 것은 레플리케이션 컨트롤러가 아니라 레플리케이션 매니저이다.

 

API 서버는 원하는 복제본 개수와 매칭되는 포드의 수에 영향을 줄 가능성이 있는 변화마다 감시 매커니즘에 의해 통지가 된다. 이런 변경으로 컨트롤러가 원하는 복제본 수와 실제 복제본 수를 다시 확인하고 동작한다.

 

레플리케이션 매니저는 API 오브젝트의 변화를 감시한다.

 

실행하고 있는 포드 인스턴스가 너무 적으면 레플리케이션컨트롤러가 추가로 인스턴스를 실행한다는 것을 이미 알고 있다. 하지만 실제로 그 자신을 실행하지 않는다. 새로운 포드 매니페스트를 생성해 API 서버에 게시하면 스케줄러가 스케줄링을, kublet이 포드를  실행할 수 있게 해준다.

 

레플리케이션 매니저는 API 서버에서 포드 API 오브젝트를 조작해 작업을 수행한다. 이것이 모든 컨트롤러가 동작하는 원리이다.

 

ㅁ Replicaset, Daemonset, Job 컨트롤러

 

레플리카셋 컨트롤러는 레플리케이션 매니저와 거의 동일하다. 데몬셋과 잡컨트롤러 역시 비슷하다. 이들은 포드 리소스를 각 리소스에 정의된 포드 템플릿에서 만드다. 레플리케이션 매니저와 달리 이들 컨트롤러는 포들르 실행하지는 않지만 포드 정의를 API 서버로 포스트 하여 kubelet이 컨테이너를 생성하고 실행하게 만든다.

 

ㅁ Deployment 컨트롤러

 

디플로이먼트 컨트롤러는 디플로이먼트 API 객체에 지정된 상태와 동기를 맞추기 위해 실제 디플로이먼트 상태를 유지 관리한다.

 

디플로이먼트 컨트롤러는 디플로이먼트 오브젝트가 수정될 때마다 새로운 버전으로 롤아웃한다. 레플리카셋을 생성해 롤아웃하고 오래된 포드가 새로운 것으로 교체될 때까지 배포에 지정된 전략을 기반으로 예전의 레플리카셋과 새로운 레플리카셋을 적절히 스케일링 한다. 어떤 포드라도 직접 생성하지는 않는다.

 

ㅁ Statefulset 컨트롤러

 

스테이트풀셋 컨트롤러는 레플리카셋 컨트롤러 및 기타 관련 컨트롤러와 유사하며 스테이트풀셋 리소스의 스펙에 따라 포드를 생성, 관리, 삭제 한다.

 

그러나 다른 컨트롤러가 포드만 관리하는 동안 스테이트풀셋 컨트롤러는 각 포드 인스턴스의 PersistentVolumeClaim을 인스턴스화하고 관리한다.

 

ㅁ Node 컨트롤러

 

노드 컨트롤러는 클러스터의 워커 노드를 작성하는 노드 리소스를 관리한다. 무엇보다도 노드 컨트롤러는 클러스터에서 실행 중인 실제 시스템 목록과 노드 오브젝트 목록을 동기화 상태로 유지한다. 또한 각 노드의 상태를 모니터하고 연결할 수 없는 노드의 포드를 제거한다.

 

노드 컨트롤러는 노드 오브젝트를 변화시키는 유일한 컴포넌트는 아니다. 이런 동작은 kubelet도 변화시킬 수 있고 REST API 호출해 사용자가 직접 수정할 수도 이다.

 

ㅁ Service 컨트롤러

 

서비스 컨트롤러는 LoadBalancer 유형의 서비스가 생성되거나 삭제될 때 인프라스트럭처로부터 로드 밸런서를 요청하고 릴리스하는 역할을 유일하게 수행한다.

 

ㅁ Endpoint 컨트롤러

 

서비스는 포드와 직접 연결되지 않는 대신 IP와 포트 같은 서비스에 정의된 포드 셀렉터에 따라서 직접 혹은 자동으로생성하고 갱신할 수 있는 엔드포인트의 목록을 포함하고 있다. 

 

엔드포인트 컨트롤러는 라벨 셀렉터와 매칭되는 IP와 포트를 끊임없이 갱신해 엔드포인트 목록을 유지하는 컴포넌트이다.

 

엔드포인트 컨트롤러는 서비스와 포드 리소스를 감시하고 엔드포인트를 관리한다.

 

서비스가 추가되거나 갱신되거나 포드가 추가되거나 갱신되거나 삭제될 때 서비스의 포드 셀렉터와 매칭되는 포드를 선택하고 해당 IP와 포트를 엔드포인트 리소스에 추가한다.

 

엔드포인트 객체는 독립 실행형 객체이므로 필요한 경우 컨트롤러가 해당 객체를 생성한다. 반면에 서비스가 지워질 때 엔드포인트 객체도 삭제한다.

 

ㅁ 네임스페이스 컨트롤러

 

네임스페이스 리소스가 삭제될 때 네임스페이스에 속한 리소스는 반드시 모두 삭제돼야 한다. 이것이 네임스페이스가 하는 일이다.

네임스페이스 오브젝트의 삭제를 통지할 때 API 서버를 통해 네임스페이스에 속한 리소스를 삭제

 

ㅁ PersistentVolume 컨트롤러

 

사용자가 PersistentVolumeClaim을 한 번 생성하면 쿠버네티스는 적절한 PersistentVolume을 찾아 클레임에 바인드해야 한다. 이것은 PersistentVolume 컨트롤러가 하는 일이다.

 

PersistentVolumeClaim이 나타나면 컨트롤러는 요청한 용량과 일치하는 액세스 모드와 요청한 용량 이상의 선언 용량을 가진 최소 영구 볼륨을 선택해 클레임과 가장 일치하는 가장 작은 영구 볼륨을 찾는다.

 

용량을 오름차순하고 목록에서 첫번째 볼륨을 반환해 각 액세스 모드의 영구 볼륨 목록을 순서대로 유지함으로써 가능하다. 

 

ㅁ 컨트롤러 정리

 

모든 컨트롤러는 API를 통해 API 오브젝트를 가지고 동작한다. 컨트롤러는 Kubelets와 직접 통신하거나 어떤 종류의 명령도 내리지 않는다. 사실 Kubelets가 존재하는지조차 알지 못한다.

 

컨트롤러가 API 서버에서 리소스를 업데이트한 이후로 kubelets와 쿠버네티스 서비스 프록시는 컨트롤러의 존재를 알지 못한다. 그리고 포드의 컨테이너를 회전시키고 네트워크 스토리지에 연결하거나 포드의 실제 로드 밸런싱을 설정한다.

 

컨트롤 플레인은 전체 시스템 동작의 한 부분을 처리하므로, 이런 동작이 쿠버네티스 클러스터에서 어떻게 펼쳐지는지 완전히 이해하려면 kubelet과 쿠버네티스 서비스 프록시가 하는 일을 알아야 한다.

 

1.7 Kubelet이 하는 일

 

쿠버네티스 컨트롤 플레인의 일부이고 마스터 노드에서 실행되는 모든 컨트롤러와 대조적으로 kubelet과 서비스 프록시는 워커노드에서 실행된다. 

 

ㅁ Kubelet의 잡 이해

 

netshell에서 kubelet은 워커노드에서 실행되는 모든 것에 책임을 가지는 컴포넌트이다. kubelet의 초기 작업은 API 서버에서 노드 리소스를 생성해 실행하고 있는 노드를 등록하는 것이다. 그리고 나서 노드에 스케줄 됐던 포드에 대해 API 서버를 모니터링해야 한다.

그 이후 포드의 컨테이너를 시작한다.

 

설정된 컨테이너 런타임(도커, 코어OSdml rkt등)에 특정 컨테이너 이미지에서 컨테이너를 실행하도록 지시함으로써 이런 작업을 수행한다. 그런 후에 kubelet은 지속적으로 실행중인 컨테이너를 모니터링하고 상태와 이벤트, 리소스의 소모를 API 서버에게 보고한다.

 

Kubelet도 컨테이너 라이브니스 프로브를 실행하는 컴포넌트이며 프로브가 오류가 발생했을 때 컨테이너를 다시 시작한다. 마지막으로 포드가 API 서버에서 삭제됐을 때 컨테이너를 정지하고 포드가 정지됐을 때 서버에게 통지한다.

 

ㅁ API  서버 없이 정적 포드 실행

 

kubelet이 쿠버네티스 API 서버와 통신하고 그곳에서 포드 매니페스트를 얻더라도 아래와 같은 특정 로컬 디렉터리에 위치한 포드 매니페스트 파일에 기반해 포드를 실행할 수 있다.

 

kubelet은 API 서버와 로컬 파일 디렉터리의 포드 스팩에 기 반하여 포드를 실행한다.

 

사용자 정의 시스템 컨테이너도 이와 같은 방법으로 실행할 수 있지만 데몬셋을 사용하는 방식을 추천한다.

 

1.8 쿠버네티스 서비스 프록시의 역할

 

kubelet 외에도 모든 워커 노드는 Kube-proxy를 실행하는 데, 쿠버네티스 API를 통해 정의한 서비스에 클라이언트가 연결할 수 있도록 하는 것이 목적이다.

kube-proxy는 서비스 IP 및 포트 연결을 보장한다. 서비스가 둘 이상의 포드로 백업되면 프록시는 해당 포드에서 로드 밸런싱을 수행한다.

 

ㅁ 프록시로 불리는 이유

 

kube-proxy의 초기 구현은 유저스페이스 프록시(userspace proxy) 였다. 실제 서버 프로세스가 연결을 수락하고 이를 포드로 전달했다. 서비스 IP로 향하는 연결을 가로채기 위하여 프록시는 iptables 규칙(iptables는 리눅스 커널의 패킷 필터링 기능을 관리하기 위한 도구다)을 다음과 같이 구성한다.

 

userspace 프록시 모드

 

kube-proxy는 실제 프록시였기 때문에 그런 이름이 주어졌지만, 현재는 훨씬 더 나은 성능 구현을 위해 iptables 규칙을 사용해 패킷을 실제 프록시 서버를 통과시키지 않고 무작위로 선택한 백엔드 포드로 리다이렉션 한다.

 

이 모드를 iptables 프록시 모드라 부른다.

iptables 프록시 모드

이 두가지 모드 간의 주요 차이는 패킷을 kube proxy를 거쳐 유저스페이스에서 처리하느냐 아니면 오직 커널에서만 처리하느냐이며 이것은 성능상의 주요 쟁점이다.

 

1.9 쿠버네티스 애드온 소개

 

ㅁ 애드온의 배포 방식

 

애드온 목록은 항상 필요하지 않지만 쿠버네티스 서비스의 DNS 룩업, 단일 외부 IP 주소를 통한 다중 HTTP 서비스를 노출, 쿠버네티스 웹 대시보드등의 기능을 활성화한다.

 

이런 컴포넌트들은 애드온으로 사용이 가능하고 YAML 매니페스트를 API 서버로 제출하여 포드처럼 배포된다. 컴포넌트의 일부는 디플로이먼트 리소스, 레플리케이션 리소스 그리고 또 일부는 데몬 세트를 배포된다.

 

인그레스 컨트롤러와 대스보드 애드온은 다음 예제에 표시된 것 처럼 레프리케이션 컨트롤러로 배포된다.

 

$ kubectl get rc -n kube-system

NAME                                    DESIRED          CURRENT        READY         AGE

default-http-backend            1                      1                       1                  6d

kubernetes-dashboard          1                       1                      1                  6d

nginx-ingress-controller        1                      1                       1                  6d

 

DNS 애드온은 디플로이먼트에서 배포된다

 

$ kubectl get deploy -n kube-system

NAME        DESIRED       CURRENT     UP-TO-DATE     AVAILABLE   AGE

kube-dns   1                    1                   1                          1                    6d

 

ㅁ DNS 서버의 동작 원리

 

클러스터의 모든 포드는 클러스터의 내부 DNS 서버를 사용하도록 구성하는 것이 기본이다.

헤드리스 서비스의 경우에 이것은 포드가 이름이나 포드의 IP 주소를 가지고 서비스를 쉽게 찾는 것을 허용한다.

 

DNS 서버 포드는 kube-dns 서비스를 통해 익스포트 하고 다른 포드들 처럼 포드가 클러스터상에서 이동할 수 있게 한다. 서비스의 IP 주소는 클러스터에 배포되는 매 컨테이너 내부의 /etc/resolved.conf 파일의 nameserver에 지정된다.

 

kube-dns 포드는 API 서버의 서비스와 엔드포인트로의 변화를 관찰하기 위해 감시 메커니즘을 사용하고 변화할 때 마다 DNS 레코드를 갱신하여 클라이언트가 항상 최신의 DNS 정보를 얻을 수 있게 한다.

 

ㅁ 대다수 인그레스 컨트롤러의 동작 방식

 

인그레스 컨트롤러는 Nginx와 같은 역프록시 서버를 실행하고 클러스터의 인그레스, 서비스 앤드포인트 리소스에 따라 구성을 유지한다.

거기에 더해 컨트롤러는 리소스를 모니터링해야 하고(감시 메커니즘을 통해) 리소스가 변경될 때마다 프록시 서버의 구성도 변경된다.

 

인그레스 리소스의 정의가 서비스를 향하더라도 인그레스 컨트롤러는 트래픽을 서비스 IP를 통해 가는 것 대신에 서비스의 포드로 직접 전달한다. 이것은 외부 클라이언트가 인그레스 컨트롤러를 통해 연결할 때 클라이언트 IP의 보존에 영향을 준다. 이는 특정 경우에 서비스를 통하는 것을 선호하도록 만든다.