"๋น„์šฉ์ ์œผ๋กœ ํŠน์ดํ•œ ์‚ฌํ•ญ ์—†์Šต๋‹ˆ๋‹ค"๋ผ๊ณ  ๋งํ•˜๊ธฐ๊นŒ์ง€ ๊ฑธ๋ฆฌ๋Š” ๊ฒƒ๋“ค

jiyoung
  • #infra
  • #aws
  • #billing
  • #typescript

๋“ค์–ด๊ฐ€๋ฉฐ

์•ˆ๋…•ํ•˜์„ธ์š”, ๋…ธ๋จธ์Šค์—์„œ ์ธํ”„๋ผ๋ฅผ ๋‹ด๋‹นํ•˜๊ณ  ์žˆ๋Š” ๋ฐ•์ง€์˜์ž…๋‹ˆ๋‹ค.

์ €๋Š” ๋งค์ฃผ ํด๋ผ์šฐ๋“œ ๋น„์šฉ์„ ์ง‘๊ณ„ํ•˜์—ฌ ๊ณต์œ ํ•ฉ๋‹ˆ๋‹ค.
๊ทธ๋ฆฌ๊ณ  ๊ณต์œ ์˜ ๋์—๋Š” ๋Š˜ ์•ฝ์†๋œ ์งˆ๋ฌธ์ด ํ•˜๋‚˜ ๋Œ์•„์˜ต๋‹ˆ๋‹ค.

โ€œ์ด๋ฒˆ ์ฃผ์— ๋น„์šฉ์ ์œผ๋กœ ํŠน์ดํ•œ ์‚ฌํ•ญ์ด ์žˆ์„๊นŒ์š”?โ€

์‚ฌ์‹ค ๋Œ€๋‹ต์€ ๋Œ€๋ถ€๋ถ„ ๋น„์Šทํ•ฉ๋‹ˆ๋‹ค.

โ€œํŠน์ด ์‚ฌํ•ญ ์—†์Šต๋‹ˆ๋‹ค.โ€

ํ•˜์ง€๋งŒ ์ด ์งง์€ ํ•œ๋งˆ๋””๋ฅผ ๋‚ด๋ฑ‰๊ธฐ ์œ„ํ•ด ์ธํ”„๋ผ ๋‹ด๋‹น์ž๋Š” ์ƒ๊ฐ๋ณด๋‹ค ๋งŽ์€ ์ฆ๊ฑฐ๋ฅผ ์ˆ˜์ง‘ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
โ€˜ํŠน์ดํ•จโ€™ ์ด ์—†๋‹ค๊ณ  ํ•˜๋ ค๋ฉด ๋ฌด์—‡์ด โ€˜ํ‰์ดํ•จโ€™ ์ธ์ง€๋ฅผ ์ •์˜ํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

FinOps Foundation์˜ โ€œUnderstand Usage & Costโ€์— ๋”ฐ๋ฅด๋ฉด,
๋น„์šฉ ๊ฐ€์‹œ์„ฑ ํ™•๋ณด์—๋Š” ๋ช…ํ™•ํ•œ ์ˆœ์„œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

1. Data Ingestion: ํด๋ผ์šฐ๋“œ ์ œ๊ณต์ž๋กœ๋ถ€ํ„ฐ ์‚ฌ์šฉ ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ง‘ํ•˜๊ณ  ์ •๊ทœํ™”ํ•˜๋ฉฐ
2. Allocation: ์ˆ˜์ง‘๋œ ๋น„์šฉ์„ ์„œ๋น„์Šค์™€ ํŒ€์— ๋งž๊ฒŒ ํ• ๋‹นํ•˜๊ณ 
3. Reporting & Analysis: ์ผ๊ด€๋œ ๊ธฐ์ค€์œผ๋กœ ๋ณด๊ณ ํ•˜๋ฉฐ
4. Anomaly Management: ์ด์ƒ ์ง•ํ›„๋ฅผ ๊ฐ์ง€ํ•˜๋Š” ๊ฒƒ

์•ž ๋‹จ๊ณ„๊ฐ€ ๋‹จ๋‹จํ•˜๊ฒŒ ๋ฐ›์ณ์ฃผ์–ด์•ผ ๋‹ค์Œ ๋‹จ๊ณ„๋กœ ๋‚˜์•„๊ฐˆ ์ˆ˜ ์žˆ๋Š” ๊ตฌ์กฐ์ž…๋‹ˆ๋‹ค.

