kubectl과 오브젝트 관리
- 7.1 명령형 vs 선언형
- 7.2 kubectl explain
- 7.3 kubectl apply 내부 동작
Kubernetes 오브젝트를 만들고 관리하는 방법에는 명령형과 선언형, 두 가지 접근이 있어. 어떤 상황에서 어떤 걸 쓰는 게 맞는지, 그리고 kubectl apply가 내부적으로 어떻게 변경을 감지하는지까지 다뤄.
인프라를 관리하는 접근 방식에는 두 가지가 있어. **명령형(Imperative)**은 택시 기사에게 "좌회전해, 우회전해, 직진해" 같이 단계별로 지시하는 거고, **선언적(Declarative)**은 Uber에서 "톰의 집까지 가줘"라고 최종 목적지만 말하는 거야. 어떻게 할지가 아니라 무엇을 할지를 명시하는 게 선언적 접근이지.
Kubernetes에서 명령형 방식은 이런 명령들을 직접 사용하는 거야:
kubectl run nginx --image=nginx
kubectl create deployment nginx --image=nginx
kubectl expose deployment nginx --port=80
kubectl edit deployment nginx
kubectl scale deployment nginx --replicas=5
kubectl set image deployment nginx nginx=nginx:1.19
오브젝트 구성 파일을 사용하는 것도 명령형에 해당해:
kubectl create -f nginx.yaml
kubectl replace -f nginx.yaml
kubectl delete -f nginx.yaml
이런 방식에서는 Kubernetes에게 생성해, 바꿔, 삭제해 같이 "어떻게" 할지를 직접 지시하니까.
명령형 명령(run, create, expose 등)은 YAML 파일 없이 빠르게 오브젝트를 만들 수 있어서 편하고 시험에서도 유용해. 하지만 기능이 제한적이고, 한 번 실행하면 기록이 남지 않아서 다른 사람이 어떻게 생성됐는지 추적하기 어려워.
오브젝트 구성 파일을 쓰면 YAML로 정확히 기록하고, Git에 저장하고, 변경 검토 프로세스를 거칠 수 있어. 근데 kubectl edit으로 변경하면 로컬 정의 파일에는 반영이 안 돼서 나중에 다른 사람이 로컬 파일로 다시 적용하면 edit으로 한 변경이 날아갈 수 있어. 더 나은 방법은 로컬 파일을 먼저 수정하고 kubectl replace -f로 업데이트하는 거야. 이러면 변경 이력이 남으니까. 오브젝트를 완전히 삭제하고 다시 만들고 싶으면 kubectl replace --force -f를 쓸 수 있어.
명령형의 문제는 관리자가 항상 현재 상태를 파악하고 있어야 한다는 거야. 이미 존재하는 오브젝트에 create를 하면 에러가 나고, 존재하지 않는 오브젝트에 replace를 해도 에러가 나.
선언적 접근은 kubectl apply를 쓰는 거야:
kubectl apply -f nginx.yaml
kubectl apply -f /path/to/config-files/
apply 명령은 오브젝트가 없으면 생성하고, 있으면 변경 사항만 업데이트해. 에러가 안 나. 여러 구성 파일이 있는 디렉터리를 통째로 지정할 수도 있어. 앞으로 변경이 필요하면 로컬 파일을 수정하고 다시 kubectl apply를 실행하면 돼. 새 오브젝트 파일을 추가하든, 기존 파일의 이미지를 바꾸든, 나머지는 apply 명령이 알아서 처리해.
시험 관점에서 보면, 파드나 배포를 빠르게 만들어야 할 때는 명령형 명령이 시간을 절약해줘. 기존 오브젝트를 수정할 때는 kubectl edit이 가장 빠를 수 있어. 여러 컨테이너나 환경 변수, 초기화 컨테이너 같은 복잡한 요구 사항이 있으면 구성 파일을 만들고 kubectl apply를 쓰는 게 나아. 실수를 발견하면 파일을 수정하고 다시 적용하면 되니까.
그런데 YAML 파일을 작성하다 보면 어떤 필드를 쓸 수 있는지 모를 때가 있어. 터미널을 떠나지 않고도 Kubernetes 리소스와 필드에 대해 알아낼 수 있는 방법이 있어.
모든 리소스를 나열하려면:
kubectl api-resources
이 명령이 특히 유용한 경우가 있어. 리소스 이름이나 약칭을 잊어버렸을 때, API의 기본 버전(v1 등)이 뭔지 모를 때, 정의 파일에서 사용할 리소스 이름의 정확한 대소문자가 확실하지 않을 때 활용할 수 있지.
리소스의 필드와 타입에 대한 자세한 정보가 필요하면 kubectl explain을 써:
kubectl explain pod
이러면 파드의 최상위 필드(apiVersion, kind, metadata, spec, status)와 각 필드의 타입(문자열인지, 오브젝트인지 등)을 볼 수 있어.
더 깊이 들어가려면 필드를 점(.)으로 연결해서 추가하면 돼:
kubectl explain pod.spec
이러면 spec 아래의 하위 필드들이 나와. 근데 이것도 바로 아래 단계의 속성만 보여주지, 전체 목록은 아니야.
모든 필드를 YAML 파일에 넣는 것처럼 한꺼번에 보려면 --recursive 플래그를 쓰면 돼:
kubectl explain pod --recursive
이러면 해당 리소스에서 사용할 수 있는 전체 필드 목록이 쭉 출력돼. 여기서 필요한 필드를 찾아서 YAML 파일을 만드는 것도 매우 쉬워져.
마지막으로 kubectl apply가 내부적으로 어떻게 작동하는지 알아보자.
apply 명령은 변경 사항을 결정할 때 세 가지 정보를 비교해: 로컬 구성 파일(내가 작성한 YAML), Kubernetes의 라이브 오브젝트 정의(클러스터에 실제로 존재하는 상태), 그리고 마지막으로 적용된 구성(last applied configuration).
처음 kubectl apply를 실행하면, 오브젝트가 없으니까 새로 생성돼. 이때 Kubernetes 클러스터에 라이브 구성이 만들어지는데, 로컬 파일과 비슷하지만 오브젝트 상태를 저장하는 추가 필드가 붙어. 여기까지는 다른 접근 방식과 동일한데, apply 명령은 한 가지를 더 해. 로컬 파일의 YAML을 JSON으로 변환해서 **마지막으로 적용된 구성(last applied configuration)**으로 저장해. 이건 라이브 오브젝트의 어노테이션(kubectl.kubernetes.io/last-applied-configuration)으로 저장돼.
그 다음에 뭔가 변경하면 이 세 가지를 비교해서 뭘 바꿔야 하는지 알아내. 예를 들어 nginx 이미지를 1.19로 업데이트해서 apply를 실행하면, 로컬 파일의 값과 라이브 구성의 값을 비교해서 차이가 있으면 라이브 구성을 새 값으로 업데이트해. 그리고 마지막으로 적용된 JSON도 최신으로 갱신해.
그러면 마지막으로 적용된 구성이 왜 필요한 건지 궁금할 거야. 핵심은 필드가 삭제된 경우를 감지하기 위해서야. 예를 들어 로컬 파일에서 어떤 레이블을 삭제했다고 해보자. apply를 실행하면, 마지막으로 적용된 구성에는 그 레이블이 있는데 로컬 파일에는 없다는 걸 발견해. 이건 "이전에 있었는데 지금 제거됐다"는 뜻이니까, 라이브 구성에서도 해당 필드를 삭제해. 반면에 어떤 필드가 라이브 구성에는 있지만 로컬 파일에도 없고 마지막으로 적용된 구성에도 없으면, 그건 다른 경로(예: 직접 수정)로 추가된 거라서 그대로 유지해.
중요한 건, 이 마지막으로 적용된 구성은 apply 명령을 사용할 때만 저장된다는 거야. kubectl create이나 kubectl replace 명령은 이걸 저장하지 않아. 그래서 Kubernetes 오브젝트를 관리할 때 명령형과 선언형 접근 방식을 혼용하지 않는 게 좋아.
apply 명령을 실행할 때마다, 로컬 파일 / 라이브 오브젝트 구성 / 라이브 오브젝트에 저장된 마지막 적용 구성, 이 세 가지를 비교해서 라이브 구성에 어떤 변경이 필요한지 결정하는 거야.
정리
7장 읽고 기억할 거 세 가지:
- 시험에서는 명령형, 실무에서는 선언형.
kubectl run/create은 빠르고,kubectl apply는 멱등적이고 안전해. 둘을 혼용하지 않는 게 중요 kubectl explain은 오프라인 문서.kubectl explain pod.spec --recursive로 YAML 작성에 필요한 모든 필드를 터미널에서 바로 확인할 수 있어- apply는 세 가지를 비교해. 로컬 파일 / 라이브 오브젝트 / last applied config. 특히 last applied config은 필드 삭제를 감지하기 위해 존재하고, apply를 쓸 때만 저장돼