ServiceAccount
개요
서비스 어카운트(SA)는 사람이 아닌, 클러스터 리소스로서 만들어진 계정 리소스이다.
가령 파드의 앱, 시스템 컴포넌트, 클러스터 안팎에서 이 서아의 자격 증명서를 서아로서 식별되기 위해 쓸 수 있다.
신원 베이스의 보안 정책을 구현할 때나 api 서버 인증에서 유용하다.
특징 - 유정 계정과의 차이
다음의 특징을 가진다.[1]
- 네임스페이스 종속성 - 네임스페이스에 종속된다.
- 참고로 모든 네임스페이스에 대해
default
란 이름의 서비스 어카운트가 만들어진다.- 이건 kube-controller-manager에 Service Account controller가 담당한다.
- 이 서비스어카운트는 기본적으로 기본 api 탐색 권한(읽기 전용)만을 가진다.
- default SA는 직접 없앤다고 해도, 컨트롤 플레인에서 자동으로 새롭게 만들어준다.
- 참고로 모든 네임스페이스에 대해
- 가벼움 - api에 의해 정의되기에 빠르게 만들어 특정 작업을 수행케할 수 있다.
- 이식성 - 위 두 특징을 통해 얻는 특징으로 복잡한 워크로드에 설정 묶음 속에 시스템 컴포넌트를 위해 넣을 수 있다.
서비스어카운트는 클러스터에서 인간으로서 인증되는 유저 계정과 다르다.
일단 위의 특징들도 유저 계정과 분명히 다른 지점이고, 추가적으로 더 정리하자면,
- kube-apiserver에서 만들고 클러스터 리소스로서 저장한다.
- 유저 계정은 api 서버에 존재하지 않고, api서버는 유저 신원을 불분명한(opaque) 데이터로 간주한다.
- 그러나 서비스어카운트는 명백하게 클러스터 내부에서 생성되고 관리된다.
- 한정된 쿠버네티스 인증, 쿠버네티스 인가
- 유저 계정은 여러 방법으로 인증, 인가가 가능하다.
- 서비스 어카운트는 쿠버 RBAC를 통해서만 인가된다.
- 용도
- 유저 계정은 말 그대로 사람이 사용하는 계정이다.
- 서비스 어카운트는 클러스터와 통신해야 하는 워크로드와 관련한 자동화를 위해 사용되도록 만들어졌다.
용례
서비스 어카운트는 기본적으로 api 서버와 통신하기 위해 사용된다.
그런데 다른 용도로도 조금씩 사용될 수 있다!
- api 서버 통신
- 파드가 api 서버와 통신해서 어떤 데이터를 얻어와야 하는 상황에서 유용하다.
- 가령 시크릿에 저장된 민감한 정보를 읽기 전용으로 접근한다던가..
- 다른 네임스페이스의 오브젝트에 접근하고 싶다던가..
- 클러스터 내부에 설치된 보안 소프트웨어에도 따로 서비스 어카운트를 만들어 적용하면 좋다.
- 클러스터 외부의 소프트웨어가 api 서버와 통신할 때도 활용할 수 있다.
- 이건 자동화된 파이프라인을 구축할 때 활용되는 패턴이다.
- 파드가 api 서버와 통신해서 어떤 데이터를 얻어와야 하는 상황에서 유용하다.
- 클러스터의 서명을 받아 클러스터 외부와 통신
- 프라이빗 레지스트리로부터 이미지를 받기 위한 시크릿 정보[2]
서비스 어카운트 토큰
서비스 어카운트는 API 서버에 인증될 때 서비스 어카운트 토큰이라는 것을 사용한다.
이 토큰은 API 서버에서 만들어 제공되는 JWT 토큰으로, 서비스어카운트는 통신 시 헤더에 Authorization: Bearer <token>
와 같은 식으로 사용한다.
그래서 청중 대상이 있고, 만료 시간이 있고, 유효 시간이 있는 것이다.
API 서버는 서비스 어카운트 토큰으로 들어온 요청에 대해 다음의 인증 과정을 거친다.
- 토큰 서명
- 토큰 만료 여부
- 토큰 요청 출처 오브젝트 유효성 확인
- 토큰이 현재 유효한지 확인
- 청중이 유효한지
보통 서비스어카운트는 파드에 부여되는 개념이긴 하다.
그러나 엄밀하게 파드에서 사용될 것으로 용도가 국한된 것은 절대 아니다.
이 토큰은 여러 리소스에 부착될 수 있다.
- 파드 - 볼륨으로 마운팅된다.
- 시크릿 - 시크릿에 토큰이 들어가는 방식으로, [[#시크릿 주입]] 참고.
- 노드 - 노드에 대해 할당되는 토큰으로, 노드가 삭제되면 자동 회수된다.
구성
그렇다면 이 토큰은 어떻게 구성돼있을까?
{
# 청중
"aud": [
"https://my-audience.example.com"
],
# 만료시간
"exp": 1729605240,
# 발급된 시간
"iat": 1729601640,
# 발급 주체
"iss": "https://my-cluster.example.com",
# 토큰 고유 식별자
"jti": "aed34954-b33a-4142-b1ec-389d6bbb4936",
# 토큰이 유효화되는 시작 시간. Not BeFore
"nbf": 1729601640,
# 토큰의 주체
"sub": "system:serviceaccount:my-namespace:my-serviceaccount"
# 해당 토큰에 대해 추가적으로 넣어주는 정보
"kubernetes.io": {
"namespace": "my-namespace",
"serviceaccount": {
"name": "my-serviceaccount",
"uid": "14ee3fa4-a7e2-420f-9f9a-dbc4507c3798"
},
# 노드와 파드 정보는 이 토큰이 파드에 마운팅됐을 때만 들어간다.
"node": {
"name": "my-node",
"uid": "646e7c5e-32d6-4d42-9dbd-e504e6cbe6b1"
},
"pod": {
"name": "my-pod",
"uid": "5e0bd49b-f040-43b0-99b7-22765a53f7f3"
},
},
}
토큰의 클레임 부분은 이렇게 생겼다.
(Kubernetes v1.33 - Octarine 기준)
토큰 생성
서비스 어카운트 리소스를 만든다고 그냥 토큰이 갑자기 뿅 생기는 게 아니다.
크게는 두 가지 방식이 있는데, 한 쪽은 레거시 방식이라 추천되지 않는다.
TokenRequest API
1.22 버전 이후, 서비스 어카운트 토큰 생성은 TokenRequest API를 통해 이뤄진다.
POST /api/v1/namespaces/{namespace}/serviceaccounts/{name}/token
이건 말 그대로 서비스 어카운트의 토큰을 발행하는 API로, kubectl create token {SA}
와 같은 식으로도 만들 수 있다.
이 토큰은 수명이 있어 만기되면 알아서 무효화되는데, 직접 api를 쏴서 받은 토큰을 알아서 갱신해야 한다.
다른 방식으로는 시크릿에 토큰을 주입하는 방식이다.
파드에 볼륨으로 마운팅된다는 토큰이 바로 이 API를 활용한다.[3]
volumes:
- name: kube-api-access-<random-suffix>
projected:
sources:
- serviceAccountToken:
expirationSeconds: 3607
path: token
파드 스펙에 .spec.serviceAccountName
을 넣어주면 api 서버에서는 해당 서비스 어카운트에 대한 토큰을 발급한 후 projected 볼륨으로 마운팅해서 넣어준다.
조금 더 구체적으로 토큰이 부여되는 과정을 파헤쳐보자.
- 파드 생성 요청에 서비스 어카운트 어드미션 컨트롤러가 작동한다.
- 서비스어카운트 필드 주입 관련
spec.serviceAccountName
이 명시돼있지 않다면, default 서비스 어카운트를 넣어준다.- 없는 서비스어카운트를 넣었거나 default 서비스어카운트를 넣으려 했는데 없으면 요청을 거절한다.
- 토큰 필드 주입 관련
- 서비스 어카운트 스펙에
.spec.automountServiceAccountToken: false
라면 여기에서 작업 끝. - 아니라면 projected 볼륨 필드에 서비스 어카운트 토큰 필드를 작성한다.
- 모든 컨테이너
/var/run/secrets/kubernetes.io/token
경로로 해당 볼륨을 마운팅시킨다.
- 서비스 어카운트 스펙에
- 이미지 풀 시크릿 관련
- 파드 스펙에
spec.imagePullSecrets
가 세팅돼있지 않다면 이것도 서비스어카운트로부터 값을 넣어준다.
- 파드 스펙에
- 서비스어카운트 필드 주입 관련
- 파드가 노드에 배치되면 kubelet이 작동한다.
- kubelet은 api서버에 TokenRequest API를 날려서 실제 토큰을 받아와 컨테이너에 주입해준다.
- 토큰은 수명이 있는데 이건 kubelet이 추적해서 필요할 때마다 자동으로 갱신돼 반영되도록 도와준다.
참고로 1.22버전 이전에는 수명을 가지지 않은 토큰을 넣어줬다고 한다.
시크릿 주입
apiVersion: v1
kind: Secret
metadata:
name: test
annotations:
kubernetes.io/service-account.name: test
type: kubernetes.io/service-account-token
서비스 어카운트 토큰을 담은 시크릿을 만드는 식으로 생성하는 것도 가능하다!
위처럼 시크릿을 만들면 해당 시크릿은 서비스 어카운트 토큰을 담은 채로 생성된다.
그러나 이 방식은 레거시로, 매우 위험하다.
왜냐하면 이렇게 생성된 토큰은 TokenRequest API를 거치지 않은 이전의 방식으로 생성되며 수명이 존재하지 않는 영구 토큰이기 때문이다.
1.24 버전까지는 서비스 어카운트를 생성하면 이 시크릿이 자동으로 만들어졌다고 한다.
클러스터 외부에서는 이렇게 지속시간이 긴 토큰이 필요할 수도 있을 것이다.
그럴 때 최후의 방법 정도로 고려해보자.
차라리 서비스 어카운트 인증이 아니라 다른 인증 방식을 사용하는 게 대부분의 경우에서 더 나은 선택일 것이다.
인증 웹훅을 쓰거나, 잘 보호된 개인키와 인증서를 써도 되고, 굳이 서비스 어카운트 토큰을 쓰고 싶으면 주기적으로 TokenRequest API를 사용하는 로직을 넣던가 하는 게 좋다.
apiVersion: v1
kind: ServiceAccount
metadata:
name: build-robot
namespace: default
secrets:
- name: build-robot-secret
참고로 이렇게 서비스 어카운트를 생성할 때 같이 만들어지는 시크릿을 명시하는 방식도 가능하다.[4]
토큰 검증
직접 토큰의 유효성을 검증하고 싶은 너드들은 TokenReview API를 쓰면 된다.[5]
아니면 OIDC 디스커버리를 쓰는 것도 방법이다.
전자가 추천되는데, 파드나 다른 것에 바인딩된 토큰이 알아서 무효화되기 때문이다.
가령 파드에서 서아 토큰을 볼륨으로 둔 상태에서 이를 지우면, 토큰 리뷰는 바로 실패해준다.
oidc는 토큰의 만기 시간에 맞춰서 유효하다고 답을 내릴 가능성이 높다.
어플리케이션은 반드시 여기에서 항상 청중을 정의해야 한다.
그리고 그 청중이 토큰과 맞아야 한다.
이건 토큰의 영역을 최소화해준다.
그래서 앱에서만 사용될 수 있도록 해줄 것이다.
대안
토큰을 다른 매커니즘으로 발행하고, 웹훅 토큰 인증 과정을 통해 베어러 토큰으로 쓸 수도 있다.
파드의 고유 신원을 제공해주는 방법도 있다.
https://cert-manager.io 나, Istio같은 서비스 메쉬를 쓰는 것도 방법이다.
api서버가 oidc을 허용하게 하는 방법이 있다.
iam으로 하는 방법도 있다.
CertificateSigningRequest api를 이용하는 방법도 있다.
kubelet에서 프라이빗 레지스트리에서 접근할 때는 아예 처음부터 증명서 설정을 박아넣는 방법이 있다.
https://kubernetes.io/docs/tasks/administer-cluster/kubelet-credential-provider/#configuring-the-kubelet
Trusted Platform Module을 위해 디바이스 플러그인을 쓸 수있다.
관련 문서
이름 | noteType | created |
---|---|---|
ServiceAccount | knowledge | 2025-01-13 |
T-각 네임스페이스 서비스 어카운트에 권한 바인딩 | topic/temp | 2025-03-16 |
T-서비스 어카운트 토큰은 어떻게 인증되는가 | topic/temp | 2025-03-16 |
참고
annotations:
kubernetes.io/enforce-mountable-secrets: "true"
서비스 어카운트에 이런 어노테이션을 붙일 수 있다.
과거엔 이렇게 하면 서비스 어카운트의 시크릿이 파드에 마운팅되지 못하도록 할 수 있었다.
근데 지금은 마운팅이 싫다면 그냥 다른 네임스페이스로 격리할 것이 권장된다.
https://kubernetes.io/docs/reference/access-authn-authz/service-accounts-admin/ ↩︎
https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#add-imagepullsecrets-to-a-service-account ↩︎
https://kubernetes.io/docs/reference/kubernetes-api/authentication-resources/token-request-v1/ ↩︎
https://kubernetes.io/docs/reference/access-authn-authz/service-accounts-admin/#auto-generated-legacy-serviceaccount-token-clean-up ↩︎
https://kubernetes.io/docs/reference/kubernetes-api/authentication-resources/token-review-v1/ ↩︎