100%를 한번에 바꾸는건 어려워도 1%를 100번 바꾸는건 쉽다.

생각정리 자세히보기

개발/Deploy

[Deploy] Docker Image Size를 줄여 성능 개선

dc-choi 2024. 10. 10. 18:47
반응형

작은 스타트업의 특성상 백엔드 개발자가 인프라까지 만져야 하는 상황이 생깁니다.

 

저희 회사에서도 마찬가지로 제가 인프라를 관리하고 있는데요. 오늘은 해당 부분의 문제를 발견하여 개선이 필요하다고 판단하여 성능 개선을 한 내용을 공유드리려고 합니다.

 

NestJS를 Docker Image로 빌드하여 배포하게 된다면 좋은 가이드 라인이 되었으면 좋겠습니다.

 

내가 마주했던 문제점

평소부터 꾸준하게 문제점이라고 판단했던 부분이였습니다. node.js를 사용하는 이유가 보통 빠르게 개발을 진행하기 위해 사용한다고 생각하였습니다. 하지만 빌드된 Docker Image의 크기는 무려 약 909MB였습니다. Spring을 빌드한 Docker Image의 크기와 별로 다를바가 없었습니다. 이는 node.js를 사용하는 이유에 맞지 않았습니다.

 

이로 인해 Docker Image를 빌드하는 시간이 오래걸린다고 판단하였습니다. Docker Image의 사이즈를 줄여 배포 시간을 단축하면서 AWS ECR의 저장 공간도 절약하여 비용을 감소시키는 요소라고 생각하였습니다.

 

직접 성능 개선

Docker 성능개선을 진행한 방법은 두가지입니다. 먼저 첫번째로는 .dockerignore 추가입니다.

.dockerignore 파일을 추가하여 불필요한 파일과 디렉토리를 제외하였습니다. 예를 들어 테스트 파일, 로그 파일, lint 설정 파일 등등 docker 빌드에 필요없는 부분을 제거하는 작업을 하였습니다.

 

기존 .dockerignore 파일입니다.

node_modules
dist

 

새로 수정한 .dockerignore 파일입니다.

node_modules
dist
logs
test

