[Daily morning study] Serverless vs Container ๋น„๊ต

#daily morning study

Image


๊ฐœ์š”

Serverless์™€ Container๋Š” ํ˜„๋Œ€ ํด๋ผ์šฐ๋“œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ฐฐํฌ์˜ ๋‘ ๊ฐ€์ง€ ์ฃผ์š” ํŒจ๋Ÿฌ๋‹ค์ž„์ด๋‹ค. ๊ฐ๊ฐ์˜ ํŠน์„ฑ์„ ์ดํ•ดํ•˜๊ณ  ์ƒํ™ฉ์— ๋งž๊ฒŒ ์„ ํƒํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•˜๋‹ค.


Container

์ปจํ…Œ์ด๋„ˆ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๊ณผ ๊ทธ ์‹คํ–‰ ํ™˜๊ฒฝ(๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ, ์„ค์ • ํŒŒ์ผ ๋“ฑ)์„ ํ•˜๋‚˜์˜ ์ด๋ฏธ์ง€๋กœ ํŒจํ‚ค์ง•ํ•œ ๋…๋ฆฝ์ ์ธ ์‹คํ–‰ ๋‹จ์œ„๋‹ค.

ํ•ต์‹ฌ ํŠน์ง•

  • ์ด์‹์„ฑ: ๋™์ผํ•œ ์ด๋ฏธ์ง€๊ฐ€ ์–ด๋–ค ํ™˜๊ฒฝ์—์„œ๋“  ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ๋™์ž‘
  • ์ผ๊ด€์„ฑ: ๊ฐœ๋ฐœยทํ…Œ์ŠคํŠธยทํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์„ ์™„์ „ํžˆ ๋™์ผํ•˜๊ฒŒ ์œ ์ง€
  • ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜: Kubernetes๋กœ ๋Œ€๊ทœ๋ชจ ํด๋Ÿฌ์Šคํ„ฐ ๊ด€๋ฆฌ ๊ฐ€๋Šฅ
  • ์žฅ๊ธฐ ์‹คํ–‰์— ์ ํ•ฉ: ํ•ญ์ƒ ์ผœ์ ธ ์žˆ๋Š” ์„œ๋ฒ„ ํ”„๋กœ์„ธ์Šค์— ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋งž์Œ

์ฃผ์š” ๊ธฐ์ˆ 

  • Docker: ์ปจํ…Œ์ด๋„ˆ ๋นŒ๋“œ ๋ฐ ์‹คํ–‰
  • Kubernetes (K8s): ์ปจํ…Œ์ด๋„ˆ ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜
  • AWS ECS / EKS, GKE, AKS: ๊ด€๋ฆฌํ˜• ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค ์„œ๋น„์Šค

Dockerfile ์˜ˆ์‹œ (Node.js)

FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
docker build -t my-app .
docker run -p 3000:3000 my-app

Serverless

Serverless๋Š” ์„œ๋ฒ„ ๊ด€๋ฆฌ ์—†์ด ์ฝ”๋“œ(ํ•จ์ˆ˜)๋ฅผ ์‹คํ–‰ํ•˜๋Š” ํŒจ๋Ÿฌ๋‹ค์ž„์ด๋‹ค. ํด๋ผ์šฐ๋“œ ํ”„๋กœ๋ฐ”์ด๋”๊ฐ€ ์ธํ”„๋ผ๋ฅผ ์™„์ „ํžˆ ๊ด€๋ฆฌํ•˜๊ณ , ๊ฐœ๋ฐœ์ž๋Š” ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์—๋งŒ ์ง‘์ค‘ํ•œ๋‹ค.

