헬스 체크, 관측, 업그레이드, 오퍼레이터
저번 포스팅에서는, 설정과 보안 그리고 apiserver 의 요청 처리 단계를 공부했습니다. 이번에는 시리즈의 마지막으로 운영입니다. 헬스 체크를 어떻게 걸어야 하는지, 문제가 생겼을 때 어디를 봐야 하는지, 클러스터를 어떻게 올리고 확장하는지를 다룹니다.
앞선 일곱 편이 "무엇이 어떻게 동작하는가" 였다면, 이번 편은 "그래서 실제로는 무엇이 문제가 되는가" 에 가깝습니다.
1. 헬스 체크
3편에서 readinessProbe 가 무중단 배포의 핵심이라고 했는데, 프로브 세 가지를 제대로 구분하는 것부터 시작하겠습니다.
1.1. 세 가지 프로브
- livenessProbe : "살아 있는가" 를 봅니다. 실패하면 컨테이너를 재시작합니다.
- readinessProbe : "트래픽을 받을 준비가 되었는가" 를 봅니다. 실패하면 Service 의 엔드포인트에서 빠집니다. 컨테이너는 그대로 둡니다.
- startupProbe : "기동이 끝났는가" 를 봅니다. 이것이 성공할 때까지 나머지 두 프로브는 동작하지 않습니다.
1.2. liveness 를 잘못 걸면 더 위험하다
가장 흔한 사고 유형입니다. livenessProbe 에 의존 서비스까지 확인하는 로직을 넣었다고 합시다. 데이터베이스에 접속해보고 안 되면 실패로 보는 식입니다. 데이터베이스가 잠시 느려지면 어떻게 될까요. 모든 Pod 의 liveness 가 동시에 실패하고 모든 Pod 가 동시에 재시작됩니다. 재시작한다고 데이터베이스가 회복되는 것도 아니니 또 실패하고 무한 재시작에 빠집니다. 원래는 느려지기만 했을 장애가 전면 중단으로 번집니다.
그래서 원칙이 이렇습니다.
- livenessProbe 는 "이 프로세스 자체가 되살아나야만 고쳐지는 상태" 만 봅니다. 데드락처럼 재시작 외에는 방법이 없는 경우입니다. 의존 서비스는 보지 않습니다.
- 의존 서비스는 readinessProbe 에서 봅니다. 데이터베이스가 죽으면 트래픽만 안 받으면 되고 회복되면 자연히 다시 받습니다. 재시작할 이유가 없습니다.
- 확신이 없으면 livenessProbe 를 아예 걸지 않는 것도 방법입니다. 잘못 건 liveness 는 안 건 것보다 나쁩니다.
1.3. startupProbe 가 푸는 문제
기동이 오래 걸리는 애플리케이션(JVM 등)에서 문제가 있었습니다. liveness 의 initialDelaySeconds 를 짧게 두면 아직 뜨는 중인데 재시작되고 길게 두면 정말 죽었을 때 알아채는 데 그만큼 오래 걸립니다.
startupProbe 는 이 둘을 분리합니다. 기동 중에는 느슨하게, 기동이 끝난 뒤에는 촘촘하게 볼 수 있습니다.
startupProbe:
failureThreshold: 30
periodSeconds: 10 # 최대 300초까지 기동을 기다림
livenessProbe:
periodSeconds: 5 # 기동 후에는 5초마다 촘촘히
1.4. 프로브 방식
- httpGet : 가장 흔합니다. 2xx 나 3xx 면 성공입니다.
- tcpSocket : 포트가 열려 있는지만 봅니다. 포트는 열려 있는데 응답을 못 하는 경우를 못 잡습니다.
- exec : 컨테이너 안에서 명령을 실행합니다. 매번 프로세스를 새로 띄우므로 비용이 큽니다. 간격을 짧게 두면 부담이 됩니다.
- grpc : gRPC 헬스 체크 규약을 씁니다.
2. 무엇이 잘못되었는지 찾기
운영에서 가장 많이 하는 일입니다. 증상별로 정리해두면 빠릅니다.
2.1. 기본 순서
kubectl get pod -o wide # 상태와 노드 확인
kubectl describe pod <name> # 이벤트가 여기 있다
kubectl logs <name> --previous # 재시작 전 로그
kubectl get events --sort-by=.lastTimestamp
describe 의 아래쪽 Events 가 가장 중요합니다. 대부분의 원인이 여기에 문장으로 적혀 있습니다. 다만 이벤트는 기본적으로 1시간만 보관되므로, 오래된 문제는 다른 곳을 봐야 합니다.
2.2. Pending
스케줄되지 못한 상태입니다. 6편과 5편의 내용이 원인입니다.
- 노드에 requests 만큼의 여유가 없음
- taint 를 견디지 못함, 또는 affinity · nodeSelector 조건에 맞는 노드가 없음
- PVC 가 바인딩되지 않음
- Topology Spread 의
DoNotSchedule에 걸림
2.3. ImagePullBackOff
이미지를 받지 못한 것입니다. 이름이나 태그 오타, 프라이빗 레지스트리의 imagePullSecrets 누락, 레이트 리밋, 네트워크 문제 등입니다.
2.4. CrashLoopBackOff
컨테이너가 떴다가 계속 죽는 상태입니다. 이름 그대로 재시작 간격이 지수적으로 늘어납니다. 가장 먼저 볼 것은 kubectl logs --previous 입니다. 죽기 직전의 로그가 거기 있습니다. 흔한 원인들입니다.
- 애플리케이션이 시작 즉시 예외로 종료 (설정 누락 등)
- 잘못 건 livenessProbe (1.2 참고)
- OOMKilled :
describe의 Last State 에서 확인됩니다 - 명령어나 진입점이 잘못되어 곧바로 종료
2.5. OOMKilled 인지 Evicted 인지
6편에서 본 구분입니다.
- OOMKilled : 컨테이너가 자기 memory limits 를 넘어 커널이 죽였습니다. → limits 를 올리거나 애플리케이션의 메모리 사용을 줄입니다.
- Evicted : 노드 전체가 부족해 kubelet 이 내보냈습니다. → 노드를 늘리거나, 다른 Pod 의 requests 를 손보거나, QoS 를 올립니다.
describe 에서 Reason 을 보면 갈립니다. 원인이 다르므로 대응도 달라야 합니다.
2.6. 실행 중인 것 들여다보기
kubectl exec -it <pod> -- sh
kubectl port-forward <pod> 8080:8080
kubectl debug -it <pod> --image=busybox --target=<container>
마지막 kubectl debug 가 유용합니다. 임시 컨테이너(Ephemeral Container) 를 실행 중인 Pod 에 끼워 넣습니다. 7편에서 본 distroless 이미지처럼 셸조차 없는 컨테이너를 진단할 때 쓸 수 있습니다. 같은 네임스페이스를 공유하므로 그 컨테이너의 프로세스와 네트워크를 볼 수 있습니다.
노드 자체를 봐야 한다면 kubectl debug node/<node> 로 호스트 네임스페이스에 붙을 수 있습니다.
3. 관측
3.1. 로그
컨테이너가 표준 출력과 표준 에러로 내보낸 것을 kubelet 이 노드의 파일로 남깁니다. kubectl logs 는 그 파일을 읽는 것입니다. 여기서 나오는 한계가 분명합니다.
- Pod 가 사라지면 로그도 사라집니다.
- 노드 디스크를 쓰므로 로테이션이 걸려 있고 오래된 것은 지워집니다.
- 여러 Pod 에 걸쳐 검색할 수 없습니다.
그래서 로그를 클러스터 밖으로 내보내는 것이 사실상 필수입니다. 3편에서 본 DaemonSet 으로 각 노드에 수집기(Fluent Bit 등)를 띄워, 노드의 로그 파일을 읽어 Loki 나 Elasticsearch 로 보내는 구성이 일반적입니다.
애플리케이션은 파일이 아니라 표준 출력으로 로그를 내보내야 합니다. 파일에 쓰면 수집 경로가 복잡해지고 로테이션도 직접 관리해야 합니다.
3.2. 메트릭
두 계층으로 나뉩니다.
- metrics-server :
kubectl top과 HPA 가 쓰는 최소한의 지표만 제공합니다. 현재 값만 있고 이력을 저장하지 않습니다. 모니터링 도구가 아닙니다. - Prometheus : 실제 모니터링용입니다. 각 대상에서 지표를 긁어와(scrape) 시계열로 저장합니다.
Prometheus 는 끌어오는(pull) 방식입니다. 대상이 보내는 것이 아니라 Prometheus 가 주기적으로 가져옵니다. 그래서 대상 목록을 알아야 하는데 쿠버네티스에서는 API 를 조회해 대상을 자동으로 찾는다(service discovery). Pod 가 뜨고 지는 환경에 잘 맞는 이유입니다.
봐야 할 지표들입니다.
- 클러스터 : 노드 상태, 할당 가능한 자원 대비 requests 합계
- 워크로드 : Pod 재시작 횟수, 준비되지 않은 복제본 수, CPU 스로틀링 비율
- 컨트롤 플레인 : apiserver 요청 지연, etcd 쓰기 지연, 스케줄링 대기 시간
특히 etcd 지연은 2편에서 본 대로 클러스터 전체의 건강을 좌우하므로 반드시 봐야 합니다. 그리고 CPU 스로틀링 비율은 6편의 limits 설정이 적절한지를 알려주는 지표입니다. 사용률은 낮은데 응답이 느리다면 여기를 봐야 합니다.
3.3. 추적
서비스가 여러 개로 나뉘면 "어느 구간에서 느려졌는가" 를 로그만으로 찾기 어렵습니다. 분산 추적은 요청 하나에 ID 를 붙여 서비스를 넘나드는 경로를 이어 봅니다. OpenTelemetry 가 표준으로 자리 잡았고 4편에서 본 서비스 메시를 쓰면 상당 부분을 애플리케이션 수정 없이 얻을 수 있습니다.
4. 클러스터 운영
4.1. 노드 점검
kubectl cordon <node> # 새 Pod 를 받지 않게 표시
kubectl drain <node> # 기존 Pod 를 내보냄
# 작업 수행
kubectl uncordon <node> # 다시 받도록 해제
drain 은 3편의 PodDisruptionBudget 을 존중합니다. PDB 때문에 못 내보내면 기다립니다. 그래서 PDB 를 너무 빡빡하게 걸어두면 drain 이 영원히 끝나지 않습니다. --ignore-daemonsets 는 거의 항상 필요합니다. DaemonSet Pod 는 내보내도 그 노드에 다시 생기기 때문입니다.
4.2. 버전 업그레이드
쿠버네티스는 1년에 세 번 마이너 버전을 냅니다. 각 버전은 약 14개월간 지원됩니다. 꾸준히 올리지 않으면 지원이 끊깁니다. 지켜야 할 규칙들입니다.
- 마이너 버전을 건너뛸 수 없습니다. 1.28 에서 1.30 으로 바로 갈 수 없고 1.29 를 거쳐야 합니다.
- 컨트롤 플레인을 먼저, 노드를 나중에 올립니다.
- kubelet 은 apiserver 보다 높을 수 없습니다. 최대 3개 마이너 버전까지 낮을 수 있습니다.
업그레이드에서 가장 자주 사고가 나는 것은 API 버전 제거입니다. 베타 API 는 예고 후 제거되는데 오래된 매니페스트를 그대로 쓰고 있으면 업그레이드 후 배포가 실패합니다. kubectl convert 나 pluto 같은 도구로 미리 확인해야 합니다.
4.3. 백업
etcd 스냅샷이 곧 클러스터 백업입니다.
etcdctl snapshot save snapshot.db
다만 여기에는 PV 안의 실제 데이터가 들어 있지 않습니다. etcd 에는 "이 PV 가 저 디스크를 가리킨다" 는 메타데이터만 있습니다. 데이터 자체는 5편에서 본 볼륨 스냅샷이나 저장소 쪽 백업으로 따로 챙겨야 합니다. Velero 같은 도구가 오브젝트와 볼륨을 함께 다뤄줍니다.
4.4. 선언형으로 관리하기
kubectl apply 를 손으로 치는 방식은 규모가 커지면 무너집니다. 누가 언제 무엇을 바꿨는지 알 수 없고 클러스터의 실제 상태가 저장소의 매니페스트와 어긋납니다. GitOps 는 Git 저장소를 유일한 진실의 원천으로 삼고 클러스터 안의 에이전트가 저장소와 실제 상태를 계속 비교해 맞추는 방식입니다. Argo CD 와 Flux 가 대표적입니다.
여기서 재미있는 것은, 이것이 1편에서 본 컨트롤 루프를 클러스터 바깥으로 한 겹 더 확장한 것이라는 점입니다. 원하는 상태가 Git 에 있고 에이전트가 그것을 향해 계속 수렴시킵니다. 같은 패턴이 한 층 위에서 반복됩니다.
5. 쿠버네티스를 확장하기
5.1. CRD
CustomResourceDefinition 을 등록하면 제가 정의한 종류의 오브젝트를 쿠버네티스에 추가할 수 있습니다. 등록하고 나면 kubectl get databases 처럼 기본 리소스와 똑같이 다룰 수 있습니다.
apiserver 가 저장과 검증을 해주고 RBAC 도 그대로 적용됩니다. 쿠버네티스 API 를 제 도메인의 언어로 확장하는 장치입니다.
5.2. 오퍼레이터
CRD 만 등록하면 데이터는 저장되지만 아무 일도 일어나지 않습니다. 그 오브젝트를 보고 실제로 조치하는 컨트롤러가 있어야 합니다. CRD 와 그 전용 컨트롤러를 묶은 것이 오퍼레이터(Operator) 입니다.
오퍼레이터 패턴의 핵심은 운영 지식을 코드로 옮기는 것입니다. 예를 들어 데이터베이스 오퍼레이터라면, "리더가 죽으면 팔로워 중 하나를 승격시킨다", "백업은 매일 새벽에 돌린다", "복제본을 늘리면 초기 동기화를 수행한다" 같은 절차를 컨트롤러 안에 담습니다. 사람이 밤중에 일어나 하던 일을 컨트롤 루프가 대신하는 것입니다.
동작 방식은 2편에서 본 내장 컨트롤러와 완전히 같습니다. List-Watch 로 변화를 보고, 워크큐에 넣고, 현재 상태와 원하는 상태를 비교해 조치합니다. 기본 컨트롤러와 오퍼레이터가 구조적으로 다르지 않다는 것이, 이 시스템의 일관성을 잘 보여주는 부분이라고 느꼈습니다.
5.3. Helm
여러 매니페스트를 묶어 패키지로 다루는 도구입니다. 템플릿에 값을 채워 넣는 방식이라, 같은 차트를 환경별로 다른 값으로 설치할 수 있습니다. 주의할 점은 Helm 이 상태를 따로 들고 있다는 것입니다. 릴리스 정보를 Secret 으로 저장하는데 누군가 kubectl edit 으로 직접 고치면 Helm 이 아는 상태와 실제가 어긋납니다. 이런 이유로 GitOps 와 함께 쓰는 구성이 늘고 있습니다.
6. 시리즈를 마치며 정리하는 것들
여덟 편을 관통한 것들을 모아두겠습니다. 면접에서 "쿠버네티스를 한 문장으로 설명해보세요" 같은 질문에 답할 때 쓸 수 있는 뼈대이기도 합니다.
6.1. 하나의 원칙
원하는 상태를 선언하면 컨트롤 루프가 계속 그쪽으로 수렴시킵니다.
Deployment 의 롤링 업데이트도, 오토스케일러도, 오퍼레이터도, GitOps 도 전부 이 루프입니다. 다른 것은 무엇을 원하는 상태로 보느냐뿐이었습니다.
6.2. 반복되는 설계 태도
- 직접 구현하지 않고 인터페이스를 정의한다 : 네트워크는 CNI, 스토리지는 CSI, 런타임은 CRI, 사용자 관리는 외부 인증에 맡겼습니다. 그 덕에 환경에 맞게 고를 수 있고 동시에 "구현체가 지원하지 않으면 조용히 동작하지 않는" 함정도 생겼습니다.
- 판단하는 쪽과 실행하는 쪽을 가른다 : 스케줄러는 결정만 하고 kubelet 이 실행합니다. kube-proxy 는 규칙만 넣고 커널이 처리합니다.
- 느슨하게 묶는다 : 라벨과 셀렉터로 연결하고 이름으로 직접 참조하지 않습니다.
- 레벨 트리거로 판단한다 : 이벤트를 놓쳐도 결국 맞춰집니다.
6.3. 자주 틀리는 것들
여덟 편에서 나온 함정들을 모았습니다.
- readinessProbe 없이 무중단 배포를 기대하는 것 (3편)
- liveness 에 의존 서비스를 넣어 장애를 확산시키는 것 (8편)
- RWO 를 "Pod 하나" 로 오해하는 것 : 노드 기준입니다 (5편)
volumeBindingMode: Immediate로 영역이 어긋나는 것 (5편)- 스케줄러가 실제 사용량을 본다고 생각하는 것 : requests 만 봅니다 (6편)
- taint 만 걸고 전용 노드가 되었다고 생각하는 것 : affinity 가 필요합니다 (6편)
- Secret 의 Base64 를 암호화로 아는 것 (7편)
- NetworkPolicy 를 만들었는데 CNI 가 지원하지 않는 것 (4편)
- gRPC 가 kube-proxy 로 분산될 것이라 기대하는 것 (4편)
- CronJob 의 기본
concurrencyPolicy: Allow를 모르고 지나가는 것 (3편)
7. 조금 더 들어가보기 : 로그와 지표는 어디에서 오는가
운영에서 쓰는 도구들이 실제로 무엇을 하고 있는지까지 보겠습니다. 로그가 안 보이거나 지표가 이상할 때 어디를 봐야 할지가 이 층에서 정해집니다.
7.1. 로그는 어디에 어떻게 쓰이는가
컨테이너가 표준 출력으로 내보낸 것은 컨테이너 런타임이 노드의 파일로 씁니다. kubelet 이 그 파일을 읽어 돌려주는 것이 kubectl logs 입니다. 여기서 몇 가지가 따라옵니다.
- 로테이션이 걸려 있습니다. 파일 크기와 보관 개수에 한도가 있어, 로그를 많이 내보내면 오래된 것이 조용히 사라집니다. 문제를 파고들다 정작 필요한 구간이 이미 지워진 경우가 생깁니다.
- 노드 디스크를 씁니다. 로그가 많으면 노드가 디스크 압박 상태가 되고 앞서 본 대로 kubelet 이 Pod 를 쫓아냅니다. 로그 때문에 서비스가 내려가는 경로입니다.
- Pod 가 사라지면 로그도 사라집니다. 그래서 수집기로 밖으로 내보내는 것이 필수입니다.
--previous 로 이전 컨테이너의 로그를 볼 수 있는 것도 파일이 하나 더 남아 있기 때문입니다. 두 번 이상 재시작했다면 그 이전 것은 없습니다.
7.2. 프로브도 비용이다
프로브는 kubelet 이 주기적으로 실행합니다. 여기서 방식에 따라 비용이 크게 다릅니다.
- httpGet 은 kubelet 이 직접 HTTP 요청을 보냅니다. 가볍습니다.
- exec 는 컨테이너 안에서 프로세스를 새로 띄웁니다. 네임스페이스에 들어가 명령을 실행하고 결과를 받아옵니다.
exec 프로브를 1초 간격으로 걸어두고 Pod 가 노드당 100개라면, 노드에서 초당 100번 프로세스가 생겼다 사라집니다. 이것만으로 노드 CPU 가 의미 있게 올라갑니다. 노드가 바빠지면 프로브 실행 자체가 밀리고 밀린 프로브가 실패로 판정되어 멀쩡한 컨테이너가 재시작되기도 합니다. 부하가 장애로 번지는 또 하나의 경로입니다.
7.3. 지표 두 계층이 다른 이유
앞서 metrics-server 와 Prometheus 를 구분했는데 이유가 있습니다. kubelet 은 cgroup 파일에서 자원 사용량을 읽어 노출합니다. metrics-server 는 그것을 모아 현재 값만 제공하고 저장하지 않습니다. 오토스케일러가 "지금 얼마나 쓰고 있나" 만 알면 되기 때문입니다.
Prometheus 는 그것을 주기적으로 가져와 시계열로 저장합니다. 그래서 "어제 이 시각에 어땠나" 를 물을 수 있습니다. 둘은 용도가 다르므로 하나로 합칠 수 없습니다. 오토스케일링 경로에 무거운 저장소를 끼우면 확장 판단이 느려지고 모니터링에 현재 값만 있으면 아무것도 분석할 수 없습니다.
여기서 주의할 것이 있습니다. Prometheus 는 일정 간격으로 가져오는 방식이라, 그 간격보다 짧게 튀는 것은 보이지 않습니다. 15초 간격이면 5초짜리 순간 부하는 그래프에 흔적조차 남지 않습니다. "지표는 멀쩡한데 사용자는 느리다고 한다" 의 흔한 원인입니다.
7.4. 임시 컨테이너는 네임스페이스에 합류한다
앞서 본 진단용 컨테이너가 동작하는 원리는 이렇습니다. 새 컨테이너를 대상 Pod 의 네임스페이스에 합류시키는 것입니다. 그래서 그 컨테이너에서 대상의 네트워크를 보고, 프로세스를 보고, 파일을 볼 수 있습니다. 셸조차 없는 이미지를 진단할 수 있는 이유입니다.
다만 파일 시스템은 기본적으로 공유되지 않습니다. 대상 컨테이너의 파일을 보려면 프로세스 네임스페이스를 공유하도록 지정해야 합니다.
7.5. 업그레이드가 왜 그렇게 조심스러운가
앞서 버전 규칙을 정리했는데 그 배경은 컴포넌트마다 다른 버전이 동시에 돌기 때문입니다. 컨트롤 플레인을 먼저 올리고 노드를 나중에 올리는 동안, 클러스터에는 여러 버전의 kubelet 이 섞여 있습니다. 이 상태에서도 동작해야 하므로 버전 차이에 제한을 둡니다.
API 버전 제거가 사고로 이어지는 것도 같은 맥락입니다. apiserver 를 올리는 순간 없어진 API 로 보낸 요청이 거부되는데 그것을 쓰던 컨트롤러나 배포 파이프라인은 아직 옛 버전입니다. 업그레이드 전에 클러스터에서 실제로 쓰이고 있는 API 버전을 확인하는 것이 절차에 반드시 들어가야 하는 이유입니다.
7.6. 대규모에서 운영이 달라지는 지점
- 로그 양 : 노드 수 × Pod 수 × 로그 빈도입니다. 수집기가 감당하지 못하면 노드 디스크에 쌓이고 그것이 축출로 이어집니다.
- 지표 카디널리티 : Prometheus 에서 레이블 조합 수가 시계열 수를 정합니다. Pod 이름처럼 계속 바뀌는 값을 레이블에 넣으면 시계열이 폭증해 Prometheus 가 먼저 죽습니다.
- 드레인 시간 : 노드를 하나씩 비우며 업그레이드하는데 PodDisruptionBudget 이 빡빡하면 한 노드에 수십 분이 걸립니다. 노드가 200대면 업그레이드가 며칠짜리 작업이 됩니다.
- 백업 크기 : etcd 스냅샷이 커지면 복구 시간도 길어집니다.
특히 두 번째는 모니터링이 모니터링 대상보다 먼저 죽는 상황을 만듭니다. 관측 도구도 결국 하나의 시스템이고 같은 한계를 갖는다는 것을 잊기 쉽습니다.
결론
여덟 편에 걸쳐 쿠버네티스를 정리하면서 처음에 가졌던 인상이 크게 바뀌었습니다. 시작할 때는 오브젝트 종류가 수십 개이고 설정 항목이 수백 개인, 외워야 할 것이 많은 도구로 보였습니다. 그런데 다 보고 나니 하나의 아이디어를 집요하게 밀어붙인 시스템이었습니다. 원하는 상태를 적으면 루프가 맞춘다는 것 하나로, 배포도 확장도 복구도 확장 기능도 전부 설명이 되었습니다.
두 번째로 남는 것은 쿠버네티스가 대단히 많은 것을 하지 않기로 결정했다는 점입니다. 네트워크도, 저장소도, 컨테이너 실행도, 사용자 관리도 직접 만들지 않았습니다. 규격만 정해두고 위임했습니다. 그래서 어떤 환경에도 맞출 수 있게 되었지만 동시에 "쿠버네티스만 알면 되는 것이 아니다" 라는 결과도 따라왔습니다. NetworkPolicy 가 동작하지 않는 이유를 알려면 CNI 를 알아야 하고 볼륨이 안 붙는 이유를 알려면 CSI 와 클라우드 디스크를 알아야 했습니다.
세 번째는 운영에서 사고가 나는 곳이 대체로 기능의 경계라는 점이었습니다. 엔드포인트 제거와 프로세스 종료 사이, 볼륨 생성과 Pod 배치 사이, HPA 와 Cluster Autoscaler 사이처럼 두 가지가 서로 다른 속도로 움직이는 지점에서 문제가 생겼습니다. 각 기능을 따로 이해하는 것만으로는 부족하고 그것들이 만나는 자리를 봐야 한다는 것을 배웠습니다.
마지막으로, 하둡 시리즈를 먼저 공부해둔 것이 큰 도움이 되었습니다. 분산 합의, 스플릿 브레인, 자원 스케줄링, 데이터 지역성 같은 개념이 이름만 바꿔 계속 나왔습니다. etcd 의 Raft 는 ZooKeeper 와 같은 문제를 풀고 있었고 Multi-Attach 오류를 대하는 태도는 HDFS 의 펜싱과 같은 판단이었습니다. 결국 분산 시스템이 마주하는 문제는 정해져 있고 시대마다 다른 도구가 그것을 다시 푸는 것이라는 생각이 들었습니다.
공부 참고 자료
📑 1. Kubernetes 공식 문서 - Configure Liveness, Readiness and Startup Probes
📑 2. Kubernetes 공식 문서 - Troubleshooting Applications
📑 3. Kubernetes 공식 문서 - Ephemeral Containers
📑 4. Kubernetes 공식 문서 - Logging Architecture
📑 5. Kubernetes 공식 문서 - Safely Drain a Node
📑 6. Kubernetes 공식 문서 - Version Skew Policy
📑 7. Kubernetes 공식 문서 - Custom Resources