[AWS EKS] EKS를 사용하여 배포하기

Myoung Jun Ko·2024년 10월 22일
0
post-thumbnail

이전 글에서 docker-compose 프로젝트를 로컬 환경의 kubernetes로 변환했다. 이제 이를 aws eks 서비스를 사용하여 배포할 차례이다.

AWS EKS란

AWS EKS(Elastic Kubernetes Service)는 Kubernetes를 쉽게 운영할 수 있도록 aws에서 제공하는 관리형 서비스로 클라우드 기반 컨테이너 오케스트레이션 플랫폼이다. 쉽게 말해 사용자가 직접 kubernetes를 관리할 필요없이 aws가 클러스터의 배포, 구성, 업그레이드, 보안 등의 작업을 대신 수행해준다. aws 서비스와 연계도 간편하고 배포하는 과정에서도 자동으로 ec2를 생성하고 deployment를 잘 분배해서 배포한다.

aws에서 제공하는 Amazon EKS 시작하기 - AWS Management Console 및 AWS CLI를 참고하여 eks를 실행했다.

AWS 서비스 생성

0단계: 사전 조건

 	# macOS
	brea install helm

	# Helm 3.x 버전 설치
	curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3.sh | bash

1단계: Amazon EKS 클러스터 생성

eks를 시작하기에 앞서서 먼저 eks의 요구 사항을 만족하는 vpc가 존재해야 한다. vpc는 public과 private 서브넷, 인터넷 게이트웨이, NAT 게이트웨이, 보안 그룹, 라우팅 테이블 설정 등 다양한 설정을 맞춰주어야 한다. 나는 요금을 줄이기 위해 NAT 게이트웨이 대신 NAT 인스턴스를 사용하려 했으나 NAT 인스턴스가 제대로 동작하지 않아서 aws에서 제공하는 cloudformation을 사용하여 vpc를 구성했다.

NAT 인스턴스

NAT Gateway 대신 EC2 인스턴스를 NAT용으로 바꿔 사용하는 것을 말한다.
NAT Gateway보다 고려해야 할 것이 많고 안 좋은 점도 있지만 가격이 더 저렴하기에 테스트 용으로 NAT Gateway가 필요하다면 고려해봐도 좋다.

aws cloudformation create-stack \
  --region {region-code}
  --stack-name {my-eks-vpc-stack-name} \
  --template-url https://s3.us-west-2.amazonaws.com/amazon-eks/cloudformation/2020-10-29/amazon-eks-vpc-private-subnets.yaml

위 명령어를 실행하면 아래와 같이 cloudformation 콘솔에 스택이 생기고 vpc를 생성하는 것을 볼 수 있다.

생성이 완료되면 EKS 서비스로 접속해서 클러스터를 생성한다. 이때 클러스터 IAM 역할은 aws에서 미리 생성되어있는 AmazonEKSClusterPolicy 만 추가하여 역할을 만들고 사용하면 된다.

다음을 누르고 vpc와 보안 그룹은 아까 만든 vpc의 것을 사용한다. vpc의 이름으로 시작하기에 (나같은 경우는 stack 이름을 RetroPong으로 해서 'RetroPong-VPC', 'RetroPong-Control...' 식으로 이름이 생성되었다) 쉽게 선택할 수 있다. 서브넷은 vpc를 선택할 때 자동으로 선택되는 서브넷(다 선택한다)을 그대로 둔다.

그 외에 다른 설정은 기본 설정대로 넘어가고 생성 버튼을 누르면 eks 생성이 시작된다.

2단계: 클러스터와 통신하도록 컴퓨터 구성

위와 같이 상태가 활성이 되면 eks 생성이 완료된 것이다. 이 상태가 되었을 때 아래 명령어로 kubeconfig 파일을 생성 또는 갱신한다. 이를 통해 eks가 만든 클러스터와 kubectl 명령어로 통신할 수 있다.

aws eks update-kubeconfig --region {region-code} --name {my-cluster-name}

그 후, 아래 명령어로 제대로 연결되었는지 확인한다.

