BACKEND/AWS

AWS EKS - Web Application (2)

gngsn 2022. 7. 19. 23:31

본 포스팅은 AWS EKS의 실습 과정을 기록한 내용입니다.

 

좋은 기회를 얻어 AWS EKS를 학습하고 실습하는 시간을 가졌습니다.

AWS ECR로 Docker image를 올려 관리하고, EKS를 직접 실습하는 시간을 가졌습니다.

좋은 기회를 그대로 끝내기는 아쉬워서, 따로 기록한 내용을 정리했습니다.

참고 영상은 링크를 통해 확인할 수 있습니다.

 

기술의 기초 지식인 Docker와 관련된 이전 포스팅을 참고하셔도 좋을 듯 합니다.

📌  Docker Series

Docker Engine, 제대로 이해하기 (1) - docker engine deep dive

Docker Engine, 제대로 이해하기 (2) - namespace, cgroup

Docker Network, 제대로 이해하기 (1) - libnetwork

Docker Network, 제대로 이해하기 (2) - bridge, host, none drivers

 

📌  AWS EKS - Web Application

AWS EKS - Web Application(1) 

AWS EKS - Web Application(2)

AWS EKS - Web Application(3)

 


 

지난 포스팅에서는 생성한 Kubernetes Cluster에 Ingress Controller를 달아서 요청을 내부로 인입시켜 적절한 Pod Service를 찾게까지 하게끔 설정했습니다.

 

이번 포스팅에서는 각 Pod를 올려서 사용자의 요청이 ALB -> Ingress -> Service -> Pod 에 도달하여 Web Application을 접근할 수 있게 설정합니다.

 

 

 

 

Deploy Service

 

이번에는 세 개의 애플리케이션을 제작해서 Pod로 띄워보겠습니다.

본 실습에서는 웹 서비스를 구성하는 백앤드, 프론트앤드를 Amazon EKS에 배포하는 방법에 대해 알아봅니다. 각 서비스를 배포하는 순서는 아래와 같습니다.

 

 

 

#1. 소스 코드 다운로드

#2. Amazon ECR에 각 서비스에 대한 리포지토리 생성

#3. Dockerfile을 포함한 소스 코드 위치에서 컨테이너 이미지 빌드 후, 리포지토리에 푸시

#4. 각 서비스에 대한 Deployment, Service, Ingress 매니페스트 파일 생성 및 배포

 

아래의 그림은 사용자가 실제 서비스를 접근하는 순서를 나타냅니다.

 

 

Server 1 :: Flask Application

Deploy Manifest

cat <<EOF> flask-deployment.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo-flask-backend
  namespace: default
spec:
  replicas: 3
  selector:
    matchLabels:
      app: demo-flask-backend
  template:
    metadata:
      labels:
        app: demo-flask-backend
    spec:
      containers:
        - name: demo-flask-backend
          image: $ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/demo-flask-backend:latest
          imagePullPolicy: Always
          ports:
            - containerPort: 8080
EOF

 

 

Service Menifest

cat <<EOF> flask-service.yaml
---
apiVersion: v1
kind: Service
metadata:
  name: demo-flask-backend
  annotations:
    alb.ingress.kubernetes.io/healthcheck-path: "/contents/aws"
spec:
  selector:
    app: demo-flask-backend
  type: NodePort
  ports:
    - port: 8080 # 서비스가 생성할 포트  
      targetPort: 8080 # 서비스가 접근할 pod의 포트
      protocol: TCP
EOF

 

 

Ingress Menifest

아래에서 Ingress Menifest 에서 최종적으로 다룹니다.

다만, 모든 서버와 클라이언트가 배포 되기 전 단계 별로 해당 Pod와 Service가 잘 수행되는지 확인하기 위함입니다.

즉, 이번 단계인 Flask Pod이 잘 뜨는지 확인을 위해  Ingress를 배포해보겠습니다.

 

cat <<EOF> ingress.yaml
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
    name: "backend-ingress"
    namespace: default
    annotations:
      kubernetes.io/ingress.class: alb
      alb.ingress.kubernetes.io/scheme: internet-facing
      alb.ingress.kubernetes.io/target-type: ip
