-
이코에코(Eco²) GitOps #06 - Namespace · RBAC · NetworkPolicy를 한 뿌리에서이코에코(Eco²)/Kubernetes Cluster+GitOps+Service Mesh 2025. 11. 25. 18:15
🛎️ 본 포스팅은 이미 구현이 완료된 사안만 다룹니다. 현재 이코에코 14-nodes cluster는 ap-northeast-2 리전에 배포돼 있습니다.

이번 글은 이코에코의 네임스페이스(+라벨) 구조, RBAC 모델, NetworkPolicy가 어떻게 각 요소를 전제로 엮여 있는지에 집중한다.
0. 이코에코 RBAC · Namespace 설계의 특징
- 라벨 우선 설계
네임스페이스를 단순히 이름만 나누는 게 아니라tier,role,domain라벨까지 한 번에 정의했다. 덕분에 RBAC RoleBinding, NetworkPolicy selector, ServiceMonitor 등 다른 모든 리소스가 이 라벨을 공통 언어로 쓰게 된다. - Tier 기반 역할 매핑
RBAC 문서가 “tier=business-logic → api-dev Role”, “tier=data → data-ops Role”처럼 라벨을 기준으로 역할을 매핑한다. 그래서 새 도메인을 추가할 때도 namespace 라벨만 맞으면 Role/Binding 템플릿을 그대로 재사용할 수 있다. - 네트워크와 권한이 같은 메타데이터를 공유
NetworkPolicy까지 동일 라벨을 보기 때문에, 예를 들어tier=business-logicPod가postgres네임스페이스로 나갈 수 있는가는 Namespace 라벨과 RBAC에서 이미 정의된 관계를 그대로 재사용한다.
정리하면, “라벨 표준화 → RBAC 템플릿 → NetworkPolicy 재사용 → GitOps 예외 프로세스”가 한 뿌리로 묶여 있는 게 큰 특징이다. 관련 체크리스트와 정책 문서들은 이코에코 GitHub에서 확인할 수 있다.
1. Namespace: 라벨 하나가 모든 컨트롤 포인트가 된다
workloads/namespaces/base/namespaces.yaml은 단순한 정의 파일 같지만, 여기에서 결정한 라벨이 이후 RBAC/NetworkPolicy 설계의 기준선이 된다. 모든 도메인이tier,role,domain을 필수로 가지도록 했고, GitOps 외부에서 네임스페이스를 만들지 못하게 했다.metadata: name: auth labels: app.kubernetes.io/part-of: ecoeco-backend tier: business-logic role: api domain: authdocs/namespace/NAMESPACE_CONSISTENCY_CHECKLIST.md는 이 라벨 세트를 “Authoritative Matrix”로 삼고,하나라도 어긋나면 체크리스트에서 실패하도록 만들어놨다.
| Tier | Namespace | 필수 Label 세트 | 주요 역할/리소스 | | business-logic | auth | tier=business-logic, domain=auth, role=api | Auth API | | data | postgres | tier=data, role=database, data-type=postgres | PostgreSQL CR | ... - [ ] `tier` / `role` 라벨이 올바른가?Namespace × ServiceAccount 관계
RBAC 디렉터리에는 인프라 계층용 ServiceAccount도 함께 정의돼 있다.
Namespace가 먼저 준비돼 있어야 ServiceAccount의
namespace필드도 자연스럽게 채워진다.Namespace 예시 ServiceAccount 역할 출처 platform-systemexternal-secrets-sa,external-dnsESO, ExternalDNS workloads/rbac-storage/base/service-accounts.yamldata-systempostgres-operatorPostgres Operator 동일 kube-systemaws-load-balancer-controllerALB Controller 동일 rabbitmq-systemrabbitmq-cluster-operatorRabbitMQ Operator 동일 kind: ServiceAccount metadata: name: external-secrets-sa namespace: platform-system --- metadata: name: postgres-operator namespace: data-system
2. RBAC: 라벨을 전제로 한 역할 모델
| 역할 | ClusterRole 범위 | Namespace 접근 | 사용 주체 | | platform-admin | Cluster-scoped | 전체 | Platform/SRE | | data-ops | Namespaced + data/messaging | postgres, redis, messaging | DB 팀 | | api-dev | Namespaced | 각 도메인 | 서비스 팀 |
네임스페이스 라벨이 명확하니 RBAC 문서(docs/namespace/RBAC_NAMESPACE_POLICY.md)도 Tier 기준으로 정리할 수 있었다.kind: Role metadata: name: api-dev namespace: auth rules: - apiGroups: ["apps"] resources: ["deployments"] verbs: ["get", "list", "watch", "patch", "update"] - apiGroups: [""] resources: ["configmaps", "secrets", "services"] verbs: ["get", "list", "watch", "create", "update", "patch"]Role 정의는 workloads/rbac-storage/base/namespaced-roles.yaml에 있다.
예를 들어 api-dev Role은 각 도메인 네임스페이스별로 동일하게 붙는다.
3. NetworkPolicy: 라벨 기반 레이어 격리 + 예외 프로세스
NetworkPolicy 설계는
docs/networking/NAMESPACE_NETWORKPOLICY_INGRESS.md와docs/networking/NETWORK_ISOLATION_POLICY.md두 문서에 나뉘어 있다.핵심은 tier 라벨로 레이어를 나누고, 필요할 때만 네임스페이스 selector를 통해 트래픽을 열어 준다.
- tier=business-logic → tier=data 대상만 TCP 5432/6379 허용 - tier=integration ingress는 business-logic 네임스페이스만 허용
실제 정책은 아래처럼 namespace selector를 사용한다.kind: NetworkPolicy metadata: name: allow-postgres namespace: auth spec: policyTypes: [Egress] egress: - to: - namespaceSelector: matchLabels: kubernetes.io/metadata.name: postgres ports: - protocol: TCP port: 5432
번외. Application 레벨 격리
Namespace/RBAC/NetworkPolicy로 1차 격리를 끝낸 뒤에도, 애플리케이션 계층에서 두 가지 수단을 더 쓴다.
- 전용 노드 스케줄링
docs/namespace/NAMESPACE_CONSISTENCY_CHECKLIST.md의 기준표에는 각 도메인 네임스페이스가 어떤 노드와 taint를 갖는지까지 적혀 있다. 덕분에auth는k8s-api-auth에만,scan은 GPU가 붙은k8s-api-scan에만 올라가고, 다른 네임스페이스는 해당 taint를 감당하지 못해서 스케줄되지 않는다.
| Tier | Namespace | 필수 Label 세트 | 주요 역할/리소스 | 전용 노드 & Taint | | business-logic | `auth` | ... | Auth API | `k8s-api-auth`, taint `domain=auth:NoSchedule` | | business-logic | `scan` | ... | Scan API | `k8s-api-scan`, taint `domain=scan:NoSchedule` | | data | `postgres` | ... | PostgreSQL | `k8s-postgresql`, taint `domain=data:NoSchedule` |- Kustomize Overlay로 도메인별 공통 레이블/Selector 고정
## workloads/domains/location/base/kustomization.yaml labels: - includeSelectors: true pairs: app.kubernetes.io/part-of: ecoeco-backend domain: location tier: business-logic
workloads/domains/<svc>/base/kustomization.yaml에서 includeSelectors: true 옵션을 걸어 Deployment·Service·Job selector까지 tier/domain 라벨을 강제로 덮어씌운다. 덕분에 다른 네임스페이스의 Pod가 실수로 섞일 수 없고 ServiceMonitor/NetworkPolicy selector도 같은 값을 참조한다.
# api-auth ubuntu@k8s-master:~$ kubectl get pods -n auth -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES auth-api-546645d494-875h7 1/1 Running 0 132m 192.168.141.3 k8s-api-auth <none> <none> # scan ubuntu@k8s-master:~$ kubectl get pods -n scan NAME READY STATUS RESTARTS AGE scan-api-766cd85487-pvv6n 1/1 Running 0 5h3m # chat ubuntu@k8s-master:~$ kubectl get pods -n chat -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES chat-api-5c74d67bbf-tj5b9 1/1 Running 0 168m 192.168.38.218 k8s-api-chat <none> <none> # my ubuntu@k8s-master:~$ kubectl get pods -n my -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES my-api-65947b9944-qrbx6 1/1 Running 0 4h16m 192.168.44.228 k8s-api-my <none> <none> # image ubuntu@k8s-master:~$ kubectl get pods -n image -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES image-api-7964c94f7b-g5tqv 1/1 Running 0 5h4m 192.168.32.82 k8s-api-image <none> <none> # character ubuntu@k8s-master:~$ kubectl get pods -n character -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES character-api-6d6f99c655-f262k 1/1 Running 0 4h17m 192.168.237.110 k8s-api-character <none> <none> # location ubuntu@k8s-master:~$ kubectl get pods -n location -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES location-api-cc85dfb88-9fjpj 1/1 Running 0 4h18m 192.168.183.45 k8s-api-location <none> <none>
part of -> tier -> role -> domains/features
앞서 잡아둔 Namespace 구조를 Application Layer(Node)까지 확장해 묶었다.
이렇게 구성하면 모노레포 + 단일 GitOps 파이프라인을 거쳐 각 도메인은 지정된 노드에 온전히 배포된다.
리소스의 스케일을 조정할 일이 생겨도 인프라-application이 동일 네임스페이스를 전제로 움직이기에 관리 포인트가 감소한다.
이미 복잡도가 높은 구조 위에 네임스페이스 기반 제약사항을 추가한 상황이라 유연성을 내준 측면도 존재한다.
활성 노드 11개 + MQ 적용 시 투입 대기 노드 3개 = 총 노드 14개로 많기에 개발 및 운영 측면에서 네임스페이스+라벨 기반 추상화를 통해 얻는 도메인별 역할/책임 분리가 가치가 높다고 판단했다. 분산 클러스터 구성과 LLM의 생산성을 1인으로 제어하기 위해선 명료한 추상화가 필요하다.
정리하면 이코에코 인프라는 NAMESPACE, 특히 라벨 의존성이 높다. 클러스터의 격리지점과 책임을 명시하는 설계도의 역할을 한다.
7개의 도메인 서버들이 네임스페이스/라벨에 따라 노드, Network Policy, Ingress 등을 할당받아 클러스터의 일원이 된다.
네임스페이스/라벨을 끝으로 이코에코에서 가장 많은 리소스를 투입한 클러스터 및 GitOps 구축에 대한 서술은 마무리됐다.
32일 중 25일 가량을 클러스터 + GitOps 구축/개발에 쏟고, 남은 7일동안 7가지 도메인 API 개발, 프론트 연동, 본선이 진행됐다.
API 개발은 투입한 리소스가 적었던만큼 관습적으로 작성됐고(각 도메인을 레이어드 아키텍처로 통일), 그 층 또한 얇기 때문에 별 다른 서술은 하지 않을 생각이다. 다음 포스팅부턴 본선에 대한 이야기가 주를 이루니 편하게 읽어줬으면 한다.'이코에코(Eco²) > Kubernetes Cluster+GitOps+Service Mesh' 카테고리의 다른 글
이코에코(Eco²) Service Mesh #2: 내부 통신을 위한 gRPC 마이그레이션 (0) 2025.12.12 이코에코(Eco²) Service Mesh #1: Istio Sidecar 마이그레이션 (0) 2025.12.08 이코에코(Eco²) GitOps #05 Sync Wave (0) 2025.11.25 이코에코(Eco²) GitOps #04 Operator와 Helm-charts를 오가며 겪은 시행착오들 (0) 2025.11.24 이코에코(Eco²) GitOps #03 네트워크 안정화 (0) 2025.11.24 - 라벨 우선 설계