์ € ์—ญ์‹œ ์ด ๋กœ๋“œ๋งต์„ ๋”ฐ๋ผ๊ฐ€๋ฉฐ ๋งŽ์€ ๊ณ ๋ฏผ์„ ๊ฑฐ์ณค๊ณ ,
๊ทธ ๊ณผ์ •์—์„œ ๊ฒช์€ ์‹œํ–‰์ฐฉ์˜ค์™€ ์ธ์‚ฌ์ดํŠธ๋ฅผ ๊ฐ€์žฅ ๋ณต์žกํ–ˆ๋˜ AWS๋ฅผ ์ค‘์‹ฌ์œผ๋กœ ํ’€์–ด๋ณด๋ ค ํ•ฉ๋‹ˆ๋‹ค.

์ž๋™ํ™”ํ•  ์ˆ˜ ์—†๋Š” ๊ฒƒ๋“ค

์ €ํฌ๋Š” AWS๋ฅผ MSP(Managed Service Provider)๋ฅผ ํ†ตํ•ด ์ด์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
๋”ฐ๋ผ์„œ ๋น„์šฉ ๋ฐ์ดํ„ฐ์˜ 1์ฐจ ์ถœ์ฒ˜๋Š” AWS Cost Explorer๊ฐ€ ์•„๋‹Œ, MSP์—์„œ ์ œ๊ณตํ•˜๋Š” ๋ณ„๋„์˜ ์ฝ˜์†”์ž…๋‹ˆ๋‹ค.

ํ•ด๋‹น ์ฝ˜์†”์—์„œ๋Š” ๋น„์šฉ ๋ช…์„ธ๋ฅผ CSV๋กœ ๋‚ด๋ ค๋ฐ›์„ ์ˆ˜ ์žˆ๊ณ ,
์ฒญ๊ตฌ์„œ๊ฐ€ ํ™•์ •๋˜๋Š” ์‹œ์ ์—๋Š” ์ ์šฉ ํ™˜์œจ์ด ํฌํ•จ๋œ PDF ๋ช…์„ธ์„œ๋„ ํ•จ๊ป˜ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค.

์ด ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒƒ ์ž์ฒด๋Š” ์–ด๋ ต์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.
๋ฌธ์ œ๋Š” ๊ทธ ๋‹ค์Œ์ด์—ˆ์Šต๋‹ˆ๋‹ค.

๋‹ค์šด๋กœ๋“œํ•œ ํŒŒ์ผ์„ ๊ฐ€๊ณตํ•˜๋Š” ๋ชจ๋“  ๊ณผ์ •์„
๋งค์ฃผ ํ˜ผ์ž ์ฒ˜๋ฆฌํ•ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์ด ์ ์  ํฐ ๋ถ€๋‹ด์œผ๋กœ ๋А๊ปด์กŒ์Šต๋‹ˆ๋‹ค.

์ž๋™ํ™”๋ฅผ ๊ฒ€ํ† ํ–ˆ์ง€๋งŒ, MSP ์ธก์œผ๋กœ๋ถ€ํ„ฐ โ€œ๊ณต๊ฐœ API๋ฅผ ์ œ๊ณตํ•˜์ง€ ์•Š์œผ๋ฉฐ, ์ •ํ™•ํ•œ ๊ณ„ํš์ด ์—†๋‹คโ€๋Š” ๋‹ต๋ณ€์„ ๋ฐ›์•˜์Šต๋‹ˆ๋‹ค.

์ˆ˜๊ธฐ ๋‹ค์šด๋กœ๋“œ๋Š” ํ”ผํ•  ์ˆ˜ ์—†์œผ๋‹ˆ,
๋‹ค์šด๋กœ๋“œ ์ดํ›„์˜ ๊ฐ€๊ณต๊ณผ ์ง‘๊ณ„ ๊ณผ์ •๋งŒ์ด๋ผ๋„ ์ฝ”๋“œ๋กœ ์ž๋™ํ™”ํ•˜๋Š” ๋ฐฉํ–ฅ์„ ํƒํ–ˆ์Šต๋‹ˆ๋‹ค.

ํŒ€์ด ์ฝ์„ ์ˆ˜ ์žˆ๋Š” ์ฝ”๋“œ

์ €๋Š” ์‰˜์ด ํŽธํ•ฉ๋‹ˆ๋‹ค. ์ธํ”„๋ผ ๋‹ด๋‹น์ž๋กœ์„œ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์†์— ์ต์€ ์–ธ์–ด์ด๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค.
๊ทธ๋ž˜์„œ ์ฒ˜์Œ์—๋Š” ์‰˜ ์Šคํฌ๋ฆฝํŠธ๋กœ ์งฐ์Šต๋‹ˆ๋‹ค.

