EFS PVC Setup Guide
Ohouse 플랫폼에서 새로운 서비스에 EFS 기반 PVC를 붙이는 end-to-end 가이드.
agentic-ai-platform + aap-coordinator 적용 사례를 예시로 설명.
전체 그림
EFS PVC 하나를 Pod에 붙이려면 4개 레포가 협업해야 한다. 각 레포는 서로 다른 레이어를 담당.
| 레포 | 역할 | 도구 |
|---|---|---|
bucket-iac |
AWS EFS filesystem + Kubernetes StorageClass 생성 | Terraform |
infra-helm |
PVC 정의 Helm chart | Helm |
ohouse-cloud |
위 chart를 어느 클러스터·네임스페이스에 배포할지 ArgoCD Application으로 선언 | ArgoCD |
ohouse-helm-manifest |
앱 Deployment에서 PVC를 마운트 (extraVolumes / extraVolumeMounts) |
Helm |
배포 순서도 위 표 순서 그대로. 아래 단계에서 건너뛰면 다음 단계 리소스가 Pending에 빠진다.
Step 1. bucket-iac — EFS + StorageClass 생성
Terraform의 efs_sc_set 모듈에 서비스 key를 추가하면 모듈이 자동으로 efs-sc-<key> 이름의 StorageClass를 만든다.
1-1. EFS filesystem 등록
bucket-iac/admin/efs.tf:
module "efs" {
# ...
for_each = {
# ...
agentic-ai-platform = { ... }
}
}
output "agentic_ai_platform_efs" {
value = module.efs["agentic-ai-platform"].efs_id
}
1-2. StorageClass 등록
bucket-iac/eks/mgmt/{dev,prod}.tf:
efs_sc_set = {
agentic-ai-platform = {
efs_id = local.agentic_ai_platform_efs
}
}
결과: 클러스터에 efs-sc-agentic-ai-platform StorageClass 생성.
(key에 환경 접미사가 없으면 dev/prod 모두 동일한 이름)
Step 2. infra-helm — PVC chart 생성
infra-helm/<service>-components/ chart로 PVC를 선언. SC는 이미 bucket-iac에서 만들었으므로 chart에서는 PVC만 정의한다.
디렉토리 구조
infra-helm/agentic-ai-platform-components/
├── Chart.yaml
├── dev.values.yaml
├── prod.values.yaml
└── templates/pvc-efs.yaml
Chart.yaml
apiVersion: v2
name: agentic-ai-platform-components
version: "1.0.0"
description: Infrastructure components for agentic-ai-platform (EFS)
type: application
templates/pvc-efs.yaml
{{- if .Values.efs.enabled -}}
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: {{ .Values.efs.pvcName }}
namespace: {{ .Release.Namespace }}
spec:
storageClassName: {{ .Values.efs.storageClassName }}
accessModes:
- ReadWriteMany
resources:
requests:
storage: {{ .Values.efs.size }}
{{- end }}
dev.values.yaml / prod.values.yaml
efs:
enabled: true
storageClassName: efs-sc-agentic-ai-platform
pvcName: agentic-ai-platform-efs-pvc
size: 100Gi
참고: EFS는 실제 용량 제한이 없다.
resources.requests.storage값은 PVC 스펙 필수 필드라 넣을 뿐 실질적 제한이 되지 않음. 관례적으로 100Gi 정도.
commerce-rca-components는 SC도 chart에서 만드는 레거시 패턴. 새 chart는 SC 만들지 말 것. SC는 bucket-iac 단일 소스.
Step 3. ohouse-cloud — ArgoCD Application 선언
infra-helm chart를 클러스터에 배포하라고 ArgoCD에 지시하는 Application YAML.
파일 위치
ohouse-cloud/argocd-root/infra/templates/application/apps/<service>-components-{dev,prod}.yaml
디렉토리 선택 기준:
apps/: 일반 서비스용 (기본)database/: DB 성격 서비스 (postgres/mysql/mariadb)platform/: 플랫폼 공통 인프라
agentic-ai-platform-components-dev.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: 'agentic-ai-platform-components-dev'
annotations:
{{- if .Values.notiChannel }}
notifications.argoproj.io/subscribe.on-deployed.slack: {{ .Values.notiChannel }}
notifications.argoproj.io/subscribe.on-sync-failed.slack: {{ .Values.notiChannel }}
notifications.argoproj.io/subscribe.on-health-degraded.slack: {{ .Values.notiChannel }}
{{- end }}
spec:
project: {{ .Values.project }}
source:
repoURL: 'https://github.com/bucketplace/infra-helm.git'
path: agentic-ai-platform-components
targetRevision: master
helm:
releaseName: agentic-ai-platform-components
valueFiles:
- dev.values.yaml
destination:
namespace: apps
name: mgmt-dev-infra-eks
syncPolicy:
syncOptions:
- CreateNamespace=true
Prod는 destination.name과 valueFiles만 교체
name: mgmt-prod-infra-eksvalueFiles: [prod.values.yaml]
구조 원칙: 이 레포는 ApplicationSet을 쓰지 않고, dev/prod 각각 Application YAML을 복붙한다. 레포 전체가 이 패턴.
Step 4. ohouse-helm-manifest — 앱에서 PVC 마운트
앱의 app-values.yaml에 extraVolumes + extraVolumeMounts를 추가한다.
aap-coordinator/app-values.yaml
extraVolumeMounts:
- name: "sessions"
mountPath: "/home/aap/sessions"
extraVolumes:
- name: "sessions"
persistentVolumeClaim:
claimName: "agentic-ai-platform-efs-pvc"
- 하나의 PVC를 여러 앱이 동시에 마운트 가능 (
ReadWriteMany) mountPath는 앱이 기대하는 경로. 앱이 non-root 유저면 해당 유저 홈 하위가 안전
extraVolumes vs extraVolumeMounts
같은 Pod spec이지만 다른 레벨을 건드린다.
| 구분 | 대상 레벨 | 쓰임 |
|---|---|---|
extraVolumes |
Pod (spec.volumes[]) |
Pod가 어떤 저장소를 소유하는지 선언 (PVC/configMap/secret/emptyDir) |
extraVolumeMounts |
Container (spec.containers[].volumeMounts[]) |
그 저장소를 컨테이너 내부 어느 경로에 붙일지 |
렌더링 결과
spec:
template:
spec:
containers:
- name: aap-coordinator
volumeMounts: # extraVolumeMounts
- name: sessions
mountPath: /home/aap/sessions
volumes: # extraVolumes
- name: sessions
persistentVolumeClaim:
claimName: agentic-ai-platform-efs-pvc
왜 분리돼 있나
Pod에 container가 여러 개(sidecar 등) 있을 때 같은 volume을 각 container가 다른 경로에 마운트할 수 있어야 해서. Volume은 Pod가 소유, Mount는 Container가 선언. 실사용은 대부분 1:1이니 name만 맞춰서 쌍으로 추가한다고 생각하면 된다.
Root / Non-root 컨테이너 고려사항
Root 실행 (USER 지시어 없는 Dockerfile)
- EFS Access Point UID/GID 무관하게 read/write 가능
securityContext에 아무것도 안 넣어도 동작- 단, 컨테이너 보안 모범사례에 어긋남
Non-root 실행 (권장)
Dockerfile에 유저 추가:
FROM alpine:3.21 AS app
# ...
RUN adduser -D -u 1000 aap
COPY --from=builder /bin/${SERVICE} /bin/${SERVICE}
USER aap
WORKDIR /home/aap
CMD /bin/${SERVICE} server
COPY는USER aap이전에 (aap는/bin에 쓰기 권한 없음)WORKDIR /home/aap로 설정 →$HOME자동 매핑- EFS 마운트 경로도
/home/aap/하위로 두면 권한 이슈 최소화
EFS Access Point UID 불일치 시
첫 배포 후 컨테이너 들어가서 확인:
kubectl exec -it <pod> -n apps -- ls -la /home/aap/sessions
소유자 UID가 컨테이너 유저와 다르면 securityContext.fsGroup으로 맞춘다:
podSecurityContext:
fsGroup: 1000
배포 순서 (중요)
bucket-iac (SC)
└─ infra-helm (PVC chart)
└─ ohouse-cloud (ArgoCD Application)
└─ ohouse-helm-manifest (extraVolumes)
앞 단계가 ArgoCD로 sync되어 리소스가 실제 클러스터에 생성된 뒤에 다음 단계를 merge할 것. 역순으로 merge되면 Pod가 PVC를 찾지 못해 Pending에 빠진다.
검증 커맨드
# SC 존재 확인
kubectl get sc efs-sc-agentic-ai-platform
# PVC Bound 상태 확인
kubectl get pvc -n apps agentic-ai-platform-efs-pvc
# PVC provisioning 이벤트 확인
kubectl describe pvc -n apps agentic-ai-platform-efs-pvc
# 마운트 확인
kubectl exec -it <pod> -n apps -- df -h | grep efs
kubectl exec -it <pod> -n apps -- ls -la /home/aap/sessions
참고 사례
| 패턴 | chart | 특징 |
|---|---|---|
librechat-components |
PVC만 chart에서 생성 (SC는 bucket-iac) | 권장 패턴 |
commerce-rca-components |
SC + PVC 모두 chart에서 생성 | 레거시, bucket-iac 도입 전 방식 |
bucket-runner |
persistentVolumeClaims 리스트로 여러 PVC 동시 선언 |
여러 PVC 필요한 경우 |