S3를 사용한 영구 객체 스토리지
이전 단계에서 이미지 객체를 위한 스테이징 디렉터리를 생성하고, 이미지 자산을 다운로드하고, S3 버킷에 업로드하여 환경을 준비했습니다. 또한 Mountpoint for Amazon S3 CSI 드라이버를 설치하고 구성했습니다. 이제 Mountpoint for Amazon S3 CSI 드라이버가 제공하는 Persistent Volume(PV)을 사용하도록 Pod를 연결하여 수평 스케일링과 Amazon S3로 백업되는 영구 스토리지를 갖춘 이미지 호스트 애플리케이션을 생성하는 목표를 완료하겠습니다.
먼저 Persistent Volume을 생성하고 배포에서 ui 컨테이너를 수정하여 이 볼륨을 마운트하겠습니다.
먼저 s3pvclaim.yaml 파일을 검토하여 매개변수와 구성을 이해하겠습니다:
apiVersion: v1
kind: PersistentVolume
metadata:
name: s3-pv
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteMany
mountOptions:
- allow-delete
- allow-other
- uid=1000
- gid=1000
- region=$AWS_REGION
csi:
driver: s3.csi.aws.com
volumeHandle: s3-csi-driver-volume
volumeAttributes:
bucketName: $BUCKET_NAME
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: s3-claim
namespace: ui
spec:
accessModes:
- ReadWriteMany
storageClassName: ""
resources:
requests:
storage: 1Gi
volumeName: s3-pv
ReadWriteMany: 동일한 S3 버킷을 여러 Pod에 읽기/쓰기용으로 마운트할 수 있습니다
allow-delete: 사용자가 마운트된 버킷에서 객체를 삭제할 수 있습니다
allow-other: 소유자 이외의 사용자가 마운트된 버킷에 액세스할 수 있습니다
uid=: 마운트된 버킷의 파일/디렉터리의 사용자 ID(UID)를 설정합니다
gid=: 마운트된 버킷의 파일/디렉터리의 그룹 ID(GID)를 설정합니다
region= $AWS_REGION: S3 버킷의 리전을 설정합니다
bucketName은 S3 버킷 이름을 지정합니다
- Kustomize Patch
- Deployment/ui
- Diff
apiVersion: apps/v1
kind: Deployment
metadata:
name: ui
spec:
replicas: 2
template:
spec:
containers:
- name: ui
volumeMounts:
- name: mountpoint-s3
mountPath: /mountpoint-s3
env:
- name: RETAIL_UI_PRODUCT_IMAGES_PATH
value: /mountpoint-s3
volumes:
- name: mountpoint-s3
persistentVolumeClaim:
claimName: s3-claim
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app.kubernetes.io/created-by: eks-workshop
app.kubernetes.io/type: app
name: ui
namespace: ui
spec:
replicas: 2
selector:
matchLabels:
app.kubernetes.io/component: service
app.kubernetes.io/instance: ui
app.kubernetes.io/name: ui
template:
metadata:
annotations:
prometheus.io/path: /actuator/prometheus
prometheus.io/port: "8080"
prometheus.io/scrape: "true"
labels:
app.kubernetes.io/component: service
app.kubernetes.io/created-by: eks-workshop
app.kubernetes.io/instance: ui
app.kubernetes.io/name: ui
spec:
containers:
- env:
- name: RETAIL_UI_PRODUCT_IMAGES_PATH
value: /mountpoint-s3
- name: JAVA_OPTS
value: -XX:MaxRAMPercentage=75.0 -Djava.security.egd=file:/dev/urandom
- name: METADATA_KUBERNETES_POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: METADATA_KUBERNETES_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: METADATA_KUBERNETES_NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
envFrom:
- configMapRef:
name: ui
image: public.ecr.aws/aws-containers/retail-store-sample-ui:1.2.1
imagePullPolicy: IfNotPresent
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 45
periodSeconds: 20
name: ui
ports:
- containerPort: 8080
name: http
protocol: TCP
resources:
limits:
memory: 1.5Gi
requests:
cpu: 250m
memory: 1.5Gi
securityContext:
capabilities:
add:
- NET_BIND_SERVICE
drop:
- ALL
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 1000
volumeMounts:
- mountPath: /mountpoint-s3
name: mountpoint-s3
- mountPath: /tmp
name: tmp-volume
securityContext:
fsGroup: 1000
serviceAccountName: ui
volumes:
- name: mountpoint-s3
persistentVolumeClaim:
claimName: s3-claim
- emptyDir:
medium: Memory
name: tmp-volume
app.kubernetes.io/type: app
name: ui
namespace: ui
spec:
- replicas: 1
+ replicas: 2
selector:
matchLabels:
app.kubernetes.io/component: service
app.kubernetes.io/instance: ui
[...]
app.kubernetes.io/name: ui
spec:
containers:
- env:
+ - name: RETAIL_UI_PRODUCT_IMAGES_PATH
+ value: /mountpoint-s3
- name: JAVA_OPTS
value: -XX:MaxRAMPercentage=75.0 -Djava.security.egd=file:/dev/urandom
- name: METADATA_KUBERNETES_POD_NAME
valueFrom:
[...]
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 1000
volumeMounts:
+ - mountPath: /mountpoint-s3
+ name: mountpoint-s3
- mountPath: /tmp
name: tmp-volume
securityContext:
fsGroup: 1000
serviceAccountName: ui
volumes:
+ - name: mountpoint-s3
+ persistentVolumeClaim:
+ claimName: s3-claim
- emptyDir:
medium: Memory
name: tmp-volume
이제 이 구성을 적용하고 애플리케이션을 재배포하겠습니다:
namespace/ui unchanged
serviceaccount/ui unchanged
configmap/ui unchanged
service/ui unchanged
persistentvolume/s3-pv created
persistentvolumeclaim/s3-claim created
deployment.apps/ui configured
배포 진행 상황을 모니터링하겠습니다:
deployment "ui" successfully rolled out
새로운 /mountpoint-s3 마운트 포인트에 주목하여 볼륨 마운트를 확인해보겠습니다:
- mountPath: /mountpoint-s3
name: mountpoint-s3
- mountPath: /tmp
name: tmp-volume
이제 새로 생성된 PersistentVolume을 살펴보겠습니다:
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
s3-pv 1Gi RWX Retain Bound ui/s3-claim <unset> 2m31s
PersistentVolumeClaim 세부 정보를 검토하겠습니다:
Name: s3-claim
Namespace: ui
StorageClass:
Status: Bound
Volume: s3-pv
Labels: <none>
Annotations: pv.kubernetes.io/bind-completed: yes
Finalizers: [kubernetes.io/pvc-protection]
Capacity: 1Gi
Access Modes: RWX
VolumeMode: Filesystem
Used By: ui-9fbbbcd6f-c74vv
ui-9fbbbcd6f-vb9jz
Events: <none>
실행 중인 Pod를 확인해보겠습니다:
NAME READY STATUS RESTARTS AGE
ui-9fbbbcd6f-c74vv 1/1 Running 0 2m36s
ui-9fbbbcd6f-vb9jz 1/1 Running 0 2m38s
이제 Mountpoint for Amazon S3 CSI 드라이버가 적용된 최종 배포 구성을 살펴보겠습니다:
Name: ui
Namespace: ui
[...]
Containers:
ui:
Image: public.ecr.aws/aws-containers/retail-store-sample-ui:1.2.1
Port: 8080/TCP
Host Port: 0/TCP
Limits:
memory: 128Mi
Requests:
cpu: 128m
memory: 128Mi
[...]
Mounts:
/mountpoint-s3 from mountpoint-s3 (rw)
/tmp from tmp-volume (rw)
Volumes:
mountpoint-s3:
Type: PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
ClaimName: s3-claim
ReadOnly: false
tmp-volume:
Type: EmptyDir (a temporary directory that shares a pod's lifetime)
Medium: Memory
SizeLimit: <unset>
[...]
이제 공유 스토리지 기능을 실제로 확인해보겠습니다. 먼저 UI 컴포넌트 Pod 중 하나를 통해 /mountpoint-s3의 현재 파일 목록을 확인하겠습니다:
1ca35e86-4b4c-4124-b6b5-076ba4134d0d.jpg
4f18544b-70a5-4352-8e19-0d070f46745d.jpg
631a3db5-ac07-492c-a994-8cd56923c112.jpg
79bce3f3-935f-4912-8c62-0d2f3e059405.jpg
8757729a-c518-4356-8694-9e795a9b3237.jpg
87e89b11-d319-446d-b9be-50adcca5224a.jpg
a1258cd2-176c-4507-ade6-746dab5ad625.jpg
cc789f85-1476-452a-8100-9e74502198e0.jpg
d27cf49f-b689-4a75-a249-d373e0330bb5.jpg
d3104128-1d14-4465-99d3-8ab9267c687b.jpg
d4edfedb-dbe9-4dd9-aae8-009489394955.jpg
d77f9ae6-e9a8-4a3e-86bd-b72af75cbc49.jpg
이미지 목록이 이전에 S3 버킷에 업로드한 것과 일치하는 것을 확인할 수 있습니다. 이제 placeholder.jpg라는 새 이미지를 생성하고 동일한 Pod를 통해 S3 버킷에 추가하겠습니다:
스토리지 계층의 영구성과 공유를 확인하기 위해 두 번째 UI Pod를 사용하여 방금 생성한 파일을 확인해보겠습니다:
1ca35e86-4b4c-4124-b6b5-076ba4134d0d.jpg
4f18544b-70a5-4352-8e19-0d070f46745d.jpg
631a3db5-ac07-492c-a994-8cd56923c112.jpg
79bce3f3-935f-4912-8c62-0d2f3e059405.jpg
8757729a-c518-4356-8694-9e795a9b3237.jpg
87e89b11-d319-446d-b9be-50adcca5224a.jpg
a1258cd2-176c-4507-ade6-746dab5ad625.jpg
cc789f85-1476-452a-8100-9e74502198e0.jpg
d27cf49f-b689-4a75-a249-d373e0330bb5.jpg
d3104128-1d14-4465-99d3-8ab9267c687b.jpg
d4edfedb-dbe9-4dd9-aae8-009489394955.jpg
d77f9ae6-e9a8-4a3e-86bd-b72af75cbc49.jpg
placeholder.jpg <----------------
마지막으로 S3 버킷에 있는지 확인하겠습니다:
2025-07-09 14:43:36 102950 1ca35e86-4b4c-4124-b6b5-076ba4134d0d.jpg
2025-07-09 14:43:36 118546 4f18544b-70a5-4352-8e19-0d070f46745d.jpg
2025-07-09 14:43:36 147820 631a3db5-ac07-492c-a994-8cd56923c112.jpg
2025-07-09 14:43:36 100117 79bce3f3-935f-4912-8c62-0d2f3e059405.jpg
2025-07-09 14:43:36 106911 8757729a-c518-4356-8694-9e795a9b3237.jpg
2025-07-09 14:43:36 113010 87e89b11-d319-446d-b9be-50adcca5224a.jpg
2025-07-09 14:43:36 171045 a1258cd2-176c-4507-ade6-746dab5ad625.jpg
2025-07-09 14:43:36 170438 cc789f85-1476-452a-8100-9e74502198e0.jpg
2025-07-09 14:43:36 97592 d27cf49f-b689-4a75-a249-d373e0330bb5.jpg
2025-07-09 14:43:36 169246 d3104128-1d14-4465-99d3-8ab9267c687b.jpg
2025-07-09 14:43:36 151884 d4edfedb-dbe9-4dd9-aae8-009489394955.jpg
2025-07-09 14:43:36 134344 d77f9ae6-e9a8-4a3e-86bd-b72af75cbc49.jpg
2025-07-09 15:10:27 10024 placeholder.jpg <----------------
이제 UI를 통해 이미지를 사용할 수 있는지 확인할 수 있습니다:
http://k8s-ui-uinlb-647e781087-6717c5049aa96bd9.elb.us-west-2.amazonaws.com/assets/img/products/placeholder.jpg
브라우저에서 URL을 방문하세요:
이로써 EKS에서 실행되는 워크로드를 위한 영구 공유 스토리지로 Mountpoint for Amazon S3를 사용하는 방법을 성공적으로 시연했습니다.