๊ณ„์†ํ•ด์„œ ์Šคํฌ๋ฆฝํŠธ๋Š” ์ปค์กŒ๊ณ , ์–ด๋А์ƒˆ 9,000์ค„์ด ๋„˜์–ด๊ฐ”์Šต๋‹ˆ๋‹ค.
์‹คํ–‰ ์‹œ๊ฐ„๋„ ๊ธธ์—ˆ๊ณ , ์›”๋ง์ด ๋˜๋ฉด ์ค‘๊ฐ„์— ๋ฉˆ์ถ”๋Š” ์ผ๋„ ์žฆ์•˜์Šต๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ๋‹ค๋ฅธ ๋ฌธ์ œ๋„ ๋ณด์˜€์Šต๋‹ˆ๋‹ค.

์ €๋Š” ๋ฐฑ์—”๋“œ ํŒ€์— ์†Œ์†๋œ ์ธํ”„๋ผ ๋‹ด๋‹น์ž์ž…๋‹ˆ๋‹ค.
์ œ๊ฐ€ ๋งŒ๋“  ์ฝ”๋“œ๋Š” ์–ธ์  ๊ฐ€ ๋‹ค๋ฅธ ๋™๋ฃŒ๊ฐ€ ์œ ์ง€๋ณด์ˆ˜ํ•ด์•ผ ํ•  ์ˆ˜๋„ ์žˆ๋Š”๋ฐ,
์ด๋Œ€๋กœ๋ผ๋ฉด ๊ฐˆ๋ผํŒŒ๊ณ ์Šค๊ฐ€ ๋˜๊ฒ ๋‹ค ์‹ถ์—ˆ์Šต๋‹ˆ๋‹ค.

ํŒ€์€ TypeScript๋ฅผ ์”๋‹ˆ๋‹ค.
๊ทธ๋ ‡๋‹ค๋ฉด ์„ ํƒ์ง€๋Š” ํ•˜๋‚˜์˜€์Šต๋‹ˆ๋‹ค.

๋ฌธ์ œ๋Š” ์ œ๊ฐ€ ์“ธ ์ค„ ๋ชจ๋ฅธ๋‹ค๋Š” ๊ฒƒ์ด์—ˆ์Šต๋‹ˆ๋‹ค.
์ฝ์œผ๋ฉด ๋งฅ๋ฝ์€ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ์ง์ ‘ ์ž‘์„ฑํ•˜๋Š” ๊ฑด ๋‹ค๋ฅธ ์ด์•ผ๊ธฐ์˜€์Šต๋‹ˆ๋‹ค.

๊ทธ๋ž˜์„œ ์ฝ”๋“œ ์ž‘์„ฑ์€ Claude์—๊ฒŒ ๋งก๊ธฐ๊ธฐ๋กœ ํ–ˆ์Šต๋‹ˆ๋‹ค.
๋ฐฉํ–ฅ์„ ์žก๊ณ  ์ง€์‹œํ•˜๋Š” ๊ฑด ์ œ๊ฐ€, ์ฝ”๋“œ๋ฅผ ์“ฐ๋Š” ๊ฑด Claude๊ฐ€ ๋‹ด๋‹นํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ํ˜‘์—…ํ–ˆ์Šต๋‹ˆ๋‹ค.

๋ณด๊ณ ์„œ๊ฐ€ ์›ํ•˜๋Š” ์ˆซ์ž

AWS ๋น„์šฉ ๋ฐ์ดํ„ฐ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๋‹ฌ๋Ÿฌ($) ๊ธฐ์ค€์ด์ง€๋งŒ,
์‹ค์ œ ์‚ฌ๋‚ด ๋ณด๊ณ ์™€ ๋น„์šฉ ์ •์‚ฐ์€ ์›ํ™”(โ‚ฉ)๋กœ ์ด๋ฃจ์–ด์ง‘๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ ํ™˜์œจ ๋ณ€ํ™˜์ด ํ•„์ˆ˜์ ์ธ๋ฐ, ์—ฌ๊ธฐ์„œ ์ƒ๊ฐ๋ณด๋‹ค ๊นŒ๋‹ค๋กœ์šด ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

๋ฐ”๋กœ MSP๊ฐ€ ๋งค์›” ์ฒญ๊ตฌ์„œ๋ฅผ ๋ฐœํ–‰ํ•  ๋•Œ ์ ์šฉํ•˜๋Š” ํ™•์ • ํ™˜์œจ์ž…๋‹ˆ๋‹ค.

MSP๋Š” ์ฒญ๊ตฌ์„œ๋ฅผ ๋ฐœํ–‰ํ•˜๋Š” ์‹œ์ ์˜ ํ™˜์œจ์„ ํ™•์ •ํ•ด์„œ ์ ์šฉํ•˜๋Š”๋ฐ, ์ด ํ™•์ • ํ™˜์œจ์€ ์ฒญ๊ตฌ์„œ๊ฐ€ ๋‚˜์˜ค๊ธฐ ์ „๊นŒ์ง€๋Š” ์•Œ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

