본문 바로가기
Study/DevOps

[Docker_CI/CD_스터디] 3. GitLab으로 AWS Cloud로의 지속적인 배포

by Lpromotion 2025. 2. 4.

목차
 

1. ECS로 Docker Application 배포

ECS Cluster 생성

AWS Consol > ECS(Elastic Container Service) > 클러스터 > 클러스터 생성

 

컨테이너를 실행하기 위해서는 Task Definition이 필요하다.

메뉴 > 태스크 정의 > 새 태스크 정의 생성

이미지 URI에 ECR URI을 넣는다. (ECR 페이지에서 확인 가능)

 

다음은 서비스를 만들어서 이 태스크 정의서를 이용해 컨테이너를 만들도록 실행하면 된다.

클러스터 안에 서비스를 하나 만들고, 이 서비스가 애플리케이션, 즉, 컨테이너를 실행한다.

이 컨테이너는 Task Definition 사양을 이용하여 실행을 하게 된다.

 

ECS Service 실행

클러스터 > 서비스 > 생성

네트워킹은 컨테이너가 실행될 서브넷을 설정하는 것이다.

서브넷은 가용성을 위해 최소 2개 이상 선택해 주어야 한다. `16.0`, `32.0` 두 개의 서브넷을 선택했다.

보안 그룹은 새로 만들어준다. 

인바운드 규칙은 Flask app을 위해 5000번 포트와, 로드 밸런서를 위해 80번 포트를 열어주었다.

리스터는 사용자가 접근할 ALB(애플리케이션 로드 밸런서)의 포트 번호라고 생각하면 된다. (80번)

80번 포트로 들어온 유저는 sparta-app-tg 로 보내는 것이다. 나중에 이 타겟 그룹에는 인스턴스 혹은 컨테이너가 들어가게 된다. 여기서는 컨테이너가 들어간다.

지금 서비스가 만들어지게 되면, 컨테이너가 새로 만들어진다. 그 컨테이너들이 이 타겟 그룹 안에 들어가게 된다.

결과적으로 사용자가 80번 포트로 요청하면, 만든 컨테이너로 요청이 전송된다. 

 

 

2. ECS Application 테스트

서비스

 

태스크

두 개의 태스크가 성공적으로 실행되는 것을 확인할 수 있다.

 

로드 밸런서 확인

만들어진 로드 밸런서를 확인한다.

EC2 > 로드 밸런싱 > 로드 밸런서

HTTP:80 번 포트로 사용자 요청이 들어오면 sparta-app-tg 대상 그룹(타겟 그룹)으로 사용자 요청을 전달하라는 의미이다.

 

타겟(대상) 그룹 선택

타겟 그룹에 두 개의 컨테이너가 등록되어 있다.

 

연결 테스트

로드밸런서가 잘 실행 됐다면 웹 브라우저에서 DNS 이름으로 접속할 수 있다.

로드 밸런서 > "DNS 이름" 복사 > 터미널

curl sparta-app-alb-679634336.ap-northeast-2.elb.amazonaws.com

요청이 성공적으로 출력된다.

 

지금까지 "메뉴얼"하게 컨테이너 배포를 확인했다. 이제 이 과정을 자동화하면 된다.

 

 

3. GitLab으로 ECS 배포 자동화 하기

IAM 설정

GitLab Runner가 배포한다.

GitLab Runner에서 ECS를 컨트롤할 수 있는 AWS CLI 명령을 이용해 ECS Cluster를 만들거나 Service를 새로 실행하는 등의 작업을 수행할 수 있다. 

현재 ECR에 새로운 애플리케이션이 올라와 있다. 코드 내용을 수정하여 새로운 버전의 Docker Image를 올리고, Service를 새로 실행할 것이다. 이 과정을 테스트 해볼 것이다.

 

그전에, AWS CLI 명렁을 실행하여 ECS를 컨트롤하기 위해서는, ECS를 제어하는 리소스에 대한 권한을 가지고 있어야 한다. 

권한을 가지고 있는지 먼저 확인해보겠다.

 

AWS > IAM > 사용자

만들었던 ecr-user 의 권한을 보면, 테스트를 쉽게 하기 위해서 “AdministratorAccess” 권한을 할당했었다.

그렇다면 aws ecs 명령도 수행 할 수 있다.

ecs에 접근권한 있는지 테스트 해본다.

aws ecs describe-clusters --clusters test-app-cluster --output json

클러스터를 정보를 얻어오는 명령이다. 

 