spec:
    rules:
    - http:
        paths:
          - path: /contents
            pathType: Prefix
            backend:
              service:
                name: "demo-flask-backend"
                port:
                  number: 8080
EOF

 

위에서 생성한 매니페스트를 아래의 순서대로 배포합니다.

Ingress를 생성하면 AWS Application Load Balancer(ALB)가 프로비저닝됩니다.

 
kubectl apply -f flask-deployment.yaml
kubectl apply -f flask-service.yaml
kubectl apply -f ingress.yaml

 

다음 명령어로 수행 결과를 확인할 수 있습니다.

 

echo http://$(kubectl get ingress/backend-ingress -o jsonpath='{.status.loadBalancer.ingress[*].hostname}')/contents/aws

 

출력되는 결과 링크를 열어보면 Flask 서버가 출력하는 API Response를 확인할 수 있습니다.

 

 

 

Server 2 :: Node Application

Deploy Manifest

이미 만들어진 컨테이너 이미지를 포함한 deploy manifest를 생성합니다

 

cat <<EOF> nodejs-deployment.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo-nodejs-backend
  namespace: default
spec:
  replicas: 3
  selector:
    matchLabels:
      app: demo-nodejs-backend
  template:
    metadata:
      labels:
        app: demo-nodejs-backend
    spec:
      containers:
        - name: demo-nodejs-backend
          image: public.ecr.aws/y7c9e1d2/joozero-repo:latest
          imagePullPolicy: Always
          ports:
            - containerPort: 3000
EOF

 

Service Manifest

cat <<EOF> nodejs-service.yaml
---
apiVersion: v1
kind: Service
metadata:
  name: demo-nodejs-backend
  annotations:
    alb.ingress.kubernetes.io/healthcheck-path: "/services/all"
spec:
  selector:
    app: demo-nodejs-backend
  type: NodePort
  ports:
    - port: 8080
      targetPort: 3000
      protocol: TCP
EOF

 

인그레스 매니페스트 파일은 이전 파일을 수정합니다.

아래의 파일에서 demo-nodejs-backend의 경로가 추가되었음을 확인할 수 있습니다.

 

cat <<EOF> ingress.yaml
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: "backend-ingress"
  namespace: default
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
spec:
  rules:
  - http:
        paths:
          - path: /contents
            pathType: Prefix
            backend:
              service:
                name: "demo-flask-backend"
                port:
                  number: 8080
          - path: /services
            pathType: Prefix
            backend:
              service:
                name: "demo-nodejs-backend"
                port:
                  number: 8080
EOF

 

kubectl apply -f nodejs-deployment.yaml
kubectl apply -f nodejs-service.yaml
kubectl apply -f ingress.yaml

 

 

Client :: React Application

 

두 개의 백앤드를 배포했다면 이제는 웹 페이지의 화면을 구성할 프론트앤드를 배포합니다.

아래의 명령어를 통해, 컨테이너라이징할 소스 코드를 다운 받습니다.

 

$ git clone https://github.com/joozero/amazon-eks-frontend.git

 

AWS CLI를 통해, 이미지 리포지토리를 생성합니다. 본 실습에서는 리포지토리 이름을 demo-frontend라고 설정합니다.

 

$ aws ecr create-repository \
--repository-name demo-frontend \
--image-scanning-configuration scanOnPush=true \
--region ${AWS_REGION}

 

두 개의 백앤드 결과 값을 화면에 뿌리기 위해, 일부 소스 코드를 수정합니다. 프론트앤드 소스 코드가 담긴 폴더에서 App.js 파일과 page/UpperPage.js 파일 에 있는 url 값을 앞서 배포한 인그레스 주소로 변경합니다.

위의 화면에서는 아래의 결과값(인그레스 주소)을 통해 도출된 값을 붙여넣습니다.

 

$ echo http://$(kubectl get ingress/backend-ingress -o jsonpath='{.status.loadBalancer.ingress[*].hostname}')/contents/'${search}'

 

