반응형
250x250
Notice
Recent Posts
Recent Comments
Link
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
Archives
Today
Total
관리 메뉴

가끔 보자, 하늘.

AWS로 서비스 구축하기 #01 - Jenkins + AWS ECS 로 CD 구축하기 본문

개발 이야기/인프라 구축 및 운영

AWS로 서비스 구축하기 #01 - Jenkins + AWS ECS 로 CD 구축하기

가온아 2022. 3. 24. 18:34

이번에 Jenkins + ECS로 CI/CD를 구축하면서 겪었던 삽질을 정리해 보았습니다. 부디 이 글을 보시고 같은 삽질 덜 하시길 바랍니다. "Hello"라는 텍스트를 돌려주는 간단한 API 서비스를 만들고 배포하는 과정을 살펴보려 합니다.

아래와 같은 절차, 인프라를 구성해 볼 예정입니다. 작업할 순서는 다음과 같습니다.

  1. 계정 생성
  2. ECR 등록
  3. Jenkins 설정 및 배포
  4. ECS 구축
  5. 서비스 시작하기

1. 계정 생성

우선 사용할 계정을 하나 생성하겠습니다. CD에 사용할 계정은 인프라 관리를 위한 중요 권한을 할당해야 하는데, 안쓸때도 권한을 유지하기 보다는 AssumeRole을 이용하여 CD 시에만 일시적으로 권한을 할당하여 사용하도록 하겠습니다. 

"사용자 권한"은 별도 설정을 하지 않고 완료합니다.

다음으로 IAM의 정책에서 아래 세 가지 정책을 생성합니다.

// assume-policy
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "sts:AssumeRole",
            "Resource": "*"
        }
    ]
}
//	ecr-upload-policy
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "ecr:*",
                "logs:PutLogEvents"
            ],
            "Resource": "*"
        }
    ]
}
// ecs-policy
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "application-autoscaling:Describe*",
                "application-autoscaling:PutScalingPolicy",
                "application-autoscaling:DeleteScalingPolicy",
                "application-autoscaling:RegisterScalableTarget",
                "cloudwatch:DescribeAlarms",
                "cloudwatch:PutMetricAlarm",
                "ecs:List*",
                "ecs:ExecuteCommand",
                "ecs:Describe*",
                "ecs:UpdateService",
                "iam:PassRole",
                "iam:AttachRolePolicy",
                "iam:CreateRole",
                "iam:GetPolicy",
                "iam:GetPolicyVersion",
                "iam:GetRole",
                "iam:ListAttachedRolePolicies",
                "iam:ListRoles",
                "iam:ListGroups",
                "iam:ListUsers"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}

그리고 AssumeRole로 할당할 deploy-role을 생성하고 위에서 생성한 ecs-policy과 ecr-upload-policy를 연결해 줍니다. assume-policy는 위에서 생성했던 deploy-user의 권한에 추가합니다.

이렇게 하면 이후 Jenkinsfile에서 role을 동적으로 할당하여 사용할 준비가 완료된 것입니다.

2. ECR 등록

Amazon ECS 에서 "리포지토리 생성"을 클릭 후 Private 저장소로,  리포지토리 이름은 hello로 지정하여 생성합니다. 잡다한 설정은 없습니다.

3. Jenkins 설정 및 배포

우리의 목표는 REST API 서버라고 가정하겠습니다. Python fastAPI로 제작하여 샘플 코드는 아래와 같습니다.

# main.py 

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def health_check():
    return 200

@app.get("/status")
async def sayHello():
    return "Hello!!"

필요한 모듈은 fastAPI, uvicorn만 설치되어 있으면 됩니다. 설치가 필요한 모듈을 requirements.txt 파일에 정의합니다.

fastapi>=0.73.0
uvicorn>=0.17.5

터미널에서 "unvicorn main:app"로 실행해 정상적으로 작동이 되는지 확인 해보세요.

docker image 생성을 위해 Dockerfile도 필요하겠네요. http 8000번 포트를 여는 것으로 가정하겠습니다.

# from
FROM ubuntu:latest

# apt init
ENV LANG=C.UTF-8
ENV TZ=Asia/Seoul
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && \
    apt-get install -y sudo

# python stuff
RUN apt-get install -y python3-pip python3-dev
RUN cd /usr/local/bin && \
    ln -s /usr/bin/python3 python && \
    ln -s /usr/bin/pip3 pip && \
    pip3 install --upgrade pip

# apt cleanse
RUN apt-get clean && \
    rm -rf /var/lib/apt/lists/*

# timezone
RUN ln -sf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

COPY . ./

# workspace
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app

COPY . /usr/src/app

# install python packages
RUN pip install -r requirements.txt

# for run server
ENV APP_ENV=dev
EXPOSE 8000
CMD uvicorn main:app --host=0.0.0.0 --port=8000

Jenkinsfile도 아래와 같이 추가합니다.

pipeline{
    agent any

    environment {
       ECR_REPO = "----------------.dkr.ecr.ap-northeast-2.amazonaws.com"
       AWS_CREDENTIALS="aws credential id를 입력하세요."
       GIT_CREDENTIAL_ID = "git credential id를 입력하세요."
       NAME = "hello"
       VERSION = "${env.BUILD_ID}-${env.GIT_COMMIT}"
       GIT_URL="http://your_git_server_ip/git/hello.git"
    }
    stages {
        stage('Pull') {
            steps {
                git url:"${GIT_URL}", branch:"master", poll:true, changelog:true,credentialsId: "${GIT_CREDENTIAL_ID}"
            }
        }
        stage('Build') {
            steps {
                sh "docker build -t ${NAME} ."
                sh "docker tag ${NAME}:latest ${ECR_REPO}/${NAME}:latest"
            }
        }
        stage('ECR Upload'){
            steps {
                script{
                    try{
                        withAWS(credentials: "${AWS_CREDENTIALS}", role: 'arn:aws:iam::"----------------:role/jenkins-deploy-role', roleAccount: "deploy_user", externalId: 'externalId'){
                            sh "aws ecr get-login-password --region ap-northeast-2 | docker login --username AWS --password-stdin "----------------.dkr.ecr.ap-northeast-2.amazonaws.com"
                            sh "docker push ${ECR_REPO}/${NAME}:latest"
                        }
                    }catch(error){
                        print(error)
                        currentBuild.result = 'FAILURE'
                    }
                }
            }
            post{
                success {
                    echo "The ECR Upload stage successfully."
                }
                failure{
                    echo "The ECR Upload stage failed."
                }
            }
        }
    }
}

(*Dockerfile , Jenkinsfile에 대한 상세 내용은 여기에서 다루지 않습니다.)

credentials은 jenkins에 등록된 id를 입력해야 합니다. 

이제 본격적으로 Jenkins 설정을 진행하겠습니다. 우선 Credentials을 추가해야 합니다. Jenkins 관리 -> AWS Configuration에서 Region은 Auto로, Amazon Credentials에서 "Add"버튼을 누릅니다. "Kind"에서 AWS Credentials을 선택 후 ID에는 원하는 이름을 입력하고, 위에서 만들었던 deploy_user의  Access Key ID와 Secret Access Key값을 등록합니다.

이제 Jenkins Dashboard에서 새로운 Item을 생성합니다.

다른 설정은 건너띄고 Pipeline -> Definition에서 "Pipeline script from SCM"을 설정, "SCM"에 git 선택, Repository URL에 위 샘플을 push한 git 저장소 주소를 입력, 등록된 git credential을 선택하고 저장합니다. 그리고 "Build Now"를 클릭해 git clone 후 ECR Upload까지 잘 되는지 확인하세요. 정상적으로 업로드 되었으면 ECR에서 아래와 같이 확인이 가능합니다.

4. ECS 구축

ECS를 구축전에 처리해야 할 몇 가지가 있습니다. 

  • VPC 생성 및 관련 설정
  • Target Group 생성
  • Application load balancer 생성
  • 클러스터 구축

위 내용을 하나씩 살펴보고 ECS 로 다시 넘어오겠습니다. 

4-1. VPC 생성 및 관련 설정

deploy-test라는 이름으로 subnet 2개를 포함하여 vpc를 생성합니다.

"보안 그룹" -> "보안 그룹 생성"을 클릭합니다. 필요한 보안 그룹은 두 개입니다. 하나는 alb에서 유저들이 접속할 수 있는 80포트를 전체에 열어주고, alb에서 fargate task의 8000번으로 접속하는 두 가지 입니다. fargate task에 설정할 방화벽에서 source는 vpc의 CIRD를 입력해야 합니다.

4-2. Target Group 생성

대상 그룹(Target Group)은 일반적으로 오토스케일링을 위한 단위입니다. "EC2 -> 대상 그룹"에서 생성/확인 가능합니다.

ECS Fargate에 생성된 인스턴스들을 대상 그룹으로 설정하면 됩니다. "Create target group"를 클릭하고 target type은 "Instances" 를 선택합니다. "Target group name"을 "deploy-test-tg"로 명명하고 Port는 8000번으로 지정 후 위에서 만든 VPC를 선택합니다. 그리고 Step 2에서 Available instances는 비워두고 "Create target group"을 선택하여 마무리 합니다.

4-3. Application load balancer 생성

"EC2 -> 로드밸런서"로 이동하여 생성합니다. "Application Load Balancer"를 선택, 이름은 "deploy-test-alb"로 명명하고 위에서 생성한 VPC로 선택, subnet은 public으로 모두 선택, Security groups 은 위에서 80포트를 오픈한 sg를 선택합니다. 그리고 "Listeners and routing"에서 http port 80번(ALB에서 80번으로 받아 8000번으로 포트 포워딩하도록 설정합니다.), target group은 위에서 생성했던 deploy-test-tg로 설정합니다. 

이번 샘플에서는 ALB에서 하나의 규칙(80으로 받아 8000번으로 포트 포워딩)만 적용할 것이며, ALB의 기능을 전적으로 사용하지 않습니다. 그래서 위에서 설정한 ALB 기능 중 리스너 기능과 Target group 설정을 지워 비어있는 ALB를 만드세요. 생성한 로드배런서를 선택, 하단의 탭 중 "리스너" 탭을 선택해 설정된 리스너를 삭제하고, 4-2에서 생성한 대상 그룹도 삭제합니다. ECS 서비스 생성 과정 중 "2 네트워크 구성"에서 리스너와 대상 그룹을 다시 지정하게 됩니다.

4-4. 클러스터 구축

클러스터 구축 전에 등록될 도커 이미지를 배포하는 작업 정의를 합니다. 작업 정의는 ECR에 업로드된 도커 이미지를 ECS 클러스터로 배포하는 작업을 정의하는 것입니다.  ECS -> 작업 정의 -> "새 작업 정의 생성" 버튼을 클릭합니다.

"시작 유형 호환성 선택"에서는 FARGATE를 선택한 후 아래와 같이 설정하세요. (참고 사항일 뿐이니 필요한데로 정의하시면 됩니다.)

  • 태스크 정의 이름 : "hello_task" 입력
  • 태스크 역할 : "ecsTaskExecutionRole" 선택
  • 운영 체제 패밀리 : Linux 선택
  • 작업 메모리 : 0.5GB
  • 작업 CPU : 0.25 vCPU
  • 컨테이너 추가 
    • 컨테이너 이름 : hello_container
    • 이미지 : ----------------.dkr.ecr.ap-northeast-2.amazonaws.com/hello (hello라는 이름으로 이미지를 업로드 한다고 가정하겠습니다.)
    • 메모리 제한 : "소프트 제한" 선택 / "256" 입력
    • 포트 매핑 : "8000" 입력 / "tcp" 선택
    • "추가" 버튼을 클릭하고 컨테이너 설정 마무리.
Soft limit : 컨테이너가 예약해 사용할 메모리 용량
Hard limit : 해당 메모리 이상으로 사용 시 컨테이너 종료

이제 "생성" 버튼을 눌러 작업 정의를 마무리 합니다.

이제 클러스터를 생성해 보겠습니다. ECS Dashboard에서 "클러스터 생성"를 클릭하고, "네트워킹 전용"을 선택하고 "다음 단계"로 넘어간 후 클러스터 이름을 "hello-cluster"로 입력하고 "생성"버튼을 눌러 마무리합니다.

생성된 클러스터 내에서 서비스 탭 내의 "생성" 버튼을 눌러 다음과 같이 설정합니다.

클러스터에서 Task Definition를 통해 하나의 작업을 생성할 수 있으며, Service를 생성해 여러 작업들을 생성, 관리할 수 있습니다. Service에서 더 많은 설정을 할 수 있고 추후 Auto Scaling 도 살펴볼 예정이니 Service를 생성해서 작동 여부를 확인하겠습니다.

Service 생성을 시도하여 "시작 유형"에 "FARGATE", "서비스 이름"에 원하는 이름을, "작업 개수"는 서비스 초기에 실행할 인스턴스(task) 개수를 입력합니다. 여기서는 1개만 실행하도록 하겠습니다.

"Minimum healthy percent"는 healthy 상태를 유지하는 최소 인스턴스 수를 말하며 만약 50%로 정의한다면 기존에 10개의 인스턴스가 있었고 어떤 이유로 인스턴스가 종료되다면 최소 10개의 50%인 5개는 유지되도록 설정한다는 의미입니다. 여기서는 1개의 인스턴스를 실행하므로 100%로 설정해 두겠습니다. 

"Maximum percent"는 인스턴스들의 최대 활성 상태의 비율을 말하며, 신규 배포, 서비스 과부하로 인한 증가 등의 이유로 인스턴스가 증가한다고 가정할 때 활성화 될 최대 인스턴스 비율을 말합니다. 최초 10개의 인스턴스가 활성화 되어있고 이 수치가 200%라면 20개 이상으로 늘어나지 않게 설정한다는 것을 의미합니다.

Next Step에서 "Deployments"를 선택할 수 있으며 "Rolling Update"는 한 인스턴스를 실행하고, 하나를 중단하면서 순서대로 전체를 배포하는 방식을 말합니다. "Blue/gree deployment"는 한번에 새로운 인스턴스를 모두 가동 후 기존 인스턴스를 종료하는 방식이며, 이는 CodeDeploy 설정이 필요합니다. 우리는 일단 Rolling Update를 선택하고 다음으로 넘어갑니다.

네트웍 설정에서 Cluster VPC는 위에서 만들었던 VPC와 public subnet을 선택하고 security group은 8000번을 오픈한 sg를 선택한 후 Auto-assign public IP는 ENABLED를 선택(*생성되는 모든 Task에 public ip를 할당하게 됩니다만 일단 넘어갑니다.)합니다.

이후 Load balancing에서 "Application Load Balancer"를 선택, 위에서 생성했던 Load Balancer를 선택 후 "로드 밸런싱할 컨테이너"의 "로드 밸런서에 추가" 버튼을 클릭합니다.

"Production listener port" 는 Load Balancer의 포트이며, container과 상관없이 설정 가능합니다. alb에서는 80포트로 받을 예정이니 80으로 값을 입력하세요.

"Target group name"에는 Load Balancer에 붙일 대상 그룹을 선택합니다.

경로 패턴 입력값이 수정 가능한 상태라면 alb 생성 후 listener를 초기화 하지 않았기 때문입니다. 위 과정으로 돌아가거나 혹은 /*로 수정하셔도 무방합니다. 

Auto Scaling은 건너띄고 설정을 마무리 하면 됩니다. 

Auto Scaling에 대한 설정을 하려면 다음을 참고해서 설정하시면 됩니다.
"Minimum number of tasks"  >>  최소 Task 수 를 지정합니다.
"Desired number of tasks"     >>  초기화 시 할당할 Task 수를 지정합니다. (Minimum보다 크거나 같게 설정)
"Maximum number of tasks"  >>  최대로 늘어나게 할 Task 수 를 지정합니다.
[Automatic task scaling policies] >> 오토 스케일링 정책을 설정합니다.
 - Scaling policy type에서 Target tracking을 선택하면 설정한 정책에 걸리면 알람만 전달하고, Stop Scaling을 선택하면 설정한 정책에 걸리면 Scaling을 중지하고 알람을 전송합니다.
나머지 설정은 추후 상세히 정리해 보겠습니다.  저도 아직 다 몰라서... -0-;

지금까지가 ECS 설정하는 과정이었습니다. 이제 우리 서비스를 docker image로 등록하여 서비스 가능한지 확인해 보겠습니다.

5. 서비스 시작하기

docker image가 등록되면 ecs에 등록했던 작업이 자동으로 작동되어 cluster에 fargate 한 개가 작동하는 것을 확인할 수 있습니다.

업데이트된 서비스의 로그는 ECS 클러스터 -> [클러스터명] -내부의 "로그" 탭에서 로그를 확인할 수 있습니다.

외부에서 접근 가능한 URL은 EC2 -> 로드 밸런서 -> [서비스에 연결된 로드 밸런서] 선택 하면 설명 -> DNS 이름 에 있습니다.

DNS 이름을 복사한 후 우리의 서비스가 사용하는 포트 번호를 붙여 접근할 수 있습니다.

서비스가 정상적으로 실행되고 있다면 위와 같은 결과를 확인할 수 있습니다.

위에서 우리는 public subnet도 추가했으므로 ECS -> 클러스터 -> "작업" 탭에서 생성된 작업(인스턴스) 상세 정보에 public ip를 통해서도 접근할 수 있습니다. 

수고하셨습니다. 사실 다른 사람의 데모 영상을 보면서 따라하면 잘 안되는 것도 있고, 미리 세팅된 정보들도 있어서 빠진 내용들이 많아 직접 하다보면 뭐가 잘못된 상태인지 모를때가 많습니다. 그래서 처음부터 한번 정리해 보았습니다.

하지만 이번 글에서는 fargate task를 public subnet에 배치했기 때문에 불필요한 public ip를 사용했으며, 외부로 task 자체를 오픈할 것이 아니기 때문에 적절한 예는 아닙니다. 다음 글에서는 fargate task를 private subnet에 배치하여 사용하는 방법을 정리하겠습니다.

긴 글 읽어 주셔서 감사합니다. :)

(* 보통 짧게 노하우 정리 정도만 하다가 기~~~ㄴ 글을 적어보니 얼마나 글을 정리 못하는지 알게 되네요. 정리가 엉성해도 오늘은 너무 길어져서 이만 정리하고 다음에 읽으면서 어색하고 잘못된 부분들 정리하겠습니다. ^^)

 

*참고한 아티클 및 영상

https://tech.cloud.nongshim.co.kr/2021/08/30/hands-on-ci-cd-jenkins-pipeline%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-ecs-%EB%B0%B0%ED%8F%AC/

https://www.youtube.com/watch?v=bEr_98NRlzc 

 

반응형