Kubernetes 권한 관리 — ServiceAccount와 RBAC
이 글은 K8s 입문 시리즈의 여덟 번째 글이다.
- 01: Kubernetes의 탄생 — Google Borg에서 CNCF까지
- 02: 내 노트북에 클러스터 만들기 — kind와 kubectl
- 03: Kubernetes 아키텍처 — Control Plane과 Node
- 04: Pod의 모든 것 — 생성부터 스케줄링까지
- 05: 워크로드 — ReplicaSet, Deployment, StatefulSet, DaemonSet
- 06: 네트워킹 — Service와 Ingress
- 07: 스토리지와 설정 — PV/PVC, ConfigMap, Secret
- 08: 권한 관리 — ServiceAccount와 RBAC ← 현재 글
- 09: 확장과 생태계 — Operator와 CNCF Projects
이 시리즈의 커리큘럼은 SK Devocean의 Kubernetes(쿠버네티스)를 처음 공부하려면 무엇을 공부해야 할까? (seungkyua) 글의 학습 로드맵을 바탕으로 구성했다.
지금까지 우리는 kind 클러스터에서 관리자 계정 하나로 모든 걸 해왔다. Pod를 만들고, Deployment를 롤백하고, Secret까지 읽었다. 로컬 실습이니까 괜찮았지만, 회사 클러스터라면 이야기가 다르다. 신입 개발자가 실수로 kubectl delete namespace prod를 칠 수 있는 클러스터, 로그 수집용 Pod가 Secret을 읽을 수 있는 클러스터는 사고가 나기 전의 상태일 뿐이다. 이번 글에서는 Kubernetes가 “누가, 무엇을 할 수 있는가”를 통제하는 방법 — ServiceAccount와 RBAC — 을 배우고, 읽기 전용 계정을 직접 만들어 검증까지 해본다.
1. 누가(인증) 무엇을 할 수 있나(인가) — API 서버의 관문
건물 출입에 비유하면 이해가 빠르다. 1층 게이트에서 사원증을 찍는 것이 인증(Authentication) 이다. “너가 누구인지”를 확인할 뿐, 아직 아무 데도 못 간다. 사원증을 찍고 나서 서버실 문 앞에 섰을 때 문이 열리는지가 인가(Authorization) 다. 같은 직원이라도 서버실 출입 권한은 따로 부여받아야 한다.
| 질문 | 단계 | Kubernetes에서의 수단 | 실패 시 응답 |
|---|---|---|---|
| “너 누구야?” | 인증 (Authentication) | 클라이언트 인증서, 토큰(SA JWT) 등 | 401 Unauthorized |
| “너 이거 해도 돼?” | 인가 (Authorization) | RBAC (그 외 ABAC, Node, Webhook 모드) | 403 Forbidden |
| “규칙에 맞는 요청?” | 어드미션 (Admission) | Admission Controller (변형/검증) | 요청 즉시 거부 |
03편에서 kube-apiserver가 모든 통신의 관문이고, 요청이 인증 → 인가 → 어드미션 파이프라인을 거친다고 했다. 이번 글의 주인공인 RBAC은 이 중 두 번째 단계, 인가를 담당한다.
- 인증에 실패하면 401, 인가에서 거부되면 403이 돌아온다.
kubectl에러 메시지에서Forbidden을 봤다면 “누군지는 알겠는데 권한이 없다”는 뜻이다. - 어드미션 컨트롤은 인가가 통과된 뒤에만 실행된다. 권한이 있는 요청이라도 정책(리소스 쿼터 초과 등)에 걸리면 여기서 거부된다.
- Kubernetes의 인가 모드에는 RBAC 외에도 ABAC, Node, Webhook 등이 있지만, 오늘날 사실상의 표준은 RBAC(Role-Based Access Control)이다.
여기서 중요한 설계 포인트 하나. RBAC에는 거부(deny) 규칙이 없다. 기본 상태가 전부 거부이고, 규칙은 오직 허용만 추가한다(purely additive). 블랙리스트가 아니라 화이트리스트 방식이라, “실수로 너무 넓게 거부해서 장애”가 아니라 “허용을 안 해서 403”이 기본 실패 모드가 된다. 보안 관점에서 훨씬 안전한 기본값이다.
2. ServiceAccount — Pod의 신분증
인가를 하려면 먼저 “누구”가 정의되어야 한다. Kubernetes의 클라이언트는 크게 둘이다.
| 구분 | User (사람) | ServiceAccount (Pod) |
|---|---|---|
| 대상 | 개발자, 운영자 | Pod 안에서 실행되는 프로세스 |
| Kubernetes 오브젝트 | 아님 — 클러스터 외부에서 관리 | 맞음 — kubectl create serviceaccount로 생성 |
| 소속 | 클러스터 전역 | 특정 네임스페이스 |
| 인증 수단 | 인증서, OIDC 토큰 등 | TokenRequest API 기반 JWT |
| 표기 예 | jane | system:serviceaccount:dev:foo |
사람(User)은 의외로 Kubernetes API 오브젝트가 아니다. 인증서나 사내 SSO(OIDC) 같은 외부 수단으로 신원을 증명하면, API 서버는 거기 적힌 이름을 그대로 사용자명으로 쓴다. 반면 ServiceAccount는 클러스터 안에 존재하는 정식 리소스다. Pod가 API 서버에 뭔가를 요청해야 할 때 — 예를 들어 모니터링 에이전트가 Pod 목록을 읽을 때 — 그 Pod의 신분증 역할을 한다.
알아둘 기본 동작 세 가지.
- 모든 네임스페이스에는
default라는 ServiceAccount가 자동 생성된다. Pod를 만들 때 따로 지정하지 않으면 이defaultSA가 자동으로 할당된다.kubectl get pod <이름> -o yaml에서spec.serviceAccountName을 확인해 보면 보인다. - 토큰은 Pod 안에 자동 마운트된다. 경로는
/var/run/secrets/kubernetes.io/serviceaccount/이고token(JWT),ca.crt(CA 인증서),namespace세 파일이 들어 있다. Pod 안의 프로세스는 이 토큰으로 API 서버에 인증한다. - v1.24부터 토큰은 수명이 있는 단기 토큰이다. 예전에는 SA를 만들면 만료 없는 토큰 Secret이 자동 생성됐지만, 지금은 TokenRequest API로 발급된 수명 제한(bounded lifetime) 토큰이 projected volume으로 마운트되고, Pod가 삭제되면 무효화된다. 수동으로 발급해 보고 싶으면
kubectl create token <SA이름>을 쓰면 된다(--duration=24h처럼 기간 지정도 가능).
토큰이 필요 없는 Pod라면 자동 마운트를 꺼두는 것이 안전하다. ServiceAccount나 Pod 스펙에 automountServiceAccountToken: false를 주면 되고, 둘 다 지정된 경우 Pod 쪽 설정이 우선한다.
RBAC에서 ServiceAccount를 지칭할 때는 다음 형식의 전체 이름을 쓴다. 잠시 후 실습에서 그대로 사용한다.
system:serviceaccount:<네임스페이스>:<ServiceAccount 이름>
3. RBAC 4요소 — Role, ClusterRole, RoleBinding, ClusterRoleBinding
RBAC은 리소스 네 개로 굴러간다. 구조는 단순하다. “무엇을 할 수 있는가”를 정의하는 문서(Role/ClusterRole) 와 “그 문서를 누구에게 주는가”를 정의하는 문서(RoleBinding/ClusterRoleBinding) 가 분리되어 있다. 도서관에 비유하면 Role은 “열람실 이용 규정”이고 RoleBinding은 “이 회원에게 열람증 발급”이다. 규정과 발급 대장을 분리했기 때문에, 같은 규정을 여러 사람에게 재사용할 수 있다.
| 리소스 | 범위 | 역할 |
|---|---|---|
| Role | 네임스페이스 | 특정 네임스페이스 안에서의 권한 집합 정의 |
| ClusterRole | 클러스터 전역 | 클러스터 전체 또는 재사용 가능한 권한 정의 |
| RoleBinding | 네임스페이스 | Role(또는 ClusterRole)을 주체에게 연결 |
| ClusterRoleBinding | 클러스터 전역 | ClusterRole을 클러스터 전체 범위로 연결 |
Role은 반드시 특정 네임스페이스에 속하고, ClusterRole은 네임스페이스가 없는 클러스터 전역 리소스다. 여기서 조합이 하나 더 생긴다. RoleBinding이 ClusterRole을 참조할 수 있다. 이때 권한은 ClusterRole에 적힌 대로지만, 효력 범위는 RoleBinding이 있는 네임스페이스로 한정된다. “공통 권한 세트를 한 번만 정의하고, 네임스페이스마다 바인딩만 만드는” 재사용 패턴이다.
| 조합 | 권한이 미치는 범위 | 대표 용도 |
|---|---|---|
| Role + RoleBinding | 해당 네임스페이스 | 팀/앱 단위 세밀한 권한 |
| ClusterRole + RoleBinding | RoleBinding의 네임스페이스로 한정 | 공통 권한 정의를 네임스페이스별로 재사용 |
| ClusterRole + ClusterRoleBinding | 클러스터 전체 | 관리자, 노드·PV 같은 클러스터 범위 리소스 |
| Role + ClusterRoleBinding | 불가능 | ClusterRoleBinding의 roleRef는 ClusterRole만 참조 가능 |
주의할 점 하나 — 바인딩의 roleRef는 불변(immutable)이다. 만든 뒤에 참조할 Role을 바꾸려고 하면 검증 에러가 난다. 바꾸고 싶으면 바인딩을 지우고 새로 만들어야 한다. 바인딩 수정 권한을 가진 사람이 roleRef만 슬쩍 바꿔서 권한을 승격시키는 것을 막기 위한 설계다.
바퀴를 새로 발명할 필요도 없다. Kubernetes에는 사용자용 기본 ClusterRole이 내장되어 있다.
| 기본 ClusterRole | 권한 요약 |
|---|---|
cluster-admin | 클러스터 전체 슈퍼유저. 무엇이든 가능 |
admin | 네임스페이스 안의 관리자. Role/RoleBinding 생성으로 권한 위임 가능 |
edit | 네임스페이스 안 대부분 리소스 읽기/쓰기. Role/RoleBinding은 불가 |
view | 네임스페이스 안 읽기 전용. Secret은 볼 수 없음 |
네 요소가 연결되는 그림은 이렇다.
- ServiceAccount 자체에는 권한 정보가 전혀 없다. 권한은 오직 바인딩을 통해서만 흘러들어온다.
- API 서버는 요청이 올 때마다 “이 주체가 subjects에 포함된 바인딩이 있는가 → 그 바인딩의 roleRef가 가리키는 Role에 이 동사+리소스가 허용되어 있는가”를 평가한다.
- 셋 중 하나만 없어도(SA만 있고 바인딩이 없거나, 바인딩은 있는데 Role이 없거나) 결과는 403이다.
4. 규칙 해부 — apiGroups, resources, verbs
Role의 알맹이는 rules 배열이다. 공식 문서의 예시를 보자.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: pod-reader
rules:
- apiGroups: [""] # ""는 core API 그룹
resources: ["pods"]
verbs: ["get", "watch", "list"]
규칙 한 줄은 “어느 API 그룹의(apiGroups) 어떤 리소스에(resources) 어떤 동작을(verbs) 허용한다”로 읽는다. 필드를 하나씩 뜯어 보자.
-
apiGroups— 리소스가 속한 API 그룹. Pod, Service, ConfigMap 같은 초창기 리소스는 core 그룹이라 빈 문자열""로 쓴다. Deployment라면apps, Ingress라면networking.k8s.io처럼 그룹 이름을 적는다.kubectl api-resources출력의 APIVERSION 열에서 슬래시 앞부분이 바로 이 값이다. -
resources— 대상 리소스 종류.pods,deployments처럼 복수형 소문자로 쓴다. -
verbs— 허용할 동작. REST API의 동사에 대응한다.
| verb | 의미 | kubectl 대응 예 |
|---|---|---|
get | 단일 리소스 조회 | kubectl get pod nginx |
list | 목록 조회 | kubectl get pods |
watch | 변경 사항 실시간 구독 | kubectl get pods -w |
create | 생성 | kubectl apply -f (신규) |
update | 전체 수정 | kubectl replace |
patch | 부분 수정 | kubectl patch |
delete | 삭제 | kubectl delete pod nginx |
이번에는 바인딩 쪽이다.
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-pods
namespace: default
subjects:
- kind: User
name: jane # 대소문자 구분
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role # Role 또는 ClusterRole
name: pod-reader # 참조할 Role 이름
apiGroup: rbac.authorization.k8s.io
-
subjects— 권한을 받을 주체 목록.kind는 User, Group, ServiceAccount 세 가지다. User/Group은apiGroup: rbac.authorization.k8s.io를 명시하지만, ServiceAccount 주체는apiGroup없이name과namespace를 적는다(코어 리소스이기 때문). -
roleRef— 연결할 Role/ClusterRole 하나. 앞서 말했듯 생성 후 변경 불가. - 네임스페이스의 모든 SA에게 한 번에 주고 싶으면
kind: Group으로system:serviceaccounts:<네임스페이스>를, 클러스터의 모든 SA라면system:serviceaccounts를 주체로 쓸 수 있다.
5. 실습 — 읽기 전용 ServiceAccount 만들기
이제 kind 클러스터(02편에서 구축)에서 직접 만들어 본다. 목표는 “rbac-lab 네임스페이스의 Pod만 읽을 수 있는 계정” 이다.
5.1 네임스페이스와 ServiceAccount 생성
kubectl create namespace rbac-lab
kubectl create serviceaccount pod-viewer -n rbac-lab
namespace/rbac-lab created
serviceaccount/pod-viewer created
5.2 Role과 RoleBinding 작성
Pod에 대한 읽기 동사 세 개(get/list/watch)만 허용하는 Role, 그리고 그 Role을 pod-viewer에게 연결하는 RoleBinding이다.
# pod-reader.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: rbac-lab
name: pod-reader
rules:
- apiGroups: [""] # core 그룹 (Pod가 속한 곳)
resources: ["pods"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-pods
namespace: rbac-lab
subjects:
- kind: ServiceAccount
name: pod-viewer
namespace: rbac-lab
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
kubectl apply -f pod-reader.yaml
role.rbac.authorization.k8s.io/pod-reader created
rolebinding.rbac.authorization.k8s.io/read-pods created
5.3 kubectl auth can-i로 검증
권한을 확인하는 전용 명령이 있다. 문법은 다음과 같다.
kubectl auth can-i VERB [TYPE | TYPE/NAME | NONRESOURCEURL]
--as 플래그와 조합하면 다른 주체인 척(impersonation) 권한을 검사할 수 있다. ServiceAccount는 2절에서 본 system:serviceaccount:<네임스페이스>:<이름> 형식으로 지정한다. 임퍼소네이션 자체가 별도 권한이 필요한 동작이지만, kind가 만들어 준 기본 계정은 관리자 권한이라 바로 쓸 수 있다.
# 허용한 동작: pods 목록 조회 → yes
kubectl auth can-i list pods \
--as=system:serviceaccount:rbac-lab:pod-viewer -n rbac-lab
yes
# 허용 안 한 동사: pods 삭제 → no
kubectl auth can-i delete pods \
--as=system:serviceaccount:rbac-lab:pod-viewer -n rbac-lab
no
# 허용 안 한 리소스: secrets 조회 → no
kubectl auth can-i get secrets \
--as=system:serviceaccount:rbac-lab:pod-viewer -n rbac-lab
no
# 다른 네임스페이스의 pods → no (Role은 rbac-lab에서만 유효)
kubectl auth can-i list pods \
--as=system:serviceaccount:rbac-lab:pod-viewer -n default
no
정확히 의도대로다. 허용한 것(rbac-lab의 pods 읽기)만 yes고, 동사가 달라도(delete), 리소스가 달라도(secrets), 네임스페이스가 달라도(default) 전부 no다. RBAC이 화이트리스트라는 것, Role이 네임스페이스 범위라는 것이 그대로 드러난다.
주체가 가진 권한을 한눈에 보고 싶으면 --list를 쓴다.
kubectl auth can-i --list \
--as=system:serviceaccount:rbac-lab:pod-viewer -n rbac-lab
Resources Non-Resource URLs Resource Names Verbs
pods [] [] [get list watch]
...(모든 인증된 주체에게 주어지는 기본 권한 행은 생략)
참고로 kubectl auth can-i get pods --subresource=log처럼 --subresource 플래그를 주면 Pod 로그 같은 서브리소스 권한도 검사할 수 있다. kubectl logs가 되려면 pods 권한이 아니라 pods/log 권한이 필요하기 때문에, 읽기 전용 계정을 설계할 때 자주 쓰게 된다.
확인이 끝났으면 정리한다. 네임스페이스를 지우면 그 안의 SA, Role, RoleBinding이 함께 삭제된다.
kubectl delete namespace rbac-lab
namespace "rbac-lab" deleted
6. 실무 팁 — 최소 권한 원칙
RBAC의 문법보다 중요한 것은 운영 습관이다.
- 최소 권한 원칙(Principle of Least Privilege). 지금 필요한 동사·리소스·네임스페이스만 허용한다. “일단 넓게 주고 나중에 줄이자”는 대부분 영원히 안 줄어든다.
verbs: ["*"]나resources: ["*"]는 정말 필요한지 세 번 의심할 것. -
cluster-admin을 아무에게나 바인딩하지 않는다. 특히 앱 Pod의 ServiceAccount에 cluster-admin을 주는 것은 “그 Pod가 뚫리면 클러스터 전체가 뚫린다”와 같은 말이다. CI/CD 도구나 대시보드에 습관처럼 cluster-admin을 주는 사례가 흔한데, 필요한 네임스페이스에edit정도로 시작하는 편이 낫다. -
defaultSA에는 권한을 부여하지 않는다. 네임스페이스의 모든 “SA를 지정하지 않은 Pod”가 그 권한을 물려받게 된다. API 접근이 필요한 워크로드마다 전용 SA를 만들어 명시적으로 연결하자. - 토큰이 필요 없는 Pod는
automountServiceAccountToken: false. API를 안 쓰는 웹 서버 Pod에 토큰이 마운트되어 있으면 공격 표면만 넓어진다. - 기본 ClusterRole(view/edit/admin)을 재사용한다. 흔한 요구사항은 이미 잘 정의되어 있다.
view+ RoleBinding 조합이면 “이 네임스페이스 읽기 전용” 요구가 YAML 몇 줄로 끝난다. 단,view는 Secret을 볼 수 없다는 점은 07편에서 강조한 Secret 보호와 연결되는 의도된 설계다. - 권한 변경 후에는
kubectl auth can-i로 검증한다. 배포 파이프라인에서 스모크 테스트처럼 돌려도 좋다. yes/no 출력과 종료 코드로 확인할 수 있어 스크립트에 넣기도 쉽다.
7. 마무리
| 개념 | 한 줄 요약 |
|---|---|
| 인증 vs 인가 | “너 누구야?”(401) 다음에 “이거 해도 돼?”(403) — RBAC은 인가 담당 |
| ServiceAccount | Pod의 신분증. 네임스페이스마다 default SA 자동 생성, 토큰은 자동 마운트 |
| Role / ClusterRole | 권한 정의서 — 네임스페이스 범위 vs 클러스터 범위 |
| RoleBinding / ClusterRoleBinding | 권한 정의서를 주체(User/Group/SA)에게 연결. roleRef는 불변 |
| rules | apiGroups(어느 그룹) + resources(어떤 리소스) + verbs(어떤 동작) |
| kubectl auth can-i | --as=system:serviceaccount:<ns>:<name>로 임의 주체의 권한을 yes/no 검증 |
| 운영 원칙 | 화이트리스트뿐인 RBAC — 최소 권한, cluster-admin 남용 금지 |
Kubernetes 보안의 첫 단추는 화려한 도구가 아니라 “필요한 만큼만 허용한다”는 원칙과, 그것을 코드(YAML)로 표현하는 RBAC이다. 이제 내장 리소스는 대부분 훑었다. 다음 글에서는 시리즈의 마지막 주제로, Kubernetes에 우리만의 리소스 타입을 추가하는 CRD와 Operator, 그리고 그 생태계를 살펴본다.
이전 글: 07: 스토리지와 설정 — PV/PVC, ConfigMap, Secret
다음 글 09: 확장과 생태계 — Operator와 CNCF Projects에서는 CRD와 Operator로 Kubernetes를 확장하는 방법과 CNCF 프로젝트 생태계를 다루며 시리즈를 마무리한다.
참고 문헌
- Kubernetes(쿠버네티스)를 처음 공부하려면 무엇을 공부해야 할까? (seungkyua, SK Devocean, 2024) — 시리즈 로드맵 출처
- Using RBAC Authorization — Kubernetes 공식 문서
- Authorization — Kubernetes 공식 문서
- Controlling Access to the Kubernetes API — Kubernetes 공식 문서
- Configure Service Accounts for Pods — Kubernetes 공식 문서
- kubectl auth can-i — Kubernetes 공식 레퍼런스
- ClusterRoleBinding API Reference — Kubernetes 공식 문서
Enjoy Reading This Article?
Here are some more articles you might like to read next: