|
| 1 | +# 커스텀 리소스, 컨트롤러, 잡, 데몬셋, 스테이트풀셋 |
| 2 | + |
| 3 | +## 커스텀 리소스 |
| 4 | +* 쿠버네티스에서 자체적으로 제공하는 리소스 외에 직접 정의해 사용할 수 있는 리소스 |
| 5 | +* 커스텀 리소스를 제대로 사용하려면 컨트롤러라는 별도 컴포넌트를 이해하고 구현할 수 있어야 함 |
| 6 | + |
| 7 | +> ## 쿠버네티스 컨트롤러 |
| 8 | +> * 도커와 쿠버네티스는 리소스를 생성하는 방식이 다름 |
| 9 | +> * 도커 : `docker run`처럼 특정 명령을 처리하는 주체와 통신해 그 작업을 수행하고 그 결괏값을 돌려 받는 방식 (**명령형**) |
| 10 | +> * 쿠버네티스 : `kubectl apply`처럼 최종적으로 도달해야 하는 상태(바람직한 상태, Desired State)를 정의한 뒤, 현재 상태가 바람직한 상태와 다를 경우 이를 일치시키는 방법 (**선언형**) |
| 11 | +> * 선언형 사용 시 최종적으로 완성되어야 하는 상태가 되기 위해 어떠한 동작을 하는지는 쿠버네티스에서 **컨트롤러**가 내부적으로 결정 |
| 12 | +> * 이때 상태는 **etcd**에 저장되어 있으며, 컨트롤러는 쿠버네티스 API 서버의 **Watch API**를 통해 etcd에 저장된 상태를 받아와 동작 수행 |
| 13 | +> * **etcd** : 클러스터 상태와 설정을 저장하는 오픈소스 분산 키-값 저장소 |
| 14 | +> * **Watch API** : 리소스의 생성, 수정, 삭제 같은 변경 이벤트를 클라이언트나 다른 컨트롤러에게 실시간으로 알려주는 기능 |
| 15 | +
|
| 16 | +* 커스텀 리소스는 직접 정의해 사용할 수 있는 사용자 정의 리소스 |
| 17 | +* 디플로이먼트, 서비스 등의 오브젝트의 묶음을 커스텀 리소스로 추상화 함으로써 쿠버네티스 리소스를 묶어 놓은 패키지처럼 사용할 수도 있음 |
| 18 | + |
| 19 | +* 위 그림과 같이 여러 리소스를 한번에 생성할 수 있고, 각 리소스의 생애 주기를 쉽게 관리 가능 |
| 20 | +* 많은 관리의 복잡성을 줄일 수 있고, 오브젝트를 원하는 대로 확장해 사용 가능 |
| 21 | + |
| 22 | +### Custom Resource Definition |
| 23 | +* crd라 하며 `kubectl get crd` 명령어를 통해 목록 확인 가능 |
| 24 | +* 커스텀 리소스를 정의하는 역할, crd 자체가 커스텀 리소스를 의미하는건 아님 |
| 25 | + |
| 26 | +```yaml |
| 27 | +apiVersion: apiextensions.k8s.io/v1 |
| 28 | +kind: CustomResourceDefinition |
| 29 | +metadata: |
| 30 | + name: managedredises.cloud.com # CRD 이름 |
| 31 | +spec: |
| 32 | + group: cloud.com |
| 33 | + names: # CR 이름 |
| 34 | + kind: ManagedRedis |
| 35 | + plural: managedredises |
| 36 | + shortNames: |
| 37 | + - mr |
| 38 | + singular: managedredis |
| 39 | + scope: Namespaced # 네임스페이스에 속하는지 여부 |
| 40 | + versions: |
| 41 | + - name: v1 |
| 42 | + schema: |
| 43 | + openAPIV3Schema: |
| 44 | + properties: |
| 45 | + spec: |
| 46 | + properties: |
| 47 | + image: |
| 48 | + description: The image of Redis cluster |
| 49 | + type: string |
| 50 | + replica: |
| 51 | + description: The number of Redis replica |
| 52 | + type: integer |
| 53 | + required: # 반드시 포함되어야 하는 spec 정의 부분 |
| 54 | + - replica |
| 55 | + - image |
| 56 | + type: object |
| 57 | + status: # 해당 CRD를 관리하는 컨트롤러에 의해 채워지는 현재 상태 정보 |
| 58 | + properties: |
| 59 | + message: # 현재 상태 설명 |
| 60 | + description: A descriptive message about the cluster's status. |
| 61 | + type: string |
| 62 | + phase: # 현재 상태 |
| 63 | + description: The current phase of the Redis cluster (e.g., Pending, |
| 64 | + Running, Failed). |
| 65 | + type: string |
| 66 | + primary_endpoint: # 쓰기 주소 |
| 67 | + description: The Redis cluster's primary connection endpoint for writing. |
| 68 | + type: string |
| 69 | + secondary_endpoint: # 읽기 주소 |
| 70 | + description: The Redis cluster's secondary connection endpoint for |
| 71 | + reading. |
| 72 | + type: string |
| 73 | + type: object |
| 74 | + x-kubernetes-preserve-unknown-fields: true # CRD 스키마에서 알 수 없는(정의되지 않은) 필드를 허용 |
| 75 | + type: object |
| 76 | + subresources: |
| 77 | + status: {} |
| 78 | +``` |
| 79 | +* 위와 같이 crd를 정의하는 것 외에 특정 동작을 수행하는 컨트롤러를 정의해줘야 함 |
| 80 | + |
| 81 | +* 컨트롤러는 Watch API를 통해 새로운 CR이 생성되었다는 것을 갑지하고, CR이 원하는 바람직한 상태가 되도록 특정 동작 수행 |
| 82 | +* 이러한 바람직한 상태가 되도록 특정 동작을 수행하는 것을 **Reconcile**이라 부름 |
| 83 | +* 이렇게 일련의 동작을 통해 CRD를 사용할 수 있도록 컨트롤러를 구현하는 방법을 오퍼레이터(Operator) 패턴이라 부름 |
| 84 | +
|
| 85 | +## 잡 |
| 86 | +* 잡(Job)은 특정 동작을 수행하고 종료해야 하는 작업을 위한 오브젝트 |
| 87 | +* 잡에서 원하는 최종 상태는 **포드가 실행되어 정상적으로 종료되는 것** |
| 88 | +```yaml |
| 89 | +apiVersion: batch/v1 |
| 90 | +kind: Job |
| 91 | +metadata: |
| 92 | + name: job-hello-world |
| 93 | +spec: |
| 94 | + template: |
| 95 | + spec: |
| 96 | + restartPolicy: Never # OnFailure 설정 시 spec.backoffLimit으로 재시도 횟수 설정 가능 (기본적으로는 6번) |
| 97 | + containers: |
| 98 | + - image: busybox |
| 99 | + args: ["sh", "-c", "echo Hello, World && exit 0"] |
| 100 | + name: job-hello-world |
| 101 | +``` |
| 102 | +```bash |
| 103 | +> kubectl apply -f job-hello-world.yaml |
| 104 | +job.batch/job-hello-world created |
| 105 | + |
| 106 | +> kubectl get pods |
| 107 | +NAME READY STATUS RESTARTS AGE |
| 108 | +job-hello-world-cj7lv 0/1 Completed 0 12s |
| 109 | + |
| 110 | +> kubectl get jobs |
| 111 | +NAME STATUS COMPLETIONS DURATION AGE |
| 112 | +job-hello-world Complete 1/1 8s 9s |
| 113 | +``` |
| 114 | +* 한 번 수행하고 종료되는 배치 작업에 사용됨 |
| 115 | +* 잡은 동시성을 엄격하게 보장해야 하는 병렬 처리를 위해 사용하는 것이 아님 |
| 116 | +* restartPolicy에 의해 재시작될 수도 있어서 잡이 처리하는 작업은 멱등성을 가져야 함 |
| 117 | + * **멱등성** : 첫 번째 수행을 한 뒤 여러 차례 적용해도 결과를 변경시키지 않는 작업 또는 기능의 속성 |
| 118 | + |
| 119 | +### 잡의 세부옵션 |
| 120 | +* spec.completions : 잡이 성공했다고 여겨지려면 몇 개의 포드가 성공해야 하는지 |
| 121 | +* spec.parallelism : 동시에 생성될 포드의 개수 (기본값은 1) |
| 122 | +* spec.activeDeadlineSeconds : 포드가 실행될 수 있는 최대 시간. 더 오래 실행될 경우 강제로 종료되며, 잡은 실패함 |
| 123 | + |
| 124 | +### 크론잡으로 잡을 주기적으로 실행하기 |
| 125 | +* 잡을 주기적으로 실행하는 쿠버네티스 오브젝트 |
| 126 | +```yaml |
| 127 | +apiVersion: batch/v1 |
| 128 | +kind: CronJob |
| 129 | +metadata: |
| 130 | + name: cronjob-ex |
| 131 | +spec: |
| 132 | + schedule: "*/1 * * * *" |
| 133 | + jobTemplate: |
| 134 | + spec: |
| 135 | + restartPolicy: Never |
| 136 | + containers: |
| 137 | + - image: busybox |
| 138 | + args: ["sh", "-c", "echo Hello, World && exit 0"] |
| 139 | + name: job-hello-world |
| 140 | +``` |
| 141 | +* 리눅스의 크론 스케줄 방법을 사용하여 정의 가능 |
| 142 | +
|
| 143 | +## 데몬셋 |
| 144 | +* 데몬셋(DaemonSet)은 쿠버네티스의 모든 노드에 동일한 포드를 하나씩 생성하는 오브젝트 |
| 145 | +* 로깅, 모니터링, 네트워킹 등을 위한 에이전트를 각 노드에 생성해야 할 때 사용 |
| 146 | +```bash |
| 147 | +> kubectl get daemonsets -n kube-system |
| 148 | +NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE |
| 149 | +kube-proxy 1 1 1 1 1 kubernetes.io/os=linux 69d |
| 150 | +``` |
| 151 | +* 쿠버네티스 네트워킹을 위한 kube-proxy 컴포넌트 등이 데몬셋으로 실행되고 있음 |
| 152 | +```yaml |
| 153 | +apiVersion: apps/v1 |
| 154 | +kind: DaemonSet |
| 155 | +metadata: |
| 156 | + name: daemonset-example |
| 157 | +spec: |
| 158 | + selector: |
| 159 | + matchLabels: |
| 160 | + name: my-daemonset-example # 포드를 생성하기 위한 셀렉터 |
| 161 | + template: |
| 162 | + metadata: |
| 163 | + labels: |
| 164 | + name: my-daemonset-example # 포드 라벨 |
| 165 | + spec: |
| 166 | + containers: |
| 167 | + - name: daemonset-example |
| 168 | + image: busybox |
| 169 | + args: ["tail", "-f", "/dev/null"] |
| 170 | +``` |
| 171 | +* 특정 노드에만 데몬셋의 포드를 생성하고 싶다면 nodeSelector나 Node Affinity를 포드에 적용할 수도 있음 |
| 172 | +
|
| 173 | +## 스테이트풀셋 |
| 174 | +* 쿠버네티스에서 MSA 구조로 동작하는 애플리케이션은 대부분 Stateless인 경우가 많음 |
| 175 | +* 데이터베이스처럼 Stateful한 애플리케이션은 스테이트풀셋(StatefulSet)으로 관리 가능 |
| 176 | + |
| 177 | +
|
| 178 | +```yaml |
| 179 | +apiVersion: apps/v1 |
| 180 | +kind: StatefulSet |
| 181 | +metadata: |
| 182 | + name: statefulset-example |
| 183 | +spec: |
| 184 | + serviceName: statefulset-service |
| 185 | + selector: |
| 186 | + matchLabels: |
| 187 | + name: statefulset-example |
| 188 | + replicas: 3 |
| 189 | + template: |
| 190 | + metadata: |
| 191 | + labels: |
| 192 | + name: statefulset-example |
| 193 | + spec: |
| 194 | + containers: |
| 195 | + - name: statefulset-example |
| 196 | + image: alicek106/rr-test:echo-hostname |
| 197 | + ports: |
| 198 | + - containerPort: 80 |
| 199 | + name: web |
| 200 | + |
| 201 | +--- |
| 202 | + |
| 203 | +apiVersion: v1 |
| 204 | +kind: Service |
| 205 | +metadata: |
| 206 | + name: statefulset-service |
| 207 | +spec: |
| 208 | + ports: |
| 209 | + - port: 80 |
| 210 | + name: web |
| 211 | + clusterIP: None |
| 212 | + selector: |
| 213 | + name: statefulset-example |
| 214 | +``` |
| 215 | +
|
| 216 | +```bash |
| 217 | +> kubectl apply -f statefulset-example.yaml |
| 218 | +statefulset.apps/statefulset-example created |
| 219 | + |
| 220 | +> kubectl get sts |
| 221 | +NAME READY AGE |
| 222 | +statefulset-example 3/3 18s |
| 223 | + |
| 224 | +> kubectl get pods |
| 225 | +NAME READY STATUS RESTARTS AGE |
| 226 | +job-hello-world-cj7lv 0/1 Completed 0 54m |
| 227 | +statefulset-example-0 1/1 Running 0 25s |
| 228 | +statefulset-example-1 1/1 Running 0 13s |
| 229 | +statefulset-example-2 1/1 Running 0 12s |
| 230 | +``` |
| 231 | +* 포드에 랜덤한 이름이 아닌 고유한 이름이 붙혀짐 |
| 232 | +* spec.ServiceName을 정의하는 이유는 랜덤 포드가 아닌 개별 포드에 요청을 전달하기 위함 |
| 233 | + |
| 234 | +* 각 포드는 고유하게 식별되어야 하며, 랜덤한 포드로 전달되는 동작은 스테이트풀셋이 원하는 동작이 아님 |
| 235 | +* 이때 일반적인 서비스가 아닌 헤드리스 서비스 사용 가능 |
| 236 | + |
| 237 | +### 헤드리스 서비스 |
| 238 | +* clusterIP의 항목이 None으로 되어 있음 → 헤드리스 서비스 의미 |
| 239 | +* 로드 밸런싱을 수행하지 않고, 개별 파드에 직접 접근할 수 있는 경로를 제공하는 방식 |
| 240 | +```bash |
| 241 | +> kubectl run -i --tty --image busybox:1.28 debug --restart=Never --rm \ |
| 242 | +> nslookup statefulset-service |
| 243 | +Server: 10.96.0.10 |
| 244 | +Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local |
| 245 | + |
| 246 | +Name: statefulset-service |
| 247 | +Address 1: 10.1.1.186 statefulset-example-0.statefulset-service.default.svc.cluster.local |
| 248 | +Address 2: 10.1.1.188 statefulset-example-2.statefulset-service.default.svc.cluster.local |
| 249 | +Address 3: 10.1.1.187 statefulset-example-1.statefulset-service.default.svc.cluster.local |
| 250 | +``` |
| 251 | +* 위와 같이 `<포드의 이름>.<서비스 이름>`을 통해서도 포드에 접근 가능 |
| 252 | +* 포드의 데이터는 일반적으로 휘발성이기 때문에 스테이트풀셋에 PV를 마운트해서 데이터를 보존하는 것이 일반적임 |
| 253 | +* PVC를 정의해두고 다이나믹 프로비저닝으로 PV 자동 생성 방식도 사용 가능 |
0 commit comments