๊ทธ๋Ÿฐ๋ฐ ์ €๋Š” ๋งค์ฃผ ๋น„์šฉ์„ ์ง‘๊ณ„ํ•ด์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค.
ํ™˜์œจ ์ฒ˜๋ฆฌ๋ฅผ ์ œ๋Œ€๋กœ ํ•˜์ง€ ๋ชปํ•˜๋ฉด, ๋งˆ์ง€๋ง‰ ์ฃผ ๋ณด๊ณ  ๊ธˆ์•ก๊ณผ ์‹ค์ œ ์ฒญ๊ตฌ ๊ธˆ์•ก ์‚ฌ์ด์— ํฐ ์˜ค์ฐจ๊ฐ€ ์ƒ๊ฒผ์Šต๋‹ˆ๋‹ค.

๊ทธ๋ž˜์„œ ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด,
์ €๋Š” ์‹œ์ ์— ๋”ฐ๋ผ ํ™˜์œจ ์†Œ์Šค๋ฅผ ์ด์›ํ™”ํ•˜๋Š” ๋ฐฉ์‹์„ ํƒํ–ˆ์Šต๋‹ˆ๋‹ค.

์ฒญ๊ตฌ๊ฐ€ ํ™•์ •๋œ ๋‹ฌ์ด๋ฉด PDF ๋ช…์„ธ์„œ์—์„œ ํ™˜์œจ์„ ํŒŒ์‹ฑํ•ฉ๋‹ˆ๋‹ค.

const match = result.text.match(/์ ์šฉ ํ™˜์œจ\s*:\s*([\d.]+)/);
if (!match) throw new Error('PDF์—์„œ ํ™˜์œจ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.');
return Number(match[1]);

ํ™•์ • ์ „์ด๋ผ๋ฉด ExchangeRate API๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

const res = await fetch('https://api.exchangerate-api.com/v4/latest/USD');
const data = await res.json() as { rates: { KRW: number } };
return data.rates.KRW;

ํ™˜์œจ์„ ๋ณด๊ณ ์— ํฌํ•จํ•ด ์ถ”์ ํ•˜๊ฒŒ ๋˜๋ฉด์„œ,
๋‹ฌ๋Ÿฌ ๊ธฐ์ค€์œผ๋กœ๋Š” ๋ณ€ํ™”๊ฐ€ ์—†๋Š”๋ฐ ์›ํ™”๋กœ๋Š” ํฌ๊ฒŒ ์ฐจ์ด ๋‚˜ ๋ณด์ด๋Š” ์ƒํ™ฉ๋„ ์ˆ˜์น˜๋กœ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

์ƒ๊ฐ๋ณด๋‹ค ์ง€์ €๋ถ„ํ•œ ๋ฐ์ดํ„ฐ

๋ฐ์ดํ„ฐ ์ž์ฒด์—๋„ ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

์ฒซ ๋ฒˆ์งธ๋กœ ์ธ์ฝ”๋”ฉ ๋ฌธ์ œ๋ฅผ ๋ฐœ๊ฒฌํ–ˆ์Šต๋‹ˆ๋‹ค.

MSP์˜ CSV๋Š” EUC-KR๋กœ ์ธ์ฝ”๋”ฉ๋˜์–ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.
๊ทธ๋Œ€๋กœ ์ฒ˜๋ฆฌํ•˜๋ ค๊ณ  ํ•˜๋ฉด ํ•œ๊ธ€์ด ๊นจ์ ธ์„œ, UTF-8๋กœ ๋ณ€ํ™˜ํ•ด์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค.

const buffer = Buffer.from(await res.arrayBuffer());
const utf8 = iconv.decode(buffer, 'EUC-KR');
fs.writeFileSync(filePath, utf8, 'utf8');

๋‘ ๋ฒˆ์งธ๋กœ ์›” ๋‹จ์œ„ ๊ณผ๊ธˆ ๋ฐ์ดํ„ฐ๊ฐ€ ์™œ๊ณก์„ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.

์›” ๋‹จ์œ„๋กœ ๊ณผ๊ธˆ๋˜๋Š” ํ•ญ๋ชฉ์€ ์›”์ดˆ์— ํ•ด๋‹น ์›” ์ „์ฒด ๋‚ ์งœ์— ๊ธˆ์•ก์ด ๋ฏธ๋ฆฌ ๋ถ„์‚ฐ๋˜์–ด ์ฐํž™๋‹ˆ๋‹ค.

์›” ์ค‘๊ฐ„์— ์ง‘๊ณ„ํ•˜๋ฉด ์•„์ง ์ง€๋‚˜์ง€ ์•Š์€ ๋‚ ์งœ์˜ ๋ฐ์ดํ„ฐ๊นŒ์ง€ ํ•ฉ์‚ฐ๋˜๊ฑฐ๋‚˜,
๋ฐ˜๋Œ€๋กœ ์•„์ง ์ฐํžˆ์ง€ ์•Š์€ ํ•ญ๋ชฉ์ด ๋น ์ ธ ๋น„์ด์ƒ์ ์ธ ์ˆ˜์น˜๊ฐ€ ๋‚˜์™”์Šต๋‹ˆ๋‹ค.