> kubectl get svc
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.100.0.1   <none>        443/TCP   8m34s

위와 같이 kuberntes 서비스가 출력된다면 제대로 연결된 것이다.

3단계: 노드 생성

EKS 콘솔 화면에서 컴퓨팅 탭을 클릭하면 위와 같이 노드 관련 콘솔이 나온다. 여기서 '노드 그룹 추가' 를 선택한다.

처음 NodeGroup에게 주는 권한 정책을 나는 아래와 같이 지정했다.

  • AmazonEC2ContainerRegistryReadOnly
    - Amazon ECR(Elastic Container Registry)에 대한 읽기 전용 액세스를 허용하는 권한 제공
    • EKS 클러스터 내에서 컨테이너 이미지를 가져오는데 사용
  • AmazonEC2FullAccess
    - EC2에 대한 모든 권한
    • EC2 볼륨 관련 권한을 위해 추가
  • AmazonEKS_CNI_Policy
    - EKS의 CNI(Container Network Interface) 플러그인에 필요한 권한을 제공
    • VPC 네트워크 리소스를 관리하는데 사용
  • AmazonEKSWorkerNodeMinimalPolicy
    - EKS 작업자 노드가 정상적으로 작동하기 위해 필요한 최소한의 권한을 제공
    • EC2 인스턴스, ENI(Elastic Network Interface), 로깅 등에 대한 액세스 허용
    • 노드가 클러스터에 참여하고 통신할 수 있도록 만들어준다.
  • AmazonElasticFileSystemFullAccess
    - EFS(Elastic File System)에 대한 전체 액세스 허용

위 권한을 가진 역할을 배정하고 그 외 설정은 기본 설정 그대로 생성한다.

4단계: AWS EFS 생성

갑자기 efs가 등장하는 이유

ec2는 자동으로 EBS라는 영구적인 볼륨을 생성하고 연결할 수 있다. 하지만 ebs는 기본적으로 하나의 인스턴스와 연결되고 다중 연결 설정 역시 같은 가용 영역 내에 있는 인스턴스에 한해서만 설정이 가능하다. 같은 가용 영역 내에 모든 인스턴스를 생성하는 것은 안정성이나 가용성면에서 모두 좋지 못하다. 그렇기에 하나의 가용 영역을 강제해서 ebs를 사용하는 것 대신 같은 리전 내라면 어떤 가용 영역에서도 접근할 수 있는 efs를 사용하였다.

EFS 콘솔 화면에서 파일 시스템 생성을 선택하면 이름과 vpc를 선택할 수 있다. 이때 vpc가 현재 eks가 배포되어있는 vpc인지 잘 확인하여 생성하면 된다.

여기서 하나 확인해야 할 것은 efs 네트워크탭에 있는 네트워크다. 자동으로 생성되어있는 네트워크가 두 개가 있을 텐데 관리를 눌러 설정을 보면 보안 그룹이 default로 설정되어 있다. 이 설정을 default 대신 'eks-cluster-sg-{EKS name}-...' 로 바꿔줘야 한다.

5단계: EBS/EFS CSI Driver 설치

마지막으로 EKS에서 EBS와 EFS를 사용할 수 있도록 CSI Driver를 설치해야 한다. helm을 사용하여 아래 명령어로 각각 설치하면 된다.

# ebs-csi-driver 설치
helm repo add aws-ebs-csi-driver https://kubernetes-sigs.github.io/aws-ebs-csi-driver
helm repo update
helm install ebs-csi-driver aws-ebs-csi-driver/aws-ebs-csi-driver \
  --namespace kube-system \
  --create-namespace
# efs-csi-driver 설치
helm repo add efs-csi-driver https://kubernetes-sigs.github.io/aws-efs-csi-driver
helm repo update
helm install efs-csi-driver efs-csi-driver/aws-efs-csi-driver \
  --namespace kube-system \
  --create-namespace

아래와 같이 pod가 실행 중이어야 제대로 설치된 것이다.

