개발팀이 테스트 리소스를 정리하려다 운영 리소스까지 같은 클러스터에서 건드릴 뻔했습니다. 여러 팀과 환경이 한 클러스터를 공유하면 논리적 경계가 없을 때 작은 명령 하나가 큰 장애가 됩니다. Namespace는 권한, 비용, 리소스를 나누는 첫 번째 안전 울타리입니다.
Namespace — 환경 분리와 자원 제한
단일 Kubernetes 클러스터에 개발팀, 스테이징, 운영 환경을 모두 올리고 싶지만 서로 영향을 주면 안 됩니다. 개발팀이 실수로 배포한 리소스가 운영 파드와 자원을 경쟁하거나, 개발팀의 kubectl delete all이 운영 리소스를 날리는 사고가 실제로 발생합니다. 클러스터를 환경마다 따로 구축하면 비용과 관리 부담이 급증합니다. Namespace는 하나의 클러스터 안에서 논리적 구획을 만들어 이 문제를 해결합니다. RBAC으로 팀별 접근 권한을 제어하고, ResourceQuota로 Namespace별 자원 상한을 설정하면, 개발팀이 운영 Namespace에 접근하지 못하고 리소스를 독점하지도 못합니다. 대부분의 운영 조직이 이 방식으로 멀티팀 클러스터를 운영합니다.
Namespace로 팀과 환경을 논리적으로 분리하고, 자원 제한으로 안정적인 멀티팀 클러스터를 운영하는 방법을 실습합니다.
- 1Namespace 개념과 기본 제공 Namespace 설명
- 2개발·스테이징·운영 환경 분리 패턴
- 3ResourceQuota — Namespace 전체 자원 상한 설정
- 4LimitRange — 컨테이너별 기본값과 최댓값 설정
- 5다른 Namespace 리소스 접근 — DNS 네이밍 규칙
- 6TroubleCase: 다른 Namespace 리소스 접근 실패
kubectl 기본 사용법(kubectl-basics)을 익혔다면 바로 시작할 수 있습니다. 별도 환경 설치 없이 기존 클러스터에서 진행합니다.
kubectl cluster-infokubectl get namespaceskubectl config current-contextkubectl config view --minify | grep namespaceNamespace — 클러스터 안의 클러스터
하나의 클러스터에서 여러 팀이 작업하다 보면 개발 환경의 리소스가 운영 환경 파드와 같은 네임스페이스에 뒤섞입니다. kubectl delete all을 개발 환경 정리용으로 실행했는데 운영 파드까지 삭제되는 사고가 발생합니다. Kubernetes는 기본적으로 네임스페이스를 지정하지 않으면 모든 리소스가 default에 쌓입니다. Namespace는 하나의 클러스터 안에서 논리적 구획을 만들어 팀과 환경을 분리하고, RBAC으로 각 구획의 접근 권한을 제어합니다. 이 CB에서는 Kubernetes 기본 제공 Namespace의 역할과, 사용자 정의 Namespace 생성 및 관리 방법을 다룹니다.