๊ทธ๋ž˜์„œ ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋Š” ์˜ค๋Š˜ ๊ธฐ์ค€ ์ดํ‹€ ์ „๊นŒ์ง€๋งŒ ํฌํ•จํ•˜๊ณ ,
์›”๋ง์ด ์ง€๋‚˜๋ฉด ํ•ด๋‹น ์›” ์ „์ฒด๋ฅผ ์“ฐ๋„๋ก ์ฒ˜๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค.

const cutoff = monthEnd < todayMinus2Str ? monthEnd : todayMinus2Str;

์„ธ ๋ฒˆ์งธ๋กœ ๋…ธ์ด์ฆˆ๊ฐ€ ๋งŽ์•˜์Šต๋‹ˆ๋‹ค.

์ฒ˜๋ฆฌ ์‹œ๊ฐ„์ด ์˜ˆ์ƒ๋ณด๋‹ค ์˜ค๋ž˜ ๊ฑธ๋ ค ์›์ธ์„ ํŒŒ์•…ํ•˜๋˜ ์ค‘,
์ „์ฒด ๋ฐ์ดํ„ฐ์˜ ์•ฝ 40%๊ฐ€ $0 ํ–‰์ด๋ผ๋Š” ์‚ฌ์‹ค์„ ์•Œ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

์• ์ดˆ์— AWS CUR ๋ฐ์ดํ„ฐ๋Š” ์ถ”์ •์น˜์ด๊ธฐ ๋•Œ๋ฌธ์— ์™„๋ฒฝํ•œ ์ •ํ™•๋„๋ฅผ ๊ธฐ๋Œ€ํ•˜๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค.

์ •ํ™•ํ•œ ์ˆ˜์น˜๋ณด๋‹ค ์ถ”์ด๋ฅผ ํŒŒ์•…ํ•˜๋Š” ๊ฒƒ์ด ๋ชฉ์ ์ด์—ˆ๊ธฐ์—,
$0 ํ–‰์€ ๊ณผ๊ฐํžˆ ๋…ธ์ด์ฆˆ๋กœ ํŒ๋‹จํ•˜๊ณ  ์ œ์™ธํ–ˆ์Šต๋‹ˆ๋‹ค.

if (isNaN(usd) || usd <= 0) continue;

๋ณด์ด๋Š”๋ฐ ๋ชจ๋ฅด๊ฒ ๋‹ค

์ •๊ทœํ™”๊ฐ€ ๋๋‚˜๋ฉด ๋ฐ์ดํ„ฐ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ชจ์Šต์ž…๋‹ˆ๋‹ค.

โ€ฆResourceIdProductNameโ€ฆ๊ธˆ์•ก(USD)
โ€ฆarn:aws:ec2:โ€ฆ/natgateway/nat-021bcd03d05c7abedAmazon Elastic Compute Cloudโ€ฆ0.248
โ€ฆi-0f59d872251530953Amazon Elastic Compute Cloudโ€ฆ0.1073
โ€ฆarn:aws:rds:โ€ฆ/db:dev-writerAmazon Relational Database Serviceโ€ฆ0.339

๋ณด์‹œ๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ResourceId ์ปฌ๋Ÿผ์—๋Š” ํ•œ๋ˆˆ์— ํŒŒ์•…ํ•˜๊ธฐ ์–ด๋ ค์šด ์‹๋ณ„์ž๋“ค๋งŒ ๊ฐ€๋“ํ•ฉ๋‹ˆ๋‹ค.
์ด๋Œ€๋กœ ๊ณต์œ ํ•˜๋ฉด โ€œ์ด EC2 ์ธ์Šคํ„ด์Šค๊ฐ€ ์–ด๋–ค ์„œ๋น„์Šค์šฉ์ธ๊ฐ€์š”?โ€๋ผ๋Š” ์งˆ๋ฌธ์ด ์Ÿ์•„์งˆ ์ˆ˜๋ฐ–์— ์—†์Šต๋‹ˆ๋‹ค.

๊ทผ๋ณธ์ ์ธ ํ•ด๊ฒฐ์ฑ…์€ ๋ชจ๋“  ๋ฆฌ์†Œ์Šค์— ์—„๊ฒฉํ•œ ํƒœ๊น… ๊ทœ์น™์„ ์ ์šฉํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ์ „์‚ฌ์ ์ธ ๋„ค์ด๋ฐ ์ปจ๋ฒค์…˜์„ ์ƒˆ๋กœ ์ •๋ฆฝํ•˜๊ณ ,
ํ…Œ๋ผํผ ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•ด ์ „์ฒด ๋ฆฌ์†Œ์Šค๋ฅผ ์žฌ๋ฐฐํฌํ•ด์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค.

