Docker 적용기(aws ecr, github action)

June
None
Published in
8 min readJun 27, 2023
출처: Docker

안녕하세요. 휴먼스케이프 june입니다.

이번에는 개발 중인 서비스에 도커를 도입하면서 겪은 경험에 대해 공유하고자 합니다.

이전 글에서 언급한 대로, 우리는 임상 연구 데이터를 적재하는 크론 서버를 운영하고 있었습니다. 그러나 시간이 흐를수록 EC2 환경이 조금씩 변경되어 배포에 실패하는 문제가 발생했습니다. 이 문제를 해결하면서 아래의 추가적인 이점들을 가져가기 위해 도커를 도입하기로 결정했습니다.

  1. 일관된 환경: Docker는 이미지를 통해 개발 환경과 프로덕션 환경 간의 일관성을 제공합니다. 개발 환경에서 이미지를 빌드하고, 해당 이미지를 프로덕션 환경에서 실행함으로써 일관성을 유지할 수 있습니다.
  2. 빠른 배포: Docker는 이미지를 사용하여 애플리케이션을 빠르게 배포할 수 있습니다. 이미지는 가볍고 빠르게 생성되며, 필요한 경우 이미지를 공유하고 재사용할 수 있습니다.
  3. 확장성: Docker는 컨테이너 단위로 애플리케이션을 실행하므로, 필요에 따라 애플리케이션의 인스턴스 수를 쉽게 확장할 수 있습니다. 이는 애플리케이션의 성능과 가용성을 향상시키는 데 도움이 됩니다.
  4. 격리와 보안: Docker는 각 컨테이너를 격리된 환경에서 실행하므로, 애플리케이션 간의 상호 영향을 최소화하고 보안을 강화할 수 있습니다.

Docker 도입

도커를 도입하기 전에, 도커의 장점인 일관적인 환경 제공과 빠른 배포를 활용하려면 도커의 작동 방식에 대한 이해가 필요합니다. 도커는 Dockerfile의 명령문을 순차적으로 읽어가며 환경을 구성합니다. 이전에 동일한 Dockerfile을 사용하여 환경을 구성한 적이 있다면, 레이어 캐시를 사용하여 명령문 단위로 이전 구성을 가져와 적용합니다. 또한 Dockerfile의 첫 줄에서 FROM 키워드를 사용하여 이미 빌드된 이미지를 다운로드하여 사용할 수 있습니다.

이러한 이해를 바탕으로 빠른 배포를 위한 방법을 고민하였습니다.

우리는 세 가지 레포지토리 구조를 고려했습니다.

  1. 레포지토리를 사용하지 않고 배포 시마다 Beanstalk 또는 EC2에서 이미지를 빌드하여 사용하는 방법.
  2. 빌드 시간이 오래 걸리고 자주 변경되지 않는 부분(종속성 패키지 설치 등)을 base 도커 파일로 분리하고, base docker image를 다운로드하여 소스 코드만 복사하여 사용하는 방법.
  3. 각 환경마다 별도의 레포지토리(api, cron)를 만들어 배포 시마다 빌드하여 사용하는 방법.

위의 방법 중 우리는 2번 방법을 택했습니다.

각 방법의 장단점은 다음과 같이 정리했습니다.

  1. 레포지토리를 사용하지 않아 요금은 증가하지 않지만, 매번 API/Cron 서버에서 빌드를 해야 하므로 원하는 빠른 배포를 실현하기 어려워 보였습니다.
  2. 하나의 레포지토리만 사용하고, 설치할 패키지 변경 사항이 없으면 이미지를 업로드하지 않아 배포 시간을 단축할 수 있는 효과가 있었습니다. 또한 API/Cron 서버에서 소스 코드를 복사하고 프로그램을 실행하는 시간도 빠르므로 효율적인 방식으로 판단했습니다.
  3. 여러 개의 레포지토리를 사용하여 비용이 증가하고, GitHub Actions에서 여러 번 업로드하는 문제로 인해 빌드 시간이 늘어날 것으로 예상되었습니다.

