Skip to content

Commit

Permalink
[lees741] ch02 문서 추가
Browse files Browse the repository at this point in the history
  • Loading branch information
hyunseong.lee committed Nov 5, 2023
1 parent 9b0ff5e commit 2fc841c
Showing 1 changed file with 245 additions and 0 deletions.
245 changes: 245 additions & 0 deletions lees741/week1/Ch02.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
# Ch 2: 왜 파드인가?

파드는 (대부분의 객체가 그렇듯이) 쿠버네티스 API 내에 정의되어 있는 객체다.
파드는 쿠버네티스 클러스터에 배치될 수 있는 가장 작은 단위이다.

## 2.1 웹 애플리케이션 예제
> 제우스 잽 에너지 음료 회사 예시
이 회사의 웹사이트는 UI, 중간 계층, 백엔드 데이터베이스의 세 개의 계층으로 구성되고 메시징 및 큐잉 프로토콜을 갖추고 있다.

### 제우스 잽 웹 애플리케이션의 구조
* NGINX가 서비스하는 자바스크립트 프론트엔드
* 장고와 함께 호스팅된 파이썬 마이크로서비스인 두 개의 웹 컨트롤러 레이어
* 스토리지에서 지원되는 6379 포트의 CockroachDB 백엔드

운영 환경에서 4개의 컨테이너로 이 애플리케이션 실행한다고 가정, 아래의 명령어를 사용해 앱을 시작할 수 있다.

```shell
$ docker run -t -i ui -p 80:80
$ docker run -t -i microservice-a -p 8080:8080
$ docker run -t -i microservice-b -p 8081:8081
$ docker run -t -i cockroach:cockroach -p 6379:6379
```

#### 문제점
* 이미지가 실행되는 호스트 시스템에는 80번 포트가 하나만 있기 때문에 로드 밸런싱을 하지 않는 한 여러 개의 UI 컨테이너 복사본을 실행할 수 없다.
* 웹 앱에 IP 주소를 수정해서 주입(또는 CockroachDB가 이동할 때 동적으로 업데이 트되는 DNS 서버를 추가)하지 않는 한 CockroachDB 컨테이너를 다른 서버로 마이그레이션할 수 없다.
* 고가용성을 위해 각 CockroachDB 인스턴스는 별도의 서버에서 실행해야 한다. (다중화)
* 한 서버에서 CockroachDB 인스턴스가 죽으면 해당 데이터를 새로운 노드로 이동하고 사용하지 않는 스토리지 공간을 회수할 수 있는 방법이 필요하다.

###### 시끄러운 이웃 현상
한 서버에서 많은 프로세스를 실행하면 종종 발생하는 현상. 혼잡할 정도로 많은 애플리케이션은 부족한 리소스에 대한 과도한 경쟁으로 이어진다.

#### 요구사항
* 모두 같은 포트에 바인딩된 수백 개의 프로세스 사이의 공유 네트워킹
* 로컬 디스크가 지저분해지는 것을 방지하면서 바이너리로부터 스토리지 볼륨의 마이그레이션과 분리
* 비용을 절감하기 위해 사용가능한 CPU와 메모리 리소스의 활용 최적화
* 스토리지 인식 스케줄링: 데이터를 함께 사용할 수 있게 프로세스를 스케줄링
* 서비스 인식 네트워크 로드 밸런싱: 컨테이너가 한 머신에서 다른 머신으로 이동할 때 다른 IP 주소로 트래픽을 전송

### 2.1.1 웹 애플리케이션 인프라
#### 쿠버네티스가 없는 경우
* 배포 플랫폼으로서의 VM이나 물리 서버
* 로드 밸런싱
* 애플리케이션 발견
* 스토리지
* 보안 시스템

### 2.1.2 운영 요구사항
제우스 잽 에너지 음료 회사는 다양한 e-스포츠 이벤트를 후원하고 있기 때문에 많은 트래픽이 이벤트 기간에 몰린다. (burst traffic)

#### 요구사항
* 확장성
* 고가용성
* 롤백 허용하는 버전 관리 애플리케이션
* 비용 관리

## 2.2 파드란 무엇인가?
* 파드: 쿠버네티스 클러스터 노드에서 컨테이너로 실행되는 하나 이상의 이미지
* 노드: `kubelet`을 실행하는 단일 컴퓨팅 파워

쿠버네티스의 모든 것과 마찬가지로 노드도 API 객체이다.