๋‹น์žฅ ์‹คํ–‰ํ•˜๊ธฐ์—” ๋ฆฌ์Šคํฌ์™€ ๊ณต์ˆ˜๊ฐ€ ๋„ˆ๋ฌด ํฐ ์ผ์ด์—ˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋ž˜์„œ ํ˜„์žฌ ๋‹ฌ๋ ค ์žˆ๋Š” ํƒœ๊ทธ ๊ฐ’์ด๋ผ๋„ ์ตœ๋Œ€ํ•œ ํ™œ์šฉํ•˜๋Š” ๋ฐฉํ–ฅ์œผ๋กœ ์ ‘๊ทผํ–ˆ์Šต๋‹ˆ๋‹ค.
AWS API๋ฅผ ์ง์ ‘ ์กฐํšŒํ•ด Name ํƒœ๊ทธ๋ฅผ ๊ฐ€์ ธ์˜จ ๋’ค,
ResourceId์™€ ๋งค์นญํ•ด ์‚ฌ๋žŒ์ด ์ฝ๊ธฐ ์‰ฌ์šด ์ด๋ฆ„์œผ๋กœ ์น˜ํ™˜ํ–ˆ์Šต๋‹ˆ๋‹ค.

๋˜ํ•œ API ํ˜ธ์ถœ ๋ถ€ํ•˜๋ฅผ ์ค„์ด๊ธฐ ์œ„ํ•ด ๋ฐฐ์น˜ ๋ฐฉ์‹์„ ์ ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.

CSV ์ „์ฒด๋ฅผ ๋จผ์ € ์Šค์บ”ํ•ด ํ•„์š”ํ•œ ID ๋ชฉ๋ก์„ ์ถ”์ถœํ•˜๊ณ ,
๋ฆฌ์ „๋ณ„๋กœ ๋ถ„๋ฅ˜ํ•œ ๋’ค ๋ฆฌ์ „๋‹น ํ•œ ๋ฒˆ์”ฉ๋งŒ ์กฐํšŒํ–ˆ์Šต๋‹ˆ๋‹ค.

for (const row of rows) {
  const name = extractResourceName(row.resourceId);
  const region = getArnRegion(row.resourceId) ?? PREFIX_TO_REGION[usageTypePrefix] ?? 'ap-northeast-2';
  if (/^i-[0-9a-f]+$/.test(name))        regionInstanceMap.get(region)?.add(name);
  else if (/^nat-[0-9a-f]+$/.test(name)) regionNatMap.get(region)?.add(name);
  else if (/^vol-[0-9a-f]+$/.test(name)) regionVolumeMap.get(region)?.add(name);
}

for (const [region, ids] of regionInstanceMap) {
  const res = await ec2.send(new DescribeInstancesCommand({
    Filters: [{ Name: 'instance-id', Values: [...ids] }],
  }));
  for (const instance of res.Reservations?.flatMap(r => r.Instances ?? []) ?? []) {
    const name = instance.Tags?.find(t => t.Key === 'Name')?.Value;
    if (instance.InstanceId && name) nameMap.set(instance.InstanceId, name);
  }
}

EBS ๋ณผ๋ฅจ์€ ๋ณผ๋ฅจ ์ž์ฒด์— Name ํƒœ๊ทธ๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์•˜์Šต๋‹ˆ๋‹ค.
๋ณผ๋ฅจ์— ๋ถ™์–ด์žˆ๋Š” EC2 ์ธ์Šคํ„ด์Šค๋ฅผ ํ•œ ๋ฒˆ ๋” ์กฐํšŒํ•ด์„œ ์ธ์Šคํ„ด์Šค ์ด๋ฆ„์„ ๋Œ€์‹  ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.

for (const vol of res.Volumes ?? []) {
  const instanceId = vol.Attachments?.[0]?.InstanceId;
  if (vol.VolumeId && instanceId) volumeToInstance.set(vol.VolumeId, instanceId);
}
if (name) nameMap.set(volId, `${name} (volume)`);

๋ฆฌ์†Œ์Šค ๊ฑฐ๋ฒ„๋„Œ์Šค๊ฐ€ ์ž˜ ์žกํ˜€์žˆ๋‹ค๋ฉด ์ด ๊ณผ์ •์ด ํ•„์š” ์—†๊ฒ ์ง€๋งŒ,
ํƒœ๊ทธ๊ฐ€ ์—†๊ฑฐ๋‚˜ ์ œ๊ฐ๊ฐ์ธ ํ™˜๊ฒฝ์—์„œ๋Š” ์ด ๋ฐฉ์‹์ด ํ˜„์‹ค์ ์ธ ๋Œ€์•ˆ์ด์—ˆ์Šต๋‹ˆ๋‹ค.

