동적 스토리지 지연 바인딩을 사용하면 요청 시 실행 중인 사전 워밍된 Google Kubernetes Engine (GKE) 에이전트 샌드박스 포드에 Filestore 에이전트 볼륨을 직접 삽입할 수 있습니다. 표준 Kubernetes 볼륨 연결 수명 주기를 우회함으로써 동적 지연 바인딩은 포드 재시작 없이 100ms 미만의 스토리지 연결 지연 시간을 달성합니다.
이 아키텍처를 사용하면 고밀도, 지연 시간 감소 에이전트 플랫폼에서 다음을 수행할 수 있습니다.
- 포드 콜드 스타트 및 컨테이너 초기화 지연을 없앱니다.
- 주문형으로 영구 작업공간을 동적으로 연결하고 분리합니다.
- 유휴 에이전트 세션을 일시중지하거나 최대 절전 모드로 전환하고 파일 시스템 상태를 유지하면서 사용 가능한 사전 워밍 샌드박스 포드에서 다시 시작합니다.
시작하기 전에
- Filestore 에이전트 볼륨용 GKE 환경 설정에서 초기 설정을 완료합니다.
- GKE 클러스터가 버전
1.36.0-gke.3302001이상을 실행하는지 확인합니다. 이 버전은 gVisoremptyDir마운트 전파에 필요한force-shared주석을 지원합니다. volume-pool-scStorageClass가volumeBindingMode: Immediate및reclaimPolicy: Delete를 지정하는지 확인합니다.
아키텍처 개요
지연 바인딩 아키텍처는 다음 네 가지 구성요소로 구성됩니다.
- 플랫폼 오케스트레이터 또는 커스텀 컨트롤러: 세션 수명 주기를 관리하는 컨트롤 플레인 서비스 또는 Kubernetes 컨트롤러입니다.
SandboxClaim이벤트를 감시하고, 테넌트 볼륨 메타데이터를 확인하고, 스토리지 노드 데몬 API를 호출하여 스토리지를 바인딩하거나 바인딩 해제하고, 삭제 종료자를 관리합니다. - 스토리지 노드 데몬: 각 gVisor 노드에서 실행되며 마운트 API를 노출하는 권한 있는
DaemonSet입니다. force-shared가 포함된SandboxTemplate: 호스트 마운트가 gVisor 샌드박스 컨테이너에 동적으로 전파되도록 하는 gVisor 샌드박스 포드 템플릿입니다.SandboxWarmPool: 클레임 시 즉시 마운트 요청을 수신할 준비가 된 사전 워밍된 실행 중인 샌드박스 포드의 풀입니다.
스토리지 노드 데몬 배포
권한이 있는 DaemonSet이 포함된 storage-node-daemon.yaml이라는 매니페스트를 만듭니다.
매니페스트를 적용합니다.
kubectl apply -f storage-node-daemon.yaml
SandboxTemplate 및 SandboxWarmPool 배포
템플릿과 웜 풀이 포함된 sandbox-latebind.yaml이라는 매니페스트를 만듭니다.
매니페스트를 적용합니다.
kubectl apply -f sandbox-latebind.yaml
샌드박스를 요청하고 스토리지를 동적으로 바인딩
삭제 종료자(
agent.sandbox/storage-cleanup)가 포함된 클레임 매니페스트(late-bind-claim.yaml)를 만듭니다.apiVersion: extensions.agents.x-k8s.io/v1alpha1 kind: SandboxClaim metadata: name: late-bind-session-1 namespace: default finalizers: - agent.sandbox/storage-cleanup spec: sandboxTemplateRef: name: late-bind-template클레임을 적용합니다.
kubectl apply -f late-bind-claim.yaml
agent-volume-pvc.yaml라는 PVC 매니페스트를 사용하여 볼륨을 동적으로 프로비저닝합니다.apiVersion: v1 kind: PersistentVolumeClaim metadata: name: session-1-pvc namespace: default spec: accessModes: [ReadWriteMany] storageClassName: volume-pool-sc resources: requests: storage: 1GiPVC를 적용합니다.
kubectl apply -f agent-volume-pvc.yaml
할당된 포드 UID, 노드, 지원 PV 내보내기 세부정보를 가져옵니다.
POD_NAME=$(kubectl get pods \ -l extensions.agents.x-k8s.io/claimed-by=late-bind-session-1 \ -o jsonpath='{.items[0].metadata.name}') POD_UID=$(kubectl get pod "${POD_NAME}" -o jsonpath='{.metadata.uid}') NODE_NAME=$(kubectl get pod "${POD_NAME}" -o jsonpath='{.spec.nodeName}') PV_NAME=$(kubectl get pvc session-1-pvc -o jsonpath='{.spec.volumeName}') NFS_IP=$(kubectl get pv "${PV_NAME}" \ -o jsonpath='{.spec.csi.volumeAttributes.ip}') NFS_PATH="/$(kubectl get pv "${PV_NAME}" \ -o jsonpath='{.spec.csi.volumeAttributes.volume}')"포드 호스트의 노드 데몬에 마운트 신호를 보냅니다.
DAEMON_POD=$(kubectl get pods -l app=storage-node-daemon \ --field-selector spec.nodeName="${NODE_NAME}" \ -o jsonpath='{.items[0].metadata.name}') kubectl exec "${DAEMON_POD}" -c daemon -- python3 -c " import urllib.request, json payload = json.dumps({ 'action': 'bind_nfs', 'pod_uid': '${POD_UID}', 'volume_name': 'workspace-volume', 'sub_dir': 'user_data', 'nfs_server': '${NFS_IP}', 'nfs_path': '${NFS_PATH}' }).encode() req = urllib.request.Request( 'http://localhost:9090', data=payload, headers={'Content-Type': 'application/json'}) print(urllib.request.urlopen(req).read().decode()) "호스트 마운트가 활성 상태인 동안 조기 정리되지 않도록 클레임된 포드에 삭제 파이널라이저를 적용합니다.
kubectl patch pod "${POD_NAME}" --type=merge \ -p '{"metadata":{"finalizers":["agent.sandbox/storage-cleanup"]}}'실행 중인 포드 내에서 볼륨 마운트를 확인합니다.
kubectl logs "${POD_NAME}" -c agent출력에서 볼륨이 성공적으로 마운트되었음을 확인할 수 있습니다.
Waiting for late-bind signal... Filestore volume mounted successfully! drwxr-xr-x 2 1000 1000 4096 ... user_data
세션 일시중지 및 재개
상담사 세션이 종료되거나 최대 절전 모드로 전환되면 오케스트레이터는 Kubernetes가 포드를 종료하거나 재활용하도록 허용하기 전에 호스트 스토리지를 마운트 해제해야 합니다.
백그라운드에서
SandboxClaim삭제를 시작합니다. 파이널라이저로 인해 Kubernetes는 클레임을 삭제로 표시하지만 포드 종료를 일시중지합니다.kubectl delete sandboxclaim late-bind-session-1 --wait=false
호스트 노드에서 NFS 공유를 마운트 해제합니다.
kubectl exec "${DAEMON_POD}" -c daemon -- python3 -c " import urllib.request, json payload = json.dumps({ 'action': 'unbind', 'pod_uid': '${POD_UID}', 'volume_name': 'workspace-volume', 'sub_dir': 'user_data' }).encode() req = urllib.request.Request( 'http://localhost:9090', data=payload, headers={'Content-Type': 'application/json'}) print(urllib.request.urlopen(req).read().decode()) "SandboxClaim과 포드 모두에서 종료자를 삭제하여 종료를 완료합니다.kubectl patch sandboxclaim late-bind-session-1 --type=merge \ -p '{"metadata":{"finalizers":[]}}' kubectl patch pod "${POD_NAME}" --type=merge \ -p '{"metadata":{"finalizers":[]}}'
나중에 세션을 재개하려면 미리 준비된 새 포드를 요청하고 기존 PVC (session-1-pvc)를 사용하여 바인딩 요청을 전송합니다. 새 샌드박스 포드는 보존된 워크스페이스 상태에 즉시 액세스할 수 있습니다.
프로덕션 고려사항 및 맞춤 컨트롤러 설계
이 문서의 수동 명령어는 동적 지연 바인딩의 하위 수준 메커니즘을 보여줍니다. 프로덕션에서 이 아키텍처를 안정적으로 실행하려면 애플리케이션의 세션 수명 주기에 맞게 맞춤 Kubernetes 컨트롤러 또는 플랫폼 오케스트레이터를 개발해야 합니다.
프로덕션 컨트롤러와 노드 데몬을 설계할 때는 다음 아키텍처 패턴을 구현하세요.
조정 및 파이널라이저 수명 주기 자동화
스토리지 노드 데몬은 표준 Kubernetes 컨테이너 스토리지 인터페이스 (CSI) 수명 주기 관리 외부에서 호스트 수준 마운트를 실행하므로 Kubelet은 포드의 emptyDir 내 활성 마운트를 인식하지 못합니다. 마운트가 활성 상태일 때 포드가 삭제되면 Kubelet이 emptyDir 디렉터리를 삭제하지 못하고 Device or resource busy 오류가 발생하여 포드가 Terminating 상태로 멈춥니다.
맞춤 컨트롤러는 종료자(예: agent.sandbox/storage-cleanup)를 사용하여 엄격한 상태 머신을 자동화해야 합니다.
- 클레임 생성 및 바인딩:
- 생성 시 모든
SandboxClaim에 정적 종료자를 연결합니다. SandboxClaim상태 업데이트를 위해 Kubernetes API를 모니터링합니다. 클레임이 웜 풀 포드에 바인딩되면 할당된pod_uid,nodeName, 지원 Filestore 볼륨 속성을 추출합니다.- 인증된
bind요청을 타겟 노드에서 실행되는 스토리지 노드 데몬에 전송합니다. - 실행 중인
Pod객체를 즉시 패치하여 동적 종료자를 추가합니다.SandboxTemplate사양은 정적 포드 파이널라이저를 지원하지 않으므로 포드가 삭제되지만SandboxClaim은 활성 상태로 유지되는 노드 드레인 또는 재예약 이벤트 중에 포드를 보호하려면 포드를 동적으로 패치해야 합니다.
- 생성 시 모든
- 단계적 종료 및 제거 처리:
SandboxClaim및Pod리소스 모두에서deletionTimestamp를 감시합니다.- 삭제 또는 퇴출이 감지되면 노드 데몬의
unbind엔드포인트를 호출하여 호스트 디렉터리 (umount -l)를 완전히 마운트 해제합니다. Pod및SandboxClaim에 패치를 적용하여 최종자를 삭제하기 전에 마운트 해제가 성공했고 보류 중인 모든 쓰기가 플러시되었는지 확인합니다. 이를 통해 정상적인 클레임 삭제, GKE 노드 업그레이드, 스팟 VM 선점, 메모리 부족 (OOM) 종료 전반에서 완전한 해체를 달성할 수 있습니다.
스토리지 노드 데몬 보안 및 강화
- 인증된 API로
kubectl exec대체: 프로덕션에서는kubectl exec를 사용하거나 데몬을localhost에 바인딩하지 마세요. 상호 TLS (mTLS) 또는 KubernetesServiceAccount토큰 인증으로 보호되는 클러스터 네트워크를 통해 전용 gRPC 또는 HTTPS 엔드포인트를 노출하도록 스토리지 노드 데몬을 구성합니다. - 데몬 네임스페이스 및 네트워크 액세스 격리: 제한된 관리 네임스페이스(예:
sandbox-storage-system)에 권한이 있는storage-node-daemonDaemonSet를default또는 테넌트 네임스페이스가 아닌 제한된 관리 네임스페이스에 배포합니다. 커스텀 컨트롤러 포드에서만 데몬 API에 대한 수신을 허용하고 샌드박스 처리된 에이전트 포드에서 발생하는 모든 트래픽을 차단하는 KubernetesNetworkPolicy규칙을 적용합니다. - 사전 빌드된 컨테이너 이미지 사용:
initContainer에서 런타임에nfs-common와 같은 패키지를 설치하지 마세요. 필요한 모든 마운트 유틸리티가 사전 설치된 변경 불가 사전 빌드 컨테이너 이미지를 사용하여 노드 시작 지연과 외부 저장소 종속 항목을 없앱니다.
스토리지 할당량 및 멀티 테넌트 격리 적용
- 에이전트별 스토리지 사용량 모니터링: 공유
ReadWriteMany(RWX) Filestore 볼륨의 하위 디렉터리를emptyDir에 동적으로 바인드 마운트하면 표준 KubernetesemptyDir.sizeLimit설정이 마운트된 NFS 경로에 에이전트별 스토리지 할당량을 적용할 수 없습니다. 단일 비정상 에이전트가 공유 볼륨을 소진하여 서비스 거부 (DoS)를 일으키지 않도록 오케스트레이터에서 디렉터리 할당량 모니터링을 구현하거나 볼륨 풀을 사용하여 전용 볼륨을 프로비저닝하세요. - 다양한 작업공간 액세스 모드에 맞게 마운트 페이로드 조정: 컨트롤러는 노드 데몬에 전송되는 매개변수를 다양하게 지정하여 여러 에이전트 저장소 토폴로지를 지원할 수 있습니다.
- 비공개 격리된 작업공간: 읽기-쓰기 권한이 있는 단일 샌드박스 포드에 고유한 테넌트 하위 디렉터리 또는 전용 PVC를 바인딩합니다.
- 협업 작업 공간: 실시간 파일 공유를 위해 여러 조정 에이전트 포드에서 동일한 공유 RWX 하위 디렉터리를 동시에 바인딩합니다.
- 탐색 분기 작업공간: 에이전트가 골든 카피를 수정하지 않고 공유 애셋을 읽을 수 있도록 기본 템플릿 디렉터리를 읽기 전용 (
ro)으로 마운트하는 동시에 새 쓰기를 별도의 쓰기 가능한 스크래치 경로 또는 복원 시 복사 디렉터리로 라우팅합니다.
특정 시점 스냅샷 및 정리 조정
- 스냅샷 전에 쓰기 정지 달성: 데이터 손상 없이 일관된 시점의 워크스페이스 스냅샷을 캡처하려면 오케스트레이터가 워크스페이스 디렉터리를 보관하거나 Filestore 스냅샷을 트리거하기 전에 바인딩 해제 워크플로를 시작하여 활성 쓰기를 일시중지해야 합니다 (또는 파일 시스템 버퍼를 플러시).
- 테넌트 프로비저닝 해제 자동화: 사용자 세션 또는 워크스페이스가 영구적으로 만료되면 컨트롤러가 먼저 모든 노드에서 활성 마운트를 바인딩 해제한 후 비동기 백그라운드 작업을 실행하여 지원 볼륨에서 테넌트의 영구 디렉터리를 삭제해야 합니다.
동적 종료자 관리, 멀티 테넌트 격리, 스냅샷 복원 워크플로를 보여주는 전체 참조 구현은 GitHub의 GKE Sandbox 지연 바인딩 스토리지 예를 참고하세요.
다음 단계
- 정적 에이전트 샌드박스 통합을 살펴봅니다.
- 자체 관리형 GKE 워크로드를 배포합니다.
- 볼륨 풀을 만들고 관리하는 방법을 알아봅니다.