.git
.github/**
http
deployment/**
.eslintrc.js
.prettierrc.js
Readme.md
env.example
jest-**.json
jest.**.json
.gitignore
.dockerignore

 

두번째로는 멀티스테이지 빌드를 사용하여 Docker Image의 Size를 줄였습니다. 또한 devDependencies를 Docker Image에 빌드하지 않게 하였습니다. 그렇게 하여 최종 이미지에는 필요한 파일만 포함하도록 설정하였습니다.

 

기존 Dockerfile입니다.

FROM node:18.17-alpine

ARG NODE_ENV

WORKDIR /home/node

ENV NODE_ENV=${NODE_ENV}
ENV TZ=Asia/Seoul

RUN apk add --no-cache tzdata && \
    cp /usr/share/zoneinfo/$TZ /etc/localtime && \
    echo $TZ > /etc/timezone && \
    npm i -g pnpm@8.7.1 pm2

COPY . .

RUN pnpm i && \
    pnpm build

 

새로 수정한 Dockerfile입니다.

# Build stage
FROM node:18.17-alpine AS builder

ARG NODE_ENV

WORKDIR /home/node

ENV NODE_ENV=${NODE_ENV} \
    TZ=Asia/Seoul

RUN apk add --no-cache tzdata && \
    cp /usr/share/zoneinfo/$TZ /etc/localtime && \
    echo $TZ > /etc/timezone && \
    npm i -g pnpm@8.7.1 pm2

COPY package.json pnpm-lock.yaml prisma ./

RUN pnpm i --prod;

COPY . .

RUN pnpm build

# Production stage
FROM node:18.17-alpine

ARG NODE_ENV

WORKDIR /home/node

ENV TZ=Asia/Seoul

RUN apk add --no-cache tzdata && \
    cp /usr/share/zoneinfo/$TZ /etc/localtime && \
    echo $TZ > /etc/timezone && \
    npm i -g pnpm@8.7.1 pm2 && \
    rm -rf /var/cache/apk/*

COPY --from=builder /home/node/dist ./dist
COPY --from=builder /home/node/node_modules ./node_modules
COPY --from=builder /home/node/.env ./
COPY --from=builder /home/node/package.json ./
COPY --from=builder /home/node/pnpm-lock.yaml ./

 

추가적으로 저희 회사는 Github actions를 통해 배포 자동화를 진행하였습니다. 다음은 해당 Script입니다.

name: Deploy Server

on:
    push:
        branches: [develop]

jobs:
    deployment:
        runs-on: ubuntu-22.04
        timeout-minutes: 10

        steps:
            - name: Checkout
              uses: actions/checkout@v4

            - name: Configure AWS Credentials
              uses: aws-actions/configure-aws-credentials@v2
              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 ECR
              id: login-ecr
              uses: aws-actions/amazon-ecr-login@v1

            - name: Create Env File
              run: |
                  touch .env
                  echo '${{ secrets.ENV_DEV }}' >> .env

            - name: Make Docker Image
              run: |
                  docker build \
                    -t server:${GITHUB_SHA::8} \
                    -f deployment/node.dockerfile .

            - name: Tag Docker Image
              run: |
                  docker tag \
                    earlivery-server:${GITHUB_SHA::8} \
                    ${{ steps.login-ecr.outputs.registry }}/server:${GITHUB_SHA::8}

            - name: Push Docker Image to ECR
              run: |
                  docker push \
                     ${{ steps.login-ecr.outputs.registry }}/server:${GITHUB_SHA::8}

            - name: Deploy
              uses: appleboy/ssh-action@v1.0.0
              with:
                  host: ${{ secrets.SSH_DEV_HOST }}
                  username: ${{ secrets.SSH_DEV_USERNAME }}
                  port: ${{ secrets.SSH_DEV_PORT }}
                  key: ${{ secrets.SSH_DEV_KEY }}
                  envs: GITHUB_SHA
                  script: |
                      cd ~/deployment
                      ./deploy_server.sh ${GITHUB_SHA::8} dev

 

Github Actions에서 최종적으로 실행하는 배포 스크립트는 다음과 같습니다.

#!/bin/sh

### Check Argument
if [ $# -ne 2 ]; then
	echo 'Check arguments'
	exit -1
fi

### Pull Docker Image From ECR
ECR_URL=$(aws sts get-caller-identity --query Account --output text).dkr.ecr.ap-northeast-2.amazonaws.com
ECR_REPOSITORY="server"

aws ecr get-login-password --region ap-northeast-2 | docker login --username AWS --password-stdin $ECR_URL

docker pull $ECR_URL/$ECR_REPOSITORY:$1
docker tag $ECR_URL/$ECR_REPOSITORY:$1 $ECR_REPOSITORY:$2

### Deploy
docker compose -p earlivery up -d

docker image prune -a -f

 

Docker Compose Script는 다음과 같습니다.

version: "3.5"

services:
  nginx:
    container_name: nginx
    image: nginx:${ENV}
    ports:
      - 80:80
      - 443:443
    volumes:
      - ./docker/data/nginx/build:/home
      - ./docker/data/nginx/logs:/logs
      - ./docker/data/nginx/conf:/etc/nginx/conf.d
      - ./docker/data/nginx/ssl:/etc/letsencrypt
    command: sh -c "nginx && tail -f /dev/null"

  app:
    container_name: app
    image: server:${ENV}
    ports:
      - 3000:3000
    volumes:
      - ./docker/data/app/logs:/home/node/logs
    command: "pm2-runtime start dist/src/main.js && tail -f /dev/null"

  redis:
    container_name: redis
    image: redis:7-alpine3.17
    ports:
      - 6379:6379

 

이런 방식으로 Docker Image Size를 줄여 성능 개선을 진행하게 되었습니다. 다음은 얼마나 성능이 개선되었는지 확인해보도록 하겠습니다.

 

성능 비교

개선을 하고 난 후 다음과 같이 개선되었습니다.

Docker Image Size 기존 909MB -> 개선 후 513MB (약  44%의 성능 개선)

배포 시간 기존 3분  1초 -> 개선 후 2분  14초 (약  26%의 성능 개선)

 

느낀점

1. Docker에 대해 어느정도 이해하고 있다고 생각하였지만 이번 성능 개선을 통해서 좀 더 Docker에 대해 알아볼 수 있었다.

 

2. Image Size를 줄여 배포 시간을 줄여 개발 생산성을 증가시킬 수 있었고 AWS ECR의 저장 공간도 줄이게 되면서 서버 유지 비용을 점점 줄일 수 있었다.

반응형

'개발 > Deploy' 카테고리의 다른 글

[Deploy] Docker란?  (0) 2024.07.29
[Deploy] Nginx 설치 및 HTTPS 적용  (0) 2022.07.12