반응형

개요

  • ECS 이전할때 Trouble Shooting...

 

ECS 이전할때 Trouble Shooting...

3주정도 ECS 이전하면서 

고생을 너무 많이 했다. 뒤돌아보면 별거 아닌 이슈들도 많았지만...

NEXT ( SSR ) 을 어느정도 공부할 수 있었던 계기가 된것같다. 

 

하지만 Front 는 여전히 너무 어려운것 같다...

 

1. Dockerizing 

Vercel 이라는 플랫폼이 동작하는 원리를 보면 아래와 같다.

  1. 브랜치 배포
  2. 배포된 브랜치 checkout 후, 그대로 build ( npm run build )
  3. 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 에 옮기는 작업을 해줘야 한다.

 

 

반응형
반응형

개요

  • Vercel 이 뭐냐?
  • ECS로 왜 넘어가냐?
  • 일정 / 구성을 어떻게 잡아야 하나...

 

Vercel 이 뭐냐?

 

현재 우리회사의 Front는 NEXT.js 를 사용하고 있었고, 그에따른 PaaS를 Vercel을 사용하고 있었다.

나도 Next 라던가, Vercel 이라던가 이런거 잘 모르지만, 참 좋은 Platform? 인 것 같다.

 

자체적으로 환경변수도 지원해주고, CSR, SSR 방식 모두 지원하고

Serverless 형태로 동작하고 비용이나 모니터링 정말 모든걸 All-In-One 으로 해준다. (도메인도...)

 

근데 한가지 문제가 생겼다.

문제발생 1. IP 통제

ISMS 를 진행하면서, IP 통제를 진행해야 했다.

여기서의 IP 통제는 여러가지 방식으로 진행했어야 하는데, 아래 사진과 같다.

 

일단 사내망에서만 접근 가능하도록 IP 통제를 진행했어야 했다 (개발환경 / 스테이징 환경)

물론 Vercel에서도 이는 가능하다 (WAF 기능이 존재함 -> 하지만 이건 문제가 있음)

 

그리고 Vercel 의 SSR은 Serverless 형태로 띄워지기 때문에 IP를 고정시킬 수 없다.

그래서 전통적인 Front - Server 기반의 Architecture 내에서 Server 로 통신하는 IP를 특정시키기에는 어려움이 존재한다.

 

문제발생 2. 비용

 

현재 우리회사도 Vercel Pro를 사용하고 있고, 

몇몇 보안기능도 활용하고 있다.

 

하지만 WAF 기능을 사용하기 위해선 Enterprise 사용해야 했다.

관련해서 Vercel 담당 엔지니어와 논의를 진행해봤지만...... 생각보다 너무 많은 비용이 지불해야 되서 이건 포기하기로 했다.

 

현재 우리 회사에 FE 개발자는 10명정도 존재하고, 프로젝트도 10~20개 정도 존재한다.

그 과정에서 Enterprize 옵션으로 옮기는것이 비용효율적으로 좋진 않았기 때문에 AWS로 이전하기로 결정하였다.

 

ECS로 왜 넘어가냐?

처음부터 ECS로 결정한건 아니었다.

다양한 옵션이 있었고... 많은 논의를 거쳐 Elastic Container Service로 결정을 하게 되었다.

 

Amplify 도 고민을 했었고...
EKS 도 고민했었다...

 

하지만 .. 결국 ECS로 결정했다.

간단하게 결정한 이유를 보면

 

Amplify를 사용하지 않은 이유

Amplify는 Vercel이랑 완전 똑같았다

똑같았기에 그냥 FE분들이 알아서 인프라를 구성하고 기존과 동일하게 할수 있다는 점이 큰 장점이었다.

  • 브랜치 별로 배포 구성 / Domain 자동 연결
  • 환경변수 자체등록
  • Vercel과 같이 PaaS 처럼 Console UI 제공

 

하지만 2가지 문제가 있다고 생각했다.

 

첫번째는 IP를 특정지을 수 없다.

Amplify도 결국 Vercel 처럼 Serverless 형태처럼 Service가 구성되기때문에 고정적인 IP를 알수가 없다.

그래서 Pass

 

두번째는 성숙되지 않았다.

이 부분은 FE 분들과 많이 대립된 부분이긴한데, CloudFormation 스택에서 한번 꼬이면

배포가 20~30분 정도 걸리는 이슈가 많고 아직은 Amplify 를 사용하기에는 좋지 않다고 생각했다.

 

 

EKS를 사용하지 않은 이유

... 간단하다

진짜 우리회사는 EKS를 정말 좋아하지 않는다

뭐... 찬성 1에 반대 20 이니까 뭐 할말이 없음 그래서 ECS로 결정하게 되었다.

 

최종 아키텍처

아키텍처 구성을 간단하게 살펴보면...

총 3가지 구성으로 나뉜다.

 

Front Layer

Front Layer는 말 그대로 NEXT 서버를 SSR 방식으로 서비스를 제공할 수 있도록 하는 구성이다.

  • Application Load Balacncer + Elastic Container Service로 제공한다

 

Static File Serving Layer

SSR 방식의 경우, 정적파일도 서버쪽에서 내려주면 전반적으로 성능이 좋지 않다.

그렇기 때문에 CS / JSS / 이미지 파일같은 경우 CDN을 활용해서 제공하게된다.

  • CloudFront + S3 

 

Middleware / Resource Layer

Next내에서는 점검중 이런 표기를 Middleware 에서 나타낼 수 있다고 한다

이때 Vercel Edge-Config를 사용해서 Json 값을 가져왔다고 하기에, 이부분도 CF + S3로 대체하였다.

 

또한 SSR 방식이다보니, AWS 내부에 위치하는 서버가 되기때문에 Redis ( vercel k/v 대체 ) 를 접근하고

S3 ( vercel blob ) 을 대체하는 방식을 채택하기로 했다.

 

일정 / 구성을 어떻게 잡아야 하냐?

.... 2달 정도 걸렸던 것 같다.

물론 아직까지도 마이그레이션 중이긴한데,,,, 어렵긴 한다

 

