Abstract
최근 배포 프로세스는 프로젝트를 Docker
이미지로 말아서 AWS ECS/ECR
조합으로 가는 것이 일반적이다.
그러나 라이트유저에겐 상대적으로 사악할 수 있는 가격 정책을 보여주는 AWS ECS
이기에 이번 포스팅에서는 그냥 기본 서버 환경을 제공하는 AWS EC2
에 AWS ECR
이미지를 올리는 방법을 소개하고자 한다.
추가로 대세 CI 툴인 CircleCI
를 통해 파이프 라인을 구성해보았다.
참고할만한 링크: https://circleci.com/developer
Getting Started
Prerequisite
-
AWS EC2
인스턴스 생성 -
Docker
docker compose
설치 작업할 Project Repository
Preparing AWS ECR
AWS ECR (Elastic Container Registry) 서비스에 들어가 Repository를 생성
다른 설정은 기본으로 name 만 잘 설정해주면 앞으로 이미지를 저장할 레포지토리가 생성된다.
여기서 중요한 것은 Repository name
과 URI
. 기억해두자.
Setting up CircleCI Application
먼저 프로젝트에서 /.circleci/config.yml
empty 파일을 생성한 후 깃레포지토리에 푸시
Project tree
.
├── ./circleci/
│ └── config.yml
└── ...project
CircleCI
에 로그인한뒤 프로젝트 탭에서 Set Up Project
를 선택
작업할 branch
이름를 입력해주면 다음처럼 메세지가 나오고 진행하면 프로젝트 대쉬보드로 진입
Project Setting
에 환경 변수 입력
실제 프로젝트에서 config.yml
설정할때 사용하게 될 변수들
추가로 SSH Keys
메뉴에서 EC2
접속을 위해 Additional SSH Keys
를 등록해준다
안할 경우 CircleCi
에서 EC2
접근 불가할 수 있음
해당하는 키 생성 방법 링크 참조: https://circleci.com/docs/add-ssh-key/
Required Env Value
- AWS_ACCOUNT_ID
- AWS_ACCESS_KEY_ID
- AWS_SECRET_ACCESS_KEY
- AWS_REGION
- AWS_ECR_ACCOUNT_URL
- AWS_ECR_REPO_NAME
Generating Dockerfile
먼저 로컬에서 Docker image
를 말아보자.
여기서는 자세한 Docker
사용법은 언급하지 않고 넘어가겠다.
참고할만한 링크: https://docs.docker.com/get-started/
Root 폴더에 Dockerfile
을 생성, 다음과 같이 작성
Doockerfile
ARG node_version=18-bullseye-slim
ARG prod_port=3000
FROM node:${node_version}
WORKDIR /app
COPY . .
ENV TZ=Asis/Seoul
RUN corepack enable
RUN corepack prepare pnpm@latest --activate
RUN pnpm install
RUN pnpm build
EXPOSE ${prod_port}
CMD pnpm start:prod
node 18
버전 환경에서 작업: 18-bullseye-slimnode 18
에서pnpm
패키지는corepack
으로 활성 가능하다
Configuring CircleCI in project
Root 폴더에 /.circleci
폴더를 생성
해당하는 폴더 내에 config.yml
파일을 작성해보자
맨 첫줄은 CircleCI version
부분이다.
두번째 orbs
란 CircleCI
가 제공하는 편의성 패키지
여기서는 ecr
을 편리하게 지원해주는 aws-ecr
패키지를 사용
executor
는 여기서 반복적으로 사용할 환경 실행에 대한 언급
- machine:image:ubuntu-2004:current
보통 CircleCI
에서는 직접 Docker
환경을 불러오는 것이 보통이나
여기서는 이미지에 프로젝트를 복사해서 직접 명령어를 실행하는것이 아닌
Dockerfile
로 이미지를 빌드 후 AWS ECR
에 업로드할 예정이기에
다양한 명령어 접근을 위해 이미지가 아닌 machine
레벨로 구성하였다. (여기서는 ubuntu
환경을 구성)
- working_directory: ~/project
/project
폴더에 작업할 예정 (checkout
도 이 폴더에서 진행된다)
config.yml
version: 2.1
orbs:
aws-ecr: circleci/aws-ecr@8.2.1
executors:
machine-executor:
machine:
image: ubuntu-2004:current
docker_layer_caching: true
working_directory: ~/project
# ... more on next line
기본적으로 CircleCI
는 Job
을 Jobs
에 정의하고
workflow
에 흐름을 등록하는 방식으로 진행된다.
여기서는 Pre-Build
라는 첫번째 job
을 선언하였다.
미리 선언해 주었던 machine-executor
를 이용해 환경 구성을 진행하고
- checkout
checkout
을 통해 연결된 git repository
를 복사한다. (/project
폴더 안에 복사)
- Getting Node Environment / the Code
여기에서는 machine
의 Node Environment
확인, checkout
정상 진행여부 확인
- persist_to_workspace
마지막으로 이 전체를 다음 job
에 동일하게 진행할 예정이기에 workspace
에 관련 부분을 선언
Note
CircleCI
어플리케이션 설정은 다음 챕터에 설명 (Github
연결 포함)workspace
개념은 다음 링크 참조: https://circleci.com/docs/workspaces/
config.yml
# ... continue
jobs:
Pre-Build:
executor: machine-executor
steps:
- checkout
- run:
name: Getting Node Environment
command: |
node -v
npm -v
npm -g ls
echo '^^^ node default env^^^'
- run:
name: Getting the Code
command: |
ls -al
echo '^^^ Your repo files^^^'
- persist_to_workspace:
root: .
paths:
- .
# ... more on next line
두번째 job
선언 부분
- attach_workspace
여기서는 방금 전 step에서 저장한 환경을 다시 load한다.
- Attach Workspace Complete
그 다음에 환경이 다시 잘 구성되었는지 확인
- aws-ecr/build-and-push-image
마지막으로 aws-ecr
orb
설정 부분이다.
이 orb
는 따로 복잡한 설정 필요없이 간단한 설정만으로
도커로 이미지 빌드후 등록한 AWS ECR
에 이미지를 푸시해주는 역할을 해준다.
$AWS_ECR_REPO_NAME
은 ECR Repo 이름이고 $CIRCLE_SHA1
는 CircleCI
에서 자동으로 생성하는 난수
image_name:tag
name 형식으로 AWS ECR
에 이미지 생성
Note
- 원래라면
aws-ecr
orb
를 사용하려면 AWS Account 및 다양한 정보를 설정해야하지만 미리 환경변수로 선언해두면 이부분을 생략할 수 있다.aws-ecr
상세한 내용은 다음 링크 참조: https://circleci.com/developer/orbs/orb/circleci/aws-ecr
config.yml
# ... continue
jobs:
# ... Pre-Build
Build-and-Push:
executor: machine-executor
steps:
- attach_workspace:
at: ~/project
- run:
name: Attach Workspace Complete
command: |
ls- al
echo '^^^ Attaching workspace success. ^^^'
- aws-ecr/build-and-push-image:
repo: $AWS_ECR_REPO_NAME
tag: $CIRCLE_SHA1
dockerfile: Dockerfile
# ... more on next line
마지막 job
인 Deploy
- Allow Access to Production EC2
먼저 첫번째 step 에서는 AWS EC2
에 ssh
를 이용해 접속하기 위해 22번 포트를 개방
- Waiting for AWS Security Settings to Take Effect
적용 하는데 다소 시간이 필요하기에 sleep
을 실행
- AWS EC2 Deploy
AWS EC2
에 접속 후 서버 환경에서 Docker
login 후 ECR에 등록된 Docker Image
를 pull 해온다.
이 이미지를 미리 AWS EC2
에 작성해둔 docker-compose.yaml
을 통해 서버에서 실행할 예정
docker-compose.yaml
파일은 infra
폴더안에 위치할 예정
이때 docker-compose.yaml
에서 사용할 환경변수를 미리 선언
- Remove Circle CI Instance Ingress Rule
일전에 개방한 22번 포트를 다시 폐쇄
config.yml
# ... continue
jobs:
# ... Pre-Build, Build ..
Deploy:
executor: machine-executor
steps:
- run:
name: Allow Access to Production EC2
command: |
CIRCLE_CI_BUILD_MACHINE_IP=$(curl ipinfo.io/ip)
aws ec2 authorize-security-group-ingress --region $AWS_REGION \
--group-id $PROD_SERVER_SG_ID \
--protocol tcp \
--port 22 \
--cidr $CIRCLE_CI_BUILD_MACHINE_IP/24
- run:
name: Waiting for AWS Security Settings to Take Effect
command: sleep 5
- run:
name: AWS EC2 Deploy
command: |
ssh -o StrictHostKeyChecking=no $AWS_DEPLOYER@$EC2_PUBLIC_DNS \
"sudo docker login --username AWS -p $(aws ecr get-login-password --region $AWS_REGION) $AWS_ECR_ACCOUNT_URL && \
sudo docker pull $AWS_ECR_ACCOUNT_URL/$AWS_ECR_REPO_NAME:$CIRCLE_SHA1 && \
export CIRCLE_SHA1=$CIRCLE_SHA1 && \
export AWS_ECR_ACCOUNT_URL=$AWS_ECR_ACCOUNT_URL && \
export AWS_ECR_REPO_NAME=$AWS_ECR_REPO_NAME && \
export PROD_PORT=3000 && \
cd /home/ec2-user/infra && \
docker compose up -d hello-world"
- run:
name: Remove Circle CI Instance Ingress Rule
command: |
CIRCLE_CI_BUILD_MACHINE_IP=$(curl ipinfo.io/ip)
aws ec2 revoke-security-group-ingress --region $AWS_REGION \
--group-id $PROD_SERVER_SG_ID \
--protocol tcp \
--port 22 \
--cidr $CIRCLE_CI_BUILD_MACHINE_IP/24
# ... more on next line
config.yml
전문은 다음과 같다
기본 workflow
상태에서는 설정한 branch
(여기서는 main
) 소스를 푸시하면 바로 빌드가 시작
여기서는 따로 릴리즈 태그를 등록해야 배포 프로세스가 시작하도록 설정하였다.
config.yml
version: 2.1
orbs:
aws-ecr: circleci/aws-ecr@8.2.1
executors:
machine-executor:
machine:
image: ubuntu-2004:current
docker_layer_caching: true
working_directory: ~/project
jobs:
Pre-Build:
executor: machine-executor
steps:
- checkout
- run:
name: Getting Node Environment
command: |
node -v
npm -v
npm -g ls
echo '^^^ node default env^^^'
- run:
name: Getting the Code
command: |
ls -al
echo '^^^ Your repo files^^^'
- persist_to_workspace:
root: .
paths:
- .
Build-and-Push:
executor: machine-executor
steps:
- attach_workspace:
at: ~/project
- run:
name: Attach Workspace Complete
command: |
ls- al
echo '^^^ Attaching workspace success. ^^^'
- aws-ecr/build-and-push-image:
repo: $AWS_ECR_REPO_NAME
tag: $CIRCLE_SHA1
path: docker
dockerfile: Dockerfile.prod
Deploy:
executor: machine-executor
steps:
- run:
name: Allow Access to Production EC2
command: |
CIRCLE_CI_BUILD_MACHINE_IP=$(curl ipinfo.io/ip)
aws ec2 authorize-security-group-ingress --region $AWS_REGION \
--group-id $PROD_SERVER_SG_ID \
--protocol tcp \
--port 22 \
--cidr $CIRCLE_CI_BUILD_MACHINE_IP/24
- run:
name: Waiting for AWS Security Settings to Take Effect
command: sleep 5
- run:
name: AWS EC2 Deploy
command: |
ssh -o StrictHostKeyChecking=no $AWS_DEPLOYER@$EC2_PUBLIC_DNS \
"sudo docker login --username AWS -p $(aws ecr get-login-password --region $AWS_REGION) $AWS_ECR_ACCOUNT_URL && \
sudo docker pull $AWS_ECR_ACCOUNT_URL/$AWS_ECR_REPO_NAME:$CIRCLE_SHA1 && \
export CIRCLE_SHA1=$CIRCLE_SHA1 && \
export AWS_ECR_ACCOUNT_URL=$AWS_ECR_ACCOUNT_URL && \
export AWS_ECR_REPO_NAME=$AWS_ECR_REPO_NAME && \
export PROD_PORT=3000 && \
cd /home/ec2-user/infra && \
docker compose up -d allco-kids-backend"
- run:
name: Remove Circle CI Instance Ingress Rule
command: |
CIRCLE_CI_BUILD_MACHINE_IP=$(curl ipinfo.io/ip)
aws ec2 revoke-security-group-ingress --region $AWS_REGION \
--group-id $PROD_SERVER_SG_ID \
--protocol tcp \
--port 22 \
--cidr $CIRCLE_CI_BUILD_MACHINE_IP/24
workflows:
build_and_deploy_image:
jobs:
- Pre-Build:
context: fetch-code
filters:
tags:
only: /server-v\d{1}\.\d{1,2}\.\d{1,3}/ # 버전 정규식 server-v1.0.0의 형태
branches:
ignore: /.*/
- Build-and-Push:
requires:
- Pre-Build
filters:
tags:
only: /server-v\d{1}\.\d{1,2}\.\d{1,3}/ # 버전 정규식 server-v1.0.0의 형태
branches:
ignore: /.*/
- Deploy:
requires:
- Pre-Build
- Build-and-Push
filters:
tags:
only: /server-v\d{1}\.\d{1,2}\.\d{1,3}/ # 버전 정규식 server-v1.0.0의 형태
branches:
ignore: /.*/
Setting up docker-compose on EC2
EC2
에 접속해서 root
에 /infra
폴더를 생성
그 안에 docker-compose.yaml
을 생성한 뒤 다음과 같이 작성
docker-compose.yaml
version: '3.9'
services:
hello-world:
image: ${AWS_ECR_ACCOUNT_URL}/${AWS_ECR_REPO_NAME}:${CIRCLE_SHA1}
restart: always
ports:
- ${PROD_PORT}:${PROD_PORT}
volumes:
- ../src:/app/src
이제 github
에 태그를 생성하면 자동으로 CircleCI
에서 배포 프로세스가 시작될 것이다.
ECR
에 이미지도 정상적으로 생성된 것을 확인할 수 있다.
Conclusion
CircleCI
를 쓰면서 느낀점은 Jenkins
보다는 훨씬 직관적인데다 미관적으로 더 낫다 라는 점이었다.
이런 부분이 작업하는 중간중간 만족감을 주었으며, 특히, SasS 레벨에서 환경 변수를 따로 관리해주는 부분은 보안 부분에 있어서도 훨씬 간단하고 쉽게 접근할 수 있다는 장점을 느낄 수 있었다.
여기에는 언급하지 않았지만 CircleCI
를 통해 ECS
로 배포하는 과정은 aws-ecs
orb를 통해 훨씬 간단하게 진행할 수 있으나 Abstract에서 언급했다시피 AWS ECS
는 가격부분에서 다소 부담스러운게 사실이기 때문에 여기서는 언급하지 않았다.
본 포스팅에서는 EC2
를 사용하여 적은 비용으로 배포를 진행할 수 있는 나름의 접근 방법을 소개하였다.
물론, ECR
을 쓰지않고 Gitlab
등을 통해 무료로 이미지를 관리할 수 있으나 그 부분은 차후 기회가되면 소개하도록 하겠다.
Top comments (0)