도커로 프로젝트 실행 환경을 구성했다면, CI/CD를 보다 수월하게 구현하는 것이 가능하다.
Docker Hub + Elastic Beanstalk(EB) + Github Action 세가지를 적절히 잘 사용해서 CI/CD 환경을 구성해보자.
전체적인 플로우는 아래와 같다.
1. git flow에서 release 또는 hotfix 작업을 통해 github 리파지토리의 main 브랜치에 업데이트가 발생
2. Github Action에서 main 브랜치의 변경을 감지하고, 작성해 둔 Workflow를 실행
2.1. docker login
2.2. docker build & push
-> Docker Hub에 새로운 버전의 이미지가 push 되어 올라감
2.3. ELB에서 Docker Hub의 이미지를 받아와서 앱을 재배포하도록 명령
결국 필요한 것은 Github Action에서 변경사항을 감지해서, 배포에 필요한 일련의 과정들을 수행하도록 하는 것이다.
먼저 Docker Hub 계정을 생성하고, Elastic Beanstalk environment를 생성해야 한다.
또한 ELB에 접근할 수 있는 권한을 가진 AWS IAM 계정도 필요하다.
해당 부분은 간단하기도 하고, 다른 분들의 블로그에도 잘 설명되어 있어서 생략한다.
향후 과정에서 필요한 것은 Docker Hub username/password, EB 환경 이름 / 앱 이름, AWS IAM 유저의 Access Key/Secret Key 이다.
Repository 화면에서 Settings - Secrets and Variables - Actions 항목으로 이동하면 Github Action에서 사용할 Secret들을 등록하는 페이지가 나온다.
저기서 Repository Secret에 아까 준비해둔 정보들을 등록해준다.
(Docker Hub username/password, EB 환경 이름 / 앱 이름, AWS IAM 유저의 Access Key/Secret Key)
Github Action에 필요한 Workflow들은 [프로젝트/.github/workflows] 폴더 하위에 파일들로 작성한다.
배포에 필요한 워크플로우들을 deploy.yml에 정리했다.
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout source code
uses: actions/checkout@v2
- name: Login to Docker hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Docker build and push
uses: docker/build-push-action@v2
with:
context: .
file: ./Dockerfile
push: true
tags: ${{ secrets.DOCKERHUB_USERNAME }}/node-project:latest
- name: Beanstalk Deploy
uses: einaregilsson/beanstalk-deploy@v20
with:
aws_access_key: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws_secret_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
application_name: ${{ secrets.AWS_APPLICATION_NAME }}
environment_name: ${{ secrets.AWS_ENVIRONMENT_NAME }}
region: ap-northeast-2
deployment_package: Dockerrun.aws.json
크게 3가지의 스텝으로 워크플로우가 구성되어 있다 - [docker login, docker build & push, EB deploy]
처음에는 github action에서 제공하는 docker/login-action@v1 이나 docker/build-push-action@v2 을 사용하지 않고,
run 키워드를 통해 직접 docker login, docker build, docker tag, docker push 하도록 작성했다.
- name: Docker build
run: |
docker login -u ${{ secrets.DOCKERHUB_USERNAME }} -p ${{ secrets.DOCKERHUB_TOKEN }}
docker build -t nimble-client .
docker tag nimble-client jadenkim5179/nimble-client:latest
docker push jadenkim5179/nimble-client:latest
하지만 docker push 과정에서 denied: requested access to the resource is denied
에러가 지속적으로 발생했다
이렇게 작성할 경우 정상적으로 로그인 처리가 잘 안 되는 것으로 보이므로, 위에서와 같이 docker/login-action@v1, docker/build-push-action@v2을 각각 사용해서 docker login, docker build & push 하도록 하자
2번에서 작성한 코드의 마지막 부분을 살펴보면 deployment_package: Dockerrun.aws.json
로 지정한 것을 볼 수 있다.
이는 EB에서 도커를 어떤 방식으로 실행할지 정의하는 파일이다.
docker run 커맨드 실행 시 적어주는 옵션들, 이미지 명을 해당 파일에서 지정하게 된다.
[프로젝트/Dockerrun.aws.json] 경로에 작성해두자 (원한다면 docker-compose.yml로 작성하는 것도 가능하다)
{
"AWSEBDockerrunVersion": "1",
"Image": {
"Name": "jaden/node-project",
"Update": "true"
},
"Environment": {
"DATABASE_HOST": "$DATABASE_HOST",
"DATABASE_USER": "$DATABASE_USER",
"DATABASE_PASSWORD": "$DATABASE_PASSWORD",
},
"Ports": [
{
"ContainerPort": 3000,
"HostPort": 5000
}
]
}
DATABASE_HOST, DATABASE_USER, DATABASE_PASSWORD는 환경 변수로 등록하는 값들이다.
elastic beanstalk의 환경변수는 해당 eb 환경에 대한 구성
- 소프트웨어 설정
에서 환경 속성
에 등록하면 된다.
AWSEBDockerrunVersion에서 1을 지정하면 싱글 컨테이너, 2를 지정하면 멀티 컨테이너로 실행하겠다는 의미이다.
위 파일에서는 싱글 컨테이너로 실행한다는 의미로 1을 지정했다.
또한 문자열이 아닌 숫자로 지정할 경우 다음의 에러가 발생하므로, 주의하자!!
09:08:13 ERROR: During an aborted deployment, some instances may have deployed the new application version. To ensure all instances are running the same version, re-deploy the appropriate application version.
eb의 로그에서 확인해보면 아래와 같은 에러가 발생하고 있다고 확인된다.
Getting Error: Failed to parse Dockerrun JSON file: json: invalid use of ,string struct tag, trying to unmarshal unquoted value into int
environment를 지정하는 경우 아래의 에러 메시지가 뜰 수 있다
Error: EPERM: operation not permitted, scandir '/proc/1/map_files/55836c87b000-55836c897000'
이는 WORKDIR을 지정하지 않아서 발생하는 문제일 수 있다.
환경 변수를 적용하는 과정에서 docker는 WORKDIR로 지정한 디렉토리 하위의 모든 내용을 재귀적으로 돌면서 탐색하게 된다.
WORKDIR을 지정하지 않게 되면 /
아래의 모든 디렉토리를 재귀적으로 탐색하게 되므로, 접근 권한이 없는 부분까지 도달하게 되어 에러가 발생한 것이다.
이러한 오류가 나게 하지 않기 위해서는 사용하는 WORKDIR을 직접 지정해줘야 한다.