반응형
반응형

개요

  • 2024 re:Invent에 발표된 AWS Network Resource 소개
  • 별로 관심없는 것들은 제외 (Cloud WAN, VPC Lattice)

PrivateLink UDP 지원

  • PrivateLink 에서 UDP 도 지원 (기존 TCP만 지원)
  • Target Server에 IPv6 를 부여
  • Network Load Balancer에서 IPv6 Target Server에 구성
  • 미디어 스트리밍, 게이밍 분야에서 해당 PrivateLink (UDP)를 사용할 수 있을듯함

PrivateLink 내 Region 간 연결 가능 ***

  • 기존 PrivateLink는 동일리전내에서만 구성 가능했음
  • NLB 의 Target과 VPC Endpoint내 다른 영역에 있어도 활용가능
  • ap-northeast-2 서울리전은 2026년 상반기에 지원할 예정

Resource Gateway 활용

  • 기존에는 Privatelink를 사용하기 위해선, NLB를 사용해야 했음
  • Resource Gateway를 사용하여 다른 VPC내 리소스를 제공할 수 있음 (ARN 활용)
    • RDS 데이터 베이스 지정가능
    • DNS를 사용하여, VPC내 리소스 + 고객사의 리소스도 사용가능
    • Resource Gateway 기반의 Privatelink는 동일리전에 TCP만 지원가능

ELB 용량 확보 기능 제공 ***

  • ALB / NLB 대한 용량 확보 요청 기능 제공 (트래픽 급증... -> 안정적 서비스 제공)
  • 짧은시간내 트래픽급증이나, 사전에 용량확보를 통한 안정적 서비스 제공 가능 
  • 서울 리전은 ALB만 제공

CloudFront 신규 기능 ***

  • VPC Origin 기능 추가
    • 기존은 공인 IP 가 존재하는 놈들만 가능했음 
    • VPC내 Private Subnet에 존재하는 ALB , NLB, EC2를 Origin으로 지정가능
  • AnyCast 고정 IP 주소지원 
    • 기존에는 CF 의 IP가 계속 수정이 되었는데 -> 이젠 안바뀌게 할 수 있다. 
    • IP 주소기반의 방화벽 사용 시, 할당된 고정 IP 주소를 이용하여 접근 제어 가능
  • gRPC 프로토콜 지원
    • gRPC 사용하는 어플리케이션에 대한 응답속도 개선 및 보안강화
  • Enganced Logging 가능
    • CloudWatch Log, Kinesis Data Firehose로 로그 전송 가능 (JSON / Parquet)

CloudWatch Flow Monitoring 기능제공 ***

 

  • EC2 인스턴스내 Flow 정보를 활용하여 실시간으로 네트워크 성능 상태를 분석
  • VPC -> EC2에 진행되는 모든 Network 성능분석을 할 수 있음 (네트워크 구간별 이슈 파악 가능)
  • Flow Monitor Agent 설치가 필요

 

https://www.youtube.com/watch?v=mkJEn_VlE0Q

반응형
반응형

개요

  • 푸념
  • 사용하지 않는 DB , Table 삭제는 어떻게 진행 되어야 할까?
  • 후기

푸념

ISMS 심사가 몇일 남지 않았다.

한달하고도 몇일 밖에 남지 않았고, 모든일은 부랴부랴 처리되어 가고있다.

 

진짜 매일매일 야근하고 가끔 주말에도 출근을 하다보니 처음부터 잘 구성해놓을걸 이라는 후회와

내 앞길을 닦아놓으셨던 퇴사자들이 가끔은 미워지기도 한다.

 

하지만... 결국은 해내야 하는 업무고

ISMS 는 통과 아니면 실패기 때문에 해야지...


사용하지 않는 DB , Table 삭제는 어떻게 진행 되어야 할까?

 


DB 유저 권한 제어 - 문제인식

일단 기본적인 DB의 권한제어는 DBSafer를 통해서 진행할 것이고, DBSafer는 이미 모든것이 완벽하게 구축이 되어있다.

하지만 문제는....

 

유저별로 과하게 정책이 매겨진것이 문제였다.

show grants for '유저';

1> grant all privileges ...
2> grant select, update, delete ... prod%.* 
3> grant select, update, delete ... prd%.*
4> grant select, update ... create role, delete role '*'.'*'

 

저 4가지의 정책이 매겨진 애들이 문제였다.


DB 유저 권한 제어 - 진행해야 하는 부분

결국 우리가 진행해야 할 부분은 아래와 같았다.

  • 사용하지 않는 DB , Table 을 솎아내기
  • 사용하지 않는 유저 솎아내기
  • 사용하지 않는 DB, Table 권한 제거 - 추후 삭제
  • 사용하지 않는 유저 제거 - 추후 삭제 

DB 유저 권한 제어 - 사용하지 않은 DB, Table 을 솎아내기

사용하지 않은 DB, Table은 Data 팀원 분들이 도와주셨다

Data 팀분들은 아래 쿼리를 통해 Database 내 정책이나 테이블 내 권한을 확인 하였다.

SELECT * FROM information_schema.SCHEMA_PRIVILEGES ;
SELECT * FROM information_schema.USER_PRIVILEGES ;
SELECT * from information_schema.TABLE_PRIVILEGES

 

위 쿼리들과 테이블 내 최종 업데이트 시간을 모아서 

관련한 전체 시트를 만들어서 공유 주셨다


DB 유저 권한 제어 - 사용하지 않은 유저 솎아내기

해당 부분은 Devops 파트내에서 진행하였다.

다행히 이전에 모든 소스코드내에서 매직넘버로 붙어있던 것들을 SSM Paramter Store를 사용하는것으로 바꿔놓았기 때문에,

 

SSM Paramter Store를 Retrive 하는 CLI를 만들어서,

현재 서비스 내에서 참조 하고 있는 유저인지, 아닌지를 확인할 수 있었다.

 

물론, 그럼에도 100% 검출은 안된다.

그래서 몇몇 계정의 대해서는 추후 inactive 모드를 활성화 해서 지켜볼 계획이었다.


