<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: GyeongSeon</title>
    <description>The latest articles on DEV Community by GyeongSeon (@shingyeongseon).</description>
    <link>https://dev.to/shingyeongseon</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3389597%2F235f5e26-6000-4009-ae39-f25440945197.png</url>
      <title>DEV Community: GyeongSeon</title>
      <link>https://dev.to/shingyeongseon</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shingyeongseon"/>
    <language>en</language>
    <item>
      <title>Istio 핵심 개념 정리</title>
      <dc:creator>GyeongSeon</dc:creator>
      <pubDate>Sun, 22 Mar 2026 13:28:33 +0000</pubDate>
      <link>https://dev.to/shingyeongseon/istio-haegsim-gaenyeom-jeongri-g39</link>
      <guid>https://dev.to/shingyeongseon/istio-haegsim-gaenyeom-jeongri-g39</guid>
      <description>&lt;h1&gt;
  
  
  Istio 쉽게 이해하기 — DevOps / Platform 엔지니어 관점 정리
&lt;/h1&gt;

&lt;h2&gt;
  
  
  한 줄 요약
&lt;/h2&gt;

&lt;p&gt;Istio는 &lt;strong&gt;Sidecar(Envoy)를 통해 모든 서비스 간 트래픽을 통제하고, Istiod가 정책(config)을 내려 네트워크를 코드처럼 다루게 해주는 시스템&lt;/strong&gt;이다.&lt;/p&gt;




&lt;h1&gt;
  
  
  1. 먼저 구조부터 이해하자
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
Client → Pod(App) → Envoy → 네트워크 → 상대 Envoy → 상대 App
↑
Istiod

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;모든 요청은 Envoy를 통과한다&lt;/li&gt;
&lt;li&gt;Istiod는 Envoy에게 “어떻게 동작할지”를 알려준다&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 핵심  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;애플리케이션이 아니라 &lt;strong&gt;네트워크 레이어에서 제어한다&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  2. Sidecar / Envoy 핵심 정리
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Sidecar
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Pod 안에서 앱 옆에 붙는 컨테이너&lt;/li&gt;
&lt;li&gt;트래픽을 가로채고 처리&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Envoy
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Istio가 사용하는 실제 프록시 엔진&lt;/li&gt;
&lt;li&gt;HTTP / gRPC 이해&lt;/li&gt;
&lt;li&gt;라우팅 / 보안 / 메트릭 담당&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 관계&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;
&lt;span class="s"&gt;Istio = Control Plane&lt;/span&gt;
&lt;span class="s"&gt;Envoy = Data Plane&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
`&lt;/p&gt;




&lt;h1&gt;
  
  
  3. Istiod의 “정책”은 무엇인가?
&lt;/h1&gt;

&lt;p&gt;👉 핵심  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Kubernetes CRD로 정의된 설정을 Envoy가 이해할 수 있는 config로 변환해서 내려준다&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  4. CRD를 “역할 중심”으로 이해하기
&lt;/h1&gt;

&lt;p&gt;CRD를 하나씩 외우면 헷갈린다.&lt;br&gt;&lt;br&gt;
👉 역할 기준으로 보면 바로 이해된다.&lt;/p&gt;




&lt;h2&gt;
  
  
  4.1 Gateway — "입구"
&lt;/h2&gt;

&lt;p&gt;👉 외부에서 들어오는 요청을 받는 지점&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`yaml&lt;br&gt;
kind: Gateway&lt;br&gt;
spec:&lt;br&gt;
  servers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;port:
  number: 80
  protocol: HTTP
hosts:

&lt;ul&gt;
&lt;li&gt;"*"
&lt;code&gt;&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;📌 비유&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Nginx / LoadBalancer&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  4.2 VirtualService — "어디로 보낼지 결정"
&lt;/h2&gt;

&lt;p&gt;👉 요청을 어떤 서비스로 보낼지 정의&lt;/p&gt;

&lt;p&gt;`&lt;code&gt;&lt;/code&gt;yaml&lt;br&gt;
kind: VirtualService&lt;br&gt;
spec:&lt;br&gt;
  hosts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;my-service
http:&lt;/li&gt;
&lt;li&gt;route:

&lt;ul&gt;
&lt;li&gt;destination:
host: my-service
subset: v1
weight: 90&lt;/li&gt;
&lt;li&gt;destination:
host: my-service
subset: v2
weight: 10
&lt;code&gt;&lt;/code&gt;`&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;📌 비유&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;API Gateway 라우팅 규칙&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;📌 역할&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Canary 배포&lt;/li&gt;
&lt;li&gt;A/B 테스트&lt;/li&gt;
&lt;li&gt;Path 기반 라우팅&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  4.3 DestinationRule — "어떻게 보낼지 결정"
&lt;/h2&gt;

&lt;p&gt;👉 선택된 서비스에 대해 &lt;strong&gt;전송 방식 정의&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;`yaml&lt;br&gt;
kind: DestinationRule&lt;br&gt;
spec:&lt;br&gt;
  host: my-service&lt;br&gt;
  trafficPolicy:&lt;br&gt;
    loadBalancer:&lt;br&gt;
      simple: ROUND_ROBIN&lt;br&gt;
    tls:&lt;br&gt;
      mode: ISTIO_MUTUAL&lt;br&gt;
`&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;📌 역할&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;로드밸런싱 방식&lt;/li&gt;
&lt;li&gt;mTLS 적용&lt;/li&gt;
&lt;li&gt;connection pool / retry&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  4.4 AuthorizationPolicy — "접근 가능한지 검사"
&lt;/h2&gt;

&lt;p&gt;👉 요청을 허용할지 차단할지 결정&lt;/p&gt;

&lt;p&gt;`&lt;code&gt;&lt;/code&gt;yaml&lt;br&gt;
kind: AuthorizationPolicy&lt;br&gt;
spec:&lt;br&gt;
  rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;from:

&lt;ul&gt;
&lt;li&gt;source:
namespaces: ["frontend"]
&lt;code&gt;&lt;/code&gt;`&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;📌 역할&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;namespace 기반 접근 제어&lt;/li&gt;
&lt;li&gt;service account 기반 제어&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  4.5 PeerAuthentication — "암호화 강제"
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;`yaml&lt;br&gt;
kind: PeerAuthentication&lt;br&gt;
spec:&lt;br&gt;
  mtls:&lt;br&gt;
    mode: STRICT&lt;br&gt;
`&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;📌 역할&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;mTLS 필수 여부 설정&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  5. CRD를 하나의 흐름으로 보면
&lt;/h1&gt;

&lt;p&gt;👉 이게 가장 중요하다&lt;/p&gt;




&lt;h2&gt;
  
  
  요청 흐름 (직관적으로)
&lt;/h2&gt;

&lt;p&gt;`&lt;code&gt;&lt;/code&gt;yaml&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Gateway → 요청 받음&lt;/li&gt;
&lt;li&gt;VirtualService → 어디로 보낼지 결정&lt;/li&gt;
&lt;li&gt;DestinationRule → 어떻게 보낼지 결정&lt;/li&gt;
&lt;li&gt;Envoy → 실제 전달&lt;/li&gt;
&lt;li&gt;AuthorizationPolicy → 허용 여부 검사
&lt;code&gt;&lt;/code&gt;`&lt;/li&gt;
&lt;/ol&gt;




&lt;h1&gt;
  
  
  6. 트래픽 가로채기 (iptables)
&lt;/h1&gt;

&lt;p&gt;👉 중요 포인트&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;앱이 Envoy로 보내는 것이 아님&lt;/li&gt;
&lt;li&gt;OS가 강제로 Envoy로 보냄&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;`plaintext&lt;br&gt;
App → iptables → Envoy → 외부&lt;br&gt;
`&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;👉 결과&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Envoy를 절대 우회할 수 없음&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  7. TLS / mTLS
&lt;/h1&gt;

&lt;h2&gt;
  
  
  TLS
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;`plaintext&lt;br&gt;
Client → 암호화 → Server&lt;br&gt;
`&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  mTLS
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;`plaintext&lt;br&gt;
Client ↔ Server (서로 인증)&lt;br&gt;
`&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;👉 Istio에서는&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;인증서 자동 발급&lt;/li&gt;
&lt;li&gt;Envoy가 처리&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  8. 배포 전략 (Istio 활용)
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Canary 배포
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;`plaintext&lt;br&gt;
v1 → 90%&lt;br&gt;
v2 → 10%&lt;br&gt;
`&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A/B 테스트
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;`yaml&lt;br&gt;
User A → v1&lt;br&gt;
User B → v2&lt;br&gt;
`&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;👉 핵심&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;코드 수정 없이 트래픽 분배&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  9. 전체 흐름 다이어그램 (핵심)
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9look7xxkm1ntz80fcej.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9look7xxkm1ntz80fcej.png" alt=" " width="800" height="226"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  10. 한 번에 정리
&lt;/h1&gt;

&lt;p&gt;`&lt;code&gt;&lt;/code&gt;yaml&lt;br&gt;
Istio =&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Envoy → 트래픽 처리&lt;/li&gt;
&lt;li&gt;Istiod → 정책 전달&lt;/li&gt;
&lt;li&gt;iptables → 강제 라우팅&lt;/li&gt;
&lt;li&gt;TLS → 암호화&lt;/li&gt;
&lt;li&gt;RBAC → 접근 제어
&lt;code&gt;&lt;/code&gt;`&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  11. 실무 관점 핵심
&lt;/h1&gt;

&lt;p&gt;👉 Istio는 단순 네트워크 도구가 아니라&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"서비스 간 통신을 코드 없이 제어하는 플랫폼"&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  12. 이 글에서 중요한 포인트 3개
&lt;/h1&gt;

&lt;ol&gt;
&lt;li&gt;Envoy는 반드시 거친다&lt;/li&gt;
&lt;li&gt;Istiod는 설정을 내려준다&lt;/li&gt;
&lt;li&gt;CRD는 “역할 기반”으로 이해해야 한다&lt;/li&gt;
&lt;/ol&gt;




&lt;h1&gt;
  
  
  다음 글 추천
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Istio 디버깅 가이드 (Envoy / mTLS / iptables)&lt;/li&gt;
&lt;li&gt;Istio vs Ingress 실제 차이&lt;/li&gt;
&lt;li&gt;Multi-tenant 환경에서 Istio 설계&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>istio</category>
    </item>
    <item>
      <title>Time Table Template with AI guideline</title>
      <dc:creator>GyeongSeon</dc:creator>
      <pubDate>Tue, 03 Mar 2026 00:22:47 +0000</pubDate>
      <link>https://dev.to/shingyeongseon/time-table-template-with-ai-guideline-3cjj</link>
      <guid>https://dev.to/shingyeongseon/time-table-template-with-ai-guideline-3cjj</guid>
      <description>&lt;p&gt;핵심 설계 철학은 다음입니다:&lt;/p&gt;

&lt;p&gt;✅ 사용자가 대충 써도 AI가 반드시 작업을 쪼개야 함&lt;/p&gt;

&lt;p&gt;✅ 시간 미기재 시 자동 추정 강제&lt;/p&gt;

&lt;p&gt;✅ 결과물 기준(완료조건) 없으면 AI가 정의&lt;/p&gt;

&lt;p&gt;✅ 우선순위 없으면 AI가 재정렬&lt;/p&gt;

&lt;p&gt;✅ 일정 초과 시 자동 이월 전략 제시 필수&lt;/p&gt;




&lt;p&gt;📌 1️⃣ 주 목표 설정 템플릿 (AI 강제 분해 버전)&lt;/p&gt;

&lt;h1&gt;
  
  
  📅 주간 목표 설정 (AI 자동 분해 모드)
&lt;/h1&gt;

&lt;p&gt;날짜 범위: YYYY-MM-DD ~ YYYY-MM-DD&lt;/p&gt;




&lt;h2&gt;
  
  
  🔷 1. 이번 주에 해야 할 일 (그냥 편하게 적으세요)
&lt;/h2&gt;

&lt;h2&gt;
  
  
  - 
&lt;/h2&gt;

&lt;h2&gt;
  
  
  - 
&lt;/h2&gt;

&lt;p&gt;(설명은 거칠어도 됩니다. AI가 자동 정리합니다.)&lt;/p&gt;




&lt;h2&gt;
  
  
  🔷 2. AI 수행 지침 (필수 수행)
&lt;/h2&gt;

&lt;p&gt;아래를 반드시 수행하세요:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;입력된 작업을 의미 단위로 재정리&lt;/li&gt;
&lt;li&gt;유사 작업 통합&lt;/li&gt;
&lt;li&gt;결과물 기준(Definition of Done) 자동 정의&lt;/li&gt;
&lt;li&gt;우선순위 자동 재배치 (P1/P2/P3)&lt;/li&gt;
&lt;li&gt;각 목표의 총 소요시간 추정&lt;/li&gt;
&lt;li&gt;실행 가능한 작업 단위(1~3시간 단위)로 세분화&lt;/li&gt;
&lt;li&gt;이번 주 총 가용시간(근무 8시간 × 5일 - 점심 제외) 기준으로
초과 여부 분석&lt;/li&gt;
&lt;li&gt;초과 시 → 이월 제안 및 우선순위 조정안 제시&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  🔷 3. 출력 형식 강제
&lt;/h2&gt;

&lt;p&gt;반드시 아래 구조로 출력:&lt;/p&gt;

&lt;h3&gt;
  
  
  ✅ 재정의된 주간 핵심 목표 (Top 3~5)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;우선순위&lt;/th&gt;
&lt;th&gt;목표&lt;/th&gt;
&lt;th&gt;완료 기준&lt;/th&gt;
&lt;th&gt;예상 총 소요&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h3&gt;
  
  
  ✅ 세부 실행 작업 분해표
&lt;/h3&gt;

&lt;p&gt;(각 목표별 작업 쪼개기)&lt;/p&gt;




&lt;h3&gt;
  
  
  ✅ 주간 시간 분석
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;총 필요 시간:&lt;/li&gt;
&lt;li&gt;총 가용 시간:&lt;/li&gt;
&lt;li&gt;초과 여부:&lt;/li&gt;
&lt;li&gt;권장 조정안:&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;📌 2️⃣ 일 목표 계획 템플릿 (AI 자동 판단 강화 버전)&lt;/p&gt;

