Kubernetes Practice 09

Kubernetes Namespace: 클러스터 안의 논리적 구분

클러스터가 하나인데 namespace를 또 만드는 이유와, YAML에 namespace: production을 쓰는 의미가 모호하다.

근거 · Kubernetes 교안 p98-p106, yaml/ns/*.yaml

Kubernetes Namespace: 클러스터 안의 논리적 구분 대표 이미지

1장: 클러스터 안의 또 다른 구분선, 왜 필요할까?

솔라는 모니터를 끄고 노트에 적어둔 문장을 다시 들여다보았다. 밑줄까지 그어둔 문장이었지만, 곱씹을수록 미궁에 빠지는 기분이었다.

“Namespace를 사용하면 같은 클러스터 안에서 리소스 이름과 범위를 분리할 수 있습니다.”

분명 간결하고 명확한 정의였다. 하지만 솔라의 머릿속에서는 이 문장이 계속 맴돌며 질문을 만들어냈다. ‘클러스터가 이미 분리된 단위 아니었나?’ 개발용 클러스터, 운영용 클러스터처럼 용도에 따라 아예 별개의 클러스터를 만드는 것이 일반적이라고 이해하고 있었다. 그런데 이미 격리된 환경인 클러스터 안에 또 다른 분리 단위를 두는 이유를 납득하기 어려웠다.

“언니, 잠깐만. 나 이거 진짜 모르겠어.”

거실에서 책을 읽던 루나가 고개를 들었다. 솔라는 노트를 들고 루나 옆으로 다가가 앉았다.

“클러스터라는 게 이미 서버들을 묶어서 격리된 환경을 만드는 거잖아. 그런데 왜 그 안에 Namespace라는 걸로 또 공간을 나눠야 해? 물리적으로 서버를 더 나누는 것도 아니라는데, 그냥 이름표만 붙이는 거랑 뭐가 달라?”

솔라의 질문에는 날카로운 혼란이 묻어 있었다. ‘격리’라는 단어가 주는 묵직한 물리적 경계의 이미지와, 문서에서 설명하는 ‘논리적 구분’이라는 가벼운 개념 사이의 간극 때문이었다.

루나는 솔라의 노트를 잠시 들여다보더니, 질문에 바로 답하는 대신 솔라의 노트북을 가리켰다.

“지금 그 클러스터에 접속되어 있지? 터미널 한번 열어볼래?”

솔라는 고개를 끄덕이며 노트북을 열어 터미널 창을 띄웠다. 프롬프트가 깜빡이고 있었다.

“거기에 이렇게 한번 입력해봐. 우리가 쓰는 이 클러스터가 지금 어떤 상태인지, 일종의 ‘주소록’을 보는 거야.”

루나가 말한 명령어는 간단했다.

kubectl get namespaces

엔터 키를 누르자, 몇 줄의 텍스트가 나타났다.

NAME              STATUS   AGE
default           Active   30d
kube-node-lease   Active   30d
kube-public       Active   30d
kube-system       Active   30d

“어… 뭐가 있네. default는 많이 봤고, 나머지는 쿠버네티스 시스템이 쓰는 건가 봐.”

“맞아. 우리가 지금까지 파드를 만들고 서비스를 배포할 때, 특별히 어디에 만들라고 지정하지 않았지? 그럴 때마다 저 default라는 공간에 자동으로 생성된 거야. 마치 큰 건물에 들어갔을 때, 특정 호수를 지정하지 않으면 일단 1층 로비로 안내되는 것처럼.”

“건물의 1층 로비?”

“응. 클러스터를 하나의 거대한 아파트 건물이라고 생각해봐. 건물 자체는 외부와 완전히 분리되어 있지. 이게 솔라가 말한 ‘클러스터의 격리’야. 그런데 건물 안에는 101호, 102호, 201호처럼 여러 세대가 살잖아. 각 세대는 자기 집 현관문이라는 경계 안에서 독립적으로 생활하고. 여기서 Namespace가 바로 그 ‘세대’나 ‘사무실’ 같은 역할을 해.”

루나의 설명에 솔라는 잠시 생각에 잠겼다. 건물과 그 안의 세대라니. 물리적인 비유였지만, 무언가 다른 점을 암시하고 있었다.

“그럼… Namespace를 만든다는 건, 건물을 증축하거나 벽을 세우는 게 아니라는 뜻?”

“정확해. 새 세대를 만든다고 해서 건물의 벽돌이 늘어나진 않지. 그냥 관리사무소 주소록에 ‘501호: 개발팀’, ‘601호: 운영팀’ 하고 새로운 주소를 등록하고, 그 공간을 논리적으로 분리해서 관리하기 시작하는 거야. 자, 우리도 새 주소를 하나 등록해볼까? ‘production’이라는 이름의 사무실을 만들어보자.”

루나는 다음 명령어를 알려주었다.

kubectl create namespace production

솔라가 명령어를 입력하자 ‘namespace/production created’라는 메시지가 즉시 나타났다. 벽돌 하나 옮기지 않았는데, 순식간에 새로운 공간이 만들어진 것이다. 솔라는 믿기지 않는다는 표정으로 다시 한번 ‘주소록’을 확인했다.

kubectl get namespaces
NAME              STATUS   AGE
default           Active   30d
kube-node-lease   Active   30d
kube-public       Active   30d
kube-system       Active   30d
production        Active   7s

결과 목록 맨 아래에 방금 생성한 production이 선명하게 보였다.

“아…!”

솔라는 짧은 탄성을 터뜨렸다. 그제야 ‘논리적 구분’이라는 말의 의미가 피부로 와 닿았다. 서버를 추가하거나 네트워크를 분리하는 물리적인 작업 없이, 오직 명령어 하나로 클러스터라는 큰 공간 안에 새로운 ‘관리 경계’가 그어졌다. 이건 리소스를 격리하는 또 다른 차원의 이야기였다. 물리적 자원의 분리가 아니라, 자원을 부르고 관리하는 이름과 범위를 나누는 것이었다.

“이제 알겠어. 클러스터는 물리적인 울타리고, Namespace는 그 울타리 안에 용도별로 그어놓은 보이지 않는 구분선 같은 거구나. 개발팀과 운영팀이 같은 클러스터를 쓰더라도 서로의 작업 공간을 침범하지 않도록 칸막이를 쳐주는 거네.”

솔라는 자신이 처음 가졌던 질문이 해결되었음을 느꼈다. 하지만 새로운 의문이 곧바로 고개를 들었다.

“좋아, production이라는 이름의 빈방을 하나 만들었어. 그런데 내가 만든 애플리케이션 파드를 저 production 방에 넣으려면 어떻게 해야 해? YAML 파일에 그냥 namespace: production이라고 한 줄 적으면 정말 그 방으로 쏙 들어가는 건가? 그게 단순한 주석이나 꼬리표가 아니라, 정말 효력이 있는 거야?”

2장: YAML 속 namespace: production, 단순한 라벨이 아니다

솔라의 노트북 화면에는 방금 전과는 다른 창이 떠 있었다. 하얀 배경에 텍스트 편집기가 열려 있었고, 그 안에는 쿠버네티스 디플로이먼트를 정의하는 YAML 파일의 내용이 가지런히 정렬되어 있었다. 솔라의 시선은 파일의 앞부분, metadata 섹션에 한참 동안 머물러 있었다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  namespace: production
spec:
  ...

바로 전, 명령어 하나로 production이라는 논리적 공간이 생성되는 것을 직접 확인했다. 빈방이 만들어진 것은 알겠다. 하지만 솔라는 여전히 개운치 않은 표정이었다. 그녀의 손가락이 namespace: production이라는 줄을 가리켰다. 이 한 줄이 정말로 애플리케이션을 방금 만든 그 ‘방’으로 데려다 놓는 열쇠가 될 수 있을까? 솔라에게는 마치 소스 코드에 다는 주석이나, 단순히 정보를 표기하기 위한 이름표처럼 보였다.

“언니, 이 YAML 파일 좀 봐. namespaceproduction으로 지정했어. 이제 이걸 클러스터에 적용하면 my-app이라는 디플로이먼트가 만들어지겠지. 그런데 이 한 줄이 정말로 ‘격리’라는 효과를 만들어 낼까? 그냥 ‘이건 production용이야’라고 메모해두는 것 이상의 의미가 없을 것 같은데.”

솔라의 의심은 타당했다. 물리적인 분리 없이, 단순히 텍스트 파일의 한 줄 선언만으로 자원의 소속과 가시성이 바뀐다는 개념은 직관적으로 와닿지 않았다. 루나는 솔라의 곁으로 다가와 화면을 함께 들여다보았다.

“좋은 의심이야. 그 한 줄이 단순한 메모인지, 아니면 구속력 있는 ‘주소 지정’인지 확인해볼 가장 좋은 방법은 직접 배달시켜보는 거지.”

루나는 솔라의 터미널 창 두 개를 나란히 배치하도록 했다. 마치 두 개의 다른 장소에서 상황을 지켜보려는 듯이.

“자, 이제 그 YAML 파일을 클러스터에 적용해봐. 우리가 원하는 상태를 쿠버네티스에게 알려주는 거야.”

솔라는 익숙하게 명령어를 입력했다.

kubectl apply -f deploy-svc-ns.yaml

‘deployment.apps/my-app created’라는 메시지가 나타났다. 배포는 성공적으로 시작된 것 같았다. 솔라는 즉시 자신이 늘 하던 대로 파드의 상태를 확인하는 명령어를 입력했다. 이것이 자신의 예측을 증명할 순간이었다. 만약 이 namespace 필드가 단순한 라벨이라면, 방금 생성된 파드가 이 목록에 나타나야 했다.

kubectl get pods

하지만 터미널은 솔라의 기대를 배신했다.

No resources found in default namespace.

“어? 왜 아무것도 안 나오지? 배포는 성공했다고 했는데… 파드가 아직 안 뜬 건가?”

솔라는 몇 번 더 같은 명령어를 반복했지만 결과는 같았다. 당황한 솔라의 표정을 보며 루나가 조용히 말했다.

“우리가 특별히 방을 지정하지 않으면, 어디를 보게 된다고 했지?”

“1층 로비… 아, default 네임스페이스.”

“맞아. 솔라는 지금 1층 로비만 계속 두리번거리고 있는 거야. 우리가 배달시킨 물건은 ‘production’이라는 이름의 방으로 갔을 테니, 그곳을 직접 확인해봐야지.”

루나는 kubectl 명령어 뒤에 새로운 옵션을 붙이는 법을 알려주었다. 특정 방의 문을 열고 안을 들여다보는 것과 같은 역할이었다.

kubectl get pods -n production

솔라가 조심스럽게 명령어를 입력하고 엔터를 눌렀다. 잠시 후, 화면에 새로운 내용이 나타났다.

NAME                      READY   STATUS    RESTARTS   AGE
my-app-6d8b9875f9-abcde   1/1     Running   0          15s

“와…!”

솔라는 두 개의 터미널 창을 번갈아 보았다. 왼쪽 창(default를 조회한)은 텅 비어 있었고, 오른쪽 창(production을 조회한)에는 방금 배포한 my-app 파드가 정상적으로 실행되고 있었다.

그제야 솔라는 모든 조각이 맞춰지는 것을 느꼈다. YAML 파일에 적은 namespace: production은 단순한 꼬리표가 아니었다. 그것은 쿠버네티스 API 서버에 보내는 명확하고 강제적인 ‘명령’이었다. ‘이 리소스를 production이라는 지정된 공간 안에 생성하고 관리하라’는 지시였던 것이다. 그리고 그 지시에 따라 리소스는 해당 공간 안에 완벽히 소속되었고, 다른 공간에서는 보이지 않게 되었다.

“알았어. 이제 확실히 알겠어. YAML 파일에 쓰는 namespace는 리소스의 ‘주소’를 지정하는 거였구나. 그리고 kubectl에서 -n 옵션을 쓰는 건, 내가 들여다보고 싶은 방의 ‘문패’를 부르는 거였네. 이 두 가지가 짝을 이뤄서 리소스의 범위를 완벽하게 제어하는 거였어.”

솔라는 ‘논리적 구분선’이라는 말이 이제야 선명하게 그려졌다. 보이지 않는 선이지만, YAML의 선언과 kubectl의 조회 옵션이라는 구체적인 도구를 통해 누구에게나 동일하게 작동하는 강력한 경계였다. 솔라는 새로운 발견에 신이 나서 키보드를 두드렸다.

“그럼 이건 정말 유용한데? 개발용 my-appdefault 네임스페이스에 두고, 운영용 my-appproduction 네임스페이스에 두면 되겠네. 이름이 같아도…”

솔라의 손가락이 순간 멈칫했다. 새로운 의문이 꼬리를 물고 피어올랐다.

“잠깐만. 정말 그래도 되나? 이름이 똑같은 디플로이먼트를, 단지 네임스페이스만 다르다고 해서 두 개나 만들 수 있다고? 시스템 입장에서 보면 똑같은 my-app인데, 정말 충돌이 일어나지 않을까?”

3장: 이름은 같아도 너와 나는 남, Namespace로 구분된 세계

솔라의 손가락이 키보드 위에서 멈칫했다. 화면에는 방금 전 production 네임스페이스에 성공적으로 배포했던 my-app 디플로이먼트의 YAML 파일이 열려 있었다. 솔라는 그 파일에서 namespace: production 한 줄을 지우고, 다른 이름으로 저장해 둔 상태였다. 이 파일을 적용하면, namespace가 지정되지 않았으니 디플로이먼트는 default 네임스페이스에 생성될 것이다.

문제는 이름이었다. 파일의 metadata.name은 여전히 my-app이었다. 이미 클러스터 어딘가에, production이라는 방 안에 my-app이라는 이름의 디플로이먼트가 존재하고 있었다. 솔라는 컴퓨터에게 같은 이름의 파일을 두 번 저장할 수 없듯이, 쿠버네티스 클러스터 역시 같은 이름의 리소스를 중복해서 생성하는 것을 허용하지 않을 거라고 막연히 짐작하고 있었다. 엔터 키를 누르기 직전, 솔라는 망설였다. 충돌이 일어나거나, 최악의 경우 기존 productionmy-app에 어떤 영향을 줄지도 모른다는 불안감이 스쳤다.

솔라의 멈춘 손을 본 루나가 조용히 물었다.

“왜? 뭔가 마음에 걸리는 게 있어?”

“응. 지금 default 네임스페이스에 my-app이라는 디플로이먼트를 만들려고 하는데, 아까 production 네임스페이스에 똑같은 이름으로 하나 만들었잖아. 보통 이런 건 이름이 중복된다고 오류가 나지 않아? 시스템이 어떻게 my-app이라는 똑같은 이름을 구분할 수 있겠어?”

솔라의 질문은 지극히 상식적이었다. 하나의 시스템 안에서 고유해야 할 식별자가 중복되는 상황을 걱정하는 것은 당연했다.

루나는 솔라의 질문에 바로 답하지 않고, 대신 솔라의 화면을 가리켰다.

“그 질문에 대한 답을 쿠버네티스에게 직접 물어보는 게 어때? default 네임스페이스에 my-app 배포를 시도해봐.”

“정말 괜찮을까? production에 있는 게 망가지면 어떡해?”

production이라는 방의 문은 굳게 닫혀 있어. 우리가 그 방의 열쇠(-n production)를 쓰지 않는 한, default라는 로비에서 하는 일이 그 방 안까지 영향을 주진 못할 거야.”

루나의 말에 솔라는 작은 용기를 얻었다. 그녀는 심호흡 한번 하고, 망설이던 엔터 키를 눌렀다.

kubectl apply -f deploy-svc-default.yaml

솔라는 잔뜩 긴장한 채로 화면을 응시했다. ‘AlreadyExists’ 같은 오류 메시지가 나타날 거라 예상했다. 하지만 터미널에 나타난 메시지는 솔라의 예상을 완전히 빗나갔다.

deployment.apps/my-app created

“어… 만들어졌어. 오류가 안 났네?”

솔라는 어리둥절한 표정으로 중얼거렸다. 시스템이 경고 없이 명령을 수행했다. 그렇다면 둘 중 하나였다. 기존 productionmy-app이 지워지고 새로 만들어졌거나, 혹은 솔라가 모르는 방식으로 두 개가 공존하고 있거나.

“이제 확인해볼 시간이야. 각자의 방에 my-app이 잘 있는지, 문을 열고 들여다봐.”

루나의 말에 솔라는 먼저 default 네임스페이스를 확인했다. namespace 옵션을 따로 주지 않으면 default를 보게 된다는 것을 이제는 알고 있었다.

kubectl get deployment
NAME     READY   UP-TO-DATE   AVAILABLE   AGE
my-app   1/1     1            1           10s

방금 생성한 my-appdefault 공간에 잘 있었다. 이제 남은 것은 production 네임스페이스의 안부를 확인하는 것이었다. 솔라는 가슴을 졸이며 확인 명령어를 입력했다.

kubectl get deployment -n production
NAME     READY   UP-TO-DATE   AVAILABLE   AGE
my-app   1/1     1            1           5m

“와… 그대로 있네!”

솔라는 두 개의 터미널 결과를 번갈아 보며 탄성을 질렀다. 두 개의 my-app 디플로이먼트가 서로 아무런 영향을 주지 않은 채, 각자의 공간에서 독립적으로 실행되고 있었다. AGE(생성 후 경과 시간) 값이 다른 것이 그 명백한 증거였다.

그제야 솔라는 모든 것을 이해했다. 쿠버네티스에게 리소스의 진짜 ‘전체 이름’은 단순히 metadata.name이 아니었다. 그것은 바로 **‘네임스페이스 + 이름’**의 조합이었던 것이다. 마치 동명이인이 다른 도시에 살면 아무 문제가 없는 것처럼, production 네임스페이스의 my-appdefault 네임스페이스의 my-app은 이름만 같을 뿐, 시스템이 인식하는 주소 자체가 다른 별개의 존재였다.

“이제야 알겠어. 네임스페이스는 단순한 관리용 구분선이나 꼬리표가 아니었어. 리소스의 고유성을 보장하는 주소의 일부였네. 이게 바로 ‘이름 공간(Namespace)’이라는 단어의 진짜 의미구나.”

솔라는 자신이 얻은 새로운 깨달음을 노트에 정리하기 시작했다. 단순히 글자를 적는 것이 아니었다. 그녀는 커다란 사각형을 그리고 ‘클러스터’라고 적었다. 그 안에 두 개의 작은 사각형을 그리고 각각 development, production이라고 이름 붙였다. 그리고 각 사각형 안에 ‘my-app’이라는 작은 동그라미를 하나씩 그려 넣었다. 두 개의 ‘my-app’이 서로 다른 경계 안에 안전하게 존재하는 모습이었다. 물리적 분리 없이도 충돌을 막아주는 논리적 격리, 그 그림이 이제 머릿속에서 선명하게 그려졌다.

“이제 개발팀은 development 네임스페이스에서 마음껏 my-app을 테스트하고, 운영팀은 production 네임스페이스에서 안정적으로 my-app을 서비스할 수 있겠네. 같은 클러스터 안에서, 서로 방해할 걱정 없이 말이야.”

솔라는 자신이 그린 간단한 다이어그램을 보며 만족스럽게 미소 지었다. 클러스터라는 하나의 큰 땅에, 용도에 따라 독립적인 집을 지을 수 있는 설계도를 손에 넣은 기분이었다.