11. 쿠버네티스 내부 이해
11.1 쿠버네티스 아키텍처 이해
쿠버네티스 클러스터 구성요소
- 컨트롤 플레인
- (워커)노드
컨트롤 플레인 구성요소
클러스터 기능을 제어하고 전체 클러스터가 동작하게 만드는 역할을 한다.
- etcd 분산 저장 스토리지
- API 서버
- 스케줄러
- 컨트롤러 매니저
워커 노드에서 실행하는 구성 요소
- kubelet
- 쿠버네티스 서비스 프록시(kube-proxy)
- 컨테이너 런타임(Docker, rkt 외 기타)
애드온 구성 요소
컨트롤 플레인과 노드에서 실행되는 구성 요소 외에 클러스터에서 추가 기능을 위한 구성 요소
- 쿠버네티스 DNS 서버
- 대시보드
- 인그레스 컨트롤러
- 힙스터
- 컨테이너 네트워크 인터페이스(CNI) 플러그인
11.1.1 쿠버네티스 구성 요소의 분산 특성
- 모든 구송요소는 개별 프로세스로 실행된다.
컨트롤 플레인 구성 요소의 상태 확인
1
kubectl get componentstatuses
구성 요소가 서로 통신하는 방법
- 쿠버네티스 시스템 구성 요소는 오직 API 서버하고만 통신한다.
- API서버는 etcd와 통신하는 유일한 구성 요소
- 클러스터 내에 존재하는 리소스 데이터는 etcd에 저장되고, 이를 처리하는건 API 서버라는 의미
- kubectl을 이용해 로그를 가져오거나 attach 명령으로 실행중인 컨테이너에 연결할 때와 kubectl port-forward명령을 실행할때는 API 서버가 kubelet에 접속한다.(이 경우를 제외하고는 모두 API서버로만 요청한다)
개별 구성 요소의 여러 인스턴스 실행
- 컨트롤 플레인의 구성 요소는 여러 서버에 걸쳐 실행될 수 있다.
- 컨트롤 플레인 구성 요소 중 etcd와 API 서버는 둘 이상 실행해 가용성을 높일수도 있다.
- 스케줄러와 컨트롤러 매니저는 하나의 인스턴스만 활성화되고 나머지는 대기 상태로 존재한다.
구성 요소 실행 방법
- kube-proxy와 같은 컨트롤 플레인 구성 요소는 시스템에 직접 배포하거나 파드로 실행할 수 있따.
- kubelet은 항상 시스템 구성 요소로 실행되는 유일한 구성 요소이며, kubelet이 다른 구성 요소를 파드로 실행한다.
- etcd, API 서버, 스케줄러, 컨트롤러 매니저, DNS 서버는 마스터에서 실행되고, kube-proxy, flannel 네트워킹 파드는 워커노드에서 실행된다.
1
2
# 쿠버네티스 구성요소의 pod의 node 확인
kubectl get po -o custom-columns=POD:metadata.name,NODE:spec.nodeName --sort-by spec.nodeName -n kube-system
11.1.2 쿠버네티스 etcd를 사용하는 방법
- API 서버가 다시 시작되거나 실패하더라도 유지하기 위해 매니페스트가 영구적으로 저장될 필요가 있다.
- 그래서 쿠버네티스는 빠르고, 분산해서 저장되며, 일관된 키-값 저장소를 제공하는 etcd를 사용한다.
- 둘 이상의 etcd 인스턴스를 실행해 고가용성과 우수한 성능을 제공할 수 있다.
- 쿠버네티스가 클러스터 상태와 메타데이터를 저장하는 유일한 장소는 etcd뿐이다.
낙관적 동시성 제어
- 데이터 조작에 잠금을 설정해 그동안 데이터를 읽거나 업데이트하지 못하도록 하기 위해서 데이터 업데이트시 버전 번호를 포함시키는 방법.(DB에서의 낙관적 락과 동일한 방식)
리소스를 etcd 저장하는 방법
- etcd v2는 키를 계층적 키 공간에 저장해 파일시스템의 파일과 유사한 키-값 쌍을 만든다.(디렉터리 존재)
- etcd v3는 디렉터리를 지원하지 않지만 키 형식은 동일하게 유지되고, 키 이름에 ‘/’을 포함시켜서 마치 디렉터리인것처럼 사용 가능하다.
- etcd v3에서는 ls를 사용할 수 없고
etcdctl get /registry --prefix=true
을 이용하면 v2와 동일한 결과를 얻을 수 있다.
- etcd v3에서는 ls를 사용할 수 없고
- etcd의 계층적 키 공간때문에, 저장된 모든 리소스를 단순하게 파일시슽메에 있는 JSON 파일로 생각할 수 있다.
- 쿠버네티스 1.7 이전에 Secret 리소스의 JSON을 암호화없이 etcd에 저장했으나 1.7부터 암호화하여 저장하고 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# etcd registry 하위 조회
etcdctl ls /registry
/registry/configmaps
/registry/daemonsets
/registry/deployments
/registry/events
/registry/namespaces
/registry/pods
# deafult 네임스페이스의 pods 조회
etcdctl ls /registry/pods/default/
# deafult 네임스페이스의 pods 조회
etcdctl ls /registry/pods/default/
# deafult 네임스페이스의 pod 중 하나의 상세 정보 조회
etcdctl get /registry/pods/default/kubia-159041347-wt6ga
...
저장된 오브젝트의 일관성과 유효성 보장
- 쿠버네티스는 다른 모든 구성 요소가 API 서버를 통하도록 함으로써 데이터 불일치를 방지하고 일관성을 유지한다.
- API 서버 한곳에서 낙관적 락 매너키즘을 구현해서 클러스터의 상태를 업데이트하기 때문에, 오류가 발생할 가능성을 줄이고 항상 일관성을 가질 수 있다.
클러스터링된 etcd의 일관성 보장
- 고가용성을 위해 2개 이상의 etcd 인스턴스를 실행하는것이 일반적이다.
- 분산 시스템은 실제 상태가 무엇인지 합의(consensus)에 도달해야 한다.
- etcd는 RAFT 합의 알고리즘을 사용해 어느 순가이든 각 노드의상태가 대다수의 노드가 동의하는 현재 상태이거나 이전에 동의한 상태 중에 하나임을 보장한다.
- 합의 알고리즘은 클러스터가 다음 상태로 진행하기 위해 과반수(혹은 쿼럼[quorum])가 필요하다.
- 2개의 노드는 여전히 과반을 가지고 있어 클라이언트에서 상태 변경 요청을 받을수 있고, 과반을 충족하지 못한 노드는 상태 변경 요청을 받을 수 없다.
etcd 인스턴스 수를 홀수로 하는 이유
- 일반적으로 홀수로 배포한다.
- 2개로 할 경우 둘 중 하나라도 실패하면 과반이 존재하지 않기 때문에 상태를 변경할 수 없음..
- 대규모 etcd 클러스터에서는 일반적으로 5대 혹은 7대 노드면 충분하다.
11.1.3 API 서버의 기능
- 쿠버네티스 APi 서버는 다른 모든 구성 요소와 kubectl 같은 클라이언트에서 사용하는 중심 구성 요소이다.
- 유효성 검사와 함께 낙관적 락도 처리하기 때문에 동시에 업데이트가 발생하더라도 다른 클라이언트에 의해 오브젝트의 변경 사항이 재정의되지 않는다.
kubectl을 이용해 처리하는 과정
- kubectl은 파일의 내용을 API 서버에 HTTP POST 요청으로 전달하면 내부에서는 다음과 같은 일이 발생한다.
- Authentication, Authorization, Admission control을 거친후에 resoruce validation을 거치고 이를 통과해야 etcd에 데이터를 처리한다.
인증 플러그인으로 클라이언트 인증
- 인증 작업은 API 서버에 구성된 하나 이상의 플러그인에 의해 수행되고 HTTP 요청을 검사해 수행한다.
- 클라이언트 인증서 혹은 HTTP Header를 가져와 인증하는 그런 처리들이 해당된다.
인가 플러그인을 통한 클라이언트 인가
- 인증된 사용자가 요청한 작ㅇ버이 요청한 리소스를 대상으로 수행할 수 있는지를 판별한다.
- 사용자가 요청한 네임스페이스 안에 파드를 생성할 수 있는지를 결정한다.
어드미션 컨트롤 플러그인으로 요청된 리소스 확인과 수정
- 해당 플러그인은 리소스를 여러 가지 이유로 수정할 수 있다.
- 누락된 필드를 기본값으로 초기화하거나 재정의하기도 하고, 요청에 없는 관계된 리소스를 수정하거나 어떤 이유로든 요청을 거부할 수도 있다.
- 데이터를 읽는 GET 요청의 경우에는 어드미션 컨트롤러를 거치지 않는다.
어드미션 컨트롤러 플러그인 예시
- AlwasysPullImages : 이미지를 항상 강제로 가져오도록 재정의
- ServiceAccount : 명시적으로 지정하지 않는 경우 default 서비스 어카운트
- NamespaceLifecycle : 네임스페이스가 존재하지 않을 경우 파드 생성 방지
- ResourceQuota : 특정 네임스페이스 안에 있는 파드가 네임스페이스에 할당된 CPU와 메모리를 사용하도록 강제한다.
리소스 유효성 확인 및 영구 저장
- 요청이 모든 어드미션 컨트롤 플러그인을 통과하면 API 서버는 오브젝트의 유효성을 검증하고 etcd에 저장한다.
11.1.4 API 서버가 리소스 변경을 클라이언트에 통보하는 방법
- API 서버는 위 과정을 제외한 다른 일은 하지 않는다.
- 파드를 만든다던지, 서비스의 엔드포인트를 관리한다던지.. 이런 일들 아무것도 하지 않는다.(컨트롤러 매니저의 역할)
- API가 직접 구성요소에게 명령을 내리는 방식이 아니다. 단지 각각 구성요소에서 관찰한 변경사항을 노티해주기만 한다.
- 클라이언트는 API 서버에 HTTP 연결을 맺고 변경 사항을 감지한다.
- 오브젝트가 갱신될 때마다, 서버는 오브젝트를 감시하고 있는 연결된 모든 클라이언트에게 오브젝트의 새로운 버전을 보낸다.
- 갱신된 오브젝트를 감시하고 있는 모든 관찰자에게 갱신된 오브젝트를 전달한다.
- kubectl 도구 또한 리소스 변경을 감시할 수 있는 API 서버의 클라이언트 중 하나이고,
--watch
옵션을 통해 파드의 생성, 수정, 삭제 통보를 받을 수 있다.
1
2
3
# watch 설정
kubectl get po --watch
kubectl get po -o yaml --watch
11.1.5 스케줄러
- 스케줄러는 API 서버의 감시 매커니즘을 통해 새로 생성될 파드를 기다리고 있다가 할당된 노드가 없는 새로운 파드를 노드에 할당한다.
- 선택된 노드(해당 노드에서 실행중인 kubelet)에 파드를 실행하도록 지시하지 않는다. 단지 스케줄러는 API 서버로 파드 정의를 갱신한다. 이러면 API 서버는 kubelet에 파드 스케줄링된 것을 통보하고, 대상 노드의 kubelet은 파드의 컨테이너를 생성하고 실행한다.
- 스케줄러의 중요한 작업 중 하나는 파드에 가장 적합한 노드를 선택하는 것인데 이 작업은 단순하지는 않다.
- 가장 쉬운 방법 : 이미 실행중인 파드를 신경 쓰지 않고 무작위로 노드를 선택하는 것
- 머신 러닝 등 고급 기술을 이용해 향후 몇분 혹은 몇 시간 내에 어떤 종류의 파드를 스케줄링할지 예측해 기존 파드를 다시 스케줄링하지 않고도 하드웨어 활용을 극대화 시킬수 있는 노드를 선택하는 것.
- 쿠버네티스의 기본 스케줄러는 위 2가지의 사이 정도
기본 스케줄링 알고리즘
- 모든 노드 중에서 파드를 스케줄링할 수 있는 노드 목록을 필터링
- 수용 가능한 노드의 우선순위를 정하고 점수가 높은 노드를 선택
- 노드가 같은 차상위 점수를 가지고 있다면, 파드가 모든 노드에 고르게 배포되도록 라운드-로빈을 사용한다.
수용 가능한 노드 찾기
미리 설정된 조건 함수 목록에 각 노드를 전달하여 처리된다.
- 노드가 하드웨어 리소스에 대한 파드 요청을 충족할 수 있는가?
- 노드에 리소스가 부족한가?
- 파드를 특정 노드로 스케줄링하도록 요청한 경우에 해당 노드인가?
- 노드가 파드 정의 안에 있는 노드 셀렉터와 일치하는 레이블을 가지고 있는가(정의한 경우에 한하여)?
- 파드가 특정 호스트 포트에 할당되도록 요청한 경우 해당 포트가 이 노드에서 이미 사용중인가?
- 파드 요청이 특정한 유형의 볼륨을 요청하는 경우 이 노드에서 해당 볼륨을 파드에 마운트할 수 있는가? 아니면 이 노드에 있는 다른 파드가 이미같은 볼륨을 사용하고 있는가?
- 파드가 노드의 테인트를 허용하는가?
- 파드가 노드의 파드 어피니티, 안티-어피니티 규칙을 지정했는가? 그렇다면 노드에 파드를 스케줄링하면 이 규칙을 어기게 되는가?
파드에 가장 적합한 노드 선택
- 일반적으로는 리소스가 남아 도는 쪽의 노드를 선택하도록 되어야 한다.
고급 파드 스케줄링
- 레플리카가 여러개인 경우, 한 노드에 스케줄링하는 것보다 가능한 많은 노드에 분산되는 것이 이상적일 것이다.
- 동일한 서비스 또는 레플리카셋에 속한 파드는 기본적으로 여러 노드에 분산되는데, 어피니티와 안티-어피니티 규칙을 정의해서 클러스터 전체에 퍼지거나 가깝게 유지되도록 강제할 수 있다.
다중 스케줄러 사용
- 여러 개의 스케줄러를 실행할 수 있다.(파드 정의 안에 schedulerName 속성에 파드를 스케줄링할 때 사용할 스케줄러를 지정할 수 있음)
- 사용자가 직접 스케줄러를 구현해 클러스터에 배포하거나 다른 설정 옵션을 가진 쿠버네티스 스케줄러를 배포할 수도 있다.
11.1.6 컨트롤러 매니저에서 실행되는 컨트롤러 소개
- 다양한 조정작업을 수행하는 여러 컨트롤러가 하나의 컨트롤러 매니저 프로세스에서 실행된다.
- 거의 대부분의 리소스에는 그에 해당하는 컨트롤러가 있고, 컨트롤러는 리소스를 배포함에 따라 실제 작업을 수행하는 활성화된 쿠버네티스 구성요소이다.
컨트롤러 종류
- 레플리케이션 매니저
- 레플리카셋, 데몬셋, 잡 컨트롤러
- 디플로이먼트 컨트롤러
- 스테이트풀셋 컨트롤러
- 노드 컨트롤러
- 서비스 컨트롤러
- 엔드포인트 컨트롤러
- 네임스페이스 컨트롤러
- 퍼시스턴트볼륨 컨트롤러
- 그 밖의 컨트롤러
컨트롤러 역할과 동작 방식 이해
- 모두 API 서버에서 리소스가 변경되는 것을 감시하고 각 변경 작업을 수행한다.
- 컨트롤러는 조정 루프를 실행해, 실제 상태를 원하는 상태로 조정하고, 새로운 상태의 리소스의 status 섹션에 기록한다.
- 감시 매커니즘을 이용해 변경사항을 통보받지만 모든 이벤트를 놓치지 않고 받는다는 것을 보장하진 않기 때문에, 정기적으로 목록을 가져오는 작업을 수행해 누락된 이벤트가 없는지 확인을 하기도 한다.
- 각 컨트롤러는 API 서버에 연결하고 감시 메커니즘을 통해 컨트롤러가 담당하는 리소스 유형에서 변경이발생하면 통보해줄 것을 요청한다.
컨트롤러의 동작 방식
- 모든 컨트롤러는 API 서버로 API 오브젝트를 제어하고, 자신이 맡은 감시 리소스를 기준으로 적절히 정의된 동작을 수행한다.
11.1.7 kubelet이 하는 일
- 워커 노드에서 실행하는 모든것을 담당하는 구성요소
kubelet의 작업 이해
- 첫번째 작업은 실행중인 노드를 노드 리소스로 만들어 API 서버에 등록하는 것이다.
- 그리고 API 서버를 지속적으로 모니터링하다가 해당 노드에 파드가 스케줄링 되면, 파드의 컨테이너를 실행시킨다.
- 실행중인 컨테이너를 계속 모니터링하면서 상태, 이벤트, 리소스 사용량을 API 서버에 보고한다.
- kubelet은 컨테이너 라이브니스 프로브를 실행도 담당한다.
API 서버 없이 정적 파드 실행
- 특정 로컬 디렉터리 안에 있는 매니페스트 파일을 기반으로 파드를 실행할 수도 있다.
- 컨트롤 플레인 구성요소를 파드로 실행하는데 이 방식을 이용한다.
- 시스템 구성 요소 파드 매니페스트를 kubelet의 매니페스트 디렉터리 안에 너허서 kubelet이 실행하고 관리하도록 할 수 있다.
11.1.8 쿠버네티스 서비스 프록시의 역할
- 모든 워커 노드는 클라이언트가 쿠버네티스 API로 정의한 서비스에 연결할 수 있도록 해주는 kube-proxy도 같이 실행시킨다.
- kube-proxy는 서비스의 IP와 포트로 들어온 접속을 서비스를 지원하는 하나의 파드와 연결시켜준다.
- 파드간에 로드 밸런싱도 수행해준다.
프록시라고 부르는 이유
- kube-proxy의 초기 구현은 userspace에서 동작하는 프록시였기 떄문.
- userspace 프록시 모드에서는 아래와 같이 iptables 규칙을 설정해 kube-proxy로 전송하여 처리하였다.
- 현재는 훨씬 성능이 우수한 구현체에서 iptables 규칙만 사용해 프록시 서버를 거치지 않고 패킷을 무작위로 선택한 백엔드 파드로 전달한다.(iptables 프록시모드)
userspace proxy vs iptables proxy
- 이 두 모드의 가장 큰 차이점은 패킷이 kube-proxy를 통과해 사용자 공간에서 처리되는지, 아니면 커널에서 처리되는지 여부이다.
iptables proxy 유의사항
- iptables 프록시 모드는 파드를 무작위로 선택하여 라운드 로빈 방식으로 처리되지 않는 다는 것이다. 클라이언트와 파드 수가 적다면 문제가 두드러지나, 많다면 특별한 문제는 없다.
11.1.9 쿠버네티스 애드온
- 쿠버네티스 서비스의 DNS 조회, 여러 HTTP 서비스를 단일 외부 IP 주소로 노출하는 인그레스 컨트롤러, 쿠버네티스 웹 대시보드 등이 있다.
애드온 배포 방식
- minikube 안에는 이런 애드온들이 레플리케이션 컨트롤러로 배포되어 있다.
- dns 애드온은 deployment로 배포되어있다.
1
2
3
4
5
# rc 조회
kubectl get rc -n kube-system
# deployment 조회
kubectl get deploy -n kube-system
DNS서버 동작 방식
- 클러스터의 모든 파드는 기본적으로 클러스터의 내부 DNS 서버를 사용하도록 설정되어 있다.
- DNS 서버 파드는 kube-dns 서비스로 노출된다.
- 배포된 모든 컨테이너가 가지고 있는 /etc/resolv.conf 안에 nameserver로 지정되어 있다.
- kube-dns 파드는 API 서버 감시 매커니즘을 이용해 모든 클라이언트가 거의 항상 최신 DNS 정보를 얻을 수 있도록 한다.
- 서비스 혹은 엔드포인트 리소스가 갱신되는 짧은 시간동안 유효하지 않는 타이밍이 있기는 하다.
인그레스 컨트롤러 동작 방식
- 리버스 프록시 서버(nginx)를 실행하고 클러스터에 정의된 인그레스, 서비스, 엔드포인트 리소스 설정을 유지한다.
- 인그레스 컨트롤러는 트래픽을 서비스의 IP로 보내지 않고 서비스의 파드로 직접 전달한다. 외부에서 접속한 클라이언트가 인그레스 컨트롤러로 연결할 때 IP를 보존하는데 영향을 주기 때문에 특정 사용 사례에서는 서비스를 선호하도록 만든다.
다른 애드온 사용
- 모두 클러스터 상태를 관찰하고 변화가 생기면 그에 맞는 필요한 조치를 수행한다.
11.1.10 모든 것을 함께 가져오기
- 쿠버네티스는 시스템 전체가 상대적으로 작고, 관심의 분리로 느슨하게 결합된 구성요소로 이뤄져있다. 또한 이런 구성요소들은 모두 함꼐 상호작용하여 실제 상태가 사용자가 지정한 원하는 상태와 일치하도록 유지시켜준다.
11.2 컨트롤러가 협업하는 방법
- 대략적인 모습은 다음 그림과 같다.
11.2.2 이벤트 체인
디플로이먼트 컨트롤러가 레플리카셋 생성
레플리카셋 컨트롤러가 파드 리소스 생성
스케줄러가 새로 생성한 파드에 노드 할당
kubelet은 파드의 컨테이너를 실행
12.2.3 클러스터 이벤트 관찰
- 컨트롤 플레인 구성 요소와 kubelet은 이러한 작업을 수행할 때 API 서버로 이벤트를 발송한다.
1 2
# event 관찰 kubectl get events --watch
11.3 실행중인 파드에 관한 이해
- 실행중인 파드에 접근해서 컨테이너를 조회해보면 다음과 같은 내용을 확인할 수 있다.
퍼즈(pause) 컨테이너
- 파드의 모든 컨테이너를 함꼐 담고 있는 컨테이너로, 파드의 모든 컨테이너가 동일한 네트워크와 리눅스 네임스페이스를 보유하는게 유일한 목적인 인프라스트럭쳐 컨테이너이다.
- 이로인해 파드의 각 컨테이너들은 인프프라스르럭처 컨테이너의 리눅스 네임스페이스를 사용할수 있다.
- 만약 2개의 컨테이너를 가진 파드를 실행시켰다면 결과적으로는 3개의 컨테이너가 실행되는 셈이다.
- 컨테이너가 종료되고 다시 시작하기 위해서는 이전과 동일한 리눅스 네임스페이스의 일부가 돼야 하기 떄문에 인프라스트럭처 컨테이너의 라이프사이클은 파드의 라이프사이클과 동일하다.
- 인프라스트럭처 컨테이너가 그 중간에 종료되면 kubelet이 파드의 모든 컨테이너를 다시 생성한다.
11.4 파드간 네트워킹
- 쿠버네티스 클러스터에서는 각 파드가 고유한 Ip 주소를 가지고 다른 모든 파드와 NAT 없이 플랫 네트워크로 서로 통신할 수 있다.
- 이 역할은 쿠버네티스 자체가 아닌 시스템 관리자 또는 컨테이너 네트워크 인터페이스(CNI) 플러그인에 의해 ㅔㅈ공된다.
11.4.1 네트워크는 어떤 모습이어야 하는가?
- 파드가 동일한 워커 노드에서 실행중인지 여부와 관계 없이 파드끼리 서로 통신할 수 있어야 한다.
- 패킷은 네트워크 주소 변환(NAT) 없이 파드 A에서 파드 B로 출발지와 목적지 주소가 변경되지 않은 상태로 도착해야 한다.
- 파드 내붕부에서 실행중인 애플리케이션의 네트워킹이 동일한 네트워크 스위치에 접속한 시스템에서 실행되는 것처럼 간단하고 정확하게 이뤄지도록 해주기 때문이다.
- 인터넷에 있는 서비스와 통신할 때는 패킷의 출발지 IP를 변경하는 것이 필요하다. 파드의 IP는 모두 사설(private)이기 때문인데, 외부로 난가는 패킷의 출발지 IP는 워커 노드의 IP로 변경된다.
- 이 경우에는 X-Forwarded-For HTTP header 등을 통해 client ip를 알아낼수 있다.
11.4.2 네트워킹 동작 방식 자세히 살펴보기
- 여기서 각 파드의 네트워크 인터페이스는 인프라스트럭처 컨테이너(퍼즈 컨테이너)로부터 설정한 것이다.
동일한 노드에서 파드 간의 통신 활성화
- 컨테이너를 위한 가상 이더넷 인터페이스 쌍(veth쌍)이 생성된다.
- 이 2개의 가상 인터페이스는 파이프의 양쪽 끝과 같다.
- 이 이더넷 인터페이스는 브리지의 주소 범위 안에서 IP를 할당받고, 컨테이너에서 패킷을 전송할떄는 이 eth0 인터페이스로 나와서 브리지를 통해 다른쪽으로 전달된다.(브리지에 연결된 모든 네트워크 인터페이스에서 수신 가능)
- 노드에 있는 모든 컨테이너는 같은 브리지에 연결되어 있어서 서로 통신이 가능하다. 다른 노드에서 실행중인 컨테이너가 서로 통신하려면 노드 사이의 브리지가 서로 연결되어있으면 가능하다.
서로 다른 노드에서 파드간의 통신 활성화
- 오버레이, 언더레이 네트워크, 일반적인 계층3 라우팅을 통해서 처리가 가능하다.
- 다만 파드 IP 주소는 전체 클러스터 내에서 유일해야 하기 때문에 노드 사이의 브리지는 겹치지 않는 주소 범위를 사용해서 클러스터 내 파드들이 같은 IP주소를 얻지 못하도록 한다.(이렇게 IP 충돌을 막는다.)
- 다른 노드로 패킷을 전송할떄는 패킷이 먼저 veth쌍을 통과하고 브리지를 통해 노드의 물리 어댑터로 전달된다. 그 다음 회선을 통해 다른 노드의 물리 어댑터로 전달되고, 노드의 브리지를 지나 목표 컨테이너의 veth쌍을 통과하게 된다.
- 두 노드가 라우터 없이 같은 네트워크 스위치에 연결된 경우만 동작한다.
- 라우터의 경우 노드간의 라우터가 늘어날수록 점점 어려워지고, 오류가 발생할 여지가 늘어나서, 소프트웨어 정의 네트워크(SDN, Software Defined Network)을 사용하는 것이 더 쉽다.
- SDN을 이용하면 하부 네트워크 토폴로지가 아무리 복잡해지더라도 노드들이 같은 네트워크에 연결된 것으로 볼 수 있다.
- 파드에서 전송한 패킷은 캡슐화돼 네트워크로 다른 파드가 실행중인 노드로 전달되고 디캡슐화 단계를 거쳐 원래 패킷 형태로 대상 파드에 전달된다.
11.4.3 컨테이너 네트워크 인터페이스 소개
- 컨테이너를 네트워크에 쉽게 연결하기 위해 시작된 프로젝트.
- 쿠버네티스는 어떤 CNI 플러그인이든 설정이 가능하다.
- 쿠버네티스에 네트워크 플러그인 설치는 데몬셋과 다른 지원 리소스를 가지고 있는 yaml을 배포하면 된다.
- kubelet을 시작할 떄 –network-plugin=cni 옵셥능 주고 시작하면 노드의 CNI 인터페이스에 연결할 수 있다.
CNI 플러그인 종류
- Calico
- Flannel
- Romana
- Weave Net
- 그 외 기타
11.5 서비스 구현 방식
11.5.1 kube-proxy 소개
- 서비스와 관련된 모든 것은 각 노드에서 동작하는 kube-proxy 프로세스에 의해 처리된다.
- 초기에는 실제 프록시로서 연결을 기다리다가 들어온 연결을 위해 해당 파드로 가는 새로운 연결을 생성했었다(userspace 프록시 모드)
- 현재는 성능이 더 우수한 iptables 프록시 모드를 사용한다.
- 서비의 Ip주소는 가상이고, 서비스의 주요 핵심 사항인 서비스는 IP와 포트의 쌍으로 구성된다는것이 중요한 사항이고, 서비스 IP만으로는 아무것도 나타내지 않는다.(ping을 할 수 없는 이유)
11.5.2 kube-proxy가 iptables를 사용하는 방법
- 서비스 리소스가 생성되면 가상 IP주소가 바로 할당되고, API 서버는 워커 노드에 실행중인 모든 kube-proxy 에이전트에 새로운 서비스가 생성됐음을 통보한다.
- 각 kube-proxy는 실행 중인 노드에 해당 서비스 주소로 접근할 수 있도록 만든다.
- 서비스의 IP/포트 쌍으로 향하는 패킷을 가로채서, 목적지 주소를 변경해 패킷이 서비스를 지원하는 여러 파드 중 하나로 리디렉션되도록 하는 몇개의 iptables 규칙을 설정하는 것.
- kubeproxy는 모든 엔드포인트 오브젝트를 감시한다.
- 처음 패킷의 목적지가 서비스의 IP와 포트로 지정되지만, 패킷이 네트워크로 전송되기 전에 노드A의 커널이 노드에 설정된 iptables 규칙에 따라 먼저 처리가 이루어지고, 임의로 선택한 파드의 IP와 포트로 교체한다.
11.6 고가용성 클러스터 실행
- 서비스를 중단 없이 계속 실행하게 하기 위해서는 애플리케이션 뿐만 아니라 쿠버네티스 컨트롤 플레인 구성 요소도 항상 동작하고 있어야 한다. 이를 위해서는 고가용성이 필요할 수 있다.
11.6.1 애플리케이션 가용성 높이기
가동 중단 시간을 줄이기 위한 다중 인스턴스 실행
- 일반적으로 애플리케이션을 수평으로 확장할 수 있어야 하지만 수평확장할수 없는 경우라도 레플리카수를 1로 지정된 디플로이먼트를 사용하는 것이 좋다.
- 레플리카를 사용할수는 없지만, 문제가 생겼을때 새 레플리카로 빠르게 교체하기 위함.
수평 스케일링이 불가능한 애플리케이션을 위한 리더 선출 매커니즘 사용
- 중단 시간이 발생하는 것을 피하려면, 활성 복제본과 함께 비활성 복제본을 실행해두고, 빠른 임대 혹은 리더 선출 매커니즘을 이용해 단 하나만 활성화 상태로 만들어야 한다.
- 리더가 아닌 인스턴스들은 리더가 되는것을 기다리고 리더만 데이터를 처리한다. 그러다 리더가 문제가 생기면 대기하던 인스턴스들이 리더가 되기를 경쟁하는 방식
- 리더만 쓰기가 가능하고 나머지는 읽기전용으로 처리해서 사용하는 방식
- 경쟁 조건(race condition)으로 예측할 수 없는 시스템 동작이 발생하더라도 두 인스턴스가 같은 작업을 하지 않도록 할 수 있다.
11.6.2 쿠버네티스 컨트롤 플레인 구성 요소의 가용성 향상
컨트롤 플레인의 구성요소인 etcd, API 서버, 컨트롤러 매니저, 스케줄러의 가용성을 확장한 경우 다음과 같이 구성된다.
etcd 클러스터 실행
- etcd 자체가 분산 시스템으로 설게뙤어 있어서 필요한 수의 머신(3,5,7 : 홀수로 지정)에서 인스턴스를 실행하고 서로를 인식할 수 있게만 하면 된다.
- 모든 인스턴스에 걸쳐 데이터를 복제하기 때문에 3개의 머신으로 구성된 클러스터는 한 노드가 실패하더라도 읽기와 쓰기 작업을 모두 수행할 수 있다.
- 7대보다 더 크게 하는 경우 레플리케이션으로 인해 오히려 성능에 영향을 줄수도 있따.
여러 APi 서버 인스턴스 실행
- stateless하기 때문에 서로 인지할 필요도 없고 레플리카수를 쉽게 늘려도 된다.
컨트롤러와 스케줄러의 고가용성 확보
- 컨트롤러 매니저나 스케줄러는 여러 인스턴스에서 동시에 실행하는 것은 어려운 일이다.(하나의 작업이 중복 수행되면 안되기 떄문에)
- 컨트롤러 매니저나 스케줄러 같은 구성 요소는 여러 인스턴스를 실행하기 보다는 한 번에 하나의 인스턴스만 활성화되게 해야 하고, 가용성을 위해 –leader-elect 옵션으로 제어할 수 있다.
- 리더만 실제로 작업을 수행하고 나머지 다른 인스턴스는 대기하면서 현재 리더가 실패할 경우를 기다린다.
컨트트롤 플레인 구성 요소에서 사용되는 리더 선출 매커니즘 이해
- 리더를 선출하기 위해 서로 직접 대화할 필요가 없고, API 서버에 오브젝트를 생성하는 것만으로 완전히 동작시킬 수 있다.
- 쿠버네티스에서는 “control-plane.alpha.kubernetes.io/leader” 어노테이션의 holderIdentity 필드에 이름을 넣는데 처음 성공한 인스턴스가 리더가 되는 방식을 사용한다.(승자는 언제나 하나뿐, 낙관적 락에 의해)
Comments powered by Disqus.