์šฐ๋ฆฌ๊ฐ€ ์›ํ•˜๋Š” ๋Œ€๋กœ

MSP ์ฝ˜์†”์—์„œ๋„ ์„œ๋น„์Šค๋ณ„ ๋น„์šฉ์€ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.
ํ•˜์ง€๋งŒ ์ €๋Š” ๊ทธ ๋ฐ์ดํ„ฐ๋ฅผ ์šฐ๋ฆฌ ๋ณด๊ณ  ๋ฐฉ์‹์— ๋งž๊ฒŒ ๋‹ค์‹œ ๊ฐ€๊ณตํ•ด์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค.

AWS ์„œ๋น„์Šค โ†’ SKU(usageType) โ†’ ๋ฆฌ์†Œ์Šค ์ˆœ์œผ๋กœ ๊ณ„์ธต์„ ์žก์•„ ํ•ฉ์‚ฐํ•˜๊ณ ,
๊ธˆ์•ก ๊ธฐ์ค€์œผ๋กœ ๋‚ด๋ฆผ์ฐจ์ˆœ ์ •๋ ฌํ•ด ํ•œ๋ˆˆ์— ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค.

๋˜ํ•œ ํ•ต์‹ฌ ์ถ”์ด๋ฅผ ๋ณด๋Š” ๊ฒƒ์ด ๋ชฉ์ ์ด์—ˆ๊ธฐ์—,
$5 ๋ฏธ๋งŒ ํ•ญ๋ชฉ์€ ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋‚ฎ๋‹ค๊ณ  ํŒ๋‹จํ•ด ์ œ์™ธํ–ˆ์Šต๋‹ˆ๋‹ค.

const svc = services.get(row.productName)!;
svc.usd += row.usd;

const sku = svc.skus.get(usageTypeParsed)!;
sku.usd += row.usd;

// ...

const sortedServices = [...services.values()].sort((a, b) => b.usd - a.usd);
for (const svc of sortedServices) {
  if (svc.usd < 5) continue;
  // ...
}

์ •ํ™•ํ•œ ์ถ”์ด๋ฅผ ์œ„ํ•ด

์ˆซ์ž๋งŒ ๋ณด๋ฉด ์ด๊ฒŒ ๋งŽ์€ ๊ฑด์ง€ ์ ์€ ๊ฑด์ง€ ์•Œ๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค.
์˜๋ฏธ ์žˆ๋Š” ํŒ๋‹จ์„ ํ•˜๋ ค๋ฉด ์ „์›”์ด๋‚˜ ์ตœ๊ทผ ๋ช‡ ๋‹ฌ๊ณผ ๋น„๊ตํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์—ฌ๊ธฐ์„œ ๋‘ ๊ฐ€์ง€ ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

์ฒซ ๋ฒˆ์งธ๋กœ ์›”๋งˆ๋‹ค ๋‚ ์งœ ์ˆ˜๊ฐ€ ๋‹ค๋ฆ…๋‹ˆ๋‹ค.
๋‹จ์ˆœํžˆ ์ด๋ฒˆ ๋‹ฌ ํ•ฉ๊ณ„์™€ ์ง€๋‚œ ๋‹ฌ ํ•ฉ๊ณ„๋ฅผ ๋น„๊ตํ•˜๋ฉด ๋‚ ์งœ ์ฐจ์ด๋งŒํผ ์™œ๊ณก์ด ์ƒ๊น๋‹ˆ๋‹ค.

๋‘ ๋ฒˆ์งธ๋กœ ์ €๋Š” ์›” ์ค‘๊ฐ„์—๋„ ์ง‘๊ณ„ํ•ฉ๋‹ˆ๋‹ค.
์ด๋‹ฌ 1์ผ๋ถ€ํ„ฐ 15์ผ์„ ์ง‘๊ณ„ํ–ˆ๋‹ค๋ฉด,
์ „์›”๋„ 1์ผ๋ถ€ํ„ฐ 15์ผ๊นŒ์ง€๋งŒ ์ž˜๋ผ์„œ ๋น„๊ตํ•ด์•ผ ๊ณต์ •ํ•œ ๋น„๊ต๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.

