궁금한게 많은 개발자 노트

[ k8s ] Kubernetes Pod에 AWS S3 마운트 본문

DevOps

[ k8s ] Kubernetes Pod에 AWS S3 마운트

궁금한게 많은 개발자 2024. 1. 26. 10:29

쿠버네티스상에 서버나 앱서비스를 배포할 때, Persistent Volume을 마운트해야 하는 경우가 종종 있습니다.

이전에 쿠버네티스에서 Persistent Volume사용하기라는 글을 게시한 적이 있는데,

해당 게시글에서는 Persistent Volume으로 AWS EBS(Elastic Block Store)를 사용하였습니다.

 

이처럼 컨테이너화된 애플리케이션이 동작할 때 데이터 지속성을 위해 외부 Storage에 대한 액세스가 필요할 수 있습니다.

고가용성과 확장성을 보장하기 위해 EKS를 사용하고 S3를 외부 Storage로 사용한다면 다양한 환경에서도 원활하게 서비스를 제공할 수 있을 것입니다.

 

 

특히 MLOps환경에서 GenAI의 경우에는 매우 큰 LLM Model을 Container내부에 가져가야 하는 경우 큰 도움이 될 수 있습니다. Docker Image내부에 Model을 복사해서 이미지화 하는 것 대신 (복잡한 모델의 경우 이미지 사이즈 20GB이상), Model을 S3 Bucket에 올려두고, 컨테이너 시작 시에 S3를 Pod내부에 마운트하여 데이터를 네트워크로 공유할 수 있도록 처리할 수 있습니다. 물론 네트워크 비용은 감안해야 할 것 같습니다.

그래서 이번 게시글을 통해 S3를 EKS내 Pod에 마운트하는 방법에 대해 알아보려 합니다. ✔

 

 

 

우선, S3를 마운트할 때 사용하는 서비스로는 S3FS, goofys등이 있는 걸로 알고있는데 작년 8월 AWS에서 출시한 Mountpoint for amazon S3를 사용해보려 합니다.

https://aws.amazon.com/ko/blogs/korea/mountpoint-for-amazon-s3-generally-available-and-ready-for-production-workloads/ 

With the Mountpoint for Amazon S3 Container Storage Interface (CSI) driver, your Kubernetes applications can access S3
objects through a file system interface, achieving high aggregate throughput without changing any application code.
Built on Mountpoint for Amazon S3, the CSI driver presents an Amazon S3 bucket as a volume that can be accessed by
containers in Amazon EKS and self-managed Kubernetes clusters.

 

 

 


AWS Credentials(Access Key, Secret Key)전달없이 Pod에서 사용하기 위해서는 IRSA(IAM Roles for Service Account)형태로 Pod에 S3 접근 권한을 부여해주어야 합니다.

SA(Service Account)를 위한 IAM Role을 생성하기 전에 IAM Poilcy를 먼저 생성해야 합니다.

Creating an IAM policy

아래 처럼 IAM Policy를 작성할 수 있습니다. 물론 AWS Managed Policy인  AmazonS3FullAccess를 사용할 수도 있지만,

권한은 목적에 맞게 최소한으로 작성하는 것이 남용과 의도치 않은 동작을 방지할 수 있다고 합니다.

{
   "Version": "2012-10-17",
   "Statement": [
        {
            "Sid": "MountpointFullBucketAccess",
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::DOC-EXAMPLE-BUCKET1"
            ]
        },
        {
            "Sid": "MountpointFullObjectAccess",
            "Effect": "Allow",
            "Action": [
                "s3:GetObject",
                "s3:PutObject",
                "s3:AbortMultipartUpload",
                "s3:DeleteObject"
            ],
            "Resource": [
                "arn:aws:s3:::DOC-EXAMPLE-BUCKET1/*"
            ]
        }
   ]
}

 

