Next.js + Docker + AWS ECR + Github Actions로 빌드 자동화하기⚙️(CI/CD)

영근·2023년 1월 2일
8
post-thumbnail

도커 공부하다가 찾은 귀여운 이미지
펭귄이랑 고래는 찍먹해봤는데, 곰돌이는 ... 만져볼 일 없지 않을까..?🤔

# 배경

CSR로 개발된 우리 회사 서비스가 SEO에 취약한 점이 있어, 곧 Next.js를 이용한 SSR 마이그레이션이 있을 예정이다.
그 전에, 앞으로의 SSR 프로젝트를 더욱 간편하게 시작하기 위해, Next.js 템플릿을 만들었다.

특히 SSR 프로젝트는 S3로 호스팅할 수 없기 때문에(S3는 정적인 웹사이트만 호스팅할 수 있다), 기존에 하던 빌드 방식과는 다른 방식이 필요했다.
그래서 선택한 것이 바로 도커!
(사실 내가 선택한 건 아니고 우리 CTO님이 선택해주셨다.)

여기서 잠깐, 도커란?

도커란 컨테이너 기반의 오픈소스 가상화 플랫폼이다.
프로젝트에서 실행하는 다양한 실행 환경, 프로그램들을 컨테이너로 추상화해서 빌드와 관리를 간편하게 하고, 어디서든 실행 가능하게 해준다.
초보를 위한 도커 안내서

기존 회사 프로젝트에서 CI/CD는 Github Actions를 이용하고 있었고, 여기저기 구글링해보니 AWS 코드 파이프라인은 설정이 너무 어렵다는 경고가 있었다. (nightmare..?)

그래서 친숙한 Github Actions를 사용하기로 결정했다😓.


# 목표

Github Actions, Docker를 활용해서 Next.js 프로젝트를 자동으로 빌드하자!

  • ⚠️ 주의 : AWS ECR에서 ECS로 자동 배포하는 과정은 포함되어 있지 않습니다.

# 과정

1. Dockerfile을 루트 위치에 만들기.

Dockerfile 에서 사용하는 요 커맨드들을 가지고 어떻게 이미지를 만들건지 적어주면 된다.

command설명
FROMbase 이미지 설정
WORKDIR작업 디렉터리 설정
RUN이미지 빌드 시 커맨드 실행
ENTRYPOINT이미지 실행 시 항상 실행되야 하는 커맨드 설정
CMD이미지 실행 시 디폴트 커맨드 또는 파라미터 설정
EXPOSE컨테이너가 리스닝할 포트 및 프로토콜 설정
COPY/ADD이미지의 파일 시스템으로 파일 또는 디렉터리 복사
ENV환경 변수 설정
ARG빌드 시 넘어올 수 있는 인자 설정

1. Dependencies 설치를 위한 첫 번째 레이어 만들기

  • NodeJS 16(Alpine Linux Project 기반)을 base image로 한다.

  • 그 과정 중에 필요한 라이브러리가 사라지는 이슈가 있으니, 추가해주기 위해 libc6-compat 패키지를 추가해준다. (자세한 내용은 여기를 확인하세요)

  • 다했으면 app 디렉토리로 이동하기~!

FROM node:16-alpine AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app

2. 사용하는 패키지 매니저에 맞게 Dependencies 설치하기

  • 먼저 ~~~.lock 파일을 복사해주고,

  • 사용하는 패키지(yarn/npm/pnpm)를 사용해 Dependencies를 설치해준다.

  • 만약 관련 파일이 없다면 exit 1로 빌드를 중지한다.

COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
RUN \
  if [ -f yarn.lock ]; then yarn install --frozen-lockfile; \
  elif [ -f package-lock.json ]; then npm ci; \
  elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i --frozen-lockfile; \
  else echo "Lockfile not found." && exit 1; \

3. 빌드를 위한 두 번째 레이어 만들기

잠깐, 레이어를 왜 세 개나 만드나요?🤔

이미지 용량 최소화를 위해서 입니다!
도커 이미지는 레이어를 축적해서 만들어지는데요! 이 때 이전 이미지의 레이어와 현재 빌드하는 이미지의 레이어가 같다면 재사용합니다.(캐싱)
따라서 역할별 레이어를 세분화해서 바뀌지 않는 부분은 재사용하게 해서 빌드 시 사용되는 리소스를 최소화하는 것입니다~

  • 첫 번째 레이어와 같이 Node.js 16을 base image로 한다.

  • app에 있는 모든 파일(.dockerignore에 지정된 파일 제외)을 복사한다.

  • 이 때 Next.js에서 원격으로 데이터 수집을 못하게 하고싶다면 telemetry 서버를 꺼준다.

FROM node:16-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