기본 제공 Namespace
kubectl get namespaces
# NAME STATUS AGE
# default Active 30d ← namespace 미지정 시 사용되는 기본 공간
# kube-system Active 30d ← Kubernetes 시스템 컴포넌트 (CoreDNS, kube-proxy 등)
# kube-public Active 30d ← 인증 없이 읽기 가능 (클러스터 정보 공개용)
# kube-node-lease Active 30d ← 노드 heartbeat용 Lease 오브젝트 저장
- NAME—조회 대상 리소스 이름이 예상한 대상과 일치하는지 확인합니다.
- STATUS/READY—Running, Ready, Available처럼 정상 상태를 나타내는 필드가 있는지 봅니다.
- RESTARTS/EVENTS—재시작 횟수나 Warning 이벤트가 증가하지 않는지 확인합니다.
kube-system은 절대 건드리지 않는 것이 원칙입니다. CoreDNS나 kube-proxy 같은 핵심 컴포넌트가 여기 있습니다.
Namespace 생성
# 명령어로 생성
kubectl create namespace development
kubectl create namespace staging
kubectl create namespace production
# YAML로 생성 (팀 정보, 환경 정보를 label로 관리)
# namespaces.yaml
---
apiVersion: v1
kind: Namespace
metadata:
name: development
labels:
environment: dev
team: backend
---
apiVersion: v1
kind: Namespace
metadata:
name: staging
labels:
environment: staging
team: backend
---
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
environment: prod
team: backend
kubectl apply -f namespaces.yaml
kubectl get namespaces --show-labels
# NAME STATUS LABELS
# development Active environment=dev,team=backend
# staging Active environment=staging,team=backend
# production Active environment=prod,team=backend
Namespace 범위 오브젝트 vs 클러스터 범위 오브젝트
| Namespace 범위 (격리됨) | 클러스터 범위 (공유) |
|---|---|
| Pod, Deployment, Service | Node, PersistentVolume |
| ConfigMap, Secret | StorageClass |
| PersistentVolumeClaim | ClusterRole, ClusterRoleBinding |
| ServiceAccount | Namespace 자체 |
# 어떤 리소스가 Namespace 범위인지 확인
kubectl api-resources --namespaced=true | head -10
kubectl api-resources --namespaced=false | head -10
환경 분리 실습 — 개발·스테이징·운영
같은 이름의 Deployment를 개발 환경과 운영 환경에 따로 운영해야 하는데 단일 클러스터라면 이름이 충돌합니다. 개발팀이 실수로 운영 Deployment를 롤백하거나, 스테이징 테스트가 운영 서비스에 영향을 주는 사고가 발생합니다. 환경마다 별도 클러스터를 쓰면 비용과 관리 부담이 세 배가 됩니다. Namespace를 환경별로 분리하면 같은 이름의 리소스를 독립적으로 관리하고, RBAC으로 각 환경의 접근 권한을 팀별로 제한할 수 있습니다. 이 CB에서는 development, staging, production 세 환경을 Namespace로 분리하고 같은 앱을 각각 독립적으로 배포하는 실습을 다룹니다.

