infra
Platform

모듈 맵

[Kubernetes] Namespace를 활용한 여러 개발팀 간 리소스 분할 운영

0 / 29 완료

펼치기
0 / 29 완료0%

Kubernetes · 10 / 29

[Kubernetes] Namespace를 활용한 여러 개발팀 간 리소스 분할 운영

Namespace로 개발·스테이징·운영을 격리하고 ResourceQuota와 LimitRange로 자원 사용을 제어합니다

🚨INCIDENT ALERT
HIGH

개발팀이 테스트 리소스를 정리하려다 운영 리소스까지 같은 클러스터에서 건드릴 뻔했습니다. 여러 팀과 환경이 한 클러스터를 공유하면 논리적 경계가 없을 때 작은 명령 하나가 큰 장애가 됩니다. 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-info
현재 Namespace 목록 확인
kubectl get namespaces
현재 컨텍스트 확인
kubectl config current-context
현재 기본 Namespace 확인
kubectl config view --minify | grep namespace
💡개념

Namespace — 클러스터 안의 클러스터

하나의 클러스터에서 여러 팀이 작업하다 보면 개발 환경의 리소스가 운영 환경 파드와 같은 네임스페이스에 뒤섞입니다. kubectl delete all을 개발 환경 정리용으로 실행했는데 운영 파드까지 삭제되는 사고가 발생합니다. Kubernetes는 기본적으로 네임스페이스를 지정하지 않으면 모든 리소스가 default에 쌓입니다. Namespace는 하나의 클러스터 안에서 논리적 구획을 만들어 팀과 환경을 분리하고, RBAC으로 각 구획의 접근 권한을 제어합니다. 이 CB에서는 Kubernetes 기본 제공 Namespace의 역할과, 사용자 정의 Namespace 생성 및 관리 방법을 다룹니다.

Namespace — 클러스터 안의 클러스터

기본 제공 Namespace

Kubernetes
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/READYRunning, Ready, Available처럼 정상 상태를 나타내는 필드가 있는지 봅니다.
  • RESTARTS/EVENTS재시작 횟수나 Warning 이벤트가 증가하지 않는지 확인합니다.

kube-system은 절대 건드리지 않는 것이 원칙입니다. CoreDNS나 kube-proxy 같은 핵심 컴포넌트가 여기 있습니다.

Namespace 생성

Kubernetes
# 명령어로 생성
kubectl create namespace development
kubectl create namespace staging
kubectl create namespace production

# YAML로 생성 (팀 정보, 환경 정보를 label로 관리)
YAML
# 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
Kubernetes
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, ServiceNode, PersistentVolume
ConfigMap, SecretStorageClass
PersistentVolumeClaimClusterRole, ClusterRoleBinding
ServiceAccountNamespace 자체
Kubernetes
# 어떤 리소스가 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에 각각 배포

YAML
# 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
YAML
# 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
Kubernetes
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 변경

Kubernetes
# 매번 -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 설정

YAML
# 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 수
Kubernetes
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.requestsresources.limits를 명시해야 합니다. 없으면 파드 생성이 거부됩니다.

Kubernetes
# 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
YAML
# 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 검사가 제대로 동작하지 않습니다. 반대로 모든 파드에 일일이 requestslimits를 작성하도록 강제하면 개발자 부담이 늘고 누락 사고가 생깁니다. LimitRange는 이 문제를 해결합니다. 개별 파드/컨테이너 수준에서 기본값과 상한·하한을 설정하여, resources를 명시하지 않은 파드에 자동으로 적절한 값을 주입합니다. 이 CB에서는 LimitRange 설정과 ResourceQuota와의 적용 순서를 다룹니다. resources 필드를 명시하지 않은 파드에 자동으로 기본값을 적용합니다.

LimitRange 설정

YAML
# 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 최소 용량
Kubernetes
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)

문제 상황

Kubernetes
# 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에 있는지 확인

Kubernetes
# 모든 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 이름 사용

Kubernetes
# 방법 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": [...]}

코드/환경변수에서 수정

YAML
# 파드의 환경변수에서 서비스 URL을 FQDN으로 설정
env:
  - name: API_URL
    value: "http://api-service.production.svc.cluster.local"
    # value: "http://api-service"  ← 같은 Namespace에서만 동작

DNS 동작 확인 도구

Kubernetes
# 특정 서비스 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 접근 제어

YAML
# 개발팀에게 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 안의 Pod, Service, PVC 등 모든 리소스가 함께 삭제될 수 있습니다.

Namespace 삭제

안전한 실행 조건: 학습용 Namespace이거나 소유 리소스를 모두 확인한 뒤 정리할 때만 실행하세요.

실행 전 반드시 확인

  • 현재 컨텍스트와 Namespace가 의도한 대상인지 확인했는가
  • 운영 트래픽이나 상태 저장 데이터에 미치는 영향을 확인했는가
  • 되돌릴 매니페스트, 백업, 또는 복구 절차가 준비되어 있는가
kubectl delete namespace development

위 항목을 모두 확인한 후 복사할 수 있습니다

Kubernetes
# 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 안의 Pod, Service, PVC 등 모든 리소스가 함께 삭제될 수 있습니다.

Namespace 삭제

안전한 실행 조건: 학습용 Namespace이거나 소유 리소스를 모두 확인한 뒤 정리할 때만 실행하세요.

실행 전 반드시 확인

  • 현재 컨텍스트와 Namespace가 의도한 대상인지 확인했는가
  • 운영 트래픽이나 상태 저장 데이터에 미치는 영향을 확인했는가
  • 되돌릴 매니페스트, 백업, 또는 복구 절차가 준비되어 있는가
kubectl delete namespace <name> # 안의 모든 리소스도 삭제됨!

위 항목을 모두 확인한 후 복사할 수 있습니다

Kubernetes
# 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 패키지 매니저를 학습하면 실제 운영 수준의 클러스터 관리가 가능합니다.

지식 확인

퀴즈 — 4문제

Q1

Kubernetes Namespace에 대한 설명으로 올바른 것은?

Q2

다른 Namespace의 Service에 접근하는 올바른 DNS 형식은?

Q3

ResourceQuota와 LimitRange의 차이는?

Q4

kubectl 명령어에서 특정 Namespace를 지정하지 않으면 어떤 Namespace가 사용되는가?

0 / 4 답변

🧪 실습으로 확인하기

K8s 기초 — Pod/Deployment/Service 생성

초급

kubectl로 nginx Pod를 생성하고 Deployment와 Service를 차례로 만들어 클러스터 외부에서 접근 가능한 상태까지 구성한다. K8s 3대 리소스의 역할과 관계를 직접 손으로 익힌다.

40📋 5단계💻 직접 환경
실습 시작하기 →

이것도 배워보세요

kubernetes중급 · 60
[Kubernetes] HPA(Horizontal Pod Autoscaler) 메트릭 기반 파드 확장
Kubernetes 트랙 계속
docker입문 · 30
[Docker] 백엔드 개발자에게 Docker와 컨테이너 가상화가 필수인 이유
Docker 트랙 시작점