위의 화면에서는 아래의 결과값을 통해 도출된 값을 붙여넣습니다.

 

$ echo http://$(kubectl get ingress/backend-ingress -o jsonpath='{.status.loadBalancer.ingress[*].hostname}')/services/all

 

amazon-eks-frontend 폴더에서 아래의 명령어를 수행합니다.

 

$ npm install
$ npm run build

 

이미지 리포지토리 생성 및 이미지 푸시 단계를 진행합니다.

본 실습에서는 이미지 리포지토리 이름을 demo-frontend로 설정합니다.

 

$ docker build -t demo-frontend .
$ docker tag demo-frontend:latest $ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/demo-frontend:latest
$ docker push $ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/demo-frontend:latest

 

만약 바로 위의 명령어를 수행하는 동안, denied: Your authorization token has expired. Reauthenticate and try again. 메세지를 받는다면 아래의 명령어를 실행한 후, 다시 위의 명령어를 수행합니다.

 

$ aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com

 

Deploy Manifest

$ cat <<EOF> frontend-deployment.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo-frontend
  namespace: default
spec:
  replicas: 3
  selector:
    matchLabels:
      app: demo-frontend
  template:
    metadata:
      labels:
        app: demo-frontend
    spec:
      containers:
        - name: demo-frontend
          image: $ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/demo-frontend:latest
          imagePullPolicy: Always
          ports:
            - containerPort: 80
EOF

 

이미지 값에는 demo-frontend 리포지토리 URI 값을 넣습니다. 

 

Service Manifest

cat <<EOF> frontend-service.yaml
---
apiVersion: v1
kind: Service
metadata:
  name: demo-frontend
  annotations:
    alb.ingress.kubernetes.io/healthcheck-path: "/"
spec:
  selector:
    app: demo-frontend
  type: NodePort
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
EOF

 

 

Ingress Manifest

위에서 설정했던 Ingress Manifest를 최종적으로 수정합니다.

 

cat <<EOF> ingress.yaml
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: "backend-ingress"
  namespace: default
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
spec:
  rules:
    - http:
        paths:
          - path: /contents
            pathType: Prefix
            backend:
              service:
                name: "demo-flask-backend"
                port:
                  number: 8080
          - path: /services
            pathType: Prefix
            backend:  
              service:
                name: "demo-nodejs-backend"
                port:
                  number: 8080
          - path: /
            pathType: Prefix
            backend:
              service:
                name: "demo-frontend"
                port:
                  number: 80
EOF

 

매니페스트를 배포합니다.

 

kubectl apply -f frontend-deployment.yaml
kubectl apply -f frontend-service.yaml
kubectl apply -f ingress.yaml

 

다음 명령어 수행 결과를 웹 브라우저에 붙여넣어 확인합니다.

$ echo http://$(kubectl get ingress/backend-ingress -o jsonpath='{.status.loadBalancer.ingress[*].hostname}')

 

아래와 같은 화면이 나오면 모든 컨테이너들이 정상적으로 작동하는 것입니다.

 

 

 

지금까지 올린 아키텍처는 아래와 같습니다.

 

 

 

 

 

AWS Fargate

AWS Fargate는 Amazon EC2 서버나 클러스터 관리할 필요 없이 컨테이너를 실행하기 위한 Amazon ECS를 사용할 수 있습니다. Fargate를 사용하면 컨테이너를 실행하기 위해 가상 머신의 클러스터를 프로비저닝, 구성 또는 조정할 필요가 없습니다.

즉, 서버 유형을 선택하거나, 클러스터를 조정할 시점을 결정하거나, 클러스터 패킹을 최적화할 필요가 없습니다.

 

AWS Fargate는 컨테이너를 위한 서버리스 컴퓨팅 엔진으로 ECS와 EKS에서  작동합니다. Fargate는 애플리케이션을 빌드하는 데 보다 쉽게 초점을 맞출 수 있도록 해줍니다. Fargate에서는 서버를 프로비저닝하고 관리할 필요가 없어 애플리케이션별로 리소스를 지정하고 관련 비용을 지불할 수 있으며, 계획적으로 애플리케이션을 격리함으로써 보안 성능을 향상시킬 수 있습니다.

 

 

 