#### 파드 배치
```shell
$ cat << EOF > pod.yaml
apiVersion: v1 # API 서버의 버전과 일치
kind: Pod # kind는 API 서버에 대한 API 객체의 타입을 선언
metadata:
spec:
container:
- name: busybox
image: mycontainerregistry.io/foo # 레지스트리 내의 이미지 이름을 지정한다.
EOF

$ kubectl create -f pod.yaml # kubectl 명령어
```

대부분의 경우 파드는 직접 배포되지 않고 정의된 디플로이먼트, 잡, 스테이트풀셋, 데몬셋 같은 다양한 API 객체에 의해 생성된다.
* 디플로이먼트(`Deployment`): 가장 일반적으로 사용되는 API 객체, 마이크로서비스로 배포된다.
* 잡(`Job`): 일괄 처리 프로세스로 파드를 실행한다.
* 스테이트풀셋(`StatefulSet`): 특정 요구사항을 필요로 하는 호스트 애플리케이션으로, 데이터베이스 같은 상태 저장 애플리케이션일 때가 많다.
* 데몬셋(`DaemonSet`): 클러스터의 모든 노드에서 '에이전트'로 단일 파드를 실행하려는 경우 사용, 일반적으로 네트워킹이나 스토리지, 로깅을 포함하는 시스템 서비스에 사용된다.

#### 스테이트풀셋의 기능
* 고유한 네트워크 식별자를 얻기 위한 Ordinal 파드 이름 지정
* 항상 같은 파드에 마운트되는 영구적인 스토리지
* 순서에 따르는 시작, 확장, 업데이트

파드가 시작되면 `kubectl get pod` 명령어를 통해 기본 네임스페이스에서 실행 중인 파드를 볼 수 있다.

kubectl은 영리해서 복수형 및 축약형도 다 알아먹는다.

ex. `kubectl get pods` 또는 `kubectl get po`

### 2.2.1 다양한 리눅스 네임스페이스
쿠버네티스 네임스페이스는 리눅스 네임스페이스와 다르다.

* 리눅스 네임스페이스: 커널 내부에서 프로세스 분리를 허용하는 리눅스 커널의 기능

기본적으로 파드는 특정 구성 내부에 있는 네임스페이스의 묶음이다.

네트워킹 네임스페이스 내에는 쿠버네티스 클러스터를 포괄하는 SDN 시스템에 연결되는 가상 네트워킹 스택이 존재한다.

애플리케이션에 대한 여러 파드의 로드 밸런싱을 위해서는 확장이 필요할 때가 많다. 쿠버네티스 클러스터 내의 SDN은 로드 밸런싱을 지원하는 네트워킹 프레임워크다.

### 2.2.2 쿠버네티스, 인프라, 파드
#### 노드의 요구사항
* 서버
* 다양한 리눅스 및 윈도우 지원 요구사항을 갖는 운영체제
* `systemd`: 리눅스 시스템 및 서비스 관리자
* `kubelet`: 노드 에이전트
* 컨테이너 런타임 (ex. 도커)
* `kube-proxy`: 쿠버네티스 서비스를 처리하는 네트워크 프록시
* CNI 공급자

노드는 라즈베리 파이나 클라우드의 VM 등 다양한 플랫폼에서 실행할 수 있다.

#### kubelet
에이전트로 실행되는 바이너리 프로그램으로, 다양한 제어 루프를 통해 쿠버네티스 API 서버와 통신하고 모든 노드에서 실행된다. 이것이 없으면 쿠버네티스 노드는 스케줄링을 할 수 없거나 클러스터 일부로 간주될 수 없다.

* `kubelet`의 호스트에 예약된 모든 파드는 어떤 파드가 어떤 노드에 예약되었는지 감시하는 제어 루프를 통해 실행된다.
* 쿠버네티스 1.17 이상에서 API 서버는 하트비트를 통해 `kubelet`이 정상인 것을 계속해서 인식한다.
* 임시 스토리지나 네트워크 디바이스를 포함하는 파드에 대해 필요에 따라 가비지가 수집된다.

`kubelet`은 CNI 공급자와 컨테이너 런타임 없이 아무런 작업도 수행할 수 없다.

#### Service
* `ClusterIP`: 쿠버네티스 파드의 로드 밸런싱을 하는 내부 서비스
* `NodePort`: 다양한 파드의 로드 밸런싱을 하는 쿠버네티스 노드의 개방형 포트
* `LoadBalancer`: 클러스터 외부에 로드 밸런서를 생성하는 외부 서비스