DB 유저 권한 제어 - DB, Table 권한 제거 - 추후 삭제

일단 전략은 이러했다.

사용하지 않는 DB, Table 권한은 삭제하고,

사용하지 않는다면 제거 하는 방향으로 하자고 결정이 되었다.

관련해서는 Data Team, Devops Team, CS, Operation Team 모두 모니터링을 해보자고 논의했고

 

많고 많은 테스트와 확인을 거쳤지만,

혹시나 운영상에 조금이라도 문제가 생기면 다시 권한 Rollback 을 해야하자는 마음으로 진행하였다.

 

일단, 위 기재된 4개의 과한 정책을 가진 유저들은 스키마별로 정책을 다시 매겨줬다.

USE sys;

-- 여러 데이터베이스 이름을 쉼표로 나열
SET @databases = 'prod_a,prod_b,prod_c,prod_d';

SET @username = 'dobby';
SET @userhost = '%';

-- 권한 분리
SET @db_privileges = 'SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, REFERENCES, INDEX, ALTER, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE, CREATE VIEW, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE';
SET @global_privileges = 'RELOAD, PROCESS, SHOW DATABASES, REPLICATION SLAVE, REPLICATION CLIENT';

-- GLOBAL 권한 먼저 부여
SET @global_grant_stmt = CONCAT('GRANT ', @global_privileges, ' ON *.* TO "', @username, '"@"', @userhost, '";');
PREPARE stmt FROM @global_grant_stmt;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

-- DB별 권한 부여 프로시저
DROP PROCEDURE IF EXISTS GrantDbPermissions;

DELIMITER //

CREATE PROCEDURE GrantDbPermissions()
BEGIN
    DECLARE done INT DEFAULT FALSE;
    DECLARE db_name VARCHAR(255);

    DECLARE cur CURSOR FOR 
        SELECT SUBSTRING_INDEX(SUBSTRING_INDEX(@databases, ',', n), ',', -1) AS db
        FROM (
            WITH RECURSIVE numbers(n) AS (
                SELECT 1
                UNION ALL
                SELECT n + 1 FROM numbers WHERE n <= CHAR_LENGTH(@databases) - CHAR_LENGTH(REPLACE(@databases, ',', '')) + 1
            )
            SELECT n FROM numbers
        ) AS t;

    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;

    OPEN cur;
    read_loop: LOOP
        FETCH cur INTO db_name;
        IF done THEN
            LEAVE read_loop;
        END IF;
        SET @grant_stmt = CONCAT('GRANT ', @db_privileges, ' ON `', db_name, '`.* TO "', @username, '"@"', @userhost, '";');
        PREPARE stmt FROM @grant_stmt;
        EXECUTE stmt;
        DEALLOCATE PREPARE stmt;
    END LOOP;
    CLOSE cur;

    FLUSH PRIVILEGES;
END //

DELIMITER ;

-- 실행
CALL GrantDbPermissions();

-- 정리
DROP PROCEDURE GrantDbPermissions;

 

위 프로시저를 사용해서, 모든 스키마의 권한을 부여했다.

그리고 기존 매겨진 권한은 revoke 명령어를 사용해서 권한을 제거하는 방향으로 진행하였다.

 

해당 작업이 다 끝나고,

모든 계정의 대해서 하나하나씩 필요없는 DB, Table 별 권한을 삭제했다 (다행히 이슈없었음) - 애초에줄때도 필요한 것만 줘도 됨

 

그리고 모든 DB.Table 별 권한제거를 했다면

아래 쿼리를 통해 다시한번 Double Check를 진행했다.

SELECT 
    u.User AS '사용자',
    u.Host AS '호스트',
    d.Db AS '데이터베이스',
    GROUP_CONCAT(
        DISTINCT 
        CASE 
            WHEN d.Select_priv = 'Y' THEN 'SELECT' 
            WHEN d.Insert_priv = 'Y' THEN 'INSERT' 
            WHEN d.Update_priv = 'Y' THEN 'UPDATE' 
            WHEN d.Delete_priv = 'Y' THEN 'DELETE' 
            WHEN d.Create_priv = 'Y' THEN 'CREATE' 
            WHEN d.Drop_priv = 'Y' THEN 'DROP' 
            WHEN d.Grant_priv = 'Y' THEN 'GRANT' 
            WHEN d.References_priv = 'Y' THEN 'REFERENCES' 
            WHEN d.Index_priv = 'Y' THEN 'INDEX' 
            WHEN d.Alter_priv = 'Y' THEN 'ALTER' 
            WHEN d.Create_tmp_table_priv = 'Y' THEN 'CREATE TEMPORARY TABLES' 
            WHEN d.Lock_tables_priv = 'Y' THEN 'LOCK TABLES' 
            WHEN d.Create_view_priv = 'Y' THEN 'CREATE VIEW' 
            WHEN d.Show_view_priv = 'Y' THEN 'SHOW VIEW' 
            WHEN d.Create_routine_priv = 'Y' THEN 'CREATE ROUTINE' 
            WHEN d.Alter_routine_priv = 'Y' THEN 'ALTER ROUTINE' 
            WHEN d.Execute_priv = 'Y' THEN 'EXECUTE' 
            WHEN d.Event_priv = 'Y' THEN 'EVENT' 
            WHEN d.Trigger_priv = 'Y' THEN 'TRIGGER' 
        END
        SEPARATOR ', '
    ) AS '권한'
FROM mysql.user u
LEFT JOIN mysql.db d ON u.User = d.User AND u.Host = d.Host
WHERE d.Db IS NOT NULL 
and d.Db in ("지워져야 할 DB")
GROUP BY u.User, u.Host, d.Db
ORDER BY u.User, u.Host, d.Db;

DB 유저 권한 제어 - 사용하지 않는 유저 제거 - 추후 삭제 

이건 솔직히 좀 애매한 구석이 있었다.

유저를 삭제하기라도 하는 날에, Rollback이 안된다