Deploy Pod

클러스터에 Fargate로 pod를 배포하기 위해서는 pod가 실행될 때 사용하는 하나 이상의 fargate profile을 정의해야 합니다. 즉, fargate profile이란 fargate로 pod를 생성하기 위한 조건을 명시해놓은 프로파일이라고 보시면 됩니다.

 

$ cat <<EOF> eks-demo-fargate-profile.yaml
---
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
  name: eks-demo
  region: ${AWS_REGION}
fargateProfiles:
  - name: frontend-fargate-profile
    selectors:
      - namespace: default
        labels:
          app: frontend-fargate
EOF

 

위의 yaml 파일에서 selectors에 기재된 조건에 부합하는 pod의 경우, fargate로 배포됩니다. 아래의 명령어를 통해, fargate profile을 프로비저닝합니다.

 

$ eksctl create fargateprofile -f eks-demo-fargate-profile.yaml

 

Fargate profile이 정상적으로 생성되었는지 확인합니다. 

$ eksctl get fargateprofile --cluster eks-demo -o json
# 결과 값 예시 
{
    "name": "frontend-fargate-profile",
    "podExecutionRoleARN": "arn:aws:iam::account-id:role/eksctl-eks-demo-test-farga-FargatePodExecutionRole-OLC3P21AD5DX",
    "selectors": [
        {
            "namespace": "default",
            "labels": {
                "app": "frontend-fargate"
            }
        }
    ],
    "subnets": [
        "subnet-07e2d55650225419c",
        "subnet-0ac4a7fdbd803039c",
        "subnet-046a3dcfabce11b5f"
    ],
    "status": "ACTIVE"
} 

 

배포한 3개의 pod 중, Frontend pod를 fargate로 프로비저닝하는 작업을 수행하겠습니다.

먼저, 기존의 pod를 삭제하는 작업을 수행합니다. 아래의 명령어를 yaml 파일이 위치한 폴더에서 작업합니다.

 

$ kubectl delete -f frontend-deployment.yaml

 

그리고 frontend-deployment.yaml 파일을 수정합니다. 이전에 작성한 yaml 파일과 비교해보면 label의 value 값이 demo-frontend에서 frontend-fargate로 변경되었음을 확인할 수 있습니다. 1번에서 key 값이 app이고 value 값이 frontend-fargate이며 namespace가 default일 때, pod를 fargate로 배포하겠다는 조건을 맞추기 위해 값을 변경하였습니다.

$ cat <<EOF> frontend-deployment.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo-frontend
  namespace: default
spec:
  replicas: 3
  selector:
    matchLabels:
      app: frontend-fargate
  template:
    metadata:
      labels:
        app: frontend-fargate
    spec:
      containers:
        - name: demo-frontend
          image: $ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/demo-frontend:latest
          imagePullPolicy: Always
          ports:
            - containerPort: 80
EOF

 

frontend-service.yaml 파일도 수정합니다.

$ cat <<EOF> frontend-service.yaml
---
apiVersion: v1
kind: Service
metadata:
  name: demo-frontend
  annotations:
    alb.ingress.kubernetes.io/healthcheck-path: "/"
spec:
  selector:
    app: frontend-fargate
  type: NodePort
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
EOF

 

매니페스트를 배포합니다.

kubectl apply -f frontend-deployment.yaml
kubectl apply -f frontend-service.yaml

 

아래의 명령어를 통해, pod 리스트를 확인해보면 demo-frontend pod의 경우, fargate-ip-XX 노드에 프로비저닝되었음을 확인할 수 있습니다.

 

$ kubectl get pod -o wide

 

또한, 아래 명령어 수행 결과를 웹 브라우저에 붙여넣으면 이전과 같은 화면을 확인할 수있습니다.

 

$ echo http://$(kubectl get ingress/backend-ingress -o jsonpath='{.status.loadBalancer.ingress[*].hostname}')