먼저 Dockerfile을 베이스, API, Cron으로 나누었습니다. 리눅스와 파이썬 패키지를 설치하고, 모든 환경에 공통적으로 적용되는 명령문은 base file에 넣었습니다. API와 Cron 파일에서는 각각 API와 Cron 서버에 배포하기 위해 소스 코드를 복사하고 프로그램을 실행하는 명령문을 추가했습니다.

Dockerfile.base

FROM python:3.8

ENV LANG C.UTF-8
ENV LC_ALL C.UTF-8
ENV LC_NUMERIC C.UTF-8
ENV LC_TIME C.UTF-8
ENV LC_COLLATE C.UTF-8
ENV LC_MONETARY C.UTF-8
ENV LC_MESSAGES C.UTF-8
ENV PYTHONDONTWRITEBYTECODE 1
ENV TZ=Asia/Seoul

WORKDIR /app

COPY requirements.txt /app

RUN apt-get update
RUN apt-get install gcc g++ libffi-dev build-essential nginx supervisor make gunicorn curl libssl-dev libbz2-dev libffi-dev cron git libsqlite3-dev -y
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

RUN pip3 install -r requirements.txt

Dockerfile.api

FROM ecr_image

COPY . /app
WORKDIR /app

COPY infra/docker-entrypoint.production.sh /docker-entrypoint.production.sh

EXPOSE 80

RUN chmod +x /docker-entrypoint.production.sh

Github action을 이용한 docker image build

우리는 GitHub Actions에서 베이스 이미지를 빌드하고 ECR로 업로드하는 과정에서 레이어 캐시를 사용하여 빌드 시간을 줄이려고 노력했습니다. build-push-action을 사용하여 buildx를 이용해 도커 이미지를 빌드하며, 이 과정에서 GitHub Actions 캐시를 사용하여 레이어 캐시를 활용했습니다.

그러나 해당 스크립트에 문제가 있었는데, buildx toolkit을 사용하다 보니 멀티 플랫폼을 지원하는 이미지로 빌드되어 ECR에 정상적이지 않은 형태로 업로드되었습니다.

이 문제를 해결하기 위해 직접 빌드 스크립트를 작성했습니다.

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2

- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ap-northeast-2

- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1

- name: pull base image
continue-on-error: true
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: repository_name
run: |
docker pull ${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:latest

- name: push base image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: repository_name
run: |
docker build --cache-from=${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:latest -f ./infra/Dockerfile.base -t tag_name .
docker tag image_name ${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:latest
docker tag image_name ${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:${{ github.sha }}
docker push ${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }} --all-tags

회고

위의 작업들을 진행하며 아쉬운점, 잘한점이 생겼습니다.

아쉬운점은 다음과 같습니다.

  • 작성한 빌드 스크립트에서는 API와 Cron 서버에서 도커 빌드 시 가장 최근의(latest) 태그가 붙은 이미지를 가져와 빌드하도록 되어 있었습니다. 하지만 롤백 등의 상황에서 최신 베이스 이미지를 가져와 빌드하면 문제가 발생할 수 있어 해당 부분을 개선해야 한다고 생각합니다.
  • 위의 레포지토리 구조 고민에서 1번 방법을 선택하고 API/Cron 서버에서 레이어 캐시를 사용해 빌드하는 방식으로 진행했다면 속도 측면에서 큰 차이가 없었을 것으로 예상되는데, 이 부분을 인지하지 못하고 넘어간 것이 아쉬웠습니다.

잘한점은 다음과 같습니다.

  • docker기반 배포의 이점인 빠른 배포 속도를 가져가기 위해 github actions에서 많은 삽질을 했고, 우리 상황에 맞는 방법을 찾아낸 것이 잘한점이라 생각됩니다.

지금까지 Docker 적용 과정에 대해 적어 보았습니다. 감사합니다!

--

--