왜냐하면... 몇몇 계정들은 비밀번호를 모른다... ( 실제 사용하는지 안하는지 알수가 없음 )

 

그래서 Inactive 기능을 사용하기로 했다.

Inactive 기능을 사용하고 몇일동안 문제가 없다면 삭제 하고, 문제가 있으면 다시 active 하는 방향으로 진행하였다.

## inactive
alter user 'dobby'@'%' account lock;

## active
alter user 'dobby'@'%' account unlock;

## status locked
select user,host, account_locked
from mysql.user
where user = "dobby";

 


후기

보통 검증하는 기간이 2~3주 정도 소요가 되었다.

사용하지 않는 유저를 찾고...

사용하지 않는 DB 찾고...

사용하지 않는 테이블을 뒤지고...

 

그리고 실제 실행과 모니터링하는 시간까지 한 한달가까이 했던 것 같다.

해당 형태가 완성되기까지 같이 협업했던 데이터팀과, 운영팀, CS팀에게 감사를 보낸다.

반응형
반응형

개요

  • GPU 컴퓨팅 고도화
  • Model은 어떻게 관리해야 할까
  • 앞으로 해결해나가야 할 문제

이전 포스팅 

GPU 인스턴스 구성을 구성해보자 - 1

 


GPU 컴퓨팅 고도화

이전 p5 타입의 인스턴스에 gpu를 활성화까지 진행하고, 대체로 잘 동작하고 있었다.

하지만 이게 동작이 결국에 docker-compose 형태로 동작이 되고, 문제 시 Rollback 이나 CI/CD 구조내에서 많은 문제가 있었다.

그에따라 Devops 인원들이 붙어서 배포를 관리해주는 건 너무 비효율적이었다.

 

아무래도 바쁜데 이런것까지 잡아먹힐 수는 없으니, 어떻게 구성하는것이 좋을까 고민하던차에 ECS on EC2 형태가 어떨까... 싶었다.

 


GPU 타입의 ECS Cluster 생성

ECS Cluster 내에서 GPU 옵션이 붙어있는 EC2를 생성할 수 있었다.

원래는 EKS로 구축하려 했으나... 아직은 회사가 EKS를 받아들이기에는 시기상조

 

그리고 띄워봤는데, 띄우자 마자 gpu 활성화 없이 바로 gpu가 활성화 되어있네?

이전에 하루 다 날린 결과물이 허탈할 지경이었다.

이미 활성화 되어있는 gpu
ecs agent 까지 준비완료

좋아 목표는 아래와 같다

  • 기존 CD 구성형태로 ECS 배포 관리
  • ECS Agent를 활용하여 Self healing 구성
  • 배포 알림 시스템 구성

GPU ECS Task Definition

{
    "family": "<family name>",
    "containerDefinitions": [
        {
            "name": "<ecs name>",
            "image": "<image arn>",
            "cpu": 0,
            "memory": <memory>,
            "portMappings": [
                {
                    "name": "<server>",
                    "containerPort": <container port>,
                    "hostPort": 0,
                    "protocol": "tcp",
                    "appProtocol": "http"
                }
            ],
            "essential": true,
            "environment": [
                {
                    "name": "CUDA_VISIBLE_DEVICES",
                    "value": "0"
                },
                {
                    "name": "MKL_NUM_THREADS",
                    "value": "4"
                },
                {
                    "name": "TZ",
                    "value": "Asia/Seoul"
                },
                {
                    "name": "NUMEXPR_NUM_THREADS",
                    "value": "4"
                },
                {
                    "name": "GPU_SETTING",
                    "value": "0"
                },
                {
                    "name": "NUMEXPR_MAX_THREADS",
                    "value": "4"
                },
                {
                    "name": "NVIDIA_DRIVER_CAPABILITIES",
                    "value": "compute,utility"
                },
                {
                    "name": "OMP_NUM_THREADS",
                    "value": "4"
                }
            ],
            "environmentFiles": [],
            "mountPoints": [],
            "volumesFrom": [],
            "ulimits": [
                {
                    "name": "nofile",
                    "softLimit": 65536,
                    "hardLimit": 65536
                }
            ],
            "logConfiguration": {
                "logDriver": "awsfirelens",
                "options": {
                    "apikey": "<datadog api key>",
                    "compress": "gzip",
                    "provider": "ecs",
                    "dd_service": "<server>",
                    "Host": "http-intake.logs.datadoghq.com",
                    "TLS": "on",
                    "dd_source": "inference-server",
                    "dd_tags": "env:<environment>,team:<team>,role:server,app:<server>",
                    "Name": "datadog"
                },
                "secretOptions": []
            },
            "systemControls": [],
            "resourceRequirements": [
                {
                    "value": "1",
                    "type": "GPU"
                }
            ]
        },
        {
            "name": "datadog-agent",
            "image": "public.ecr.aws/datadog/agent:latest",
            "cpu": 0,
            "links": [],
            "portMappings": [
                {
                    "containerPort": 8126,
                    "hostPort": 8126,
                    "protocol": "tcp"
                }
            ],
            "essential": true,
            "entryPoint": [],
            "command": [],
            "environment": [
                {
                    "name": "ENVIRONMENT_NAME",
                    "value": "<environment>"
                },
                {
                    "name": "SERVICE_NAME",
                    "value": "<server>"
                },
                {
                    "name": "DD_API_KEY",
                    "value": "<datadog api key>"
                },
                {
                    "name": "ECS_FARGATE",
                    "value": "true"
                },
                {
                    "name": "DD_APM_ENABLED",
                    "value": "true"
                },
                {
                    "name": "DD_LOGS_ENABLED",
                    "value": "true"
                },
                {
                    "name": "APPLICATION_NAME",
                    "value": "<server>"
                }
            ],
            "mountPoints": [],
            "volumesFrom": [],
            "logConfiguration": {
                "logDriver": "awslogs",
                "options": {
                    "awslogs-group": "<group>",
                    "awslogs-create-group": "true",
                    "awslogs-region": "ap-northeast-2",
                    "awslogs-stream-prefix": "ecs-service"
                }
            },
            "systemControls": []
        },
        {
            "name": "firelens_log_router",
            "image": "amazon/aws-for-fluent-bit:stable",
            "cpu": 0,
            "links": [],
            "portMappings": [],
            "essential": true,
            "entryPoint": [],
            "command": [],
            "environment": [
                {
                    "name": "SERVICE_NAME",
                    "value": "<server>"
                },
                {
                    "name": "ENVIRONMENT_NAME",
                    "value": "<environment>"
                },
                {
                    "name": "APPLICATION_NAME",
                    "value": "<server>"
                }
            ],
            "mountPoints": [],
            "volumesFrom": [],
            "user": "0",
            "logConfiguration": {
                "logDriver": "awslogs",
                "options": {
                    "awslogs-group": "<group>",
                    "awslogs-create-group": "true",
                    "awslogs-region": "ap-northeast-2",
                    "awslogs-stream-prefix": "ecs-service"
                },
                "secretOptions": []
            },
            "systemControls": [],
            "firelensConfiguration": {
                "type": "fluentbit",
                "options": {
                    "config-file-type": "file",
                    "config-file-value": "/fluent-bit/configs/parse-json.conf",
                    "enable-ecs-log-metadata": "true"
                }
            },
            "credentialSpecs": []
        }
    ],
    "executionRoleArn": "arn:aws:iam::<accountId>:role/ecsTaskExecutionRole",
    "networkMode": "bridge",
    "volumes": [],
    "placementConstraints": [],
    "requiresCompatibilities": [
        "EC2"
    ],
    "cpu": "8192",
    "memory": "24576",
    "runtimePlatform": {
        "cpuArchitecture": "X86_64",
        "operatingSystemFamily": "LINUX"
    },
    "enableFaultInjection": false
}
  • 기존 ECS Task Definition 과 동일하나 GPU 옵션이 몇몇 추가되었다.
  • 또한 DataDog 을 사용하는 만큼, 관련 SideCar도 추가하였다.