WS S3 Bucket List를 조회하기 위한 권한이 위에 있는 Policy이며, 아래 Policy는 Bucket내 Object에 대한 접근 제어 권한입니다. 자세히 보면 Resource부분이 arn::aws::s3::bueckt_name(bucket list확인 용)과 arn::aws::s3::bueckt_name/*(bucket objectd 제어 용)로 다른 점을 확인하실 수 있습니다. 

 

 

Creating an IAM role

그 다음 절차로는 생성한 Policy로 Pod에 IAM역할을 위임하기 위해 iamserviceaccount를 생성합니다.

기본적으로 kubernetes내에서 pod가 다른 resource들에 대한 권한을 얻기 위해서는 role, rolebinding, clusterrole, cluseterrolebinding, serviceaccount가 있습니다.

k8s내 serviceaccount자원을 활용하여 AWS자원에 접근 권한을 줄 수 있는데, 이 때 serviceaccounts는 AWS자원이 아닌데 어떻게 접근이 가능할까요??

이 때, k8s내 resource가 aws내 resource/service에 접근할 수 있게 해주는 것이 OIDC(OpenID Connect)와 AWS STS(Security Token Service)기능입니다.

CLUSTER_NAME=my-cluster
REGION=region-code
ROLE_NAME=AmazonEKS_S3_CSI_DriverRole
POLICY_ARN=AmazonEKS_S3_CSI_DriverRole_ARN
eksctl create iamserviceaccount \
    --name s3-csi-driver-sa \
    --namespace kube-system \
    --cluster $CLUSTER_NAME \
    --attach-policy-arn $POLICY_ARN \
    --approve \
    --role-name $ROLE_NAME \
    --region $REGION

 

 

기본적으로 k8s의 serviceaccount는 쿠버네티스 클러스터 내에서 실행되는 Pod가 API서버와 상호작용할 수 있도록 권한을 부여하는데 사용되는 자격증명입니다. 위 eksctl명령어를 통해 serviceaccount를 생성함과 동시에 전 단계에서 생성한 policy를 이용하여 IAM Role을 생성하고 해당 Role을 k8s serviceaccount에 부여하여, k8s resource이지만 AWS resource에 접근할 수 있는 권한이 생기게 됩니다.

 

 

위에서 설명한 자격 증명이 어떻게 이루어지는지 조금 더 자세히 살펴보려 합니다. 😲

serviceaccount를 생성하면 그에 대응하는 k8s secret resource가 자동으로 생성되며, 쿠버네티스 API서버는 이 시크릿의 JWT Token을 통해 serviceaccount인증을 수행합니다.

 

또한, pod생성 시 해당 serviceaccount를 붙여주면 Pod Identity Webhook이 Pod에 aws-iam-token(JWT)을 마운트해줍니다. 마운트 경로는 kubectl describe pod pod_name으로 확인 해보면 AWS_WEB_IDENTITY_TOKEN_FILE에 위치해 있으며, 그것을 통해 얻을 자격은 AWS_ROLE_ARN에 정의되어 있습니다.

Pod에 올라간 AWS SDK는 AWS_ROLE_ARN, AWS_WEB_IDENTITY_TOKEN_FILE라는 이름의 환경변수 값이 설정되어 있을 때,해당 변수 값을 읽어들여 Web Identity 토큰으로 AssumeRoleWithWebIdentity를 호출하게 되고 이로써 특정 IAM역할을 임시 자격 증명(STS)를 통해 가질 수 있게 됩니다.

 

이 때 해당 토큰을 발급한 주체는 EKS IdP(Identity Provider)이며, 생성된 IAM Role의 Trust Relationship에서 확인할 수 있습니다 🙌

생성된 JWT Token을 decode해보면 exp, aud, iss속성등이 추가되어 있으며 여기서 눈여겨 볼 속성은 iss입니다.
https://oidc.eks..로 시작하는 URL은 EKS가 자체적으로 제공하는 OpenID Connect Provider (EKS IdP)주소입니다.

 

 

 

Installing the Mountpoint for Amazon S3 CSI driver

그 다음 절차로 Mountpoint for Amazon S3 CSI driver를 설치해야 합니다.
CSI driver는 EKS Add-on이나 Helm을 통해 설치할 수 있습니다.
 
EKS Add-on
eksctl create addon --cluster my-cluster --name name-of-addon --version latest \
    --service-account-role-arn arn:aws:iam::111122223333:role/role-name --force
 
Helm
# Add the aws-mountpoint-s3-csi-driver Helm repository.
helm repo add aws-mountpoint-s3-csi-driver https://awslabs.github.io/mountpoint-s3-csi-driver
helm repo update

# Install the latest release of the driver.
helm upgrade --install aws-mountpoint-s3-csi-driver \
    --namespace kube-system \
    aws-mountpoint-s3-csi-driver/aws-mountpoint-s3-csi-driver

 

 

Deploying Pod with serviceaccount

이제 Pod에 S3를 마운트할 준비를 마쳤으니, Pod를 배포해야 합니다. Pod에 올라가는  Container실행 시 S3가 바로 마운트되게 하기 위해서는 Docker Image생성 시 mount-s3.deb설치 등의 사전 작업이 필요합니다. 또는 Pod생성 후 Pod에 접속하여 수동으로 S3를 pod내부에 마운트하는 방법도 있습니다.

저는 Dockerfile에서 mount-s3에 필요한 패키지를 미리 설치하고, 해당 Image가 Container위에서 실행될 때 S3가 마운트되도록 설정해보려 합니다.

FROM nvidia/cuda:12.2.0-runtime-ubuntu22.04

RUN apt update && \
    apt upgrade -y && \
    apt install -y python3 python3-pip unzip wget curl

RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
RUN unzip awscliv2.zip && ./aws/install
RUN wget https://s3.amazonaws.com/mountpoint-s3-release/latest/x86_64/mount-s3.deb && \
    apt install -y ./mount-s3.deb
RUN mkdir -p /opt/model
...

위에서 볼 수 있듯 mount-s3.deb를 설치하고, S3 bucket이 mount될 /opt/model 디렉토리를 미리 생성해주었습니다.

apiVersion: apps/v1
kind: Deployment
metadata:
  ...

    spec:
      serviceAccountName: pod-access-s3-sa
      containers:
      - name: s3-mount
        image: 111222333444.dkr.ecr.us-west-2.amazonaws.com/mlops:latest
        command: ["/bin/sh", "-c"]
          args:
            - |
              mount-s3 --region us-west-2 platform-us-west-2-dev-triton-model-repo /opt/model/
              python3 /opt/apps/stable_diffusion/main.py

 

그 다음, Pod배포를 위한 Deployment작성 시 첫 번째 절차에서 생성한 S3접근 권한을 가진 serviceaccount를 명시해주고, Container가 실행될 때 수행할 명령어를 위와 같이 작성해줍니다.

mount-s3 --region us-west-2 platform-us-west-2-dev-triton-model-repo /opt/model/ 을 통해 해당 경로에 S3 bucket을 마운트하고, python3 /opt/apps/stable_diffusion/main.py 로 서버를 실행하도록 하였습니다.

 

 

해당 Pod가 배포되고 Pod내부 컨테이너에 접속하여 /opt/model/ 경로를 확인해보면 AWS S3에 존재하는 Bucket이 잘 마운트된 것을 확인할 수 있습니다.

기존에 Pod에서 주로 사용하는 Persistent Volume(대표적으로 EBS)과는 조금 다른 절차라 업무를 하면서 어려운 점도 있었지만, 대용량 데이터를 고가용성, 보안, 성능을 보장하면서 저장할 수 있는 S3와 Pod를 연동하는 것은 종종 필요할 수 있을 것 같아 내용 정리도하고 공유하게 되었습니다.

혹시 관련해서 조언해주실 내용이나 피드백은 언제나 환영합니다. 감사합니다. 🤗🙌

Comments