`kube-proxy`는 모든 노드에서 `ClusterIP``NodePort` Service의 생성을 처리한다.

### 2.2.3 노드 API 객체
다음과 같이 간단한 `kubectl` 명령으로 클러스터의 노드를 확인할 수 있다.
```shell
$ kubectl get node
NAME STATUS ROLES AGE VERSION
kind-control-plane NotReady master 25s v1.17.0
```
마찬가지로 복수형 및 축약형도 다 알아먹는다.

ex. `kubectl get nodes` 또는 `kubectl get no`

#### 쿠버네티스의 제어 역할
* 프로세스의 스토리지 바인딩
* 실행 중인 컨테이너의 생성과 컨테이너 개수의 확장
* 컨테이너가 정상적이지 않은 경우 해당 컨테이너를 제거 및 마이그레이션
* 포트에 대한 IP 경로 생성
* 로드 밸런싱된 엔드포인트의 동적 업데이트

CoreDNS 같은 DNS 시스템은 애플리케이션 조회 기능을 제공한다.

ex. 한 파드의 마이크로서비스가 또 다른 파드에서 실행 중인 CockroachDB와 통신 가능

#### 영구 스토리지
`mnt` 리눅스 네임스페이스, `kubelet`, 그리고 노드의 조합을 통해 파드에 드라이브를 마운트할 수 있다.
그러면 `kubelet`이 파드를 생성할 때 해당 스토리지가 파드에 마운트된다.

### 2.2.4 웹 애플리케이션과 컨트롤 플레인
* 고가용성
* 확장
* 비용 절감
* 컨테이너 버전 관리
* 사용자와 애플리케이션의 보안

## 2.3 kubectl로 웹 애플리케이션 생성하기
`kubectl apply`를 실행하면 `kubectl`은 컨트롤 플레인의 첫 번째 컴포넌트인 API 서버와 통신한다.

### 2.3.1 쿠버네티스 API 서버: kube-apiserver
`kube-apiserver`: 다양한 API 객체를 노출하는 HTTP 기반의 REST 서버

컨트롤 플레인의 컴포넌트는 API 서버와도 통신한다. 노드가 시작되면 `kubelet`은 API 서버와 통신을 통해 노드가 클러스터에 등록됐는지 확인한다.

컨트롤 플레인의 모든 컴포넌트는 API 서버의 객체 변경을 모니터링하기 위한 제어 루프를 갖고 있다.

API 서버는 쿠버네티스의 데이터베이스인 `etcd`와 통신하는 컨트롤 플레인의 유일한 컴포넌트다.

API 서버는 클러스터를 수정하는 모든 작업에 대한 상태 저장 인터페이스를 제공하기 때문에 API 서버와 서버의 HTTPS 엔드포인트를 보호하는 것이 중요하다.

클라이언트가 API 서버와 통신할 때 API 서버의 일부로 실행되는 승인 컨트롤러는 인증과 권한 부여 기능을 모두 제공한다.

### 2.3.2 쿠버네티스 스케줄러: kube-scheduler
`kube-scheduler`: 파드를 특정 노드에 할당한다.

스케줄러는 파드를 할당하기 위해 다양한 요소를 고려하는데 노드의 하드웨어 구성요소, 이용 가능한 CPU와 메모리 리소스, 스케줄링 제약 정책, 다른 가중치 요소 등을 고려한다.

* 파드 선호도(Pod affinity) 규칙: 규칙과 일치하는 노드에 파드를 끌어들인다.
* 파드 반선호도(Pod anti-affinity) 규칙: 노드에서 파드를 제거한다.
* 테인트(Taint): 노드가 파드의 세트에 접근하지 못하게 한다.

쿠버네티스 스케줄러가 `NodeName`으로 파드를 업데이트하면 `kubelet`은 해당 파드를 노드에 배치한다.

### 2.3.3 인프라 컨트롤러
데이터베이스와 같이 상태를 저장하는 애플리케이션은 특정 운영 요구사항을 갖는 경우가 꽤 있다.

이로 인해 컨트롤러나 오퍼레이터가 애플리케이션을 관리하는 것이 필요한데, 오퍼레이터 패턴이 표준 메커니즘이 되고 있기 때문에 YAML을 사용하는 대신 오퍼레이터를 설치하는 것을 권장한다.

#### 사용자 정의 리소스 정의
사용자 정의 리소스 정의(CRD)는 새로운 API 객체를 정의하는 API 객체다. 사용자는 주로 YAML 파일로 리소스를 정의하는 방식으로 CRD를 생성한다.