ํ•ต์‹ฌ ํŠน์ง•

  • ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜: HTTP ์š”์ฒญ, ํ ๋ฉ”์‹œ์ง€, ํƒ€์ด๋จธ ๋“ฑ์œผ๋กœ ํŠธ๋ฆฌ๊ฑฐ
  • ์ž๋™ ์Šค์ผ€์ผ๋ง: ์š”์ฒญ๋Ÿ‰์— ๋”ฐ๋ผ 0 โ†’ ์ˆ˜์ฒœ ๊ฐœ๋กœ ์ž๋™ ํ™•์žฅยท์ถ•์†Œ
  • ์‚ฌ์šฉ๋Ÿ‰ ๊ธฐ๋ฐ˜ ๊ณผ๊ธˆ: ํ•จ์ˆ˜ ์‹คํ–‰ ์‹œ๊ฐ„ยทํšŸ์ˆ˜๋งŒํผ๋งŒ ๋น„์šฉ ๋ฐœ์ƒ
  • ์ฝœ๋“œ ์Šคํƒ€ํŠธ: ๋น„ํ™œ์„ฑ ์ƒํƒœ์—์„œ ์ฒซ ์š”์ฒญ ์‹œ ์ดˆ๊ธฐํ™” ์ง€์—ฐ ๋ฐœ์ƒ

์ฃผ์š” ์„œ๋น„์Šค

  • AWS Lambda
  • Google Cloud Functions
  • Azure Functions
  • Cloudflare Workers

Lambda ํ•จ์ˆ˜ ์˜ˆ์‹œ (Node.js)

exports.handler = async (event) => {
    const { name } = JSON.parse(event.body);

    return {
        statusCode: 200,
        body: JSON.stringify({ message: `Hello, ${name}!` }),
    };
};

๋น„๊ต ์ •๋ฆฌ

ํ•ญ๋ชฉContainerServerless
์„œ๋ฒ„ ๊ด€๋ฆฌ์ง์ ‘ ๊ด€๋ฆฌ (๋˜๋Š” ๊ด€๋ฆฌํ˜•)ํด๋ผ์šฐ๋“œ ํ”„๋กœ๋ฐ”์ด๋”๊ฐ€ ์ „๋‹ด
์Šค์ผ€์ผ๋ง์ˆ˜๋™ ์„ค์ • / HPA์ž๋™ (0~๋ฌดํ•œ๋Œ€)
๊ณผ๊ธˆ ๋ฐฉ์‹์‹คํ–‰ ์ค‘์ธ ์ธ์Šคํ„ด์Šค ๊ธฐ์ค€ํ•จ์ˆ˜ ์‹คํ–‰ ์‹œ๊ฐ„ยทํšŸ์ˆ˜ ๊ธฐ์ค€
์ฝœ๋“œ ์Šคํƒ€ํŠธ์—†์Œ (ํ•ญ์ƒ ์‹คํ–‰ ์ค‘)์žˆ์Œ (๋น„ํ™œ์„ฑ ํ›„ ์ฒซ ์š”์ฒญ ์‹œ ์ง€์—ฐ)
์‹คํ–‰ ์‹œ๊ฐ„ ์ œํ•œ์—†์Œ์žˆ์Œ (Lambda ์ตœ๋Œ€ 15๋ถ„)
์ƒํƒœ ์œ ์ง€๊ฐ€๋Šฅ๊ธฐ๋ณธ์ ์œผ๋กœ stateless
ํ™˜๊ฒฝ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•๋†’์Œ๋‚ฎ์Œ (๋Ÿฐํƒ€์ž„ ์ œํ•œ)
์ดˆ๊ธฐ ์„ค์ • ๋ณต์žก๋„๋†’์Œ๋‚ฎ์Œ

์–ธ์ œ ์–ด๋–ค ๊ฑธ ์„ ํƒํ• ๊นŒ

