K8s 기초 #7 Namespace와 라벨 — 클러스터 정리법
K8s 기초 시리즈의 마지막 글입니다. 이번 글에서는 지금까지 만든 객체들이 왜 default 네임스페이스에 몰려 있었는지, Namespace가 무엇을 분리해 주는지, 그리고 라벨과 selector가 어떻게 객체를 묶고 찾는지 정리하겠습니다. 마지막으로 이 7편 전체를 짚고, 다음 트랙인 K8s 중급에서 무엇을 다룰지 예고하겠습니다.
이번 시리즈는 K8s 기초 7편입니다.
- #1 쿠버네티스란 — 왜 컨테이너 오케스트레이터가 필요한가
- #2 로컬 환경 — minikube / kind / Docker Desktop k8s
- #3 kubectl과 첫 Pod
- #4 Deployment와 ReplicaSet — 선언형 배포와 롤링 업데이트
- #5 Service — ClusterIP / NodePort / LoadBalancer
- #6 ConfigMap과 Secret — 설정 분리
- #7 Namespace와 라벨 — 클러스터 정리법 ← 이번 글
default 네임스페이스의 한계 #
-A 옵션을 붙이고 클러스터의 모든 객체를 한 번 들여다보면, 우리가 안 만든 것들이 잔뜩 떠 있는 게 보입니다.
kubectl get all -ANAMESPACE NAME READY STATUS RESTARTS AGE
kube-system pod/coredns-5d78c9869d-2xvlw 1/1 Running 0 3d
kube-system pod/etcd-minikube 1/1 Running 0 3d
kube-system pod/kube-apiserver-minikube 1/1 Running 0 3d
kube-system pod/kube-controller-manager-minikube 1/1 Running 0 3d
kube-system pod/kube-proxy-7gbdn 1/1 Running 0 3d
kube-system pod/kube-scheduler-minikube 1/1 Running 0 3d
default pod/web-7f8b6c8f7d-abcde 1/1 Running 0 1hkube-system은 K8s가 자기 자신을 굴리기 위한 컨트롤플레인 컴포넌트들이 모여 있는 네임스페이스입니다. coredns(클러스터 DNS), etcd, apiserver, controller-manager, scheduler, kube-proxy — #1에서 그림으로 본 그 컴포넌트들이 모두 여기에 살아 있습니다. 우리가 #2부터 보고 있었지만 이름은 짚지 않았던 격리 공간입니다.
kubectl get nsNAME STATUS AGE
default Active 3d
kube-node-lease Active 3d
kube-public Active 3d
kube-system Active 3d기본으로 만들어지는 시스템 네임스페이스 넷 — default, kube-system, kube-public, kube-node-lease — 이 항상 떠 있습니다. 한 줄씩 짚어 두면 다음과 같습니다.
default—-n옵션 없이 만든 객체가 들어가는 곳. 시리즈 #1~#6의 모든 객체가 여기에 모였습니다.kube-system— K8s 컨트롤플레인 컴포넌트가 사는 곳. 일반 사용자가 직접 손대지 않습니다.kube-public— 인증 없이도 읽을 수 있는 객체를 위한 곳. 클러스터 정보 공개용으로 거의 쓰지 않습니다.kube-node-lease— 노드 하트비트를 효율적으로 관리하기 위해 1.13부터 분리된 곳. 운영자가 직접 만질 일은 없습니다.
여기까지는 시스템이 알아서 굴리니 큰 문제가 없는데, 한 클러스터에서 dev / staging / prod를 함께 굴리거나, 팀 A,B가 같은 클러스터를 나눠 쓰거나, 어떤 앱은 한 묶음으로 어떤 앱은 다른 묶음으로 갈라 두고 싶을 때부터 default 한 곳에 다 넣는 모양이 깨집니다. 같은 이름의 Service가 환경마다 따로 있어야 하고, 권한도 환경별로 갈라야 하고, 한쪽 환경의 사고가 다른 쪽으로 새지 않아야 하니까요.
Namespace가 풀어 주는 것 #
Namespace는 한 줄로 요약하면 한 클러스터 안의 가상 클러스터입니다. 리눅스의 user 계정이나 git의 branch와 비슷한 결의 분리입니다 — 같은 물리 자원 위에 논리적인 칸을 갈라 두는 도구입니다. 풀어 주는 일은 다음 넷이 핵심입니다.
- 이름 공간 분리 — 같은 이름의 객체를 다른 네임스페이스에 따로 둘 수 있습니다.
web이라는 Service가 dev와 prod 양쪽에 별개로 존재할 수 있고, 서로 충돌하지 않습니다. - RBAC의 단위 — 권한을 네임스페이스 단위로 나눠 줄 수 있습니다. “팀 A는
team-a네임스페이스 안에서만 읽고 쓰기 가능, 그 밖은 못 본다” 같은 정책의 기본 단위입니다. - 리소스 쿼터의 단위 —
ResourceQuota와LimitRange객체로 네임스페이스마다 CPU,메모리,객체 개수의 상한을 둘 수 있습니다. dev가 prod의 자원을 잡아먹지 않도록 막는 도구입니다. - NetworkPolicy의 단위 — 네임스페이스 사이의 트래픽을 차단하거나 허용하는 정책을 적을 수 있습니다. 기본은 모든 네임스페이스가 서로 트래픽이 통하는 상태이고, 이걸 좁히려면 NetworkPolicy를 써야 합니다.
여기서 한 가지를 분명히 짚어 둘 가치가 있습니다 — Namespace 자체는 보안 경계가 아닙니다. 단순히 객체 이름을 갈라 주는 논리 칸일 뿐입니다. 진짜 격리는 위 네 항목 중 뒤의 셋(RBAC, 리소스 쿼터, NetworkPolicy)이 따로 합니다. Namespace만 만들어 두고 RBAC도 NetworkPolicy도 안 적었다면, 권한 있는 사용자는 아무 네임스페이스의 객체나 보고 만질 수 있고 Pod끼리도 서로 통신합니다. 이번 시리즈는 매니페스트 모양까지만 다루고, RBAC / NetworkPolicy / ResourceQuota의 깊이는 K8s 중급에서 묶어 다루겠습니다.
클러스터 스코프 vs 네임스페이스 스코프 #
객체에는 두 갈래가 있습니다. 네임스페이스 스코프(namespaced) 인 객체는 어떤 네임스페이스에 속해야 하고, 클러스터 스코프(cluster-scoped) 인 객체는 네임스페이스 없이 클러스터 전역에 하나로 존재합니다. 우리가 본 객체로 갈라 보면 다음과 같습니다.
| 스코프 | 예시 |
|---|---|
| 네임스페이스 스코프 | Pod, Deployment, ReplicaSet, Service, ConfigMap, Secret, Job, Ingress |
| 클러스터 스코프 | Node, PersistentVolume, Namespace 자체, ClusterRole, StorageClass |
Node가 네임스페이스에 속하지 않는 건 직관적입니다 — 노드는 물리,가상 머신이고 한 클러스터의 자원이지, 어느 환경에 속하지 않으니까요. 객체가 어느 쪽에 속하는지 명령으로 확인할 수도 있습니다.
kubectl api-resources --namespaced=truekubectl api-resources --namespaced=false운영하다가 “이 객체에 -n을 붙여야 하나?“가 헷갈릴 때 한 번 돌려 보면 답이 바로 나옵니다.
Namespace 만들기 #
명령형으로 한 줄에 만들 수 있습니다.
kubectl create namespace devnamespace/dev createdgit에 의도를 남기고 싶으면 매니페스트로 적습니다.
apiVersion: v1
kind: Namespace
metadata:
name: dev
labels:
env: devkubectl apply -f dev-ns.yamlapiVersion은 ConfigMap,Secret과 마찬가지로 코어 그룹의 v1 입니다. Namespace 자체는 클러스터 스코프 객체라서 그 metadata 안에 namespace: 필드를 적지 않습니다(적을 수 없습니다).
객체를 특정 네임스페이스에 넣는 길은 두 가지입니다.
- 매니페스트에 적기 —
metadata.namespace: dev한 줄을 객체의 metadata에 직접 적습니다. - 명령에 옵션으로 —
kubectl apply -f web.yaml -n dev처럼-n으로 지정합니다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
namespace: dev
labels:
app: web
spec:
# ... 이하 [#4](/ko/posts/k8s-basics-4)와 동일둘이 같이 있으면 매니페스트의 값이 우선합니다(명령 옵션은 매니페스트에 namespace가 없을 때만 적용됨). 혼동을 줄이려면 둘 중 한쪽만 쓰는 게 좋습니다. git에 의도를 남기는 관점에서는 매니페스트에 적어 두는 편이 다음 사람한테 친절합니다 — 명령 옵션은 셸 히스토리에만 남고, 다음에 같은 매니페스트를 보면 “이게 어디로 가는 거지?” 가 다시 의문이 되니까요.
NS 별로 둘러보기 #
-n 옵션으로 한 네임스페이스의 객체만 봅니다.
kubectl get pods -n devkubectl get all -n kube-system-A 옵션은 모든 네임스페이스를 한 번에 봅니다.
kubectl get pods -A-n을 매번 적기 번거로워요. 현재 컨텍스트의 기본 네임스페이스를 바꾸면 그 다음부터는 옵션 없이도 그 네임스페이스로 가게 됩니다.
kubectl config set-context --current --namespace=devkubectl config view --minify | grep namespace:이 명령은 ~/.kube/config의 현재 컨텍스트에 기본 네임스페이스를 적어 둡니다. 그 뒤 kubectl get pods만 쳐도 dev의 Pod가 나옵니다.
kubens — 한 줄로 네임스페이스 전환 #
위 명령이 길어서, 운영자들이 거의 다 쓰는 도구가 kubens (kubectx 패키지의 짝꿍)입니다. 한 줄에 네임스페이스를 갈아탈 수 있습니다.
kubens # 현재 네임스페이스 출력 + 후보 목록
kubens dev # dev로 전환
kubens - # 직전 네임스페이스로 돌아가기kubectx가 클러스터(컨텍스트) 전환용이라면 kubens는 네임스페이스 전환용입니다. 둘 다 한 패키지에서 옵니다 — Homebrew, apt, scoop 어디든 kubectx를 깔면 같이 들어옵니다. 매일 K8s를 만지는 사람한테는 거의 필수에 가까운 편의 도구입니다.
NS 안 객체가 어떻게 서로 부르나 #
#5의 Service 절에서 본 흐름을 한 번 더 짚을 차례입니다. 같은 네임스페이스 안에 사는 Pod 끼리는 Service 이름만으로 서로 부를 수 있었습니다.
http://web/api # web이라는 Service를 짧은 이름으로다른 네임스페이스의 Service를 부르려면 이름 뒤에 네임스페이스를 붙입니다.
http://web.prod/api # 짧게
http://web.prod.svc.cluster.local/api # FQDNK8s 클러스터 안의 모든 Service는 <service>.<namespace>.svc.cluster.local이라는 FQDN으로 풀립니다. 짧게 줄여 web.prod만 적어도 클러스터 DNS가 알아서 채워 줍니다. 한 줄로 정리하면 DNS가 네임스페이스 사이의 다리입니다 — 네임스페이스가 객체 이름을 갈라 두는 칸이라면, DNS는 그 칸 너머로 객체를 부를 수 있게 해 주는 통로 역할을 합니다.
라벨 vs 어노테이션 #
여기서 시점을 바꿔, 클러스터 정리의 또 다른 축인 라벨로 넘어갑니다. 라벨은 #4의 selector부터 사실 계속 보고 있었습니다 — Deployment의 spec.selector.matchLabels, Pod 템플릿의 metadata.labels, Service의 spec.selector가 다 라벨로 객체를 묶고 골라내는 메커니즘입니다.
라벨과 자주 헷갈리는 것이 **어노테이션(annotation)**입니다. 둘 다 metadata 아래에 키-값으로 적는 모양은 같지만, 쓰임새가 분명히 갈립니다.
| 라벨 (label) | 어노테이션 (annotation) | |
|---|---|---|
| K8s가 매칭에 사용 | 예 (selector) | 아니오 |
| 길이,내용 | 짧고 의미 있는 키-값 (수십 자) | 임의 — 길어도 OK, JSON,base64 등 |
| 용도 | 객체 분류,선택 | 도구,운영자가 붙이는 메모 |
| 키 예시 | app=web, env=prod, tier=backend | prometheus.io/scrape: "true", kubectl.kubernetes.io/last-applied-configuration |
한 줄 차이는 — 라벨은 검색 키, 어노테이션은 메모지입니다. K8s의 컨트롤러(Deployment, Service, NetworkPolicy 등)가 어떤 객체를 다룰지 고를 때 보는 것이 라벨이고, 외부 도구(Prometheus, Helm, ArgoCD, Ingress 컨트롤러 등)나 운영자가 객체에 메타데이터를 덧붙일 때 쓰는 것이 어노테이션입니다.
키와 값의 제약도 살짝 다릅니다. 라벨은 selector에 쓰이는 만큼 형식이 좁습니다 — 키는 ASCII 영숫자에 -, _, . 정도, 값도 비슷하게 짧은 문자열만 들어갑니다(둘 다 수십 자 안). 어노테이션은 그 제약이 풀려 있어 임의의 텍스트,JSON도 들어갈 수 있습니다. kubectl.kubernetes.io/last-applied-configuration 어노테이션이 통째로 매니페스트 JSON을 들고 있는 게 그 예입니다.
metadata:
name: web
labels:
app: web
env: prod
tier: backend
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
kubernetes.io/change-cause: "bump nginx 1.27 -> 1.28"표준 라벨 컨벤션 — app.kubernetes.io/* #
라벨에 app=web 같은 자체 키를 자유롭게 붙여도 되지만, K8s 커뮤니티가 권장하는 표준 라벨 셋이 있습니다. 키 접두사가 app.kubernetes.io/로 시작하는 여섯 가지입니다.
| 키 | 의미 | 예시 값 |
|---|---|---|
app.kubernetes.io/name | 앱 이름 | nginx, web, kafka |
app.kubernetes.io/instance | 이 배포 인스턴스 식별자 | web-prod, kafka-shop |
app.kubernetes.io/version | 버전 | 1.27, 2.4.1 |
app.kubernetes.io/component | 역할 | frontend, backend, database |
app.kubernetes.io/part-of | 상위 시스템 | shop-platform, analytics |
app.kubernetes.io/managed-by | 관리 도구 | Helm, argocd, kubectl |
이 키들을 쓰는 이유는 운영 도구,대시보드가 표준으로 인식하기 때문입니다. Lens, k9s, Datadog, Helm 같은 도구들이 이 키를 보고 객체를 묶어 보여 줍니다. 자체 키만 쓰는 것보다 호환성이 분명히 좋습니다. 자체 키(예: env, team)를 함께 써도 무방하고, 표준 라벨 셋과 자체 라벨을 같이 적는 게 일반적인 모양입니다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
namespace: prod
labels:
app.kubernetes.io/name: nginx
app.kubernetes.io/instance: web-prod
app.kubernetes.io/version: "1.27"
app.kubernetes.io/component: frontend
app.kubernetes.io/part-of: shop-platform
app.kubernetes.io/managed-by: kubectl
env: prod
spec:
replicas: 3
selector:
matchLabels:
app.kubernetes.io/name: nginx
app.kubernetes.io/instance: web-prod
template:
metadata:
labels:
app.kubernetes.io/name: nginx
app.kubernetes.io/instance: web-prod
app.kubernetes.io/version: "1.27"
app.kubernetes.io/component: frontend
env: prod
spec:
containers:
- name: nginx
image: nginx:1.27
ports:
- containerPort: 80selector의 matchLabels와 Pod 템플릿의 labels에는 표준 라벨 중에서도 변하지 않는 것들만 넣는 편이 안전합니다. version 같은 키를 selector에 넣어 두면 버전을 올릴 때 selector도 같이 바꿔야 해서 #4에서 본 selector immutability 함정에 걸리기 쉽습니다.
라벨로 골라내기 — kubectl -l #
라벨이 붙어 있으면 kubectl의 -l 옵션으로 객체를 골라낼 수 있습니다. selector 문법은 단순한 등호부터 집합 표현까지 몇 가지가 있습니다.
kubectl get pods -l app=web
kubectl get pods -l env=prod,tier=backend # AND쉼표로 이어 붙이면 AND입니다. OR은 다음의 집합 표현으로 적습니다.
kubectl get pods -l 'env in (dev,staging)'
kubectl get pods -l 'env notin (prod)'
kubectl get pods -l 'tier' # tier 라벨이 있는 것
kubectl get pods -l '!debug' # debug 라벨이 없는 것여러 객체 종류에 같이 쓸 수도 있습니다.
kubectl get deploy,svc,cm -l app.kubernetes.io/instance=web-prodkubectl delete pods -l env=dev마지막 명령은 강력합니다 — 매칭되는 모든 Pod를 한 번에 지웁니다. 운영 클러스터에서 라벨을 잘못 넣었다가 의도보다 많은 객체를 지우는 사고가 종종 납니다. 일괄 삭제 전에는 같은 selector로 get을 먼저 돌려 대상이 의도한 그것인지 확인하는 게 안전합니다.
-l 셀렉터의 문법이 중요한 이유는 — 같은 문법이 Service의 spec.selector, Deployment의 spec.selector.matchLabels, NetworkPolicy의 podSelector, ResourceQuota의 scopeSelector 등 K8s 안의 거의 모든 객체 매칭에 그대로 쓰이기 때문입니다. 라벨 한 번 익혀 두면 그 위에 얹히는 객체들의 selector가 자연스럽게 읽힙니다.
가상의 운영 한 컷 — 이걸 다 합치면 #
지금까지의 도구를 한 매니페스트 묶음으로 맞춰 보면, 운영 클러스터의 기본 모양이 드러나요. dev / staging / prod 세 네임스페이스, 그 안에 같은 이름의 Deployment,Service,ConfigMap,Secret이 있고, 라벨로 환경과 버전이 붙어 있습니다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
namespace: prod
labels:
app.kubernetes.io/name: nginx
app.kubernetes.io/instance: web-prod
app.kubernetes.io/version: "1.27"
app.kubernetes.io/component: frontend
app.kubernetes.io/part-of: shop-platform
env: prod
spec:
replicas: 3
selector:
matchLabels:
app.kubernetes.io/name: nginx
app.kubernetes.io/instance: web-prod
template:
metadata:
labels:
app.kubernetes.io/name: nginx
app.kubernetes.io/instance: web-prod
app.kubernetes.io/component: frontend
env: prod
spec:
containers:
- name: nginx
image: nginx:1.27
ports:
- containerPort: 80
envFrom:
- configMapRef:
name: web-config
- secretRef:
name: db-secret
---
apiVersion: v1
kind: Service
metadata:
name: web
namespace: prod
labels:
app.kubernetes.io/name: nginx
app.kubernetes.io/instance: web-prod
env: prod
spec:
selector:
app.kubernetes.io/name: nginx
app.kubernetes.io/instance: web-prod
ports:
- port: 80
targetPort: 80dev와 staging도 거의 같은 매니페스트입니다 — metadata.namespace, instance, version, env 라벨 정도만 환경마다 다르고 나머지는 그대로. 이 모양이 한 클러스터 안에서 환경별로 객체가 깨끗이 갈라지는 운영 클러스터의 기본기입니다.
운영에서 같은 매니페스트를 환경별로 약간씩 바꿔서 적용해야 할 때, 매번 손으로 갈아 두면 실수가 잦습니다. 그래서 등장하는 도구가 Helm(템플릿 + 값 분리)과 Kustomize(베이스 + 오버레이)입니다. 둘 다 이번 시리즈 범위 밖이고, K8s 고급 / 실전 트랙에서 다루겠습니다.
정리,치우기 #
이번 글에서 만든 dev 네임스페이스를 정리하겠습니다.
kubectl delete ns devnamespace "dev" deleted이 한 줄이 무서운 이유는 그 네임스페이스 안의 모든 객체가 같이 사라진다는 점입니다. Deployment, Service, Pod, ConfigMap, Secret, PVC 모두 비동기로 삭제됩니다. 운영 클러스터에서 네임스페이스 삭제는 거의 마지막 카드라고 봐야 합니다 — 한 번 누르면 그 안의 데이터까지 날아갈 수 있고, PV(영속 볼륨)의 reclaimPolicy 설정에 따라서는 디스크 자체도 같이 사라질 수 있습니다.
kubectl config set-context --current --namespace=defaultkubens를 설치해 둔 환경이라면 kubens default로 한 줄입니다.
시리즈 회고 — 7편으로 손에 들어온 것 #
마지막 글이므로 7편을 한 번 정리하겠습니다.
- #1 — 왜 K8s 인가. 컨테이너 한 호스트의 한계, 오케스트레이터가 풀어 주는 것, 컨트롤플레인과 워커의 그림.
- #2 — 로컬 클러스터. minikube / kind / Docker Desktop 셋의 쓰임새와 차이, kubectl 컨텍스트.
- #3 — 첫 Pod. 매니페스트의 척추(
apiVersion / kind / metadata / spec),kubectl apply/get/describe/logs/exec. - #4 — Deployment와 ReplicaSet. 선언형 배포, 롤링 업데이트,
kubectl rollout, selector immutability. - #5 — Service. ClusterIP / NodePort / LoadBalancer의 세 단계, 클러스터 내부 DNS.
- #6 — ConfigMap과 Secret. 12-factor의 “설정은 환경에 둔다”, env / envFrom / volume 세 가지 주입 방식.
- #7 Namespace와 라벨 — 클러스터 정리법 ← 이번 글.
이 정도면 K8s 매니페스트 한 장을 처음 보고도 무엇을 의미하는지 읽고 쓸 수 있는 수준입니다. 회사 클러스터의 매니페스트 디렉터리를 열어도 객체 종류와 그 안의 필드 이름이 낯설지 않을 것입니다. 그 위에 얹힐 더 깊은 주제들이 다음 트랙입니다.
다음 — K8s 중급 #
이번 시리즈에서 일부러 미룬 주제들이 K8s 중급 7편의 줄거리입니다.
| 주제 | 설명 |
|---|---|
| StatefulSet / DaemonSet / Job / CronJob | Deployment가 아닌 다른 컨트롤러들. 데이터베이스, 노드 에이전트, 일회성 배치, 스케줄링된 배치. |
| PV / PVC / StorageClass | 영속 데이터. Pod가 죽어도 살아남는 디스크의 모델. |
| Ingress + Ingress Controller | 외부 진입점을 한 곳에 모으기. nginx / Traefik / GKE Ingress 같은 컨트롤러. |
| resources.requests / limits | Pod의 CPU,메모리 요청,상한. 스케줄링과 OOM의 기준. |
| Health check | liveness / readiness / startup probe. K8s가 컨테이너의 살아 있음을 어떻게 판정하나. |
| HPA / VPA / Cluster Autoscaler | 부하에 맞춰 Pod 수, Pod의 자원, 노드 수까지 자동으로 조정. |
| RBAC / NetworkPolicy / ResourceQuota | 이번 글에서 짧게 짚은 보안,자원 정책의 깊이. |
이 일곱 주제를 다루고 나면, 회사 클러스터의 매니페스트 디렉터리에 본인 매니페스트를 쓸 수 있는 단계까지 한 발 더 들어가게 됩니다. 그 다음 단계인 K8s 고급 / 실전에서는 Helm, Kustomize, GitOps(ArgoCD / Flux), 옵저버빌리티(Prometheus / Grafana / Loki), 클러스터 운영(업그레이드, 백업, 멀티 클러스터) 같은 주제들을 본격적으로 다루겠습니다.
마무리 #
기초 시리즈 7편으로 K8s의 기본기를 정리했습니다. 단일 호스트에서 출발해 클러스터를 띄우고, 첫 매니페스트를 쓰고, 여러 Pod를 안정적으로 유지하고, 외부에 노출하고, 설정과 비밀값을 분리하는 과정까지 따라왔습니다. 매니페스트 한 장이 실제로 클러스터 안에서 어떤 객체로 구성되는지, 그리고 그 객체들이 서로 어떻게 연결되는지를 이해할 수 있다면 이 시리즈의 목적은 충분히 달성된 것입니다. 다음 트랙인 K8s 중급에서는 이 기초 위에 더 깊은 주제를 차례대로 다루겠습니다.