ubuntu@ubuntu-VirtualBox:~$ aws ecs describe-clusters --clusters test-app-cluster --output json
{
    "clusters": [
        {
            "clusterArn": "arn:aws:ecs:ap-northeast-2:277707140590:cluster/test-app-cluster", // 리소스 이름
            "clusterName": "test-app-cluster",
            "status": "ACTIVE", // 작동 중
            "registeredContainerInstancesCount": 0,
            "runningTasksCount": 2, // 태스크 2개
            "pendingTasksCount": 0,
            "activeServicesCount": 1, // 서비스 1개
            "statistics": [],
            "tags": [],
            "settings": [],
            "capacityProviders": [
                "FARGATE",
                "FARGATE_SPOT"
            ],
            "defaultCapacityProviderStrategy": [],
            "serviceConnectDefaults": {
                "namespace": "arn:aws:servicediscovery:ap-northeast-2:277707140590:namespace/ns-vtjx2i2msmkie54g"
            }
:

최소 ECS에 대한 읽기 권한을 가지고 있다는 의미이다.

 

AWS CLI로 ECS 서비스 실행

이번엔 ECS 명령으로 서비스를 새로 실행해보겠다.

ECS 서비스를 새로 시작하는 aws ecs 명령이다.

aws ecs update-service \
--cluster test-app-cluster \
--service sparta-app-srv \
--task-definition test-app:1 \
--force-new-deployment \
--output json

 

  • `aws ecs update-service` → ECS 서비스 설정 업데이트
  • `--cluster` → 클러스터 지정
  • `--service` → 서비스 지정
  • `--task-definition` → 배포할 태스크 정의 지정
  • `--force-new-deployment` → 같은 설정이어도 강제로 새 배포 진행

 

명령을 실행하면 update-service 가 실행되며, AWS console에서 확인 할 수 있다.

ECS > 클러스터 > Deployments and tasks를 보면 롤링 업데이트 정책에 따라서 하나씩 task가 업데이트 되는 것을 확인 할 수 있다.

배포 전으로 롤링 업데이트를 선택했는데, 롤링 업데이트는 서비스가 전부 내려갔다가 올라오는 것이 아니라, 서비스를 구성하고 있는 인스턴스 혹은 컨테이너가 하나씩 내려간다. 

그래서 현재 버전의 컨테이너가 내려가고, 새로운 버전의 컨테이너로 채우는 방식(롤링)으로 업데이트가 실행된다. 이 방식은 서비스가 중단없이 계속해서 새로운 서비스로 업데이트 할 수 있다.

 

GitLab으로 ECS 서비스 배포 자동화

위에서 메뉴얼하게 실행했던 aws ecs 명령을 GitLab으로 자동화하면 된다. 

 

GitLab runner는 make 명령을 실행하므로 아래와 같이 `deploy:` 부분을 수정한다. aws ecs 명령을 그대로 입력하면 된다.

Makefile

PRJ_NAME=teamjoinc/test-app
ECR_URI=277707140590.dkr.ecr.ap-northeast-2.amazonaws.com
VERSION:=$(shell git rev-parse --short HEAD)


build:
	docker build -t $(PRJ_NAME):$(VERSION) .
	
push:
	aws ecr get-login-password --region ap-northeast-2 | docker login --username AWS --password-stdin $(ECR_URI)
	docker tag $(PRJ_NAME):$(VERSION) $(ECR_URI)/$(PRJ_NAME):$(VERSION)
	docker tag $(PRJ_NAME):$(VERSION) $(ECR_URI)/$(PRJ_NAME):latest
	docker push $(ECR_URI)/$(PRJ_NAME):$(VERSION)
	docker push $(ECR_URI)/$(PRJ_NAME):latest

deploy:
	aws ecs update-service --cluster test-app-cluster --service sparta-app-srv --task-definition test-app:2 --force-new-deployment --output json

 

gitlab CICD가 `make deploy` 를 실행하도록 Pipeline editor로 수정해준다.

.gitlab-cicd.yml

stages:          # List of stages for jobs, and their order of execution
  - build
  - test
  - deploy

build-job:       # This job runs in the build stage, which runs first.
  stage: build
  script:
    - echo "Docker build start"
    - make build
    - echo "Docker build complete."

push-job:       # This job runs in the build stage, which runs first.
  stage: build 
  script:
    - echo "Docker push start"
    - make push
    - echo "Docker push complete."

unit-test-job:   # This job runs in the test stage.
  stage: test    # It only starts when the job in the build stage completes successfully.
  script:
    - echo "Running unit tests."
    - sleep 5 
    - echo "Unit tests complete."

lint-test-job:   # This job also runs in the test stage.
  stage: test    # It can run at the same time as unit-test-job (in parallel).
  script:
    - echo "Linting code... This will take about 10 seconds."
    - sleep 5 
    - echo "No lint issues found."

deploy-job:      # This job runs in the deploy stage.
  stage: deploy  # It only runs when *both* jobs in the test stage complete successfully.
  environment: production
  script:
    - echo "Deploying application..."
    - make deploy
    - echo "Application successfully deployed."

 

이제 새로운 코드가 올라오면 build → test → deploy state를 거치게 된다. deploy state 는 `deploy-job` 을 실행하고 `make deploy` 가 실행되면서 `aws ecs update-service` 를 수행하게 된다.

 

테스트

코드를 수정하고 push 한다.

app.py

from flask import Flask

app = Flask(__name__)

@app.route('/') # Route URL 호출
def home():
    return '<h1>Hello World Version 2</h1>'

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

 

GitLab에서 새로운 Pipline이 실행되는 것을 확인할 수 있다.

 

pipline이 성공적으로 실행되고 나서 task를 update 하는데 시간이 걸린다. 배포 상태는 ECS > 클러스터 > 서비스 에서 확인할 수 있다.

 

서비스가 배포 성공한 후, 웹 브라우저를 이용해 "로드밸런서 - DNS 이름"으로 접속하면 변경된 코드를 확인할 수 있다.

 

또는 curl 명령으로도 확인할 수 있다.

 

전체 구성

  1. 개발자가 코드를 개발한다.
  2. 개발자가 코드를 GitLab 프로젝트에 push 한다.
  3. 프로젝트에 등록된 GitLab Runner가 변경사항을 확인하고 CI/CD 파이프라인을 실행한다.
  4. 이 파이프라인은 build → test → deploy 3개의 stage로 구성되어 있다.
  5. build stage에서는 docker image를 만들고 ECR 에 push 한다.
  6. test stage는 지금은 echo 로 문자열만 출력하고 있다. 나중에 각 언어에 맞게 UnitTest, Test Coverage 스크립트를 추가하면 된다.
  7. deploy stage에서 aws ecs 명령을 이용해서 새로운 서비스를 실행한다.

개발자가 코드를 push 하면 나머지는 GitLab이 알아서 수행한다.

반응형

댓글