&lt;h1&gt;
  
  
  📆 일일 목표 자동 설계 요청
&lt;/h1&gt;

&lt;p&gt;날짜: YYYY-MM-DD&lt;/p&gt;




&lt;h2&gt;
  
  
  🔷 1. 오늘 해야 할 것 (거칠게 작성 가능)
&lt;/h2&gt;

&lt;h2&gt;
  
  
  - 
&lt;/h2&gt;

&lt;h2&gt;
  
  
  - 
&lt;/h2&gt;




&lt;h2&gt;
  
  
  🔷 2. 고정 일정 (있으면 작성)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;HH:MM ~ HH:MM :&lt;/li&gt;
&lt;li&gt;HH:MM ~ HH:MM :&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔷 3. AI 강제 수행 지침
&lt;/h2&gt;

&lt;p&gt;반드시 수행:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;오늘 해야 할 일들을&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;딥워크&lt;/li&gt;
&lt;li&gt;협업&lt;/li&gt;
&lt;li&gt;운영/잡무
로 자동 분류&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;완료조건이 없으면 자동 생성&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;시간 미기재 작업은&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;기본 30~60분 추정&lt;/li&gt;
&lt;li&gt;복잡 작업은 90~120분 추정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;오늘 실제 가용시간 계산&lt;br&gt;
(08:30~17:30, 점심 11:30~13:00 제외)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;총 작업 시간과 비교하여&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;초과 시 자동 우선순위 조정&lt;/li&gt;
&lt;li&gt;일부 작업 내일 이월 제안&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;오늘 반드시 끝낼 Top3 자동 선정&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  🔷 4. 출력 형식 강제
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ✅ 재정리된 오늘 목표
&lt;/h3&gt;

&lt;h3&gt;
  
  
  ✅ 작업 분류표
&lt;/h3&gt;

&lt;h3&gt;
  
  
  ✅ 시간 분석
&lt;/h3&gt;

&lt;h3&gt;
  
  
  ✅ 오늘의 Top3
&lt;/h3&gt;

&lt;h3&gt;
  
  
  ✅ 이월/조정 제안 (필수)
&lt;/h3&gt;




&lt;p&gt;📌 3️⃣ 30분 타임테이블 자동 생성 템플릿 (강제 스케줄링 모드)&lt;/p&gt;

&lt;h1&gt;
  
  
  🕒 30분 타임테이블 자동 생성 (강제 분해 모드)
&lt;/h1&gt;

&lt;p&gt;날짜: YYYY-MM-DD&lt;br&gt;
근무: 08:30~17:30&lt;br&gt;
점심: 11:30~13:00 (고정)&lt;/p&gt;




&lt;h2&gt;
  
  
  🔷 1. 오늘 목표 (거칠게 작성 가능)
&lt;/h2&gt;

&lt;h2&gt;
  
  
  - 
&lt;/h2&gt;

&lt;h2&gt;
  
  
  - 
&lt;/h2&gt;




&lt;h2&gt;
  
  
  🔷 2. 고정 일정
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;HH:MM ~ HH:MM :&lt;/li&gt;
&lt;li&gt;HH:MM ~ HH:MM :&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔷 3. 스케줄링 강제 규칙
&lt;/h2&gt;

&lt;p&gt;반드시 수행:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;모든 작업을 30분 블록 단위로 변환&lt;/li&gt;
&lt;li&gt;딥워크는 최소 60~120분 연속 배치&lt;/li&gt;
&lt;li&gt;잡무는 1~2회로 묶기&lt;/li&gt;
&lt;li&gt;하루 최소 30~60분 버퍼 확보&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;시간 초과 시:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;잡무 축소&lt;/li&gt;
&lt;li&gt;낮은 우선순위 이월&lt;/li&gt;
&lt;li&gt;부분 완료 단위로 쪼개기
→ 반드시 이월안 제시&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;시간 미기재 작업은 자동 추정 후 표시&lt;br&gt;
(예상치임을 명시)&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  🔷 4. 출력 형식 (절대 변경 금지)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1️⃣ 30분 단위 타임테이블 표
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;시간&lt;/th&gt;
&lt;th&gt;블록명&lt;/th&gt;
&lt;th&gt;목표(완료조건)&lt;/th&gt;
&lt;th&gt;비고&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h3&gt;
  
  
  2️⃣ 캘린더 입력용 이벤트 리스트
&lt;/h3&gt;

&lt;p&gt;YYYY-MM-DD HH:MM-HH:MM | 제목 | 설명&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>llm</category>
      <category>productivity</category>
    </item>
    <item>
      <title>'도메인 주도 개발 시작하기..' 읽고 인사이트 정리</title>
      <dc:creator>GyeongSeon</dc:creator>
      <pubDate>Tue, 17 Feb 2026 15:54:21 +0000</pubDate>
      <link>https://dev.to/shingyeongseon/domein-judo-gaebal-sijaghagi-ilggo-insaiteu-jeongri-2i0j</link>
      <guid>https://dev.to/shingyeongseon/domein-judo-gaebal-sijaghagi-ilggo-insaiteu-jeongri-2i0j</guid>
      <description>&lt;h1&gt;
  
  
  DDD 핵심 개념부터 JPA Fetch 전략까지 한 번에 정리하기
&lt;/h1&gt;

&lt;p&gt;오랜만에 '도메인 주도 개발 시작하기: DDD의 핵심개념 정리부터 구현까지' 읽고 독후감(?!) 작성함 , 내가 나중에 보고 기억할 수 있게 &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DDD의 핵심은 “도메인을 구조적으로 이해 가능하게 만들고, 일관성을 어디서 강하게 보장할지 명확히 정하는 것”이며, JPA의 지연 로딩과 Fetch 전략은 그 설계를 기술적으로 구현하는 수단이다.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;이 글은 다음 흐름으로 정리한다.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;도메인 정의&lt;/li&gt;
&lt;li&gt;엔티티와 밸류 객체&lt;/li&gt;
&lt;li&gt;엔티티 동일성의 의미&lt;/li&gt;
&lt;li&gt;애그리거트의 등장 배경과 정의&lt;/li&gt;
&lt;li&gt;일관성 경계의 의미&lt;/li&gt;
&lt;li&gt;JPA를 활용한 애그리거트 구현&lt;/li&gt;
&lt;li&gt;지연 로딩 전략&lt;/li&gt;
&lt;li&gt;Fetch Join / EntityGraph&lt;/li&gt;
&lt;li&gt;JPQL / QueryDSL 예제&lt;/li&gt;
&lt;li&gt;이벤트 기반 동작&lt;/li&gt;
&lt;li&gt;CQRS 구성&lt;/li&gt;
&lt;li&gt;실무 점검 질문&lt;/li&gt;
&lt;/ol&gt;




&lt;h1&gt;
  
  
  1️⃣ 도메인 정의
&lt;/h1&gt;

&lt;p&gt;DDD에서 가장 중요한 것은 기술이 아니라 &lt;strong&gt;도메인&lt;/strong&gt;이다.&lt;/p&gt;

&lt;p&gt;도메인은 “해결하려는 문제 영역”이며,&lt;br&gt;
코드는 그 도메인을 반영해야 한다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CRUD 중심 사고 → 행위 중심 사고&lt;/li&gt;
&lt;li&gt;테이블 중심 설계 → 개념 중심 설계&lt;/li&gt;
&lt;li&gt;기능 구현 → 규칙 구현&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  2️⃣ 엔티티(Entity)와 밸류(Value Object)
&lt;/h1&gt;

&lt;h2&gt;
  
  
  엔티티
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;식별자(ID)를 가진다&lt;/li&gt;
&lt;li&gt;상태가 변한다&lt;/li&gt;
&lt;li&gt;동일성이 중요하다&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  밸류 객체
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;식별자가 없다&lt;/li&gt;
&lt;li&gt;값 자체가 의미다&lt;/li&gt;
&lt;li&gt;불변으로 설계하는 것이 이상적&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;구분&lt;/th&gt;
&lt;th&gt;엔티티&lt;/th&gt;
&lt;th&gt;밸류&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;식별자&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;동일성 기준&lt;/td&gt;
&lt;td&gt;ID&lt;/td&gt;
&lt;td&gt;값&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;변경&lt;/td&gt;
&lt;td&gt;가능&lt;/td&gt;
&lt;td&gt;불변 권장&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h1&gt;
  
  
  3️⃣ 엔티티 동일성이 중요한 이유
&lt;/h1&gt;

&lt;p&gt;엔티티는 “값”이 아니라 “식별자”로 동일성을 판단한다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;값이 달라도 ID가 같으면 같은 엔티티&lt;/li&gt;
&lt;li&gt;값이 같아도 ID가 다르면 다른 엔티티&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;왜 중요한가?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Dirty Checking 기반 변경 추적&lt;/li&gt;
&lt;li&gt;낙관적 락 충돌 관리&lt;/li&gt;
&lt;li&gt;생성 vs 수정 구분&lt;/li&gt;
&lt;li&gt;컬렉션 중복 관리&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;엔티티는 “현재 값”이 아니라&lt;br&gt;
&lt;strong&gt;시간을 따라 추적되는 존재&lt;/strong&gt;다.&lt;/p&gt;




&lt;h1&gt;
  
  
  4️⃣ 애그리거트가 등장한 배경
&lt;/h1&gt;

&lt;p&gt;복잡한 도메인 모델은&lt;br&gt;
100개 테이블이 얽힌 ERD와 같다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;전체 구조 파악 어려움&lt;/li&gt;
&lt;li&gt;수정 영향도 예측 불가&lt;/li&gt;
&lt;li&gt;변경 회피 → 꼼수 증가&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;이를 해결하기 위해 등장한 개념이 &lt;strong&gt;애그리거트(Aggregate)&lt;/strong&gt; 다.&lt;/p&gt;




&lt;h1&gt;
  
  
  5️⃣ 애그리거트의 정확한 정의
&lt;/h1&gt;

&lt;p&gt;애그리거트는 두 가지 목적을 가진다.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. 구조적 단위
&lt;/h3&gt;

&lt;p&gt;도메인을 상위 수준에서 조망 가능하게 만드는 단위&lt;/p&gt;

&lt;h3&gt;
  
  
  2. 일관성 경계
&lt;/h3&gt;

&lt;p&gt;하나의 트랜잭션에서 강하게 보장해야 하는 규칙 범위&lt;/p&gt;

&lt;p&gt;즉,&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;애그리거트는 “하나의 트랜잭션에서 불변식을 반드시 보장해야 하는 최소 단위”다.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  6️⃣ 일관성 경계의 의미
&lt;/h1&gt;

&lt;p&gt;일관성 = 항상 참이어야 하는 규칙&lt;/p&gt;

&lt;p&gt;예:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;주문 합계는 주문 라인의 합과 같아야 한다&lt;/li&gt;
&lt;li&gt;결제 완료된 주문은 취소 불가&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;기준 질문:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;두 객체는 반드시 같은 트랜잭션에서 함께 변경되어야 하는가?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;YES → 같은 애그리거트&lt;br&gt;
NO → 다른 애그리거트 (ID 참조 + 이벤트)&lt;/p&gt;




&lt;h1&gt;
  
  
  7️⃣ JPA로 애그리거트 구현하기
&lt;/h1&gt;

&lt;h3&gt;
  
  
  1. 조회
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Repository는 애그리거트 단위 조회&lt;/li&gt;
&lt;li&gt;Lazy 기본&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. 상태 변경
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Dirty Checking 이해 필수&lt;/li&gt;
&lt;li&gt;트랜잭션 범위 명확히&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. 페이징
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Pageable 사용&lt;/li&gt;
&lt;li&gt;컬렉션 fetch join 주의&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Subselect / JPQL
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;복잡 조회는 JPQL&lt;/li&gt;
&lt;li&gt;객체 중심 쿼리&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  8️⃣ 지연 로딩(LAZY)의 필요성
&lt;/h1&gt;

&lt;p&gt;기본 전략:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;모든 연관은 LAZY로 시작한다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;왜?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;조인 폭발 방지&lt;/li&gt;
&lt;li&gt;불필요한 데이터 로드 방지&lt;/li&gt;
&lt;li&gt;애그리거트 경계 유지&lt;/li&gt;
&lt;li&gt;성능 안정성 확보&lt;/li&gt;
&lt;/ol&gt;




&lt;h1&gt;
  
  
  9️⃣ N+1 문제
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Order&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMember&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getName&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;1번 + N번 쿼리 발생&lt;/p&gt;

&lt;p&gt;해결 방법:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fetch Join&lt;/li&gt;
&lt;li&gt;EntityGraph&lt;/li&gt;
&lt;li&gt;DTO 프로젝션&lt;/li&gt;
&lt;li&gt;2단계 조회&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  🔟 Fetch Join
&lt;/h1&gt;

&lt;h2&gt;
  
  
  JPQL 예제
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;select&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
&lt;span class="n"&gt;join&lt;/span&gt; &lt;span class="n"&gt;fetch&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;member&lt;/span&gt;
&lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="n"&gt;join&lt;/span&gt; &lt;span class="n"&gt;fetch&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orderLines&lt;/span&gt;
&lt;span class="n"&gt;where&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;단건 상세 조회에 적합.&lt;/p&gt;

&lt;p&gt;주의:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;컬렉션 fetch + 페이징 거의 금지&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  1️⃣1️⃣ QueryDSL Fetch Join
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;queryFactory&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;selectFrom&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;join&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;member&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;fetchJoin&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;eq&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fetchOne&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;.fetchJoin()&lt;/code&gt;이 핵심.&lt;/p&gt;




&lt;h1&gt;
  
  
  1️⃣2️⃣ 목록 + 페이징 전략 (실무 패턴)
&lt;/h1&gt;

&lt;h3&gt;
  
  
  2단계 조회
&lt;/h3&gt;

&lt;p&gt;1️⃣ ID만 페이징 조회&lt;br&gt;
2️⃣ fetch join으로 재조회&lt;/p&gt;

&lt;p&gt;이 방식이 가장 안전하다.&lt;/p&gt;