결과는? -> 잘 동작함 

이참에 이렇게 할걸 ... 약간 후회


Model은 어떻게 관리해야 할까?

EC2 on ECS 로 구성을 하고, 나서 큰 문제는 없었으나 아래와 같은 이슈가 발생하였다.


모델 추가 변경의 건 - 첫번째 시도 (Git LFS)

Dev : 모델업데이트를 해야되는데 어떻하죠?

Devops : 모델은 VCS에 못올리나요?

Dev : 모델자체가 크기가 커서 에러가 납니다.

Devops : Git LFS로 올려야될것 같네요. 확인해보겠습니다.

 

모델을 생각못한건 아니지만, 아니 솔직히 생각못하긴 했다.

그래서 모델을 Git LFS를 사용하기로 했다.

git lfs install

git lfs track "~~.pt"
git lfs track "~~.lib"
git lfs track "~~.dll"

>> 산출물

## .gitattributes

* text = auto
~~.pt filter=lts diff=lfs merge=lfs -text
~~.lib filter=lts diff=lfs merge=lfs -text
~~.dll filter=lts diff=lfs merge=lfs -text

근데, 이걸 설정하고 CD를 진행하려 보니 문득 이런생각이 들었다.

 

LFS 사용한다면?

  • Git LFS를 설정 -> Git Storage 비용 발생 -> CD 내에서 LFS 관련 옵션 설정 -> 어차피 CD 할때마다 Pull / Push 함...

S3 사용한다면? 

  • 필요 모델 S3에 올림 -> S3 비용발생 -> CD 내에서 S3 파일다운받아서 해제 
Devops : ~~님, 혹시 모델 변경이 잦을까요?

Dev : 아뇨, 잦지는 않을 것 같습니다. 현재는 개발중이라 수정이 될텐데
추후에는 한달에 한번정도만 수정 될 것 같아요

Devops : OK

모델 추가 변경의 건 - 두번째 시도 ( S3 사용하자)

 

S3로 선택한 건, 더 편하고 비용친화적이고 관리하기 더 편할것같아서 

머리아픈 LFS 보다는 S3를 선택하기로 했다.

 

CD 단에서 S3 데이터를 풀어서 Dockerizing 하면 끝이라고 생각했다.

## action 내에서 발췌

- name: Pull Models
  run: |
   aws s3 cp s3://<S3 Bucket>/models.zip .
      
- name: Unzip Models
  run: |
   unzip -o models.zip

 

오... 잘 동작하는데?


앞으로 해결해나가야 할 문제

  • GPU 인스턴스 내 배포문제
    • 현재 p5.xlarge, p5.2xlarge 타입을 사용하고 있는데
    • 각각 사용할 수 있는 GPU는 1개씩이다.
    • 이 얘기는 배포 시, Rolling Update가 안된다.

이 문제의 대해서는 일단 최소 인스턴스를 2개 운영하는 방식으로 구성하기로 했다.

물론 개발환경만 DeploymentConfiguration 을 조절해서 Recreate 방식으로 수정했다.

운영은 다운타임이 발생해서 일단 좋은방법이 나타날때까지 GPU 인스턴스를 2개 운영하는것으로 대체...

 

  • 운영환경 내 비용효율화 문제
    • 일단 개발환경은 SPOT 인스턴스로 대체하여, 어느정도의 비용은 낮췄다.
    • 그외 더 좋은 방법이 없을까 고민했지만 -> 애초에 GPU 옵션을 사용하는 서비스에서 비용효율화... 가능할까?
  • 더 좋은 방법 없을까?
    • SageMaker를 고려중이다.
    • 이건 좀더 공부해봐야 할 것 같다.
반응형
반응형
  • 모델 소스코드가 파일크기가 너무커서... 자체 GitLab을 만들기로 했다 (LFS용)

 

Resource Name / Desc
OS amazon-linux 2 (ARM64)
Computing t4g.medium
Storage gp3 ( 100GB ) 

 

Gitlab Install

sudo yum update -y

sudo yum install curl \
policycoreutils-python \
openssh-server \
openssh-clients \
perl \
posfix -y

sudo systemctl enable sshd
sudo systemctl start sshd