Container๊ฐ€ ์ ํ•ฉํ•œ ๊ฒฝ์šฐ

  • ์›น ์„œ๋ฒ„, API ์„œ๋ฒ„์ฒ˜๋Ÿผ ํ•ญ์ƒ ์ผœ์ ธ ์žˆ์–ด์•ผ ํ•˜๋Š” ์žฅ๊ธฐ ์‹คํ–‰ ํ”„๋กœ์„ธ์Šค
  • ๋ณต์žกํ•œ ์˜์กด์„ฑ์ด๋‚˜ ์ปค์Šคํ…€ ๋Ÿฐํƒ€์ž„์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ
  • ์‹คํ–‰ ์‹œ๊ฐ„์ด 15๋ถ„์„ ๋„˜๋Š” ๋ฐฐ์น˜ ์ž‘์—…
  • ์„ธ๋ฐ€ํ•œ ์ธํ”„๋ผ ์ œ์–ด๊ฐ€ ํ•„์š”ํ•  ๋•Œ
  • ๋ ˆ๊ฑฐ์‹œ ์‹œ์Šคํ…œ์„ ์ ์ง„์ ์œผ๋กœ ํด๋ผ์šฐ๋“œ๋กœ ์ด์ „ํ•  ๋•Œ

Serverless๊ฐ€ ์ ํ•ฉํ•œ ๊ฒฝ์šฐ

  • ํŒŒ์ผ ์—…๋กœ๋“œ ํ›„ ์ด๋ฏธ์ง€ ๋ฆฌ์‚ฌ์ด์ง•, ์ด๋ฉ”์ผ ๋ฐœ์†ก์ฒ˜๋Ÿผ ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ์ฒ˜๋ฆฌ
  • ํŠธ๋ž˜ํ”ฝ์ด ๋ถˆ๊ทœ์น™ํ•˜๊ฑฐ๋‚˜ ์˜ˆ์ธกํ•˜๊ธฐ ์–ด๋ ค์šด ๊ฒฝ์šฐ
  • ๋น ๋ฅธ ํ”„๋กœํ† ํƒ€์ดํ•‘๊ณผ MVP ๊ฐœ๋ฐœ
  • ์‹คํ–‰ ์‹œ๊ฐ„์ด ์งง์€ API ์—”๋“œํฌ์ธํŠธ
  • ํฌ๋ก  ์žก(์Šค์ผ€์ค„ ๊ธฐ๋ฐ˜ ์ž‘์—…)

์ฝœ๋“œ ์Šคํƒ€ํŠธ ๋ฌธ์ œ์™€ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

Serverless์˜ ๊ฐ€์žฅ ํฐ ๋‹จ์ ์€ ์ฝœ๋“œ ์Šคํƒ€ํŠธ๋‹ค. ํ•จ์ˆ˜๊ฐ€ ์ผ์ • ์‹œ๊ฐ„ ๋น„ํ™œ์„ฑ ์ƒํƒœ์˜€๋‹ค๊ฐ€ ์š”์ฒญ์ด ๋“ค์–ด์˜ค๋ฉด ์ปจํ…Œ์ด๋„ˆ ์ดˆ๊ธฐํ™”์— ์ˆ˜๋ฐฑ ms~์ˆ˜ ์ดˆ์˜ ์ง€์—ฐ์ด ๋ฐœ์ƒํ•œ๋‹ค.

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

  1. Provisioned Concurrency (AWS Lambda): ์ธ์Šคํ„ด์Šค๋ฅผ ๋ฏธ๋ฆฌ ์›Œ๋ฐ์—…ํ•ด ๋Œ€๊ธฐ ์ƒํƒœ๋กœ ์œ ์ง€
  2. ์ฃผ๊ธฐ์ ์ธ Warm-up ์š”์ฒญ: CloudWatch ์ด๋ฒคํŠธ๋กœ ๋”๋ฏธ ์š”์ฒญ์„ ๋ณด๋‚ด ํ™œ์„ฑ ์œ ์ง€
  3. ๊ฐ€๋ฒผ์šด ๋Ÿฐํƒ€์ž„ ์„ ํƒ: Python, Node.js๋Š” Javaยท.NET๋ณด๋‹ค ์ดˆ๊ธฐํ™”๊ฐ€ ํ›จ์”ฌ ๋น ๋ฆ„
  4. ์˜์กด์„ฑ ์ตœ์†Œํ™”: ํŒจํ‚ค์ง€ ํฌ๊ธฐ๋ฅผ ์ค„์—ฌ ์ดˆ๊ธฐํ™” ์‹œ๊ฐ„ ๋‹จ์ถ•