&lt;h1&gt;
  
  
  1️⃣3️⃣ EntityGraph
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@EntityGraph&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attributePaths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"member"&lt;/span&gt;&lt;span class="o"&gt;})&lt;/span&gt;
&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;findByStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OrderStatus&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;로딩 정책 분리&lt;/li&gt;
&lt;li&gt;JPQL 오염 없음&lt;/li&gt;
&lt;li&gt;재사용성 높음&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  1️⃣4️⃣ Fetch Join vs EntityGraph
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;Fetch Join&lt;/th&gt;
&lt;th&gt;EntityGraph&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;제어력&lt;/td&gt;
&lt;td&gt;강함&lt;/td&gt;
&lt;td&gt;중간&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;재사용성&lt;/td&gt;
&lt;td&gt;낮음&lt;/td&gt;
&lt;td&gt;높음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;추천&lt;/td&gt;
&lt;td&gt;상세 조회&lt;/td&gt;
&lt;td&gt;재사용 조회&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h1&gt;
  
  
  1️⃣5️⃣ 이벤트 기반 동작
&lt;/h1&gt;

&lt;p&gt;애그리거트 간 직접 참조 대신&lt;br&gt;
&lt;strong&gt;도메인 이벤트 사용&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;장점:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;결합도 감소&lt;/li&gt;
&lt;li&gt;확장성 증가&lt;/li&gt;
&lt;li&gt;비동기 처리 가능&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;비동기 설계 고려사항:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;트랜잭션 보장 여부&lt;/li&gt;
&lt;li&gt;재시도 전략&lt;/li&gt;
&lt;li&gt;메시지 브로커 선택&lt;/li&gt;
&lt;li&gt;Outbox 패턴 적용 여부&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  1️⃣6️⃣ CQRS 구성
&lt;/h1&gt;

&lt;p&gt;Command와 Query 분리&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Query&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;상태 변경&lt;/td&gt;
&lt;td&gt;조회&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;도메인 중심&lt;/td&gt;
&lt;td&gt;읽기 모델 중심&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;조회는 DTO / Projection / View 활용&lt;br&gt;
도메인 모델 오염 방지&lt;/p&gt;




&lt;h1&gt;
  
  
  1️⃣7️⃣ OSIV와 LazyInitializationException
&lt;/h1&gt;

&lt;p&gt;트랜잭션 밖에서 LAZY 접근 시 발생.&lt;/p&gt;

&lt;p&gt;권장:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;서비스 계층에서 DTO 변환&lt;/li&gt;
&lt;li&gt;엔티티 직접 반환 금지&lt;/li&gt;
&lt;li&gt;OSIV는 신중히&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  전체 요약
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;도메인이 먼저다&lt;/li&gt;
&lt;li&gt;엔티티는 ID로 동일성 유지&lt;/li&gt;
&lt;li&gt;애그리거트는 일관성 경계&lt;/li&gt;
&lt;li&gt;연관은 기본 LAZY&lt;/li&gt;
&lt;li&gt;상세는 fetch join&lt;/li&gt;
&lt;li&gt;목록은 DTO 또는 2단계 조회&lt;/li&gt;
&lt;li&gt;이벤트로 애그리거트 간 연결&lt;/li&gt;
&lt;li&gt;CQRS로 조회/명령 분리&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;DDD 설계와 JPA Fetch 전략은&lt;br&gt;
분리된 주제가 아니라 하나의 설계 문제다.&lt;/p&gt;




&lt;h1&gt;
  
  
  스스로 점검 질문
&lt;/h1&gt;

&lt;ol&gt;
&lt;li&gt;내 애그리거트 경계는 명확한가?&lt;/li&gt;
&lt;li&gt;즉시 강제해야 할 불변식은 무엇인가?&lt;/li&gt;
&lt;li&gt;모든 연관이 LAZY인가?&lt;/li&gt;
&lt;li&gt;N+1이 실제 발생하는지 로그로 확인했는가?&lt;/li&gt;
&lt;li&gt;컬렉션 fetch + 페이징을 쓰고 있지 않은가?&lt;/li&gt;
&lt;li&gt;엔티티를 그대로 API로 반환하고 있지 않은가?&lt;/li&gt;
&lt;li&gt;CQRS가 필요한 조회가 섞여 있지 않은가?&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>architecture</category>
      <category>backend</category>
      <category>java</category>
      <category>oop</category>
    </item>
    <item>
      <title>"가시성은 중앙에서, 제어는 서비스에서" : Azure 환경의 실전 LLM 토큰 거버넌스 설계</title>
      <dc:creator>GyeongSeon</dc:creator>
      <pubDate>Sun, 28 Dec 2025 14:10:40 +0000</pubDate>
      <link>https://dev.to/shingyeongseon/gasiseongeun-jungangeseo-jeeoneun-seobiseueseo-azure-hwangyeongyi-siljeon-llm-tokeun-geobeoneonseu-seolgye-2c9n</link>
      <guid>https://dev.to/shingyeongseon/gasiseongeun-jungangeseo-jeeoneun-seobiseueseo-azure-hwangyeongyi-siljeon-llm-tokeun-geobeoneonseu-seolgye-2c9n</guid>
      <description>&lt;h1&gt;
  
  
  TL:DR
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;PaaS 비용 문제의 본질은 플랫폼이 아니라 프롬프트·모델·RAG 설계 방식에 있다.&lt;/li&gt;
&lt;li&gt;토큰 중앙화의 목적은 통제가 아니라 조직 단위의 통찰 확보이며,&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;“가시성은 중앙에서, 실행은 서비스에서”&lt;/strong&gt;라는 선을 넘으면 구조는 오히려 무거워진다.&lt;/li&gt;
&lt;/ul&gt;




&lt;ul&gt;
&lt;li&gt;0. 글을 작성하게 된 개요&lt;/li&gt;
&lt;li&gt;1. 토큰이 많아질 때 시스템에서 실제로 발생하는 문제&lt;/li&gt;
&lt;li&gt;
2. Azure AI Foundry 비용 논쟁을 다시 바라봐야 하는 이유

&lt;ul&gt;
&lt;li&gt;1) PaaS 비용에는 '관리 오버헤드'가 포함되어 있습니다&lt;/li&gt;
&lt;li&gt;2) GPU 직접 운영 시의 '유휴 자원 비용'&lt;/li&gt;
&lt;li&gt;문제는 플랫폼이 아니라 '사용 방식'&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;3. 토큰 최적화는 중앙이 아니라 각 솔루션의 책임이다&lt;/li&gt;

&lt;li&gt;4. Ops 관점에서 필요한 것은 ‘제어’가 아니라 ‘관찰’이다&lt;/li&gt;

&lt;li&gt;5. Azure AI Foundry 기반 모니터링의 명확한 한계&lt;/li&gt;

&lt;li&gt;

6. [핵심] 다양한 환경에서 토큰 데이터를 전송하는 3가지 방법

&lt;ul&gt;
&lt;li&gt;방법 A: APIM(Azure API Management) 프록시 활용&lt;/li&gt;
&lt;li&gt;방법 B: 애플리케이션 직접 구현 (SDK / OpenTelemetry)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;7. 모든 데이터의 종착지: Log Analytics Workspace(LAW) 중앙화&lt;/li&gt;

&lt;li&gt;8. 중앙화된 시스템이 해결해주지 못하는 ‘제어’의 영역&lt;/li&gt;

&lt;li&gt;9. 결론: 토큰 중앙화의 목적은 ‘통찰’이지 ‘통제’가 아니다&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  0. 글을 작성하게 된 개요
&lt;/h2&gt;

&lt;p&gt;최근 프로젝트를 진행하며 여러 AI Solution을 &lt;strong&gt;Azure Native 환경으로 이관(Migration)&lt;/strong&gt;하는 작업을 간접적으로 접할 기회가 있었습니다. 해당 작업이 제 역할의 중심은 아니었지만, 설계 논의와 운영 이슈를 옆에서 지켜보며 자연스럽게 함께 고민하게 되었습니다.&lt;/p&gt;

&lt;p&gt;그 과정에서 눈에 띄었던 점은, 대부분의 AI Solution이 &lt;strong&gt;토큰 사용량을 명확한 운영 지표로 다루지 않고 있었다&lt;/strong&gt;는 것입니다. 모델 성능, 응답 품질, 기능 구현에는 많은 관심이 있었지만, 정작 얼마나 많은 토큰을 쓰고 있는지, 그 증가가 무엇을 의미하는지에 대한 논의는 부족했습니다.&lt;/p&gt;

&lt;p&gt;이 글은 &lt;strong&gt;“토큰 계산이 왜 이렇게 중요한가?”&lt;/strong&gt;라는 단순한 질문에서 출발합니다. 비용 이야기를 하기 이전에, 토큰이 시스템과 사용자 경험에 어떤 영향을 미치는지를 먼저 정리해보고자 합니다.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. 토큰이 많아질 때 시스템에서 실제로 발생하는 문제
&lt;/h2&gt;

&lt;p&gt;토큰 증가는 흔히 비용 문제로만 인식됩니다. 하지만 운영 관점에서 보면, 이는 훨씬 더 직접적인 &lt;strong&gt;리소스 소비 문제&lt;/strong&gt;입니다. 출력 토큰이 많아질수록 시스템은 다음과 같은 부담을 동시에 안게 됩니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;네트워크 점유 시간 증가:&lt;/strong&gt; 응답 데이터를 전송하기 위한 세션 유지 시간 증대&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;연결 유지 비용 증가:&lt;/strong&gt; 스트리밍 응답을 유지하기 위한 서버 및 로드밸런서의 부하&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Latency(지연 시간) 증가:&lt;/strong&gt; 전체 응답이 완료될 때까지의 대기 시간 가중&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;여기서 중요한 점은, 사용자 경험(UX)은 단순히 &lt;strong&gt;첫 토큰이 얼마나 빨리 도착했는지(TTFT)&lt;/strong&gt;로만 결정되지 않는다는 것입니다. 첫 토큰이 빠르게 도착하더라도, 출력 토큰이 과도하게 많다면 전체 응답이 끝나는 시점은 오히려 늦어질 수 있습니다.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;핵심 요약&lt;/strong&gt;&lt;br&gt;
출력 토큰이 많다는 것은 &lt;strong&gt;“사용자에게 전달해야 할 데이터가 많아졌고, 그만큼 시스템 자원을 오래 점유한다”&lt;/strong&gt;는 의미입니다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;제한된 리소스 환경에서는 이 특성이 곧바로 &lt;strong&gt;UX 저하, 동시 처리량 감소, 운영 리스크 증가&lt;/strong&gt;로 이어집니다. 그래서 토큰은 단순한 과금 단위가 아니라, 시스템 상태를 드러내는 &lt;strong&gt;'신호(Signal)'&lt;/strong&gt;로 다뤄져야 합니다.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Azure AI Foundry 비용 논쟁을 다시 바라봐야 하는 이유
&lt;/h2&gt;

&lt;p&gt;솔직히 말하면, 처음에는 &lt;strong&gt;"PaaS는 나를 눈탱이 친다"&lt;/strong&gt;고 생각했습니다. Azure AI Foundry를 쓰면서도 &lt;em&gt;“이 비용이 정말 예측 가능했는가?”&lt;/em&gt;라는 질문에 명확히 답할 수 없었고, OpenAI 모델 역시 충분히 사용해본 경험이 없었기 때문에 비용 구조를 신뢰하기 어려웠습니다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fek1p40ygqm3dt60tckdf.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fek1p40ygqm3dt60tckdf.jpg" alt="루리앱&amp;gt;제목_다구리에 장사없다&amp;gt;눈탱이 맞은 기영이" width="640" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;이런 경험이 쌓이다 보니 &lt;strong&gt;“PaaS는 편한 대신 무조건 비싸다”&lt;/strong&gt;라는 인식이 자연스럽게 자리 잡았습니다. 하지만 비용을 조금 더 구조적으로 분해해보면, 이 인식은 몇 가지 중요한 요소를 간과하고 있다는 것을 알게 됩니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  1) PaaS 비용에는 '관리 오버헤드'가 포함되어 있습니다
&lt;/h3&gt;

&lt;p&gt;GPU 프로비저닝, 스케줄링, 장애 대응, API 안정성, 모니터링과 같은 운영 비용은 눈에 잘 보이지 않지만, 자체 운영 시에는 반드시 사람이 감당해야 하는 영역입니다. PaaS는 이 비용을 숨기는 대신 &lt;strong&gt;청구서에 명시적으로 드러낼 뿐&lt;/strong&gt;입니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  2) GPU 직접 운영 시의 '유휴 자원 비용'
&lt;/h3&gt;

&lt;p&gt;피크 트래픽을 기준으로 잡은 GPU는 대부분의 시간 동안 놀고 있고, 그 비용은 그대로 고정비로 남습니다. 이 비용은 &lt;strong&gt;“안 보이기 때문에 싸다고 느껴질 뿐”&lt;/strong&gt; 실제로는 꾸준히 누적됩니다.&lt;/p&gt;




&lt;h3&gt;
  
  
  문제는 플랫폼이 아니라 '사용 방식'
&lt;/h3&gt;

&lt;p&gt;이 지점까지 정리하고 나서야, 비용 문제의 초점이 조금씩 이동하기 시작했습니다. 결국 비용을 폭증시키는 주된 요인은 &lt;strong&gt;플랫폼이 아니라, 우리가 모델을 어떻게 쓰고 있느냐&lt;/strong&gt;였습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;필요 이상으로 &lt;strong&gt;큰 모델&lt;/strong&gt;을 선택하지 않았는가?&lt;/li&gt;
&lt;li&gt;출력 토큰을 통제하지 않은 &lt;strong&gt;프롬프트&lt;/strong&gt;를 사용하고 있지는 않은가?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RAG 컨텍스트&lt;/strong&gt;를 무의식적으로 키우고 있지는 않은가?&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"비용의 근본 원인은 플랫폼이 아니라, 사용 방식(프롬프트 설계와 모델 선택)에 있습니다."&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;아래는 요청하신 &lt;strong&gt;3, 4, 5번 섹션 본문&lt;/strong&gt; 초안입니다.&lt;br&gt;
앞선 섹션들과 동일하게, &lt;strong&gt;불필요한 설명은 제거하고 판단 기준이 드러나도록&lt;/strong&gt; 구성했습니다.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. 토큰 최적화는 중앙이 아니라 각 솔루션의 책임이다
&lt;/h2&gt;