sudo systemctl enable postfix
sudo systemctl start postfix

## 필요한 경우 (X)
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload

curl https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.rpm.sh | sudo bash

## 실제 도메인으로 변경 (domain 로 구성) - gitlab 설치
sudo EXTERNAL_URL="http://domain..." yum install -y gitlab-ce

 

Gitlab 설정

sudo vi /etc/gitlab/gitlab.rb
# 다음 설정들을 필요에 따라 수정:
# external_url 'http://Domain...'
# gitlab_rails['gitlab_shell_ssh_port'] = 22
# postgresql['shared_buffers'] = "256MB" # RAM에 따라 조정

# 설정 적용
sudo gitlab-ctl reconfigure
# 8GB swap memory 설정
sudo dd if=/dev/zero of=/swapfile bs=1M count=8192
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile

# 부팅시 자동 마운트
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

# 백업 디렉토리 생성
sudo mkdir -p /var/opt/gitlab/backups
sudo chmod 700 /var/opt/gitlab/backups

# 백업 설정 (gitlab.rb에 추가)
sudo vi /etc/gitlab/gitlab.rb

# 다음 라인 추가:
# gitlab_rails['backup_path'] = "/var/opt/gitlab/backups"
# gitlab_rails['backup_keep_time'] = 604800 # 7일

# 설정 적용
sudo gitlab-ctl reconfigure
sudo cat /etc/gitlab/initial_root_password ## 초기설정 시 비밀번호 확인

# /etc/gitlab/gitlab.rb에 추가
postgresql['shared_buffers'] = "2GB"  # RAM의 25%
nginx['worker_processes'] = 4         # CPU 코어 수만큼
nginx['worker_connections'] = 2048

# /etc/gitlab/gitlab.rb에 추가
gitlab_rails['env'] = {
  'MALLOC_ARENA_MAX' => 2
}

 

Gitlab SMTP + AWS SES 설정

// /etc/gitlab/gitlab.rb
gitlab_rails['smtp_enable'] = true
gitlab_rails['smtp_address'] = "email-smtp.ap-northeast-2.amazonaws.com"
gitlab_rails['smtp_port'] = 465
gitlab_rails['smtp_user_name'] = "SES에서 생성한 SMTP 사용자 이름"
gitlab_rails['smtp_password'] = "SES에서 생성한 SMTP 비밀번호"
gitlab_rails['smtp_domain'] = "SES에서 설정한 도메인"
gitlab_rails['smtp_authentication'] = "login"
gitlab_rails['smtp_tls'] = true
gitlab_rails['smtp_force_ssl'] = true

# 발신자명을 설정하고 싶다면 다음을 추가
gitlab_rails['gitlab_email_from'] = '발신자 이메일 도메인'

// 저장 후 설정 - 재부팅
sudo gitlab-ctl reconfigure

 

Gitlab Status 확인

sudo gitlab-ctl status | cat
반응형
반응형

개요

  • ECS가 뭔데?
  • Kubernetest가 정답인가?
  • 그럼에도 왜 ECS Fargate를 사용하는가?
  • ECS Fargate 를 잘 활용하는 방법

 


ECS 가 뭔데?

 

간단하다. ECS ( Elastic Container Service )

Container 기반 서비스를 AWS 내 쉽게 띄우기 위한 하나의 시스템이다.

어쩌면 Docker 기반의 구성된 서비스를 굉장히 쉽게 만들어서 Cloud 시스템에 구축할 수 있겠는가? -> 그게 바로 ECS라고 생각한다

 


ECS를 사용하면 뭐가 좋은가?

보통 ECS를 얘기하면, 매번 나오는게 EKS와 비교하게 된다.

하지만 그전에 간단하게 Docker 기반 시스템을 구축하기 위한 서비스를 어느정도 나열해보면 아래와 같다.

 

Layer로 나눠본 Container 기반 시스템 구성


Layer 1. Docker를 쓴다 - EC2에 올려서 쓴다

뭐... 굉장히 심플하다

Dockerfile을 구성하든, docker-compose를 구성하든 그 자체로 EC2에 올려서 서비스를 한다.

정말 간단하다는 말 외에는 따로 할 말이 없다

하지만 그 간단함 때문에 -> 많은 부분들이 고민이 되는 부분이다

모니터링, 로그, 배포, Self Healing, Rollback 모든 부분에서 손수 수작업을 진행해야 한다.


 Layer 2. Orchestration Tool을 사용하되, EC2를 사용한다

보통 production 즉, 회사내에서 서비스를 운영하는 단계라면 Layer 2 에서 시작하는 것 같다.

기본적인 Orchestration 을 사용하되, ECS , EKS 를 EC2를 host로 사용한다.

물론 나쁘진 않다. 하지만 나는 Serverless 더 선호한다. ( 비용 및 효율성 측면에서 어쩔 땐 EC2 기반이 좋을 때가 존재함 )


Layer 3. Serverless 형태로 사용한다

Serverless...

마법의 단어다. 대부분의 고민거리를 해결해준다.

물론 Serverless 가 모든 고민거리, 기술적 이슈들을 해결해주진 않는다.

Serverless 이기에 겪는 문제도 있고, 여전히 손수 수작업으로 해줘야 할 부분도 존재한다.

그럼에도 불구하고 관리하기 편하기때문에 Devosp Engineer 입장에서는 업무 시간에 다른부분을 더 강화할 시간을 벌게된다.

 


Kubernetes가 정답인가?

대부분의 회사에서 사용하는 스택이나, 개발자 취업 사이트를 보면 Kubernetes를 사용한다. 

거의 무조건 사용하고, 사용 / 활용할 수 있는 사람을 뽑는다.

왜 그럴까? ECS 보다 Kubernets가 더 좋은 점은 무엇일까? (경험기반...)

 


EKS는 자유롭다

EKS 만큼 자유로운 시스템은 또 없다.

여기서 자유롭다라는 뜻은, 따로 vendor에 구애 받지 않는다는 얘기다.

 