// ํ•ธ๋“ค๋Ÿฌ ๋ฐ–์—์„œ DB ํด๋ผ์ด์–ธํŠธ๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๋ฉด ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅ
// ์ฝœ๋“œ ์Šคํƒ€ํŠธ ์‹œ์—๋งŒ ์ƒ์„ฑ๋˜๊ณ , ์ดํ›„ ์š”์ฒญ์—์„œ๋Š” ์žฌ์‚ฌ์šฉ๋จ
const dbClient = new DatabaseClient(process.env.DB_URL);

exports.handler = async (event) => {
    const result = await dbClient.query('SELECT * FROM users');
    return { statusCode: 200, body: JSON.stringify(result) };
};

ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ์•„ํ‚คํ…์ฒ˜

์‹ค์ œ ํ”„๋กœ๋•์…˜์—์„œ๋Š” Container์™€ Serverless๋ฅผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ๋‹ค.

  • Kubernetes ์œ„์—์„œ ๋ฉ”์ธ ์›น ์„œ๋ฒ„ ์‹คํ–‰ โ†’ Container
  • Lambda๋กœ ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ, ์•Œ๋ฆผ ๋ฐœ์†ก โ†’ Serverless
  • AWS Fargate๋กœ ๋ฐฐ์น˜ ์ž‘์—… ์‹คํ–‰ โ†’ Container์ฒ˜๋Ÿผ ์“ฐ์ง€๋งŒ ์„œ๋ฒ„ ๊ด€๋ฆฌ ๋ถˆํ•„์š”

Fargate๋Š” Container์™€ Serverless์˜ ์ค‘๊ฐ„ ํ˜•ํƒœ๋‹ค. ์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€ ๊ทธ๋Œ€๋กœ ์‹คํ–‰ํ•˜๋ฉด์„œ๋„ ์„œ๋ฒ„ ํ”„๋กœ๋น„์ €๋‹์ด ํ•„์š” ์—†๋‹ค. Google Cloud Run๋„ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ๋™์ž‘ํ•œ๋‹ค.


์ •๋ฆฌ

Container์™€ Serverless๋Š” ๋Œ€๋ฆฝ ๊ด€๊ณ„๊ฐ€ ์•„๋‹ˆ๋ผ ์ƒํ˜ธ ๋ณด์™„์ ์ด๋‹ค. ์žฅ๊ธฐ ์‹คํ–‰ยท๋ณต์žกํ•œ ํ™˜๊ฒฝ์ด ํ•„์š”ํ•˜๋ฉด Container๋ฅผ, ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜์˜ ์งง์€ ์‹คํ–‰ยท์˜ˆ์ธก ๋ถˆ๊ฐ€ํ•œ ํŠธ๋ž˜ํ”ฝ์—๋Š” Serverless๋ฅผ ์„ ํƒํ•œ๋‹ค. Fargate, Cloud Run ๊ฐ™์€ ์„œ๋น„์Šค๊ฐ€ ๋“ฑ์žฅํ•˜๋ฉด์„œ ๋‘ ํŒจ๋Ÿฌ๋‹ค์ž„์˜ ๊ฒฝ๊ณ„๋Š” ์ ์  ํ๋ ค์ง€๊ณ  ์žˆ๋‹ค. ์ค‘์š”ํ•œ ๊ฑด ์„œ๋น„์Šค์˜ ํŠน์„ฑ์— ๋งž๊ฒŒ ๋„๊ตฌ๋ฅผ ๊ณ ๋ฅด๋Š” ๊ฒƒ์ด๋‹ค.