ENV NEXT_TELEMETRY_DISABLED 1

4. 빌드하기

RUN yarn build

5. 이미지 생성을 위한 세 번째 레이어 만들기

  • 이젠 말 안해도 아는 Node.js 16 base image로 하기

  • app 디렉토리로 이동

  • Next.js 원격 데이터 수집 싫으면 하지말라고 말해주기

  • sudo 없이 로컬에서 이미지를 컨테이너에서 실행할 수 있도록 add group, add user!

  • 파일들 몽땅 복사!

FROM node:16-alpine AS runner
WORKDIR /app

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --from=builder /app/public ./public

6. 이미지 사이즈 줄이기

  • Next.js에서는 빌드할 때 무려 자동으로 이미지 사이즈를 줄여준다 !

  • 빌드 과정에서 standalone이라는 폴더를 만들고, 코드에서 사용한 import, request 들을 분석해서 꼭 필요한 친구들만 요 폴더에 넣은 뒤 빌드한다고 한다. (자세한 내용은 여기에서 확인)

  • 그 기능을 사용해보자 ~!

COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs

7. 이미지가 사용할 포트를 설정해주고, 마무리!

  • 3000번 포트를 사용한다고 설정한다.(포트가 1024번보다 낮으면 에러뜰 수 있다)

  • 그리고 컨테이너가 실행되었을 때 실행되는 명령어를 정의해준다.

EXPOSE 3000

ENV PORT 3000

CMD ["node", "server.js"]

8. 로컬에서 빌드된 이미지 확인하기

  • 수동 빌드 커맨드
docker build -t (이미지 태그명)
  • 수동 컨테이너 실행
docker run (<옵션>) <이미지 식별자> (<명령어>) (<인자>)

2. 이미지에 안넣을 파일 빼주기

  • 루트 디렉토리에 .dockerignore 파일을 만들어준다.

  • 나는 AWS ECR repository에서 env 설정을 해주기 때문에 .env도 포함시켰다.

Dockerfile
.dockerignore
node_modules
npm-debug.log
README.md
.next
.git
Footer
.env

3. next.config.js 설정 변경해주기

  • 아까 Next.js가 자동으로 빌드 파일 용량 줄여주는 기능을 쓰려면 ! 이렇게 추가해줘야 한다.
/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  experimental: { appDir: true }, // Next.js v13 app directory 사용 여부
  output: 'standalone',  // 여기서 standalone 폴더를 사용한다고 해준다.
};

export default nextConfig;

4. Github Action을 사용해 자동으로 빌드하고 AWS ECR에 업로드하기

  • 루트 디렉토리에 .github 폴더를 만들고, workflow 폴더를 만든 뒤 안에 yaml 확장자로 파일을 만들어준다!

  • 깃헙 repository에서 actions 설정을 하면 자동으로 폴더가 생긴다.

name: Deploy to Amazon ECR

on:
  push:
    branches:
      - main  # main 브랜치에 push할 때
  pull_request:
    branches:
      - main  # main 브랜치에 PR할 때 실행한다.
env: # 사용할 env	
  AWS_REGION: 
  ECR_REGISTRY: 
  ECR_REPOSITORY: 
jobs:	
  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    environment: production

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

      - name: Configure AWS credentials	# AWS 로그인
        uses: aws-actions/configure-aws-credentials@v1
        with:	# AWS 로그인에 필요한 정보들. 얘네는 Github Secret에 설정해야 한다.
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ env.AWS_REGION }}

      - name: Login to Amazon ECR # ECR에도 로그인
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v1

      - name: Build, tag, and push image to Amazon ECR # 이미지 빌드하고 ECR push
        id: build-image
        env:
          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
       # 아래처럼 docker 커맨드를 실행한다.
        run: |
          docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:latest .
          docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest
          echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:latest"

요렇게 해주면 자동으로 빌드 / 이미지 생성 / AWS ECR push까지 끝 ~!


# 결과

요렇게해서 Next.js 템플릿에 CI/CD를 무사히 추가했다.
사실 나는 프론트엔드다보니, 도커에 대해서 정말 1도 모르고 있었다.(고래라는 거 밖에 몰랐음)
이 기회에 도커가 뭔지 개념도 이해하고, 직접 이미지도 말아보고 컨테이너도 실행해볼 수 있었다.

사실 CI/CD에 대해서도 큰 이해가 없었고, 그냥 있는 코드를 몇 군데 수정하는데 지나지 않았었는데,
이번 기회에 직접 처음부터 CI/CD에 대해 공부하고, 어떤 툴을 사용할지 고민하고, 직접 코드를 작성하면서 확실히 이해하게 되었다.
(나름 재미있을지도 ?)

그리고 AWS는 아직도 무섭다..😓

reference

0개의 댓글