동일 앱을 다른 Namespace에 각각 배포
# dev-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
namespace: development # 개발 환경
spec:
replicas: 1 # 개발: 1개 레플리카
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: app
image: hashicorp/http-echo
args: ["-text=Hello from DEVELOPMENT"]
ports:
- containerPort: 5678
---
apiVersion: v1
kind: Service
metadata:
name: my-app-svc
namespace: development
spec:
selector:
app: my-app
ports:
- port: 80
targetPort: 5678
# prod-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
namespace: production # 운영 환경 — 같은 이름이지만 다른 Namespace
spec:
replicas: 3 # 운영: 3개 레플리카
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: app
image: hashicorp/http-echo
args: ["-text=Hello from PRODUCTION"]
ports:
- containerPort: 5678
---
apiVersion: v1
kind: Service
metadata:
name: my-app-svc
namespace: production
spec:
selector:
app: my-app
ports:
- port: 80
targetPort: 5678
kubectl apply -f dev-deployment.yaml
kubectl apply -f prod-deployment.yaml
# 각 Namespace에서 파드 수와 내용이 다름
kubectl get deployments -n development
# NAME READY UP-TO-DATE AVAILABLE
# my-app 1/1 1 1
kubectl get deployments -n production
# NAME READY UP-TO-DATE AVAILABLE
# my-app 3/3 3 3
# 전체 Namespace 조회
kubectl get pods -A | grep my-app
기본 Namespace 변경
# 매번 -n 플래그 입력이 번거로울 때
kubectl config set-context --current --namespace=development
# 이후 명령어는 development Namespace가 기본
kubectl get pods # development 파드만 표시
kubectl get pods -n production # production 파드 확인 시 -n 명시
# 원래대로 복원
kubectl config set-context --current --namespace=default
ResourceQuota — Namespace 전체 자원 상한
개발팀이 테스트 목적으로 수십 개의 파드를 배포하면서 클러스터 전체 CPU와 메모리를 잠식합니다. 운영 파드가 자원을 확보하지 못해 Pending 상태가 되고 배포가 지연됩니다. 팀에게 클러스터 접근 권한을 주되 전체 자원을 무제한으로 쓸 수는 없습니다. ResourceQuota는 Namespace 단위로 자원 예산을 설정하는 메커니즘입니다. Namespace 안에서 생성할 수 있는 리소스의 총합을 제한합니다. 팀별로 자원 예산을 배정하는 개념입니다.
ResourceQuota 설정
# resource-quota.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
name: dev-quota
namespace: development
spec:
hard:
# 컨테이너 수준 CPU/메모리 합계 제한
requests.cpu: "4" # 이 Namespace 전체 CPU request 합계 ≤ 4코어
requests.memory: 8Gi # 전체 메모리 request 합계 ≤ 8Gi
limits.cpu: "8" # 전체 CPU limit 합계 ≤ 8코어
limits.memory: 16Gi # 전체 메모리 limit 합계 ≤ 16Gi
# 오브젝트 수 제한
pods: "20" # 최대 파드 수
services: "10" # 최대 서비스 수
persistentvolumeclaims: "5" # 최대 PVC 수
secrets: "20" # 최대 Secret 수
configmaps: "20" # 최대 ConfigMap 수
kubectl apply -f resource-quota.yaml
# ResourceQuota 사용량 확인
kubectl describe resourcequota dev-quota -n development
# Name: dev-quota
# Namespace: development
# Resource Used Hard
# -------- ---- ----
# limits.cpu 500m 8
# limits.memory 512Mi 16Gi
# pods 2 20
# requests.cpu 250m 4
# requests.memory 256Mi 8Gi
ResourceQuota가 있으면 requests/limits 필수
ResourceQuota에 CPU/메모리 제한이 있는 Namespace에서는, 파드의 모든 컨테이너가 resources.requests와 resources.limits를 명시해야 합니다. 없으면 파드 생성이 거부됩니다.
# ResourceQuota가 있는 Namespace에서 resources 없이 파드 생성 시
kubectl run test-pod --image=nginx -n development
# Error from server (Forbidden): pods "test-pod" is forbidden:
# failed quota: dev-quota: must specify limits.cpu for: test-pod
# resources를 명시한 올바른 파드
spec:
containers:
- name: app
image: nginx
resources:
requests:
cpu: "100m" # 0.1 CPU 코어
memory: "128Mi"
limits:
cpu: "500m" # 0.5 CPU 코어
memory: "256Mi"
LimitRange — 컨테이너별 기본값과 최댓값 설정
ResourceQuota를 설정했는데 resources 필드 없이 파드를 배포하면 CPU와 메모리 요청이 0으로 처리되어 Quota 검사가 제대로 동작하지 않습니다. 반대로 모든 파드에 일일이 requests와 limits를 작성하도록 강제하면 개발자 부담이 늘고 누락 사고가 생깁니다. LimitRange는 이 문제를 해결합니다. 개별 파드/컨테이너 수준에서 기본값과 상한·하한을 설정하여, resources를 명시하지 않은 파드에 자동으로 적절한 값을 주입합니다. 이 CB에서는 LimitRange 설정과 ResourceQuota와의 적용 순서를 다룹니다. resources 필드를 명시하지 않은 파드에 자동으로 기본값을 적용합니다.
LimitRange 설정
# limit-range.yaml
apiVersion: v1
kind: LimitRange
metadata:
name: dev-limits
namespace: development
spec:
limits:
- type: Container
default: # resources 미설정 시 자동 적용되는 limits 기본값
cpu: "500m"
memory: "256Mi"
defaultRequest: # requests 기본값
cpu: "100m"
memory: "128Mi"
max: # 컨테이너 하나의 최대 한도
cpu: "2"
memory: "1Gi"
min: # 컨테이너 하나의 최소 한도
cpu: "50m"
memory: "64Mi"
- type: Pod
max: # 파드 전체(컨테이너 합산) 최대 한도
cpu: "4"
memory: "2Gi"
- type: PersistentVolumeClaim
max:
storage: "50Gi" # PVC 최대 용량
min:
storage: "1Gi" # PVC 최소 용량
kubectl apply -f limit-range.yaml
# LimitRange 확인
kubectl describe limitrange dev-limits -n development
# Name: dev-limits
# Namespace: development
# Type Resource Min Max Default Request Default Limit
# ---- -------- --- --- --------------- -------------
# Container cpu 50m 2 100m 500m
# Container memory 64Mi 1Gi 128Mi 256Mi
# resources를 명시하지 않은 파드가 LimitRange 기본값을 받는지 확인
kubectl run no-limits-pod --image=nginx -n development
kubectl get pod no-limits-pod -n development -o jsonpath='{.spec.containers[0].resources}' | jq
# {
# "limits": {"cpu": "500m", "memory": "256Mi"}, ← LimitRange 기본값 자동 적용
# "requests": {"cpu": "100m", "memory": "128Mi"}
# }
ResourceQuota vs LimitRange 적용 순서
파드 생성 요청
│
▼ LimitRange 적용
resources 없는 컨테이너에 기본값 주입
│
▼ ResourceQuota 검사
이 Namespace의 총 사용량 + 새 파드 ≤ Hard 한도?
│ │
YES → 파드 생성 허용 NO → 파드 생성 거부 (403 Forbidden)
문제 상황
# frontend 파드(development Namespace)에서 api-service에 접근 시도
kubectl exec -it frontend-pod -n development -- \
curl http://api-service/users
# curl: (6) Could not resolve host: api-service
# 또는
# curl: (7) Failed to connect to api-service port 80: Connection refused
api-service는 실제로 존재하고 Running 상태인데 접근이 안 됩니다.
진단: 서비스가 어느 Namespace에 있는지 확인
# 모든 Namespace에서 api-service 찾기
kubectl get service -A | grep api-service
# NAMESPACE NAME TYPE CLUSTER-IP PORT(S)
# production api-service ClusterIP 10.96.45.100 80/TCP
# ↑ production Namespace에 있음!
원인 분석
같은 Namespace에서는 서비스 이름만으로 접근 가능합니다. 다른 Namespace의 서비스에는 Namespace가 포함된 DNS 이름을 사용해야 합니다.
클러스터 내부 DNS 해석 순서:
1. <service-name> → <service-name>.<현재-namespace>.svc.cluster.local
2. <service-name>.<ns> → <service-name>.<ns>.svc.cluster.local
3. FQDN → <service-name>.<ns>.svc.cluster.local (직접 지정)
"api-service"로 조회 → development.svc.cluster.local에서 찾음 → 없음 → 실패!
해결: Namespace 포함한 DNS 이름 사용
# 방법 1: <service>.<namespace> 형식 (자주 쓰는 축약형)
kubectl exec -it frontend-pod -n development -- \
curl http://api-service.production
# 방법 2: 전체 FQDN
kubectl exec -it frontend-pod -n development -- \
curl http://api-service.production.svc.cluster.local
# 정상 응답 확인
# {"users": [...]}
코드/환경변수에서 수정
# 파드의 환경변수에서 서비스 URL을 FQDN으로 설정
env:
- name: API_URL
value: "http://api-service.production.svc.cluster.local"
# value: "http://api-service" ← 같은 Namespace에서만 동작
DNS 동작 확인 도구
# 특정 서비스 DNS 해석 테스트
kubectl run dns-debug --image=busybox -it --rm --restart=Never \
-n development -- nslookup api-service.production
# Server: 10.96.0.10
# Name: api-service.production.svc.cluster.local
# Address 1: 10.96.45.100
# 현재 파드의 resolv.conf 확인 (어떤 search domain이 설정되어 있는지)
kubectl exec -it frontend-pod -n development -- cat /etc/resolv.conf
# nameserver 10.96.0.10
# search development.svc.cluster.local svc.cluster.local cluster.local
# ↑ 현재 namespace가 첫 번째 search domain
실무에서 Namespace를 어떻게 구분하는가
팀마다 다르지만 가장 많이 쓰이는 두 가지 패턴입니다.
패턴 1: 환경별 분리
production ← 운영 환경 (인프라 팀 관리)
staging ← 스테이징 (QA 팀 접근)
development ← 개발 환경 (개발 팀 자유롭게 사용)
패턴 2: 팀별 분리
team-backend ← 백엔드 팀
team-frontend ← 프론트엔드 팀
team-data ← 데이터 팀
infra ← 공유 인프라 (Ingress Controller, 모니터링)
규모가 커지면 두 패턴을 조합합니다: backend-prod, backend-dev, frontend-prod 등.
RBAC으로 Namespace 접근 제어
# 개발팀에게 development Namespace만 접근 권한 부여
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: dev-team-binding
namespace: development # 이 Namespace에서만 유효
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: edit # edit = 조회, 생성, 수정 가능 (삭제 불가)
subjects:
- kind: Group
name: dev-team # OIDC 그룹 또는 ServiceAccount
apiGroup: rbac.authorization.k8s.io
Namespace 삭제 시 주의
Namespace 삭제
안전한 실행 조건: 학습용 Namespace이거나 소유 리소스를 모두 확인한 뒤 정리할 때만 실행하세요.
실행 전 반드시 확인
- 현재 컨텍스트와 Namespace가 의도한 대상인지 확인했는가
- 운영 트래픽이나 상태 저장 데이터에 미치는 영향을 확인했는가
- 되돌릴 매니페스트, 백업, 또는 복구 절차가 준비되어 있는가
kubectl delete namespace development위 항목을 모두 확인한 후 복사할 수 있습니다
# Namespace 삭제는 그 안의 모든 리소스를 삭제함
kubectl delete namespace development
# 삭제 전 반드시 내부 리소스 확인
kubectl get all -n development
kubectl get pvc -n development # PVC는 데이터 포함
# Namespace가 Terminating 상태에 멈추는 경우 (finalizer 문제)
kubectl get namespace development -o json | jq '.spec.finalizers'
# 비어 있어야 정상 삭제됨
공유 서비스를 위한 Namespace 패턴
monitoring ← Prometheus, Grafana (전체 팀이 읽기 접근)
ingress-nginx ← Ingress Controller (인프라 팀 관리)
cert-manager ← 인증서 자동화
logging ← Loki, ELK (전체 로그 집중)
이런 공유 Namespace는 ClusterRole로 특정 팀에 읽기 권한만 부여하고, 수정은 인프라 팀만 가능하게 제한합니다.
정리
Namespace 삭제
안전한 실행 조건: 학습용 Namespace이거나 소유 리소스를 모두 확인한 뒤 정리할 때만 실행하세요.
실행 전 반드시 확인
- 현재 컨텍스트와 Namespace가 의도한 대상인지 확인했는가
- 운영 트래픽이나 상태 저장 데이터에 미치는 영향을 확인했는가
- 되돌릴 매니페스트, 백업, 또는 복구 절차가 준비되어 있는가
kubectl delete namespace <name> # 안의 모든 리소스도 삭제됨!위 항목을 모두 확인한 후 복사할 수 있습니다
# Namespace 관련 주요 명령어
# Namespace 목록
kubectl get namespaces
kubectl get ns # 축약형
# Namespace 생성/삭제
kubectl create namespace <name>
kubectl delete namespace <name> # 안의 모든 리소스도 삭제됨!
# 특정 Namespace에서 명령어 실행
kubectl get pods -n <namespace>
kubectl get all -n <namespace>
# 모든 Namespace에서 조회
kubectl get pods -A
kubectl get pods --all-namespaces
# 기본 Namespace 변경 (매번 -n 생략하려면)
kubectl config set-context --current --namespace=<namespace>
# ResourceQuota 확인
kubectl describe resourcequota -n <namespace>
# LimitRange 확인
kubectl describe limitrange -n <namespace>
# 다른 Namespace 서비스 접근 (DNS)
# http://<service-name>.<namespace>.svc.cluster.local
이제 Kubernetes 핵심 네트워킹과 스토리지, 설정 관리의 기초를 모두 습득했습니다. 다음 단계로는 RBAC을 통한 접근 제어, HPA를 통한 자동 스케일링, 또는 Helm 패키지 매니저를 학습하면 실제 운영 수준의 클러스터 관리가 가능합니다.