본문으로 건너뛰기

3.5 네임스페이스

클러스터 하나를 여러 팀과 여러 환경이 나눠 쓰기 시작하면 이름 충돌이 먼저 온다. 두 팀이 각자 web이라는 Deployment를 만들려는 순간 한쪽이 거부된다. namespace는 이 문제에 대한 Kubernetes의 대답이다. 오브젝트 이름의 유효 범위를 구획해 같은 이름이 구획마다 공존하게 하고, 나아가 권한(RBAC), 자원 총량(ResourceQuota), 각종 정책이 걸리는 경계 단위가 된다. 다만 namespace가 나누는 것은 이름과 정책이지 커널이나 네트워크가 아니므로, 멀티 테넌시의 첫 단계일 뿐 완성은 아니다. 권한 분리는 Part IX. 보안에서 이어진다.

무엇이 namespace 안에 있는가

모든 리소스가 namespace에 속하지는 않는다. Pod, Service, Deployment, ConfigMap처럼 워크로드에 가까운 것은 namespaced이고, Node, PersistentVolume, StorageClass, ClusterRole, 그리고 Namespace 자신처럼 클러스터 전체에 하나뿐인 것은 cluster-scoped다. 어느 쪽인지 외울 필요는 없고 조회 명령이 있다.

$ kubectl api-resources --namespaced=true | head -4
NAME          SHORTNAMES   APIVERSION   NAMESPACED   KIND
bindings                   v1           true         Binding
configmaps    cm           v1           true         ConfigMap
endpoints     ep           v1           true         Endpoints

$ kubectl api-resources --namespaced=false | head -4
NAME                SHORTNAMES   APIVERSION   NAMESPACED   KIND
componentstatuses   cs           v1           false        ComponentStatus
namespaces          ns           v1           false        Namespace
nodes               no           v1           false        Node

namespace는 서비스의 DNS 이름에도 들어간다. 같은 namespace 안에서는 서비스 이름만으로 접속하고, 다른 namespace의 서비스는 <service>.<namespace> 또는 전체 이름 <service>.<namespace>.svc.cluster.local로 부른다. 애플리케이션 설정에 서비스 이름만 적어 두면 매니페스트 전체를 다른 namespace로 옮겨도 설정이 그대로 동작하는데, 이 상대 참조가 환경 복제를 떠받치는 성질이다.

기본으로 존재하는 네 namespace

새 클러스터에도 namespace 네 개가 이미 있다.

namespace용도
defaultnamespace를 지정하지 않은 오브젝트가 들어가는 곳
kube-systemcontrol plane과 시스템 컴포넌트 (CoreDNS, kube-proxy 등)
kube-public모든 클라이언트가 읽는 공개 데이터용. 관례적 예약
kube-node-lease노드 heartbeat용 Lease 오브젝트

공식 문서는 default를 그대로 쓰는 대신 용도별 namespace를 만들라고 권한다. default에 쌓인 워크로드는 소속이 드러나지 않아 정리도 권한 부여도 어려워진다. kube-로 시작하는 이름은 시스템용으로 예약되어 있으므로 새 namespace 이름에 쓰지 않는다.

ResourceQuota와 LimitRange

이름을 나눈 다음 단계는 자원을 나누는 일이다. ResourceQuota는 namespace 단위의 총량 제한이다. CPU와 메모리의 requests, limits 합계, Pod나 PVC의 개수 같은 것에 상한을 정하면, 초과하는 생성 요청을 API server가 거부한다.

apiVersion: v1
kind: ResourceQuota
metadata:
  name: team-a-quota
  namespace: team-a
spec:
  hard:
    requests.cpu: "10"
    requests.memory: 20Gi
    pods: "50"

CPU나 메모리에 quota가 걸린 namespace에서는 requests와 limits를 명시하지 않은 Pod의 생성이 거부된다. 이때 개별 Pod에 기본값을 채워 주는 장치가 LimitRange다. LimitRange는 namespace 안 오브젝트 하나하나의 기본값과 허용 범위(min, max)를 정한다. 총량은 ResourceQuota, 개별 기본값과 범위는 LimitRange로 역할이 나뉜다. requests와 limits 자체의 의미는 Part VIII. 스케줄링과 자원 관리에서 다룬다.

삭제는 안의 전부를 지운다

kubectl delete namespace team-a는 team-a 안의 모든 오브젝트를 함께 지운다. Deployment, Service, ConfigMap, Secret, PVC까지 예외가 없고, 확인 질문도 없다. 오브젝트 수십 개를 하나씩 지울 필요가 없다는 편리함과, 명령 한 줄이 환경 전체를 지우는 파괴력이 같은 동작의 양면이다.

namespace 삭제는 되돌리지 못한다. PVC가 지워지면 StorageClass의 reclaim policy에 따라 그 아래 PersistentVolume과 실제 데이터까지 연쇄 삭제될 여지가 있다. 삭제 전에 kubectl get all,cm,secret,pvc -n <ns>로 안에 무엇이 있는지 확인하는 습관이 사고를 막는다.

삭제를 실행하면 namespace는 Terminating 상태로 들어가 안의 리소스가 정리되기를 기다린다. 보통 수십 초 안에 끝나지만, 이 상태로 몇 시간씩 고착되는 경우가 있다. 흔한 원인은 두 가지다. 안의 어떤 오브젝트에 finalizer가 남아 그 오브젝트의 삭제가 끝나지 않는 경우, 그리고 aggregated API service(대표적으로 metrics-server)가 응답하지 않아 API server가 “이 namespace에 남은 리소스가 없다"는 확인 자체를 끝내지 못하는 경우다.

$ kubectl get namespace team-a
NAME     STATUS        AGE
team-a   Terminating   2h

# 응답하지 않는 API service 확인
$ kubectl get apiservice | grep False
v1beta1.metrics.k8s.io   kube-system/metrics-server   False (ServiceUnavailable)   30d

# namespace 관점에서 무엇이 남았는지 확인
$ kubectl get namespace team-a -o jsonpath='{.status.conditions}'

원인이 된 오브젝트나 API service를 복구하는 것이 정공법이다. namespace의 finalizer를 강제로 제거해 Terminating을 끝내는 우회법이 널리 알려져 있지만, 정리되지 않은 리소스가 잔류(orphan)할 위험을 감수하는 마지막 수단으로 남겨 둔다.

정리

namespace는 이름의 구획이자 RBAC, ResourceQuota, LimitRange가 걸리는 정책 경계로, 멀티 테넌시의 출발점이다. 리소스에는 namespaced와 cluster-scoped 두 범위가 있고 kubectl api-resources --namespaced로 구분한다. 삭제 한 번이 안의 전부를 지운다는 점을 기억하고, Terminating에 고착된 namespace는 finalizer와 apiservice 상태부터 확인한다.