정의된 CRD는 기존 쿠버네티스 클러스터에 적용된다.

> ex. n2c의 Jenkins 객체 ??
## 2.4 확장, 고가용성 애플리케이션, 컨트롤 플레인
`kubectl scale`을 실행하면 클러스터에서 실행 중인 파드의 양이 증가하거나 감소할 수 있다.
이 명렁은 데몬셋에는 적용되지 않는다. (클러스터의 모든 노드에서 단일 파드로 실행되기 때문에 확장할 수 없기 때문)

#### 파드 중단 (Pod outage)
`kubelet`은 파드가 실패하는 경우 재시작을 시도하고, 활성 프로브(liveness probe)를 통해 파드가 실패했거나 파드의 프로세스가 중지된 것을 확인한다.

#### 노드 중단 (node outage)
`kubelet`의 제어 루프 중 하나는 계속해서 API 서버를 업데이트하고 하트비트를 통해 노드가 정상 상태임을 보고한다.

1.17 버전 이상에서는 실행 중인 클러스터의 `kube-node-lease` 네임스페이스를 살펴보면 이 하트비트가 어떻게 유지되는지 확인할 수 있다.

만약, 노드가 하트비트를 자주 업데이트하지 않으면 KCM 컨트롤러에 의해 노드는 오프라인 상태로 변경되고, 파드들은 더 이상 해당 노드에 스케줄링되지 않는다.
그리고 해당 노드에 존재했던 파드는 삭제가 예약되고 다른 노드에 다시 스케줄링된다.

#### 소프트웨어 업데이트 중단 (software update outage)
쿠버네티스 플랫폼에서 호스팅되는 애플리케이션은 반드시 우아하게 종료한 다음 재시작하는 것을 지원해야 한다.
일반적으로 배포 업그레이드는 아래 세 가지 방법 중 하나로 수행된다.
* `kubectl edit`: 쿠버네티스 API 객체를 입력으로 받아 API 객체를 편집하기 위해 로컬 터미널을 연다.
* `kubectl apply`: 파일을 입력으로 받아 해당 파일과 관련된 API 객체를 찾아서 자동으로 교체한다.
* `kubectl patch`: 객체에 대해 달라진 부분을 정의하는 작은 패치 파일을 적용한다.

### 2.4.1 자동 확장
갑자기 요청이 확 늘어날 때 수동으로 디플로이먼트를 확장하는 것은 힘들다. 자동 확장(Autoscaling)이 이러한 상황을 효율적으로 해결한다.
1. 더 많은 파드 생성: 수평적 파드 확장 (HorizontalPodAutoscaler)
2. 파드에 더 많은 리소스 제공: 수직적 파드 확장 (VerticalPodAutoscaler)
3. 더 많은 노드 생성 (Cluster Autoscaler)

### 2.4.2 비용 관리
오토 스케일링이 발생해 더 많은 노드가 추가되면 클라우드 사용 비용이 증가한다.

이 때 파드 밀도(Pod density)가 중요해진다. 하나의 노드에 할당된 파드가 많을수록 서버를 추가하는 데 사용되는 비용이 줄어든다.
파드 밀도는 다음 단계에 따라 제어된다.

1. 애플리케이션의 크기와 프로파일링: 애플리케이션의 메모리와 CPU 사용량은 테스트되고 프로파일링돼야 한다. (애플리케이션에 대한 쿠버네티스의 리소스 제한을 적절하게 설정)
2. 노드 크기 선택: 노드 크기가 크면 같은 노드에 여러 개의 애플리케이션을 패키징할 수 있다. SLA 스펙을 만족시키면서 고가용성을 허용할 만큼 충분히 많은 노드 개수를 보장해야 한다.
3. 특정 노드에 대한 특정 애플리케이션의 그룹화: 항아리 안에 짱돌이 여러 개 들어있다면 남는 공간이 많을 것이다.
만약 짱돌 대신 구슬이 여러 개 들어있다면 남는 공간이 줄어들 것이고 모래가 들어있다면 남는 공간은 더 줄어들 것이다.
이렇듯 노드와 애플리케이션을 묶어 그룹화하면 파드 밀도를 높일 수 있다. 테인트와 톨러레이션은 오퍼레이터 패턴이 파드의 배포를 그룹화하고 제어할 수 있게 한다.

0 comments on commit 2fc841c

Please sign in to comment.