&lt;p&gt;토큰 최적화는 중앙에서 일괄적으로 처리할 수 있는 문제가 아닙니다.&lt;br&gt;
이유는 단순합니다. &lt;strong&gt;토큰을 왜 쓰는지 아는 주체는 각 솔루션뿐&lt;/strong&gt;이기 때문입니다.&lt;/p&gt;

&lt;p&gt;솔루션 단위에서 반드시 통제해야 할 요소들은 명확합니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;출력 토큰 상한 설정&lt;/strong&gt;
응답 품질과 UX를 고려한 합리적인 최대 길이&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RAG 컨텍스트 크기 관리&lt;/strong&gt;
“많이 넣을수록 정확하다”는 착각에서 벗어나는 설계&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;프롬프트 압축과 구조화&lt;/strong&gt;
설명을 늘리는 대신, 의도를 정확히 전달하는 방향&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;이 결정들은 모두 &lt;strong&gt;비즈니스 맥락을 전제로 한 판단&lt;/strong&gt;입니다.&lt;br&gt;
어떤 기능은 요약이 적합하고, 어떤 기능은 상세 응답이 필요합니다.&lt;br&gt;
이 차이는 중앙 시스템이 알 수 없습니다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;중앙에서 토큰을 직접 제어하려고 하면, 필연적으로 부작용이 발생합니다.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;중요한 응답이 잘려 UX가 깨지거나&lt;/li&gt;
&lt;li&gt;특정 기능의 품질이 예고 없이 저하되거나&lt;/li&gt;
&lt;li&gt;문제의 원인이 플랫폼인지, 서비스인지 분간하기 어려워집니다&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;결국 토큰 최적화는&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“어디까지가 적절한 응답인가”를 가장 잘 아는 쪽&lt;/strong&gt;,&lt;br&gt;
즉 각 솔루션의 책임으로 남겨두는 것이 합리적입니다.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  4. Ops 관점에서 필요한 것은 ‘제어’가 아니라 ‘관찰’이다
&lt;/h2&gt;

&lt;p&gt;Ops 관점에서 토큰을 다루는 방식은 다릅니다.&lt;br&gt;
여기서 토큰은 더 이상 &lt;strong&gt;과금 단위&lt;/strong&gt;가 아니라, &lt;strong&gt;시스템 상태를 드러내는 신호&lt;/strong&gt;입니다.&lt;/p&gt;

&lt;p&gt;토큰 사용량을 보면 다음과 같은 이상 징후를 감지할 수 있습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;특정 API에서 출력 토큰이 갑자기 급증한다면
→ 프롬프트 변경, RAG 버그, 무한 반복 가능성&lt;/li&gt;
&lt;li&gt;사용자별 토큰 사용량이 비정상적으로 높다면
→ Abuse, 자동화 요청, 권한 문제&lt;/li&gt;
&lt;li&gt;시간대별 토큰 패턴이 달라진다면
→ 트래픽 특성 변화 또는 신규 기능 영향&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;중요한 점은, Ops는 여기서 &lt;strong&gt;직접 개입하지 않는다는 것&lt;/strong&gt;입니다.&lt;br&gt;
Ops의 역할은 “막는 것”이 아니라 &lt;strong&gt;“보여주는 것”&lt;/strong&gt;에 가깝습니다.&lt;/p&gt;

&lt;p&gt;주기적인 점검과 추세 기반 관찰을 통해&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;어디서 이상이 발생하고 있는지&lt;/li&gt;
&lt;li&gt;누가 판단해야 하는 문제인지를 빠르게 전달하는 것이 핵심입니다.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  5. Azure AI Foundry 기반 모니터링의 명확한 한계
&lt;/h2&gt;

&lt;p&gt;Azure AI Foundry를 기반으로 한 토큰 모니터링은 분명한 장점이 있습니다.&lt;br&gt;
Azure OpenAI 모델을 사용하는 경우, 요청·응답 단위의 토큰 사용량을 비교적 쉽게 확인할 수 있습니다.&lt;/p&gt;

&lt;p&gt;하지만 이 가시성은 &lt;strong&gt;PaaS 경계 안에서만 유효&lt;/strong&gt;합니다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr7n5oabzccjsv35iur9q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr7n5oabzccjsv35iur9q.png" alt="Azure Portal의 비용 데이터 (출처: Learn Azure MS Foundry)" width="800" height="435"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzbo5x785egfjirja1g81.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzbo5x785egfjirja1g81.png" alt="Azure AI Foundry Dashboard (출처: Learn Azure Managed Grafana)" width="800" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;문제는 커스텀 모델이나 자체 호스팅 모델로 시선이 옮겨갈 때 발생합니다.&lt;/p&gt;

&lt;p&gt;모델을 직접 띄워 사용하는 순간, Azure AI Foundry가 제공하던 토큰 가시성은 더 이상 적용되지 않습니다.&lt;/p&gt;

&lt;p&gt;즉, &lt;strong&gt;모델이 다양해질수록 관측은 분절&lt;/strong&gt;됩니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PaaS 모델은 보이고&lt;/li&gt;
&lt;li&gt;커스텀 모델은 보이지 않는 구조&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;이 Observability 공백은, 이후 토큰 중앙화를 고민하게 되는 직접적인 계기가 됩니다.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. 토큰 데이터를 전송하는 2가지 방법
&lt;/h2&gt;

&lt;p&gt;토큰 중앙화를 논의할 때 가장 먼저 결정해야 할 것은 단순합니다.&lt;br&gt;
&lt;strong&gt;“어디에서 토큰 데이터를 추출할 것인가”&lt;/strong&gt;입니다.&lt;br&gt;
Azure 환경에서는 크게 세 가지 선택지가 존재합니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  방법 A: APIM(Azure API Management) 프록시 활용
&lt;/h3&gt;

&lt;p&gt;Azure API Management를 프록시로 두고, 요청·응답을 가로채 토큰 메트릭을 수집하는 방식입니다.&lt;br&gt;
Azure에서 공식적으로 제공하는 &lt;code&gt;llm-emit-token-metric&lt;/code&gt; 정책은 이 접근을 표준화합니다.&lt;/p&gt;

&lt;p&gt;이 방식의 가장 큰 장점은 &lt;strong&gt;애플리케이션 코드 변경이 필요 없다는 점&lt;/strong&gt;입니다.&lt;br&gt;
외부 SaaS 모델이나, 이미 배포된 서비스처럼 코드를 수정하기 어려운 경우 사실상 유일한 선택지입니다.&lt;/p&gt;

&lt;p&gt;반면, APIM은 어디까지나 &lt;strong&gt;프록시 계층&lt;/strong&gt;입니다.&lt;br&gt;
토큰 수는 알 수 있지만, &lt;em&gt;왜 그 토큰이 사용되었는지&lt;/em&gt;에 대한 맥락은 담기 어렵습니다.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;설정 중심, 빠른 도입, 낮은 침투성&lt;br&gt;
대신 맥락은 제한적&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frc2jc54oas997gkckgy6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frc2jc54oas997gkckgy6.png" alt="APIM을 통한 토큰 메트릭 수집 흐름" width="800" height="513"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  방법 B: 애플리케이션 직접 구현 (SDK / OpenTelemetry)
&lt;/h3&gt;

&lt;p&gt;애플리케이션 코드 내부에서 LLM 응답을 파싱하고, 토큰 정보를 직접 전송하는 방식입니다.&lt;br&gt;
Application Insights와 &lt;a href="https://opentelemetry.io/docs/what-is-opentelemetry/" rel="noopener noreferrer"&gt;OpenTelemetry&lt;/a&gt;를 함께 사용하면 자연스럽게 확장할 수 있습니다.&lt;/p&gt;

&lt;p&gt;이 방식의 핵심 가치는 &lt;strong&gt;맥락(Context)&lt;/strong&gt;입니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;조직 ID&lt;/li&gt;
&lt;li&gt;기능 이름&lt;/li&gt;
&lt;li&gt;요청 유형&lt;/li&gt;
&lt;li&gt;비즈니스 이벤트&lt;/li&gt;
&lt;li&gt;.... etc&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;토큰 수치에 이러한 차원을 함께 붙일 수 있기 때문에, “누가”, “어떤 기능에서”, “왜 많이 썼는지”까지 분석이 가능합니다.&lt;/p&gt;

&lt;p&gt;단점은 명확합니다.&lt;br&gt;
&lt;strong&gt;개발 비용이 들고&lt;/strong&gt;, 모든 서비스에 일관되게 적용하기 어렵습니다. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;비즈니스 중심, 정밀한 분석&lt;br&gt;
대신 구현 책임은 각 팀에 있음&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyp1eaib5j95rqzk7bafz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyp1eaib5j95rqzk7bafz.png" alt="SDK 기반의 상세 맥락 수집 흐름" width="800" height="513"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  7. 모든 데이터의 종착지: Log Analytics Workspace(LAW) 중앙화
&lt;/h2&gt;

&lt;p&gt;수집 경로가 어디든, 데이터는 결국 하나로 모여야 합니다.&lt;br&gt;
Azure 환경에서 그 역할을 맡는 곳이 &lt;strong&gt;Log Analytics Workspace&lt;/strong&gt;입니다.&lt;/p&gt;

&lt;p&gt;LAW를 사용하는 이유는 단순합니다.&lt;br&gt;
&lt;strong&gt;모든 로그와 메트릭을 같은 언어로 분석할 수 있기 때문&lt;/strong&gt;입니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;APIM에서 수집한 토큰 로그&lt;/li&gt;
&lt;li&gt;애플리케이션이 직접 보낸 커스텀 메트릭&lt;/li&gt;
&lt;li&gt;Azure AI Foundry의 진단 로그&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;이 모든 데이터를 &lt;strong&gt;Kusto Query Language(KQL)&lt;/strong&gt;로 조인하여 분석할 수 있습니다.&lt;/p&gt;

&lt;p&gt;이때 중앙화의 진짜 가치는 대시보드 그 자체가 아닙니다.&lt;br&gt;
다음과 같은 질문에 &lt;strong&gt;즉시 답할 수 있는 능력&lt;/strong&gt;입니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;이번 달 전체 AI 워크로드의 총 토큰 사용량은?&lt;/li&gt;
&lt;li&gt;특정 서비스가 평균 대비 얼마나 더 쓰고 있는가?&lt;/li&gt;
&lt;li&gt;비용 급증의 시작 지점은 언제인가?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;“로그가 모였다”가 아니라, &lt;strong&gt;“조직 단위로 사고할 수 있게 되었다”&lt;/strong&gt;는 점이 핵심입니다.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. 중앙화된 시스템이 해결해주지 못하는 ‘제어’의 영역
&lt;/h2&gt;

&lt;p&gt;토큰 데이터가 중앙에 모이면, 자연스럽게 이런 요구가 등장합니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“많이 쓰는 요청은 중앙에서 끊자”&lt;/li&gt;
&lt;li&gt;“임계치 초과 시 자동으로 큐잉하자”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;하지만 이 지점에서 시스템은 급격히 무거워집니다.&lt;/p&gt;

&lt;p&gt;실시간 제어를 중앙에서 수행하려면,&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;요청 경로가 길어지고&lt;/li&gt;
&lt;li&gt;정책 판단이 복잡해지며&lt;/li&gt;
&lt;li&gt;장애 시 영향 범위가 커집니다&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;무엇보다, 중앙 시스템은 여전히 &lt;strong&gt;비즈니스 맥락을 알지 못합니다&lt;/strong&gt;.&lt;br&gt;
이 요청이 중요한지, 단순한 실험인지 판단할 수 없습니다.&lt;/p&gt;

&lt;p&gt;그래서 적정선은 명확합니다.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;가시성은 중앙에서, 실행과 제어는 로컬(App)에서&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;중앙은 “보여주고”, 각 솔루션은 “결정한다”. ⚠️⚠️⚠️ 이 분리가 깨지는 순간, 중앙화는 장점보다 위험이 커집니다.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. 결론: 토큰 중앙화의 목적은 ‘통찰’이지 ‘통제’가 아니다
&lt;/h2&gt;

&lt;p&gt;토큰 중앙화의 목적은 비용 절감이 아닙니다.&lt;br&gt;
또한 모든 것을 중앙에서 통제하기 위함도 아닙니다.&lt;/p&gt;

&lt;p&gt;진짜 목적은 &lt;strong&gt;통찰(Insight)&lt;/strong&gt;입니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;어떤 설계가 토큰을 낭비하고 있는지&lt;/li&gt;
&lt;li&gt;어떤 프롬프트가 비효율적인지&lt;/li&gt;
&lt;li&gt;어떤 서비스가 위험 신호를 보내고 있는지&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;토큰은 AI 시스템의 &lt;strong&gt;상태 신호&lt;/strong&gt;입니다.&lt;br&gt;
CPU 사용률이나 메모리와 다르지 않습니다.&lt;/p&gt;

&lt;p&gt;그래서 가장 현실적인 결론은 다음과 같습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;중앙에서는 &lt;strong&gt;보인다&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;로컬에서는 &lt;strong&gt;판단하고 최적화한다&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;그리고 그 판단은 다시 데이터로 검증된다&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;이 선을 지킬 때,&lt;br&gt;
토큰 중앙화는 비용 관리 도구를 넘어 &lt;strong&gt;성숙한 AI 운영의 기반&lt;/strong&gt;이 됩니다.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>azure</category>
      <category>llm</category>
      <category>devops</category>
    </item>
    <item>
      <title>[설계, 기록] 기업별 아키텍처 , 사례연구</title>
      <dc:creator>GyeongSeon</dc:creator>
      <pubDate>Sat, 29 Nov 2025 04:30:24 +0000</pubDate>
      <link>https://dev.to/shingyeongseon/siseutem-seolgye-gasang-myeonjeob-saryero-baeuneun-daegyumo-siseutem-seolgye-gico-pilsu-camgojaryo-moeum-1fjp</link>
      <guid>https://dev.to/shingyeongseon/siseutem-seolgye-gasang-myeonjeob-saryero-baeuneun-daegyumo-siseutem-seolgye-gico-pilsu-camgojaryo-moeum-1fjp</guid>
      <description>&lt;h2&gt;
  
  
  📚 들어가며