K8S 는 GCP, AWS, Azure Multi Cloud내에 GKE, EKS, AKE 여러개로 불리기도하고, 온프레미스에서도

동작방식이 같아 흔히 Cloud Lock In 현상을 줄일 수 있다.

 

ECS만 봐도 AWS에서만 사용가능하고,

GCP에서는 Cloud Run, App Engine 같은 기능을 써야하기 때문에 그것의 비해서는 자유롭다.

 


배포 전략이 자유롭다 (CNCF)

ECS는 기본적인 Rolling Update, Code Deploy를 사용해서 Blue/Green, Canary 배포방식을 사용할 수 있다.

하지만 좀더 트래픽을 세밀하게 조절해서 배포하는 방안들의 대해서는 굉장히 많은 노력이 필요하다.

 

그에 반해, K8s 같은 경우에는 Traffic Splitting 같은 배포전략을 Istio로 한다거나, ArgoCD 같은 Tool로 굉장히 

다양한 방법의 배포 / Rollback 기능을 활용할 수 있다.

 


AutoScaling 시, 세부적으로 Control 이 가능하다

ECS도 이건 가능하다. 불가능하지는 않다.

간단하게 CPU, Memory 기반으로도 AutoScaling 가능하고

CloudWatch를 연동해서 SQS, SNS, ALB의 지표등을 확인해서 다양한 AutoScaling 전략을 사용할 수 있다.

 

하지만 K8s 같은 경우에는 더 세밀하게 조정이 가능한데 HPA & VPA 을 사용하면 CPU, Memory 기반으로 자동확장이 더 유연하다.

그리고 애초에 Node , Pod 기반으로 동작하기 때문에 당연한 얘기기도 하다

 

추가적으로 Karpenter 이놈을 쓰면 굉장히 도움이 많이 때문에 좀 부럽긴하다.

나중에 Kaprenter 기능을 Deep 하게 공부해서 글써보면 도움이 많이 될지도...

 


Addons가 너무 훌륭하다 (CNCF)

사실 이 부분은 넘사벽이라고 생각하는데,

ECS도 AWS 내부서비스와 조합은 훌륭하다.

하지만 K8s는 CNCF 내 오픈소스와 다양한 툴링이 가능하다.

 

어떤것이 좋다. 어떤것이 있다. 하나하나 세부적으로 얘기하지 않아도 

정말 좋은 오픈소스나 툴이 있다고 하면 대부분 K8S 를 기반으로 만들어져 있다.

 


그럼에도 왜 ECS Fargate 를 사용하는가?

간단하다...

쉽다...

아주 적은 인원으로, 여러개의 서비스를 구축할 때 이만한건 없는 것 같다.

내가 생각했을 때 굉장히 좋은 시스템이고 Lock In이 걸렸다면 어쩔수 없지만...

당분간 AWS 만 쓸건데, Lock In 정도야 ... 큰 문제일까 싶다.

 


그래도 나름대로 비용 효율성

AWS내에서 K8s를 사용하려면 EKS 옵션이 있는데,

이건 Cluster 비용이 별도로 나간다. 큰 비용은 아니지만 월에 $ 70 ~ 80 발생한다.

하지만 ECS는 Cluster를 여러개 만들어도 공짜이기 때문에 짱짱맨이다.

 


나는 인프라 이런거 모르고 그냥 개발에만 집중할래

딱 이런 사람들에게는 최적의 솔루션이 아닌가 싶다.

ECS Fargate 를 사용하면 정말 많은것을 해준다.

Self Healing도 해주고, Auto Scaling도 너무 쉽고, ALB나 Event Bridge pipe를 사용해서 여러 서비스와 조합도 가능하다.

그리고 Scale-up 하는것도 굉장히 옵션이 다양하기 때문에 GPU 기반이나 LLM을 크게 돌릴것이 아니라면 더할나위 없다.

 

 

ECS Fargate 를 잘 활용하는 방법

솔직히 개인적인 경험으로는 기존 ECS Fargate를 사용해도 큰 문제는 없지만

몇몇 더 잘 사용하면 기존보다 2~3배 가량 더 잘 사용할 수 있다고 생각한다.

 


비용 효율화 (SPOT 인스턴스)

ECS Fargate OS System은 총 2가지로 나뉜다

  • ARM
  • x86_64

이때 x86_64를 사용하게되면 SPOT 인스턴스를 사용할 수 있다.

물론 SPOT 인스턴스 자체가 동작하는 원리는 AWS 내 노는 컴퓨팅을 잠시 빌려써서 다시 임대하는 형식이기 때문에,

가끔은 죽을 수도 있다.

 

그렇기에 개발 / 스테이징 / 데이터 배치 같은 가끔은 죽어도 되는 애들은 SPOT 인스턴스를 고려해보는것도 좋은것같다.


ECS Fargate 가 아닌, EC2 on ECS도 고려해보자

너무 위에서 ECS Fargate 만 얘기했지만,

EC2에 ECS 사용하는 옵션도 굉장히 좋다.

 

GPU, 그외 다른 성격을 가진 Computing 유형도 ECS를 사용해서 편하게 배포하고 구성하고를 사용할 수 있다 (ecs agent)

추가적으로 보통 ECS Fargate 에서는 서비스 당 ENI를 하나씩 가지게된다 (network mode)

 

하지만 EC2 on ECS는 다양한 network mode를 선택 할 수 있다.

network mode 설명
bridge - EC2 내부에 Docker bridge 사용
- Container 간 NAT 통신
host - 컨테이너나 EC2의 네트워크를 직접사용 (IP공유)
- EC2에 바인딩 된 포트 그대로 사용
- 보안이슈가 존재

 

그래서 고성능 네트워크가 필요하다거나, 특정 포트를 컨테이너 내에서 직접 노출한다거나,

로드밸런서가 아닌 EIP로 바로 트래픽을 받는 어느정도 특수한 환경일 경우, 위와같이 처리할 수 있다.

 

결론

정말 대규모의 시스템이나 비용효율적인 방안들을 검토해서 구성을 해야한다면 K8S 가 정답일 수도 있다고 생각한다.