> kubectl get pods -n kube-system
...
bs-csi-controller-84cb4d8486-fjcw4   5/5     Running   0          12m
ebs-csi-controller-84cb4d8486-vfh8m   5/5     Running   0          12m
ebs-csi-node-4krdl                    3/3     Running   0          12m
ebs-csi-node-6plrj                    3/3     Running   0          12m
efs-csi-controller-55d87d98d9-dslhg   3/3     Running   0          21m
efs-csi-controller-55d87d98d9-gjjjn   3/3     Running   0          21m
efs-csi-node-lq2bl                    3/3     Running   0          21m
efs-csi-node-w7g88                    3/3     Running   0          21m
...

추후 pv가 생성되지 않거나 pvc에서 오류가 발생할 때, 대부분은 이 드라이버 문제로 발생했었다. 그때는 gpt에게 삭제 명령어를 물어봐서 삭제 후, 다시 설치하면 된다. 이때, helm과 kubectl로 삭제하는 방법 중 자신이 설치한 방법을 따라서 삭제하면 된다. 만약 기억이 안나면 두 방법으로 모두 삭제하고 설치해야 설치하는 과정에서 이미 있어서 설치가 안된다는 오류가 발생하지 않는다.

kubernetes 파일 변경

저번 글에서 로컬용으로 만든 파일을 aws 환경에 맞게 수정해야 한다. 각 파일별로 확인하면 다음과 같다.

configMap.yaml & deployment.yaml

# configMap.yaml
...
data:
  ...
  PGDATA: "/var/lib/postgresql/data/pgdata" # 마운트된 경로 안의 하위 디렉토리 사용
# deployment.yaml
	  ...
      containers:
      - env:
        - name: PGDATA  # ebs와 postgre 간 충돌을 막기 위함
          valueFrom:
            configMapKeyRef:
              key: PGDATA
              name: db-env
      ...

postgresql의 데이터를 저장하는 경로를 지정하는 환경변수인 PGDATA 를 변경했다. 이는 위에 써있는 것처럼 볼륨 간의 충돌을 막기 위함이다. 만약 환경변수를 변경하지 않는다면 현재 postgresql에서 사용하는 데이터 폴더와 ebs에 마운트되는 경로가 동일해지는데 볼륨이 postgresql 폴더를 강제로 초기화하여 postgresql를 구성하는 파일이 사라질 수 있기 때문이다.

실제로 환경변수를 설정하지 않고 실행하면 db를 배포한 파드에서 /var/lib/postgresql/data 내에 있는 파일을 찾지 못해서 에러가 발생함을 확인할 수 있다.

service.yaml

# service.yaml
apiVersion: v1
kind: Service
metadata:
  name: frontend
  namespace: app
spec:
  selector:
    app: frontend
  ports:
    - name: "443"
      port: 443
      targetPort: 443
  type: LoadBalancer

service.yaml 중에선 frontend의 port의 type만 NodePorts에서 LoadBalancer로 변경했다. 이는 aws ec2에 배포되는 frontend에 접근하기 위해서 사용한다. 또한 도메인 구매 없이 테스트하기 위함이기도 하다.

persistentVolume.yaml

# persistentVolume.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass  # 동적 프로비저닝
metadata:
  name: ebs-sc
provisioner: ebs.csi.aws.com  # AWS EBS CSI 드라이버를 사용하여 스토리지를 동적으로 프로비저닝
volumeBindingMode: WaitForFirstConsumer  # 처음으로 사용하는 파드가 생성되기 전까지 프로비저닝되지 않음
allowVolumeExpansion: true  # EBS 볼륨 확장을 허용
parameters:
  type: gp3  # AWS EBS에서 gp3 유형의 볼륨 사용. 일반적인 SSD 스토리지 유형
  encrypted: "true"  # EBS 볼륨 암호화