์ด ๋‘ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋ ค๋ฉด ์ด์ „ ๋‹ฌ ๋ฐ์ดํ„ฐ๋ฅผ ๋‚ ์งœ ๋‹จ์œ„๋กœ ๋ณด๊ด€ํ•ด๋‘์–ด์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค.
๊ทธ๋ž˜์„œ ์‹คํ–‰ํ•  ๋•Œ๋งˆ๋‹ค ์ง‘๊ณ„ ๊ฒฐ๊ณผ๋ฅผ data.json์œผ๋กœ ์ €์žฅํ•ด๋‘๊ณ ,
๋‹ค์Œ ๋‹ฌ ์‹คํ–‰ ์‹œ ๋ถˆ๋Ÿฌ์™€์„œ ๊ฐ™์€ ์ผ์ž๊นŒ์ง€๋งŒ ์ž˜๋ผ ๋น„๊ตํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์ฒ˜๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค.

const prevAccDataPath = path.join(OUTPUT_PATH, prevYearMonth, 'aws', account.slug, 'data.json');
const prevAccData = fs.existsSync(prevAccDataPath)
  ? JSON.parse(fs.readFileSync(prevAccDataPath, 'utf8'))
  : null;

// ...

fs.writeFileSync(path.join(accOutDir, 'data.json'), JSON.stringify(accDataJson, null, 2) + '\n', 'utf8');

์„œ๋น„์Šค๋ณ„, SKU๋ณ„, ๋ฆฌ์†Œ์Šค๋ณ„๋กœ ์ „์›” ๊ฐ’์ด ์žˆ์œผ๋ฉด ์ฐจ์ด๋ฅผ ํ•จ๊ป˜ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.
๊ทธ๋ž˜ํ”„๋„ ๋‹น์›”๊ณผ ์ „์›”์„ ๊ฒน์ณ์„œ ํ•œ๋ˆˆ์— ์ถ”์ด๋ฅผ ๋ณผ ์ˆ˜ ์žˆ๋„๋ก ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

์ƒ์„ฑ๋œ ๋นŒ๋ง ์ฐจํŠธ - ์ „์›” ๋Œ€๋น„ ๋‹น์›” ๋น„๊ต

๋งˆ์น˜๋ฉฐ

์ด ๋ชจ๋“  ๊ณผ์ •์ด ๋๋‚˜๋ฉด ์„œ๋น„์Šค๋ณ„ ์ฐจํŠธ์™€ ๊ณ„์ •๋ณ„ ๋ณด๊ณ ์„œ๊ฐ€ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค.
์ €๋Š” ๊ทธ ๊ณผ์ •์—์„œ ์ „์›” ๋Œ€๋น„ ํฌ๊ฒŒ ์˜ค๋ฅธ ์„œ๋น„์Šค๋‚˜ ๋ฆฌ์†Œ์Šค๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

๋‹จ์ˆœํžˆ ์ˆ˜์ž‘์—…์ด ๊ท€์ฐฎ์•„์„œ ์‹œ์ž‘ํ•œ ์ž๋™ํ™”์˜€์ง€๋งŒ,
๊ฒฐ๊ณผ์ ์œผ๋กœ๋Š” ๋ฐ์ดํ„ฐ์— ๊ธฐ๋ฐ˜ํ•˜์—ฌ ์ž์‹  ์žˆ๊ฒŒ ๋งํ•  ์ˆ˜ ์žˆ๋Š” ๊ทผ๊ฑฐ๊ฐ€ ์ƒ๊ฒผ์Šต๋‹ˆ๋‹ค.

TypeScript๋ฅผ ์“ธ ์ค„ ๋ชฐ๋ž๋˜ ์ € ํ˜ผ์ž์˜€๋‹ค๋ฉด ์—ฌ๊ธฐ๊นŒ์ง€ ์˜ค์ง€ ๋ชปํ–ˆ์„ ๊ฒ๋‹ˆ๋‹ค.
Claude์™€ ํ•จ๊ป˜์˜€๊ธฐ์— ๊ฐ€๋Šฅํ•œ ์ผ์ด์—ˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋ ‡๊ฒŒ ์˜ค๋Š˜๋„ ์ €๋Š” ๋Œ€๋‹ตํ•ฉ๋‹ˆ๋‹ค.

โ€œ๋„ค, ์ด๋ฒˆ ์ฃผ๋„ ํŠน์ด ์‚ฌํ•ญ ์—†์Šต๋‹ˆ๋‹ค.โ€

โ† ๋ชฉ๋ก์œผ๋กœ ๋Œ์•„๊ฐ€๊ธฐ

Art Changes Life

๋…ธ๋จธ์Šค์™€ ํ•จ๊ป˜ ์—”ํ„ฐํ…Œํฌ ์‚ฐ์—…์„ ํ˜์‹ ํ•ด๋‚˜๊ฐˆ ๋ฉค๋ฒ„๋ฅผ ์ฐพ์Šต๋‹ˆ๋‹ค.

์ฑ„์šฉ ์ค‘์ธ ๊ณต๊ณ  ๋ณด๊ธฐ