하지만 본인 내지, 회사의 도메인과 서비스를 이해하고 현재 어느수준에 있는지를 파악한다면 어떤 솔루션이 적합한지 한번쯤은 더 생각해볼수 있지 않을까 생각한다.

 

물론 나도 K8S를 하고싶지만, 현재 ECS 를 사용하면서도 회사의 비즈니스는 정말 잘 굴러간다.

그렇다고 ECS -> K8S 갈아탄다고 해서 서비스 안정성이 올라가냐?, 트래픽이 올라가냐? 그건 아니다.

 

하지만 K8S로 마이그레이션하거나 진행해야 할때는 있을거다.

그때를 위해서 공부는 꾸준히 해놓자...

 

 

반응형

'Architecture > Computing' 카테고리의 다른 글

self hosted 로 gitlab 설치하기  (0) 2025.03.27
python 앱 빌드시간 단축  (0) 2025.01.12
반응형

개요

  • GPU 인스턴스를 구축해보자
  • GPU 활성화 하기
  • Docker 내에서는 왜 안되지?
  • Docker 에서도 GPU 활성되게 하기

GPU 인스턴스를 구축해보자

최근 추론모델을 학습하기 위해서, GPU를 사용하는 인스턴스가 필요했다.

가만보자...

gpu 가 있는 인스턴스

현재 회사에서 알맞은 스펙은 Amazon EC2 g5로 진행하기로 했다.

테스트하고, 비교한 결과는 내부적인 자료라서 공유하기는 애매하고....

많은 테스트를 통해서 g5.2xlarge 를 사용하기로 하였다.

 

 

가격은 시간 당 $ 1.212

하루에 1.212 * 24 = $ 29

한달에 30 * 29 = $ 872  (매우 비싸다...)


 

GPU 활성화하기

EC2 기본적인 스펙은 아래와 같다.

운영체제 Computing Storage
ubuntu g5.2xlarge 150 GB (gp3)

 

사실 처음에 gpu 인스턴스를 구축하면, 자동으로 gpu가 있는건 줄 알았는데, 

driver를 직접 설치하고 구성을 해야 하더라...

이것도 모르고 왜 느리지?... 했었따.

 


기본적인 Ubuntu 환경 세팅

sudo apt-get update -y
sudo apt install gcc -y

sudo apt-get install linux-headers-$(uname -r)
sudo apt install ubuntu-drivers-common -y

ubuntu-drivers devices

 

ubuntu-drivers devices를 하게되면 아래 화면이 나타난다.

 

ubuntu nvidia driver

여기서 그냥 아무것도 모르고 driver 낮은 버전설치했다가 -> 호환이 안되서 시간만 엄청 잡아먹었다.

왠만하면 recommended 로 된걸로 설치하자 (2025.3.24일 기준 - nvidia-driver-550)

 


gpu driver 설치

sudo apt-get install nvidia-driver-550
sudo reboot ## 재부팅 후에 적용 완료 됨
nvidia-smi

 

오우 여기까지 나왔다면, 

nvidia gpu가 설치가 된거다 (스크린샷은 470으로 찍힘)

 


Docker 내에서는 왜 안되지?

docker 서비스를 gpu 인스턴스에 띄워놓고, 테스트를 해보니... 추론모델이 cpu 만 사용하였다.

 

아... 뭐가 문제지

알고보니, container내에서 gpu를 사용하기 위해선 자체적인 toolkit 을 설치해야 한다. (이걸 알아차리기까지 2시간 가까이 소요됨)

 


container toolkit 설치 (docker가 설치가 이미 되어있다는 가정)

# NVIDIA GPG 키와 리포지토리 추가
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl gnupg-agent software-properties-common
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg
curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \
  sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
  sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list

# NVIDIA Container Toolkit 설치
sudo apt-get update
sudo apt-get install -y nvidia-container-toolkit

sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker

docker run --gpus all nvidia/cuda:12.4.0-base-ubuntu22.04 nvidia-smi

## docker deamon을 재구성
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker

docker run --gpus all nvidia/cuda:12.4.0-base-ubuntu22.04 nvidia-smi

 


과연 잘 구성이 되었을까?

docker info | grep Runtimes

이렇게 나와야 함 - 1

docker exec -it [container-id] /bin/sh
nvidia-smi ## nvidia 사용현황이 나와야 함

이렇게 나와야 함 - 2

 


 

Docker 에서도 GPU 활성되게 하기

자 그럼 Container 내에서, 잘 동작되는지 확인이 되었으니 명령어에 gpu를 사용한다고 명시해보자...

 


docker run 명령어에 gpu 옵션 추가

docker run --gpus all [이미지명] [명령어]

 


docker-compose 에 gpu 옵션 추가

version: '3.8'
services:
  [추론모델]:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: [추론모델]
    ports:
      - "8888:8888"
    restart: unless-stopped
    volumes:
      - ./logs:/app/logs
    environment:
      - TZ=Asia/Seoul
    deploy:                             ## 이 부분 부터 추가
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: all
              capabilities: [gpu]
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8888/ping"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 10s

gpu를 잘 쓰고있다.

 

서버상태를 보니 기존 3초정도 걸렸던 작업이 -> 0.2 ~ 0.5 초로 줄었다.

결국 happy ending

 

앞으로 해야할 일

  • gpu 인스턴스를 EC2 로 구성하였다 -> AutoScaling, 모니터링, Disk 관련 부분들을 다시한번 점검해봐야 한다
  • 현재 Service Code 모델로 인해 10~20GB 정도된다. 현재는 어쩔수 없이 S3로 주고받으나 다른방법을 모색해봐야 한다
    • Github Submodule...
    • Github Self hosted...
  • CI/CD CD 구성은 아직 안했는데, main push 되면 ssm Run Command가 EC2에 명령을 질의하게끔 하면 되지 않을까?

 

나처럼 많이 헤멜것 같은 분들에게 한줄기 희망이 되길...

한 12번 포기할뻔 했는데 다행이 해냈다.

반응형

+ Recent posts