개요
- ECS 이전할때 Trouble Shooting...
ECS 이전할때 Trouble Shooting...
3주정도 ECS 이전하면서
고생을 너무 많이 했다. 뒤돌아보면 별거 아닌 이슈들도 많았지만...
NEXT ( SSR ) 을 어느정도 공부할 수 있었던 계기가 된것같다.
하지만 Front 는 여전히 너무 어려운것 같다...
1. Dockerizing
Vercel 이라는 플랫폼이 동작하는 원리를 보면 아래와 같다.
- 브랜치 배포
- 배포된 브랜치 checkout 후, 그대로 build ( npm run build )
- build 된 산출물 기반으로 서버 시작 ( npm run start )
약간 과장된 부분이 없지 않아 있지만, 그냥 EC2에 올려서 쓰는느낌이다.
그래서 처음부터 Dockerzing 을 진행해야 했다.
여기서 살짝 정신이 1차로 나간것 같다.
// monorepo
################################################ Base
FROM node:20-bullseye-slim AS base
## add canvas lib
RUN apt-get update && apt-get install -y \
tree \
python3 \
make \
g++ \
build-essential \
libcairo2-dev \
libjpeg-dev \
libpango1.0-dev \
libgif-dev \
librsvg2-dev \
&& rm -rf /var/lib/apt/lists/*
RUN npm i -g pnpm
################################################ Install
FROM base AS builder
WORKDIR /usr/src/app
COPY package*.json pnpm-lock.yaml pnpm-workspace.yaml turbo.json .npmrc ./
COPY apps/refund-web ./apps/refund-web
## 현재 작업하는 폴더 내 .env를 위치시켜야 함
## build 시 .env 가 알아서 .next/standalone으로 위치함
COPY .env ./apps/refund-web/.env
COPY packages ./packages
RUN pnpm install
ENV NEXT_PUBLIC_CDN_BASE_URL=[CDN 주소]
ENV NEXT_PUBLIC_APP_NAME=[APP NAME]
ENV NEXT_PUBLIC_RESOURCE_CENTER_URL=[CDN 주소]
ENV NEXT_PUBLIC_ZENV=[environment]
ENV NODE_ENV=[environemnt]
RUN pnpm run build:refund
RUN pnpm prune --prod
################################################ Runner
FROM node:20-bullseye-slim AS runner
COPY --from=builder /usr/src/app/apps/refund-web/.next/standalone/ .
COPY --from=builder /usr/src/app/apps/refund-web/.next/static ./.next/static
COPY --from=builder /usr/src/app/apps/refund-web/public ./apps/refund-web/public
EXPOSE 3000
ENV HOSTNAME=0.0.0.0
// single repo
FROM node:20.18.3-bullseye-slim AS base
RUN apt-get update && apt-get install -y \
tree \
python3 \
make \
g++ \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /usr/src/app
COPY package*.json pnpm-lock.yaml ./
RUN npm i -g pnpm
# RUN pnpm install --frozen-lockfile --production
RUN pnpm install
RUN pnpm add sharp
COPY . .
##################### Requrie Changes... #####################
ENV CDN_BASE_URL=[CDN]
ENV NEXT_PUBLIC_SERVER_ZENT_API_URL=[API]
ENV APP_NAME=[APP_NAME]
ENV PORT=[PORT]
ENV NODE_ENV=[ENVIRONEMNT]
RUN pnpm run build
RUN rm -rf ./.next/cache
FROM node:20.18.3-bullseye-slim AS runner
WORKDIR /usr/src/app
COPY --from=base /usr/src/app/public ./public
COPY --from=base /usr/src/app/package*.json .
COPY --from=base /usr/src/app/.next/standalone ./
COPY --from=base /usr/src/app/next.config.js ./
COPY --from=base /usr/src/app/.next/static ./.next/static
EXPOSE 3000
ENV HOSTNAME=0.0.0.0
자... Dockerfile 작성할때 유의할점도 같이 보자..
2. env 파일 생성
Backend 서버에서는 env 파일을 따로 두진 않고, ECS TaskDefinition에서 값을 넣어준다.
그렇게 해서 자체적으로 mapping 되게 하지만 NEXT는 달랐다.
애초에 Docker build 타임에 env 파일이 필요했다.
그래서 DockerBuild 하는 시점 전에, CD 쪽에서 .env 값을 만들어 줘야 했다.
COPY .env ./apps/refund-web/.env
3. hostname 이슈 ( Next 13.4.13)
이건 특정 버전 이슈였는데, Next 13.4.13 버전에서 내부적으로 API를 호출하는데,
Docker환경에서만 비정상적인 내부포트를 자동으로 생성
그래서 해당 버전은 13.4.19 버전으로 수정했었다.
추후 이 문제로 많은 논의를 진행했는데 NEXT 자체가 버전문제가 많아서
최대한 15버전으로 옮기겠단 말을 해주셔따 (FE 감사)
4. next.config.js
NEXT 에서 Dockerzing 시, output 되는 dist 폴더 사이즈를 줄이기 위해 옵션을 추가해야한다. (output)
또한 cs / jss 접근 시, CDN 으로 접근하기 위해서도 아래 옵션을 기재해야 한다. (assetPrefix)
// next.config.ts
const { version: packageVersion } = require("./package.json");
/** @type {import('next').NextConfig} */
const zentNextConfig = {
...nextConfig,
output: 'standalone', ## 이 옵션 필요
assetPrefix: `${process.env.CDN_BASE_URL}/${process.env.APP_NAME}/${packageVersion}`, ## 이 옵션 필요 //.. options
compiler: {
//...nextConfig.compiler,
removeConsole: {
exclude: ["error"],
},
},
};
/** @type {import('next').NextConfig} */
5. health check route 추가
ECS로 옮기게 되면, Target Group이 Health Check를 통해서 api에게 질의를 한다.
이때 healthCheck 할 수 있는 router를 추가해야한다. (버전마다 상이)
- next 12
- pages/api/ping.ts
- next 13 ~ 15
- src/app/ping/route.ts
6. hostname 이슈
몇몇 콘솔은 Cognito 를 사용하여 사용자 인증을 진행하였다.
이때, ALB -> ECS -> Cognito -> OKTA 형태로 동작하는데 아래와 같은 이슈가 발생하였다.
위 화면은 OKTA 인증 후 나타나면 화면이다.
.Har (관리자 콘솔 -> 네트워크 탭) 파일을 확인해도 별다른 내용을 확인 할 수 없었음 ...
ALB 내 Cognito 연결을 구성하는 부분이 있지만 -> 우리는 CallBack을 기반으로 동작해서 해당 문제는 아니었음
결국 Docker 내 hostname 이슈 였음 (아래와 같이 해결)
...
CMD HOSTNAME="https://[domain]" node ./apps/op/server.js
OKTA 에서 Service로 Redirect 해줄때, 내부 ip로 전달을 해주는 (hostname) 이슈였다.
7. CD 구성... *****
#!/bin/bash
## if occured error > process.exit(1)
set -e
# Variables
SERVICE_NAME=$1
ENV=$2
TAG=$3
S3_BUCKET=$4
DOCKERFILE_PAHT=$5
ASSET_PREFIX=$6
_ENV=$ENV
if [ "$ENV" == "prd" ]; then
echo "Deploy to production"
ACCOUNT_ID=[account_id]
ECS_CLUSTER_NAME=[prd-cluster]
PORT="3000"
CPU=2048
MEM=4096
else
echo "Deploy to development"
ACCOUNT_ID=[account_id]
ECS_CLUSTER_NAME=[dev-cluster]
PORT="3000"
CPU=1024
MEM=2048
fi
## 예외) env-1 ~ n
if [[ "$ENV" =~ ^([a-zA-Z]+)-([0-9]+)$ ]]; then
ENV="${BASH_REMATCH[2]}-${BASH_REMATCH[1]}"
ECR_REPOSITORY=dkr.ecr.ap-northeast-2.amazonaws.com/${SERVICE_NAME}-${BASH_REMATCH[2]}/${BASH_REMATCH[1]}
else
ECR_REPOSITORY=dkr.ecr.ap-northeast-2.amazonaws.com/${SERVICE_NAME}/${ENV}
fi
echo $TAG
echo $ENV
echo $ECR_REPOSITORY
TASK_VERSION=v3.0.0 ## Task Definition Version
############################## Not Changed ##############################
TEAM=client
ECS_SERVICE_NAME=${SERVICE_NAME}-${ENV}-svc
ssm v4 --json ./devops-repo/task_definition/${TASK_VERSION}/task_def.json \
--zent_account_id ${ACCOUNT_ID} \
--zent_image_arn ${ECR_REPOSITORY}:${TAG} \
--zent_port ${PORT} \
--zent_service_name ${SERVICE_NAME} \
--zent_env ${ENV} \
--zent_dd_team ${TEAM} \
--zent_cpu ${CPU} \
--zent_mem ${MEM} \
--zent_tag ${TAG}
cat task_def.json | jq
mv task_def.json ${DOCKERFILE_PAHT}/
aws ecr get-login-password --region ap-northeast-2 | docker login --username AWS --password-stdin ${ACCOUNT_ID}.dkr.ecr.ap-northeast-2.amazonaws.com
cd ${DOCKERFILE_PAHT}
docker build -t ${ECS_SERVICE_NAME} -f Dockerfile --cache-from ${ACCOUNT_ID}.${ECR_REPOSITORY}:latest ../../
docker tag ${ECS_SERVICE_NAME} ${ACCOUNT_ID}.${ECR_REPOSITORY}:latest
docker tag ${ECS_SERVICE_NAME} ${ACCOUNT_ID}.${ECR_REPOSITORY}:${TAG}
docker push ${ACCOUNT_ID}.${ECR_REPOSITORY}:latest
docker push ${ACCOUNT_ID}.${ECR_REPOSITORY}:${TAG}
container_id=$(docker create ${ACCOUNT_ID}.${ECR_REPOSITORY}:${TAG})
docker cp $container_id:/.next .
docker rm $container_id
aws s3 sync .next/static s3://${S3_BUCKET}/[path]/${ASSET_PREFIX}/_next/static --cache-control max-age=31536000,public,immutable
TASK_DEFINITION_ARN=$(aws ecs register-task-definition \
--cli-input-json file://task_def.json \
--query 'taskDefinition.taskDefinitionArn' \
--output text)
echo "Registered Task Definition: $TASK_DEFINITION_ARN"
aws ecs update-service \
--cluster $ECS_CLUSTER_NAME \
--service $ECS_SERVICE_NAME \
--task-definition $TASK_DEFINITION_ARN \
--force-new-deployment
echo "ECS service $ECS_SERVICE_NAME updated successfully with task definition $TASK_DEFINITION_ARN"
여기서 중요한 건,
아래 부분이다.
container_id=$(docker create ${ACCOUNT_ID}.${ECR_REPOSITORY}:${TAG})
docker cp $container_id:/.next .
docker rm $container_id
이게 무슨 얘기냐면, Dockerizng을 진행하게 되면 next.config.js 에 기재된 형태처럼
standalone 파일과 .next 폴더가 나오게 된다.
이때, standalone 폴더는 ECS 내 올라가야 하고
.next 폴더는 ECS가 아닌 S3 버켓에 올라가서 CDN으로 동작이 되어야 한다.
그렇기때문에 Docker Container 내에서 이 폴더를 로컬로 Copy 하여 S3 에 옮기는 작업을 해줘야 한다.
'Architecture > 회고 및 경험' 카테고리의 다른 글
Vercel -> ECS 로 넘어가기 (1. 틀 잡기) (0) | 2025.05.06 |
---|---|
Database 권한제어 후기 (0) | 2025.04.03 |
GPU 인스턴스 구성을 구성해보자 - 2 (0) | 2025.03.29 |
GPU 인스턴스 구성을 구성해보자 - 1 (0) | 2025.03.24 |
Self Hosted -> CodeBuild (Gtihub Action) 로 넘어가기 (0) | 2025.03.16 |