파드 내부의 배치 잡이 Kubernetes API를 호출하려는데 401 Unauthorized가 발생합니다. 운영팀은 사람 계정과 파드가 사용하는 ServiceAccount를 구분해야 권한을 안전하게 부여할 수 있습니다. ServiceAccount는 워크로드가 클러스터와 통신할 때 쓰는 신원입니다.
ServiceAccount — 파드의 K8s API 접근 권한
운영팀에서 배포 자동화 도구를 개발한다고 상상해보세요. 이 도구는 파드 안에서 실행되면서 다른 Deployment의 레플리카 수를 동적으로 조정해야 합니다. 아무 설정 없이 K8s API를 호출하면 403 Forbidden이 돌아옵니다. 단순히 파드가 실행 중이라는 사실만으로는 클러스터를 제어할 권한이 없기 때문입니다. 여기에 더해, AWS S3 버킷에서 설정 파일을 내려받아야 한다면 어떻게 할까요? 예전 방식처럼 Access Key를 Secret에 넣어두면 키가 노출될 위험이 있습니다. ServiceAccount는 파드에 클러스터 내부 ID를 부여하고, IRSA는 그 ID를 AWS IAM 권한과 연결하여 이 두 문제를 모두 해결합니다. 이 모듈에서는 ServiceAccount의 동작 원리부터 실무에서 자주 쓰는 IRSA 패턴까지 단계적으로 익힙니다.
- 1ServiceAccount의 역할과 default 계정의 한계를 설명할 수 있다
- 2ServiceAccount 토큰의 자동 마운트 경로와 구조를 설명할 수 있다
- 3전용 ServiceAccount를 생성하고 RBAC를 연결할 수 있다
- 4automountServiceAccountToken으로 공격 표면을 최소화할 수 있다
- 5IRSA(IAM Roles for Service Accounts)의 원리를 이해하고 설정할 수 있다
- 6파드 내부에서 K8s API를 직접 호출할 수 있다
kubectl cluster-infokubectl get serviceaccountkubectl get sa default -o yamlkubectl create namespace sa-demoServiceAccount 토큰이 파드에 마운트되는 구조
K8s는 파드가 생성될 때 ServiceAccount 토큰을 /var/run/secrets/kubernetes.io/serviceaccount/ 경로에 자동으로 마운트합니다. 이 디렉토리에는 세 파일이 있습니다.
# 파드 안에서 실행
ls /var/run/secrets/kubernetes.io/serviceaccount/
# ca.crt namespace token
# token: JWT 형태의 ServiceAccount 토큰
cat /var/run/secrets/kubernetes.io/serviceaccount/token
# eyJhbGciOiJSUzI1NiIsImtpZCI6Ii...
# namespace: 현재 파드가 속한 네임스페이스
cat /var/run/secrets/kubernetes.io/serviceaccount/namespace
# default
# ca.crt: K8s API 서버의 TLS 인증서 검증용 CA
ls -la /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
이 토큰을 Bearer 헤더에 넣고 API 서버 주소로 요청을 보내면 K8s API를 호출할 수 있습니다.
파드가 API를 호출하면 실제로 무슨 일이 — 인증부터 인가까지 5단계
파드 안에서 curl로 K8s API를 부르면 401이 날 때도, 403이 날 때도 있는데 이 둘은 완전히 다른 단계에서 막힌 것입니다. 토큰이 만들어져 마운트되고 → 파드가 그 토큰을 제시하고 → API server가 토큰을 검증하고(인증) → RBAC이 권한을 확인하는(인가) 전체 경로를 단계로 보면, 401("너 누구야"가 실패)과 403("누군진 알겠는데 권한 없음")을 바로 구분해 고칠 수 있습니다. 앞에서 본 토큰 파일 세 개(token·ca.crt·namespace)가 이 흐름의 어디서 쓰이는지도 함께 보입니다.
[ServiceAccount 생성] kubectl create sa → 클러스터에 SA 신원 등록
│
① kubelet이 파드 생성 시 projected 토큰(JWT)을 발급받아
│ /var/run/secrets/.../token 에 마운트 (기본 1시간, 자동 갱신)
│
② 파드 앱이 API 호출 시 그 토큰을 Authorization: Bearer 헤더에 실음
│
③ API server가 토큰 검증(인증) — 서명·발급자(iss)·만료(exp) 확인
│ → 통과하면 "너는 system:serviceaccount:<ns>:<sa>"로 신원 확정
│
④ RBAC 인가 — 그 신원이 요청한 동사·리소스에 Role/RoleBinding이 있는지
│
⑤ 허용 → 요청 실행 후 결과 반환
▼
[결과] 200/201(허용) · 401(인증 실패) · 403(인가 실패)
각 단계에서 무슨 일이 일어나고, 막히면 어떤 증상인가:
| 단계 | 하는 일 | 여기서 막히면 |
|---|---|---|
| ① 토큰 마운트 | kubelet이 SA의 projected JWT를 발급해 /var/run/secrets/kubernetes.io/serviceaccount/token에 마운트 | automountServiceAccountToken: false거나 마운트 경로 없음 → 토큰 자체가 없어 호출 불가 |
| ② 토큰 제시 | 앱이 토큰을 Authorization: Bearer 헤더에 넣어 API server로 요청 | 앱이 시작 시 토큰을 캐시했다가 만료된 값 사용 → 401 (매 요청 파일에서 다시 읽거나 공식 SDK 사용) |
| ③ 인증(서명·발급자) | API server가 토큰의 서명·iss·exp를 검증해 신원 확정 | 토큰 만료·서명 불일치 → 401 Unauthorized (신원 증명 실패) |
| ④ 인가(RBAC) | 확정된 신원에 대해 요청한 동사·리소스의 Role/RoleBinding 존재 여부 확인 | 바인딩 없음·default SA라 권한 없음 → 403 Forbidden (cannot list ... 메시지) |
| ⑤ 실행·응답 | 인가 통과 시 요청을 실행하고 결과 반환 | default SA에 과한 권한을 바인딩해두면 파드 침해 시 그 권한만큼 횡적 이동 가능 |
즉 401과 403을 헷갈리지 않는 것이 이 흐름의 핵심입니다 — 401은 ②③(토큰·인증) 문제라 토큰 만료·마운트를 보고, 403은 ④(RBAC) 문제라 kubectl auth can-i <동사> <리소스> --as=system:serviceaccount:<ns>:<sa>로 어떤 권한이 빠졌는지 좁힙니다. 403 에러 메시지의 cannot <동사> resource ... 문구가 곧 Role에 추가해야 할 정확한 권한입니다.
전용 ServiceAccount 생성과 RBAC 연결
파드 안에서 Kubernetes API를 호출하는 도구를 배포할 때, 아무 ServiceAccount 설정 없이 실행하면 default 계정이 사용됩니다. default 계정은 기본적으로 권한이 거의 없어 403이 반환됩니다. 그렇다고 cluster-admin을 부여하면 이 파드가 탈취됐을 때 클러스터 전체가 위험해집니다. 전용 ServiceAccount를 만들고 필요한 최소 권한만 Role로 정의해 바인딩하는 것이 올바른 방법입니다. 파드별로 독립된 신원을 부여하면 사고 발생 시 피해 범위도 해당 Role로 제한됩니다.
API를 호출해야 하는 파드는 반드시 전용 ServiceAccount를 만들고, 필요한 권한만 Role로 묶어 바인딩해야 합니다.
확대
# serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: deployment-scaler
namespace: sa-demo
annotations:
description: "Deployment 레플리카 수 조정용 서비스 계정"
---
# role.yaml — 필요한 최소 권한만 부여
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: deployment-scaler-role
namespace: sa-demo
rules:
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "patch", "update"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"]
---
# rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: deployment-scaler-binding
namespace: sa-demo
subjects:
- kind: ServiceAccount
name: deployment-scaler
namespace: sa-demo
roleRef:
kind: Role
name: deployment-scaler-role
apiGroup: rbac.authorization.k8s.io
kubectl apply -f serviceaccount.yaml -f role.yaml -f rolebinding.yaml
# 파드에 ServiceAccount 지정
kubectl run scaler-pod \
--image=curlimages/curl:latest \
--overrides='{"spec":{"serviceAccountName":"deployment-scaler"}}' \
--namespace=sa-demo \
--command -- sleep 3600
# 파드 안에서 K8s API 호출 테스트
kubectl exec -it scaler-pod -n sa-demo -- sh
# 토큰 원문을 출력하거나 로그에 남기지 않습니다. 필요하면 아래처럼 변수로만 사용합니다.
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
APISERVER="https://${KUBERNETES_SERVICE_HOST}:${KUBERNETES_SERVICE_PORT}"
CA="/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
# Deployment 목록 조회
curl -s --cacert $CA \
-H "Authorization: Bearer $TOKEN" \
"$APISERVER/apis/apps/v1/namespaces/sa-demo/deployments" | jq '.items[].metadata.name'
automountServiceAccountToken: false — 불필요한 토큰 제거
보안 감사에서 "모든 파드에 ServiceAccount 토큰이 마운트되어 있고, 그 중 일부는 K8s API를 전혀 사용하지 않음에도 불구하고 토큰이 노출되어 있습니다"라는 지적이 나왔습니다. 컨테이너 취약점을 통해 공격자가 파드에 접근하면 /var/run/secrets/kubernetes.io/serviceaccount/token을 바로 읽어 클러스터 API 호출에 악용할 수 있습니다. 사용하지 않는 토큰은 처음부터 마운트하지 않는 것이 가장 확실한 방어입니다.
K8s API를 호출하지 않는 파드에는 토큰을 마운트하지 않는 것이 보안 모범 사례입니다. 컨테이너가 탈취되어도 공격자가 클러스터 API를 악용할 수 없습니다.
확대
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-frontend
namespace: sa-demo
spec:
replicas: 2
selector:
matchLabels:
app: web-frontend
template:
metadata:
labels:
app: web-frontend
spec:
# 토큰 마운트 비활성화 — K8s API 호출 없는 파드
automountServiceAccountToken: false
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
kubectl apply -f web-frontend.yaml
# 토큰 마운트 여부 확인
kubectl exec -it $(kubectl get pod -n sa-demo -l app=web-frontend -o name | head -1) \
-n sa-demo -- ls /var/run/secrets/ 2>&1
# ls: cannot access '/var/run/secrets/': No such file or directory
# ← 토큰이 없어서 디렉토리 자체가 존재하지 않음
IRSA — ServiceAccount로 AWS 권한 얻기
EKS에서 파드가 S3, DynamoDB, Secrets Manager 같은 AWS 리소스에 접근해야 할 때 IRSA를 사용합니다. 노드 전체에 IAM 역할을 붙이는 방식과 달리 ServiceAccount 단위로 최소 권한을 부여합니다.
# 1. EKS OIDC 제공자 확인
aws eks describe-cluster --name my-cluster \
--query "cluster.identity.oidc.issuer" --output text
# https://oidc.eks.ap-northeast-2.amazonaws.com/id/EXAMPLE1234
# 2. IAM OIDC 제공자 등록 (클러스터당 1회)
eksctl utils associate-iam-oidc-provider \
--cluster my-cluster \
--approve
# 3. IAM 역할 생성 (S3 읽기 권한)
eksctl create iamserviceaccount \
--cluster my-cluster \
--namespace sa-demo \
--name s3-reader \
--attach-policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess \
--approve
# eksctl이 자동 생성하는 ServiceAccount
# (수동으로 생성하는 경우 아래와 같이 annotation 추가)
apiVersion: v1
kind: ServiceAccount
metadata:
name: s3-reader
namespace: sa-demo
annotations:
# IAM 역할 ARN 연결
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/sa-demo-s3-reader
# IRSA를 사용하는 파드
apiVersion: v1
kind: Pod
metadata:
name: s3-reader-pod
namespace: sa-demo
spec:
serviceAccountName: s3-reader
containers:
- name: aws-cli
image: amazon/aws-cli:latest
command: ["sleep", "3600"]
env:
# AWS SDK가 자동으로 토큰을 읽는 환경 변수 (EKS가 자동 주입)
- name: AWS_ROLE_ARN
valueFrom:
fieldRef:
fieldPath: metadata.annotations['eks.amazonaws.com/role-arn']
- name: AWS_WEB_IDENTITY_TOKEN_FILE
value: /var/run/secrets/eks.amazonaws.com/serviceaccount/token
# 파드 안에서 S3 접근 테스트 (자격증명 없이 동작)
kubectl exec -it s3-reader-pod -n sa-demo -- \
aws s3 ls s3://my-config-bucket/
# 2024-01-15 09:30:00 1234 app-config.yaml
실습: 파드 내부에서 K8s API 직접 호출
ServiceAccount 토큰을 사용해 K8s API를 직접 호출하고, RBAC 권한이 어떻게 동작하는지 확인합니다.
# 1. 실습 환경 준비
kubectl create namespace api-test
# 권한이 없는 SA로 API 호출 시도
kubectl run no-perm-pod \
--image=curlimages/curl:latest \
--namespace=api-test \
--command -- sleep 3600
kubectl exec -it no-perm-pod -n api-test -- sh -c '
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
APISERVER="https://${KUBERNETES_SERVICE_HOST}:${KUBERNETES_SERVICE_PORT}"
CA="/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
curl -s --cacert $CA -H "Authorization: Bearer $TOKEN" \
"$APISERVER/api/v1/namespaces/api-test/pods"
'
예상 결과 (권한 없을 때):
{
"kind": "Status",
"apiVersion": "v1",
"status": "Failure",
"message": "pods is forbidden: User \"system:serviceaccount:api-test:default\" cannot list resource \"pods\" in API group \"\" in the namespace \"api-test\"",
"reason": "Forbidden",
"code": 403
}
# 2. Pod 읽기 권한 부여
cat <<EOF | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-reader
namespace: api-test
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: default-pod-reader
namespace: api-test
subjects:
- kind: ServiceAccount
name: default
namespace: api-test
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
EOF
# 3. 다시 API 호출 — 이제 성공
kubectl exec -it no-perm-pod -n api-test -- sh -c '
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
APISERVER="https://${KUBERNETES_SERVICE_HOST}:${KUBERNETES_SERVICE_PORT}"
CA="/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
curl -s --cacert $CA -H "Authorization: Bearer $TOKEN" \
"$APISERVER/api/v1/namespaces/api-test/pods" | python3 -m json.tool | grep '"name"'
'
# "name": "no-perm-pod"
# 4. 권한 확인 명령어
kubectl auth can-i list pods \
--as=system:serviceaccount:api-test:default \
--namespace=api-test
# yes
kubectl auth can-i delete pods \
--as=system:serviceaccount:api-test:default \
--namespace=api-test
# no
K8s 1.21 이후 ServiceAccount 토큰은 기본적으로 시간 제한이 있는 Projected Token으로 발급됩니다(기본 1시간). 오래 실행되는 파드에서 초기화 이후 한참이 지나 토큰을 읽으면 만료된 토큰을 사용해 401 오류가 발생합니다.
# 증상: 파드가 장시간 실행 후 K8s API 호출 실패
kubectl logs api-caller-pod
# Error: API call failed: 401 Unauthorized
# time="2024-01-15T08:00:00Z" token_expired=true
# 원인 진단: 토큰 만료 시각 확인
kubectl exec -it api-caller-pod -- sh -c '
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
# JWT 페이로드 디코딩 (base64)
echo $TOKEN | cut -d. -f2 | base64 -d 2>/dev/null | python3 -m json.tool
'
# {
# "aud": ["https://kubernetes.default.svc"],
# "exp": 1705298400, ← Unix 타임스탬프 (이미 만료됨)
# "iat": 1705294800,
# "iss": "https://oidc.eks...",
# }
# 해결책 1: 토큰을 캐시하지 말고 매번 파일에서 새로 읽기
# 잘못된 방식 (시작 시 한 번만 읽음)
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
# 올바른 방식 (매 요청마다 새로 읽음)
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
# 해결책 2: K8s 공식 SDK 사용 — 토큰 갱신 자동 처리
# Python: kubernetes 라이브러리가 자동으로 토큰을 갱신합니다
pip install kubernetes
# from kubernetes import client, config
# config.load_incluster_config() # 클러스터 내부에서 자동 설정
# 해결책 3: 토큰 만료 시간 연장 (보안상 권장하지 않음)
# ServiceAccount에 TokenRequest API로 장기 토큰 발급
kubectl create token deployment-scaler \
--namespace=sa-demo \
--duration=24h
근본 원인: Projected Service Account Token(K8s 1.21+)은 kubelet이 자동으로 갱신하지만, 애플리케이션이 토큰을 메모리에 캐시하면 오래된 토큰을 계속 사용합니다. K8s 공식 클라이언트 라이브러리(Go k8s.io/client-go, Python kubernetes)는 이 갱신을 자동으로 처리하므로 가능하면 직접 curl 대신 공식 SDK를 사용하세요.
심화 — annotation은 달았는데 왜 AWS는 거부할까
심화: IRSA는 어드미션 웹훅과 신뢰 정책이 맞물려야 동작한다
IRSA를 "SA에 annotation만 달면 되는 것"으로 이해하면, 정작 annotation이 무시되거나 AWS가 거부하는 순간 원인을 못 찾습니다. IRSA는 K8s와 AWS 두 층이 파드 생성 시점에 맞물려 돌아가는 구조입니다.
파드가 생성되면 EKS Pod Identity 뮤테이팅 어드미션 웹훅이 그 파드의 ServiceAccount를 봅니다. SA에 eks.amazonaws.com/role-arn annotation이 있으면, 웹훅이 파드에 세 가지를 주입합니다.
AWS_ROLE_ARN환경변수 — assume할 IAM 역할.AWS_WEB_IDENTITY_TOKEN_FILE환경변수 — projected 토큰의 경로.- audience가
sts.amazonaws.com인 projected service account token 볼륨.
그러면 AWS SDK가 이 토큰으로 STS AssumeRoleWithWebIdentity를 호출하고, STS는 토큰의 발급자(iss)를 클러스터의 IAM OIDC 제공자와 대조한 뒤, 대상 IAM 역할의 신뢰 정책(trust policy) 조건을 검사합니다 — sub가 system:serviceaccount:<네임스페이스>:<SA이름>과 같고 aud가 sts.amazonaws.com인지. 여기서 두 가지 실무 함정이 나옵니다.
- 주입은 파드 admission 시점에만 일어난다: 그래서 실행 중인 SA에 annotation을 나중에 달면 기존 파드엔 소급되지 않습니다. 파드는 주입이 없어 SDK가 노드 인스턴스 역할로 폴백하고, 겉보기엔 조용히 "잘못된 권한"으로 돕니다. 반드시 파드를 재생성해야 합니다.
- 신뢰 정책 조건이 정확히 맞아야 한다:
sub의 네임스페이스·SA 이름이 실제와 한 글자라도 다르거나aud가 어긋나면, K8s 쪽은 완벽해도 STS가AccessDenied를 돌려줍니다.
상황: EKS에서 파드가 S3를 읽어야 해 SA에 eks.amazonaws.com/role-arn annotation을 달았습니다. 그런데 파드는 여전히 AccessDenied를 받거나, aws sts get-caller-identity를 찍어 보면 의도한 역할이 아니라 노드의 인스턴스 IAM 역할이 나옵니다.
원인: 두 갈래입니다. (1) annotation을 실행 중인 파드에 나중에 달았고 파드를 재생성하지 않았다면, 뮤테이팅 웹훅이 주입할 기회가 없어 SDK가 노드 역할로 폴백합니다 — 이 경우 get-caller-identity에 노드 역할이 찍힙니다. (2) 주입은 됐는데(환경변수가 보임) IAM 역할 신뢰 정책의 sub·aud 조건이 실제 네임스페이스·SA와 불일치하면 STS가 토큰을 거부해 AccessDenied가 납니다.
진단: 먼저 파드 안에서 env | grep AWS_ROLE_ARN으로 주입 여부를 봅니다. 없으면 (1) — 파드를 재생성합니다. 있으면 (2) — aws sts get-caller-identity의 에러가 AssumeRoleWithWebIdentity의 AccessDenied인지 확인하고, IAM 역할 신뢰 정책의 조건을 실제 system:serviceaccount:<네임스페이스>:<SA이름>, aud=sts.amazonaws.com과 대조합니다.
해결: (1)이면 kubectl rollout restart로 파드를 재생성해 웹훅 주입을 유발합니다(annotation은 SA에, 파드는 그 SA를 쓰도록). (2)이면 IAM 역할 신뢰 정책의 StringEquals(또는 StringLike) 조건을 올바른 네임스페이스·SA·audience로 수정합니다. K8s 토큰 만료(앞 TroubleCase)와 달리, 이 문제는 K8s가 아니라 AWS 신뢰 관계에서 풀린다는 점을 구분하는 것이 핵심입니다(RBAC(Role-based Access Control) 기반 다중 사용자 보안).
시나리오: 모니터링 에이전트를 파드로 배포하는 상황
SRE 팀 막내로서 "클러스터 내 모든 파드의 상태를 수집해 Slack에 보내는 모니터링 에이전트를 배포해줘"라는 요청을 받았습니다. 에이전트가 K8s API로 파드 목록을 조회하고 AWS Secrets Manager에서 Slack Webhook URL을 가져와야 합니다.
# 1. 필요 권한 목록 작성 (최소 권한 원칙)
# K8s: 클러스터 전체 파드 읽기 (ClusterRole 필요)
# AWS: Secrets Manager 특정 시크릿 읽기
# 2. ClusterRole 생성 (네임스페이스 경계 없이 파드 조회)
cat <<EOF | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: pod-monitor
rules:
- apiGroups: [""]
resources: ["pods", "nodes"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments", "replicasets"]
verbs: ["get", "list"]
EOF
# 3. ServiceAccount + IRSA 생성
eksctl create iamserviceaccount \
--cluster production \
--namespace monitoring \
--name pod-monitor-sa \
--attach-policy-arn arn:aws:iam::123456789012:policy/SlackSecretReadOnly \
--approve
# 4. ClusterRoleBinding
kubectl create clusterrolebinding pod-monitor-binding \
--clusterrole=pod-monitor \
--serviceaccount=monitoring:pod-monitor-sa
# 5. 배포
kubectl run pod-monitor \
--image=myrepo/pod-monitor:v1.2 \
--namespace=monitoring \
--serviceaccount=pod-monitor-sa
실무 포인트: ClusterRole은 필요한 경우에만 사용하고, 가능하면 네임스페이스 범위의 Role을 선호합니다. 모니터링 에이전트처럼 클러스터 전체를 봐야 하는 경우가 ClusterRole의 정당한 사용 사례입니다. IRSA를 사용하면 Access Key 없이도 AWS 리소스에 안전하게 접근할 수 있습니다.
핵심 요약
| 개념 | 명령/설정 | 실무 사용 빈도 |
|---|---|---|
| SA 조회 | kubectl get sa | 자주 |
| 전용 SA 생성 | kubectl create sa <name> | 서비스 배포 시 |
| 토큰 수동 발급 | kubectl create token <sa> | 디버깅 시 |
| 토큰 마운트 경로 | /var/run/secrets/kubernetes.io/serviceaccount/ | API 호출 시 |
| 토큰 비활성화 | automountServiceAccountToken: false | 일반 파드에 |
| IRSA 설정 | eks.amazonaws.com/role-arn annotation | EKS + AWS 연동 |
| 권한 확인 | kubectl auth can-i <verb> <resource> --as=system:serviceaccount:<ns>:<sa> | RBAC 디버깅 |
전용 ServiceAccount 생성 및 확인
kubectl create namespace sa-demo
kubectl create serviceaccount deployment-scaler -n sa-demo
kubectl get serviceaccount -n sa-demo예상 출력
NAME SECRETS AGE default 0 5s deployment-scaler 0 2s
Role 및 RoleBinding 생성
kubectl create role deployment-scaler-role \
--verb=get,list,patch,update \
--resource=deployments \
-n sa-demo
kubectl create rolebinding deployment-scaler-binding \
--role=deployment-scaler-role \
--serviceaccount=sa-demo:deployment-scaler \
-n sa-demo예상 출력
role.rbac.authorization.k8s.io/deployment-scaler-role created rolebinding.rbac.authorization.k8s.io/deployment-scaler-binding created
ServiceAccount 권한 확인
kubectl auth can-i list deployments \
--as=system:serviceaccount:sa-demo:deployment-scaler \
-n sa-demo
kubectl auth can-i delete pods \
--as=system:serviceaccount:sa-demo:deployment-scaler \
-n sa-demo예상 출력
yes no
파드에 ServiceAccount 지정 후 토큰 마운트 확인
kubectl run scaler-pod \
--image=curlimages/curl:latest \
--serviceaccount=deployment-scaler \
--namespace=sa-demo \
--command -- sleep 3600
kubectl exec -it scaler-pod -n sa-demo -- ls /var/run/secrets/kubernetes.io/serviceaccount/예상 출력
ca.crt namespace token
- kubectl get sa -n <namespace> 에서 생성한 ServiceAccount가 목록에 있어야 함 — 없으면 kubectl apply 또는 create 명령이 올바른 네임스페이스에 실행됐는지 확인
- kubectl exec -it <pod> -- ls /var/run/secrets/kubernetes.io/serviceaccount/ 에서 ca.crt, namespace, token 세 파일이 있으면 토큰 정상 마운트. 디렉토리가 없으면 automountServiceAccountToken: false 설정 확인
- kubectl auth can-i list pods --as=system:serviceaccount:<ns>:<sa> -n <ns> 결과가 yes 이면 권한 바인딩 성공, no 이면 RoleBinding 또는 ClusterRoleBinding 누락
- 403 Forbidden 발생 시: kubectl exec 에서 API 호출 후 에러 메시지에 포함된 "cannot <verb> resource" 부분이 Role에 추가해야 할 정확한 권한 — verbs와 resources 매핑 확인
- IRSA 동작 확인: kubectl exec -it <pod> -- aws sts get-caller-identity 에서 Arn 필드에 role-arn annotation의 IAM 역할이 보이면 IRSA 정상. AssumeRole 에러면 OIDC 제공자 등록 또는 Trusted Relationship 설정 오류
- 조합 해석 — 401 Unauthorized + 토큰 파일은 있음 → 토큰 만료 가능성 높음. token 파일 내 JWT payload의 exp 필드(base64 디코딩)를 확인하고, K8s 공식 SDK로 전환하여 자동 갱신 처리
명령어·단축키 빠른 참조
이 모듈에서 파드 신원(SA)·토큰·IRSA를 다룰 때 쓴 kubectl/aws 명령을 모았습니다.
| 명령어/단축키 | 용도 | 자주 쓰는 예 |
|---|---|---|
kubectl get sa | ServiceAccount 목록 조회 | kubectl get sa -n <ns> |
kubectl create serviceaccount | 전용 SA 생성 | kubectl create serviceaccount deployment-scaler -n <ns> |
kubectl create token | SA 토큰 수동 발급(디버깅) | kubectl create token <sa> -n <ns> --duration=24h |
kubectl auth can-i --as | SA 실효 권한 확인 | kubectl auth can-i list pods --as=system:serviceaccount:<ns>:<sa> -n <ns> |
kubectl run --serviceaccount | SA 지정해 파드 실행 | kubectl run p --image=curlimages/curl --serviceaccount=<sa> --command -- sleep 3600 |
kubectl exec … ls | 토큰 마운트 여부 확인 | kubectl exec -it X -- ls /var/run/secrets/kubernetes.io/serviceaccount/ |
env | grep AWS_ROLE_ARN | IRSA 웹훅 주입 여부 확인 | 값이 없으면 파드 재생성 필요(주입은 생성 시점에만) |
aws sts get-caller-identity | IRSA 역할 assume 검증 | kubectl exec -it X -- aws sts get-caller-identity (Arn 확인) |
kubectl rollout restart | annotation 후 웹훅 재주입 | kubectl rollout restart deployment/X |
JWT exp 디코딩 | 토큰 만료 확인(401) | … token | cut -d. -f2 | base64 -d 후 exp 확인 |
관련 모듈로 더 깊이:
- RBAC(Role-based Access Control) 기반 다중 사용자 보안 — ServiceAccount에 권한을 부여하는 Role/RoleBinding의 작동 원리
- NetworkPolicy를 활용한 특정 파드 간 통신 차단 및 제한 — 파드 신원을 기반으로 네트워크 경계까지 보안을 확장하는 법
- Pending, Running, Failed, CrashLoopBackOff 생명주기 분석 — 토큰 마운트와 API 통신이 파드 시작 단계에서 일어나는 시점
다음 모듈 network-policy에서는 ServiceAccount에서 정의한 신원(ID)을 기반으로 파드 간 네트워크 트래픽을 제어하는 방법을 다룹니다. Ingress/Egress 규칙으로 허용된 서비스끼리만 통신하게 만들고, Deny-All 정책으로 보안 경계를 구성하는 실습을 진행합니다.