투영 볼륨
투영 볼륨 (Projected Volumes)
이 문서는 쿠버네티스의 투영 볼륨(projected volumes) 을 설명해요. 볼륨에 대한 기본 지식을 권장해요.
소개 (Introduction)
projected 볼륨은 여러 기존 볼륨 소스를 같은 디렉터리에 매핑해요.
현재 다음 유형의 볼륨 소스를 투영할 수 있어요.
secretdownwardAPIconfigMapserviceAccountTokenclusterTrustBundlepodCertificate
모든 소스는 Pod와 같은 네임스페이스에 있어야 해요. 자세한 내용은 올인원 볼륨(all-in-one volume) 설계 문서를 참고하세요.
secret, downwardAPI, configMap을 사용한 예시 구성
apiVersion: v1
kind: Pod
metadata:
name: volume-test
spec:
containers:
- name: container-test
image: busybox:1.28
command: ["sleep", "3600"]
volumeMounts:
- name: all-in-one
mountPath: "/projected-volume"
readOnly: true
volumes:
- name: all-in-one
projected:
sources:
- secret:
name: mysecret
items:
- key: username
path: my-group/my-username
- downwardAPI:
items:
- path: "labels"
fieldRef:
fieldPath: metadata.labels
- path: "cpu_limit"
resourceFieldRef:
containerName: container-test
resource: limits.cpu
- configMap:
name: myconfigmap
items:
- key: config
path: my-group/my-config
각 투영 볼륨 소스는 스펙의 sources 아래에 나열돼요. 파라미터는 두 가지 예외를 제외하면 거의 같아요.
- 시크릿의 경우
secretName필드가 ConfigMap 명명과 일관성을 유지하도록name으로 바뀌었어요. defaultMode는 투영(projected) 수준에서만 지정할 수 있고 각 볼륨 소스별로는 지정할 수 없어요. 하지만 위에서 보듯이 각 개별 투영에 대해mode를 명시적으로 설정할 수는 있어요.
serviceAccountToken 투영 볼륨
현재 서비스 어카운트의 토큰을 지정된 경로의 Pod에 주입할 수 있어요. 예를 들어:
apiVersion: v1
kind: Pod
metadata:
name: sa-token-test
spec:
containers:
- name: container-test
image: busybox:1.28
command: ["sleep", "3600"]
volumeMounts:
- name: token-vol
mountPath: "/service-account"
readOnly: true
serviceAccountName: default
volumes:
- name: token-vol
projected:
sources:
- serviceAccountToken:
audience: api
expirationSeconds: 3600
path: token
이 예시 Pod는 주입된 서비스 어카운트 토큰을 포함하는 투영 볼륨을 가져요. 이 Pod의 컨테이너는 그 토큰을 사용해 Pod의 ServiceAccount의 정체성으로 인증하며 쿠버네티스 API 서버에 접근할 수 있어요. audience 필드는 토큰의 의도된 수신자(audience)를 포함해요. 토큰의 수신자는 토큰의 audience에 지정된 식별자로 자신을 식별해야 하고, 그렇지 않으면 토큰을 거부해야 해요. 이 필드는 선택 사항이며 기본값은 API 서버의 식별자예요.
expirationSeconds는 서비스 어카운트 토큰의 유효 기간 예상치예요. 기본값은 1시간이고 최소 10분(600초)이어야 해요. 관리자는 API 서버의 --service-account-max-token-expiration 옵션을 지정해 최대값을 제한할 수도 있어요. path 필드는 투영 볼륨의 마운트 지점까지의 상대 경로를 지정해요.
참고:
투영 볼륨 소스를
subPath볼륨 마운트로 사용하는 컨테이너는 그 볼륨 소스에 대한 업데이트를 받지 못해요.
clusterTrustBundle 투영 볼륨
기능 상태: Kubernetes v1.33 [beta](기본 비활성화)
참고:
쿠버네티스 1.36에서 이 기능을 사용하려면
ClusterTrustBundle기능 게이트와--runtime-config=certificates.k8s.io/v1beta1/clustertrustbundles=truekube-apiserver 플래그로 ClusterTrustBundle 객체 지원을 활성화한 다음,ClusterTrustBundleProjection기능 게이트를 활성화해야 해요.
clusterTrustBundle 투영 볼륨 소스는 하나 이상의 ClusterTrustBundle 객체의 내용을 컨테이너 파일시스템의 자동 업데이트되는 파일로 주입해요.
ClusterTrustBundle은 이름이나 서명자 이름(signer name)으로 선택할 수 있어요.
이름으로 선택하려면 name 필드를 사용해 단일 ClusterTrustBundle 객체를 지정해요.
서명자 이름으로 선택하려면 signerName 필드(선택적으로 labelSelector 필드)를 사용해 주어진 서명자 이름을 사용하는 ClusterTrustBundle 객체 집합을 지정해요. labelSelector가 없으면 해당 서명자의 모든 ClusterTrustBundle이 선택돼요.
kubelet은 선택된 ClusterTrustBundle 객체의 인증서를 중복 제거하고, PEM 표현을 정규화하며(주석과 헤더를 버리고), 인증서를 재정렬한 뒤 path가 가리키는 파일에 기록해요. 선택된 ClusterTrustBundle 집합이나 그 내용이 바뀌면 kubelet이 파일을 최신 상태로 유지해요.
기본적으로 kubelet은 이름이 지정된 ClusterTrustBundle을 찾지 못하거나, signerName/labelSelector가 어떤 ClusterTrustBundle과도 일치하지 않으면 Pod가 시작되는 것을 막아요. 이 동작이 원하는 게 아니라면 optional 필드를 true로 설정하세요. 그러면 Pod가 path에 빈 파일을 두고 시작돼요.
apiVersion: v1
kind: Pod
metadata:
name: sa-ctb-name-test
spec:
containers:
- name: container-test
image: busybox
command: ["sleep", "3600"]
volumeMounts:
- name: token-vol
mountPath: "/root-certificates"
readOnly: true
serviceAccountName: default
volumes:
- name: token-vol
projected:
sources:
- clusterTrustBundle:
name: example
path: example-roots.pem
- clusterTrustBundle:
signerName: "example.com/mysigner"
labelSelector:
matchLabels:
version: live
path: mysigner-roots.pem
optional: true
podCertificate 투영 볼륨
기능 상태: Kubernetes v1.35 [beta](기본 비활성화)
참고:
쿠버네티스 1.36에서 Pod 인증서 지원을 사용하려면
PodCertificateRequest기능 게이트와--runtime-config=certificates.k8s.io/v1beta1/podcertificaterequests=truekube-apiserver 플래그를 활성화해야 해요.
podCertificate 투영 볼륨 소스는 Pod가 클라이언트나 서버 자격 증명으로 사용할 개인 키와 X.509 인증서 체인을 안전하게 프로비저닝해요. kubelet은 개인 키와 인증서 체인이 만료에 가까워지면 새로고침을 처리해요. 애플리케이션은 inotify나 폴링 같은 메커니즘으로 파일이 바뀔 때 신속하게 다시 로드하기만 하면 돼요.
각 podCertificate 투영은 다음 구성 필드를 지원해요.
signerName: 인증서를 발급하길 원하는 서명자(signer)예요. 서명자는 자체 접근 요구사항이 있을 수 있고 Pod에 인증서 발급을 거부할 수도 있다는 점을 유의하세요.keyType: 생성해야 하는 개인 키의 유형이에요. 유효한 값은ED25519,ECDSAP256,ECDSAP384,ECDSAP521,RSA3072,RSA4096이에요.maxExpirationSeconds: Pod에 발급된 인증서에 대해 수용할 최대 수명이에요. 설정하지 않으면86400(24시간)이 기본값이에요. 최소3600(1시간), 최대7862400(91일)이어야 해요. 쿠버네티스 내장 서명자는 최대 수명86400(1일)으로 제한돼요. 서명자는 지정한 것보다 짧은 수명의 인증서를 발급할 수 있어요.credentialBundlePath: 자격 증명 번들이 기록되어야 하는 투영 내의 상대 경로예요. 자격 증명 번들은 PEM 형식 파일로, 첫 블록은 PKCS#8 직렬화된 개인 키를 포함하는 "PRIVATE KEY" 블록이고 나머지 블록은 인증서 체인(리프 인증서와 중간 인증서)을 구성하는 "CERTIFICATE" 블록이에요.keyPath와certificateChainPath: kubelet이 개인 키나 인증서 체인만 기록해야 하는 별도의 경로예요.userAnnotations: 서명자 구현에 추가 정보를 전달할 수 있는 맵이에요. kubelet이 생성하는 PodCertificateRequest 객체의spec.unverifiedUserAnnotations필드에 그대로 복사돼요. 항목은 객체 메타데이터 애노테이션과 동일한 검증을 받으며, 추가로 모든 키가 도메인 접두사로 시작해야 해요. 값에는 필드 전체의 전체 크기 제한을 제외하고는 제한이 없어요. 이런 기본 검증 외에 API 서버는 추가 검증을 수행하지 않아요. 서명자 구현은 이 데이터를 소비할 때 매우 신중해야 해요. 서명자는 적절한 검증 단계를 먼저 수행하지 않고 이 데이터를 본질적으로 신뢰하면 안 돼요. 서명자는 자신이 지원하는 키와 값을 문서화해야 해요. 서명자는 인식하지 못하는 키를 포함한 요청을 거부해야 해요.
참고:
대부분의 애플리케이션은 호환성 이유로 키와 인증서를 별도 파일로 필요로 하지 않는 한
credentialBundlePath를 사용하는 걸 선호해야 해요. kubelet은 투영하는 파일을 열 때 이전 내용이나 새 내용을 읽도록 보장하기 위해 심볼릭 링크 기반의 원자적 쓰기 전략을 사용해요. 하지만 키와 인증서 체인을 별도 파일에서 읽으면, kubelet이 첫 읽기와 두 번째 읽기 사이에 자격 증명을 교체해서 애플리케이션이 일치하지 않는 키와 인증서를 로드할 수 있어요.
# ED25519 개인 키와 `coolcert.example.com/foo` 서명자의 인증서를 요청하고
# 결과를 `/var/run/my-x509-credentials/credentialbundle.pem`에 기록하는
# podCertificate 투영을 사용하는 샘플 Pod 스펙.
apiVersion: v1
kind: Pod
metadata:
namespace: default
name: podcertificate-pod
spec:
serviceAccountName: default
containers:
- image: debian
name: main
command: ["sleep", "infinity"]
volumeMounts:
- name: my-x509-credentials
mountPath: /var/run/my-x509-credentials
volumes:
- name: my-x509-credentials
projected:
defaultMode: 0644
sources:
- podCertificate:
keyType: ED25519
signerName: coolcert.example.com/foo
credentialBundlePath: credentialbundle.pem
userAnnotations:
example.com/annotation1: "value1"
example.com/annotation2: "value2"
SecurityContext 상호작용
투영 서비스 어카운트 볼륨 개선의 파일 권한 처리에 관한 제안서는 투영 파일이 올바른 소유자 권한을 갖도록 설정하는 것을 도입했어요.
Linux
투영 볼륨이 있고 Pod SecurityContext에 RunAsUser가 설정된 Linux Pod에서는, 투영 파일이 컨테이너 사용자 소유권을 포함해 올바른 소유권을 갖게 돼요.
Pod의 모든 컨테이너가 PodSecurityContext나 컨테이너 SecurityContext에 같은 runAsUser를 설정했다면, kubelet은 serviceAccountToken 볼륨의 내용이 그 사용자가 소유하도록 보장하고, 토큰 파일의 권한 모드는 0600으로 설정해요.
참고:
Pod가 생성된 후 추가된 임시 컨테이너(ephemeral containers)는 Pod가 생성될 때 설정된 볼륨 권한을 변경하지 않아요.
Pod의 serviceAccountToken 볼륨 권한이 다른 모든 컨테이너가 같은 runAsUser를 가지기 때문에 0600으로 설정된 경우, 임시 컨테이너는 토큰을 읽으려면 같은 runAsUser를 사용해야 해요.
Windows
투영 볼륨이 있고 Pod SecurityContext에 RunAsUsername이 설정된 Windows Pod에서는, Windows에서 사용자 어카운트가 관리되는 방식 때문에 소유권이 강제되지 않아요. Windows는 로컬 사용자·그룹 어카운트를 Security Account Manager(SAM)라는 데이터베이스 파일에 저장하고 관리해요. 각 컨테이너는 자체 SAM 데이터베이스 인스턴스를 유지하는데, 호스트는 컨테이너가 실행 중인 동안 그 데이터베이스에 접근할 수 없어요. Windows 컨테이너는 OS의 사용자 모드 부분을 호스트와 격리된 상태로 실행하도록 설계되어 있기 때문에 가상 SAM 데이터베이스를 유지하는 거예요. 그 결과 호스트에서 실행되는 kubelet은 가상화된 컨테이너 어카운트에 대해 호스트 파일 소유권을 동적으로 구성할 능력이 없어요. 호스트 머신의 파일을 컨테이너와 공유해야 한다면 C:\ 밖의 자체 볼륨 마운트에 배치하는 것이 권장돼요.
기본적으로 투영 파일은 예시 투영 볼륨 파일에 대해 보여진 것처럼 다음 소유권을 가져요:
PS C:\> Get-Acl C:\var\run\secrets\kubernetes.io\serviceaccount\..2021_08_31_22_22_18.318230061\ca.crt | Format-List
Path : Microsoft.PowerShell.Core\FileSystem::C:\var\run\secrets\kubernetes.io\serviceaccount\..2021_08_31_22_22_18.318230061\ca.crt
Owner : BUILTIN\Administrators
Group : NT AUTHORITY\SYSTEM
Access : NT AUTHORITY\SYSTEM Allow FullControl
BUILTIN\Administrators Allow FullControl
BUILTIN\Users Allow ReadAndExecute, Synchronize
Audit :
Sddl : O:BAG:SYD:AI(A;ID;FA;;;SY)(A;ID;FA;;;BA)(A;ID;0x1200a9;;;BU)
이것은 ContainerAdministrator 같은 모든 관리자 사용자가 읽기·쓰기·실행 접근을 갖고, 비관리자 사용자는 읽기·실행 접근을 갖는다는 뜻이에요.
참고:
일반적으로 컨테이너에 호스트에 대한 접근 권한을 부여하는 것은 잠재적 보안 악용의 문을 열 수 있으므로 권장되지 않아요.
SecurityContext에 RunAsUser를 넣은 Windows Pod를 만들면 Pod가 ContainerCreating 상태에 영원히 갇히게 돼요. 따라서 Linux 전용인 RunAsUser 옵션을 Windows Pod와 함께 사용하지 않는 것이 좋아요.