들어가며: kubectl이 되는데, 왜 Pod에서는 API 호출이 거부될까?
Kubernetes 클러스터에서 kubectl get pods는 잘 되는데, Pod 안에서 실행한 애플리케이션이 Kubernetes API를 호출하면 403 Forbidden을 받는 상황. 원인은 RBAC(Role-Based Access Control)이다. kubectl은 kubeconfig에 지정된 사용자 인증서로 인증되지만, Pod는 자동 마운트된 ServiceAccount 토큰으로 인증되며, 해당 ServiceAccount에 권한이 없으면 모든 API 호출이 거부된다.
이 글에서는 Kubernetes 공식 문서(Using RBAC Authorization, Configure Service Accounts for Pods)를 근거로, RBAC의 네 가지 핵심 리소스 — ServiceAccount, Role, ClusterRole, Binding — 의 관계와 설계 원칙을 실무 시나리오와 함께 다룬다.
1. RBAC 4대 리소스와 관계
1-1. 전체 구조
| 리소스 | 범위 | 역할 |
|---|---|---|
ServiceAccount |
Namespace | Pod의 신원(identity). Pod가 API 서버에 인증할 때 사용하는 주체 |
Role |
Namespace | 특정 네임스페이스 내의 리소스에 대한 권한(verbs) 정의 |
ClusterRole |
Cluster-wide | 클러스터 전체 또는 비-네임스페이스 리소스(Node, PV 등)에 대한 권한 정의 |
RoleBinding |
Namespace | Role/ClusterRole을 특정 네임스페이스의 주체(ServiceAccount/User/Group)에 연결 |
ClusterRoleBinding |
Cluster-wide | ClusterRole을 클러스터 전체 범위로 주체에 연결 |
핵심 관계: 주체(Who) → Binding(연결) → Role(What). Role은 “무엇을 할 수 있는가”를 정의하고, Binding은 “누가 그 Role을 갖는가”를 연결한다.
1-2. Role vs. ClusterRole 선택 기준
| 사용 사례 | 선택 | 이유 |
|---|---|---|
| 특정 네임스페이스의 Pod/Service 조회 | Role + RoleBinding |
네임스페이스 범위로 충분 |
| 여러 네임스페이스에서 동일 권한 재사용 | ClusterRole + 각 NS에 RoleBinding |
ClusterRole을 정의하고 RoleBinding으로 NS별 연결 가능 |
| Node, PersistentVolume 등 비-NS 리소스 | ClusterRole + ClusterRoleBinding |
네임스페이스 범위가 아닌 리소스 |
| 모든 네임스페이스의 Pod 조회 (모니터링) | ClusterRole + ClusterRoleBinding |
전체 클러스터 범위 필요 |
2. ServiceAccount: Pod의 신원 관리
2-1. 기본 동작
Kubernetes 공식 문서에 따르면, 모든 네임스페이스에는 default ServiceAccount가 자동 생성된다. Pod에 serviceAccountName을 지정하지 않으면 default SA가 사용된다.
Kubernetes 1.24+부터 ServiceAccount에 대한 시크릿 자동 생성이 중단되었다. 대신 TokenRequest API를 통해 시간 제한(bound) 토큰이 자동 마운트된다(공식 문서 Bound Service Account Tokens).
2-2. 최소 권한 원칙: default SA 사용 금지
# ❌ 안티패턴: default SA에 권한을 부여
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: default-pod-reader
namespace: production
subjects:
- kind: ServiceAccount
name: default # 모든 Pod가 이 권한을 갖게 됨!
namespace: production
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
# ✅ 권장: 전용 ServiceAccount 생성
apiVersion: v1
kind: ServiceAccount
metadata:
name: monitoring-agent
namespace: production
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: monitoring-agent
namespace: production
spec:
template:
spec:
serviceAccountName: monitoring-agent # 전용 SA 지정
automountServiceAccountToken: true
2-3. 토큰 자동 마운트 비활성화
API 서버에 접근할 필요가 없는 Pod는 토큰 마운트를 비활성화하는 것이 보안 모범 사례다:
apiVersion: v1
kind: ServiceAccount
metadata:
name: web-app
namespace: production
automountServiceAccountToken: false # SA 레벨에서 비활성화
또는 Pod spec에서 automountServiceAccountToken: false를 지정할 수 있다. Pod 레벨 설정이 SA 레벨보다 우선한다.
3. Role과 ClusterRole: 권한 정의의 정밀도
3-1. 기본 Role 작성
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-reader
namespace: production
rules:
- apiGroups: [""] # core API group
resources: ["pods"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["pods/log"] # 서브리소스
verbs: ["get"]
3-2. verbs 전체 목록
| Verb | HTTP 메서드 | 설명 |
|---|---|---|
get |
GET (단건) | 특정 리소스 조회 |
list |
GET (목록) | 리소스 목록 조회 |
watch |
GET (watch) | 리소스 변경 감시 |
create |
POST | 리소스 생성 |
update |
PUT | 리소스 전체 업데이트 |
patch |
PATCH | 리소스 부분 업데이트 |
delete |
DELETE | 리소스 삭제 |
deletecollection |
DELETE (목록) | 리소스 일괄 삭제 |
주의: *(와일드카드)는 모든 verb를 의미한다. 프로덕션에서 verbs: ["*"]는 사용하지 않는다.
3-3. resourceNames로 특정 리소스만 허용
rules:
- apiGroups: [""]
resources: ["configmaps"]
resourceNames: ["app-config", "feature-flags"] # 이 2개만 접근 가능
verbs: ["get", "update"]
resourceNames를 지정하면 해당 이름의 리소스에만 접근을 허용한다. list나 create verb에는 resourceNames가 적용되지 않는다는 점에 주의한다(공식 문서 명시).
3-4. Aggregated ClusterRoles
Kubernetes 공식 문서에 따르면, aggregationRule을 사용하면 레이블 셀렉터로 다른 ClusterRole의 규칙을 자동 병합할 수 있다. 빌트인 admin, edit, view ClusterRole이 이 방식으로 동작한다:
# CRD 권한을 기본 admin 역할에 자동 추가
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: my-crd-admin
labels:
rbac.authorization.k8s.io/aggregate-to-admin: "true" # admin에 병합
rules:
- apiGroups: ["myapp.example.com"]
resources: ["widgets"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
4. Binding: 주체와 권한의 연결
4-1. RoleBinding 예시
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: monitoring-agent-pod-reader
namespace: production
subjects:
- kind: ServiceAccount
name: monitoring-agent
namespace: production
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
4-2. ClusterRole + RoleBinding 패턴 (재사용)
ClusterRole을 정의하고, 각 네임스페이스에 RoleBinding으로 연결하면 권한 정의는 한 번, 적용은 네임스페이스별로 할 수 있다:
# 1. ClusterRole 정의 (한 번)
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
---
# 2. 각 NS에 RoleBinding (필요한 곳마다)
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: dev-pod-reader
namespace: development
subjects:
- kind: ServiceAccount
name: ci-agent
namespace: development
roleRef:
kind: ClusterRole # Role이 아닌 ClusterRole 참조
name: pod-reader
apiGroup: rbac.authorization.k8s.io
이 패턴에서 ci-agent는 development 네임스페이스의 Pod만 조회할 수 있다. ClusterRole이지만 RoleBinding의 네임스페이스 범위로 제한된다.
5. 디버깅: 권한 확인 방법
5-1. kubectl auth can-i
# 현재 사용자의 권한 확인
kubectl auth can-i create deployments --namespace production
# 특정 ServiceAccount의 권한 확인
kubectl auth can-i list pods
--namespace production
--as system:serviceaccount:production:monitoring-agent
# 모든 권한 나열
kubectl auth can-i --list
--as system:serviceaccount:production:monitoring-agent
5-2. 403 에러 디버깅 체크리스트
- Pod의
serviceAccountName이 올바른지 확인:kubectl get pod -o jsonpath='{.spec.serviceAccountName}' - 해당 SA에 바인딩된 Role/ClusterRole 확인:
kubectl get rolebinding,clusterrolebinding -A -o json | grep -A5 "monitoring-agent" - Role의
apiGroups,resources,verbs가 실제 API 호출과 일치하는지 확인 - 서브리소스 접근 시
resources에pods/log,pods/exec등 서브리소스를 명시했는지 확인 automountServiceAccountToken이false로 설정되어 토큰이 마운트되지 않는 것은 아닌지 확인
6. 실전 설계 패턴
6-1. 네임스페이스별 역할 분리
| 역할 | ClusterRole | Binding | 권한 |
|---|---|---|---|
| 개발자 | edit (빌트인) |
RoleBinding (dev NS) | 리소스 CRUD, 시크릿 제외 |
| 운영자 | admin (빌트인) |
RoleBinding (prod NS) | Role/RoleBinding 포함 전체 NS 관리 |
| 모니터링 | 커스텀 pod-reader |
ClusterRoleBinding | 전체 NS Pod/Service 읽기 |
| CI/CD | 커스텀 deployer |
RoleBinding (대상 NS) | Deployment/Service/ConfigMap CRUD |
6-2. CI/CD Deployer Role 예시
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: deployer
rules:
- apiGroups: ["apps"]
resources: ["deployments", "replicasets"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
- apiGroups: [""]
resources: ["services", "configmaps"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
- apiGroups: ["networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
delete를 의도적으로 제외하여 CI/CD 파이프라인이 실수로 리소스를 삭제하지 못하게 한다.
7. 흔한 실수와 방지법
| 실수 | 증상 | 방지법 |
|---|---|---|
| default SA에 권한 부여 | 해당 NS의 모든 Pod가 과도한 권한 획득 | 워크로드별 전용 ServiceAccount 생성. default SA의 automountServiceAccountToken을 false로 설정 |
cluster-admin ClusterRoleBinding 남용 |
단일 토큰 탈취 시 클러스터 전체 장악 | 최소 권한 원칙. cluster-admin은 긴급 운영용으로만 사용하고 일상 작업에는 사용 금지 |
apiGroups 누락/오류 |
403 Forbidden (core group은 "", apps는 "apps") |
kubectl api-resources로 정확한 apiGroup 확인 |
| 서브리소스 미등록 | kubectl exec나 kubectl logs 403 |
resources에 pods/exec, pods/log 서브리소스 명시 |
정리
Kubernetes RBAC은 ServiceAccount(누구) → Binding(연결) → Role(무엇)의 삼각 구조다. 최소 권한 원칙을 지키려면 ①워크로드별 전용 SA 생성, ②필요한 verb만 명시, ③ClusterRole + RoleBinding 재사용 패턴을 활용한다. kubectl auth can-i --as로 권한을 사전 검증하고, 403 에러 시 apiGroups·resources·verbs·서브리소스를 하나씩 확인하면 대부분의 문제를 해결할 수 있다.