Admission Controller, 모니터링과 로깅
- 11.1 Admission Controller
- 11.2 Validating과 Mutating Admission Controller
- 11.3 클러스터 모니터링
- 11.4 애플리케이션 로그 관리
인증과 인가를 통과한 요청을 한 번 더 걸러내는 Admission Controller부터, 커스텀 웹훅으로 나만의 정책을 만드는 법, 클러스터와 파드의 리소스 사용량을 모니터링하는 법, 그리고 애플리케이션 로그를 확인하는 기본 명령까지 한꺼번에 다뤄.
어드미션 컨트롤러는 인증(Authentication)과 인가(Authorization) 다음에 오는 세 번째 관문이야. kubectl로 명령을 보내면 요청이 API 서버에 도달하고, 먼저 인증을 거쳐서 사용자가 유효한지 확인하고, 그 다음 인가를 거쳐서 RBAC로 해당 작업을 수행할 권한이 있는지 확인해. 그 다음이 어드미션 컨트롤러야.
RBAC만으로는 한계가 있거든. RBAC는 "누가 어떤 API 작업을 할 수 있는가"만 판단해. 파드를 만들 수 있느냐 없느냐, 서비스를 삭제할 수 있느냐 없느냐 이 수준이야. 특정 리소스 이름이나 네임스페이스로 제한할 수도 있지만, 그게 한계야. 근데 이런 건 못 해. 파드 생성 요청이 들어올 때 이미지 이름을 보고 공용 Docker Hub 레지스트리의 이미지를 막는다든지, latest 태그 사용을 금지한다든지, 컨테이너가 루트 사용자로 실행되는 걸 차단한다든지, 메타데이터에 반드시 라벨을 포함하도록 강제한다든지. 이런 세밀한 정책 제어가 어드미션 컨트롤러의 역할이야.
어드미션 컨트롤러는 요청을 검증해서 거부할 수 있을 뿐만 아니라, 요청 자체를 변경하거나 백엔드에서 추가 작업을 수행할 수도 있어. 쿠버네티스에 미리 내장된 여러 어드미션 컨트롤러가 있는데, **AlwaysPullImages**는 파드가 생성될 때마다 이미지를 항상 Pull하게 해. **DefaultStorageClass**는 PVC 생성 요청에 스토리지 클래스가 지정 안 되어 있으면 기본 스토리지 클래스를 자동으로 추가해줘. **EventRateLimit**은 API 서버 요청 폭주를 방지하기 위해 요청 속도를 제한하고.
NamespaceLifecycle 어드미션 컨트롤러를 예로 들어보자. 존재하지 않는 네임스페이스에 파드를 만들려고 하면 요청이 거부돼. kubectl run nginx --image=nginx -n blue를 실행했는데 blue라는 네임스페이스가 없으면, 인증과 인가를 통과한 다음 이 어드미션 컨트롤러가 네임스페이스를 확인하고 거부하는 거야. 이 컨트롤러는 또한 kube-system이나 kube-public 같은 기본 네임스페이스가 삭제되는 것도 막아줘.
기본적으로 활성화된 어드미션 컨트롤러 목록을 보려면, kubeadm 환경에서는 kube-apiserver 파드 안에서 kube-apiserver -h | grep enable-admission-plugins를 실행하면 돼.
kubectl exec -it kube-apiserver-controlplane -n kube-system -- kube-apiserver -h | grep enable-admission-plugins
어드미션 컨트롤러를 추가하려면 kube-apiserver의 --enable-admission-plugins 플래그를 업데이트해. kubeadm 환경이면 /etc/kubernetes/manifests/kube-apiserver.yaml 파일을 수정하면 돼. 비활성화하려면 --disable-admission-plugins 플래그를 써.
어드미션 컨트롤러는 크게 두 가지 역할을 해. 하나는 **검증(Validate)**으로, 요청이 정책에 맞는지 확인하고 안 맞으면 거부하는 거야. 다른 하나는 **변경(Mutate)**으로, 요청 자체를 수정하는 거야. DefaultStorageClass가 PVC에 스토리지 클래스를 자동으로 추가하는 게 변경의 예시지. 네임스페이스 자동 생성처럼 백엔드에서 추가 작업을 수행하는 것도 가능해. 두 가지를 동시에 하는 컨트롤러도 있어.
이 두 가지 역할의 실행 순서가 중요한데, Mutating이 먼저 실행되고 Validating이 나중에 실행돼. 왜냐하면 Mutating이 변경한 내용을 Validating 단계에서 검증할 수 있어야 하니까.
예를 들어 DefaultStorageClass 플러그인은 Mutating 어드미션 컨트롤러야. PVC 생성 요청에 스토리지 클래스가 없으면 기본 스토리지 클래스를 자동으로 추가해서 요청을 수정하거든. 반면에 NamespaceLifecycle은 Validating 어드미션 컨트롤러야. 존재하지 않는 네임스페이스에 대한 요청을 확인하고 거부해.
만약 순서가 반대였다면, NamespaceLifecycle이 먼저 실행돼서 네임스페이스가 없다고 거부하고, NamespaceAutoProvision(네임스페이스 자동 생성) 컨트롤러는 호출도 안 됐을 거야. 어드미션 컨트롤러가 요청을 거부하면 요청이 바로 거부되고 사용자에게 오류 메시지가 표시돼.
이 모든 건 쿠버네티스 소스 코드에 내장된 컨트롤러들이야. 근데 자체적인 변경 및 검증 로직을 가진 나만의 어드미션 컨트롤러를 원한다면 어떻게 할까? 두 가지 특수 어드미션 컨트롤러를 사용할 수 있어. **MutatingAdmissionWebhook**과 **ValidatingAdmissionWebhook**이야. 이 웹훅들이 쿠버네티스 클러스터 내부나 외부에 호스팅된 서버를 가리키도록 구성하면 돼. 요청이 내장 어드미션 컨트롤러를 통과한 후에 이 웹훅에 도달하면, JSON 형식의 AdmissionReview 오브젝트를 전달하면서 웹훅 서버를 호출해. 여기에 요청한 사용자, 수행하려는 작업 유형, 오브젝트 세부 정보 등이 다 들어 있어. 웹훅 서버는 응답으로 allowed: true 또는 false가 포함된 AdmissionReview 오브젝트를 돌려보내.
설정하는 순서는 이래. 먼저 자체 로직이 있는 웹훅 서버를 배포해. 이건 어떤 언어로든 만들 수 있어. 유일한 요구사항은 mutate와 validate API를 받아서 예상되는 JSON 오브젝트로 응답할 수 있어야 한다는 거야. Validating 웹훅이라면 들어오는 요청을 검사해서 허용할지 거부할지 결정하는 로직을 넣고, Mutating 웹훅이라면 JSON Patch 형식으로 오브젝트를 수정하는 응답을 보내. 패치 작업은 add, remove, replace, move, copy, test가 가능하고, JSON 오브젝트 내에서 변경할 경로를 지정해.
시험에서 이런 코드를 직접 작성하라는 문제는 안 나와. 여기서 알아둘 건 웹훅 서버가 요청을 허용하거나 거부하는 로직을 담은 서버이고, 적절한 JSON 응답을 보낼 수 있어야 한다는 거야.
웹훅 서버를 개발했으면 어딘가에 호스팅해야 해. 외부 서버에서 돌리거나, 컨테이너화해서 쿠버네티스 클러스터 안에 디플로이먼트로 배포할 수 있어. 클러스터 안에 배포하면 접근하기 위한 서비스도 필요해.
마지막으로 웹훅 구성 오브젝트를 만들어서 클러스터에 등록해.
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: my-webhook
webhooks:
- name: my-webhook.example.com
clientConfig:
service:
namespace: webhook-demo
name: webhook-service
caBundle: <base64-encoded-CA-cert>
rules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE"]
resources: ["pods"]
Mutating 웹훅이면 kind를 MutatingWebhookConfiguration으로 바꾸면 돼. **clientConfig**에서 웹훅 서버의 위치를 지정하는데, 클러스터 내부 서비스면 service로 네임스페이스와 이름을 주고, 외부 서버면 url로 경로를 줘. API 서버와 웹훅 서버 간 통신은 TLS로 이루어져야 하니까 **caBundle**에 CA 인증서를 base64로 인코딩해서 넣어야 해. 서버 쪽에도 TLS 인증서 쌍을 구성해야 하고. **rules**에서는 어떤 리소스의 어떤 작업에 대해 웹훅을 호출할지 지정해. 이 예제에서는 파드가 CREATE될 때만 호출되게 했어.
이 오브젝트가 생성되면, 파드를 만들 때마다 웹훅 서비스에 호출이 가고, 응답에 따라 요청이 허용되거나 거부돼.
여기서 주제를 바꿔서, 클러스터의 리소스 사용량을 어떻게 모니터링하는지 보자. 쿠버네티스에는 기본 내장 모니터링 솔루션이 없어. 그래서 별도의 도구를 써야 하는데, 대표적으로 Metrics Server라는 게 있거든. 예전에는 Heapster라는 프로젝트가 있었는데 지금은 deprecated 됐고, 그걸 가볍게 만든 게 바로 Metrics Server야.
Metrics Server는 쿠버네티스 클러스터당 하나씩 띄울 수 있고, 각 노드와 파드에서 메트릭을 수집해서 메모리에 저장해. 여기서 중요한 건 "메모리에만" 저장한다는 거야. 디스크에 안 쓰니까 과거 데이터를 볼 수가 없어. 과거 성능 데이터까지 보고 싶으면 Prometheus, Elastic Stack, Datadog, Dynatrace 같은 더 고급 모니터링 솔루션을 써야 해.
그러면 메트릭은 어디서 오는 걸까? 각 노드에서 돌아가는 Kubelet 안에 **cAdvisor(Container Advisor)**라는 하위 컴포넌트가 있거든. 이 cAdvisor가 파드에서 CPU, 메모리 같은 성능 메트릭을 수집해서 Kubelet API를 통해 노출해. 그러면 Metrics Server가 그걸 가져다 쓰는 구조야.
모니터링하고 싶은 항목은 크게 두 가지로 나뉘어. 노드 수준 메트릭이랑 파드 수준 메트릭이야. 노드 수준에서는 클러스터에 노드가 몇 개 있는지, 정상인 노드는 몇 개인지, CPU/메모리/네트워크/디스크 사용률이 얼마인지를 보고, 파드 수준에서는 파드 개수랑 각 파드의 CPU/메모리 소비량을 봐.
Metrics Server를 배포하는 방법은 환경에 따라 달라. 로컬에서 minikube를 쓰고 있으면 이렇게 하면 돼.
minikube addons enable metrics-server
그 외 환경에서는 GitHub 리포지토리에서 배포 파일을 클론한 다음 kubectl create 명령으로 배포해.
kubectl create -f <metrics-server-deployment-files>
이 명령이 파드, 서비스, 역할 등 필요한 리소스를 알아서 만들어줘. 배포하고 나면 데이터를 수집하고 처리하는 데 시간이 좀 걸리니까 잠깐 기다려야 해.
준비가 되면 kubectl top 명령으로 성능 메트릭을 확인할 수 있어.
kubectl top node
이렇게 하면 각 노드의 CPU, 메모리 사용량이 나와. 예를 들어 마스터 노드에서 CPU 8%, 약 166밀리코어가 사용되고 있다는 식으로 보여줘.
kubectl top pod
이건 파드별 성능 메트릭을 보여줘. 어떤 파드가 리소스를 많이 먹고 있는지 한눈에 파악할 수 있지.
정리하면, Metrics Server는 가볍고 빠르게 현재 상태를 파악하는 데 좋고, 히스토리 데이터가 필요하면 Prometheus 같은 걸 별도로 붙여야 해. CKA 시험에서는 kubectl top node랑 kubectl top pod 명령을 잘 쓸 줄 알면 돼.
마지막으로 애플리케이션 로그를 다루는 법을 알아보자. 쿠버네티스에서 애플리케이션 로그를 보는 건 사실 Docker 로그 보는 것과 거의 똑같아. 핵심 명령어는 **kubectl logs**야.
먼저 Docker부터 짚고 넘어가자면, 컨테이너를 포그라운드로 실행하면 로그가 바로 화면에 출력되잖아. 근데 -d 옵션으로 백그라운드(분리 모드)로 실행하면 로그가 안 보여. 그때 docker logs <컨테이너ID> 명령으로 확인할 수 있고, -f 옵션을 붙이면 실시간으로 로그가 쭉 따라와.
쿠버네티스에서도 마찬가지야. 파드를 만들고 나서 이렇게 하면 돼.
kubectl logs <파드이름>
실시간으로 로그를 계속 보고 싶으면 -f 옵션을 붙여.
kubectl logs -f <파드이름>
Docker에서 docker logs -f하는 거랑 완전히 같은 거야. 여기서 중요한 포인트가 하나 있어. 파드 안에 컨테이너가 하나만 있으면 위 명령으로 충분한데, 파드 안에 컨테이너가 여러 개 있으면 얘기가 달라져.
멀티 컨테이너 파드에서는 어떤 컨테이너의 로그를 볼 건지 명시적으로 지정해줘야 해. 안 그러면 쿠버네티스가 "어떤 컨테이너인지 알려달라"고 에러를 뱉거든.
kubectl logs <파드이름> <컨테이너이름>
예를 들어 파드 안에 event-simulator랑 image-processor 두 개의 컨테이너가 있으면, event-simulator의 로그를 보고 싶을 때 컨테이너 이름을 꼭 넣어줘야 해.
kubectl logs -f my-pod event-simulator
이게 쿠버네티스의 기본 로깅 기능이야. CKA 시험에서도 이 정도만 알면 충분해. 참고로 애플리케이션이 로그를 **표준 출력(stdout)**으로 보내야 kubectl logs로 볼 수 있다는 것도 기억해둬. 파일로만 로그를 쓰는 애플리케이션이면 이 명령으로는 안 보이거든.
Prometheus나 EFK Stack 같은 서드파티 로깅 프레임워크를 쿠버네티스에 통합하는 더 고급 설정도 있긴 한데, 그건 기본 범위를 넘어서는 내용이야.
정리
11장 읽고 기억할 거 세 가지:
- Admission Controller는 인증/인가 다음의 세 번째 관문으로, Mutating이 먼저 실행되고 Validating이 나중에 실행돼. 커스텀 정책은
MutatingAdmissionWebhook과ValidatingAdmissionWebhook으로 구현하고, 웹훅 서버를 배포한 뒤 WebhookConfiguration 오브젝트로 등록하면 돼. - Metrics Server는 cAdvisor가 수집한 메트릭을 메모리에만 저장하는 가벼운 모니터링 도구야.
kubectl top node와kubectl top pod로 현재 리소스 사용량을 확인하고, 히스토리가 필요하면 Prometheus 같은 별도 솔루션을 써야 해. - **
kubectl logs**로 파드 로그를 보되, 멀티 컨테이너 파드에서는 컨테이너 이름을 반드시 지정해야 하고, 애플리케이션이 stdout으로 로그를 보내야 확인할 수 있어.