Files
blog/_posts/Development/Kubernetes/2021-05-30-11-k8s-internals.md

528 lines
36 KiB
Markdown

---
share: true
toc: true
categories: [Development, Kubernetes]
tags: [kubernetes, sre, devops]
title: "11. Understanding Kubernetes Internals"
date: "2021-05-30"
github_title: "2021-05-30-11-k8s-internals"
image:
path: /assets/img/posts/Development/Kubernetes/k8s-11.jpeg
attachment:
folder: assets/img/posts/Development/Kubernetes
---
![k8s-11.jpeg](/assets/img/posts/Development/Kubernetes/k8s-11.jpeg) _The chain of events that unfolds when a Deployment resource is posted to the API server (출처: https://livebook.manning.com/book/kubernetes-in-action/chapter-11)_
### 주요 내용
- Kubernetes 클러스터를 구성하는 컴포넌트의 기능과 역할에 대한 이해
- Pod 생성시 일어나는 일에 대한 이해
- Kubernetes 내부 네트워크와 Service 의 동작 방식 이해
Kubernetes 리소스들이 어떻게 구현되었는지 살펴보자!
## 11.1 Understanding the architecture
---
Kubernetes 클러스터는 다음과 같이 구성되어 있음을 1장에서 배웠다!
- Kubernetes Control Plane: 클러스터의 상태를 저장하고 관리한다
- etcd distributed persistent storage
- API server
- Scheduler
- Controller Manager
- (Worker) Node(s): 실제로 컨테이너를 동작하게 한다
- Kubelet
- Kubernetes Service Proxy (kube-proxy)
- Container Runtime (Docker 등)
이와 별개로도 리소스들을 관리하거나 사용하기 위해서는 이하의 것들이 추가로 필요하다.
- Kubernetes DNS server
- Dashboard
- Ingress controller
- Heapster (14장)
- Container Network Interface network plugin
### 11.1.1 The distributed nature of Kubernetes components
위에서 언급한 컴포넌트들은 각각 별도의 프로세스로 실행된다!
#### 컴포넌트의 통신 방식
컴포넌트들은 반드시 API server 와만 통신하고, 서로 직접 통신하지 않는다. 그리고 etcd 와 통신하는 유일한 컴포넌트는 API server 이다. 클러스터의 상태를 변경하기 위해서는 무조건 API server 를 거쳐야 한다.
#### 컴포넌트의 인스턴스 여러 개 만들기
Control Plane 의 컴포넌트들은 여러 개의 서버 사이에 분리될 수 있으며, Control Plane 을 여러 개 만들어 주면 high availability (HA/고가용성) 을 달성할 수 있다.
또한 etcd 와 API server 의 경우 병렬적인 작업 처리가 가능하지만, Scheduler 와 Controller Manager 의 경우 작업을 한 번에 한 인스턴스만 실행할 수 있다. 나머지는 standby 상태가 된다.
#### 컴포넌트들의 실행 방식
Kubelet 만 시스템에서 실행하고 나머지는 Kubelet 이 pod 의 형태로 실행한다. (물론 Control Plane 의 컴포넌트들은 시스템에서 직접 실행 할수도 있다)
### 11.1.2 How Kubernetes uses etcd
Kubernetes 를 사용하면서 리소스를 생성하고 수정하게 되면 이러한 정보가 persistent 하게 어딘가에 저장되어야 한다. 그래야만 API server 가 재시작하는 등의 경우에도 리소스들이 유지될 수 있기 때문이다. Kubernetes 에서는 클러스터의 정보와 메타데이터를 저장하는 *유일한* 저장소가 etcd 이다. etcd 는 fast, distributed, consistent key-value store 이다. (Distributed 이므로 HA 를 위해 여러 개 생성 가능)
etcd 와 통신하는 유일한 컴포넌트는 API server 이고, 나머지 컴포넌트들은 API server 를 통해 간접적으로 etcd 에 접근하게 된다. 이렇게 구현된 이유는 validation 과 more robust optimistic locking system, 그리고 저장소와의 통신을 API server 에게 맡겨서 abstraction 의 효과를 얻기 위해서이다.
> **Optimistic Concurrency Control** (Optimistic Locking): 데이터에 lock 을 걸어서 read/write 를 제한하지 않고, 데이터에 버전을 추가하는 방식이다. 대신 데이터가 수정될 때마다 버전이 증가하게 되며, 클라이언트가 데이터를 수정하려 할 때 데이터를 읽을 때와 쓰려고 할 때 버전을 비교하여 만약 다르다면 업데이트가 기각당하는 방식이다. 이 때 클라이언트는 데이터를 다시 읽어와서 업데이트를 다시 시도해야 한다.
>
> 모든 Kubernetes 리소스에는 `metadata.resourceVersion` field 가 있어 클라이언트 쪽에서 API server 에 업데이트 요청을 보낼 때 반드시 함께 전달해야 한다. 만약 etcd 에 저장된 버전과 다르다면, 업데이트가 기각된다.
#### etcd 에 리소스가 저장되는 방식
etcd v2 에서는 key 를 계층적으로 저장해서 (hierarchical key space) key-value pair 가 파일 시스템처럼 관리 되었다. 그래서 key 는 다른 key 를 포함하는 폴더이거나, 그냥 value 를 가졌다.
etcd v3 에서는 폴더를 지원하지 않는데, 대신 key 의 형태는 변하지 않고 유지되었다. `/` 를 포함할 수 있는 것이므로 폴더처럼 계층적으로 나뉘어 있다고 봐도 괜찮다.
Kubernetes 에서는 etcd 의 `/registry` 안에 정보를 저장한다.
> etcd 설치하고 etcdctl 로 확인해보려 했으나 실패... 아래 내용은 책의 내용이다. May be outdated.
```
$ etcdctl ls /registry
/registry/configmaps
/registry/daemonsets
/registry/deployments
/registry/events
/registry/namespaces
/registry/pods
...
```
각 리소스 별로 저장되어 있음을 알 수 있다. 만약 pod 의 정보를 보고 싶다면
```
$ etcdctl ls /registry/pods
/registry/pods/default
/registry/pods/kube-system
```
Namespace 별로 구분이 되어있는 것을 확인할 수 있고, 더욱 자세히 확인하면
```
$ etcdctl ls /registry/pods/default
/registry/pods/default/kubia-159041347-xk0vc
/registry/pods/default/kubia-159041347-wt6ga
/registry/pods/default/kubia-159041347-hp2o5
```
Pod 마다 key 가 하나씩 존재하고 있음을 알 수 있다. Value 를 가져와 보면 pod definition 이 JSON 형태로 저장되어 있는 것을 확인할 수 있다.
#### Consistency and validity of stored objects
Kubernetes 에서는 다른 Control Plane 컴포넌트들이 무조건 API server 를 거쳐서 etcd 와 통신하기 때문에, optimistic locking 을 이용하여 항상 consistent 한 업데이트를 할 수 있게 된다. 또한 API server 가 validation 을 해주기 때문에 etcd 에 저장되는 정보는 항상 valid 하며, 업데이트 요청이 인증된 클라이언트로부터만 이뤄지도록 하고 있다.
#### Consistency when etcd is clustered
HA 를 위해 etcd 가 여러 개인 경우, 동기화가 이뤄져야 한다. etcd 에서는 RAFT 알고리즘을 사용하여 etcd 간의 상태를 동기화한다. 그래서 항상 노드의 상태는 다수의 노드가 동의한 현재 상태이거나, 과거에 동의했던 상태가 된다.
이 알고리즘은 consensus 알고리즘이기 때문에 '다수'가 동의해야 다음 상태로 나아갈 수 있게 된다. 그래서 만약 클러스터가 분리되어 두 그룹이 생긴다고 해도, 둘 중 한 그룹에는 분명 '다수'의 노드가 포함되어 있을 것이므로 해당 그룹만 상태를 변경할 수 있고, 다른 그룹은 상태를 변경할 수 없게 된다. 즉, 각 그룹의 상태는 diverge 할 수 없다.
나중에 클러스터가 다시 합쳐지게 되면 '다수'가 아니었던 노드들은 '다수'의 노드 상태를 확인하고 그에 맞게 자신의 상태를 변경하면 된다.
따라서 etcd 인스턴스의 개수는 홀수개로 정하는 것이 좋다!
### 11.1.3 What the API server does
API server 는 기본적인 CRUD interface 를 제공하여 RESTful API 로 클러스터 상태를 조회하거나 수정할 수 있도록 한다. 그리고 그 정보를 etcd 에 저장하며, 저장할 때 validation 을 수행하여 잘못된 데이터가 저장되지 않도록 한다. 또한 optimistic locking 을 이용해 동시성도 관리하고 있다.
`kubectl` 을 사용하게 되면 요청이 API server 로 오게 되는데, 이 과정을 상세하게 살펴본다.
우선 `kubectl` 을 이용해 리소스를 생성하게 되면 HTTP POST 요청이 API server 에 가게 된다.
#### Authenticating the client with authentication plugins
API server 입장에서는, 요청을 보낸 사람이 누구인지 먼저 확인할 필요가 있다. 내부에 authentication plugin 이 존재하므로, 이 plugin 을 사용해서 누가 요청을 보낸 것인지 판단한다. (HTTP 요청을 분석하는 것)
#### Authorizing the client with authorization plugins
요청을 어떤 사용자가 보냈는지 판단했다면, 해당 사용자가 요청의 내용을 실제로 수행할 수 있는지 확인한다. 이 때는 authorization plugin 을 사용하게 된다.
#### Validating and/or modifying the resource in the request with admission control plugins
요청이 리소스 생성/수정/삭제라면, Admission Control 로 요청이 전달된다. 이 또한 plugin 을 사용하게 되는데, 요청의 리소스 spec 에서 누락된 field 값을 채워주거나 (기본값으로 설정하거나) 다른 관련된 리소스의 값을 수정하기도 하며, 요청을 기각할 수도 있다.
> 리소스 읽기는 Admission Control 에 전달되지 않는다!
> Docs: https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/
#### Validating the resource and storing it persistently
위 과정을 모두 거친 뒤에는 validation 을 수행하고 etcd 에 저장하여 클라이언트에게 응답을 돌려준다.
### 11.1.4 Understanding how the API server notifies clients of resource changes
Controller Manager 의 경우 ReplicaSet 을 관리하거나 service 의 endpoint 를 관리해야 하는데, 이를 위해서는 리소스의 정보가 필요하고, 리소스가 변경되었다면 변경 사실을 알 수 있어야 한다.
그래서 API server 는 리소스 변경사항을 다른 컴포넌트가 확인할 수 있도록 해준다. Control Plane 의 컴포넌트들은 리소스가 생성/수정/삭제될 때 알림을 받도록 요청할 수 있다. 이를 통해 다른 컴포넌트들은 클러스터 상태 변경에 대응할 수 있게 되는 것이다.
클라이언트(변경사항을 지켜보는 다른 컴포넌트)는 API server 에 HTTP 연결을 하게 된다. 이 연결을 통해 요청한 리소스의 수정 내역이 전달된다. 그래서 리소스 변경이 일어나면, 이 리소스의 변경 내역을 요청한 (watch 하고 있는) 모든 클라이언트에게 수정 내역이 전달된다.
### 11.1.5 Understanding the Scheduler
Scheduler 의 동작은 비교적 간단한 편이다. API server 의 watch 를 이용해 새로운 pod 가 생기면 pod 가 띄워질 노드를 하나 골라주면 된다.
Scheduler 는 노드에게 pod 를 띄우라고 절대 알려주지 않는다. Scheduler 가 하는 일은 API server 에 요청을 보내 pod definition 을 수정하는 것이다. 그러면 해당 노드의 Kubelet 이 watch 를 이용해 보고있다가 pod 가 schedule 되었음을 알게 되는 것이다. 이제 Kubelet 은 띄워야 할 pod 가 생긴 것이므로 pod 를 실행하게 된다.
큰 그림은 간단하지만, 노드를 고르는 일은 생각보다 복잡하다.
#### Default scheduling algorithm
- Scheduling 이 가능한 노드(acceptable nodes)의 리스트를 만든다.
- 만든 리스트의 노드들 중에서 가장 적합한 노드를 고르고, 만약 가장 적합한 노드가 여러 개라면, round-robin 을 이용하여 균등하게 scheduling 되도록 한다.
#### Finding acceptable nodes
Acceptable 인지 아닌지 판단하기 위해서는 미리 정의된 조건들을 확인한다. 몇 가지만 살펴보면..
- Pod 가 요구하는 하드웨어 리소스가 노드에 충분한가?
- 노드에 리소스가 거의 바닥나지는 않았는지?
- Pod 에서 특정한 노드로의 scheduling 을 요청하지는 않았는지?
- Node selector label 이 맞는지?
- Pod 가 특정 포트로 요청을 받는다면 해당 포트가 노드에서 이미 bind 되어 있지는 않은지?
- Pod 가 요구하는 volume 이 이 노드에서 mount 될 수 있는지?
- Pod 에 node affinity/anti-affinity 설정이 있는지?
이러한 조건들을 모두 만족해야 acceptable node 가 된다.
#### Selecting the best node for the pod
위에서 다양한 조건으로 검사를 했지만, 그럼에도 불구하고 어떤 노드는 더 나은 선택일 수도 있다. 2-노드 클러스터가 있다고 하면, 둘다 acceptable 인데 한 노드가 10개의 pod 를 이미 실행하고 있는 반면 나머지 하나는 실행 중인 pod 가 없다고 하자. 그러면 자연스럽게 실행 중인 pod 가 없는 쪽으로 scheduling 을 할 것이다.
하지만 클라우드에서 실행하는 경우라면, 그냥 10개 pod 를 실행 중인 노드에 scheduling 하고 나머지 node 는 없애서 cloud provider 에게 메모리를 돌려주는 것이 나을 수도 있다.
#### Multiple schedulers
여러 개의 scheduler 를 사용할 수도 있으며, `schedulerName` field 를 사용해 어떤 scheduler 를 사용할지 정할 수 있다.
### 11.1.6 Introducing the controllers running in the Controller Manager
앞에서 언급한 것처럼 API server 는 etcd 에 저장하고 클라이언트에게 알리며, scheduler 는 노드를 선택하기만 하기 때문에, 변경 사항이 생겼을 때 실제로 변경된 클러스터 상태에 다가가도록 일할 컴포넌트가 필요하다. 이 일은 Controller Manager 안의 controller 들이 수행한다.
Controller 종류는 거의 모든 리소스 별로 하나씩 있는 느낌이다.
- Replication Manager (ReplicationController)
- ReplicaSet, DaemonSet, Job controllers
- Deployment, StatefulSet controllers
- Node controller
- Service, Endpoints controller
- Namespace controller
- PersistentVolume controller
- Others
즉 리소스는 클러스터 내부에서 돌아가야 하는 것들의 정보/명세이며 controller 는 그 정보를 바탕으로 실제로 실행하는 역할을 한다고 생각하면 된다.
#### Understanding what controllers do and how they do it
결국에는 API server 를 watch 하고 있다가 변경 내역이 생기면 일을 한다고 생각하면 된다. Controller 들은 서로의 존재를 모르며 각자 API server 와 통신할 뿐이다.
추가로 controller 들은 reconciliation loop 을 사용하여 목표 상태와 현재 상태를 비교한다. 또 `status` 에 현재 상태를 기록하며, API server 의 watch 기능이 모든 변경 이벤트를 준다는 보장은 없으므로 주기적으로 re-list operation 을 수행하여 놓친 변경 내역이 없는지 확인한다.
#### Replication Manager
ReplicationController 를 관리한다.
API server 에 ReplicationController 리소스를 watch 하고 있으며, replica count 에 변경 사항이 생기면 알림을 받게 된다. 또한 pod 리소스도 watch 하고 있어서 실제로 몇 개의 replica 가 실행 중인지 확인할 수 있다.
만약 replica count 와 실제 replica 수가 다르다면, POST/DELETE 요청을 API server 에게 보내서 pod 를 생성/삭제하도록 한다. 실제 생성과 삭제는 Scheduler 와 노드의 Kubelet 이 담당하게 된다.
#### ReplicaSet, DaemonSet, Job controllers
ReplicaSet 의 경우 Replication Manager 와 비슷하다.
한편 DaemonSet 과 Job controller 는 비슷한데, 자신들이 watch 하고 있는 리소스의 pod template 를 참고하여 API server 에게 pod 생성 요청을 보낸다. 마찬가지로 실제 pod 생성은 Kubelet 이 하게 된다.
#### Deployment controller
실행 중인 Deployment 의 상태가 리소스 정보와 동일하도록 계속 동기화하는 역할을 담당한다.
Deployment 리소스의 정보가 수정될 때마다 controller 는 새 버전을 rollout 을 하게 되는데, 이는 ReplicaSet 을 이용해서 한다. 그리고 deployment strategy 에 따라 각 ReplicaSet 의 replica 수를 적절히 조절하여 모든 pod 이 새로운 pod 로 교체되도록 한다.
9장에서 언급했듯이 Deployment 가 pod 를 직접 만들지 않는다.
#### StatefulSet Controller
(Deployment 에 state 가 추가된 것이니...) ReplicaSet controller 와 유사하다. 하지만 ReplicaSet controller 는 pod 만 관리하는 반면 StatefulSet controller 는 pod 의 PVC 까지 같이 관리하게 된다.
#### Node controller
클러스터의 worker node 정보를 담고있는 Node 리소스를 관리한다. 또한 노드의 health 를 확인하고 도달할 수 없는 (unreachable) 노드의 pod 는 제거한다.
#### Service controller
`LoadBalancer` service 를 관리하는 controller 이다.
#### Endpoints controller
Service object 들은 endpoints 를 확인하여 요청을 분산하는데, 이 endpoint 를 관리한다. Service 를 watch 하고 있고, label selector 에 맞는 pod 들을 watch 하고 있다가 pod 가 `READY` 상태가 되면 Endpoints 리소스에 pod 의 IP 와 포트를 추가한다.
Endpoints 는 standalone object 이므로 controller 가 직접 생성하고 삭제한다.
#### Namespace controller
Namespace 가 삭제될 때, 해당 namespace 안의 모든 리소스를 삭제하는 역할을 담당한다.
#### PersistentVolume controller
사용자가 PVC 를 생성했을 때, 적절한 PV 를 찾아 bind 해주는 역할을 담당한다.
PVC 가 생성됐을 때 access mode 를 만족하는 가장 작은 PV 를 찾아준다. 내부적으로 용량으로 정렬된 PV 목록을 가지고 있다.
### 11.1.7 What the Kubelet does
처음에 노드가 생성될 때는 API server 에 Node 리소스를 생성한다. 그리고 APi server 에 watch 하여 자신의 노드로 scheduling 된 pod 가 있으면 pod 의 컨테이너를 실행한다. Kubelet 은 실행중인 컨테이너들을 지속적으로 모니터링하여 상태와 이벤트 그리고 (하드웨어) 리소스 사용량을 API server 에 보고한다.
Liveness probe 를 실행하는 것도 Kubelet 이다. 실패하면 Kubelet 이 재시작 명령을 내린다.
#### Running static pods without the API server
일반적으로는 API server 에 pod definition 을 요청하여 pod 를 생성하겠지만, 로컬 저장소에 있는 definition 으로부터 pod 를 생성할 수도 있다. Pod manifest 를 Kubelet 의 manifest 폴더에 넣어주면 Kubelet 이 그것을 실행하고 관리해준다.
이 방식을 이용해서 Control Plane 의 컴포넌트들을 pod 형태로 실행할 수 있는 것이다.
### 11.1.8 The role of the Kubernetes service proxy
kube-proxy 는 클라이언트들이 생성된 service 에 연결될 수 있도록 해준다. Service IP 와 port 로 들어오는 요청이 service endpoints 중 하나의 pod 로 연결되도록 보장하며, 만약 endpoint 가 여러 개라면 로드 밸런싱도 해준다.
#### Why it's called a proxy
예전에는 리눅스 `iptables` 를 수정하여 kube-proxy 서버로 요청이 오도록 한 다음 처리하여 pod 에게 전달했었기 때문이다. 현재는 `iptables` 규칙을 수정하여 proxy 서버를 거치지 않고 바로 endpoint pod 중 랜덤한 하나에게 요청이 가도록 하고 있다. (packet redirection) 후자가 성능이 더 좋다.
### 11.1.9 Introducing Kubernetes add-ons
#### How add-ons are deployed
Pod 로 띄워지기도 하고 Deployment 나 ReplicationController 로 띄워지기도 한다. 결국 YAML 파일을 API server 에 POST 하는 점은 동일하다.
> `minikube` 에서는 Ingress controller 와 coredns 가 deployment 로 띄워져있는 것을 확인했다.
#### How the DNS server works
DNS server pod 는 `kube-dns` service 를 통해 expose 되어 있으며, service 의 IP 주소는 pod 의 모든 컨테이너 내부의 `/etc/resolv.conf` 파일 안에 `nameserver` 로 들어가 있다.
`kube-dns` pod 는 Service 와 Endpoints 리소스를 watch 하고 있어서 변경 사항이 생기면 DNS record 를 업데이트한다. 이 업데이트에는 약간의 지연이 있을 수 있다.
#### How (most) Ingress controllers work
구현체마다 조금씩 다를 수 있지만 대부분 reverse proxy server 를 둔다. 그리고 reverse proxy server 의 세팅을 Ingress, Service, Endpoints 에 맞게 해준다. (API server 의 watch 기능 이용)
Ingress 를 사용하게 되면 service 를 거치지 않고 endpoint 로 바로 요청이 전달되는 것도 이 때문이다. 더불어 client IP 가 유지되는 장점도 있다.
### 11.1.10 Bringing it all together
Kubernetes 시스템이 각 역할과 책임을 가진 개별적인 컴포턴트들로 잘 분리되어있음을 알게 되었다.
사용자가 정의한 목표 상태에 클러스터가 도달할 수 있도록 컴포넌트들이 협동한다.
## 11.2 How controllers cooperate
---
Pod 를 생성할 때 어떤 일이 일어나는지 자세히 살펴본다. Pod 를 직접 생성하는 경우는 잘 없으므로, Deployment 를 생성하는 경우를 살펴볼 것이다.
### 11.2.1 Understanding which components are involved
시작하기 전, controllers, Scheduler, Kubelet 이 API server 를 watch 하고 있다는 사실을 기억해야 한다.
### 11.2.2 The chain of events
`kubectl` 을 이용해서 Deployment 를 생성하게 되면, Kubernetes API server 는 POST 요청을 받게 된다.
API server 는 spec 을 validation 하고, etcd 에 저장하며, `kubectl` 에 응답을 돌려준다.
그 다음부터는 연쇄적으로 반응이 일어난다.
#### Deployment controller creates the ReplicaSet
Deployment 리소스를 watch 하고 있던 모든 클라이언트들은 새롭게 생성된 deployment 에 대해 알게 된다. 이 클라이언트 중에 Deployment controller 가 있으므로, controller 는 새로운 deployment 를 감지하고 현재 specification 에 맞는 ReplicaSet 을 생성한다. 이 또한 API server 에 요청하는 것이다.
#### ReplicaSet controller creates the pod resources
ReplicaSet controller 가 새로운 ReplicaSet 을 감지하고, pod selector 의 값을 이용해서 replica count 와 같은 pod 개수가 존재하고 있는지 확인한다.
(새로 생성하면 당연히 개수가 부족하므로) 이제 controller 는 ReplicaSet spec 의 pod template 을 참고하여 API server 에 pod 생성을 요청한다.
#### Scheduler assigns a node to the newly created pods
새롭게 생성된 pod 는 etcd 에 저장되어 있지만 아직 노드가 결정되지 않은 상태라 `nodeName` field 가 없다. Scheduler 는 이렇게 `nodeName` field 가 없는 pod 들을 watch 하고 있다가 발견하면 scheduling 을 해준다. 이제 pod 정보에 `nodeName` 이 존재하게 된다.
여기까지는 Control Plane 에서 일어나는 일이다. Controller 들은 단지 API server 와 통신하여 리소스를 업데이트하기만 했다.
#### Kubelet runs the pod's containers
이제 worker node 들의 차례이다. Kubelet 은 자신의 노드에 schedule 된 pod 를 watch 하고 있으므로 pod definition 을 가져와 Docker (혹은 container runtime) 에게 특정 이미지를 기반으로 컨테이너를 실행하라고 명령한다. 이제 container runtime 이 컨테이너를 실행하게 된다.
### 11.2.3 Observing cluster events
위 작업이 수행되는 과정에서 Control Plane 컴포넌트와 Kubelet 은 API server 에 event 가 발생했다고 알려준다. Event 리소스를 생성하여 알려주며, 이는 `kubectl get events` 로 확인할 수 있다.
> 참고로 `kubectl describe` 하면 밑에 event 가 나왔었다.
명령어를 입력해 보면 `SOURCE` 에 어떤 컴포넌트/controller 가 event 를 발생시켰는지, `KIND`, `NAME` 에서 event 가 영향을 미친 리소스의 종류와 이름을 확인할 수 있다. `REASON``MESSAGE` 에서는 상세한 설명을 확인할 수 있게 된다.
## 11.3 Understanding what a running pod is
---
Pod 를 실행했으므로, *실행중인 pod* 가 무엇인지 살펴보자. Kubelet 이 컨테이너를 실행하는 것은 알았는데, 더 할 일이 있을까?
Pod 를 실행한 뒤, *노드*에 `ssh` 연결하여 `docker ps` 를 입력해 보면 `COMMAND``/pause` 인 컨테이너가 하나 있는데, 이 컨테이너가 한 pod 내의 컨테이너를 하나로 묶는 역할을 해준다.
Pod 내의 컨테이너는 linux namespace 와 네트워크를 공유하기 때문에, `/pause` 컨테이너는 이 linux namespace 를 붙잡고 있는 infrastructure 컨테이너인 것이다.
Pod 내의 컨테이너는 재시작 되는 경우가 많다. 이 때 같은 linux namespace 로 재시작 되어야 하는데, 위와 같이 infrastructure 컨테이너가 존재하기 때문에 같은 linux namespace 를 사용할 수 있게 된다. 이 infrastructure 컨테이너는 pod 와 lifecycle 이 같기 때문에 pod 가 삭제될 때 같이 지워진다. 만약 infrastructure 컨테이너를 삭제하면 Kubelet 이 다시 만들어 주고, pod 의 컨테이너도 전부 다시 만든다.
## 11.4 Inter-pod networking
---
Pod 들은 고유 IP 를 가지며, NAT 없이 flat network 구조를 갖는다. 작동 원리를 살펴본다.
### 11.4.1 What the network must be like
Kubernetes 는 특정 네트워크 기술을 요구하지는 않지만, 컨테이너들끼리는 어떤 노드에 있을지라도 서로 통신이 가능해야 한다.
또한 통신에 사용하는 IP 는 NAT 가 적용되지 않아서, 어디서 바라보더라도 자신의 IP 가 동일해야 한다. 이렇게 되야 하는 이유는 마치 같은 네트워크에 연결된 머신에서 동작하는 것처럼 보이고 또 간단해지기 때문이다.
이러한 요구사항을 만족하는 네트워크 기술은 많지만 구현체마다 다르고 상황에 따라 장단점이 다르기 때문에, 일반적인 방법에 대해 다룰 것이다.
### 11.4.2 Diving deeper into how networking works
Infrastructure 컨테이너에 IP 주소와 network namespace 가 설정된다는 것을 11.3 에서 확인했다. Pod 의 컨테이너는 해당 network namespace 를 사용하게 된다.
#### Enabling communication between pods on the same node
Infrastructure 컨테이너가 시작되기 전에 virtual Ethernet (veth) 인터페이스 pair 가 컨테이너에 생성된다. 한쪽은 노드의 namespace 에 남아있고, 다른 쪽은 컨테이너 안의 namespace 가 되어 `eth0` 으로 이름이 변경된다.
이제 노드의 namespace 에 남아있는 인터페이스는 container runtime 이 사용하는 network bridge 에 연결된다. 이제 `eth0` 인터페이스는 bridge 의 IP 대역 중 하나를 할당받아 IP 로 사용하게 된다.
컨테이너 내부에서 요청이 나가는 경우 `eth0` -> `vethXXX` -> Bridge 의 순서로 가게 된다.
만약 이 요청의 destination 이 노드 내의 다른 pod 라면 Bridge -> `vethYYY` -> `eth0` 의 경로로 다른 pod 에 요청이 전달된다.
#### Enabling communication between pods on different nodes
다른 노드의 pod 와 통신하는 방법은 다양하지만, layer 3 routing 방법을 살펴볼 것이다.
우선 Pod IP 는 클러스터 내에서 유일해야 하므로, 노드의 network bridge 에 할당된 IP 대역은 서로 겹치지 않아야 한다.
각 노드에 layer 3 networking 을 적용하여 노드의 physical network interface 가 bridge 에 연결되도록 해주면 된다. 그리고 각 노드에서 routing table 을 설정해주면 된다.
이제 다른 노드로 통신하려는 경우 패킷의 이동 경로는 `eth0` -> `vethXXX` -> Bridge -> Node's physical adapter -> Network Wire -> Other node's physical adapter -> Bridge -> `vethYYY` -> `eth0` 가 된다.
이 방법은 물론 노드가 같은 네트워크 스위치에 연결된 경우, 사이에 라우터가 없을 때만 유효하다. 물론 라우터를 사용하여 노드 사이에 오고가는 패킷을 routing 할 수 있겠지만, 노드 개수가 많아지거나 노드 사이의 라우터 개수가 많아지면 어렵고 오류가 발생하기 쉽다. 이러한 이유로 Software Defined Network (SDN) 을 사용하는 것이 편하다.
SDN 을 사용하게 되면 노드들이 같은 네트워크 스위치에 연결된 것처럼 보이게 되며, pod 에서 나가는 패킷은 캡슐화되어서 다른 pod 에 전달되고 un-캡슐화된다.
### 11.4.3 Introducing the Container Network Interface
컨테이너가 네트워크에 연결하는 것을 쉽게 하기 위해 Container Network Interface (CNI) 프로젝트가 시작되었다. Kubernetes 에서 해당 CNI 플러그인을 사용하도록 할 수 있다.
종류에는 Calico, Flannel, Romana, Weave Net 등이 있다.
플러그인 설치는DaemonSet 과 기타 리소스를 포함한 YAML 파일을 생성하면 된다. 참고로 Kubelet 실행시 `--network-plugin=cni` 로 옵션을 줘야 한다.
## 11.5 How services are implemented
---
복습!
- 각 service 는 stable IP 주소와 포트를 갖고, 클라이언트는 이 주소로 연결하여 service 를 사용한다.
- IP 주소는 가상 IP 주소로, 네트워크 인터페이스에 할당되어있지 않고, 노드 밖으로 나가는 패킷에 source/destination IP 로 들어가지 않는다.
### 11.5.1 Introducing the kube-proxy
Service 와 관련된 모든 것들은 각 노드에서 실행중인 kube-proxy 프로세스에 의해 관리된다.
예전에는 `userspace` proxy mode 를 통해 진짜 pod 로 연결을 proxy 해주는 역할을 했으나, 현재는 `iptables` 를 사용하고 있다.
### 11.5.2 How kube-proxy uses iptables
API server 가 service 생성 요청을 받으면, service 에 가상 IP 주소가 바로 할당된다. 그 다음, API server 는 worker node 의 kube-proxy 에게 새로운 service 가 생성됐음을 알린다. 그 다음 kube-proxy 는 해당 service 가 참조 가능하도록 자신의 노드의 `iptables` 규칙을 수정한다.
`iptables` 규칙을 수정하게 되면 service 를 목적지로 갖는 패킷을 가로채 목적지 주소가 변경되고 endpoint 중 한 곳으로 패킷이 연결된다.
kube-proxy 는 service 의 변화만 watch 하고 있는 것은 아니고, Endpoints 의 변화도 지켜보고 있다. 그래서 pod 가 생성/삭제되거나 readiness 상태가 바뀌거나 label 이 수정되었는지 확인한다.
#### 예시
예를 들어, pod A 에서 172.30.0.1:80 에 있는 service 로 패킷을 보낸다고 하면, 패킷의 목적지 주소는 172.30.0.1:80 일 것이다. 네트워크로 패킷이 보내지기 전에, 노드의 `iptables` 규칙을 먼저 적용하게 된다.
규칙 중에서 적용할 규칙이 있는지 확인하게 되는데, service 가 생성된 상태이므로 '목적지가 172.30.0.1:80 인 패킷의 목적지는 임의로 선택된 pod 의 IP 로 교체되어야 한다'는 규칙이 있을 것이다.
그러므로 이 패킷의 목적지는 service 의 endpoint 중 한 pod 의 주소와 포트로 변경된다. 이때부터는 마치 클라이언트에서 선택된 pod 로 직접 (directly) 요청을 보낸 것처럼 보이게 된다.
## 11.6 Running highly available clusters
---
Kubernetes 를 사용하는 이유 중 하나로 인프라에 문제가 생겨도 중단 없이 서비스를 유지할 수 있다는 점이 있다. 중단 없이 서비스를 유지하기 위해서는 앱이 계속 실행되고 있어야 하기도 하지만, 노드에 있는 Control Plane 컴포넌트들이 잘 작동해 줘야 한다. 고가용성 (HA) 를 달성하기 위해 어떤 것들이 관련되어 있는지 살펴볼 것이다.
### 11.6.1 Making your apps highly available
노드에 문제가 생기더라도 Kubernetes 의 다양한 controller 덕분에 우리의 애플리케이션은 안정적으로 돌아갈 수 있게 된다. 애플리케이션의 HA 를 위해서는 Deployment 를 사용해서 충분한 replica 수를 정해주면, 나머지는 Kubernetes 가 알아서 해줄 것이다.
#### Running multiple instances to reduce the likelihood of downtime
당연히 여러 인스턴스를 만들어두면 downtime 이 줄어들 것이다. 물론 애플리케이션이 horizontally scalable 해야한다는 조건이 있다. 만약 scalable 하지 않더라도 Deployment 를 이용해 replica count 를 1로 설정해서 배포하는 것이 좋다. 문제가 생겼을 때 어쩔 수 없이 약간의 지연이 있긴 하겠지만 알아서 생성해주긴 할 것이다.
#### Using leader-election for non-horizontally scalable apps
Downtime 을 회피하기 위해서는 비활성 상태의 replica 를 몇 개 더 만들어두고, leader-election 방법을 이용해 replica 들 중 반드시 하나만 작동하도록 해주면 된다.
만약 나머지도 전부 동작한다면, 하나의 replica 만 write 가 가능하고 나머지는 read 만 가능하게 해도 괜찮을 것이다. 중요한 것은 인스턴스끼리 동기화 문제가 발생하지 않도록 하면 된다는 점이다.
그리고 이 leader-election 을 애플리케이션에 구현할 필요는 없고, sidecar 컨테이너에 담을 수 있도록 이미 구현체가 존재한다.
### 11.6.2 Making Kubernetes Control Plane components highly available
만약 Kubernetes 에 문제가 생기면 진짜 답이 없어지는데, 이런 경우를 위해서는 Control Plane 컴포넌트들에 HA 를 달성해야 한다. 이를 위해서는 마스터 노드를 여러개 띄워야 하는데, 각 노드들은 API server, etcd, Controller Manager, Scheduler 를 각각 가지고 있어야 한다.
#### Running an etcd cluster
etcd 는 애초에 분산 시스템을 위해 설계되었기 때문에 여러 개의 etcd 를 띄워도 괜찮다. 홀수 개의 etcd 만 유지하고, 각 etcd 인스턴스가 다른 노드에 있는 etcd 인스턴스의 존재를 알고 있으면 된다. 이는 각 인스턴스의 설정에 다른 인스턴스들의 리스트를 넘겨줘서 설정한다. (다른 etcd 의 IP/포트 정보를 넘겨준다)
etcd 는 앞에서 설명한 RAFT 로 데이터를 모든 인스턴스에 복제할 것이기 때문에, 인스턴스 몇 개에 문제가 생겨도 괜찮다. HA 를 위해서는 5~7개의 etcd 를 띄워서 2~3개 정도의 etcd failure 를 감당할 수 있도록 하면 된다.
#### Running multiple instances of the API server
API server 를 복제하는 것은 더 쉽다! API server 는 거의 stateless 하다. 물론 약간의 caching 이 있지만 데이터는 전부 etcd 에 저장되기 때문에 여러 인스턴스를 띄울 수 있게 된다. 인스턴스끼리 서로의 존재를 알 필요도 없다.
다만 API server 의 앞단에 로드 밸런서를 두어 API server 중 healthy 한 쪽으로만 클라이언트의 요청이 가도록 조절할 필요가 있다.
#### Ensuring high availability of the controllers and the Scheduler
얘네 둘은 간단하지 않다. Controller 와 Scheduler 는 클러스터의 상태를 계속 모니터링하고 상태가 변할 때 일하기 때문에 클러스터 상태가 변한 것을 감지한 다수의 controller 가 한꺼번에 반응하게 될 수도 있다. (예를 들면 replica count 가 변경된 것을 보고 5개의 controller 가 한꺼번에 pod 생성 요청을 보내면 pod 가 5개 추가될 것이다)
위와 같은 이유로 controller 와 Scheduler 는 한 번에 한 인스턴스씩 일을 할수 있도록 이미 설계되어 있다. 내부적으로 leader-election 알고리즘을 가지고 있어서, 자신이 leader 일 때만 작업을 수행한다. 나머지 인스턴스들은 leader 에게 문제가 생길 때까지 가만히 있는다.
#### Understanding the leader election mechanism used in Control Plane components
여기서 흥미로운 점은 leader election 을 위해 컴포넌트들이 서로 통신하지 않아도 된다는 점이다. Leader election 의 동작 과정을 보면 API server 에 Endpoints 리소스를 만들어서 한다. (저자는 Endpoints 리소스를 그렇게 쓰는거 아니라는 의미에서 *abused* 라고 표현했다. 다른 리소스를 사용했어도 괜찮았을 것이라고 한다.)
`kube-scheduler` 라는 이름을 가진 Endpoints 리소스를 확인해 보면 annotation 으로 `control-plane.alpha.kubernetes.io/leader` 를 가지고 있는 것을 확인할 수 있다. 여기에 `holderIdentity` 라는 필드가 있는데, 여기에 현재 leader 의 이름이 들어간다.
Optimistic locking 때문에 여러 개의 Scheduler 가 자신을 `holderIdentity` 에 넣으려고 하겠지만, 오직 한 인스턴스만 성공하며, 그 인스턴스가 leader 가 된다. 또한 자신이 계속 살아있음을 증명해야 하기 때문에 주기적으로(2초마다) 이 Endpoints 리소스를 업데이트하여 다른 Scheduler 에게 알린다. Leader 가 실패했음을 알게된다면 다른 인스턴스들은 모두 자신의 이름을 `holderIdentity` 에 넣고 leader 가 되기 위해 또다시 경쟁한다.
---
## Discussion & Additional Topics
### RAFT
- https://en.wikipedia.org/wiki/Raft_(algorithm)
- https://raft.github.io/
### Authentication vs Authorization
### Leader Election Algorithm
- https://en.wikipedia.org/wiki/Leader_election