&lt;/h2&gt;

&lt;p&gt;AlexXu『가상 면접 사례로 배우는 대규모 시스템 설계 기초(System Design Interview)』을 읽고 가장 마지막에 이 글이 인상 깊었다. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;좋은 시스템을 설계하려면 다년간 많은 지식을 쌓아야 한다. 지식을 쌓는 한가지 지름길은, 실세계에서 쓰이는 시스템의 구조를 공부하는 것이다. 아래에 도움될 만한 자료들을 정리해 보았다. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;가능하다면 다양한 글을 볼 생각이고 , 관련된 기술 블로그를 정리해둔다.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. 기업별 아키텍처 &amp;amp; 사례 연구 (Case Studies)
&lt;/h2&gt;

&lt;p&gt;실제 대규모 트래픽을 처리하는 기업들이 겪은 문제와 해결책을 담은 핵심 자료들입니다.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;기업 / 주제&lt;/th&gt;
&lt;th&gt;내용 (한글/영문)&lt;/th&gt;
&lt;th&gt;링크&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Facebook&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;타임라인: 비정규화의 힘&lt;/strong&gt;&lt;br&gt;&lt;em&gt;(Facebook Timeline: Brought to you by the power of denormalization)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href="https://goo.gl/FCNrbm" rel="noopener noreferrer"&gt;Link&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Facebook&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;타임라인: 한 사람의 인생을 담기에 충분한 규모 확장성&lt;/strong&gt;&lt;br&gt;&lt;em&gt;(Building Timeline: Scaling up to hold your life story)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href="https://goo.gl/8p5wDV" rel="noopener noreferrer"&gt;Link&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Facebook&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;페이스북 채팅 아키텍처&lt;/strong&gt;&lt;br&gt;&lt;em&gt;(Facebook Chat)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href="https://goo.gl/qzSiWC" rel="noopener noreferrer"&gt;Link&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Facebook&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Erlang 사용 사례&lt;/strong&gt;&lt;br&gt;&lt;em&gt;(Erlang at Facebook)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href="https://goo.gl/zSLHrj" rel="noopener noreferrer"&gt;Link&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Facebook&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Haystack: 사진 저장소 아키텍처&lt;/strong&gt;&lt;br&gt;&lt;em&gt;(Finding a needle in Haystack: Facebook's photo storage)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href="https://goo.gl/edj4FL" rel="noopener noreferrer"&gt;Link&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Facebook&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;멀티피드: 재설계를 통한 효율성과 성능 향상&lt;/strong&gt;&lt;br&gt;&lt;em&gt;(Serving Facebook Multifeed)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href="https://goo.gl/adFVMQ" rel="noopener noreferrer"&gt;Link&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Facebook&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;멤캐시(Memcache) 시스템의 규모 확장성&lt;/strong&gt;&lt;br&gt;&lt;em&gt;(Scaling Memcache at Facebook)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href="https://goo.gl/rZiAhX" rel="noopener noreferrer"&gt;Link&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Facebook&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;TAO: 소셜 그래프 분산 저장소&lt;/strong&gt;&lt;br&gt;&lt;em&gt;(TAO: Facebook's Distributed Data Store for the Social Graph)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href="https://goo.gl/Tk1DyH" rel="noopener noreferrer"&gt;Link&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Facebook&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;왓츠앱(WhatsApp) 아키텍처 분석&lt;/strong&gt;&lt;br&gt;&lt;em&gt;(The WhatsApp Architecture Facebook Bought For $19 Billion)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href="https://bit.ly/2AHJnFn" rel="noopener noreferrer"&gt;Link&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Amazon&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;아마존 아키텍처&lt;/strong&gt;&lt;br&gt;&lt;em&gt;(Amazon Architecture)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href="https://goo.gl/k4feoW" rel="noopener noreferrer"&gt;Link&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Amazon&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;다이나모: 고가용성 키-값 저장소&lt;/strong&gt;&lt;br&gt;&lt;em&gt;(Dynamo: Amazon's Highly Available Key-value Store)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href="https://goo.gl/C7zxDL" rel="noopener noreferrer"&gt;Link&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Google&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;구글 아키텍처&lt;/strong&gt;&lt;br&gt;&lt;em&gt;(Google Architecture)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href="https://goo.gl/dvkDiY" rel="noopener noreferrer"&gt;Link&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Google&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;GFS: 구글 파일 시스템&lt;/strong&gt;&lt;br&gt;&lt;em&gt;(The Google File System)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href="https://goo.gl/xj5n9R" rel="noopener noreferrer"&gt;Link&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Google&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;빅테이블: 구조화된 데이터 분산 저장소&lt;/strong&gt;&lt;br&gt;&lt;em&gt;(Bigtable: A Distributed Storage System for Structured Data)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href="https://goo.gl/6NaZca" rel="noopener noreferrer"&gt;Link&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Google&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;차이 기반 동기화 (델타 동기화)&lt;/strong&gt;&lt;br&gt;&lt;em&gt;(Differential Synchronization)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href="https://goo.gl/9zqG7x" rel="noopener noreferrer"&gt;Link&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;YouTube&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;유튜브 아키텍처&lt;/strong&gt;&lt;br&gt;&lt;em&gt;(YouTube Architecture)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href="https://goo.gl/mCPRUF" rel="noopener noreferrer"&gt;Link&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;YouTube&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;유튜브의 규모 확장성&lt;/strong&gt;&lt;br&gt;&lt;em&gt;(YouTube Scalability)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href="https://goo.gl/dH3zYq" rel="noopener noreferrer"&gt;Link&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Netflix&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;넷플릭스 기술 스택 360도 분석&lt;/strong&gt;&lt;br&gt;&lt;em&gt;(A 360 Degree View Of The Entire Netflix Stack)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href="https://goo.gl/rYSDTz" rel="noopener noreferrer"&gt;Link&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Netflix&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;A/B 테스팅 플랫폼&lt;/strong&gt;&lt;br&gt;&lt;em&gt;(It's All A/Bout Testing)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href="https://goo.gl/agbA4K" rel="noopener noreferrer"&gt;Link&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Netflix&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;추천 시스템 1부&lt;/strong&gt;&lt;br&gt;&lt;em&gt;(Netflix Recommendations: Beyond the 5 stars Part 1)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href="https://goo.gl/A4FkYi" rel="noopener noreferrer"&gt;Link&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Netflix&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;추천 시스템 2부&lt;/strong&gt;&lt;br&gt;&lt;em&gt;(Netflix Recommendations: Beyond the 5 stars Part 2)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href="https://goo.gl/XNPMXm" rel="noopener noreferrer"&gt;Link&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Twitter&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;1억 5천만 활성 사용자를 감당하는 아키텍처&lt;/strong&gt;&lt;br&gt;&lt;em&gt;(The Architecture Twitter Uses To Deal With 150M Active Users)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href="https://goo.gl/EwvfRd" rel="noopener noreferrer"&gt;Link&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Twitter&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;트위터를 10000배 빠르게 만든 비결&lt;/strong&gt;&lt;br&gt;&lt;em&gt;(Scaling Twitter)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href="https://goo.gl/nYGC1k" rel="noopener noreferrer"&gt;Link&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Twitter&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;스노플레이크: 유일 ID 생성기&lt;/strong&gt;&lt;br&gt;&lt;em&gt;(Announcing Snowflake)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href="https://goo.gl/GzVWYm" rel="noopener noreferrer"&gt;Link&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Twitter&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;타임라인과 규모 확장성 문제&lt;/strong&gt;&lt;br&gt;&lt;em&gt;(Timelines at Scale)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href="https://goo.gl/8KbqTy" rel="noopener noreferrer"&gt;Link&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Instagram&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;인스타그램 아키텍처&lt;/strong&gt;&lt;br&gt;&lt;em&gt;(14 Million Users, Terabytes Of Photos...)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href="https://goo.gl/s1VcW5" rel="noopener noreferrer"&gt;Link&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Uber&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;실시간 마켓 플랫폼 확장 전략&lt;/strong&gt;&lt;br&gt;&lt;em&gt;(How Uber Scales Their Real-Time Market Platform)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href="https://goo.gl/kGZuVy" rel="noopener noreferrer"&gt;Link&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pinterest&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;핀터레스트 규모 확장성&lt;/strong&gt;&lt;br&gt;&lt;em&gt;(Scaling Pinterest)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href="https://goo.gl/KtmjW3" rel="noopener noreferrer"&gt;Link&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pinterest&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;아키텍처 업데이트&lt;/strong&gt;&lt;br&gt;&lt;em&gt;(Pinterest Architecture Update)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href="https://goo.gl/w6rRsf" rel="noopener noreferrer"&gt;Link&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LinkedIn&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;링크드인 규모 확장성의 역사&lt;/strong&gt;&lt;br&gt;&lt;em&gt;(A Brief History of Scaling Linkedin)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href="https://goo.gl/8A1Pi8" rel="noopener noreferrer"&gt;Link&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Flickr&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;플리커 아키텍처&lt;/strong&gt;&lt;br&gt;&lt;em&gt;(Flickr Architecture)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href="https://goo.gl/dWtgYa" rel="noopener noreferrer"&gt;Link&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Dropbox&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;드롭박스 규모 확장기&lt;/strong&gt;&lt;br&gt;&lt;em&gt;(How We've Scaled Dropbox)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href="https://goo.gl/NjBDtC" rel="noopener noreferrer"&gt;Link&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  2. 회사별 엔지니어링 블로그 모음
&lt;/h2&gt;

&lt;p&gt;면접 전 해당 회사의 기술 블로그를 확인하면 &lt;strong&gt;사용 기술(Stack)&lt;/strong&gt;과 &lt;strong&gt;관심사&lt;/strong&gt;를 파악하는 데 큰 도움이 됩니다. 정기적으로 살펴보는 습관을 들이는 것을 추천합니다.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;회사명&lt;/th&gt;
&lt;th&gt;블로그 주소&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Google&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://developers.googleblog.com" rel="noopener noreferrer"&gt;https://developers.googleblog.com&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Facebook (Meta)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://engineering.fb.com/" rel="noopener noreferrer"&gt;https://engineering.fb.com/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Netflix&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://netflixtechblog.com/" rel="noopener noreferrer"&gt;https://netflixtechblog.com/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Twitter (X)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://blog.twitter.com/engineering" rel="noopener noreferrer"&gt;https://blog.twitter.com/engineering&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Amazon&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://developer.amazon.com/blogs" rel="noopener noreferrer"&gt;https://developer.amazon.com/blogs&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Uber&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="http://eng.uber.com" rel="noopener noreferrer"&gt;http://eng.uber.com&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Airbnb&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://medium.com/airbnb-engineering" rel="noopener noreferrer"&gt;https://medium.com/airbnb-engineering&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Instagram&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://instagram-engineering.com/" rel="noopener noreferrer"&gt;https://instagram-engineering.com/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LinkedIn&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://engineering.linkedin.com/blog" rel="noopener noreferrer"&gt;https://engineering.linkedin.com/blog&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Slack&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://slack.engineering" rel="noopener noreferrer"&gt;https://slack.engineering&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pinterest&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://medium.com/pinterest-engineering" rel="noopener noreferrer"&gt;https://medium.com/pinterest-engineering&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Dropbox&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://blogs.dropbox.com/tech" rel="noopener noreferrer"&gt;https://blogs.dropbox.com/tech&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Spotify&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://engineering.atspotify.com/" rel="noopener noreferrer"&gt;https://engineering.atspotify.com/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GitHub&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.blog/category/engineering/" rel="noopener noreferrer"&gt;https://github.blog/category/engineering/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Line&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://engineering.linecorp.com/ko/blog/" rel="noopener noreferrer"&gt;https://engineering.linecorp.com/ko/blog/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Kakao&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://tech.kakao.com/" rel="noopener noreferrer"&gt;https://tech.kakao.com/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Naver&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://d2.naver.com/home" rel="noopener noreferrer"&gt;https://d2.naver.com/home&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;High Scalability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;a href="http://highscalability.com" rel="noopener noreferrer"&gt;http://highscalability.com&lt;/a&gt; (강력 추천)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;






&lt;h3&gt;
  
  
  🔖 출처 및 참고
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;도서:&lt;/strong&gt; 가상 면접 사례로 배우는 대규모 시스템 설계 기초 (Alex Xu 저 / 이병준 역, 인사이트)&lt;/p&gt;

</description>
      <category>systemdesign</category>
      <category>architecture</category>
      <category>alexxu</category>
    </item>
    <item>
      <title>[KubeRay로 LLM 서빙 인프라 찍먹] 4부: LLM 서빙을 위한 Ray Managed API 설계 및 회고</title>
      <dc:creator>GyeongSeon</dc:creator>
      <pubDate>Sun, 12 Oct 2025 10:39:00 +0000</pubDate>
      <link>https://dev.to/shingyeongseon/kuberayro-llm-seobing-inpeura-jjigmeog-4bu-llm-seobingeul-wihan-ray-managed-api-seolgye-mic-hoego-4743</link>
      <guid>https://dev.to/shingyeongseon/kuberayro-llm-seobing-inpeura-jjigmeog-4bu-llm-seobingeul-wihan-ray-managed-api-seolgye-mic-hoego-4743</guid>
      <description>&lt;p&gt;안녕하세요! 길었던 KubeRay 시리즈의 마지막 편입니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;1부&lt;/strong&gt;에서는 LLM 서빙에 왜 Ray가 적합한지 고민했고,&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2부&lt;/strong&gt;에서는 RayCluster로 단단한 기반을 다졌으며,&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3부&lt;/strong&gt;에서는 RayService와 vLLM을 활용해 실제 LLM 서빙 API를 구축했습니다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;지금까지의 과정은 &lt;code&gt;kubectl apply -f&lt;/code&gt; 명령어를 통해 수동으로 YAML 파일을 관리하는 방식이었습니다. 하지만 여러 모델을 관리하고, 여러 사용자가 함께 사용하는 환경에서는 이런 수동 방식은 명확한 한계를 가집니다.&lt;/p&gt;

&lt;p&gt;이번 4부에서는 이 모든 과정을 자동화하고 사용자 친화적으로 만들기 위해, 어떻게 Ray Managed API를 설계하고 구현했는지 그 여정을 공유하며 시리즈를 마무리하고자 합니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. 왜 API가 필요했을까?: 복잡성 추상화
&lt;/h3&gt;

&lt;p&gt;수십 줄에 달하는 RayCluster와 RayService YAML 파일을 매번 작성하고 관리하는 것은 번거롭고 실수의 여지가 많습니다. 특히 쿠버네티스나 KubeRay에 익숙하지 않은 사용자에게는 큰 진입장벽이 됩니다.&lt;/p&gt;

&lt;p&gt;제가 원했던 것은 사용자가 KubeRay의 복잡한 CRD 구조를 몰라도, 다음과 같이 간단하게 자신의 요구사항을 표현할 수 있는 환경이었습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"저는 A100 GPU 1개를 사용하는 Ray 클러스터가 필요해요."&lt;/li&gt;
&lt;li&gt;"Hugging Face의 &lt;code&gt;gpt2&lt;/code&gt; 모델을 vLLM 엔진을 사용해서 배포하고 싶어요."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;이러한 'Managed Service' 경험을 제공하기 위해, 모든 복잡성을 뒤로 숨겨주는 API 서버를 구축하기로 결정했습니다.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Ray Managed API 설계
&lt;/h3&gt;

&lt;p&gt;핵심 API 엔드포인트를 다음과 같이 설계했습니다.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: 실제 구현에서는 네임스페이스와 클러스터 이름을 경로에 포함하여 (&lt;code&gt;/api/v1/clusters/{cluster_name}/namespaces/{namespace}/services&lt;/code&gt;) 리소스를 더 명확하게 구분했지만, 이 글에서는 핵심 아이디어를 보여주기 위해 경로를 간소화했습니다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;클러스터 관리 (&lt;code&gt;/clusters&lt;/code&gt;)&lt;/strong&gt;: RayCluster 생성, 조회, 삭제&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;서비스(모델) 관리 (&lt;code&gt;/services&lt;/code&gt;)&lt;/strong&gt;: RayService 배포, 조회, 중단&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Job 관리 (&lt;code&gt;/jobs&lt;/code&gt;)&lt;/strong&gt;: RayJob 제출, 조회&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  API 설계의 교훈: CRD 위에서 똑똑하게 일하기
&lt;/h4&gt;

&lt;p&gt;단순히 CRD를 감싸는 것을 넘어, 안정적이고 예측 가능하게 동작하는 API를 만들기 위해서는 몇 가지 중요한 원칙이 필요했습니다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. 제약의 미학: 필수 필드에 집중하고 API를 단순하게 유지하기&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;KubeRay CRD는 매우 많은 속성을 가지고 있습니다. 모든 속성을 API 파라미터로 노출하는 것은 유연해 보이지만, 오히려 API의 복잡도를 높이고 사용자를 혼란스럽게 만듭니다. 저는 유연성보다는 &lt;strong&gt;명확성과 안정성&lt;/strong&gt;을 선택했습니다.&lt;/p&gt;

&lt;p&gt;가장 먼저 CRD가 동작하기 위한 필수 필드가 무엇인지 파악했습니다. 이는 &lt;code&gt;kubectl get crd rayclusters.ray.io -o yaml&lt;/code&gt;과 같은 명령어로 CRD의 스키마 정의(&lt;code&gt;openAPIV3Schema&lt;/code&gt;) 내 &lt;code&gt;required&lt;/code&gt; 항목을 통해 확인할 수 있습니다.&lt;/p&gt;

&lt;p&gt;API 설계 시 이 필수 필드들은 반드시 포함시키거나 안정적인 기본값을 설정해주고, 그 외에는 사용자들이 가장 빈번하게 사용할 것이라 예상되는 핵심적인 옵션들만 선별적으로 노출했습니다. 이는 의도적인 제약을 통해 오히려 더 나은 사용자 경험을 제공하는 중요한 설계 원칙이었습니다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. 수정은 Patch 대신 Put으로: 복잡성을 피하는 전략&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;리소스 수정 시 부분 업데이트를 지원하는 &lt;code&gt;PATCH&lt;/code&gt; 메서드는 매우 유용하지만, CRD와 같이 깊고 복잡한 구조를 다룰 때는 오히려 독이 될 수 있습니다. 예를 들어 &lt;code&gt;workerGroupSpecs&lt;/code&gt; 배열에서 특정 워커 그룹의 복제본 수를 변경하거나, 새로운 워커 그룹을 추가하거나, 기존 그룹을 삭제하는 로직을 &lt;code&gt;PATCH&lt;/code&gt;로 구현하는 것은 매우 복잡합니다.&lt;/p&gt;

&lt;p&gt;그래서 저는 수정 로직을 &lt;code&gt;PUT&lt;/code&gt; 메서드 기반으로 설계했습니다. 사용자가 수정을 요청하면, API 서버는 기존 상태를 가져와 요청된 변경사항을 적용한 완전한 새 템플릿(YAML)을 생성한 뒤, 이를 쿠버네티스에 제출하여 리소스 전체를 **교체(replace)**하도록 했습니다. 이 방식은 구현의 복잡도를 크게 낮추고, 예측 불가능한 상태 변경을 방지하여 안정성을 높여주었습니다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. 오퍼레이터와의 상호작용: 보이지 않는 규칙 이해하기&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Managed API는 결국 KubeRay 오퍼레이터의 클라이언트입니다. 따라서 오퍼레이터가 어떻게 동작하는지 이해하는 것이 매우 중요했습니다.&lt;/p&gt;

&lt;p&gt;예를 들어, RayService를 수정할 때 파드 템플릿(&lt;code&gt;template&lt;/code&gt;)의 변경사항이 없다면 KubeRay는 RayCluster를 재시작(rolling update)하지 않습니다. 만약 API가 이 동작을 인지하지 못하고 단순하게 CR만 수정한 뒤 '성공'을 반환한다면, 사용자는 변경사항이 즉시 적용될 것이라 기대했지만 실제로는 아무 일도 일어나지 않는 상황이 발생할 수 있습니다.&lt;/p&gt;

&lt;p&gt;이처럼 API는 리소스를 수정하기 전에 현재 상태를 확인하고, 오퍼레이터의 동작 규칙을 고려하여 사용자의 기대와 실제 동작이 일치하도록 설계되어야 합니다.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. 프로젝트 회고
&lt;/h3&gt;

&lt;h4&gt;
  
  
  가장 큰 배움: 사용자가 아닌 '운영자'의 관점을 얻다
&lt;/h4&gt;

&lt;p&gt;이번 프로젝트를 통해 얻은 가장 큰 수확은 단순히 'Ray를 어떻게 사용하는가'를 넘어, **'Ray를 어떻게 안정적으로 운영할 것인가'**에 대한 시각을 갖게 된 것입니다. 사용자의 입장에서 기능을 바라보는 것을 넘어, 수많은 사용자들이 이 시스템을 문제없이 사용하게 하려면 어떤 점들을 고려해야 하는지, 즉 Ops의 관점에서 고민하게 된 것이 가장 큰 성장 동력이었습니다.&lt;/p&gt;

&lt;h4&gt;
  
  
  아쉬웠던 점 (Lessons Learned)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;동적 설정 관리의 복잡성&lt;/strong&gt;: 사용자의 간단한 요청("GPU A100 1개")을 완전한 KubeRay YAML로 변환하는 로직은 생각보다 복잡했습니다. 다양한 옵션 조합을 처리하고, 유효성을 검사하며, 안정적인 템플릿을 동적으로 생성하는 부분을 견고하게 만드는 데 많은 노력이 필요했습니다.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;상태 동기화 및 에러 핸들링&lt;/strong&gt;: API가 쿠버네티스에 리소스 생성을 요청하는 것은 비동기적으로 처리됩니다. RayCluster가 생성되는 도중 문제가 발생했을 때, 그 실패 상태를 사용자에게 명확하게 전달하고 비정상적인 리소스를 정리하는 등, API의 상태와 실제 클러스터의 상태를 일관성 있게 유지하는 로직을 구현하는 것이 까다로운 과제였습니다.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  4. 앞으로의 과제 (Future Work)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. 모니터링 시스템 고도화 및 플랫폼 통합&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;현재 구축된 모니터링 시스템만으로는 운영에 한계가 있었습니다. 특히 기존 클러스터 관리 플랫폼에 Ray의 상태를 효과적으로 표현하는 것이 어려웠습니다. 앞으로 Ray의 상태를 더 깊이 있게 수집하고, 이를 운영자가 직관적으로 이해할 수 있도록 플랫폼에 통합하는 작업을 진행할 것입니다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. 사용자 경험(UX) 개선을 위한 심층적 추상화&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;솔직하게 고백하자면, 저는 Ray의 모든 것을 완벽하게 이해하고 이 프로젝트를 시작하지 않았습니다. 요구사항이 발생하면 분석, 설계, 구현을 빠르게 반복하는 애자일한 방식으로 시스템을 발전시켰습니다. 이 접근법은 빠르게 결과물을 만들어내는 데 효과적이었지만, 때로는 API가 Ray의 개념을 너무 직접적으로 노출하여 사용자 친화성이 부족한 결과로 이어지기도 했습니다.&lt;/p&gt;

&lt;p&gt;앞으로는 그동안 축적된 운영 경험과 Ray에 대한 깊어진 이해를 바탕으로, API를 사용하는 '사용자'의 입장에서 시스템을 다시 한번 점검하고 개선해나갈 계획입니다.&lt;/p&gt;




&lt;h3&gt;
  
  
  시리즈를 마치며
&lt;/h3&gt;

&lt;p&gt;지금까지 KubeRay를 활용하여 LLM 서빙 인프라를 구축하고, 이를 관리하기 위한 API를 설계하는 여정을 함께해주셔서 감사합니다. 이 시리즈가 쿠버네티스 환경에서 Ray를 도입하고자 하는 분들께 작은 도움이 되었기를 바랍니다.&lt;/p&gt;

</description>
      <category>ray</category>
      <category>kubernetes</category>
      <category>mlops</category>
    </item>
    <item>
      <title>[KubeRay로 LLM 서빙 인프라 찍먹] 3부: vLLM과 Ray Serve를 활용한 고성능 추론 엔드포인트 구축기</title>
      <dc:creator>GyeongSeon</dc:creator>
      <pubDate>Sun, 12 Oct 2025 08:06:37 +0000</pubDate>
      <link>https://dev.to/shingyeongseon/kuberayro-llm-seobing-inpeura-jjigmeog-3bu-vllmgwa-ray-servereul-hwalyonghan-goseongneung-curon-endeupointeu-gucuggi-p50</link>
      <guid>https://dev.to/shingyeongseon/kuberayro-llm-seobing-inpeura-jjigmeog-3bu-vllmgwa-ray-servereul-hwalyonghan-goseongneung-curon-endeupointeu-gucuggi-p50</guid>
      <description>&lt;p&gt;안녕하세요! &lt;a href="https://dev.tolink-to-part2"&gt;2부: KubeRay로 Ray 클러스터 구축하기&lt;/a&gt;에서는 LLM 서빙을 위한 기본 인프라인 RayCluster를 성공적으로 구축했습니다. 하지만 클러스터는 아직 비어있는 그릇일 뿐, 이제 그 위에 실제 애플리케이션을 올려야 합니다.&lt;/p&gt;

&lt;p&gt;KubeRay는 RayCluster 위에서 실행되는 워크로드(Workload)를 관리하기 위해 두 가지 핵심적인 CRD, RayJob과 RayService를 제공합니다. 이번 3부에서는 두 CRD의 차이점을 명확히 이해하고, 우리의 목표인 LLM 서빙에 최적화된 RayService를 활용하여 vLLM 모델을 배포하는 과정을 실습해 보겠습니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  1️⃣ KubeRay 워크로드 CRD 이해하기: RayJob vs RayService
&lt;/h2&gt;

&lt;p&gt;RayCluster가 컴퓨팅 자원을 제공하는 '인프라'라면, RayJob과 RayService는 그 위에서 실행되는 '애플리케이션'입니다. 둘은 목적에 따라 명확하게 구분됩니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  RayJob: 일회성 배치(Batch) 작업
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;언제 사용하나요?&lt;/strong&gt;&lt;br&gt;
시작과 끝이 명확한 작업을 실행할 때 사용합니다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;주요 사용 사례:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;대규모 데이터셋 전처리&lt;/li&gt;
&lt;li&gt;모델 파인튜닝&lt;/li&gt;
&lt;li&gt;주기적 리포트 생성&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;워크플로우:&lt;/strong&gt;&lt;br&gt;
RayJob CR을 생성하면 KubeRay가 작업을 실행합니다. 기존 RayCluster를 지정하거나, Job 전용 임시 클러스터를 생성할 수 있습니다. 작업 완료 후에는 자원이 자동으로 정리됩니다.&lt;/p&gt;
&lt;h3&gt;
  
  
  RayService: 상시 운영되는 온라인 서빙
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;언제 사용하나요?&lt;/strong&gt;&lt;br&gt;
외부 요청을 지속적으로 처리해야 하는 서비스를 배포할 때 사용합니다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;주요 사용 사례:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;머신러닝 모델 추론 API 서버&lt;/li&gt;
&lt;li&gt;실시간 데이터 처리 애플리케이션&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;주요 기능:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;RayCluster와 Ray Serve 애플리케이션 통합 관리&lt;/li&gt;
&lt;li&gt;자동 서비스 디스커버리 (쿠버네티스 서비스 자동 생성)&lt;/li&gt;
&lt;li&gt;무중단 배포 (Zero-downtime deployment)&lt;/li&gt;
&lt;li&gt;자동 확장 (Autoscaling)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;이처럼 RayJob과 RayService는 목적은 다르지만, 모두 RayCluster라는 컴퓨팅 기반 위에서 동작합니다. RayCluster가 작업 공간을 제공하면, RayJob은 그 공간을 잠시 빌려 쓰고, RayService는 그 공간에 상주하며 계속 일을 하는 셈입니다.&lt;/p&gt;

&lt;p&gt;LLM 모델을 API로 제공하는 것이 목표이므로, 상시 운영에 최적화된 RayService를 사용하는 것이 적합합니다.&lt;/p&gt;
&lt;h2&gt;
  
  
  2️⃣ vLLM과 Ray Serve로 고성능 추론 API 만들기
&lt;/h2&gt;

&lt;p&gt;이제 2부에서 만든 RayCluster 위에 vLLM을 이용한 LLM 추론 API를 배포해 보겠습니다.&lt;/p&gt;
&lt;h3&gt;
  
  
  왜 vLLM인가요?
&lt;/h3&gt;

&lt;p&gt;vLLM은 PagedAttention 기술을 활용하여 기존 대비 높은 처리량(Throughput)으로 LLM 추론이 가능한 라이브러리입니다. Ray Serve의 분산 처리 및 오토스케일링 기능과 결합하면 강력한 LLM 서빙 시스템을 구축할 수 있습니다.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ℹ️ vLLM에 대한 자세한 내용은 &lt;a href="https://github.com/vllm-project/vllm" rel="noopener noreferrer"&gt;공식 GitHub 저장소&lt;/a&gt;에서 확인하실 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  단계 1: Ray Serve 애플리케이션 작성하기
&lt;/h3&gt;

&lt;p&gt;vLLM 모델을 로드하고 API 요청을 처리하는 Python 스크립트를 작성합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# serve_vllm.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;ray&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;serve&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;vllm.engine.arg_utils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AsyncEngineArgs&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;vllm.engine.async_llm_engine&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AsyncLLMEngine&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;

&lt;span class="c1"&gt;# FastAPI 앱 정의 (Ray Serve가 내부적으로 사용)
&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nd"&gt;@serve.deployment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;num_replicas&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ray_actor_options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;num_gpus&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@serve.ingress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;VLLMDeployment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# vLLM 엔진 설정
&lt;/span&gt;        &lt;span class="n"&gt;engine_args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AsyncEngineArgs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;tensor_parallel_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# GPU 수에 맞게 조절
&lt;/span&gt;            &lt;span class="n"&gt;trust_remote_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AsyncLLMEngine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_engine_args&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;engine_args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/generate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sampling_params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;vllm.utils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;random_uuid&lt;/span&gt;
        &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;vllm.sampling_params&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SamplingParams&lt;/span&gt;

        &lt;span class="c1"&gt;# 샘플링 파라미터 파싱
&lt;/span&gt;        &lt;span class="n"&gt;sampling_params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SamplingParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;sampling_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;request_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;random_uuid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="n"&gt;results_generator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sampling_params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# 비동기 스트리밍 처리
&lt;/span&gt;        &lt;span class="n"&gt;final_output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;request_output&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results_generator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;final_output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request_output&lt;/span&gt;

        &lt;span class="c1"&gt;# 최종 결과 반환
&lt;/span&gt;        &lt;span class="n"&gt;text_outputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;final_output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outputs&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;text_outputs&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# 모델 배포
&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;VLLMDeployment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;EleutherAI/gpt-neo-125m&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이 스크립트는 &lt;code&gt;/generate&lt;/code&gt; 엔드포인트를 통해 프롬프트와 샘플링 파라미터를 받아 추론 결과를 반환하는 API 서버를 정의합니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  단계 2: RayService YAML 파일 작성하기
&lt;/h3&gt;

&lt;p&gt;Python 스크립트를 실행할 RayService CR을 정의합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# rayservice-vllm.yaml&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ray.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RayService&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vllm-llm-server&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;rayClusterConfig&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;rayVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2.9.0'&lt;/span&gt;
    &lt;span class="na"&gt;headGroupSpec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;rayStartParams&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;dashboard-host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.0.0.0'&lt;/span&gt;
      &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ray-head&lt;/span&gt;
            &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rayproject/ray-ml:2.9.0-py310-gpu&lt;/span&gt;
            &lt;span class="c1"&gt;# ... (Head 파드 설정)&lt;/span&gt;
    &lt;span class="na"&gt;workerGroupSpecs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
      &lt;span class="na"&gt;minReplicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
      &lt;span class="na"&gt;maxReplicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
      &lt;span class="na"&gt;groupName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gpu-worker-group&lt;/span&gt;
      &lt;span class="na"&gt;rayStartParams&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
      &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ray-worker&lt;/span&gt;
            &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rayproject/ray-ml:2.9.0-py310-gpu&lt;/span&gt;
            &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;nvidia.com/gpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1"&lt;/span&gt;
              &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;nvidia.com/gpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1"&lt;/span&gt;
            &lt;span class="c1"&gt;# ... (Worker 파드 설정, 2부의 nodeSelector, tolerations 등 포함)&lt;/span&gt;

  &lt;span class="c1"&gt;# Ray Serve 애플리케이션 설정&lt;/span&gt;
  &lt;span class="na"&gt;serveConfigV2&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;import_path: serve_vllm:app&lt;/span&gt;
    &lt;span class="s"&gt;deployments:&lt;/span&gt;
    &lt;span class="s"&gt;- name: VLLMDeployment&lt;/span&gt;
      &lt;span class="s"&gt;user_config:&lt;/span&gt;
        &lt;span class="s"&gt;model_name: "EleutherAI/gpt-neo-125m"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;주요 설정 설명:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;import_path&lt;/code&gt;: 실행할 Ray Serve 애플리케이션의 위치 (파일명:인스턴스명)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;user_config&lt;/code&gt;: 스크립트의 &lt;code&gt;__init__&lt;/code&gt; 메서드로 전달될 파라미터&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  단계 3: 배포 및 확인
&lt;/h3&gt;

&lt;p&gt;배포를 위해 Python 스크립트가 포함된 Docker 이미지를 빌드하거나, 테스트 목적으로는 &lt;code&gt;kubectl cp&lt;/code&gt;로 파일을 전송할 수 있습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# RayService 배포&lt;/span&gt;
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; rayservice-vllm.yaml

&lt;span class="c"&gt;# 서비스 상태 확인&lt;/span&gt;
kubectl get rayservice vllm-llm-server

&lt;span class="c"&gt;# 생성된 서비스 확인&lt;/span&gt;
kubectl get svc vllm-llm-server-head-svc

&lt;span class="c"&gt;# 로컬에서 테스트를 위한 포트포워딩&lt;/span&gt;
kubectl port-forward svc/vllm-llm-server-head-svc 8000:8000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이제 &lt;code&gt;http://localhost:8000/generate&lt;/code&gt;로 API 요청을 보내어 모델 추론을 테스트할 수 있습니다.&lt;/p&gt;

</description>
      <category>ray</category>
      <category>kubernetes</category>
      <category>mlops</category>
      <category>llm</category>
    </item>
    <item>
      <title>[KubeRay로 LLM 서빙 인프라 찍먹] 2부: KubeRay로 Ray 클러스터 구축하기</title>
      <dc:creator>GyeongSeon</dc:creator>
      <pubDate>Sun, 12 Oct 2025 07:55:23 +0000</pubDate>
      <link>https://dev.to/shingyeongseon/kuberayro-llm-seobing-inpeura-jeongboghagi-2bu-kuberayro-ray-keulreoseuteo-gucughagi-4jm</link>
      <guid>https://dev.to/shingyeongseon/kuberayro-llm-seobing-inpeura-jeongboghagi-2bu-kuberayro-ray-keulreoseuteo-gucughagi-4jm</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;안녕하세요! &lt;strong&gt;[1부: LLM 서빙, 왜 Ray 여야만 했을까?]&lt;/strong&gt; 에 이어, 오늘은 본격적인 실습의 첫 단계를 시작합니다. 우리가 꿈꾸는 LLM 서빙 인프라를 구축하기 위한 가장 단단한 기반, 바로 쿠버네티스 위에 Ray 클러스터를 띄워보는 과정을 전부 보여드릴 예정입니다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;쿠버네티스 환경에서 Ray를 직접 운영하는 것은 꽤나 번거로운 일입니다. Head 파드와 Worker 파드의 생애 주기를 관리하고, 네트워크를 설정하고, 장애가 났을 때 복구하는 로직까지 모두 직접 구현해야 하죠.&lt;/p&gt;

&lt;p&gt;이러한 번거로움을 해결하기 위해 &lt;strong&gt;KubeRay&lt;/strong&gt;를 사용할 수 있습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  🤔 KubeRay가 뭐죠? 왜 써야 하나요?
&lt;/h3&gt;

&lt;p&gt;간단히 말해 &lt;code&gt;KubeRay&lt;/code&gt;는 쿠버네티스 위에서 Ray 클러스터를 손쉽게 관리해주는 '자동화 관리 로봇(Operator)' 입니다.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;KubeRay&lt;/code&gt;를 설치하면, 우리 쿠버네티스 클러스터는 &lt;code&gt;RayCluster&lt;/code&gt;와 같은 새로운 리소스 종류를 이해할 수 있게 됩니다. 이것이 바로 CRD(Custom Resource Definition) 덕분입니다. &lt;code&gt;KubeRay&lt;/code&gt;가 &lt;code&gt;RayCluster&lt;/code&gt;라는 설계도(CRD)를 등록해놓았기 때문이죠. 여기서 &lt;code&gt;RayCluster&lt;/code&gt; CRD는 오랜 시간 실행되는 영구적인(persistent) Ray 클러스터를 만드는 KubeRay의 가장 기본 단위입니다.&lt;/p&gt;

&lt;p&gt;그리고 우리는 "Ray 클러스터가 필요해..." 와 같이 원하는 상태를 YAML 파일로 정의해서 쿠버네티스에 제출합니다. 이 YAML 파일이 바로 설계도를 바탕으로 만들어진 실체, 즉 &lt;strong&gt;CR(Custom Resource)&lt;/strong&gt; 입니다. 그러면 &lt;code&gt;KubeRay&lt;/code&gt; 오퍼레이터가 이 CR을 읽고 알아서 Ray 클러스터를 생성하고, 노드가 죽으면 다시 살리는 등 모든 귀찮은 일을 대신 처리해줍니다.&lt;/p&gt;

&lt;p&gt;저도 이번 프로젝트를 진행하면서 CR과 CRD의 차이를 명확하게 알게 되었습니다. 만약 쿠버네티스의 CRD와 오퍼레이터의 기본 동작 원리가 궁금하신 분이라면 &lt;a href="https://heumsi.github.io/posts/what-is-an-operator/" rel="noopener noreferrer"&gt;heumsi님의 블로그 글&lt;/a&gt;을 읽어보시는 것을 강력히 추천합니다.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;TL DR   &lt;/p&gt;

&lt;p&gt;우리가 YAML 파일로 작성하는 '요구사항'이 바로 CR(주문서)입니다. KubeRay 오퍼레이터(자동화 로봇)는 이 주문서를 보고, 내용 그대로 Ray 클러스터를 자동으로 만들고 문제가 생기면 알아서 관리까지 해줍니다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;이를 통해 비즈니스 로직에만 집중할 수 있습니다. 이제 직접 설치를 진행하겠습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  1️⃣ 단계: KubeRay 오퍼레이터 설치하기
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;사전 준비:&lt;/strong&gt; 이 가이드를 따라오려면 &lt;code&gt;kubectl&lt;/code&gt;이 설치되어 있고, 쿠버네티스 클러스터에 접속할 수 있는 환경이 필요합니다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;먼저, &lt;code&gt;KubeRay&lt;/code&gt; 공식 저장소에 있는 YAML 파일을 사용하여 오퍼레이터를 설치합니다. 터미널에 아래 명령어를 입력하세요.&lt;/p&gt;

&lt;p&gt;KubeRay 오퍼레이터 v1.1.0 버전을 설치합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl create &lt;span class="nt"&gt;-k&lt;/span&gt; &lt;span class="s2"&gt;"[github.com/ray-project/kuberay/ray-operator/config/default?ref=v1.1.0](https://github.com/ray-project/kuberay/ray-operator/config/default?ref=v1.1.0)"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;설치가 완료되면, &lt;code&gt;ray-system&lt;/code&gt; 네임스페이스에 오퍼레이터 파드가 정상적으로 실행 중인지 확인합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pods &lt;span class="nt"&gt;-n&lt;/span&gt; ray-system
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;출력 결과&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NAME                                READY   STATUS    RESTARTS   AGE
kuberay-operator-6d7bb9f8b4-abcde   1/1     Running   0          1m
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Running&lt;/code&gt; 상태가 확인되면 오퍼레이터 설치가 완료된 것입니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  2️⃣ 단계: GPU를 지원하는 Ray 클러스터 배포하기
&lt;/h3&gt;

&lt;p&gt;이제 &lt;code&gt;KubeRay&lt;/code&gt;가 알아들을 수 있는 &lt;code&gt;RayCluster&lt;/code&gt; 명세(YAML)를 작성하여, GPU를 사용하는 Ray 클러스터를 생성해 보겠습니다.&lt;/p&gt;

&lt;p&gt;아래는 제가 사용하는 기본적인 &lt;code&gt;RayCluster&lt;/code&gt; YAML 파일입니다.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;raycluster-gpu.yaml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ray.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RayCluster&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;raycluster-llm-serving&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;rayVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2.9.0'&lt;/span&gt; &lt;span class="c1"&gt;# 사용하는 Ray 버전을 명시합니다.&lt;/span&gt;
  &lt;span class="na"&gt;headGroupSpec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;rayStartParams&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;dashboard-host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.0.0.0'&lt;/span&gt;
    &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ray-head&lt;/span&gt;
            &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rayproject/ray-ml:2.9.0-py310-gpu&lt;/span&gt;
            &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;6379&lt;/span&gt; &lt;span class="c1"&gt;# Ray Head 기본 포트&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8265&lt;/span&gt; &lt;span class="c1"&gt;# Ray Dashboard 포트&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10001&lt;/span&gt;
            &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2"&lt;/span&gt;
                &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8Gi"&lt;/span&gt;
              &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2"&lt;/span&gt;
                &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8Gi"&lt;/span&gt;
  &lt;span class="na"&gt;workerGroupSpecs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
      &lt;span class="na"&gt;minReplicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
      &lt;span class="na"&gt;maxReplicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
      &lt;span class="na"&gt;groupName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gpu-worker-group&lt;/span&gt;
      &lt;span class="na"&gt;rayStartParams&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
      &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="c1"&gt;# (추가!) 특정 레이블이 있는 노드에만 파드를 할당합니다.&lt;/span&gt;
          &lt;span class="na"&gt;nodeSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;gpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt; &lt;span class="c1"&gt;# 'gpu=true' 레이블이 있는 노드를 타겟팅&lt;/span&gt;
          &lt;span class="c1"&gt;# GPU 노드에만 파드가 스케줄링되도록 설정&lt;/span&gt;
          &lt;span class="na"&gt;tolerations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[nvidia.com/gpu](https://nvidia.com/gpu)"&lt;/span&gt;
              &lt;span class="na"&gt;operator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Exists"&lt;/span&gt;
              &lt;span class="na"&gt;effect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NoSchedule"&lt;/span&gt;
          &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ray-worker&lt;/span&gt;
              &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rayproject/ray-ml:2.9.0-py310-gpu&lt;/span&gt;
              &lt;span class="c1"&gt;# GPU 자원을 요청하는 핵심 부분!&lt;/span&gt;
              &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;64"&lt;/span&gt;
                  &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;128Gi"&lt;/span&gt;
                  &lt;span class="na"&gt;nvidia.com/gpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8"&lt;/span&gt; &lt;span class="c1"&gt;# 1개의 GPU를 요청합니다. (https://nvidia.com/gpu)&lt;/span&gt;
                &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;32"&lt;/span&gt;
                  &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;64Gi"&lt;/span&gt;
                  &lt;span class="na"&gt;nvidia.com/gpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8"&lt;/span&gt; &lt;span class="c1"&gt;# (https://nvidia.com/gpu)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이제 이 YAML 파일을 쿠버네티스에 제출해 봅시다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; raycluster-gpu.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;몇 분 정도 기다린 후, 파드들이 정상적으로 생성되었는지 확인합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pods
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;출력 결과&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NAME                                         READY   STATUS    RESTARTS   AGE
raycluster-llm-serving-head-g9plz            1/1     Running   0          2m
raycluster-llm-serving-worker-gpu-g-1-j7c82  1/1     Running   0          2m
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Head 파드 1개와 Worker 파드 1개가 &lt;code&gt;Running&lt;/code&gt; 상태라면 쿠버네티스 위에 GPU Ray 클러스터 구축이 완료된 것입니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  💡 안정적인 GPU 스케줄링을 위한 팁: &lt;code&gt;nodeSelector&lt;/code&gt; 활용하기
&lt;/h3&gt;

&lt;p&gt;GPU 워커 파드를 원하는 GPU 노드에 안정적으로 할당하기 위해서는 몇 가지 쿠버네티스 스케줄링 개념을 활용하는 것이 좋습니다.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;resources&lt;/code&gt;로 GPU 명시적으로 요청하기 (필수)&lt;/strong&gt;&lt;br&gt;
가장 기본입니다. 파드가 GPU를 필요로 한다는 사실을 쿠버네티스에 알려야 합니다. &lt;code&gt;spec.containers.resources&lt;/code&gt; 필드에 &lt;code&gt;nvidia.com/gpu: "1"&lt;/code&gt; 과 같이 필요한 GPU 수를 명시해야만 쿠버네티스 스케줄러가 GPU가 있는 노드를 탐색하기 시작합니다.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;Tolerations&lt;/code&gt;로 Taint가 걸린 노드 접근 허용하기&lt;/strong&gt;&lt;br&gt;
보통 GPU와 같이 특별한 하드웨어가 있는 노드에는 다른 일반 파드들이 스케줄링되지 않도록 'Taint'라는 표식을 붙여둡니다. &lt;code&gt;Toleration&lt;/code&gt; 설정은 "나는 이 표식이 붙은 노드에 들어가도 괜찮아"라고 허용하는 역할을 합니다. 클러스터 환경에 따라 필요할 수 있습니다.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;nodeSelector&lt;/code&gt;로 원하는 노드 콕 집어주기 (강력 추천)&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;Toleration&lt;/code&gt;이 특정 노드에 들어갈 '자격'을 주는 것이라면, &lt;code&gt;nodeSelector&lt;/code&gt;는 특정 '레이블'이 붙은 노드로만 가도록 '지정'하는 강력한 방법입니다.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;먼저, 파드를 할당하고 싶은 GPU 노드에 식별 가능한 레이블을 붙여줍니다.&lt;/p&gt;

&lt;p&gt;예시: my-gpu-node-1 이라는 이름의 노드에 gpu=true 라는 레이블을 추가&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl label nodes my-gpu-node-1 &lt;span class="nv"&gt;gpu&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;그리고 위 &lt;code&gt;raycluster-gpu.yaml&lt;/code&gt; 파일의 &lt;code&gt;workerGroupSpecs.template.spec&lt;/code&gt;에 &lt;code&gt;nodeSelector&lt;/code&gt; 필드를 추가하면 됩니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;          &lt;span class="s"&gt;...&lt;/span&gt;
          &lt;span class="s"&gt;template&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;nodeSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;gpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt; &lt;span class="c1"&gt;# 이 부분!&lt;/span&gt;
&lt;span class="err"&gt;          &lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이렇게 하면 &lt;code&gt;KubeRay&lt;/code&gt;는 워커 파드를 생성할 때 &lt;code&gt;gpu=true&lt;/code&gt; 레이블이 붙은 노드 중에서만 찾게 되므로, 의도치 않게 다른 노드에 파드가 할당되는 것을 원천적으로 방지할 수 있습니다.&lt;/p&gt;

&lt;h3&gt;
  
  
  다음 장을 향하여
&lt;/h3&gt;

&lt;p&gt;이제 기반이 되는 클러스터가 준비되었습니다. 다음 &lt;strong&gt;3부&lt;/strong&gt;에서는 이 클러스터 위에서 &lt;strong&gt;온라인 서빙을 위한 &lt;code&gt;RayService&lt;/code&gt;와&lt;/strong&gt; 일회성 작업을 위한 &lt;code&gt;RayJob&lt;/code&gt;**을 어떻게 활용하는지, 그리고 그 차이는 무엇인지 알아보겠습니다.&lt;/p&gt;

</description>
      <category>ray</category>
      <category>kubernetes</category>
      <category>mlops</category>
    </item>
    <item>
      <title>[KubeRay로 LLM 서빙 인프라 찍먹] 1부: LLM 서빙, 왜 Ray 여야만 했을까?</title>
      <dc:creator>GyeongSeon</dc:creator>
      <pubDate>Thu, 09 Oct 2025 04:47:39 +0000</pubDate>
      <link>https://dev.to/shingyeongseon/llm-seobing-wae-ray-yeoyaman-haesseulgga-2noa</link>
      <guid>https://dev.to/shingyeongseon/llm-seobing-wae-ray-yeoyaman-haesseulgga-2noa</guid>
      <description>&lt;p&gt;안녕하세요! 오늘부터 새로운 시리즈를 통해 제가 거대한 언어 모델(LLM)을 효율적으로 서빙하기 위해 쿠버네티스 환경에서 Ray를 활용하고, 나아가 이 모든 과정을 자동화하는 Managed API를 구축했던 여정을 간단하게 공유해보고자 합니다.&lt;/p&gt;

&lt;p&gt;요즘 어딜 가나 LLM 이야기뿐입니다. 많은 팀이 LLM을 활용해 새로운 가치를 만들고 싶어 하지만, 멋진 아이디어를 현실로 만드는 길은 생각보다 험난합니다. 특히 프로토타입을 넘어 수많은 사용자의 요청을 실시간으로 처리해야 하는 '서빙' 단계에 이르면, 비싼 GPU 비용, 까다로운 동시성 처리, 변화무쌍한 트래픽에 대한 스케일링 등 현실적인 문제들이 발목을 잡곤 하죠. &lt;/p&gt;

&lt;p&gt;이번 첫 번째 글에서는 제가 마주했던 이 문제들을 어떻게 정의했고, 수많은 선택지 속에서 왜 최종적으로 'Ray'라는 카드를 선택하게 되었는지, 그 고민의 과정을 솔직하게 이야기해보려 합니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  🎯 제가 풀어야 할 숙제: 거대 모델을 제 손으로 직접 서빙하기
&lt;/h2&gt;

&lt;p&gt;저의 목표는 명확했습니다. '효율적인 LLM 모델 배포 및 운영'.&lt;/p&gt;

&lt;p&gt;하지만 &lt;strong&gt;'효율적'&lt;/strong&gt;이라는 단어 안에는 여러 가지 의미가 담겨 있었습니다. 고민 끝에, 제가 구축할 LLM 서빙 인프라는 다음과 같은 요구사항을 만족해야 했습니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;⚡ 높은 동시성(High Concurrency): 수많은 사용자의 요청을 지연 없이 동시에 처리할 수 있어야 한다.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🌊 탄력적 확장성(Elastic Scaling): 트래픽이 몰릴 때는 자원을 늘리고, 한가할 때는 자원을 줄여 비용을 최적화해야 한다.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;💰 자원 효율성(Resource Efficiency): 비싼 GPU 자원을 낭비 없이 최대한 활용해야 한다. 하나의 GPU를 여러 모델이 나눠 쓰거나, 필요에 따라 유연하게 할당할 수 있어야 한다.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🧑‍💻 개발 편의성(Developer Experience): 인프라에 대한 깊은 지식이 없는 개발자도 간단한 API 호출만으로 모델을 배포하고 관리할 수 있도록 필수 과정을 추상화해야 한다.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;이 까다로운 조건들을 만족시킬 도구를 찾기 위한 기나긴 여정이 시작되었습니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  🤔 수많은 선택지 속에서, 왜 'Ray'였을까?
&lt;/h2&gt;

&lt;p&gt;단순히 쿠버네티스 Deployment와 Service를 활용하는 방법부터 KServe, BentoML 같은 전문 서빙 프레임워크까지, 정말 다양한 도구들을 검토했습니다. 각 도구는 저마다 훌륭한 장점을 가지고 있었지만, 저의 모든 요구사항을 만족시키기엔 조금씩 아쉬운 부분이 있었습니다.&lt;/p&gt;

&lt;p&gt;그러던 중, 제 눈에 들어온 것이 바로 Ray였습니다. 처음에는 분산 처리를 위한 라이브러리 정도로만 생각했지만, 깊게 들여다볼수록 Ray는 제가 찾던 '해답'에 가장 가까운 도구였습니다.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Python 개발자들에게 너무나 자연스러운 확장&lt;br&gt;&lt;br&gt;
LLM과 머신러닝 생태계의 중심은 단연 파이썬입니다. Ray의 가장 큰 매력은 기존 파이썬 코드를 거의 그대로 유지한 채 분산 환경으로 확장할 수 있다는 점이었습니다. 복잡한 분산 시스템 개념을 배우기 전에, 익숙한 파이썬 함수나 클래스에 &lt;a class="mentioned-user" href="https://dev.to/ray"&gt;@ray&lt;/a&gt;.remote 데코레이터 하나만 붙이면 마법처럼 코드가 클러스터 환경에서 실행되는 경험은 정말 강력했습니다.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;유연하고 강력한 분산 처리 모델 (Actor Model)&lt;br&gt;&lt;br&gt;
LLM 서빙은 모델을 GPU 메모리에 올리고, 여러 요청을 상태를 유지하며(stateful) 처리해야 하는 대표적인 작업입니다. Ray의 액터(Actor) 모델은 이러한 stateful 워크로드를 처리하는 데 최적화되어 있습니다. 특정 모델을 특정 GPU에 할당한 액터로 만들고, 요청이 들어올 때마다 해당 액터에게 작업을 전달하는 방식으로 서빙 로직을 매우 직관적으로 구현할 수 있었습니다.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;똑똑한 GPU 자원 관리&lt;br&gt;&lt;br&gt;
"3번 노드의 1번 GPU가 비어있나?" 같은 질문은 더 이상 할 필요가 없습니다. Ray는 클러스터 전체의 GPU를 하나의 거대한 풀(pool)처럼 관리합니다. 우리는 그저 "GPU 0.5개가 필요한 액터를 만들어줘" 라고 요청하기만 하면, Ray 스케줄러가 알아서 가장 적합한 노드에 작업을 할당해 줍니다. 이러한 추상화 덕분에 복잡한 자원 관리 로직을 직접 구현하는 수고를 덜 수 있었습니다.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;LLM 서빙을 위해 태어난 듯한, Ray Serve&lt;br&gt;&lt;br&gt;
Ray의 여러 라이브러리 중에서도 Ray Serve는 LLM 서빙을 위한 기능의 집약체였습니다. 들어오는 트래픽에 따라 자동으로 액터(파드) 수를 조절하는 오토스케일링, 여러 요청을 묶어 GPU 효율을 높이는 동적 배치(Dynamic Batching), 여러 모델을 조합하여 파이프라인을 만드는 기능(Deployment Graph) 등 제가 원했던 대부분의 기능이 이미 구현되어 있었습니다.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  🚀 앞으로 펼쳐질 여정: KubeRay부터 Managed API까지
&lt;/h2&gt;

&lt;p&gt;물론 Ray가 모든 것을 해결해주는 '마법의 은 탄환'은 아니었습니다. 오히려 Ray라는 강력한 도구를 제 손에 맞게 길들이는 과정은 또 다른 도전의 연속이었습니다.&lt;/p&gt;

&lt;p&gt;앞으로 이어질 시리즈에서는 다음과 같은 이야기들을 하나씩 풀어가려고 합니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;2부: KubeRay로 Ray 클러스터 구축하기&lt;/li&gt;
&lt;li&gt;3부: 3부: vLLM과 Ray Serve를 활용한 고성능 추론 엔드포인트 구축기&lt;/li&gt;
&lt;li&gt;4부: LLM 서빙을 위한 Ray Managed API 설계 및 회고&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;이 시리즈가 저처럼 LLM을 안정적으로, 그리고 효율적으로 서빙하는 방법에 대해 고민하는 분들께 작게나마 도움이 되기를 바랍니다.&lt;/p&gt;

&lt;p&gt;다음 글에서는 KubeRay를 활용하여 쿠버네티스 위에 Ray 클러스터를 구축하는 구체적인 방법으로 찾아뵙겠습니다.&lt;/p&gt;

</description>
      <category>ray</category>
      <category>kubernetes</category>
      <category>llm</category>
      <category>mlops</category>
    </item>
  </channel>
</rss>