---

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: efs-sc  # pv는 글로벌 범위기에 namespace 표기 할 필요 없음
provisioner: efs.csi.aws.com  # AWS EFS CSI 드라이버 사용하여 스토리지를 동적으로 프로비저닝
parameters:
  provisioningMode: efs-ap  # EFS의 Access Point를 사용하여 파일 시스템을 연결
  fileSystemId: fs-  # TODO 생성한 efs ID 입력
  directoryPerms: "700"  # EFS 파일 시스템 내의 디렉터리 권한 설정. 700은 소유자만 읽기, 쓰기, 실행 권한을 가짐

---

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: db-data
  namespace: app
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
  storageClassName: ebs-sc

---

# AWS EFS는 파일 시스템이므로 스토리지 크기를 고정하지 않는다. 즉 용량을 미리 할당하지 않으며 요청된 용량도 무시된다.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: back-data
  namespace: app
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: efs-sc
  resources:
    requests:
      storage: 1Mi  # EFS는 이 값을 무시하지만 누락되면 오류가 발생하기에 최소 크기 요청

persistentVolume.yaml의 변경사항은 많다. 우선 StorageClass를 각각 EBS와 EFS에 맞도록 변경해줘야 한다. 또한 앞서 만든 efs의 ID도 입력해 주어야 한다. 각 주석을 참고하면 된다.

배포

이제 배포하면 된다. 앞서 aws eks와 연결을 했기에 kubectl을 사용해서 배포하면 된다. 하지만 나는 도메인을 사용하지 않고 로드 밸런서의 url을 사용해서 테스트만 할 것이기에 배포 순서가 중요하다.

만약 url이 미리 필요하지 않거나 도메인이 고정되어있다면 바로 배포하면 된다.

나는 먼저 service.yaml 만 배포해서 로드 밸런서를 먼저 생성했다. 배포하면 아래와 같이 로드 밸런서가 만든 url을 확인할 수 있다.

> kubectl get all -n app
NAME               TYPE           CLUSTER-IP      EXTERNAL-IP                                                                   PORT(S)         AGE
service/backend    ClusterIP      10.100.68.53    <none>                                                                        8000/TCP        7s
service/db         ClusterIP      10.100.47.163   <none>                                                                        5432/TCP        7s
service/frontend   LoadBalancer   10.100.35.153   adab0bb2802a04cbfa1e9d4c6af6341d-819121854.ap-northeast-2.elb.amazonaws.com   443:30295/TCP   6s

이 url을 frontend와 backend 환경변수에 저장하여 이미지를 다시 빌드시키고 푸시한 후, 나머지 파일들을 배포하면 로드 밸런서의 url로 서비스에 접근할 수 있다.

추후 추가한 부분

deployment에 도커 이미지 내에서 이미 정의된 환경 변수를 재정의하면 기존 도커 이미지에 있던 환경 변수 대신 새로 정의한 환경 변수를 사용한다. 이 기능을 이용하면 굳이 다시 빌드하고 푸시하는 수고를 할 필요가 없다.

삭제

서비스를 계속 제공할 것이 아니라면 빠르게 삭제해야 요금을 줄일 수 있다. 삭제는 생성한 순서에 역순으로 진행하면 된다.

  1. 배포한 k8s 리소스 제거
  2. eks 클러스터 내 컴퓨팅 탭에서 노드 그룹 삭제 & efs 삭제
  3. 노드 그룹 삭제 완료 후, eks 클러스터 삭제
  4. eks 클러스터 삭제 완료 후, cloudformation의 stack 삭제
  5. (선택) 역할 삭제
  6. (로컬에서 docker-desktop으로 k8s 사용 시)kubectl config use-context docker-desktop 실행

나는 반나절 정도 에러와 씨름하며 서비스를 유지했는데 아래와 같이 총 5.41달러의 요금이 발생했다.

후기

aws에서 많은 것을 커버해주기에 배포 자체는 어렵지 않으나 볼륨 설정에서 특히 많이 애먹었다. 그래도 다른 aws 서비스를 사용한다면 서로 연계하기 편하게 구성되어 있기에 eks의 이점은 확실히 강한 것 같다. 추후엔 이 프로젝트에 elk 스택과 그라파나 등의 모니터 도구를 연결할 계획이다.

0개의 댓글