<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>wero90</title>
    <link>https://wero90.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Wed, 10 Jun 2026 06:07:33 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>wero90</managingEditor>
    <item>
      <title>AI 서비스 비용 거버넌스: 팀별 할당, 예산 알림, 사용량 대시보드 설계</title>
      <link>https://wero90.tistory.com/80</link>
      <description>&lt;blockquote style=&quot;border-left: 4px solid #3b82f6; padding: 1rem 1.2rem; margin: 1.5rem 0; background: #f0f9ff; color: #1e40af; font-size: 1.05rem; line-height: 1.7; border-radius: 0 8px 8px 0;&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;AI 서비스 비용은 토큰 단위로 과금되며, 사용량이 예측하기 어렵습니다. 팀 단위 비용 가시성 없이 운영하면, 월말 청구서를 받고서야 어떤 팀이 얼마를 썼는지 파악하게 됩니다. 이 글은 AI 서비스 비용을 팀별로 할당하고, 예산 초과를 사전에 감지하며, 사용량을 대시보드로 가시화하는 거버넌스 아키텍처를 설계 관점에서 정리합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;_1&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;핵심 요약&lt;/h2&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AI 서비스 비용은 전통적인 인프라 비용과 달리 토큰 수, 모델 종류, 요청 패턴에 따라 크게 변동합니다.&lt;/li&gt;
&lt;li&gt;팀별 비용 귀속(Cost Attribution)을 위해 LLM Gateway 계층에서 요청을 식별하고, 메타데이터로 태깅하는 구조가 필요합니다.&lt;/li&gt;
&lt;li&gt;예산 알림은 임계값 기반 단계별 알림과 자동 차단을 조합하여 설계합니다.&lt;/li&gt;
&lt;li&gt;사용량 대시보드는 팀별, 모델별, 기간별 토큰 소비량과 비용을 실시간으로 보여줘야 운영 판단이 가능합니다.&lt;/li&gt;
&lt;li&gt;Chargeback(실비 청구)과 Showback(비용 가시화) 중 조직 성숙도에 맞는 모델을 선택합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 id=&quot;ai_1&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;AI 비용 구조의 특수성&lt;/h2&gt;
&lt;h3 id=&quot;_2&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;전통 인프라 비용과의 차이&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;전통적인 클라우드 인프라는 인스턴스 유형과 가동 시간으로 비용이 결정됩니다. EC2 m5.large를 720시간 운영하면 월 비용은 대체로 예측 가능합니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;AI 서비스 비용은 다릅니다. 같은 API를 호출하더라도 프롬프트 길이, 응답 길이, 사용 모델에 따라 비용이 수십 배 차이가 납니다.&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;비용 변수&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;전통 인프라&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;AI 서비스&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;과금 단위&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;시간, vCPU, GB&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;토큰(입력/출력 별도)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;예측 가능성&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;높음 (고정 스펙)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;낮음 (요청마다 다름)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;비용 증폭 요인&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;트래픽 증가&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;프롬프트 길이, 멀티턴, Reasoning 토큰&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;비용 가시성&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;태깅으로 즉시 확인&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;별도 계측 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;비용 누수 패턴&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;미사용 리소스&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;실패 요청, Agent 루프, 불필요한 재시도&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;figure style=&quot;text-align: center; margin: 2rem 0;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dna/bDlyg5/dJMb99UcBWx/AAAAAAAAAAAAAAAAAAAAAO4EmzOiKrUby5n7DEcvhzYCKmc1w2xTlOmnBwQDu19_/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1782831599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=ABJ0vI4lIlQkN8exktELEXKxRog%3D&quot; alt=&quot;AI 비용 구조의 특수성&quot; /&gt;
&lt;figcaption style=&quot;margin-top: 0.5rem; font-size: 0.85rem; color: #64748b;&quot;&gt;AI 비용 구조의 특수성&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;_3&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;비용 예측이 어려운 이유&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;운영 환경에서 AI 서비스 비용이 급증하는 패턴은 대체로 세 가지입니다.&lt;/p&gt;
&lt;ol style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Agent 루프&lt;/b&gt;: AI Agent가 도구를 반복 호출하면서 한 요청당 수십 회 API 호출이 발생합니다. 단일 사용자 요청이 수만 토큰을 소비하는 경우가 생깁니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;멀티턴 대화 누적&lt;/b&gt;: 대화 이력을 매 요청에 포함하면, 대화가 길어질수록 입력 토큰이 기하급수적으로 증가합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Reasoning 토큰&lt;/b&gt;: 일부 모델(예: OpenAI o3, Claude Opus 계열)은 사고 과정에 별도 토큰을 사용하며, 이는 출력 토큰보다 비용이 높을 수 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 id=&quot;_4&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;비용 귀속 아키텍처: 누가 얼마를 썼는지 식별하기&lt;/h2&gt;
&lt;h3 id=&quot;llm-gateway&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;LLM Gateway 중심 설계&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;팀별 비용을 추적하려면, 모든 LLM API 호출이 단일 게이트웨이를 통과하도록 설계합니다. 애플리케이션이 LLM Provider에 직접 호출하면 비용 귀속이 불가능합니다.&lt;/p&gt;
&lt;figure style=&quot;text-align: center; margin: 2rem 0;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dna/bUyFj4/dJMcaaS8g0l/AAAAAAAAAAAAAAAAAAAAAFCDxYABvGUkAXsiSOR-ng6MMq7BI1pxa8EjY00eTvBZ/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1782831599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=QIlVD6joQ6bHxqyOwag3FeohUec%3D&quot; alt=&quot;LLM Gateway 기반 비용 귀속 아키텍처&quot; /&gt;
&lt;figcaption style=&quot;margin-top: 0.5rem; font-size: 0.85rem; color: #64748b;&quot;&gt;LLM Gateway 기반 비용 귀속 아키텍처&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Gateway가 수행하는 역할:&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;요청 식별&lt;/b&gt;: 팀 ID, 프로젝트 ID, 사용자 ID를 요청 메타데이터에서 추출합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;토큰 계측&lt;/b&gt;: Provider 응답에서 실제 토큰 수(입력/출력/Reasoning)를 파싱합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;비용 계산&lt;/b&gt;: 모델별 단가 &amp;times; 토큰 수로 실시간 비용을 산출합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;예산 검증&lt;/b&gt;: 팀/프로젝트 예산 한도를 확인하고, 초과 시 요청을 차단하거나 저비용 모델로 라우팅합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;_5&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;실무 구현 옵션&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;방식&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;설명&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;적합한 상황&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;LiteLLM Proxy&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;오픈소스. Virtual Key + Team 기반 예산 관리. PostgreSQL로 사용량 저장&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;스타트업, 중소 규모 팀&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;자체 Gateway&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Kong/Envoy 기반 커스텀 플러그인으로 토큰 계측과 라우팅 구현&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;대규모 엔터프라이즈, 커스텀 요구사항&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Provider 내장 기능&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;OpenAI Projects, AWS Bedrock Projects 등 Provider별 비용 분리&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;단일 Provider 환경, 빠른 시작&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;_6&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;태깅 전략&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;비용 귀속의 핵심은 모든 요청에 일관된 메타데이터를 부착하는 것입니다.&lt;/p&gt;
&lt;pre class=&quot;json&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;{
  &quot;metadata&quot;: {
    &quot;team_id&quot;: &quot;platform-team&quot;,
    &quot;project_id&quot;: &quot;customer-chatbot&quot;,
    &quot;environment&quot;: &quot;production&quot;,
    &quot;feature&quot;: &quot;ticket-classification&quot;,
    &quot;cost_center&quot;: &quot;CC-4200&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;태깅 설계 원칙:&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;필수 태그&lt;/b&gt;: team_id, project_id, environment는 모든 요청에 반드시 포함합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;선택 태그&lt;/b&gt;: feature, user_id, session_id는 세부 분석용으로 추가합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;태그 검증&lt;/b&gt;: Gateway에서 필수 태그가 누락된 요청은 거부합니다. 태그 없는 요청은 비용 귀속이 불가능하기 때문입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;태그 표준화&lt;/b&gt;: 태그 값은 중앙에서 관리하는 목록(Allow List)으로 제한합니다. 자유 입력을 허용하면 오타와 불일치로 집계가 깨집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 id=&quot;_7&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;예산 할당과 알림 설계&lt;/h2&gt;
&lt;h3 id=&quot;chargeback-vs-showback&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;예산 모델 선택: Chargeback vs Showback&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;모델&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;설명&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;장점&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;단점&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;b&gt;Showback&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;팀별 비용을 가시화하되, 실제 과금하지 않음&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;도입 저항 낮음, 비용 인식 향상&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;직접적인 비용 절감 동기 부족&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;b&gt;Chargeback&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;팀별 사용량을 실제 부서 예산에서 차감&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;강한 비용 절감 동기, 정확한 ROI 산출&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;도입 복잡, 비용 산정 분쟁 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;대부분의 조직은 Showback에서 시작해서, 비용이 일정 규모를 넘기면 Chargeback으로 전환합니다. AI 비용이 월 $10,000 미만인 단계에서는 Showback으로 비용 인식을 먼저 확립하는 것이 실용적입니다.&lt;/p&gt;
&lt;h3 id=&quot;_8&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;팀별 예산 할당 구조&lt;/h3&gt;
&lt;figure style=&quot;text-align: center; margin: 2rem 0;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dna/c1MY7m/dJMcajbmGX2/AAAAAAAAAAAAAAAAAAAAAJwHmbGxdorJn_2yKoegP67LrV5dHP7YGXB6VkuadzcD/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1782831599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=n%2FKvsSIuvY7QNAjCornqUnrRPM0%3D&quot; alt=&quot;팀별 예산 할당 구조&quot; /&gt;
&lt;figcaption style=&quot;margin-top: 0.5rem; font-size: 0.85rem; color: #64748b;&quot;&gt;팀별 예산 할당 구조&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;예산은 계층적으로 설계합니다:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;Organization Budget ($50,000/월)
├── Platform Team ($20,000/월)
│   ├── Customer Chatbot ($12,000)
│   └── Internal Tools ($8,000)
├── Data Team ($15,000/월)
│   ├── Document Processing ($10,000)
│   └── Analytics Agent ($5,000)
├── Product Team ($10,000/월)
│   └── Recommendation Engine ($10,000)
└── Reserve ($5,000/월)
    └── 실험/PoC용 공유 예산
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;설계 시 고려사항:&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;리저브 풀&lt;/b&gt;: 전체 예산의 10~15%는 예비 풀로 유지합니다. 새 프로젝트 PoC나 예기치 않은 사용량 증가에 대응합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;예산 주기&lt;/b&gt;: 월 단위가 기본이지만, AI 프로젝트 초기에는 주 단위 리뷰가 필요할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;모델별 분리&lt;/b&gt;: 고비용 모델(GPT-4o, Claude Opus 4)과 저비용 모델(GPT-4o mini, Claude Haiku)의 예산을 분리하면 비용 최적화 동기를 부여할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;_9&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;임계값 기반 알림 설계&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;단순히 &quot;예산 초과&quot; 알림 하나로는 운영 대응이 어렵습니다. 단계별 임계값을 설계합니다.&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;임계값&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;알림 대상&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;액션&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;50% 도달&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;팀 리드 (Slack)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;정보 제공, 추세 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;75% 도달&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;팀 리드 + FinOps (Slack + Email)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;사용 패턴 리뷰, 최적화 검토&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;90% 도달&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;팀 리드 + FinOps + 관리자 (PagerDuty)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;저비용 모델 자동 전환 검토&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;100% 도달&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;전체 이해관계자&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;요청 차단 또는 Fallback 모델 강제 적용&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;100% 초과 시 즉시 차단할지, Fallback 모델로 전환할지는 서비스 중요도에 따라 결정합니다. 고객 대면 서비스라면 차단보다 Fallback이 적합합니다. 내부 도구라면 차단이 단순하고 효과적입니다.&lt;/p&gt;
&lt;h3 id=&quot;_10&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;예산 초과 대응 자동화&lt;/h3&gt;
&lt;pre class=&quot;yaml&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# 예산 정책 예시 (Gateway 설정)
budget_policies:
  - team_id: platform-team
    monthly_budget: 20000
    alerts:
      - threshold: 0.5
        channels: [slack]
        message: &quot;Platform Team AI 예산 50% 도달&quot;
      - threshold: 0.75
        channels: [slack, email]
        message: &quot;Platform Team AI 예산 75% 도달 &amp;mdash; 사용 패턴 리뷰 필요&quot;
      - threshold: 0.9
        channels: [slack, pagerduty]
        action: downgrade_model
        fallback_model: gpt-4o-mini
      - threshold: 1.0
        channels: [slack, pagerduty, email]
        action: block_requests
        exceptions: [critical-customer-chatbot]
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 id=&quot;_11&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;사용량 대시보드 설계&lt;/h2&gt;
&lt;h3 id=&quot;_12&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;대시보드가 보여줘야 하는 지표&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;대시보드는 &quot;얼마나 썼는가&quot;와 &quot;왜 그만큼 썼는가&quot;를 모두 답할 수 있어야 합니다.&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;계층&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;핵심 지표&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;목적&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;조직 전체&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;총 비용, 전월 대비 증감률, 예산 소진율&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;경영진 보고, 전체 추세&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;팀별&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;팀별 비용, 예산 대비 사용률, 일별 추세&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;팀 리드 비용 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;프로젝트별&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;프로젝트별 비용, 모델별 분포, 토큰 효율&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;최적화 대상 식별&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;요청 단위&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;p50/p95/p99 토큰 수, 비용 아웃라이어&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;비용 이상 탐지, 디버깅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;_13&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;대시보드 구조 설계&lt;/h3&gt;
&lt;figure style=&quot;text-align: center; margin: 2rem 0;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dna/xsLvJ/dJMcai4CtBp/AAAAAAAAAAAAAAAAAAAAAGRHIYreNeR0pw26_PXlBATYiccrHnctEmrKuPBjL1yO/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1782831599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=GwqDmRsm5lqui4hV0%2BRdRPzeOl8%3D&quot; alt=&quot;사용량 대시보드 구조&quot; /&gt;
&lt;figcaption style=&quot;margin-top: 0.5rem; font-size: 0.85rem; color: #64748b;&quot;&gt;사용량 대시보드 구조&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;실무에서 효과적인 대시보드는 다음 세 가지 뷰로 구성합니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. Executive View (경영진/FinOps)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&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 style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. Team View (팀 리드/엔지니어)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일별 비용 추세 (막대 그래프)&lt;/li&gt;
&lt;li&gt;모델별 비용 분포 (어떤 모델이 비용 비중이 높은지)&lt;/li&gt;
&lt;li&gt;예산 잔액과 소진 속도 (며칠 후 예산 소진 예상인지)&lt;/li&gt;
&lt;li&gt;Top 5 비용 요청 (이상 탐지용)&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. Optimization View (엔지니어)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;캐시 히트율 (Semantic Cache 활용도)&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;h3 id=&quot;_14&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;데이터 파이프라인 설계&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;대시보드를 구동하려면 아래 데이터 흐름이 필요합니다.&lt;/p&gt;
&lt;pre class=&quot;gcode&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;LLM Gateway &amp;rarr; 이벤트 스트림 (Kafka/Kinesis)
  &amp;rarr; 실시간 집계 (Flink/Lambda) &amp;rarr; 대시보드 (Grafana/Datadog)
  &amp;rarr; 배치 적재 (S3/BigQuery) &amp;rarr; 월간 리포트, 예측 모델
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;각 요청 이벤트에 포함해야 하는 필드:&lt;/p&gt;
&lt;pre class=&quot;json&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;{
  &quot;timestamp&quot;: &quot;2026-06-09T14:30:00Z&quot;,
  &quot;team_id&quot;: &quot;platform-team&quot;,
  &quot;project_id&quot;: &quot;customer-chatbot&quot;,
  &quot;model&quot;: &quot;gpt-4o&quot;,
  &quot;input_tokens&quot;: 2340,
  &quot;output_tokens&quot;: 856,
  &quot;reasoning_tokens&quot;: 0,
  &quot;cached_tokens&quot;: 1200,
  &quot;cost_usd&quot;: 0.0287,
  &quot;latency_ms&quot;: 1842,
  &quot;status&quot;: &quot;success&quot;,
  &quot;user_id&quot;: &quot;user-12345&quot;,
  &quot;session_id&quot;: &quot;sess-abc&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 id=&quot;provider&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;클라우드 Provider별 비용 관리 기능&lt;/h2&gt;
&lt;h3 id=&quot;aws-bedrock&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;AWS Bedrock&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;AWS Bedrock은 2026년 4월부터 IAM 주체별 세분화된 비용 귀속 기능을 제공합니다. CUR 2.0의 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;line_item_iam_principal&lt;/code&gt; 컬럼과 Cost Explorer의 IAM 주체 태그 필터링이 추가되어, 동일 계정 내에서도 팀별 비용을 직접 분리할 수 있게 되었습니다.&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Bedrock Projects&lt;/b&gt;: 프로젝트 ID를 API 호출에 전달하면, Cost Explorer에서 프로젝트별 필터링이 가능합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Cost Allocation Tags&lt;/b&gt;: 리소스 태그를 활성화하면 AWS Billing에서 팀/부서별 비용을 분리할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;AWS Budgets&lt;/b&gt;: Bedrock 서비스에 대한 예산을 생성하고, 임계값 초과 시 SNS 알림을 발송합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Budget Controls&lt;/b&gt;: 예산 초과 시 자동으로 리소스를 중지하거나 삭제하는 액션을 설정할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;sql&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# AWS Budgets로 Bedrock 비용 예산 설정 예시
aws budgets create-budget \
  --account-id 123456789012 \
  --budget '{
    &quot;BudgetName&quot;: &quot;bedrock-platform-team&quot;,
    &quot;BudgetLimit&quot;: {&quot;Amount&quot;: &quot;20000&quot;, &quot;Unit&quot;: &quot;USD&quot;},
    &quot;TimeUnit&quot;: &quot;MONTHLY&quot;,
    &quot;BudgetType&quot;: &quot;COST&quot;,
    &quot;CostFilters&quot;: {
      &quot;Service&quot;: [&quot;Amazon Bedrock&quot;],
      &quot;TagKeyValue&quot;: [&quot;user:team$platform-team&quot;]
    }
  }' \
  --notifications-with-subscribers '[
    {
      &quot;Notification&quot;: {
        &quot;NotificationType&quot;: &quot;ACTUAL&quot;,
        &quot;ComparisonOperator&quot;: &quot;GREATER_THAN&quot;,
        &quot;Threshold&quot;: 75
      },
      &quot;Subscribers&quot;: [{&quot;SubscriptionType&quot;: &quot;SNS&quot;, &quot;Address&quot;: &quot;arn:aws:sns:...&quot;}]
    }
  ]'
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;openai-platform&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;OpenAI Platform&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;OpenAI는 Organization 내에서 Project 단위로 비용을 분리할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Projects&lt;/b&gt;: 팀별 Project를 생성하고, 각 Project에 별도 API Key를 발급합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Usage Dashboard&lt;/b&gt;: Project별 토큰 사용량과 비용을 확인할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Budget Limits&lt;/b&gt;: Project별 월간 예산 한도를 설정할 수 있습니다. 다만 2025년 이후 OpenAI 지원팀에 따르면 Project 예산은 하드 리밋이 아닌 알림 전용으로 동작합니다. 따라서 Provider 자체 예산 기능에만 의존하면 실제 차단이 되지 않을 수 있으므로, LLM Gateway에서 별도로 예산 제어를 구현하는 것이 안전합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Rate Limits&lt;/b&gt;: TPM(Tokens Per Minute), RPM(Requests Per Minute) 제한을 Project별로 설정합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;azure-openai-service&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;Azure OpenAI Service&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Azure는 기존 Azure 비용 관리 체계와 통합되는 장점이 있습니다.&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Resource Group/Subscription 분리&lt;/b&gt;: 팀별 Resource Group이나 Subscription으로 물리적 비용 분리가 가능합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Azure Cost Management&lt;/b&gt;: 태그 기반 비용 분석, 예산 알림, 이상 탐지를 기본 제공합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Azure Monitor&lt;/b&gt;: 토큰 사용량 메트릭을 기반으로 커스텀 알림 규칙을 설정합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 id=&quot;llm-gateway_1&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;LLM Gateway 기반 거버넌스 구현&lt;/h2&gt;
&lt;h3 id=&quot;litellm&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;LiteLLM을 이용한 팀별 예산 관리&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;LiteLLM Proxy는 오픈소스로, 팀과 Virtual Key 기반의 예산 관리를 제공합니다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# LiteLLM config.yaml 예시
model_list:
  - model_name: gpt-4o
    litellm_params:
      model: openai/gpt-4o
      api_key: os.environ/OPENAI_API_KEY

  - model_name: gpt-4o-mini
    litellm_params:
      model: openai/gpt-4o-mini
      api_key: os.environ/OPENAI_API_KEY

general_settings:
  master_key: sk-master-key-1234
  database_url: os.environ/DATABASE_URL

litellm_settings:
  max_budget: 50000        # 조직 전체 월 예산 ($)
  budget_duration: 30d     # 예산 리셋 주기
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;팀 생성 및 예산 할당:&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# 팀 생성 (API 호출)
curl -X POST http://gateway:4000/team/new \
  -H &quot;Authorization: Bearer sk-master-key-1234&quot; \
  -d '{
    &quot;team_alias&quot;: &quot;platform-team&quot;,
    &quot;max_budget&quot;: 20000,
    &quot;budget_duration&quot;: &quot;30d&quot;,
    &quot;models&quot;: [&quot;gpt-4o&quot;, &quot;gpt-4o-mini&quot;],
    &quot;metadata&quot;: {&quot;cost_center&quot;: &quot;CC-4200&quot;}
  }'

# 팀 멤버용 Virtual Key 발급
curl -X POST http://gateway:4000/key/generate \
  -H &quot;Authorization: Bearer sk-master-key-1234&quot; \
  -d '{
    &quot;team_id&quot;: &quot;platform-team&quot;,
    &quot;key_alias&quot;: &quot;chatbot-service-key&quot;,
    &quot;max_budget&quot;: 12000,
    &quot;models&quot;: [&quot;gpt-4o&quot;, &quot;gpt-4o-mini&quot;]
  }'
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;이 구조에서 각 팀은 할당된 Virtual Key로만 API를 호출하므로, 예산 소진 시 자동으로 요청이 거부됩니다.&lt;/p&gt;
&lt;h3 id=&quot;gateway&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;자체 Gateway 구축 시 고려사항&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;상용 Gateway나 LiteLLM으로 부족한 요구사항이 있다면 자체 구축을 검토합니다. 다만 아래 복잡도를 감수해야 합니다.&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;구성 요소&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;구현 내용&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Reverse Proxy&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Kong/Envoy 기반, 요청 라우팅 + 인증&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Token Counter&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Provider 응답 파싱, 모델별 과금 단위 매핑&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Budget Engine&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Redis/DB 기반 실시간 잔액 확인 + 차감&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Alert Publisher&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;임계값 확인 &amp;rarr; Slack/PagerDuty 발송&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Dashboard Backend&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;집계 데이터 API &amp;rarr; Grafana/커스텀 UI&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;자체 구축은 멀티 Provider(OpenAI + Anthropic + AWS Bedrock) 환경에서 통합 비용 관리가 필요하거나, Provider별 과금 모델 변경에 즉시 대응해야 하는 경우에 적합합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 id=&quot;_15&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;비용 최적화와 거버넌스의 조합&lt;/h2&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;비용 거버넌스는 단순히 &quot;예산을 넘기지 마라&quot;가 아닙니다. 예산 내에서 최대 가치를 뽑아내는 구조를 만드는 것이 목적입니다.&lt;/p&gt;
&lt;h3 id=&quot;tiering&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;모델 Tiering 전략&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;모든 요청에 최고 성능 모델을 사용할 필요는 없습니다. 요청 복잡도에 따라 모델을 분류합니다.&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;Tier&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;용도&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;모델 예시&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;비용 수준&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Tier 1&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;단순 분류, 요약, 포맷 변환&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;GPT-4o mini, Claude Haiku&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Tier 2&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;일반 대화, 코드 생성, 분석&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;GPT-4o, Claude Sonnet 4&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;$$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Tier 3&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;복잡한 추론, 의사결정, 전문 분석&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;GPT-4o + Reasoning, Claude Opus 4&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;$$$&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Gateway에서 요청 특성을 분석하여 자동으로 적절한 Tier 모델로 라우팅하면, 품질 저하 없이 비용을 줄일 수 있습니다.&lt;/p&gt;
&lt;h3 id=&quot;_16&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;캐싱과 비용 절감&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;동일하거나 유사한 요청이 반복되는 경우, Semantic Cache를 적용하면 토큰 비용을 크게 줄일 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Exact Match Cache&lt;/b&gt;: 동일한 프롬프트에 대해 캐시된 응답을 반환합니다. FAQ 봇처럼 정형화된 질문에 효과적입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Semantic Cache&lt;/b&gt;: 의미적으로 유사한 질문에 대해 캐시를 활용합니다. 임베딩 유사도 기반으로 동작합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Provider Cache&lt;/b&gt;: OpenAI의 Prompt Caching, Anthropic의 Prompt Caching 등 Provider 자체 캐싱 기능을 활용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;대시보드에서 캐시 히트율을 추적하면, 캐싱이 실제로 비용 절감에 기여하는지 정량적으로 확인할 수 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 id=&quot;50-ai&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;실무 시나리오: 50인 규모 팀의 AI 비용 거버넌스&lt;/h2&gt;
&lt;h3 id=&quot;_17&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;상황&lt;/h3&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;3개 팀(Platform, Data, Product)이 AI 서비스를 사용합니다.&lt;/li&gt;
&lt;li&gt;월 AI API 비용이 $30,000~$50,000 수준입니다.&lt;/li&gt;
&lt;li&gt;각 팀이 별도 Provider API Key를 직접 사용하고 있어, 팀별 비용 추적이 불가능합니다.&lt;/li&gt;
&lt;li&gt;월말에 청구서를 받고서야 예산 초과를 인지합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;_18&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;설계 방안&lt;/h3&gt;
&lt;ol style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;LLM Gateway 도입&lt;/b&gt;: LiteLLM Proxy를 배포하고, 모든 팀이 Gateway를 통해서만 API를 호출하도록 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;팀별 Virtual Key 발급&lt;/b&gt;: 각 팀에 예산이 설정된 Virtual Key를 발급합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;태그 강제화&lt;/b&gt;: Gateway에서 team_id, project_id 태그가 없는 요청은 거부합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;단계별 알림&lt;/b&gt;: 50%, 75%, 90%, 100% 임계값에서 Slack과 Email 알림을 발송합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Grafana 대시보드&lt;/b&gt;: Gateway 메트릭을 Prometheus로 수집하고, 팀별/모델별 대시보드를 구성합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;월간 비용 리뷰&lt;/b&gt;: FinOps 담당자가 팀별 비용 보고서를 작성하고, 최적화 기회를 식별합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;_19&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;기대 효과&lt;/h3&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;팀별 비용 가시성 확보: &quot;어떤 팀이 왜 이만큼 썼는지&quot; 즉시 확인 가능&lt;/li&gt;
&lt;li&gt;예산 초과 사전 차단: 90% 도달 시 자동 모델 다운그레이드&lt;/li&gt;
&lt;li&gt;비용 30~40% 절감: 모델 Tiering + 캐싱 + 불필요한 요청 제거&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 id=&quot;_20&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;거버넌스 도입 단계별 로드맵&lt;/h2&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;모든 조직에 동일한 거버넌스 구조가 필요한 것은 아닙니다. AI 비용 규모와 조직 성숙도에 따라 단계적으로 도입합니다.&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;단계&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;AI 비용 규모&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;핵심 활동&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;도구&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;1단계: 가시화&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&amp;lt; $5,000/월&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Provider 대시보드로 비용 확인, 기본 예산 알림&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Provider Dashboard, AWS Budgets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;2단계: 귀속&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;$5,000~$20,000/월&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;LLM Gateway 도입, 팀별 Key 분리, 태깅&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;LiteLLM, 태그 정책&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;3단계: 제어&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;$20,000~$100,000/월&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;자동 예산 차단, 모델 Tiering, 캐싱&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Gateway + Budget Engine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;4단계: 최적화&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&amp;gt; $100,000/월&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Chargeback, 예측 모델, 자동 Right-sizing&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;FinOps 플랫폼, ML 예측&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 id=&quot;_21&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;운영 시 주의사항&lt;/h2&gt;
&lt;h3 id=&quot;_22&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;비용 데이터 지연&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Provider별로 비용 데이터 반영 시점이 다릅니다.&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;OpenAI&lt;/b&gt;: Usage Dashboard는 최대 5분 지연, Billing은 최대 24시간 지연이 있을 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;AWS Bedrock&lt;/b&gt;: Cost Explorer는 최대 24시간 지연. 실시간 추적이 필요하면 CloudWatch 메트릭을 사용합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;자체 Gateway&lt;/b&gt;: 실시간 계측이 가능하지만, Provider 청구서와의 차이(반올림, 캐시 할인 등)가 발생할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;실시간 대시보드와 월말 청구서 사이에 5~10% 수준의 차이는 정상입니다. 이 차이를 줄이려면 Provider별 과금 규칙(최소 과금 단위, 반올림 방식)을 정확히 파악해야 합니다.&lt;/p&gt;
&lt;h3 id=&quot;_23&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;예산 차단의 부작용&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;예산 100% 소진 시 요청을 차단하면 서비스 장애로 이어질 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;고객 대면 서비스&lt;/b&gt;: 차단 대신 Fallback 모델(저비용) 전환을 기본으로 설정합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;내부 도구&lt;/b&gt;: 차단 후 예산 증액 요청 프로세스를 마련합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;긴급 상황&lt;/b&gt;: 특정 Key나 서비스에 대해 예산 제한을 우회할 수 있는 Override 메커니즘을 준비합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;_24&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;조직 저항 관리&lt;/h3&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;초기에는 Showback으로 비용 가시성만 제공하고, 즉시 차단하지 않습니다.&lt;/li&gt;
&lt;li&gt;팀별 예산을 설정할 때 현재 사용량 + 20~30% 여유분으로 시작합니다.&lt;/li&gt;
&lt;li&gt;비용 절감이 아닌 &quot;예측 가능한 비용 운영&quot;을 목표로 커뮤니케이션합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 id=&quot;_25&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;AI 서비스 비용 거버넌스의 핵심은 세 가지입니다.&lt;/p&gt;
&lt;ol style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;식별&lt;/b&gt;: 누가, 무엇에, 얼마를 쓰는지 알아야 합니다 (LLM Gateway + 태깅).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;통제&lt;/b&gt;: 예산을 설정하고, 초과 전에 대응해야 합니다 (임계값 알림 + 자동 차단/전환).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;최적화&lt;/b&gt;: 같은 비용으로 더 많은 가치를 뽑아야 합니다 (모델 Tiering + 캐싱).&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;비용 거버넌스를 도입한다고 AI 혁신이 느려지는 것은 아닙니다. 오히려 비용 예측이 가능해야 조직이 AI 투자를 지속할 수 있습니다. &quot;이번 달 AI 비용이 얼마나 나올지 모르겠다&quot;는 상태가 지속되면, 경영진은 결국 AI 예산 자체를 줄이게 됩니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 id=&quot;_26&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;관련 글&lt;/h2&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://wero90.tistory.com/13&quot;&gt;RAG란 무엇인가: LLM 애플리케이션 아키텍처 관점에서 이해하기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wero90.tistory.com/entry/AWS-Bedrock-기반-RAG-챗봇-아키텍처-설계-Knowledge-Bases-Agent-보안까지&quot;&gt;AWS Bedrock 기반 RAG 챗봇 아키텍처 설계: Knowledge Bases, Agent, 보안까지&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wero90.tistory.com/entry/OpenTelemetry란-무엇인가-분산-트레이싱-기본-개념&quot;&gt;OpenTelemetry란 무엇인가: 분산 트레이싱 기본 개념&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wero90.tistory.com/entry/알림-설계-노이즈를-줄이고-의미-있는-알림만-받는-방법&quot;&gt;알림 설계: 노이즈를 줄이고 의미 있는 알림만 받는 방법&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>AI</category>
      <category>AI Cost Management</category>
      <category>Budget Alert</category>
      <category>Cost Attribution</category>
      <category>FinOps</category>
      <category>governance</category>
      <category>LLM Gateway</category>
      <category>llmops</category>
      <category>Token Metering</category>
      <author>wero90</author>
      <guid isPermaLink="true">https://wero90.tistory.com/80</guid>
      <comments>https://wero90.tistory.com/80#entry80comment</comments>
      <pubDate>Tue, 9 Jun 2026 18:03:23 +0900</pubDate>
    </item>
    <item>
      <title>GitOps란 무엇인가: Argo CD와 Flux 중심으로</title>
      <link>https://wero90.tistory.com/78</link>
      <description>&lt;blockquote style=&quot;border-left: 4px solid #3b82f6; padding: 1rem 1.2rem; margin: 1.5rem 0; background: #f0f9ff; color: #1e40af; font-size: 1.05rem; line-height: 1.7; border-radius: 0 8px 8px 0;&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Terraform으로 인프라를 코드로 관리하는 건 익숙해졌는데, Kubernetes 배포는 여전히 CI 파이프라인에서 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;kubectl apply&lt;/code&gt;를 실행합니다. 어느 날 누군가 클러스터에 직접 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;kubectl edit&lt;/code&gt;으로 설정을 바꿨고, 다음 배포에서 그 변경이 덮어씌워져 장애가 발생합니다. GitOps는 이 문제를 구조적으로 해결하는 운영 모델입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;_1&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;핵심 요약&lt;/h2&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GitOps는 Git을 Single Source of Truth로 사용하여, 선언적으로 정의된 상태를 클러스터에 지속적으로 동기화하는 운영 모델입니다.&lt;/li&gt;
&lt;li&gt;전통적인 CI/CD의 Push 방식과 달리, GitOps는 Pull 방식으로 동작합니다. 클러스터 내부의 Agent가 Git 상태를 감시하고 자동으로 조정합니다.&lt;/li&gt;
&lt;li&gt;Argo CD는 Web UI와 멀티 클러스터 관리에 강점이 있어 플랫폼 팀에 적합합니다.&lt;/li&gt;
&lt;li&gt;Flux는 컨트롤러 조합 방식의 경량 아키텍처로, Kustomize 기반 워크플로우와 이미지 자동 업데이트에 강점이 있습니다.&lt;/li&gt;
&lt;li&gt;두 도구 모두 CNCF Graduated 프로젝트이며, 선택 기준은 팀 규모, UI 필요 여부, 매니페스트 관리 방식에 따라 달라집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;1-gitops&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;1. 왜 GitOps가 필요한가&lt;/h2&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;팀에서 Kubernetes 기반 서비스를 운영하고 있다고 가정합니다. CI/CD 파이프라인은 GitHub Actions로 구축되어 있고, 배포 단계에서 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;kubectl apply&lt;/code&gt;나 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;helm upgrade&lt;/code&gt;를 실행합니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;이 구조에서 흔히 발생하는 문제들:&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;문제&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;상황&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Configuration Drift&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;운영자가 긴급 대응으로 클러스터에 직접 변경을 적용했지만, Git에는 반영하지 않음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;배포 추적 어려움&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&quot;현재 클러스터에 어떤 버전이 떠 있는지&quot;를 확인하려면 클러스터에 직접 접속해야 함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;롤백 복잡성&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;이전 상태로 돌아가려면 어떤 커밋, 어떤 Helm values가 적용됐는지 추적해야 함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;보안 리스크&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;CI 파이프라인에 클러스터 admin 권한 kubeconfig를 넣어야 배포가 가능함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;멀티 클러스터 일관성&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;dev, staging, prod 환경 간 설정 차이가 누적되어 &quot;staging에서 되는데 prod에서 안 됨&quot; 발생&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;GitOps는 이 문제들을 하나의 원칙으로 해결합니다. Git에 있는 상태가 곧 클러스터의 상태여야 한다.&lt;/p&gt;
&lt;h2 id=&quot;2-gitops-4-opengitops&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;2. GitOps의 4대 원칙 (OpenGitOps)&lt;/h2&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;CNCF OpenGitOps Working Group이 정의한 GitOps의 핵심 원칙은 4가지입니다.&lt;/p&gt;
&lt;figure style=&quot;text-align: center; margin: 2rem 0;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dna/DSpVo/dJMcacpSSS0/AAAAAAAAAAAAAAAAAAAAAFiEKRNbrWgvODg4BuBG_yDoA5extTByMXrVdjQw-4Md/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1782831599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=NxPnAAvwuyjVysHoL5%2F1f%2Bi92ak%3D&quot; alt=&quot;GitOps Reconciliation Loop와 4대 원칙&quot; /&gt;
&lt;figcaption style=&quot;margin-top: 0.5rem; font-size: 0.85rem; color: #64748b;&quot;&gt;GitOps Reconciliation Loop와 4대 원칙&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;원칙&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;설명&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;실무 의미&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;b&gt;Declarative&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;시스템의 원하는 상태를 선언적으로 정의&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Kubernetes YAML, Helm values, Kustomize overlay&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;b&gt;Versioned &amp;amp; Immutable&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;선언된 상태를 버전 관리 시스템에 저장&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Git 커밋 = 배포 이력. 모든 변경이 추적됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;b&gt;Pulled Automatically&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Agent가 원하는 상태를 자동으로 가져옴&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;클러스터 내부에서 Git을 polling하거나 webhook으로 감지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;b&gt;Continuously Reconciled&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Agent가 실제 상태를 원하는 상태와 지속적으로 비교하고 조정&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Drift 자동 감지 + 자동 복구 또는 알림&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;이 4가지 원칙의 조합이 만드는 핵심 가치는 자동 복구(Self-healing)입니다. 누군가 클러스터에서 직접 설정을 변경하더라도, Agent가 Git 상태로 되돌립니다.&lt;/p&gt;
&lt;h2 id=&quot;3-push-vs-pull&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;3. Push 기반 vs Pull 기반 배포&lt;/h2&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;GitOps를 이해하려면 기존 CI/CD와의 구조적 차이를 먼저 파악해야 합니다.&lt;/p&gt;
&lt;figure style=&quot;text-align: center; margin: 2rem 0;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dna/BNXYI/dJMcagsfuMK/AAAAAAAAAAAAAAAAAAAAAGA6UOfhpe4cTwfJdD-d1pGgrRzFOzbR-sQmejqoNi0Z/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1782831599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=5wfpRv4An3kLnNB9rGEkao23kk4%3D&quot; alt=&quot;Push 기반 배포와 Pull 기반 배포(GitOps) 비교&quot; /&gt;
&lt;figcaption style=&quot;margin-top: 0.5rem; font-size: 0.85rem; color: #64748b;&quot;&gt;Push 기반 배포와 Pull 기반 배포(GitOps) 비교&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;push-cicd&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;Push 기반 (전통적 CI/CD)&lt;/h3&gt;
&lt;pre class=&quot;applescript&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# GitHub Actions 배포 단계 예시
- name: Deploy to EKS
  run: |
    aws eks update-kubeconfig --name my-cluster
    kubectl apply -f k8s/
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;CI 파이프라인이 클러스터에 직접 명령을 보냅니다.&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장점: 구현이 단순하고, 기존 파이프라인에 배포 단계만 추가하면 됨&lt;/li&gt;
&lt;li&gt;단점: CI 서버에 클러스터 접근 권한 필요, Drift 감지 불가, 배포 실패 시 상태 불확실&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;pull-gitops&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;Pull 기반 (GitOps)&lt;/h3&gt;
&lt;pre class=&quot;yaml&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# Argo CD Application 정의 예시
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-app
spec:
  source:
    repoURL: https://github.com/team/k8s-manifests
    targetRevision: main
    path: apps/my-app/production
  destination:
    server: https://kubernetes.default.svc
    namespace: production
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;클러스터 내부의 Agent가 Git을 감시하고, 변경이 감지되면 스스로 동기화합니다.&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장점: 클러스터 자격 증명이 외부에 노출되지 않음, Drift 자동 감지/복구, Git 이력이 곧 배포 이력&lt;/li&gt;
&lt;li&gt;단점: 별도 Agent 설치/운영 필요, 초기 학습 곡선 존재&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;_2&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;실무에서의 조합 패턴&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;대부분의 팀은 순수한 Push 또는 Pull 중 하나만 사용하지 않습니다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;CI (Push 영역): 코드 빌드 &amp;rarr; 테스트 &amp;rarr; 이미지 빌드 &amp;rarr; 레지스트리 Push &amp;rarr; 매니페스트 업데이트
CD (Pull 영역): GitOps Agent가 매니페스트 변경 감지 &amp;rarr; 클러스터 동기화
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;CI는 여전히 GitHub Actions/Jenkins가 담당하고, CD 부분만 GitOps 도구가 담당하는 구조가 일반적입니다.&lt;/p&gt;
&lt;h2 id=&quot;4-argo-cd&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;4. Argo CD 아키텍처&lt;/h2&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Argo CD는 2022년 12월 CNCF Graduated 프로젝트가 되었으며, 현재 v3.3이 최신 안정 버전입니다. GitOps 도구 중 가장 높은 채택률을 보이며, CNCF 2025 End User Survey 기준 Kubernetes 클러스터의 약 60%에서 사용되고 있습니다.&lt;/p&gt;
&lt;figure style=&quot;text-align: center; margin: 2rem 0;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dna/beVLux/dJMcaip38v2/AAAAAAAAAAAAAAAAAAAAADgx7bHZEAyt2CQiDRZwW_lQTw4ory-T9ZIyMsSsFgtn/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1782831599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=EhCFYhaYURzM7kqq6SWZa7eDDeU%3D&quot; alt=&quot;Argo CD 아키텍처&quot; /&gt;
&lt;figcaption style=&quot;margin-top: 0.5rem; font-size: 0.85rem; color: #64748b;&quot;&gt;Argo CD 아키텍처&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;_3&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;핵심 컴포넌트&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;컴포넌트&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;역할&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;API Server&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Web UI, CLI, gRPC API를 제공. 사용자 인터페이스의 진입점&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Repo Server&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Git 리포지토리를 클론하고, Helm/Kustomize/Jsonnet 등을 렌더링하여 최종 매니페스트 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Application Controller&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;실제 클러스터 상태를 원하는 상태와 비교하고, Sync를 실행하는 핵심 컨트롤러&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Redis&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;캐시 레이어. 매니페스트 캐싱, 세션 관리 등에 활용&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;argo-cd&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;Argo CD의 강점&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. Web UI / 토폴로지 뷰&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Argo CD의 가장 눈에 띄는 차별점은 Web UI입니다. Application 단위로 리소스 트리를 시각화하고, 각 리소스의 상태(Healthy/Degraded/Progressing)를 실시간으로 확인할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;운영 중 장애 대응 시 &quot;어떤 리소스가 문제인지&quot;를 빠르게 파악할 수 있어, 특히 온콜 엔지니어에게 유용합니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 멀티 클러스터 중앙 관리&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;하나의 Argo CD 인스턴스에서 여러 클러스터를 관리할 수 있습니다. 관리 클러스터에 Argo CD를 설치하고, 대상 클러스터를 등록하는 방식입니다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# 외부 클러스터 등록
argocd cluster add my-prod-cluster --kubeconfig ~/.kube/prod-config
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. ApplicationSet으로 대규모 배포 관리&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;수십 개의 Application을 개별 정의하는 대신, 템플릿 기반으로 대량 생성할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;less&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: my-app-set
spec:
  generators:
    - clusters:
        selector:
          matchLabels:
            env: production
  template:
    metadata:
      name: &quot;my-app-#&quot;
    spec:
      source:
        repoURL: https://github.com/team/k8s-manifests
        path: &quot;apps/my-app/{{metadata.labels.region}}&quot;
      destination:
        server: &quot;#&quot;
        namespace: production
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. Sync Waves &amp;amp; Hooks&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;배포 순서를 제어할 수 있습니다. 예를 들어 &quot;DB 마이그레이션 &amp;rarr; 앱 배포 &amp;rarr; 테스트 실행&quot; 순서를 보장합니다.&lt;/p&gt;
&lt;h3 id=&quot;argo-cd_1&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;Argo CD의 운영 고려사항&lt;/h3&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;리소스 사용량&lt;/b&gt;: API Server + Repo Server + Application Controller + Redis로 구성되어, 소규모 클러스터에서도 기본적으로 상당한 리소스를 소비합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Repo Server 병목&lt;/b&gt;: 관리하는 Application 수가 많아지면 Repo Server의 Git 클론과 매니페스트 렌더링이 병목이 될 수 있습니다. sharding이나 수평 확장이 필요할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;RBAC 설계&lt;/b&gt;: Argo CD 자체의 RBAC(프로젝트 기반)과 Kubernetes RBAC을 별도로 관리해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;5-flux&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;5. Flux 아키텍처&lt;/h2&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Flux는 2022년 11월 CNCF Graduated 프로젝트가 되었으며, 현재 v2.8이 최신 안정 버전입니다. Argo CD와 달리 단일 모놀리식 서비스가 아니라, 독립적인 컨트롤러의 조합으로 구성됩니다.&lt;/p&gt;
&lt;figure style=&quot;text-align: center; margin: 2rem 0;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dna/kYbX4/dJMcagsfuMW/AAAAAAAAAAAAAAAAAAAAAL6-jN63yYT7et9MBfhQNxAwqZyJonswjZEqeVDIitv9/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1782831599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=ikk04eSksHBzZ%2BioXIXi%2FVYG17s%3D&quot; alt=&quot;Flux 아키텍처&quot; /&gt;
&lt;figcaption style=&quot;margin-top: 0.5rem; font-size: 0.85rem; color: #64748b;&quot;&gt;Flux 아키텍처&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;_4&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;핵심 컨트롤러&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;컨트롤러&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;역할&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Source Controller&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Git, Helm, OCI 등 소스 리포지토리를 관리하고, 아티팩트를 다운로드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Kustomize Controller&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Kustomize 빌드를 실행하고 결과를 클러스터에 적용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Helm Controller&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;HelmRelease CR을 관리하며 Helm 차트를 설치/업그레이드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Notification Controller&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;이벤트를 외부 시스템(Slack, Teams, webhook)으로 전달&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Image Automation Controller&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;컨테이너 레지스트리에서 새 이미지 태그를 감지하고, Git에 자동 커밋&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;flux&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;Flux의 강점&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 경량 아키텍처&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;필요한 컨트롤러만 선택적으로 설치할 수 있습니다. Helm을 사용하지 않는 팀은 Helm Controller를 설치하지 않아도 됩니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# 기본 설치 (Source + Kustomize + Notification)
flux bootstrap github \
  --owner=my-org \
  --repository=fleet-infra \
  --branch=main \
  --path=clusters/production
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 네이티브 이미지 자동 업데이트&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;CI에서 새 이미지를 빌드하면, Flux가 자동으로 감지하여 Git 리포지토리의 매니페스트를 업데이트합니다.&lt;/p&gt;
&lt;pre class=&quot;dts&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImagePolicy
metadata:
  name: my-app
spec:
  imageRepositoryRef:
    name: my-app
  policy:
    semver:
      range: &quot;&amp;gt;=1.0.0&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;이 기능은 &quot;CI가 이미지를 빌드하면, Flux가 Git에 커밋하고, 다시 Flux가 그 커밋을 감지해서 배포&quot;하는 완전 자동화 루프를 만듭니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. Kustomize 네이티브 통합&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Flux는 Kustomize를 1등 시민(first-class citizen)으로 지원합니다. 복잡한 오버레이 구조를 그대로 활용할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: my-app
  namespace: flux-system
spec:
  interval: 10m
  sourceRef:
    kind: GitRepository
    name: fleet-infra
  path: ./apps/production
  prune: true
  healthChecks:
    - apiVersion: apps/v1
      kind: Deployment
      name: my-app
      namespace: production
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. 멀티 테넌시 설계&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Flux는 각 대상 클러스터에 직접 설치되는 구조입니다. 팀별로 별도의 GitRepository와 Kustomization을 정의하여 테넌트 격리가 자연스럽습니다.&lt;/p&gt;
&lt;h3 id=&quot;flux_1&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;Flux의 운영 고려사항&lt;/h3&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;UI 부재&lt;/b&gt;: 네이티브 Web UI가 없습니다. 상태 확인은 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;flux get&lt;/code&gt; CLI 또는 Grafana 대시보드로 해야 합니다. Weave GitOps(현 Flux Subsystem for Argo)를 추가 설치하면 UI를 사용할 수 있지만 별도 구성이 필요합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;디버깅 복잡도&lt;/b&gt;: 여러 컨트롤러가 독립적으로 동작하므로, 문제 발생 시 어떤 컨트롤러에서 이슈가 발생했는지 파악해야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;멀티 클러스터 관리&lt;/b&gt;: Argo CD처럼 중앙 집중식이 아니라, 각 클러스터에 Flux를 설치해야 합니다. 관리 오버헤드가 클러스터 수에 비례합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;6-argo-cd-vs-flux&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;6. Argo CD vs Flux: 선택 기준&lt;/h2&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;기준&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;Argo CD&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;Flux&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;b&gt;아키텍처&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;모놀리식 (API Server + Controller + Repo Server)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;마이크로서비스 (독립 컨트롤러 조합)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;b&gt;UI&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;내장 Web UI (토폴로지 뷰, 실시간 상태)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;없음 (CLI + Grafana 또는 별도 UI 설치)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;b&gt;멀티 클러스터&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;중앙 집중식 (한 곳에서 여러 클러스터 관리)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;분산형 (각 클러스터에 설치)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;b&gt;매니페스트 렌더링&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Helm, Kustomize, Jsonnet, 커스텀 플러그인&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Helm, Kustomize (Jsonnet 미지원)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;b&gt;이미지 자동 업데이트&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;별도 프로젝트(Argo CD Image Updater)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;네이티브 지원 (Image Automation Controller)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;b&gt;대규모 배포&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;ApplicationSet으로 템플릿 기반 대량 생성&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Kustomization 계층 구조로 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;b&gt;CNCF 상태&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Graduated (2022.12)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Graduated (2022.11)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;b&gt;리소스 소비&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;상대적으로 높음 (Redis, Repo Server 등)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;상대적으로 낮음 (필요한 컨트롤러만 설치)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;b&gt;학습 곡선&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;UI가 있어 초기 진입이 쉬움&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;CLI 기반으로 Kubernetes 개념에 익숙해야 함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;b&gt;커뮤니티 규모&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;더 큼 (GitHub Stars 18k+, 채택률 약 60%)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;상대적으로 작음 (채택률 약 11%)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;argo-cd_2&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;이런 상황이면 Argo CD&lt;/h3&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;팀에 Kubernetes 경험이 적은 멤버가 있어 UI 기반 관리가 필요할 때&lt;/li&gt;
&lt;li&gt;하나의 플랫폼 팀이 여러 클러스터를 중앙에서 관리해야 할 때&lt;/li&gt;
&lt;li&gt;배포 상태를 비개발자(PM, QA)에게도 가시적으로 보여줘야 할 때&lt;/li&gt;
&lt;li&gt;ApplicationSet으로 수십 개 서비스를 템플릿화해야 할 때&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;flux_2&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;이런 상황이면 Flux&lt;/h3&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Kustomize 기반 매니페스트 관리를 이미 하고 있을 때&lt;/li&gt;
&lt;li&gt;이미지 태그 변경 시 자동으로 Git 커밋 &amp;rarr; 배포까지 완전 자동화하고 싶을 때&lt;/li&gt;
&lt;li&gt;클러스터별 독립적인 GitOps 운영이 필요할 때 (팀 자율성 우선)&lt;/li&gt;
&lt;li&gt;리소스 소비를 최소화하고 싶은 소규모 클러스터일 때&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;vs&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;실무 시나리오: 스타트업 vs 엔터프라이즈&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;스타트업 (클러스터 2~3개, 엔지니어 5명)&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Argo CD를 하나의 관리 클러스터에 설치하고, dev/staging/prod 클러스터를 등록합니다. 전체 팀이 Web UI에서 배포 상태를 확인하고, PR 머지만으로 배포가 진행됩니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;이 규모에서 Flux를 선택하면 각 클러스터에 별도 설치해야 하므로 관리 포인트가 늘어납니다. 다만 리소스가 제한적이라면 Flux의 경량 구조가 유리할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;엔터프라이즈 (클러스터 50개+, 팀 20개+)&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;각 팀이 자체 클러스터와 GitOps 리포지토리를 관리합니다. 플랫폼 팀은 Flux bootstrap 구성을 표준화하고, 각 팀이 자율적으로 배포합니다. 중앙 집중식 Argo CD가 수백 개 Application을 관리하면 Repo Server와 Application Controller에 상당한 부하가 발생할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;다만 엔터프라이즈에서도 Argo CD를 선택하는 경우가 많습니다. 이 경우 Argo CD를 팀별로 분리(sharding) 하거나, Application Controller를 수평 확장하는 전략을 적용합니다.&lt;/p&gt;
&lt;h2 id=&quot;7-gitops&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;7. GitOps 도입 시 고려할 점&lt;/h2&gt;
&lt;h3 id=&quot;git&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;Git 리포지토리 구조 설계&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;GitOps를 도입할 때 가장 먼저 결정해야 하는 것은 리포지토리 구조입니다.&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;패턴&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;구조&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;장점&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;단점&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Monorepo&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;앱 코드 + 매니페스트를 한 리포에&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;변경 추적이 단순&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;권한 분리 어려움&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Polyrepo (App + Config 분리)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;앱 리포 + 배포 매니페스트 리포 분리&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;명확한 관심사 분리&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;이미지 태그 업데이트 자동화 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Multi-tenant&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;환경/팀별 디렉토리 분리&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;멀티 테넌시 지원&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;디렉토리 구조가 복잡해질 수 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;실무에서는 App 리포와 Config 리포를 분리하는 패턴이 보편적입니다. CI는 App 리포에서 이미지를 빌드하고, Config 리포의 이미지 태그를 업데이트합니다.&lt;/p&gt;
&lt;h3 id=&quot;secret&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;Secret 관리&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Git에 Secret을 평문으로 저장할 수 없으므로, 별도 전략이 필요합니다.&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;도구&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;방식&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;연동&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Sealed Secrets&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;암호화된 Secret을 Git에 저장, 클러스터에서 복호화&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Argo CD / Flux 모두 지원&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;External Secrets Operator&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;AWS Secrets Manager/Vault 등 외부 저장소 참조&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Argo CD / Flux 모두 지원&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;SOPS&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;파일 단위 암호화, KMS/PGP 기반&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Flux 네이티브 지원, Argo CD는 플러그인&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Flux는 SOPS(Mozilla)를 네이티브로 지원합니다. Argo CD를 사용한다면 External Secrets Operator 또는 Sealed Secrets를 조합하는 것이 일반적입니다.&lt;/p&gt;
&lt;h3 id=&quot;drift&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;Drift 감지 정책&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;GitOps Agent가 Drift를 감지했을 때 어떻게 대응할지 정책을 결정해야 합니다.&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Auto-sync (자동 복구)&lt;/b&gt;: Drift 발생 시 즉시 Git 상태로 되돌림. 운영 안정성이 높지만, 긴급 대응 시 불편할 수 있음.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Manual sync (알림만)&lt;/b&gt;: Drift 감지 시 알림만 보내고, 수동으로 Sync 실행. 유연하지만 Drift가 방치될 수 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;운영 환경에서는 Auto-sync를 기본으로 하되, 특정 리소스(ConfigMap, HPA 등)는 제외하는 방식이 일반적입니다.&lt;/p&gt;
&lt;h2 id=&quot;8&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;8. 관련 글&lt;/h2&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://wero90.tistory.com/10&quot;&gt;CI/CD 파이프라인 기본 구조&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wero90.tistory.com/entry/GitHub-Actions와-Jenkins-차이-CICD-도구-선택-기준&quot;&gt;GitHub Actions와 Jenkins 차이&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wero90.tistory.com/entry/Blue-Green-Deployment와-Rolling-Update-차이-배포-전략-선택-기준&quot;&gt;Blue-Green Deployment와 Rolling Update 차이&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wero90.tistory.com/entry/Kubernetes-아키텍처-기본-구조-Control-Plane과-Worker-Node&quot;&gt;Kubernetes 아키텍처 기본 구조&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wero90.tistory.com/entry/DevSecOps란-무엇인가-CICD에-보안을-통합하는-방법&quot;&gt;DevSecOps란 무엇인가&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 id=&quot;_5&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;GitOps는 &quot;배포를 어떻게 할 것인가&quot;보다 &quot;클러스터의 상태를 어떻게 관리할 것인가&quot;에 초점을 맞춘 운영 모델입니다. Git을 중심으로 변경 이력 추적, 코드 리뷰 기반 배포, 자동 Drift 감지/복구가 가능해지며, 이는 특히 Kubernetes 환경에서 운영 안정성과 보안 수준을 높입니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Argo CD와 Flux 모두 성숙한 도구입니다. &quot;어떤 것이 더 좋은가&quot;보다 팀의 워크플로우, 기존 매니페스트 관리 방식, 멀티 클러스터 전략에 맞는 도구를 선택하는 것이 중요합니다.&lt;/p&gt;</description>
      <category>DevOps</category>
      <category>argo cd</category>
      <category>CI/CD</category>
      <category>CNCF</category>
      <category>devops</category>
      <category>Flux</category>
      <category>gitops</category>
      <category>kubernetes</category>
      <author>wero90</author>
      <guid isPermaLink="true">https://wero90.tistory.com/78</guid>
      <comments>https://wero90.tistory.com/78#entry78comment</comments>
      <pubDate>Mon, 8 Jun 2026 16:18:52 +0900</pubDate>
    </item>
    <item>
      <title>Kubernetes OOMKilled 원인 분석과 메모리 설계</title>
      <link>https://wero90.tistory.com/77</link>
      <description>&lt;blockquote style=&quot;border-left: 4px solid #3b82f6; padding: 1rem 1.2rem; margin: 1.5rem 0; background: #f0f9ff; color: #1e40af; font-size: 1.05rem; line-height: 1.7; border-radius: 0 8px 8px 0;&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Deployment를 배포한 뒤 한동안 잘 동작하던 Pod가 갑자기 재시작됩니다. &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;kubectl describe pod&lt;/code&gt;를 확인하면 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;Reason: OOMKilled&lt;/code&gt;, &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;Exit Code: 137&lt;/code&gt;이 보입니다. 메모리 limit을 올리면 잠시 괜찮다가 며칠 뒤 또 같은 증상이 반복됩니다. OOMKilled는 단순히 limit을 올린다고 해결되는 문제가 아닙니다. 왜 메모리가 초과했는지, 어느 수준에서 kill이 발생했는지, 어떤 기준으로 requests와 limits를 설계해야 하는지를 이해해야 재발을 방지할 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;_1&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;핵심 요약&lt;/h2&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;구분&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;Container OOMKilled&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;Node 수준 Eviction&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;트리거&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;컨테이너가 memory limit 초과&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Node 가용 메모리가 eviction threshold 이하&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Exit Code&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;137 (SIGKILL)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;137 (SIGKILL)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Reason&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;OOMKilled&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;Evicted&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;대상&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;해당 컨테이너만 종료&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;QoS 등급 낮은 Pod부터 축출&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;확인 방법&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;kubectl describe pod&lt;/code&gt; &amp;rarr; Last State&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;kubectl describe node&lt;/code&gt; &amp;rarr; Conditions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;해결 방향&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;limit 조정 또는 앱 메모리 최적화&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;requests 설정, Node 용량 확보&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;1-oomkilled&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;1. OOMKilled 동작 원리&lt;/h2&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;OOMKilled는 두 가지 경로로 발생합니다. 하나는 &lt;b&gt;컨테이너의 cgroup memory limit 초과&lt;/b&gt;, 다른 하나는 &lt;b&gt;Node 전체 메모리 부족으로 인한 kubelet eviction&lt;/b&gt;입니다. 둘 다 Exit Code 137로 표시되지만, 원인과 대응 방법이 다릅니다.&lt;/p&gt;
&lt;figure style=&quot;text-align: center; margin: 2rem 0;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dna/bdEYLJ/dJMcabkd5Wx/AAAAAAAAAAAAAAAAAAAAAPgtOAOKnTUVqjVkpYU2BZDfCxirqRCb1QxSh9hgYS3B/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1782831599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=Bk%2BXAz4%2FtM%2BHTZzJVzk3a%2B9IWGw%3D&quot; alt=&quot;OOMKilled 발생 경로&quot; /&gt;
&lt;figcaption style=&quot;margin-top: 0.5rem; font-size: 0.85rem; color: #64748b;&quot;&gt;OOMKilled 발생 경로&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;1-1-container-oomkilled-cgroup&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;1-1. Container OOMKilled (cgroup 기반)&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Kubernetes는 컨테이너의 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;resources.limits.memory&lt;/code&gt;를 Linux cgroup의 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;memory.max&lt;/code&gt;(cgroup v2) 또는 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;memory.limit_in_bytes&lt;/code&gt;(cgroup v1)로 설정합니다. 컨테이너 내 프로세스의 메모리 사용량(RSS + page cache 일부)이 이 값을 초과하면, 커널의 OOM Killer가 해당 cgroup 내 프로세스를 SIGKILL(signal 9)로 종료합니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핵심 포인트:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Exit Code 137 = 128 + 9 (SIGKILL)&lt;/li&gt;
&lt;li&gt;컨테이너 단위로 격리되어, 같은 Pod의 다른 컨테이너에는 영향 없음&lt;/li&gt;
&lt;li&gt;limit이 설정되지 않은 컨테이너는 Node의 전체 메모리를 사용할 수 있어서, 다른 Pod에 영향을 줄 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;1-2-node-eviction&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;1-2. Node 수준 Eviction&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;kubelet은 Node의 가용 메모리를 주기적으로 확인합니다. &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;memory.available&lt;/code&gt;이 eviction threshold(기본: 100Mi) 아래로 내려가면, QoS 등급이 낮은 Pod부터 축출(evict)합니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;QoS 등급별 축출 우선순위:&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;QoS Class&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;조건&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;축출 우선순위&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;BestEffort&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;requests/limits 모두 미설정&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;가장 먼저 축출&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Burstable&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;requests &amp;lt; limits 또는 일부만 설정&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;두 번째&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Guaranteed&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;requests = limits (모든 컨테이너)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;가장 마지막&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;운영 환경에서는 모든 컨테이너에 최소 requests를 설정하여 BestEffort 등급을 피해야 합니다. BestEffort Pod는 Node에 메모리 압박이 오면 가장 먼저 종료됩니다.&lt;/p&gt;
&lt;h2 id=&quot;2-oomkilled&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;2. OOMKilled 진단 절차&lt;/h2&gt;
&lt;h3 id=&quot;2-1-pod-oomkilled&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;2-1. Pod 상태에서 OOMKilled 확인&lt;/h3&gt;
&lt;pre class=&quot;dts&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# 1. Pod 상태 확인
kubectl describe pod &amp;lt;pod-name&amp;gt; -n &amp;lt;namespace&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;출력에서 확인할 부분:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;State:          Waiting
  Reason:       CrashLoopBackOff
Last State:     Terminated
  Reason:       OOMKilled
  Exit Code:    137
  Started:      Mon, 08 Jun 2026 09:15:00 +0900
  Finished:     Mon, 08 Jun 2026 11:42:33 +0900
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;Reason: OOMKilled&lt;/code&gt;가 명시되면 container limit 초과입니다. &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;Reason: Evicted&lt;/code&gt;면 Node 수준 메모리 부족입니다.&lt;/p&gt;
&lt;h3 id=&quot;2-2-container-oomkilled-vs-node-eviction&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;2-2. Container OOMKilled vs Node Eviction 구분&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# Container OOMKilled인 경우 &amp;mdash; Pod는 같은 Node에서 재시작
kubectl get pod &amp;lt;pod-name&amp;gt; -n &amp;lt;namespace&amp;gt; -o wide
# RESTARTS 증가, NODE 동일

# Node Eviction인 경우 &amp;mdash; Pod가 다른 Node로 이동
kubectl get events -n &amp;lt;namespace&amp;gt; --sort-by='.lastTimestamp' | grep -i evict
# &quot;The node was low on resource: memory&quot; 메시지 확인
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;2-3-limit&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;2-3. 현재 메모리 사용량과 limit 비교&lt;/h3&gt;
&lt;pre class=&quot;applescript&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# 실시간 메모리 사용량 (metrics-server 필요)
kubectl top pod &amp;lt;pod-name&amp;gt; -n &amp;lt;namespace&amp;gt; --containers

# 설정된 limit 확인
kubectl get pod &amp;lt;pod-name&amp;gt; -n &amp;lt;namespace&amp;gt; \
  -o jsonpath='{range .spec.containers[*]}{.name}{&quot;\t&quot;}{.resources.limits.memory}{&quot;\n&quot;}{end}'
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;출력 예시:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;lisp&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;CONTAINER    MEMORY(bytes)
my-app       480Mi

my-app    512Mi    (&amp;larr; limit)
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;사용량이 limit에 근접하면 OOMKilled 위험이 높습니다. &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;kubectl top&lt;/code&gt;은 특정 시점의 스냅샷이므로, 피크 시점을 포착하려면 모니터링 도구(Prometheus 등)에서 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;container_memory_working_set_bytes&lt;/code&gt; 메트릭을 확인하는 것이 정확합니다.&lt;/p&gt;
&lt;h3 id=&quot;2-4-node&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;2-4. Node 수준 메모리 상태 확인&lt;/h3&gt;
&lt;pre class=&quot;crmsh&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# Node 리소스 상태
kubectl describe node &amp;lt;node-name&amp;gt; | grep -A 5 &quot;Conditions&quot;
# MemoryPressure가 True이면 Node 수준 메모리 부족

# Node에서 실행 중인 Pod의 메모리 requests 합계
kubectl describe node &amp;lt;node-name&amp;gt; | grep -A 20 &quot;Allocated resources&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;출력 예시:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;Conditions:
  Type               Status
  MemoryPressure     False
  DiskPressure       False
  PIDPressure        False
  Ready              True

Allocated resources:
  Resource           Requests    Limits
  memory             3200Mi (82%)  5120Mi (131%)
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Limits 합계가 Node 용량을 초과(overcommit)하는 것은 허용되지만, 동시에 모든 Pod가 limit까지 사용하면 OOM이 발생합니다.&lt;/p&gt;
&lt;figure style=&quot;text-align: center; margin: 2rem 0;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dna/ciXe2S/dJMcabkd5WK/AAAAAAAAAAAAAAAAAAAAANL453YWMlqxegDhikAl1T16lK9ZTLMqO4bBoFeVGh56/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1782831599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=8AVDJtB61D1tDLHiMYLp9V5lnP8%3D&quot; alt=&quot;OOMKilled 진단 흐름&quot; /&gt;
&lt;figcaption style=&quot;margin-top: 0.5rem; font-size: 0.85rem; color: #64748b;&quot;&gt;OOMKilled 진단 흐름&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;3&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;3. 원인별 분석&lt;/h2&gt;
&lt;h3 id=&quot;3-1-memory-limit&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;3-1. Memory Limit이 실제 사용량보다 낮게 설정된 경우&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;가장 단순한 원인입니다. 애플리케이션의 정상 동작에 필요한 메모리보다 limit이 낮게 설정되어 있습니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실무 시나리오:&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Java Spring Boot 애플리케이션을 배포합니다. 개발 환경에서 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;kubectl top&lt;/code&gt;으로 확인한 메모리가 300Mi 정도여서 limit을 512Mi로 설정했습니다. 하지만 운영 환경에서는 동시 요청이 많아지면 Thread Pool, Connection Pool, HTTP 요청 버퍼 등이 추가로 메모리를 소비하여 512Mi를 초과합니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;진단:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;vala&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# Prometheus 쿼리로 메모리 피크 확인
# container_memory_working_set_bytes{pod=&quot;my-app-xxx&quot;, container=&quot;my-app&quot;}
# 또는 kubectl top으로 부하 시점에 확인

kubectl top pod -n production --containers | grep my-app
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해결:&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;부하 테스트를 수행한 뒤, P99 메모리 사용량을 기준으로 limit을 설정합니다.&lt;/p&gt;
&lt;pre class=&quot;dts&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;spec:
  containers:
    - name: my-app
      resources:
        requests:
          memory: &quot;512Mi&quot;    # P50 사용량 기준
        limits:
          memory: &quot;1Gi&quot;      # P99 사용량 + 20% 여유
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;3-2-memory-leak&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;3-2. 메모리 누수 (Memory Leak)&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;시간이 지날수록 메모리 사용량이 계속 증가하여 결국 limit에 도달하는 패턴입니다. limit을 올려도 시간이 더 걸릴 뿐 결국 OOMKilled가 발생합니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실무 시나리오:&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Node.js Express 서버에서 요청마다 캐시 객체에 데이터를 저장하는데, 만료 정책 없이 무한히 쌓입니다. 배포 직후에는 메모리가 200Mi 수준이지만, 며칠 운영 후 limit(1Gi)에 도달하여 OOMKilled가 됩니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;메모리 누수를 의심할 수 있는 패턴:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;배포 직후에는 정상이지만, 일정 시간(수 시간~수 일) 후 OOMKilled 발생&lt;/li&gt;
&lt;li&gt;limit을 올려도 더 오래 버틸 뿐 결국 같은 증상 반복&lt;/li&gt;
&lt;li&gt;Prometheus 그래프에서 메모리가 계속 우상향 (톱니 패턴 없이)&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;진단:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# 시간별 메모리 추이 확인 (Prometheus/Grafana)
# rate(container_memory_working_set_bytes{pod=~&quot;my-app.*&quot;}[1h])

# Pod가 시작된 지 얼마나 됐는지 확인
kubectl get pod &amp;lt;pod-name&amp;gt; -n &amp;lt;namespace&amp;gt; \
  -o jsonpath='{.status.startTime}'
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해결 방향:&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;앱 코드에서 메모리 누수 원인을 찾아 수정 (근본 해결)&lt;/li&gt;
&lt;li&gt;임시 방편: 정기적으로 Pod를 재시작 (롤링 리스타트 CronJob)&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;vala&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# 임시 방편 &amp;mdash; 매일 새벽 3시에 롤링 리스타트
# CronJob으로 kubectl rollout restart를 수행
kubectl rollout restart deployment/my-app -n production
&lt;/code&gt;&lt;/pre&gt;
&lt;div style=&quot;padding: 1rem 1.2rem; margin: 1.5rem 0; border-radius: 8px; border-left: 4px solid #f59e0b; background: #fffbeb; line-height: 1.7;&quot;&gt;&lt;b&gt;주의&lt;/b&gt;&lt;br /&gt;정기 재시작은 메모리 누수의 근본 해결이 아닙니다. 누수 원인을 찾는 동안 서비스 안정성을 유지하기 위한 임시 방편입니다. 장기적으로는 프로파일링 도구(Java: JFR/VisualVM, Node.js: &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;--inspect&lt;/code&gt;, Go: pprof)로 누수 지점을 식별하고 코드를 수정해야 합니다.&lt;/div&gt;
&lt;h3 id=&quot;3-3-jvm-limit&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;3-3. JVM 메모리 구조와 컨테이너 limit 불일치&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Java 애플리케이션에서 가장 흔한 OOMKilled 원인입니다. JVM의 전체 메모리 사용량은 Heap만이 아닙니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;JVM 메모리 구성:&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;영역&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;설명&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;크기 예시&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Heap (-Xmx)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;객체 저장&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;512Mi&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Metaspace&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;클래스 메타데이터&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;100~200Mi&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Thread Stack&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;스레드당 1Mi (기본)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;200 threads &amp;times; 1Mi = 200Mi&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Native Memory&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;JNI, NIO DirectBuffer&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;50~200Mi&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Code Cache&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;JIT 컴파일 코드&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;50~240Mi&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;GC 오버헤드&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;GC 알고리즘 내부 구조&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;가변&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실무 시나리오:&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Container limit을 1Gi로 설정하고, &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;-Xmx512m&lt;/code&gt;을 지정했습니다. Heap은 512Mi지만 Metaspace(150Mi) + Thread Stack(200개 스레드 &amp;times; 1Mi) + Native(100Mi)을 합치면 약 960Mi~1Gi에 달합니다. 트래픽이 몰려 스레드가 더 생성되면 limit을 초과합니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;권장 설정:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;less&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;spec:
  containers:
    - name: java-app
      resources:
        requests:
          memory: &quot;768Mi&quot;
        limits:
          memory: &quot;1Gi&quot;
      env:
        - name: JAVA_OPTS
          value: &quot;-Xms256m -Xmx512m -XX:MaxMetaspaceSize=150m -XX:ReservedCodeCacheSize=64m&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;JVM과 Container limit 관계 설계 공식:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;Container limit &amp;ge; Xmx + MaxMetaspaceSize + (Thread수 &amp;times; ThreadStackSize) + Native + 여유(100~200Mi)
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;일반적으로 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;-Xmx&lt;/code&gt;를 container limit의 50~70%로 설정하면 Non-Heap 영역을 위한 여유가 확보됩니다.&lt;/p&gt;
&lt;div style=&quot;padding: 1rem 1.2rem; margin: 1.5rem 0; border-radius: 8px; border-left: 4px solid #3b82f6; background: #eff6ff; line-height: 1.7;&quot;&gt;&lt;b&gt;Tip&lt;/b&gt;&lt;br /&gt;Java 17+ 에서는 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;-XX:MaxRAMPercentage=70.0&lt;/code&gt; 옵션으로 컨테이너 메모리 limit의 70%를 자동으로 Heap에 할당할 수 있습니다. &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;-Xmx&lt;/code&gt;를 직접 계산할 필요가 줄어듭니다. 다만 이 옵션은 cgroup limit을 기준으로 동작하므로, limit이 설정되어 있어야 합니다.&lt;/div&gt;
&lt;h3 id=&quot;3-4-nodejs-go&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;3-4. Node.js / Go 런타임 메모리 설정&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Node.js:&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;V8 엔진의 기본 Heap 크기는 약 1.5~2GB(64비트 시스템)입니다. Container limit이 512Mi인데 V8이 기본 설정으로 동작하면 limit을 초과할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;less&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;spec:
  containers:
    - name: node-app
      resources:
        limits:
          memory: &quot;512Mi&quot;
      env:
        - name: NODE_OPTIONS
          value: &quot;--max-old-space-size=384&quot;  # limit의 75% (MiB 단위)
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Go:&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Go 런타임은 기본적으로 가용 메모리의 일정 비율을 GC 트리거 기준으로 사용합니다. Go 1.19+에서는 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;GOMEMLIMIT&lt;/code&gt; 환경변수로 메모리 사용 상한을 설정할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;less&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;spec:
  containers:
    - name: go-app
      resources:
        limits:
          memory: &quot;256Mi&quot;
      env:
        - name: GOMEMLIMIT
          value: &quot;200MiB&quot;   # limit의 약 80%
        - name: GOGC
          value: &quot;100&quot;      # 기본값 유지, 필요시 50으로 낮춰 GC 빈도 증가
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;3-5&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;3-5. 사이드카 컨테이너 메모리 미포함&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Pod에 사이드카(Istio Envoy, Fluentd, CloudSQL Proxy 등)가 있을 때, 메인 컨테이너만 고려하고 사이드카의 메모리를 빠뜨리는 경우입니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실무 시나리오:&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Istio Service Mesh를 사용하는 클러스터에서, 메인 앱 컨테이너 limit을 512Mi로 설정합니다. Istio가 자동 주입하는 Envoy 사이드카(istio-proxy)는 기본적으로 requests 128Mi, limit 미설정입니다. 트래픽이 많아지면 Envoy가 200~300Mi를 사용하면서 Node 메모리를 압박합니다. Node Eviction으로 다른 Pod가 축출되는 원인이 됩니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;확인 방법:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;applescript&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# Pod의 모든 컨테이너 리소스 확인
kubectl get pod &amp;lt;pod-name&amp;gt; -n &amp;lt;namespace&amp;gt; -o json | \
  jq '.spec.containers[] | {name, resources}'

# 모든 컨테이너의 실제 사용량 확인
kubectl top pod &amp;lt;pod-name&amp;gt; -n &amp;lt;namespace&amp;gt; --containers
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해결:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;dts&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# Istio 사이드카 리소스 설정 (Pod annotation)
metadata:
  annotations:
    sidecar.istio.io/proxyMemoryLimit: &quot;256Mi&quot;
    sidecar.istio.io/proxyMemory: &quot;128Mi&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;4-requests-limits&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;4. requests와 limits 설계 전략&lt;/h2&gt;
&lt;h3 id=&quot;4-1&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;4-1. 설계 원칙&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;항목&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;requests&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;limits&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;목적&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;스케줄링 기준 (Node에 공간 예약)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;런타임 상한 (초과 시 OOMKill)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;기준값&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;P50~P75 메모리 사용량&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;P99 + 20~30% 여유&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;미설정 시&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;스케줄러가 0으로 간주 (어디든 배치)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Node 전체 메모리 사용 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;QoS 영향&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Guaranteed: requests = limits&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;BestEffort: 둘 다 미설정&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;4-2&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;4-2. 측정 기반 설정 절차&lt;/h3&gt;
&lt;ol style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;부하 테스트 실행&lt;/b&gt;: 운영과 유사한 트래픽 패턴으로 부하를 생성합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;메트릭 수집&lt;/b&gt;: &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;container_memory_working_set_bytes&lt;/code&gt;를 15~30분간 관찰합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;백분위 계산&lt;/b&gt;: P50(requests 후보), P99(limits 후보)를 도출합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;여유분 추가&lt;/b&gt;: limits = P99 &amp;times; 1.2~1.3 (버스트 대응)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;검증&lt;/b&gt;: 설정 후 1~2주 운영하면서 OOMKilled 재발 여부 확인&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;routeros&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# Prometheus 쿼리 &amp;mdash; P99 메모리 사용량 (최근 7일)
quantile_over_time(0.99, 
  container_memory_working_set_bytes{
    namespace=&quot;production&quot;, 
    pod=~&quot;my-app.*&quot;, 
    container=&quot;my-app&quot;
  }[7d]
)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;4-3-requests-limits&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;4-3. requests = limits로 설정할지 여부&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Guaranteed QoS (requests = limits):&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장점: OOM 시 가장 마지막에 축출. 성능 예측 가능.&lt;/li&gt;
&lt;li&gt;단점: 메모리 효율이 낮음 (사용하지 않는 메모리도 예약됨).&lt;/li&gt;
&lt;li&gt;적합한 경우: 미션 크리티컬 서비스, DB, 메시지 큐 등&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Burstable QoS (requests &amp;lt; limits):&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장점: 평상시 메모리 효율 높음. Node에 더 많은 Pod 배치 가능.&lt;/li&gt;
&lt;li&gt;단점: 메모리 경합 시 축출 대상이 될 수 있음.&lt;/li&gt;
&lt;li&gt;적합한 경우: 일반 웹 서비스, 배치 작업, 트래픽 변동이 큰 서비스&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실무 권장:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;dts&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# 미션 크리티컬 서비스 &amp;mdash; Guaranteed
resources:
  requests:
    memory: &quot;1Gi&quot;
  limits:
    memory: &quot;1Gi&quot;

# 일반 웹 서비스 &amp;mdash; Burstable
resources:
  requests:
    memory: &quot;512Mi&quot;    # P50 사용량
  limits:
    memory: &quot;1Gi&quot;      # P99 + 여유
&lt;/code&gt;&lt;/pre&gt;
&lt;figure style=&quot;text-align: center; margin: 2rem 0;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dna/bn4ziL/dJMcaaS7ljd/AAAAAAAAAAAAAAAAAAAAAF4LM2KX4mc6Ust-guLqtRU8LuV_NIfmskNc8ZGALShC/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1782831599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=rpzvWaiXAymWfM0w0eSls%2Bdm1u4%3D&quot; alt=&quot;메모리 설계 전략&quot; /&gt;
&lt;figcaption style=&quot;margin-top: 0.5rem; font-size: 0.85rem; color: #64748b;&quot;&gt;메모리 설계 전략&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;5-memory-qos-cgroup-v2-kubernetes-v136&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;5. Memory QoS와 cgroup v2 (Kubernetes v1.36)&lt;/h2&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Kubernetes v1.36에서는 Memory QoS 기능이 알파 단계로 제공됩니다(KEP-2570). 이 기능은 cgroup v2의 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;memory.min&lt;/code&gt;, &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;memory.low&lt;/code&gt;, &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;memory.high&lt;/code&gt; 컨트롤러를 활용하여, 단순한 &quot;limit 초과 &amp;rarr; 즉시 OOM Kill&quot; 이외의 단계적 메모리 보호를 제공합니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;기존 방식 (cgroup v1 또는 Memory QoS 미사용):&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;memory.max&lt;/code&gt; = limits.memory &amp;rarr; 초과 즉시 OOM Kill&lt;/li&gt;
&lt;li&gt;단계적 경고나 throttling 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Memory QoS 활성화 시 (cgroup v2 + MemoryQoS feature gate):&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;cgroup v2 파라미터&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;Kubernetes 매핑&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;동작&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;memory.min&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;requests.memory&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;커널이 이 크기까지는 회수하지 않음 (hard reservation)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;memory.low&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;정책에 따라 자동 설정&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;이 이하로는 가급적 회수하지 않음 (soft protection)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;memory.high&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;limits.memory 근처&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;초과 시 throttling (프로세스 느려짐, kill은 아님)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;memory.max&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;limits.memory&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;초과 시 OOM Kill&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;의미:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;requests로 설정한 메모리는 Node에 메모리 압박이 와도 커널이 보호합니다.&lt;/li&gt;
&lt;li&gt;limits에 도달하기 전에 throttling 단계가 있어, 즉시 kill 대신 &quot;느려짐&quot; 상태를 먼저 경험합니다.&lt;/li&gt;
&lt;li&gt;운영자 입장에서 OOMKilled 빈도를 줄일 수 있는 가능성이 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;padding: 1rem 1.2rem; margin: 1.5rem 0; border-radius: 8px; border-left: 4px solid #f59e0b; background: #fffbeb; line-height: 1.7;&quot;&gt;&lt;b&gt;주의&lt;/b&gt;&lt;br /&gt;Memory QoS는 Kubernetes v1.36 기준 알파 기능입니다. 프로덕션에서 사용하려면 feature gate를 명시적으로 활성화해야 하며, Node OS가 cgroup v2를 지원해야 합니다. EKS(Amazon Linux 2023), AKS, GKE Autopilot은 기본적으로 cgroup v2를 사용하지만, 클러스터별로 확인이 필요합니다.&lt;/div&gt;
&lt;h2 id=&quot;6-java-oomkilled&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;6. 실무 시나리오: Java 서비스 OOMKilled 디버깅&lt;/h2&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;상황:&lt;/b&gt; 운영 환경의 Java Spring Boot 서비스가 배포 후 2~3시간이 지나면 OOMKilled로 재시작됩니다. Container limit은 1Gi, &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;-Xmx&lt;/code&gt;는 설정하지 않은 상태입니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;진단 과정:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# 1. OOMKilled 확인
kubectl describe pod order-service-7f8b9c-x4k2p -n production | grep -A 5 &quot;Last State&quot;
# Last State:     Terminated
#   Reason:       OOMKilled
#   Exit Code:    137

# 2. 현재 메모리 사용량 확인
kubectl top pod order-service-7f8b9c-x4k2p -n production --containers
# CONTAINER       CPU    MEMORY
# order-service   120m   945Mi    &amp;larr; limit(1Gi)에 근접

# 3. JVM 메모리 상세 확인 (Pod 접속 가능할 때)
kubectl exec order-service-7f8b9c-x4k2p -n production -- \
  java -XX:+PrintFlagsFinal -version 2&amp;gt;&amp;amp;1 | grep -i &quot;maxheapsize\|maxram&quot;
# MaxHeapSize = 268435456 (256Mi) &amp;larr; 기본값: container limit의 25%

# 4. Metaspace 확인 (JFR 또는 JMX로)
# Metaspace: 180Mi, Threads: 250개 &amp;times; 1Mi = 250Mi
# 합계: Heap(256Mi) + Metaspace(180Mi) + Threads(250Mi) + Native(150Mi) &amp;asymp; 836Mi~1Gi+
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;원인 분석:&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;-Xmx&lt;/code&gt;를 지정하지 않으면 JVM은 기본적으로 container limit의 25%(또는 물리 메모리의 25%)를 Heap으로 사용합니다. 1Gi limit에서 Heap은 약 256Mi입니다. 다만 Non-Heap 영역(Metaspace, Thread Stack, Native Memory)이 예상보다 커서 전체 합이 1Gi를 초과합니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해결:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;spec:
  containers:
    - name: order-service
      image: order-service:v2.1.0
      resources:
        requests:
          memory: &quot;1Gi&quot;
        limits:
          memory: &quot;1536Mi&quot;    # 1.5Gi로 상향
      env:
        - name: JAVA_OPTS
          value: &amp;gt;-
            -XX:MaxRAMPercentage=65.0
            -XX:InitialRAMPercentage=50.0
            -XX:MaxMetaspaceSize=200m
            -XX:ReservedCodeCacheSize=64m
            -Xss512k
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;설정 근거:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;MaxRAMPercentage=65.0&lt;/code&gt;: 1536Mi의 65% &amp;asymp; 998Mi를 Heap으로 사용&lt;/li&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;MaxMetaspaceSize=200m&lt;/code&gt;: Metaspace 상한 제한&lt;/li&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;Xss512k&lt;/code&gt;: 기본 1MB인 Thread Stack을 512KB로 줄임 (250 threads &amp;times; 512KB = 125Mi 절약)&lt;/li&gt;
&lt;li&gt;총 예상: Heap(998Mi) + Metaspace(200Mi) + Threads(125Mi) + Native(100Mi) = 1423Mi &amp;lt; 1536Mi&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;7-limits&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;7. limits 없이 운영하는 전략&lt;/h2&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;일부 팀에서는 memory limits를 설정하지 않고 requests만 설정하는 전략을 사용합니다. 이 접근의 trade-off를 이해해야 합니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;limits 미설정 시:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장점: OOMKilled가 발생하지 않음 (container 수준). 앱이 일시적으로 많은 메모리를 사용해도 kill되지 않음.&lt;/li&gt;
&lt;li&gt;단점: Node 전체 메모리를 소진할 수 있음. 다른 Pod에 영향. Node Eviction 발생 가능.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이 전략이 적합한 경우:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Node당 하나의 서비스만 운영하는 경우&lt;/li&gt;
&lt;li&gt;모니터링이 잘 되어 있고, 메모리 이상을 빠르게 감지할 수 있는 경우&lt;/li&gt;
&lt;li&gt;서비스의 메모리 사용 패턴이 예측 가능한 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이 전략이 위험한 경우:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;멀티 테넌트 클러스터 (여러 팀이 같은 Node를 공유)&lt;/li&gt;
&lt;li&gt;메모리 누수 가능성이 있는 서비스&lt;/li&gt;
&lt;li&gt;BestEffort Pod가 혼재하는 환경&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;운영 환경에서는 일반적으로 limits를 설정하는 것이 안전합니다. 하나의 Pod가 Node 전체를 다운시키는 상황을 방지할 수 있기 때문입니다.&lt;/p&gt;
&lt;h2 id=&quot;8&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;8. 재발 방지 체크리스트&lt;/h2&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;항목&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;확인 내용&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;권장 방법&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;메모리 측정&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;부하 테스트로 P99 사용량 확인했는가?&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;최소 30분 부하 테스트 수행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;requests 설정&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;P50~P75 기준으로 설정했는가?&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;BestEffort 등급 방지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;limits 설정&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;P99 + 20~30% 여유가 있는가?&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;버스트 대응&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;런타임 설정&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;JVM/Node.js/Go의 메모리 옵션이 limit과 정합하는가?&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Heap &amp;le; limit의 70%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;사이드카&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Envoy, 로그 수집기 등의 메모리를 포함했는가?&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;모든 컨테이너 합계 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;모니터링&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;container_memory_working_set_bytes&lt;/code&gt; 알림 설정했는가?&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;limit의 80% 도달 시 경고&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;메모리 누수&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;시간 경과에 따른 메모리 증가 패턴이 있는가?&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;프로파일링 도구 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Node 용량&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;requests 합계가 Node allocatable의 80% 이하인가?&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;시스템 예약분 확보&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;9&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;9. 모니터링 알림 설정 예시&lt;/h2&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;OOMKilled가 발생하기 전에 경고를 받으려면, 메모리 사용률이 limit의 80%에 도달했을 때 알림을 보내는 것이 효과적입니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Prometheus Alerting Rule:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;groups:
  - name: memory-alerts
    rules:
      - alert: ContainerMemoryNearLimit
        expr: |
          container_memory_working_set_bytes{container!=&quot;&quot;}
          /
          container_spec_memory_limit_bytes{container!=&quot;&quot;}
          &amp;gt; 0.8
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: &quot;Container {{ $labels.container }} in pod {{ $labels.pod }} is using &amp;gt;80% of memory limit&quot;
          description: &quot;Current usage: {{ $value | humanizePercentage }} of limit. OOMKilled risk is high.&quot;

      - alert: ContainerOOMKilled
        expr: |
          kube_pod_container_status_last_terminated_reason{reason=&quot;OOMKilled&quot;} == 1
        for: 0m
        labels:
          severity: critical
        annotations:
          summary: &quot;Container {{ $labels.container }} in pod {{ $labels.pod }} was OOMKilled&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;10&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;10. 정리&lt;/h2&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;OOMKilled는 container limit 초과(cgroup OOM)와 Node 메모리 부족(kubelet eviction) 두 경로로 발생합니다. &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;kubectl describe pod&lt;/code&gt;에서 Reason을 확인하여 구분합니다.&lt;/li&gt;
&lt;li&gt;limit을 올리는 것은 임시 방편입니다. 메모리 누수가 원인이면 결국 재발합니다. 시간에 따른 메모리 추이를 먼저 확인하세요.&lt;/li&gt;
&lt;li&gt;JVM 애플리케이션은 Heap 외에 Metaspace, Thread Stack, Native Memory를 합산해야 합니다. &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;-Xmx&lt;/code&gt;를 container limit의 50~70%로 설정하는 것이 안전합니다.&lt;/li&gt;
&lt;li&gt;requests는 스케줄링과 QoS 등급에 영향합니다. BestEffort 등급을 피하려면 최소한 requests를 설정해야 합니다.&lt;/li&gt;
&lt;li&gt;부하 테스트 &amp;rarr; P99 측정 &amp;rarr; limits 설계 순서로 접근하면, 추측이 아닌 데이터 기반으로 안정적인 메모리 설정이 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;_2&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;관련 글&lt;/h2&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://wero90.tistory.com/entry/Kubernetes-CrashLoopBackOff-원인과-해결-방법&quot;&gt;Kubernetes CrashLoopBackOff 원인과 해결 방법&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wero90.tistory.com/entry/Kubernetes-HPA가-동작하지-않는-이유&quot;&gt;Kubernetes HPA가 동작하지 않는 이유&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wero90.tistory.com/entry/Kubernetes-아키텍처-기본-구조-Control-Plane과-Worker-Node&quot;&gt;Kubernetes 아키텍처 기본 구조&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wero90.tistory.com/entry/Prometheus-Grafana로-Kubernetes-모니터링-구성하기&quot;&gt;Prometheus + Grafana로 Kubernetes 모니터링 구성하기&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;_3&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;참고 문서&lt;/h2&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/&quot;&gt;Kubernetes 공식 문서 - Resource Management for Pods and Containers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kubernetes.io/docs/concepts/scheduling-eviction/node-pressure-eviction/&quot;&gt;Kubernetes 공식 문서 - Node-pressure Eviction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kubernetes.io/docs/concepts/workloads/pods/pod-qos/&quot;&gt;Kubernetes 공식 문서 - Pod Quality of Service Classes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/kubernetes/enhancements/issues/2570&quot;&gt;Kubernetes KEP-2570 - Memory QoS with cgroups v2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Troubleshooting</category>
      <category>cgroup</category>
      <category>kubectl</category>
      <category>kubernetes</category>
      <category>Memory</category>
      <category>OOMKilled</category>
      <category>POD</category>
      <category>resource Limits</category>
      <category>TroubleShooting</category>
      <author>wero90</author>
      <guid isPermaLink="true">https://wero90.tistory.com/77</guid>
      <comments>https://wero90.tistory.com/77#entry77comment</comments>
      <pubDate>Mon, 8 Jun 2026 15:55:16 +0900</pubDate>
    </item>
    <item>
      <title>OpenTelemetry란 무엇인가: 분산 트레이싱 기본 개념</title>
      <link>https://wero90.tistory.com/76</link>
      <description>&lt;blockquote style=&quot;border-left: 4px solid #3b82f6; padding: 1rem 1.2rem; margin: 1.5rem 0; background: #f0f9ff; color: #1e40af; font-size: 1.05rem; line-height: 1.7; border-radius: 0 8px 8px 0;&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;마이크로서비스 환경에서 &quot;이 요청이 어디서 느려지는가&quot;를 확인하려면 서비스 간 요청 경로를 추적할 수 있어야 합니다. OpenTelemetry는 이 추적 데이터를 벤더 종속 없이 수집하는 오픈소스 표준입니다. Trace, Span, Context Propagation의 동작 원리를 이해하면 분산 시스템의 병목을 체계적으로 진단할 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;_1&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;핵심 요약&lt;/h2&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;OpenTelemetry(OTel)는 Metrics, Logs, Traces를 수집하는 CNCF Graduated 오픈소스 표준입니다. 2026년 5월 CNCF 졸업(Graduated) 단계에 도달했습니다.&lt;/li&gt;
&lt;li&gt;분산 트레이싱은 하나의 요청이 여러 서비스를 거치는 경로를 Trace ID로 연결하여 추적하는 기술입니다.&lt;/li&gt;
&lt;li&gt;OpenTelemetry는 계측(Instrumentation)과 백엔드(Storage/Analysis)를 분리합니다. SDK로 데이터를 생성하고, Collector로 처리한 뒤, 원하는 백엔드(Jaeger, Datadog, Tempo 등)로 전송합니다.&lt;/li&gt;
&lt;li&gt;W3C Trace Context 표준(&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;traceparent&lt;/code&gt; 헤더)을 통해 서비스 간 Context를 전파합니다.&lt;/li&gt;
&lt;li&gt;Collector는 Receiver &amp;rarr; Processor &amp;rarr; Exporter 파이프라인으로 구성되며, 배포 패턴(Agent, Gateway)에 따라 확장성과 운영 복잡도가 달라집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;1&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;1. 왜 필요한가&lt;/h2&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;5개 마이크로서비스로 구성된 이커머스 시스템을 운영하고 있다고 가정합니다. 사용자가 &quot;결제 완료까지 8초 이상 걸린다&quot;고 신고합니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;각 서비스의 로그를 확인하면:&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;API Gateway: 요청 수신 &amp;rarr; 정상 (50ms)&lt;/li&gt;
&lt;li&gt;Order Service: 주문 생성 &amp;rarr; 정상 (100ms)&lt;/li&gt;
&lt;li&gt;Inventory Service: 재고 확인 &amp;rarr; 정상 (80ms)&lt;/li&gt;
&lt;li&gt;Payment Service: 결제 요청 &amp;rarr; ??? (로그에 에러 없음)&lt;/li&gt;
&lt;li&gt;Notification Service: 알림 발송 &amp;rarr; 정상 (200ms)&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;개별 서비스는 에러를 남기지 않지만, 전체 응답 시간은 8초입니다. &quot;어디에서 시간이 소요되는가?&quot;를 파악하려면 하나의 요청이 서비스를 거치는 전체 경로를 한 화면에서 볼 수 있어야 합니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;이것이 분산 트레이싱이 해결하는 문제입니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;그런데 트레이싱을 도입하려면 또 다른 문제가 생깁니다:&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;문제&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;벤더 종속&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Datadog SDK로 계측하면 나중에 Jaeger로 바꿀 때 전체 코드 수정 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;팀별 도구 파편화&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;A팀은 Zipkin, B팀은 Jaeger를 사용하면 Trace가 연결되지 않음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;일관성 부재&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;서비스마다 다른 형식으로 데이터를 내보내면 중앙에서 분석 불가&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;OpenTelemetry는 이 문제들을 해결합니다. 계측은 OTel SDK 하나로 통일하고, 백엔드는 자유롭게 선택하거나 교체할 수 있습니다.&lt;/p&gt;
&lt;h2 id=&quot;2-opentelemetry&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;2. OpenTelemetry란 무엇인가&lt;/h2&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;OpenTelemetry(OTel)는 Observability 데이터(Metrics, Logs, Traces)를 생성, 수집, 전송하기 위한 오픈소스 표준 프레임워크입니다.&lt;/p&gt;
&lt;h3 id=&quot;_2&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;배경&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;OpenTelemetry는 두 개의 CNCF 프로젝트가 합쳐져 탄생했습니다:&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;OpenTracing&lt;/b&gt; (2016): 분산 트레이싱 API 표준&lt;/li&gt;
&lt;li&gt;&lt;b&gt;OpenCensus&lt;/b&gt; (2018): Google이 주도한 메트릭 + 트레이싱 라이브러리&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;두 프로젝트가 겹치는 영역이 많아 2019년에 합쳐져 OpenTelemetry가 되었습니다. 2026년 5월 21일 CNCF Graduated 프로젝트로 승격되어 프로덕션 사용 준비가 완료된 성숙 단계에 도달했습니다.&lt;/p&gt;
&lt;h3 id=&quot;opentelemetry_1&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;OpenTelemetry가 하는 것과 하지 않는 것&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;OTel이 하는 것&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;OTel이 하지 않는 것&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;텔레메트리 데이터 생성 (SDK)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;데이터 저장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;데이터 수집 및 처리 (Collector)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;데이터 분석/시각화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;표준 프로토콜 정의 (OTLP)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;알림 발생&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;자동 계측 (Auto-instrumentation)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;대시보드 제공&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;다양한 백엔드로 내보내기 (Exporter)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;장애 대응 자동화&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;핵심 설계 원칙은 &lt;b&gt;&quot;계측과 백엔드의 분리&quot;&lt;/b&gt;입니다. 애플리케이션 코드에서 데이터를 생성하는 부분(SDK)과, 그 데이터를 저장하고 분석하는 부분(Backend)을 독립적으로 선택할 수 있습니다.&lt;/p&gt;
&lt;h3 id=&quot;_3&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;지원 언어&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;OpenTelemetry SDK는 주요 언어를 지원합니다:&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;언어&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;Traces 상태&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;Metrics 상태&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;Logs 상태&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Java&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Stable&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Stable&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Stable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Python&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Stable&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Stable&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Stable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Go&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Stable&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Stable&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Stable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;JavaScript/Node.js&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Stable&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Stable&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Stable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;.NET (C#)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Stable&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Stable&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Stable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;C++&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Stable&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Stable&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Stable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Rust&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Stable&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Stable&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Stable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;PHP&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Stable&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Stable&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Stable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Ruby&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Stable&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Stable&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Stable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Swift&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Stable&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Stable&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Experimental&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Erlang/Elixir&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Stable&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Stable&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Experimental&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Java, Python, .NET, PHP, JavaScript는 코드 수정 없이 자동 계측(Zero-code instrumentation)도 지원합니다.&lt;/p&gt;
&lt;h2 id=&quot;3&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;3. 전체 아키텍처&lt;/h2&gt;
&lt;figure style=&quot;text-align: center; margin: 2rem 0;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dna/cbnkYz/dJMcahxWi21/AAAAAAAAAAAAAAAAAAAAAC4qtgdIuQJp1A44Ab1DwimHdDNv27qJv-At-s1puG4l/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1782831599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=TwM%2BsDLTl0fmRCLM7NQH3ISwt4s%3D&quot; alt=&quot;OpenTelemetry 전체 아키텍처&quot; /&gt;
&lt;figcaption style=&quot;margin-top: 0.5rem; font-size: 0.85rem; color: #64748b;&quot;&gt;OpenTelemetry 전체 아키텍처&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;OpenTelemetry의 전체 구조는 세 계층으로 나뉩니다:&lt;/p&gt;
&lt;h3 id=&quot;_4&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;계층별 역할&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;계층&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;구성 요소&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;역할&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;생성 (Instrumentation)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;SDK, Auto-instrumentation&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;애플리케이션에서 텔레메트리 데이터 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;수집/처리 (Collection)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Collector (Receiver &amp;rarr; Processor &amp;rarr; Exporter)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;데이터를 받아서 가공하고 내보냄&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;저장/분석 (Backend)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Jaeger, Prometheus, Grafana, Datadog 등&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;데이터를 저장하고 시각화/분석&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;이 구조의 장점:&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;백엔드 교체가 자유로움&lt;/b&gt;: Jaeger에서 Grafana Tempo로 바꿔도 애플리케이션 코드를 수정하지 않음&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데이터 처리를 중앙화&lt;/b&gt;: Collector에서 샘플링, 필터링, 메타데이터 추가를 한 곳에서 관리&lt;/li&gt;
&lt;li&gt;&lt;b&gt;언어에 구애받지 않음&lt;/b&gt;: Java, Python, Go 등 각 서비스 언어에 맞는 SDK를 사용하되, 출력 형식은 OTLP로 통일&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;4&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;4. 분산 트레이싱 핵심 개념&lt;/h2&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;분산 트레이싱은 OpenTelemetry의 세 Signal(Traces, Metrics, Logs) 중 가장 먼저 안정화된 영역이며, 분산 시스템 디버깅의 핵심입니다.&lt;/p&gt;
&lt;h3 id=&quot;trace-span&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;Trace와 Span&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;용어&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;설명&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;비유&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Trace&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;하나의 요청에 대한 전체 처리 경로&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;택배 배송의 전체 경로 (출발지 &amp;rarr; 도착지)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Span&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;하나의 작업 단위&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;각 중간 경유지에서의 처리 (집하 &amp;rarr; 허브 &amp;rarr; 배달)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Root Span&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Trace의 첫 번째 Span (부모 없음)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;최초 출발지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Child Span&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;다른 Span에 의해 생성된 Span&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;경유지에서 다음 경유지로 넘기는 과정&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;하나의 Trace는 여러 Span의 트리 구조입니다:&lt;/p&gt;
&lt;pre class=&quot;routeros&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;Trace (trace_id: abc-123)
├─ Span A: API Gateway (root span)
│   ├─ Span B: Order Service
│   │   ├─ Span D: DB Query (SELECT orders)
│   │   └─ Span E: Payment Service 호출
│   │       └─ Span F: 외부 PG API 호출
│   └─ Span C: Notification Service
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;span&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;Span의 구조&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;각 Span은 다음 정보를 포함합니다:&lt;/p&gt;
&lt;pre class=&quot;json&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;{
  &quot;name&quot;: &quot;POST /api/orders&quot;,
  &quot;context&quot;: {
    &quot;trace_id&quot;: &quot;5b8aa5a2d2c872e8321cf37308d69df2&quot;,
    &quot;span_id&quot;: &quot;051581bf3cb55c13&quot;
  },
  &quot;parent_span_id&quot;: &quot;e457b5a2e4d86bd1&quot;,
  &quot;start_time&quot;: &quot;2026-06-08T10:15:30.123Z&quot;,
  &quot;end_time&quot;: &quot;2026-06-08T10:15:30.456Z&quot;,
  &quot;duration_ms&quot;: 333,
  &quot;status&quot;: &quot;OK&quot;,
  &quot;kind&quot;: &quot;SERVER&quot;,
  &quot;attributes&quot;: {
    &quot;http.method&quot;: &quot;POST&quot;,
    &quot;http.url&quot;: &quot;/api/orders&quot;,
    &quot;http.status_code&quot;: 201,
    &quot;service.name&quot;: &quot;order-service&quot;,
    &quot;db.system&quot;: &quot;postgresql&quot;
  },
  &quot;events&quot;: [
    {
      &quot;name&quot;: &quot;order.validated&quot;,
      &quot;timestamp&quot;: &quot;2026-06-08T10:15:30.200Z&quot;
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;주요 필드:&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;필드&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;trace_id&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;요청 전체를 식별하는 128bit ID (모든 서비스가 공유)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;span_id&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;개별 작업을 식별하는 64bit ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;parent_span_id&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;이 Span을 호출한 부모 Span의 ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;kind&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;SERVER, CLIENT, PRODUCER, CONSUMER, INTERNAL 중 하나&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;attributes&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;키-값 쌍으로 된 메타데이터 (Semantic Conventions 기반)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;events&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Span 실행 중 발생한 시점별 이벤트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;status&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;OK, ERROR, UNSET&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;span-kind&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;Span Kind의 의미&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;Kind&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;설명&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;예시&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;SERVER&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;서버로서 요청을 수신&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;HTTP 서버가 요청을 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;CLIENT&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;클라이언트로서 요청을 발신&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;HTTP 클라이언트가 다른 서비스 호출&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;PRODUCER&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;비동기 메시지를 생성&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Kafka Producer가 메시지 발행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;CONSUMER&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;비동기 메시지를 소비&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Kafka Consumer가 메시지 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;INTERNAL&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;내부 작업 (네트워크 호출 없음)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;함수 내부 로직&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;5-context-propagation&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;5. Context Propagation: 서비스 간 추적 연결&lt;/h2&gt;
&lt;figure style=&quot;text-align: center; margin: 2rem 0;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dna/ejmGBO/dJMcahxWi4c/AAAAAAAAAAAAAAAAAAAAANgbH77f9ksPOgyt4pwHcJqwLfLRC-NHPdb17IyM6uez/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1782831599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=PWIcrokhQsTHC83wHShtTHmViZs%3D&quot; alt=&quot;Context Propagation 동작 원리&quot; /&gt;
&lt;figcaption style=&quot;margin-top: 0.5rem; font-size: 0.85rem; color: #64748b;&quot;&gt;Context Propagation 동작 원리&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;분산 트레이싱에서 가장 중요한 메커니즘은 Context Propagation입니다. 서비스 A가 서비스 B를 호출할 때 &quot;이 요청은 같은 Trace의 일부&quot;라는 정보를 전달해야 합니다.&lt;/p&gt;
&lt;h3 id=&quot;w3c-trace-context&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;W3C Trace Context 표준&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;OpenTelemetry는 W3C Trace Context 표준을 기본 전파 형식으로 사용합니다. HTTP 요청 시 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;traceparent&lt;/code&gt; 헤더에 Context를 담아 전달합니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
             ^^-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-^^^^^^^^^^^^^^^^-^^
             |  |                                |                |
             |  trace-id (128bit)               span-id (64bit)  flags
             version                                              (sampled)
&lt;/code&gt;&lt;/pre&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;필드&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;version&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;항상 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;00&lt;/code&gt; (현재 버전)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;trace-id&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;요청 전체를 식별하는 ID (32 hex chars)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;span-id&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;현재 Span ID (16 hex chars) &amp;mdash; 수신 서비스가 parent로 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;flags&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;01&lt;/code&gt; = sampled (수집 대상), &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;00&lt;/code&gt; = not sampled&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;_5&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;전파 과정 예시&lt;/h3&gt;
&lt;ol style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;API Gateway&lt;/b&gt;: 요청 수신 &amp;rarr; Trace ID가 없으면 새로 생성 &amp;rarr; &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;traceparent&lt;/code&gt; 헤더에 포함하여 다음 서비스 호출&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Order Service&lt;/b&gt;: &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;traceparent&lt;/code&gt; 헤더에서 trace-id와 parent span-id를 추출 &amp;rarr; 새 Span 생성 (parent = 수신한 span-id) &amp;rarr; 다음 호출 시 자신의 span-id를 traceparent에 담아 전달&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Payment Service&lt;/b&gt;: 동일한 과정 반복&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 모든 서비스가 같은 trace-id를 공유하면서 각자의 Span을 기록하면, 나중에 수집기에서 trace-id로 모든 Span을 조합하여 전체 경로를 재구성할 수 있습니다.&lt;/p&gt;
&lt;h3 id=&quot;_6&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;전파가 실패하는 경우&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;원인&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;증상&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;해결&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;미들웨어가 traceparent 헤더를 제거&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Trace가 서비스 경계에서 끊어짐&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;프록시/게이트웨이에서 헤더 보존 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;비동기 메시징(Kafka)에서 전파 누락&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Consumer Span이 별도 Trace로 분리&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Message 헤더에 traceparent 포함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;서비스가 OTel SDK를 사용하지 않음&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;해당 서비스의 Span이 누락&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;SDK 적용 또는 프록시 레벨에서 Span 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;레거시 시스템이 커스텀 헤더 사용&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;X-B3-TraceId와 traceparent 혼재&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Bridge Propagator 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div style=&quot;padding: 1rem 1.2rem; margin: 1.5rem 0; border-radius: 8px; border-left: 4px solid #3b82f6; background: #eff6ff; line-height: 1.7;&quot;&gt;&lt;b&gt;Tip&lt;/b&gt;&lt;br /&gt;서비스 메시(Istio, Linkerd)를 사용하면 애플리케이션 코드 수정 없이 사이드카 프록시가 자동으로 traceparent 헤더를 전파합니다. 다만 애플리케이션 내부의 상세 Span(DB 쿼리, 비즈니스 로직)은 여전히 SDK로 계측해야 합니다.&lt;/div&gt;
&lt;h2 id=&quot;6-opentelemetry-collector&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;6. OpenTelemetry Collector&lt;/h2&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Collector는 OpenTelemetry의 핵심 구성 요소입니다. 애플리케이션에서 생성한 텔레메트리 데이터를 수신하고, 가공하고, 원하는 백엔드로 내보내는 중간 에이전트 역할을 합니다.&lt;/p&gt;
&lt;h3 id=&quot;collector&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;왜 Collector를 사용하는가&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;SDK에서 직접 백엔드로 보내지 않고 Collector를 중간에 두는 이유:&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;이점&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;백엔드 변경 시 코드 수정 불필요&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Collector 설정만 바꾸면 됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;중앙 집중 처리&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;샘플링, 필터링, 메타데이터 추가를 한 곳에서 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;버퍼링/재시도&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;백엔드 장애 시 데이터 유실 방지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;다중 백엔드 전송&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;하나의 데이터를 여러 백엔드로 동시에 전송 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;리소스 절약&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;배치 처리로 네트워크 호출 횟수 감소&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;collector_1&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;Collector 파이프라인 구조&lt;/h3&gt;
&lt;figure style=&quot;text-align: center; margin: 2rem 0;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dna/cf3RKV/dJMcahEEyAB/AAAAAAAAAAAAAAAAAAAAAJTlKrrHLQ8gezadzUYeelJKiu1ty9A_mt6pod0TMVHz/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1782831599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=LoJZojcjqtbm5jV0U0CJPgGBvx0%3D&quot; alt=&quot;Collector 파이프라인 구조&quot; /&gt;
&lt;figcaption style=&quot;margin-top: 0.5rem; font-size: 0.85rem; color: #64748b;&quot;&gt;Collector 파이프라인 구조&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Collector는 세 가지 핵심 컴포넌트로 구성됩니다:&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Receiver&lt;/b&gt;: 데이터를 수신하는 입구&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;Receiver&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;프로토콜&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;용도&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;otlp&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;gRPC, HTTP&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;OTel SDK에서 보내는 데이터 수신 (기본)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;jaeger&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Thrift, gRPC&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;기존 Jaeger 에이전트에서 전환 시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;prometheus&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Scrape&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Prometheus 메트릭 수집&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;filelog&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;파일 읽기&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;로그 파일 수집&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Processor&lt;/b&gt;: 데이터를 가공하는 중간 처리&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;Processor&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;역할&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;batch&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;데이터를 묶어서 일괄 전송 (네트워크 효율화)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;filter&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;조건에 맞지 않는 데이터 제거 (헬스체크 등)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;tail_sampling&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;조건부 수집 (에러 100%, 정상 10%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;attributes&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;속성 추가/수정/삭제 (환경 정보, 팀 태그 등)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;resource&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;리소스 정보 추가 (service.name, k8s.namespace 등)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;memory_limiter&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;메모리 사용량 제한 (OOM 방지)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Exporter&lt;/b&gt;: 데이터를 내보내는 출구&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;Exporter&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;대상&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;otlp&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;OTLP 호환 백엔드 (Tempo, SigNoz, Datadog 등)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;prometheus&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Prometheus Remote Write&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;jaeger&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Jaeger 백엔드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;loki&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Grafana Loki&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;debug&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;콘솔 출력 (디버깅용)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;_7&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;설정 예시&lt;/h3&gt;
&lt;pre class=&quot;yaml&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# otel-collector-config.yaml
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  batch:
    send_batch_size: 1024
    timeout: 5s
  memory_limiter:
    check_interval: 1s
    limit_mib: 512
  filter:
    traces:
      span:
        - 'attributes[&quot;http.route&quot;] == &quot;/health&quot;'
  tail_sampling:
    decision_wait: 10s
    policies:
      - name: errors
        type: status_code
        status_code: {status_codes: [ERROR]}
      - name: slow-traces
        type: latency
        latency: {threshold_ms: 3000}
      - name: default
        type: probabilistic
        probabilistic: {sampling_percentage: 10}

exporters:
  otlp/tempo:
    endpoint: tempo:4317
    tls:
      insecure: true
  prometheus:
    endpoint: 0.0.0.0:8889

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [memory_limiter, filter, tail_sampling, batch]
      exporters: [otlp/tempo]
    metrics:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [prometheus]
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;이 설정의 의미:&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;OTLP gRPC(4317)와 HTTP(4318)로 데이터 수신&lt;/li&gt;
&lt;li&gt;헬스체크 요청(&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;/health&lt;/code&gt;)은 필터링으로 제거&lt;/li&gt;
&lt;li&gt;에러와 3초 이상 지연 Trace는 100% 수집, 나머지는 10% 샘플링&lt;/li&gt;
&lt;li&gt;Traces는 Grafana Tempo로, Metrics는 Prometheus로 전송&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;7&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;7. 배포 패턴&lt;/h2&gt;
&lt;figure style=&quot;text-align: center; margin: 2rem 0;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dna/m1Cxe/dJMcahxWi6H/AAAAAAAAAAAAAAAAAAAAALAlS1SNGvQmZhELanuPa59DFmHLFe3Pe00JTlUF7HOc/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1782831599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=ssqqrRuSHUyjmHyBxxbtCYjS%2BG8%3D&quot; alt=&quot;배포 패턴: Agent + Gateway&quot; /&gt;
&lt;figcaption style=&quot;margin-top: 0.5rem; font-size: 0.85rem; color: #64748b;&quot;&gt;배포 패턴: Agent + Gateway&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Collector를 어떻게 배포하느냐에 따라 운영 복잡도와 확장성이 달라집니다.&lt;/p&gt;
&lt;h3 id=&quot;agent&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;Agent 패턴&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;각 노드(또는 Pod)에 Collector를 배치하는 방식입니다. Kubernetes에서는 DaemonSet으로 배포합니다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# DaemonSet으로 Agent 배포
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: otel-agent
spec:
  selector:
    matchLabels:
      app: otel-agent
  template:
    spec:
      containers:
        - name: otel-collector
          image: otel/opentelemetry-collector-contrib:0.115.0
          ports:
            - containerPort: 4317  # OTLP gRPC
            - containerPort: 4318  # OTLP HTTP
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;장점:&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로컬 네트워크로 데이터 전송 (지연 낮음)&lt;/li&gt;
&lt;li&gt;노드별 리소스 메타데이터 자동 수집 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;단점:&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;노드 수에 비례하여 Collector 인스턴스 증가&lt;/li&gt;
&lt;li&gt;Tail-based Sampling 불가 (전체 Trace를 한 Agent가 볼 수 없음)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;gateway&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;Gateway 패턴&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;중앙에 Collector 클러스터를 배치하고, 모든 서비스가 이곳으로 데이터를 전송합니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;장점:&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Tail-based Sampling 가능 (전체 Trace를 모아서 판단)&lt;/li&gt;
&lt;li&gt;중앙 집중 관리 (설정 변경을 한 곳에서)&lt;/li&gt;
&lt;li&gt;다중 백엔드 라우팅&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;단점:&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단일 장애점(SPOF) 위험 &amp;rarr; 고가용성 구성 필요&lt;/li&gt;
&lt;li&gt;네트워크 홉 추가&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;agent-gateway&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;Agent + Gateway 조합 (권장)&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;실무에서는 두 패턴을 조합합니다:&lt;/p&gt;
&lt;ol style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Agent (DaemonSet)&lt;/b&gt;: 각 노드에서 데이터 수집, 기본 배치 처리 후 Gateway로 전송&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Gateway (Deployment)&lt;/b&gt;: 중앙에서 Tail-based Sampling, 메타데이터 보강, 다중 백엔드 라우팅&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;이 조합이 권장되는 이유:&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Agent가 로컬에서 빠르게 수집하여 애플리케이션 성능에 미치는 영향을 최소화&lt;/li&gt;
&lt;li&gt;Gateway에서 전체 Trace를 모아 지능적인 샘플링 결정&lt;/li&gt;
&lt;li&gt;Gateway를 수평 확장하여 트래픽 증가에 대응&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;8-kubernetes-otel&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;8. 실무 적용 시나리오: Kubernetes에서 OTel 도입&lt;/h2&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;스타트업의 Kubernetes 환경(EKS)에서 5개 마이크로서비스를 운영한다고 가정합니다. 현재 로그만 CloudWatch로 수집하고 있고, 서비스 간 지연 문제가 자주 발생합니다.&lt;/p&gt;
&lt;h3 id=&quot;_8&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;단계별 도입 전략&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1단계 &amp;mdash; Auto-instrumentation으로 시작 (코드 수정 최소)&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Java/Python/Node.js 서비스라면 OTel Auto-instrumentation Agent를 사용하여 코드 수정 없이 기본 Span을 생성할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# Kubernetes에서 Java 서비스에 Auto-instrumentation 적용
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  template:
    metadata:
      annotations:
        instrumentation.opentelemetry.io/inject-java: &quot;true&quot;
    spec:
      containers:
        - name: order-service
          image: order-service:v1.2
          env:
            - name: OTEL_SERVICE_NAME
              value: &quot;order-service&quot;
            - name: OTEL_EXPORTER_OTLP_ENDPOINT
              value: &quot;http://otel-agent:4317&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;이것만으로 HTTP 요청/응답, DB 쿼리, gRPC 호출에 대한 Span이 자동 생성됩니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2단계 &amp;mdash; 핵심 비즈니스 로직에 Manual Span 추가&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Auto-instrumentation은 프레임워크 수준의 Span만 생성합니다. &quot;결제 검증 &amp;rarr; 포인트 차감 &amp;rarr; PG 호출&quot;처럼 비즈니스 로직 내부를 추적하려면 수동으로 Span을 추가합니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# Python 예시: 결제 처리 내부 추적
from opentelemetry import trace

tracer = trace.get_tracer(&quot;payment-service&quot;)

def process_payment(order_id, amount):
    with tracer.start_as_current_span(&quot;validate_payment&quot;) as span:
        span.set_attribute(&quot;order.id&quot;, order_id)
        span.set_attribute(&quot;payment.amount&quot;, amount)
        validate_card()

    with tracer.start_as_current_span(&quot;call_pg_api&quot;) as span:
        span.set_attribute(&quot;pg.provider&quot;, &quot;nice-pay&quot;)
        result = call_external_pg(amount)
        span.set_attribute(&quot;pg.response_code&quot;, result.code)
        return result
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3단계 &amp;mdash; 샘플링과 비용 최적화&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;트래픽이 많아지면 모든 Trace를 수집하는 것은 비용적으로 비현실적입니다. Gateway에서 Tail-based Sampling을 적용합니다:&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;에러가 발생한 Trace: 100% 수집&lt;/li&gt;
&lt;li&gt;3초 이상 지연된 Trace: 100% 수집&lt;/li&gt;
&lt;li&gt;정상 Trace: 10% 샘플링&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4단계 &amp;mdash; 대시보드와 알림 연동&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;수집된 Trace 데이터를 기반으로:&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Grafana에서 서비스 의존성 맵 시각화&lt;/li&gt;
&lt;li&gt;p99 응답시간 알림 설정&lt;/li&gt;
&lt;li&gt;에러율 급증 시 관련 Trace로 바로 점프&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;9-semantic-conventions&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;9. Semantic Conventions: 표준화된 속성 이름&lt;/h2&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;OpenTelemetry는 Span 속성(Attributes)의 이름을 표준화하여 서로 다른 언어, 서로 다른 팀이 만든 서비스의 데이터를 일관되게 분석할 수 있도록 합니다. 이를 Semantic Conventions라고 합니다.&lt;/p&gt;
&lt;h3 id=&quot;semantic-conventions&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;주요 Semantic Conventions&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;HTTP 관련:&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;속성&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;설명&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;예시&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;http.request.method&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;HTTP 메서드&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;GET, POST&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;url.full&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;전체 URL&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;https://api.example.com/orders&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;http.response.status_code&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;응답 코드&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;200, 500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;server.address&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;서버 호스트&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;api.example.com&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;데이터베이스 관련:&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;속성&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;설명&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;예시&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;db.system&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;DB 종류&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;postgresql, mysql, redis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;db.operation.name&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;작업 유형&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;SELECT, INSERT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;db.collection.name&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;테이블/컬렉션&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;orders, users&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Kubernetes 관련:&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;속성&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;설명&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;예시&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;k8s.namespace.name&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;네임스페이스&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;production&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;k8s.pod.name&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Pod 이름&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;order-service-7b4d5c-xkf2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;k8s.deployment.name&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Deployment 이름&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;order-service&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;k8s.node.name&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;노드 이름&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;ip-10-0-1-42&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Semantic Conventions를 따르면 서로 다른 서비스에서 온 데이터를 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;db.system = &quot;postgresql&quot;&lt;/code&gt;로 일관되게 필터링할 수 있습니다. 팀마다 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;database_type&lt;/code&gt;, &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;db_kind&lt;/code&gt;, &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;dbSystem&lt;/code&gt; 등 제각각 이름을 붙이면 분석이 어렵습니다.&lt;/p&gt;
&lt;h2 id=&quot;10-otlp-opentelemetry-protocol&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;10. OTLP: OpenTelemetry Protocol&lt;/h2&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;OTLP(OpenTelemetry Protocol)는 텔레메트리 데이터를 전송하는 표준 프로토콜입니다. SDK &amp;rarr; Collector, Collector &amp;rarr; Backend 간 통신에 사용됩니다.&lt;/p&gt;
&lt;h3 id=&quot;otlp-vs&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;OTLP vs 다른 프로토콜&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;프로토콜&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;데이터 유형&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;특징&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;OTLP gRPC&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Traces, Metrics, Logs&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;바이너리 인코딩, 양방향 스트리밍, 성능 최적&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;OTLP HTTP&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Traces, Metrics, Logs&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;HTTP/1.1 호환, 프록시 통과 용이&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Jaeger Thrift&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Traces만&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;레거시, 점차 OTLP로 전환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Zipkin JSON&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Traces만&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;레거시, 경량&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Prometheus Remote Write&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Metrics만&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Prometheus 생태계 호환&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;OTLP의 장점:&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Traces, Metrics, Logs를 하나의 프로토콜로 통합&lt;/li&gt;
&lt;li&gt;Protobuf 기반 바이너리 인코딩으로 네트워크 효율성 높음&lt;/li&gt;
&lt;li&gt;gRPC 사용 시 양방향 스트리밍과 헤더 압축 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;padding: 1rem 1.2rem; margin: 1.5rem 0; border-radius: 8px; border-left: 4px solid #3b82f6; background: #eff6ff; line-height: 1.7;&quot;&gt;&lt;b&gt;Tip&lt;/b&gt;&lt;br /&gt;새로 구성하는 환경이라면 OTLP gRPC를 기본으로 사용합니다. 로드밸런서나 프록시가 gRPC를 지원하지 않는 환경에서만 OTLP HTTP를 선택합니다. Jaeger, Zipkin 형식은 기존 시스템과의 호환이 필요한 경우에만 사용합니다.&lt;/div&gt;
&lt;h2 id=&quot;11&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;11. 보안 고려사항&lt;/h2&gt;
&lt;div style=&quot;padding: 1rem 1.2rem; margin: 1.5rem 0; border-radius: 8px; border-left: 4px solid #ef4444; background: #fef2f2; line-height: 1.7;&quot;&gt;&lt;b&gt;Security Note&lt;/b&gt;&lt;br /&gt;Trace 데이터에는 요청 URL, 쿼리 파라미터, 헤더 값이 포함될 수 있습니다. 민감 정보가 Span 속성에 기록되지 않도록 설계 단계에서 정책을 수립해야 합니다.&lt;/div&gt;
&lt;h3 id=&quot;_9&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;민감 정보 노출 위험&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;위험&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;예시&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;대응&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;URL에 토큰 포함&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;/api/users?token=abc123&lt;/code&gt;이 url.full 속성에 기록됨&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;URL 파라미터 마스킹 또는 경로만 기록&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;요청 헤더에 인증 정보&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Authorization 헤더가 Span 속성에 기록됨&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;민감 헤더 수집 제외 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;DB 쿼리에 사용자 데이터&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;SELECT * FROM users WHERE email='user@email.com'&lt;/code&gt;이 db.statement에 기록됨&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;쿼리 난독화(Obfuscation) 적용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Span 속성에 PII 포함&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;user.email, user.phone을 커스텀 속성으로 기록&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;PII 속성 사용 금지 정책&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;collector_2&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;Collector에서의 보안 처리&lt;/h3&gt;
&lt;pre class=&quot;yaml&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;processors:
  attributes:
    actions:
      # 민감 속성 삭제
      - key: http.request.header.authorization
        action: delete
      - key: user.email
        action: delete
      # URL 쿼리 파라미터 제거
      - key: url.full
        action: hash  # 해시 처리
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;_10&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;통신 보안&lt;/h3&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SDK &amp;rarr; Collector: 클러스터 내부 통신이면 mTLS 또는 서비스 메시로 암호화&lt;/li&gt;
&lt;li&gt;Collector &amp;rarr; Backend: TLS 필수 (외부 SaaS 백엔드로 전송 시)&lt;/li&gt;
&lt;li&gt;Collector 엔드포인트 접근 제어: NetworkPolicy로 허용된 Pod만 접근 가능하도록 제한&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;12&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;12. 비용/운영 고려사항&lt;/h2&gt;
&lt;div style=&quot;padding: 1rem 1.2rem; margin: 1.5rem 0; border-radius: 8px; border-left: 4px solid #f59e0b; background: #fffbeb; line-height: 1.7;&quot;&gt;&lt;b&gt;주의&lt;/b&gt;&lt;br /&gt;분산 트레이싱의 데이터 볼륨은 트래픽에 비례하여 급증합니다. 초당 10,000 요청 &amp;times; 5 서비스 &amp;times; 평균 3개 Span = 초당 150,000 Span입니다. 샘플링 없이 100% 수집하면 저장 비용이 빠르게 증가합니다.&lt;/div&gt;
&lt;h3 id=&quot;_11&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;비용 구조&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;항목&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;비용 발생 요인&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;최적화 방향&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Collector 리소스&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;CPU/메모리 사용량&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;배치 크기 조정, 불필요 데이터 필터링&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;네트워크 전송&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Span 수 &amp;times; Span 크기&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;샘플링, 속성 수 제한&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;백엔드 저장&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;수집된 Span 총량 &amp;times; 보존 기간&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;샘플링 비율 조정, 보존 정책&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;쿼리 비용&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;조회 빈도와 쿼리 복잡도&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;인덱싱 최적화&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;_12&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;샘플링 전략 비교&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;전략&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;장점&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;단점&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;적합 환경&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Head-based (10%)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;구현 간단, 예측 가능한 비용&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;에러 Trace를 놓칠 수 있음&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;트래픽이 많고 비용 민감&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Tail-based (조건부)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;중요한 Trace를 놓치지 않음&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;메모리 사용량 높음, Gateway 필요&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;에러/지연 분석이 중요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Head + Tail 조합&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;균형 잡힌 수집&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;구성 복잡도 증가&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;대부분의 운영 환경&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;_13&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;운영 체크리스트&lt;/h3&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;[ ] Collector에 memory_limiter 설정 (OOM 방지)&lt;/li&gt;
&lt;li&gt;[ ] 백엔드 장애 시 Collector의 재시도 정책 확인&lt;/li&gt;
&lt;li&gt;[ ] 샘플링 비율을 트래픽 변화에 맞게 조정&lt;/li&gt;
&lt;li&gt;[ ] Span 속성 수와 크기 제한 (불필요하게 큰 데이터 방지)&lt;/li&gt;
&lt;li&gt;[ ] Collector 자체의 모니터링 (자신의 메트릭을 Prometheus로 노출)&lt;/li&gt;
&lt;li&gt;[ ] 보존 기간 정의: Trace 7~14일, 중요 Trace 30일&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;13&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;13. 기존 도구와의 관계&lt;/h2&gt;
&lt;h3 id=&quot;opentelemetry-jaegerzipkin&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;OpenTelemetry와 Jaeger/Zipkin&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Jaeger와 Zipkin은 트레이싱 백엔드(저장/시각화)입니다. OpenTelemetry는 데이터 생성/수집 표준입니다. 경쟁이 아니라 보완 관계입니다.&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;[AS-IS] 서비스 &amp;rarr; Jaeger SDK &amp;rarr; Jaeger Backend
[TO-BE] 서비스 &amp;rarr; OTel SDK &amp;rarr; OTel Collector &amp;rarr; Jaeger Backend (또는 Tempo, Datadog)
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;전환 시 장점:&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Jaeger SDK를 OTel SDK로 교체하면 나중에 Tempo나 Datadog으로 백엔드를 바꿀 수 있음&lt;/li&gt;
&lt;li&gt;Jaeger 자체도 OTLP 수신을 지원하므로 호환됨&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;opentelemetry-prometheus&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;OpenTelemetry와 Prometheus&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Prometheus는 메트릭 수집/저장 도구입니다. OpenTelemetry는 Prometheus 형식의 메트릭도 수집할 수 있고(prometheus receiver), Prometheus로 내보낼 수도 있습니다(prometheus exporter).&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존 Prometheus 환경에 트레이싱을 추가하고 싶다면: OTel SDK + Collector로 Trace를 수집하고, 메트릭은 기존 Prometheus 유지&lt;/li&gt;
&lt;li&gt;새 환경을 구축한다면: OTel SDK로 Metrics와 Traces를 모두 수집 &amp;rarr; Collector에서 각각 Prometheus와 Tempo로 라우팅&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;opentelemetry-datadognew-relic&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;OpenTelemetry와 Datadog/New Relic&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;상용 APM 도구들은 이미 OTLP 수신을 지원합니다. OTel SDK로 계측하고 Collector에서 Datadog Exporter로 전송하면 됩니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;장점:&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;나중에 비용 문제로 오픈소스 백엔드로 전환할 때 코드 수정 없음&lt;/li&gt;
&lt;li&gt;멀티 벤더 전략 가능 (Traces는 Datadog, Metrics는 Prometheus)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;14&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;14. 자주 하는 실수&lt;/h2&gt;
&lt;h3 id=&quot;1-collector-sdk&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;1. Collector 없이 SDK에서 직접 백엔드로 전송&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;초기에는 간단해 보이지만, 백엔드 변경 시 모든 서비스의 환경 변수를 수정해야 합니다. Collector를 중간에 두면 설정 한 곳만 바꾸면 됩니다.&lt;/p&gt;
&lt;h3 id=&quot;2&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;2. 모든 속성을 무분별하게 추가&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Span에 속성을 많이 추가할수록 데이터 크기가 커집니다. 분석에 실제로 필요한 속성만 추가하고, Semantic Conventions를 따릅니다.&lt;/p&gt;
&lt;h3 id=&quot;3_1&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;3. 샘플링을 고려하지 않고 시작&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;개발 환경에서 100% 수집으로 시작했다가 프로덕션에서 트래픽이 늘어나면 비용이 폭발합니다. 처음부터 샘플링 정책을 설계합니다.&lt;/p&gt;
&lt;h3 id=&quot;4-traceparent&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;4. traceparent 전파를 확인하지 않음&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;SDK를 설치했지만 서비스 간 Trace가 연결되지 않는 경우가 많습니다. 프록시나 메시지 큐에서 traceparent 헤더가 전달되는지 확인해야 합니다.&lt;/p&gt;
&lt;h3 id=&quot;5-collector&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;5. Collector의 리소스 제한을 설정하지 않음&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Collector에 memory_limiter를 설정하지 않으면, 백엔드 장애 시 데이터가 Collector에 쌓여 OOM이 발생합니다. 반드시 리소스 제한을 설정합니다.&lt;/p&gt;
&lt;h3 id=&quot;6-auto-instrumentation&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;6. Auto-instrumentation만으로 충분하다고 생각&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Auto-instrumentation은 HTTP, DB, gRPC 같은 프레임워크 수준의 Span만 생성합니다. &quot;결제 검증에 3초 걸렸다&quot;를 확인하려면 비즈니스 로직에 Manual Span을 추가해야 합니다.&lt;/p&gt;
&lt;h2 id=&quot;15&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;15. 정리&lt;/h2&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;OpenTelemetry는 Observability 데이터를 벤더 중립적으로 수집하는 CNCF Graduated 표준입니다. 계측(SDK)과 백엔드(Storage)를 분리하여 도구 교체를 자유롭게 합니다.&lt;/li&gt;
&lt;li&gt;분산 트레이싱의 핵심은 Trace ID를 서비스 간 전파하여 하나의 요청 경로를 재구성하는 것입니다. W3C Trace Context(&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;traceparent&lt;/code&gt; 헤더)가 표준 전파 형식입니다.&lt;/li&gt;
&lt;li&gt;Collector는 Receiver &amp;rarr; Processor &amp;rarr; Exporter 파이프라인으로 구성됩니다. 샘플링, 필터링, 메타데이터 보강을 중앙에서 관리합니다.&lt;/li&gt;
&lt;li&gt;배포 패턴은 Agent(DaemonSet) + Gateway(Deployment) 조합이 운영 환경에서 적합합니다.&lt;/li&gt;
&lt;li&gt;비용 관리가 중요합니다. Tail-based Sampling(에러/지연 100% + 정상 10%)으로 중요한 데이터를 놓치지 않으면서 비용을 제어합니다.&lt;/li&gt;
&lt;li&gt;신규 서비스는 처음부터 OTel SDK로 계측하는 것이 유리합니다. 기존 서비스는 Auto-instrumentation으로 시작하고 점진적으로 Manual Span을 추가합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;_14&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;관련 글&lt;/h2&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://wero90.tistory.com/entry/Observability란-무엇인가-Monitoring-Logging-Tracing의-차이&quot;&gt;Observability란 무엇인가: Monitoring, Logging, Tracing의 차이&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wero90.tistory.com/entry/CloudWatch-vs-Datadog-vs-Grafana-모니터링-도구-선택-기준&quot;&gt;CloudWatch vs Datadog vs Grafana: 모니터링 도구 선택 기준&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wero90.tistory.com/entry/Prometheus-Grafana로-Kubernetes-모니터링-구성하기&quot;&gt;Prometheus + Grafana로 Kubernetes 모니터링 구성하기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wero90.tistory.com/entry/알림-설계-노이즈를-줄이고-의미-있는-알림만-받는-방법&quot;&gt;알림 설계: 노이즈를 줄이고 의미 있는 알림만 받는 방법&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;_15&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;참고 문서&lt;/h2&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://opentelemetry.io/docs/&quot;&gt;OpenTelemetry 공식 문서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.w3.org/TR/trace-context/&quot;&gt;W3C Trace Context Specification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://opentelemetry.io/docs/collector/architecture/&quot;&gt;OpenTelemetry Collector Architecture&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://opentelemetry.io/docs/specs/semconv/&quot;&gt;OpenTelemetry Semantic Conventions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.cncf.io/announcements/2026/05/21/cloud-native-computing-foundation-announces-opentelemetrys-graduation-solidifying-status-as-the-de-facto-observability-standard/&quot;&gt;CNCF OpenTelemetry Graduation Announcement (2026-05-21)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Observability</category>
      <category>CNCF</category>
      <category>collector</category>
      <category>Distributed Tracing</category>
      <category>Jaeger</category>
      <category>kubernetes</category>
      <category>Observability</category>
      <category>opentelemetry</category>
      <category>OTLP</category>
      <author>wero90</author>
      <guid isPermaLink="true">https://wero90.tistory.com/76</guid>
      <comments>https://wero90.tistory.com/76#entry76comment</comments>
      <pubDate>Mon, 8 Jun 2026 12:57:58 +0900</pubDate>
    </item>
    <item>
      <title>EKS vs AKS vs GKE: 매니지드 Kubernetes 비교</title>
      <link>https://wero90.tistory.com/75</link>
      <description>&lt;blockquote style=&quot;border-left: 4px solid #3b82f6; padding: 1rem 1.2rem; margin: 1.5rem 0; background: #f0f9ff; color: #1e40af; font-size: 1.05rem; line-height: 1.7; border-radius: 0 8px 8px 0;&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&quot;Kubernetes로 컨테이너를 운영하기로 했습니다. AWS, Azure, GCP 모두 매니지드 Kubernetes를 제공하는데, 어떤 걸 써야 하죠?&quot; 팀에서 이미 주로 사용하는 클라우드가 있다면 해당 벤더의 매니지드 서비스를 선택하는 것이 자연스럽습니다. 다만 각 서비스의 Control Plane 관리 방식, 비용 구조, 네트워킹 모델, 운영 편의성에는 분명한 차이가 있습니다. 이 차이를 이해해야 클러스터 설계 단계에서 운영 비용과 복잡도를 예측할 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div style=&quot;display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 1.2rem 0;&quot;&gt;&lt;span style=&quot;display: inline-block; padding: 0.3rem 0.7rem; border-radius: 999px; background: #f1f5f9; color: #475569; font-size: 0.85rem; line-height: 1.4;&quot;&gt;Kubernetes&lt;/span&gt; &lt;span style=&quot;display: inline-block; padding: 0.3rem 0.7rem; border-radius: 999px; background: #f1f5f9; color: #475569; font-size: 0.85rem; line-height: 1.4;&quot;&gt;Comparison&lt;/span&gt; &lt;span style=&quot;display: inline-block; padding: 0.3rem 0.7rem; border-radius: 999px; background: #f1f5f9; color: #475569; font-size: 0.85rem; line-height: 1.4;&quot;&gt;Level 2&lt;/span&gt; &lt;span style=&quot;display: inline-block; padding: 0.3rem 0.7rem; border-radius: 999px; background: #f1f5f9; color: #475569; font-size: 0.85rem; line-height: 1.4;&quot;&gt;20분&lt;/span&gt;&lt;/div&gt;
&lt;h2 id=&quot;_1&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;요약&lt;/h2&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;기준&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;EKS (AWS)&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;AKS (Azure)&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;GKE (GCP)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Control Plane 비용&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;$0.10/시간 (~$73/월)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;무료 (Standard), $0.10/시간 (Uptime SLA 옵션)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;무료 (1 클러스터), $0.10/시간 (Standard)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;기본 K8s 버전 관리&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;수동 업그레이드 (13개월 지원)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;수동 또는 자동 업그레이드 선택&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;자동 업그레이드 (Release Channel)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;서버리스 노드 옵션&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Fargate Profile&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Virtual Nodes (ACI 기반)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Autopilot 모드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;네트워킹 모델&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;VPC CNI (Pod에 VPC IP 할당)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Azure CNI 또는 kubenet&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;VPC-native (Alias IP)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;IAM 통합&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;IRSA / Pod Identity&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Workload Identity (Managed Identity)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Workload Identity (GCP SA 바인딩)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;노드 자동 스케일링&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Karpenter 또는 Cluster Autoscaler&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Cluster Autoscaler (AKS 내장)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Node Auto-provisioning 또는 Autopilot&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;운영 복잡도&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;높음 (Add-on 수동 관리 많음)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;중간 (Azure Portal 통합)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;낮음 (Autopilot 시 대부분 자동)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;멀티 클러스터 관리&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;별도 구성 필요&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Azure Arc / Fleet Manager&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;GKE Fleet&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;1-control-plane&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;1. 아키텍처 비교: Control Plane 관리 방식&lt;/h2&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;세 서비스 모두 Control Plane(API Server, etcd, Controller Manager, Scheduler)을 벤더가 관리합니다. 사용자는 Worker Node만 관리하면 됩니다. 다만 관리의 깊이와 자동화 수준이 다릅니다.&lt;/p&gt;
&lt;figure style=&quot;text-align: center; margin: 2rem 0;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dna/dHsqkI/dJMcacwDkiJ/AAAAAAAAAAAAAAAAAAAAAAr3ootzPMPSOGHvmHzi11_9FqyasLSXryn8mjNHe7Xp/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1782831599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=C6yvXzsswLmqSpdvB7qQ9bAEVKg%3D&quot; alt=&quot;EKS vs AKS vs GKE 아키텍처 비교&quot; /&gt;
&lt;figcaption style=&quot;margin-top: 0.5rem; font-size: 0.85rem; color: #64748b;&quot;&gt;EKS vs AKS vs GKE 아키텍처 비교&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;eks-control-plane-vpc&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;EKS &amp;mdash; Control Plane을 사용자 VPC에 연결&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;EKS는 Control Plane을 AWS 관리 계정에서 실행하고, 사용자 VPC에 ENI(Elastic Network Interface)를 생성하여 연결합니다.&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;API Server Endpoint: Public, Private, 또는 Public+Private 선택 가능&lt;/li&gt;
&lt;li&gt;etcd: AWS가 관리하는 별도 계정에서 실행, 암호화 적용&lt;/li&gt;
&lt;li&gt;Worker Node와 Control Plane 통신: 사용자 VPC 내 ENI를 통해 프라이빗 통신 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;특징적인 점&lt;/b&gt;: EKS는 Control Plane의 네트워크 노출 범위를 사용자가 직접 제어할 수 있습니다. Private Endpoint만 활성화하면 VPC 외부에서 API Server에 접근할 수 없어, 보안 요구사항이 높은 환경에서 유리합니다.&lt;/p&gt;
&lt;h3 id=&quot;aks&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;AKS &amp;mdash; 관리형 리소스 그룹 분리&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;AKS는 클러스터 생성 시 두 개의 리소스 그룹을 사용합니다.&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자 리소스 그룹: 사용자가 직접 관리하는 AKS 리소스&lt;/li&gt;
&lt;li&gt;노드 리소스 그룹 (&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;MC_*&lt;/code&gt;): AKS가 자동 생성하는 인프라 리소스 (VM, Disk, NIC, NSG)&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Control Plane은 Azure가 완전히 관리하며 사용자에게 노출되지 않습니다. API Server는 기본적으로 Public Endpoint이며, Private Cluster 옵션으로 Private Endpoint를 활성화할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;특징적인 점&lt;/b&gt;: AKS는 무료 티어에서도 Control Plane SLA 없이 운영이 가능합니다. 프로덕션 환경에서 99.95% SLA가 필요하면 Standard 또는 Premium 티어를 선택해야 합니다.&lt;/p&gt;
&lt;h3 id=&quot;gke&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;GKE &amp;mdash; 가장 성숙한 자동화&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;GKE는 Google이 Kubernetes를 직접 개발한 만큼, 가장 긴밀하게 통합된 관리 환경을 제공합니다.&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Standard 모드&lt;/b&gt;: 사용자가 Node Pool을 관리 (EKS, AKS와 유사)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Autopilot 모드&lt;/b&gt;: Node 관리까지 GKE가 자동 처리 (Pod 수준에서만 관리)&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Autopilot은 노드 프로비저닝, OS 패치, 보안 설정을 모두 GKE가 처리합니다. 사용자는 Pod manifest만 배포하면 됩니다. 다만 Autopilot에서는 특권 컨테이너가 기본적으로 차단되며, DaemonSet도 allowlist를 통해 선택적으로만 허용됩니다. 보안 강화를 위해 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;privileged: true&lt;/code&gt;나 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;NET_RAW&lt;/code&gt;, &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;SYS_ADMIN&lt;/code&gt; 같은 Linux capability가 제한됩니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;특징적인 점&lt;/b&gt;: GKE는 Release Channel(Rapid, Regular, Stable)을 통해 Kubernetes 버전 자동 업그레이드를 기본으로 제공합니다. 버전 관리에 대한 운영 부담이 세 서비스 중 가장 적습니다.&lt;/p&gt;
&lt;h2 id=&quot;2&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;2. 비용 구조 비교&lt;/h2&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;매니지드 Kubernetes의 비용은 크게 &lt;b&gt;Control Plane 비용&lt;/b&gt;과 &lt;b&gt;Worker Node(컴퓨팅) 비용&lt;/b&gt;으로 나뉩니다. 동일한 워크로드를 운영해도 비용 구조의 차이로 인해 총 비용이 달라질 수 있습니다.&lt;/p&gt;
&lt;figure style=&quot;text-align: center; margin: 2rem 0;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dna/ckG7iz/dJMcafGUmJx/AAAAAAAAAAAAAAAAAAAAABbKKjWrcr9My_55pvAYuh_JhxfyZz2jcoLCY3CAI-HR/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1782831599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=uY5GS68nQkpwjDfBRdZn6jsvRi8%3D&quot; alt=&quot;EKS vs AKS vs GKE 비용 구조 비교&quot; /&gt;
&lt;figcaption style=&quot;margin-top: 0.5rem; font-size: 0.85rem; color: #64748b;&quot;&gt;EKS vs AKS vs GKE 비용 구조 비교&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;control-plane&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;Control Plane 비용&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;서비스&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;무료 티어&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;유료 티어&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;EKS&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;없음&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;$0.10/시간 (~$73/월) 고정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;AKS&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Free 티어 (SLA 없음)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Standard $0.10/시간, Premium $0.10/시간 + 추가 기능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;GKE&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Autopilot/Standard 1개 클러스터 존별 무료&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Standard: $0.10/시간 (~$73/월)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실무 시나리오&lt;/b&gt;: 개발/테스트용 클러스터 3개 + 프로덕션 클러스터 2개를 운영한다면?&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;EKS&lt;/b&gt;: 5 &amp;times; $73 = &lt;b&gt;$365/월&lt;/b&gt; (Control Plane만)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;AKS&lt;/b&gt;: Free 티어 3개(dev) + Standard 2개(prod) = 2 &amp;times; $73 = &lt;b&gt;$146/월&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;GKE&lt;/b&gt;: 무료 1개 + 4 &amp;times; $73 = &lt;b&gt;$292/월&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;AKS의 Free 티어는 SLA를 제공하지 않으므로 프로덕션에는 부적합하지만, 개발/테스트 클러스터에 활용하면 비용을 절감할 수 있습니다.&lt;/p&gt;
&lt;h3 id=&quot;worker-node&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;Worker Node 비용&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Worker Node 비용은 클라우드별 VM 가격에 따르며, 이것이 전체 Kubernetes 비용의 대부분(80~90%)을 차지합니다. 동일 스펙 VM의 가격은 리전과 프로모션에 따라 달라지므로 단순 비교가 어렵지만, 할인 프로그램에 차이가 있습니다.&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;서비스&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;Spot/Preemptible&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;예약 할인&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;자동 할인&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;EKS&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Spot Instance (최대 ~90% 할인)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Reserved Instance, Savings Plan&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;AKS&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Spot VM (최대 ~90% 할인)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Reserved VM Instance&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;GKE&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Spot VM (최대 ~91% 할인)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Committed Use Discount (1년/3년)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Sustained Use Discount (자동 적용)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;GKE의 Sustained Use Discount는 월 사용량에 따라 자동으로 할인이 적용됩니다. 별도 약정 없이도 장기 실행 워크로드의 비용이 줄어드는 점이 차별화됩니다.&lt;/p&gt;
&lt;h3 id=&quot;_2&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;서버리스 노드 옵션 비용&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;노드 관리 없이 Pod 단위로 비용을 지불하는 옵션도 비교해야 합니다.&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;서비스&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;옵션&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;과금 방식&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;적합한 상황&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;EKS&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Fargate&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;vCPU/메모리 &amp;times; 초 단위&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;배치 작업, 간헐적 워크로드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;AKS&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Virtual Nodes (ACI)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;vCPU/메모리 &amp;times; 초 단위&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;버스트 트래픽 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;GKE&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Autopilot&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Pod 요청 리소스 &amp;times; 초 단위&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;노드 관리 부담을 완전히 제거하고 싶은 팀&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div style=&quot;padding: 1rem 1.2rem; margin: 1.5rem 0; border-radius: 8px; border-left: 4px solid #3b82f6; background: #eff6ff; line-height: 1.7;&quot;&gt;&lt;b&gt;비용 최적화 관점&lt;/b&gt;&lt;br /&gt;Control Plane 비용보다 Worker Node 비용이 훨씬 큽니다. 서비스 선택 시 Control Plane 비용 차이($73/월)보다 해당 클라우드의 VM 가격, 할인 프로그램, 팀의 기존 예약 계약을 더 중요하게 고려해야 합니다.&lt;/div&gt;
&lt;h2 id=&quot;3&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;3. 네트워킹 모델 비교&lt;/h2&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Kubernetes 네트워킹은 &quot;모든 Pod가 다른 모든 Pod와 NAT 없이 통신할 수 있어야 한다&quot;는 요구사항을 충족해야 합니다. 세 서비스는 이를 구현하는 방식이 다르며, 이 차이가 IP 주소 관리, 네트워크 정책, VPC 통합에 영향을 미칩니다.&lt;/p&gt;
&lt;figure style=&quot;text-align: center; margin: 2rem 0;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dna/ctUlYl/dJMcagyZUOZ/AAAAAAAAAAAAAAAAAAAAAHEFpf7_yWYd03DfZpX558_CAo8PLmXKNeKYpDBSlp4R/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1782831599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=FsVFUAGJW40y9G8SezrnwJ4GRYc%3D&quot; alt=&quot;EKS vs AKS vs GKE 네트워킹 모델 비교&quot; /&gt;
&lt;figcaption style=&quot;margin-top: 0.5rem; font-size: 0.85rem; color: #64748b;&quot;&gt;EKS vs AKS vs GKE 네트워킹 모델 비교&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;eks-vpc-cni&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;EKS &amp;mdash; VPC CNI&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;EKS의 기본 CNI(Container Network Interface)인 VPC CNI는 각 Pod에 VPC 서브넷의 실제 IP를 할당합니다.&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Pod IP = VPC 내 실제 ENI의 Secondary IP&lt;/li&gt;
&lt;li&gt;VPC 내 다른 리소스(EC2, RDS, Lambda)와 직접 통신 가능 (별도 라우팅 불필요)&lt;/li&gt;
&lt;li&gt;Security Group을 Pod 단위로 적용 가능 (Security Group for Pods)&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;trade-off&lt;/b&gt;:&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;장점&lt;/b&gt;: Pod가 VPC 네이티브 IP를 사용하므로, VPC Flow Logs, Security Group, Route Table 등 기존 AWS 네트워크 도구를 그대로 활용할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;단점&lt;/b&gt;: 서브넷의 IP 주소를 Pod가 소비합니다. Pod 수가 많으면 IP 고갈 문제가 발생할 수 있습니다. 대규모 클러스터에서는 서브넷 CIDR 설계가 중요합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# IP 고갈 방지: 별도 CIDR을 Pod 전용으로 할당
aws ec2 associate-vpc-cidr-block --vpc-id vpc-xxx --cidr-block 100.64.0.0/16
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;aks-azure-cni-vs-kubenet&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;AKS &amp;mdash; Azure CNI vs kubenet&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;AKS는 두 가지 네트워킹 모델을 제공합니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Azure CNI (기본 권장):&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Pod에 VNet 서브넷의 실제 IP를 할당 (EKS VPC CNI와 유사)&lt;/li&gt;
&lt;li&gt;Azure 네트워크 도구(NSG, UDR, Private Endpoint)와 직접 통합&lt;/li&gt;
&lt;li&gt;IP 소비량이 크므로 서브넷 CIDR 계획이 필수&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;kubenet:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Node에만 VNet IP를 할당하고, Pod는 Node 내부의 Overlay 네트워크 IP를 사용&lt;/li&gt;
&lt;li&gt;UDR(User Defined Route)로 노드 간 통신을 라우팅&lt;/li&gt;
&lt;li&gt;IP 소비가 적지만, Azure 네트워크 기능과의 통합이 제한적&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Azure CNI Overlay (GA, 현재 AKS 기본 IPAM 모드):&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Pod에 Overlay 네트워크 IP를 사용하되, Azure CNI의 네트워크 기능은 유지&lt;/li&gt;
&lt;li&gt;VNet IP 소비 없이 대규모 클러스터 운영 가능&lt;/li&gt;
&lt;li&gt;기본 Pod CIDR: 10.244.0.0/16 (65,536 IP), 노드당 /24 할당으로 최대 256개 노드 스케일링&lt;/li&gt;
&lt;li&gt;기존 kubenet 클러스터에서 Azure CNI Overlay로의 마이그레이션 경로 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;gke-vpc-native-alias-ip&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;GKE &amp;mdash; VPC-native (Alias IP)&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;GKE의 VPC-native 모드는 각 Node에 Secondary IP Range(Alias IP)를 할당하고, 해당 범위에서 Pod IP를 배분합니다.&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Pod IP는 VPC의 Secondary Range에서 할당&lt;/li&gt;
&lt;li&gt;GCP의 네트워크 도구(Firewall Rule, Cloud NAT, VPC Flow Logs)와 통합&lt;/li&gt;
&lt;li&gt;IP 범위를 Node Pool별로 분리할 수 있어 관리가 용이&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;trade-off&lt;/b&gt;:&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;장점&lt;/b&gt;: Pod CIDR을 VPC와 분리하여 설계할 수 있어, IP 고갈 문제가 EKS보다 적습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;단점&lt;/b&gt;: Alias IP의 최대 Pod 수 제한 (노드당 기본 110개)이 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;_3&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;네트워킹 모델 비교 요약&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;항목&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;EKS (VPC CNI)&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;AKS (Azure CNI)&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;GKE (VPC-native)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Pod IP 유형&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;VPC 실제 IP&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;VNet 실제 IP&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;VPC Secondary Range IP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;IP 소비&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;높음 (Pod당 VPC IP 1개)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;높음 (CNI) / 낮음 (kubenet, CNI Overlay)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;중간 (Secondary Range 분리)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;VPC/VNet 통합&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;높음&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;높음 (CNI) / 제한적 (kubenet)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;높음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Network Policy 엔진&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Calico (Add-on)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Calico 또는 Azure NPM&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Calico 또는 Dataplane V2 (Cilium 기반)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;노드당 최대 Pod 수&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;인스턴스 타입에 따라 다름 (ENI &amp;times; IP 수)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;250개 (CNI) / 110개 (kubenet)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;110개 (기본)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;4-iam&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;4. IAM 통합과 보안&lt;/h2&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Kubernetes 워크로드에서 클라우드 리소스(S3, Blob Storage, GCS 등)에 접근해야 할 때, 각 서비스의 IAM 통합 방식이 달라집니다.&lt;/p&gt;
&lt;h3 id=&quot;eks-irsa-pod-identity&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;EKS &amp;mdash; IRSA와 Pod Identity&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;EKS는 Kubernetes ServiceAccount에 IAM Role을 연결하는 두 가지 방식을 제공합니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;IRSA (IAM Roles for Service Accounts):&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;OIDC Provider를 설정하여 K8s ServiceAccount와 IAM Role을 연결&lt;/li&gt;
&lt;li&gt;Pod가 AWS STS를 통해 임시 자격 증명을 획득&lt;/li&gt;
&lt;li&gt;설정이 다소 복잡하지만 세밀한 권한 제어 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;EKS Pod Identity (GA, 신규 클러스터 권장):&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;IRSA보다 설정이 간단한 방식으로, 2023년 말 출시 후 GA 상태&lt;/li&gt;
&lt;li&gt;EKS Add-on으로 설치하고 Association을 생성하면 완료 (OIDC Provider 설정 불필요)&lt;/li&gt;
&lt;li&gt;Trust Policy가 범용적이어 계정/클러스터마다 재작성할 필요 없음&lt;/li&gt;
&lt;li&gt;세션 태그가 자동 부여되어 ABAC(Attribute-Based Access Control) 활용 가능&lt;/li&gt;
&lt;li&gt;IRSA도 계속 지원되지만, 신규 설정에는 Pod Identity가 권장됨&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;aks-workload-identity&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;AKS &amp;mdash; Workload Identity&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;AKS는 Azure AD(Entra ID)의 Workload Identity를 사용합니다.&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Kubernetes ServiceAccount에 Azure Managed Identity를 연결&lt;/li&gt;
&lt;li&gt;Federated Credential을 통해 토큰 교환&lt;/li&gt;
&lt;li&gt;Azure 리소스(Key Vault, Storage, Cosmos DB 등)에 비밀번호 없이 접근 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;설정이 비교적 직관적이며, Azure Portal에서 시각적으로 관리할 수 있습니다.&lt;/p&gt;
&lt;h3 id=&quot;gke-workload-identity-federation&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;GKE &amp;mdash; Workload Identity Federation&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;GKE의 Workload Identity는 Kubernetes ServiceAccount를 GCP IAM Service Account에 매핑합니다.&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Node의 기본 Service Account 대신 Pod별 권한 분리 가능&lt;/li&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;iam.gke.io/gcp-service-account&lt;/code&gt; annotation으로 매핑&lt;/li&gt;
&lt;li&gt;GCP 리소스에 최소 권한으로 접근&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;GKE는 Workload Identity가 가장 일찍 도입되어 성숙도가 높고, 설정 과정이 비교적 단순합니다.&lt;/p&gt;
&lt;h3 id=&quot;_4&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;보안 기능 비교&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;기능&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;EKS&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;AKS&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;GKE&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Pod 수준 IAM&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;IRSA / Pod Identity&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Workload Identity&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Workload Identity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Node 격리&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Managed Node Group + Launch Template&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Node Pool + VMSS&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Node Pool + Shielded GKE Nodes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;이미지 검증&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;ECR 스캔 + 서명 (별도 구성)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;ACR + Defender for Containers&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Binary Authorization (네이티브)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;네트워크 정책&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Calico (Add-on 설치 필요)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Calico 또는 Azure NPM (생성 시 활성화)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Dataplane V2 (기본 활성화 가능)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Secret 암호화&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;KMS envelope encryption (수동 설정)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Azure Key Vault 통합&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Cloud KMS 통합 + Application-layer encryption&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div style=&quot;padding: 1rem 1.2rem; margin: 1.5rem 0; border-radius: 8px; border-left: 4px solid #ef4444; background: #fef2f2; line-height: 1.7;&quot;&gt;&lt;b&gt;보안 관점&lt;/b&gt;&lt;br /&gt;세 서비스 모두 Pod 수준의 IAM 분리를 지원합니다. 다만 Node의 기본 Service Account/Instance Profile에 과도한 권한이 있으면, Pod가 metadata endpoint를 통해 Node 권한을 획득할 수 있습니다. Workload Identity를 활성화하고, Node의 기본 권한은 최소화해야 합니다.&lt;/div&gt;
&lt;h2 id=&quot;5&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;5. 운영 편의성과 업그레이드&lt;/h2&gt;
&lt;h3 id=&quot;kubernetes&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;Kubernetes 버전 관리&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;항목&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;EKS&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;AKS&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;GKE&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;지원 버전 수&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;4개 (일반적으로 최근 4개 minor 버전)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;3~4개&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;3개 (Release Channel 기준)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;자동 업그레이드&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;없음 (수동)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;선택 가능 (Planned Maintenance)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;기본 활성화 (Release Channel)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;지원 종료 후&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Extended Support ($0.60/시간 추가)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;자동 업그레이드 강제&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Release Channel에서 자동 진행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;업그레이드 방식&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;In-place (Control Plane &amp;rarr; Node Group 순차)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;In-place (Surge 업그레이드)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;In-place (Surge 또는 Blue-Green)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실무 시나리오&lt;/b&gt;: Kubernetes 1.28에서 1.29로 업그레이드해야 합니다.&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;EKS&lt;/b&gt;: Control Plane 업그레이드(CLI/콘솔, ~20분) &amp;rarr; 각 Node Group을 순차적으로 업그레이드. Add-on(CoreDNS, kube-proxy, VPC CNI)도 별도로 업데이트해야 합니다. 가장 수동 작업이 많습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;AKS&lt;/b&gt;: 클러스터 업그레이드 명령 한 번으로 Control Plane + Node Pool 순차 업그레이드 가능. &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;az aks upgrade&lt;/code&gt; 또는 자동 업그레이드 정책으로 처리합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;GKE&lt;/b&gt;: Release Channel에 따라 자동으로 진행됩니다. Maintenance Window 내에서 Control Plane &amp;rarr; Node Pool 순서로 업그레이드됩니다. 사용자가 개입할 일이 가장 적습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;add-on&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;Add-on 관리&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;Add-on 유형&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;EKS&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;AKS&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;GKE&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;CNI&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;VPC CNI (EKS Add-on)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Azure CNI (기본 내장)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;VPC-native (기본 내장)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;DNS&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;CoreDNS (EKS Add-on)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;CoreDNS (내장)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;kube-dns (내장)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Ingress Controller&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;AWS LB Controller (별도 설치)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;AGIC 또는 Nginx (Add-on)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;GKE Ingress Controller (내장)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Monitoring&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;CloudWatch Agent (별도)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Container Insights (활성화)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Cloud Operations (기본 활성화)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Service Mesh&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;App Mesh 또는 Istio (별도)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Istio-based (Add-on 프리뷰)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Anthos Service Mesh (Add-on)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;EKS의 운영 부담이 큰 이유&lt;/b&gt;: EKS는 &quot;필요한 것을 직접 설치&quot;하는 철학입니다. Ingress Controller, External DNS, Cert Manager, Metrics Server, CSI Driver 등을 모두 사용자가 설치하고 버전을 관리해야 합니다. 유연하지만 운영 부담이 큽니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GKE의 운영이 편한 이유&lt;/b&gt;: GKE는 대부분의 기능이 내장되어 있거나 체크박스 하나로 활성화됩니다. Kubernetes를 처음 운영하는 팀에게는 GKE의 자동화 수준이 진입 장벽을 크게 낮춥니다.&lt;/p&gt;
&lt;h2 id=&quot;6&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;6. 노드 스케일링 전략&lt;/h2&gt;
&lt;h3 id=&quot;_5&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;기본 스케일링 옵션&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;서비스&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;기본 도구&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;고급 옵션&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;서버리스&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;EKS&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Cluster Autoscaler&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Karpenter&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Fargate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;AKS&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Cluster Autoscaler (내장)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;KEDA (이벤트 기반)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Virtual Nodes (ACI)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;GKE&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Cluster Autoscaler (내장)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Node Auto-provisioning (NAP)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Autopilot 모드&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;eks-karpenter&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;EKS + Karpenter&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Karpenter는 AWS에서 개발한 노드 스케일러로, Cluster Autoscaler보다 빠르고 유연합니다.&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Pod 요구사항에 맞는 최적의 인스턴스 타입을 자동 선택&lt;/li&gt;
&lt;li&gt;Node Group 없이 직접 EC2를 프로비저닝&lt;/li&gt;
&lt;li&gt;30초~1분 내에 노드 추가 가능 (Cluster Autoscaler는 2~5분)&lt;/li&gt;
&lt;li&gt;Spot Instance와의 통합이 용이&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;gke-autopilot&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;GKE Autopilot&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Autopilot은 노드 관리를 완전히 제거합니다.&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자는 Node Pool, 인스턴스 타입, OS 업데이트를 관리하지 않음&lt;/li&gt;
&lt;li&gt;Pod의 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;resources.requests&lt;/code&gt; 기반으로 과금&lt;/li&gt;
&lt;li&gt;보안 설정(Shielded Nodes, Workload Identity)이 기본 적용&lt;/li&gt;
&lt;li&gt;다만 DaemonSet, 특권 컨테이너 등 일부 기능에 제약 존재&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;_6&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;스케일링 속도 비교&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;시나리오&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;EKS (Karpenter)&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;AKS (CA)&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;GKE (NAP)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;신규 노드 추가&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;~30초~1분&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;~2~5분&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;~1~2분&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;스케일 다운&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;빈 노드 즉시 제거&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;10분 대기 후 제거&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;빈 노드 즉시 제거&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;인스턴스 타입 선택&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;자동 (다양한 타입 혼합)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Node Pool에 고정&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;자동 (NAP)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;7&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;7. 선택 기준: 어떤 상황에서 어떤 서비스를 고르는가&lt;/h2&gt;
&lt;figure style=&quot;text-align: center; margin: 2rem 0;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dna/PvIoA/dJMcaglsEfZ/AAAAAAAAAAAAAAAAAAAAAIMrk3uhYWlrErUArXLM9OouKcyebAzj-AwEz7B1q3pv/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1782831599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=TLCP%2FlNIwcq6fMEr1C4hLKCnGZc%3D&quot; alt=&quot;EKS vs AKS vs GKE 선택 의사결정&quot; /&gt;
&lt;figcaption style=&quot;margin-top: 0.5rem; font-size: 0.85rem; color: #64748b;&quot;&gt;EKS vs AKS vs GKE 선택 의사결정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;_7&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;팀 상황별 권장&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;EKS를 선택하는 경우:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;팀이 이미 AWS 서비스(RDS, S3, SQS 등)를 중심으로 아키텍처를 구성하고 있음&lt;/li&gt;
&lt;li&gt;Kubernetes 운영 경험이 있고, 세밀한 커스터마이징이 필요함&lt;/li&gt;
&lt;li&gt;Karpenter를 활용한 고급 노드 스케일링이 필요함&lt;/li&gt;
&lt;li&gt;IAM, VPC, Security Group 등 AWS 네트워크/보안 도구와 깊은 통합이 필요함&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AKS를 선택하는 경우:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;팀이 Azure 환경(Active Directory, Azure DevOps, Key Vault 등)을 사용 중임&lt;/li&gt;
&lt;li&gt;.NET 기반 워크로드가 있거나, Windows 컨테이너를 운영해야 함&lt;/li&gt;
&lt;li&gt;Control Plane 비용을 절감하고 싶음 (Free 티어 활용)&lt;/li&gt;
&lt;li&gt;Azure Portal에서 시각적으로 클러스터를 관리하고 싶음&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GKE를 선택하는 경우:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Kubernetes 운영 경험이 적고, 관리 부담을 최소화하고 싶음&lt;/li&gt;
&lt;li&gt;Autopilot 모드로 노드 관리를 완전히 위임하고 싶음&lt;/li&gt;
&lt;li&gt;데이터 분석/ML 워크로드와 연계해야 함 (BigQuery, Vertex AI)&lt;/li&gt;
&lt;li&gt;버전 업그레이드, 보안 패치를 자동으로 처리하고 싶음&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;_8&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;멀티 클라우드 고려사항&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&quot;멀티 클라우드를 위해 Kubernetes를 선택했으니 어떤 벤더든 상관없다&quot;는 이론과 현실은 다릅니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;실제로는 각 서비스가 클라우드 네이티브 기능(IAM, 네트워킹, 스토리지, LB)과 깊게 결합되어 있어, 클러스터를 다른 벤더로 옮기는 것은 상당한 작업을 필요로 합니다. Kubernetes manifest(Deployment, Service 등)는 이식 가능하지만, 인프라 통합 부분(Ingress annotation, StorageClass, IAM binding)은 벤더별로 다시 작성해야 합니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;멀티 클라우드가 실제로 필요한 경우라면, 벤더 종속 부분을 추상화하는 계층(예: Crossplane, Terraform, 표준화된 Helm chart)을 도입하는 것이 현실적인 접근입니다.&lt;/p&gt;
&lt;h2 id=&quot;8&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;8. 실무 사용 사례&lt;/h2&gt;
&lt;h3 id=&quot;50-msa-15&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;사례: 50인 규모 스타트업, MSA 15개 서비스 운영&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;상황&lt;/b&gt;:&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;팀: DevOps 2명, 백엔드 개발자 10명&lt;/li&gt;
&lt;li&gt;워크로드: 웹 서비스 15개 (Python, Node.js), 일일 트래픽 변동 큼&lt;/li&gt;
&lt;li&gt;이미 AWS를 주력으로 사용, RDS, S3, SQS 의존&lt;/li&gt;
&lt;li&gt;Kubernetes 운영 경험: DevOps 팀 2명만 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;선택: EKS + Karpenter&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;결정 요소&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;이유&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;AWS 종속&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;RDS, S3, SQS와의 통합이 이미 깊고, 다른 클라우드로 이전 계획 없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Karpenter&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;트래픽 변동이 커서 빠른 스케일링 필요. Spot Instance 활용으로 비용 40% 절감&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;IRSA&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;서비스별 S3 버킷, SQS 큐 접근 권한을 Pod 단위로 분리&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;고려한 대안&lt;/b&gt;: GKE Autopilot도 검토했지만, AWS 서비스와의 통합 비용(VPN, 지연)이 더 컸습니다.&lt;/p&gt;
&lt;h3 id=&quot;azure-windows-linux&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;사례: 대기업 Azure 환경, Windows + Linux 혼합 워크로드&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;상황&lt;/b&gt;:&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;조직: Azure AD로 전사 인증 관리, Azure DevOps 파이프라인 사용 중&lt;/li&gt;
&lt;li&gt;워크로드: .NET 레거시(Windows 컨테이너) + 신규 서비스(Linux)&lt;/li&gt;
&lt;li&gt;보안: 금융 규제로 Private Cluster 필수, Key Vault 중앙 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;선택: AKS&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;결정 요소&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;이유&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Windows 노드 지원&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;AKS는 Windows Node Pool을 네이티브로 지원, 혼합 워크로드 운영 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Azure AD 통합&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;RBAC를 Azure AD 그룹과 연동하여 전사 인증 체계 유지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Private Cluster&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Azure Private Link/Endpoint와의 네이티브 통합으로 네트워크 격리 용이&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;비용&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Free 티어로 dev/staging 클러스터 비용 절감&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;ml-gpu&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;사례: ML 팀, GPU 워크로드 + 최소 운영 부담&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;상황&lt;/b&gt;:&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;팀: ML 엔지니어 5명 (K8s 운영 경험 없음)&lt;/li&gt;
&lt;li&gt;워크로드: 모델 학습(GPU), 추론 서비스(CPU), 데이터 전처리(BigQuery)&lt;/li&gt;
&lt;li&gt;요구사항: K8s 운영에 시간을 쓰고 싶지 않음, 모델 개발에 집중하고 싶음&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;선택: GKE Autopilot&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;결정 요소&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;이유&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Autopilot&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;노드 관리, OS 패치, 보안 설정을 GKE가 자동 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;GPU 지원&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Autopilot에서 GPU Pod 요청 시 자동으로 GPU 노드 프로비저닝&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;BigQuery/Vertex AI 통합&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;같은 GCP 내에서 데이터 파이프라인과 자연스럽게 연결&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Workload Identity&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;GCS, BigQuery 접근 시 키 파일 없이 안전하게 인증&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;9&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;9. 주의할 점과 일반적인 실수&lt;/h2&gt;
&lt;h3 id=&quot;1-kubernetes&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;1. &quot;Kubernetes니까 어디서든 동일하게 동작한다&quot;는 가정&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Kubernetes API는 표준이지만, 아래 영역은 벤더마다 다릅니다:&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Ingress Controller 동작 방식과 annotation&lt;/li&gt;
&lt;li&gt;StorageClass와 Persistent Volume 프로비저너&lt;/li&gt;
&lt;li&gt;IAM/RBAC 통합 방식&lt;/li&gt;
&lt;li&gt;Load Balancer 타입과 설정&lt;/li&gt;
&lt;li&gt;Node 스케일링 동작&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;2-control-plane&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;2. Control Plane 비용만 보고 결정&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Control Plane 비용 차이는 월 $73 수준입니다. 실제 총 비용의 80~90%는 Worker Node입니다. 비용 비교는 VM 가격, 할인 프로그램, 네트워크 전송 비용을 포함하여 전체적으로 해야 합니다.&lt;/p&gt;
&lt;h3 id=&quot;3_1&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;3. 버전 업그레이드 전략 없이 클러스터 운영&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;EKS는 버전 지원이 종료되면 자동 업그레이드가 아닌 Extended Support(추가 비용)로 전환됩니다. 업그레이드를 미루면 비용이 증가하고, 여러 버전을 한 번에 건너뛰기 어려워집니다. 클러스터 생성 시점부터 업그레이드 주기를 계획해야 합니다.&lt;/p&gt;
&lt;h3 id=&quot;4&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;4. 서버리스 노드 옵션의 제약을 모르고 선택&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Fargate, Virtual Nodes, Autopilot 모두 일반 VM 노드 대비 제약이 있습니다:&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DaemonSet 불가 또는 제한&lt;/li&gt;
&lt;li&gt;hostNetwork/hostPort 사용 불가&lt;/li&gt;
&lt;li&gt;특권 컨테이너 제한&lt;/li&gt;
&lt;li&gt;GPU 지원 범위 차이&lt;/li&gt;
&lt;li&gt;노드 레벨 커스터마이징 불가&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;운영하려는 워크로드가 이 제약에 영향을 받는지 사전에 확인해야 합니다.&lt;/p&gt;
&lt;h2 id=&quot;10&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;10. 정리&lt;/h2&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;선택 기준&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;EKS&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;AKS&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;GKE&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;이미 사용 중인 클라우드&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;AWS 중심&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Azure 중심&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;GCP 중심&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;팀 K8s 경험&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;중~상&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;초~중&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;초~중&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;운영 자동화 수준&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;직접 구성&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;중간&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;높음 (Autopilot)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;노드 스케일링&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Karpenter (가장 유연)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;기본 CA&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;NAP / Autopilot&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;비용 효율&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Spot + Karpenter 조합&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Free 티어 + Reserved VM&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Sustained Use + Autopilot&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Windows 컨테이너&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;지원 (제한적)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;네이티브 지원 (강점)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;지원 (제한적)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;보안 자동화&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;수동 설정 많음&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;중간&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;높음 (Shielded Nodes 기본)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결론적으로&lt;/b&gt;: 대부분의 경우, 팀이 이미 사용하고 있는 클라우드의 매니지드 Kubernetes를 선택하는 것이 가장 합리적입니다. 클라우드 간 서비스 간 통합(IAM, 네트워킹, 스토리지)이 매니지드 K8s의 핵심 가치이기 때문입니다. 다만 Kubernetes 운영 경험이 없는 팀이라면 GKE Autopilot이, 세밀한 커스터마이징이 필요한 팀이라면 EKS가, Azure 생태계에 깊이 투자한 조직이라면 AKS가 각각 강점을 보입니다.&lt;/p&gt;
&lt;h2 id=&quot;_9&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;관련 글&lt;/h2&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;../kubernetes-architecture-overview&quot;&gt;Kubernetes 아키텍처 기본 구조: Control Plane과 Worker Node&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;../../cloud/aws/aws-ecs-vs-eks&quot;&gt;AWS ECS vs EKS 차이: 컨테이너 오케스트레이션 선택 기준&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;../kubernetes-service-types&quot;&gt;Kubernetes Service 종류: ClusterIP, NodePort, LoadBalancer, Ingress 비교&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;_10&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;참고 문서&lt;/h2&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.aws.amazon.com/eks/latest/userguide/&quot;&gt;AWS EKS 공식 문서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/azure/aks/&quot;&gt;Azure AKS 공식 문서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cloud.google.com/kubernetes-engine/docs&quot;&gt;Google GKE 공식 문서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.cncf.io/certification/software-conformance/&quot;&gt;CNCF Kubernetes Conformance&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Kubernetes</category>
      <category>AKS</category>
      <category>AWS</category>
      <category>AZURE</category>
      <category>Container</category>
      <category>eks</category>
      <category>GCP</category>
      <category>GKE</category>
      <category>kubernetes</category>
      <category>Managed Kubernetes</category>
      <category>multi-cloud</category>
      <author>wero90</author>
      <guid isPermaLink="true">https://wero90.tistory.com/75</guid>
      <comments>https://wero90.tistory.com/75#entry75comment</comments>
      <pubDate>Mon, 8 Jun 2026 11:03:42 +0900</pubDate>
    </item>
    <item>
      <title>IAM과 RBAC 차이: AWS, Azure, GCP 기준으로 이해하기</title>
      <link>https://wero90.tistory.com/74</link>
      <description>&lt;blockquote style=&quot;border-left: 4px solid #3b82f6; padding: 1rem 1.2rem; margin: 1.5rem 0; background: #f0f9ff; color: #1e40af; font-size: 1.05rem; line-height: 1.7; border-radius: 0 8px 8px 0;&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&quot;개발자에게 S3 읽기 권한을 주려면 IAM으로 해야 하나요, RBAC로 해야 하나요?&quot; &amp;mdash; 이 질문 자체가 두 개념의 관계를 혼동하고 있다는 신호입니다. IAM은 접근 제어 시스템 전체를 가리키고, RBAC는 그 안에서 권한을 부여하는 방식 중 하나입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;_1&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;핵심 요약&lt;/h2&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;IAM(Identity and Access Management)은 &quot;누가, 무엇에, 어떤 작업을 할 수 있는가&quot;를 관리하는 시스템 전체를 의미합니다.&lt;/li&gt;
&lt;li&gt;RBAC(Role-Based Access Control)는 역할(Role)을 기준으로 권한을 묶어서 부여하는 접근 제어 모델입니다.&lt;/li&gt;
&lt;li&gt;AWS는 IAM 안에서 Policy 기반(ABAC/PBAC)과 Role 기반(RBAC)을 혼합하여 사용합니다.&lt;/li&gt;
&lt;li&gt;Azure는 IAM 계층 안에서 RBAC를 명시적으로 분리한 구조(Azure RBAC)를 제공합니다.&lt;/li&gt;
&lt;li&gt;GCP는 IAM 안에서 Role을 중심으로 권한을 부여하되, Predefined Role과 Custom Role로 유연성을 확보합니다.&lt;/li&gt;
&lt;li&gt;실무에서는 &quot;RBAC만 쓴다&quot; 또는 &quot;IAM만 쓴다&quot;가 아니라, 각 클라우드의 IAM 시스템 안에서 RBAC 원칙을 어떻게 적용할지 설계하는 것이 핵심입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;1-iam-rbac&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;1. IAM과 RBAC는 어떤 관계인가&lt;/h2&gt;
&lt;h3 id=&quot;11&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;1.1 시나리오: 신입 개발자 온보딩&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;팀에 신입 개발자가 합류했습니다. 이 개발자에게 다음 권한이 필요합니다:&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개발 환경 S3 버킷 읽기/쓰기&lt;/li&gt;
&lt;li&gt;CloudWatch 로그 조회&lt;/li&gt;
&lt;li&gt;개발 환경 EC2 인스턴스 재시작&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;이 권한을 부여하는 방법은 크게 두 가지입니다:&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;방법 A (개별 부여):&lt;/b&gt; 개발자 계정에 S3, CloudWatch, EC2 권한을 각각 직접 연결합니다. 개발자가 5명이면 5번, 50명이면 50번 반복합니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;방법 B (역할 기반):&lt;/b&gt; &quot;Backend Developer&quot;라는 역할을 만들고, 필요한 권한을 이 역할에 묶습니다. 신입 개발자에게는 역할만 부여합니다. 권한 변경이 필요하면 역할 하나만 수정합니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;방법 B가 RBAC입니다. 그리고 IAM은 방법 A와 B 모두를 포함하는 시스템 전체를 말합니다.&lt;/p&gt;
&lt;figure style=&quot;text-align: center; margin: 2rem 0;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dna/bsW3mZ/dJMb990ZTxY/AAAAAAAAAAAAAAAAAAAAAOrskCasZUYC1C3po4pvsng7K94rW91qBmE6w-3DpJRV/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1782831599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=uDDHVuMU5HZKT4eDshnnX%2FiX7vs%3D&quot; alt=&quot;IAM과 RBAC 관계 구조&quot; /&gt;
&lt;figcaption style=&quot;margin-top: 0.5rem; font-size: 0.85rem; color: #64748b;&quot;&gt;IAM과 RBAC 관계 구조&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;12&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;1.2 개념 정리&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;구분&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;IAM&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;RBAC&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;정의&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;접근 제어 시스템 전체&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;역할 기반 권한 부여 모델&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;범위&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;인증 + 인가 + 감사&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;인가(Authorization) 방식 중 하나&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;포함 관계&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;RBAC를 포함하는 상위 개념&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;IAM 안에서 동작하는 하위 모델&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;대안&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&amp;mdash;&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;ABAC, PBAC, ACL 등&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;IAM은 다음 세 가지를 모두 다룹니다:&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;인증(Authentication):&lt;/b&gt; 이 사용자가 누구인가? (로그인, MFA, SSO)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;인가(Authorization):&lt;/b&gt; 이 사용자가 무엇을 할 수 있는가? (RBAC, ABAC, Policy)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;감사(Audit):&lt;/b&gt; 이 사용자가 무엇을 했는가? (로그, 추적)&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;RBAC는 이 중 인가 단계에서 &quot;역할&quot;이라는 중간 계층을 통해 권한을 부여하는 방식입니다.&lt;/p&gt;
&lt;h3 id=&quot;13-rbac&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;1.3 RBAC 외의 접근 제어 모델&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;모델&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;설명&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;예시&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;RBAC&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;역할에 권한을 묶어 부여&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&quot;개발자 역할&quot;에 S3 읽기 권한 포함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;ABAC&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;속성(태그, 부서, 환경)에 따라 동적으로 권한 결정&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;env=dev&lt;/code&gt; 태그가 붙은 리소스만 접근 허용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;PBAC&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;정책 문서로 세밀하게 조건 정의&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;JSON Policy에 조건절(Condition) 포함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;ACL&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;리소스별로 접근 가능한 주체 목록 관리&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;S3 버킷 ACL, 파일시스템 ACL&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;실무에서는 하나만 쓰는 경우가 드뭅니다. 대부분 RBAC를 기본으로 하되, 세밀한 조건이 필요하면 ABAC이나 PBAC를 결합합니다.&lt;/p&gt;
&lt;h2 id=&quot;2-iam-rbac&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;2. 클라우드별 IAM과 RBAC 구현 비교&lt;/h2&gt;
&lt;figure style=&quot;text-align: center; margin: 2rem 0;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dna/clt1jm/dJMcaffQJMc/AAAAAAAAAAAAAAAAAAAAANvjva-knz2rWdKNtAR1k2LK3lrs83GzI3xwzzxs8gxI/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1782831599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=rBn8NoV2gzpFLDeRXSWvkyfrVXQ%3D&quot; alt=&quot;클라우드별 IAM/RBAC 구조 비교&quot; /&gt;
&lt;figcaption style=&quot;margin-top: 0.5rem; font-size: 0.85rem; color: #64748b;&quot;&gt;클라우드별 IAM/RBAC 구조 비교&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;21-aws-iam-policy&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;2.1 AWS: IAM + Policy 기반 혼합 모델&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;AWS IAM은 RBAC라는 용어를 명시적으로 분리하지 않습니다. 대신 Policy와 Role이라는 두 축으로 접근 제어를 설계합니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;구성 요소:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;IAM User/Group:&lt;/b&gt; 사람 또는 사람의 집합&lt;/li&gt;
&lt;li&gt;&lt;b&gt;IAM Role:&lt;/b&gt; 서비스나 사용자가 임시로 맡는(assume) 자격&lt;/li&gt;
&lt;li&gt;&lt;b&gt;IAM Policy:&lt;/b&gt; 허용/거부할 작업을 JSON으로 정의한 규칙&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AWS에서 RBAC를 구현하는 방식:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;json&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;{
  &quot;Version&quot;: &quot;2012-10-17&quot;,
  &quot;Statement&quot;: [
    {
      &quot;Effect&quot;: &quot;Allow&quot;,
      &quot;Action&quot;: [
        &quot;s3:GetObject&quot;,
        &quot;s3:ListBucket&quot;
      ],
      &quot;Resource&quot;: [
        &quot;arn:aws:s3:::dev-bucket&quot;,
        &quot;arn:aws:s3:::dev-bucket/*&quot;
      ]
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;이 Policy를 &quot;BackendDeveloper&quot;라는 IAM Role에 연결하고, 개발자에게 이 Role을 부여하면 RBAC 패턴이 됩니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AWS의 특징:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;RBAC와 ABAC를 동시에 지원합니다. Policy의 Condition 블록에서 태그 기반 접근 제어(ABAC)를 설정할 수 있습니다.&lt;/li&gt;
&lt;li&gt;IAM Role은 사람뿐 아니라 EC2, Lambda 등 서비스에도 권한을 부여하는 데 사용됩니다.&lt;/li&gt;
&lt;li&gt;권한 경계(Permission Boundary)로 Role이 가질 수 있는 권한의 상한선을 설정할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;22-azure-rbac&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;2.2 Azure: 명시적 RBAC 분리 모델&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Azure는 IAM 내에서 RBAC를 별도 시스템으로 명확히 분리했습니다. &quot;Azure RBAC&quot;라는 이름으로 독립적인 접근 제어 계층을 제공합니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;구성 요소:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Security Principal:&lt;/b&gt; 권한을 받을 주체 (User, Group, Service Principal, Managed Identity)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Role Definition:&lt;/b&gt; 허용할 작업의 집합 (Reader, Contributor, Owner 등)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Scope:&lt;/b&gt; 권한이 적용되는 범위 (Management Group &amp;rarr; Subscription &amp;rarr; Resource Group &amp;rarr; Resource)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Role Assignment:&lt;/b&gt; Principal + Role + Scope를 연결하는 바인딩&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Azure에서 RBAC를 구현하는 방식:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;dsconfig&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# 개발자 그룹에 개발 리소스 그룹의 Contributor 역할 부여
az role assignment create \
  --assignee &quot;dev-team-group-id&quot; \
  --role &quot;Contributor&quot; \
  --scope &quot;/subscriptions/{sub-id}/resourceGroups/dev-rg&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Azure의 특징:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Scope 계층 구조가 명확합니다. 상위 Scope에서 부여한 권한이 하위로 상속됩니다.&lt;/li&gt;
&lt;li&gt;Built-in Role이 수백 개 제공되어 대부분의 시나리오를 커버합니다.&lt;/li&gt;
&lt;li&gt;Deny Assignment로 특정 권한을 명시적으로 차단할 수 있습니다(제한적 사용).&lt;/li&gt;
&lt;li&gt;Entra ID(구 Azure AD)의 디렉토리 역할과 Azure RBAC는 별개 시스템입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;23-gcp-iam-predefined-role&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;2.3 GCP: IAM + Predefined Role 모델&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;GCP IAM은 Role을 중심으로 권한을 부여하는 구조이며, Azure처럼 명시적으로 &quot;RBAC&quot;를 별도 제품으로 분리하지는 않습니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;구성 요소:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Principal(Member):&lt;/b&gt; 권한을 받을 주체 (Google 계정, Service Account, Group)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Role:&lt;/b&gt; 권한(Permission)의 집합 (Basic, Predefined, Custom)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Resource:&lt;/b&gt; 권한이 적용되는 대상 (Organization &amp;rarr; Folder &amp;rarr; Project &amp;rarr; Resource)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Allow Policy(IAM Binding):&lt;/b&gt; Principal + Role + Resource를 연결&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GCP에서 RBAC를 구현하는 방식:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;smali&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# 개발자 그룹에 프로젝트 수준 Storage Object Viewer 역할 부여
gcloud projects add-iam-policy-binding dev-project \
  --member=&quot;group:dev-team@company.com&quot; \
  --role=&quot;roles/storage.objectViewer&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GCP의 특징:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Predefined Role이 서비스별로 세분화되어 있습니다 (예: &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;roles/storage.objectViewer&lt;/code&gt;, &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;roles/storage.objectAdmin&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Basic Role(Owner, Editor, Viewer)은 권한이 넓어서 운영 환경에서는 사용을 피합니다.&lt;/li&gt;
&lt;li&gt;IAM Conditions로 시간, IP, 리소스 속성 기반 조건부 접근 제어(ABAC)를 추가할 수 있습니다.&lt;/li&gt;
&lt;li&gt;Resource 계층 상속이 동작하며, Organization &amp;rarr; Folder &amp;rarr; Project 순으로 권한이 내려갑니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;3&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;3. 클라우드별 상세 비교&lt;/h2&gt;
&lt;figure style=&quot;text-align: center; margin: 2rem 0;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dna/bwMYnd/dJMcafNHpyC/AAAAAAAAAAAAAAAAAAAAADpdU2AyGeyYVIOxS5_-SQFodcdKCc_w9RJN7LU5BDmS/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1782831599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=ziHeDcqwL5X%2BsmyzcFyG%2Fs9B7Mw%3D&quot; alt=&quot;클라우드별 권한 부여 흐름&quot; /&gt;
&lt;figcaption style=&quot;margin-top: 0.5rem; font-size: 0.85rem; color: #64748b;&quot;&gt;클라우드별 권한 부여 흐름&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;31&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;3.1 핵심 비교표&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;비교 항목&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;AWS&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;Azure&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;GCP&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;RBAC 명시적 분리&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;아니오 (Policy+Role 혼합)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;예 (Azure RBAC)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;아니오 (IAM 내 Role 중심)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;권한 정의 단위&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;IAM Policy (JSON)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Role Definition (Actions)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Role (Permissions 집합)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;역할 연결 방식&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Role에 Policy Attach&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Role Assignment (Principal+Role+Scope)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;IAM Binding (Member+Role+Resource)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Scope 계층&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Account (Organization &amp;rarr; OU)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Management Group &amp;rarr; Subscription &amp;rarr; RG &amp;rarr; Resource&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Organization &amp;rarr; Folder &amp;rarr; Project &amp;rarr; Resource&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;서비스 자격 증명&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;IAM Role (AssumeRole)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Service Principal / Managed Identity&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Service Account&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;속성 기반 제어 (ABAC)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Policy Condition + Tags&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;ABAC (Preview / 제한적)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;IAM Conditions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;기본 제공 역할 수&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;AWS Managed Policy ~1,000+&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Built-in Role ~500+&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Predefined Role ~1,000+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;권한 상한 설정&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Permission Boundary&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;상위 Scope 제한&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Organization Policy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Deny 메커니즘&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Explicit Deny in Policy&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Deny Assignment (제한적)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;IAM Deny Policy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;감사&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;CloudTrail&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Activity Log&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Cloud Audit Logs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;32&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;3.2 설계 철학의 차이&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AWS &amp;mdash; 유연성 우선&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Policy라는 범용 도구로 RBAC, ABAC, 조건부 접근을 모두 표현합니다.&lt;/li&gt;
&lt;li&gt;유연하지만, 그만큼 정책이 복잡해질 수 있습니다.&lt;/li&gt;
&lt;li&gt;Role과 Policy를 조합하는 방식은 자유도가 높지만 설계 패턴이 여러 개 존재하여 팀마다 다른 방식을 쓸 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Azure &amp;mdash; 구조화 우선&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;RBAC를 별도 시스템으로 명확히 분리하여 &quot;누구에게, 어떤 역할을, 어떤 범위에서&quot;를 한 문장으로 표현합니다.&lt;/li&gt;
&lt;li&gt;Scope 계층이 깔끔하여 권한 상속과 재활용이 직관적입니다.&lt;/li&gt;
&lt;li&gt;다만 Custom Role을 만들려면 Actions/DataActions 목록을 직접 정의해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GCP &amp;mdash; 서비스 세분화 우선&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서비스별로 Predefined Role이 세밀하게 나뉘어 있어 &quot;딱 필요한 만큼&quot;을 부여하기 쉽습니다.&lt;/li&gt;
&lt;li&gt;Basic Role의 유혹(Editor/Owner)이 있지만, 운영 환경에서는 Predefined 또는 Custom Role을 권장합니다.&lt;/li&gt;
&lt;li&gt;Resource 계층이 Organization &amp;rarr; Folder &amp;rarr; Project로 논리적 구분이 명확합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;4&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;4. 실무 설계 패턴&lt;/h2&gt;
&lt;h3 id=&quot;41&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;4.1 시나리오: 멀티 클라우드 환경의 개발팀 권한 설계&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;5명의 백엔드 개발 팀에게 다음 권한을 부여해야 합니다:&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개발 환경: 리소스 생성/수정/삭제 가능&lt;/li&gt;
&lt;li&gt;스테이징 환경: 읽기 + 배포만 가능&lt;/li&gt;
&lt;li&gt;프로덕션 환경: 읽기만 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;각 클라우드에서 이 요구사항을 구현하는 패턴입니다:&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AWS 구현:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;groovy&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;IAM Group: backend-dev-team
├── Role: DevEnvFullAccess (dev 리소스 CRUD)
├── Role: StagingDeployAccess (staging 읽기 + 특정 배포 Action)
└── Role: ProdReadOnly (prod 리소스 읽기만)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 Role에 환경별 리소스 ARN을 Condition으로 제한합니다.&lt;/li&gt;
&lt;li&gt;또는 Tag 기반 ABAC로 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;env=dev&lt;/code&gt; 태그가 붙은 리소스에만 접근을 허용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Azure 구현:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;Azure AD Group: backend-dev-team
├── Role Assignment: Contributor @ dev-rg (개발 리소스 그룹)
├── Role Assignment: Reader + Custom Deploy Role @ staging-rg
└── Role Assignment: Reader @ prod-rg
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Scope를 Resource Group 단위로 분리하여 환경별 접근을 제한합니다.&lt;/li&gt;
&lt;li&gt;Custom Role에 배포 관련 Actions만 포함시킵니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GCP 구현:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;groovy&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;Google Group: backend-dev-team@company.com
├── IAM Binding: roles/editor @ dev-project
├── IAM Binding: roles/viewer + custom.deployer @ staging-project
└── IAM Binding: roles/viewer @ prod-project
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Project 단위로 환경을 분리하여 권한 경계를 명확히 합니다.&lt;/li&gt;
&lt;li&gt;Custom Role에 배포에 필요한 Permission만 포함시킵니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;42-rbac&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;4.2 RBAC 설계 시 공통 원칙&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;어떤 클라우드를 사용하든 다음 원칙은 동일하게 적용됩니다:&lt;/p&gt;
&lt;ol style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;최소 권한 원칙(Least Privilege):&lt;/b&gt; 필요한 권한만 부여합니다. &quot;일단 넓게 주고 나중에 줄이자&quot;는 운영 환경에서는 위험합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;그룹/역할 기반 부여:&lt;/b&gt; 개인에게 직접 권한을 부여하지 않습니다. 역할이나 그룹을 중간에 둡니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;환경별 분리:&lt;/b&gt; 개발/스테이징/프로덕션 환경은 별도 계정, 구독, 프로젝트로 분리합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;정기 감사:&lt;/b&gt; 사용하지 않는 권한은 주기적으로 제거합니다(90일 미사용 기준).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;임시 권한(JIT):&lt;/b&gt; 프로덕션 접근은 상시 부여보다 필요 시 임시 승격 방식을 선택합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;43&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;4.3 흔한 실수와 대응&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;실수&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;문제점&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;대응&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Admin/Owner 권한 상시 부여&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;피해 범위가 전체로 확대됨&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;JIT 접근 + 별도 Break Glass 계정 운용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;개인에게 직접 권한 연결&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;퇴사/이동 시 정리 누락&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Group/Role 기반 부여 후 멤버십만 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;환경 미분리&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;개발 실수가 프로덕션에 영향&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Account/Subscription/Project 수준 분리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;권한 감사 미실시&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;불필요 권한 누적 (권한 팽창)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;90일 미사용 권한 자동 알림 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Service Account 키 직접 사용&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;키 유출 시 영구 접근 가능&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Workload Identity/IRSA/Managed Identity 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;5-kubernetes-rbac&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;5. Kubernetes RBAC와의 관계&lt;/h2&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;클라우드 IAM과 Kubernetes RBAC는 별도의 인가 시스템입니다. 두 계층이 함께 동작하며, 둘 다 통과해야 실제 작업이 가능합니다.&lt;/p&gt;
&lt;figure style=&quot;text-align: center; margin: 2rem 0;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dna/qYBJ0/dJMcafNHpyE/AAAAAAAAAAAAAAAAAAAAAAvT9WlVUidwxnGk3aPD3NkYhQxy-XD7UNEcWhK4lbZQ/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1782831599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=kHx4naQsqH%2BpyeMbmd8QS8LHgps%3D&quot; alt=&quot;클라우드 IAM과 Kubernetes RBAC 이중 인가 흐름&quot; /&gt;
&lt;figcaption style=&quot;margin-top: 0.5rem; font-size: 0.85rem; color: #64748b;&quot;&gt;클라우드 IAM과 Kubernetes RBAC 이중 인가 흐름&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;51&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;5.1 이중 인가 구조&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;사용자 &amp;rarr; 클라우드 IAM 인증 &amp;rarr; Kubernetes API Server &amp;rarr; K8s RBAC 인가 &amp;rarr; 리소스 접근
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;클라우드 IAM:&lt;/b&gt; 클러스터에 접근할 수 있는가? (kubectl 명령을 실행할 자격이 있는가)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Kubernetes RBAC:&lt;/b&gt; 클러스터 안에서 어떤 네임스페이스의 어떤 리소스에 어떤 작업을 할 수 있는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;52-kubernetes&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;5.2 클라우드별 Kubernetes 인증 연동&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;클라우드&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;인증 방식&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;RBAC 연동&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;AWS EKS&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;IAM Role &amp;rarr; aws-auth ConfigMap 또는 EKS Access Entry&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;IAM Role을 K8s Group에 매핑&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Azure AKS&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Entra ID 인증&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Entra ID Group을 K8s RoleBinding에 연결&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;GCP GKE&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Google Account / Service Account&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;IAM Role에 K8s RBAC 권한 포함 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;53-eks&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;5.3 실무 예시: EKS에서 개발자 권한 분리&lt;/h3&gt;
&lt;pre class=&quot;yaml&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# 1. K8s RBAC: dev 네임스페이스에서 Pod 읽기/실행만 허용
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: dev
  name: pod-reader
rules:
- apiGroups: [&quot;&quot;]
  resources: [&quot;pods&quot;, &quot;pods/log&quot;]
  verbs: [&quot;get&quot;, &quot;list&quot;, &quot;watch&quot;]
- apiGroups: [&quot;&quot;]
  resources: [&quot;pods/exec&quot;]
  verbs: [&quot;create&quot;]
---
# 2. RoleBinding: 개발자 그룹에 연결
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  namespace: dev
  name: dev-pod-reader
subjects:
- kind: Group
  name: backend-developers
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;이 구성에서 개발자가 실제로 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;kubectl exec&lt;/code&gt;를 실행하려면:&lt;/p&gt;
&lt;ol style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;AWS IAM Role로 EKS 클러스터에 인증되어야 하고 (클라우드 IAM)&lt;/li&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;backend-developers&lt;/code&gt; 그룹에 매핑되어 있어야 하며 (aws-auth 또는 Access Entry)&lt;/li&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;dev&lt;/code&gt; 네임스페이스의 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;pod-reader&lt;/code&gt; Role이 허용하는 범위 안에서만 동작합니다 (K8s RBAC)&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;6&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;6. 의사결정 가이드&lt;/h2&gt;
&lt;h3 id=&quot;61&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;6.1 언제 무엇을 선택하는가&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;상황&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;권장 접근 방식&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;팀 단위로 동일 권한 부여&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;RBAC (역할 기반)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;리소스 태그/속성에 따라 동적 권한&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;ABAC (속성 기반)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;세밀한 조건 분기 필요&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Policy 기반 (PBAC)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;환경별 접근 분리&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;계정/구독/프로젝트 분리 + RBAC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;서비스 간 권한&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Service Account/Role + 최소 권한&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;임시 접근 필요&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;JIT 접근 (Privileged Identity Management)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;62&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;6.2 멀티 클라우드 환경에서의 고려사항&lt;/h3&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;중앙 ID 제공자(IdP)를 통한 SSO 통합이 선행되어야 합니다.&lt;/li&gt;
&lt;li&gt;각 클라우드의 RBAC 모델이 다르므로, 역할 이름은 통일하되 구현은 클라우드별로 맞춥니다.&lt;/li&gt;
&lt;li&gt;&quot;Contributor&quot;(Azure)와 &quot;Editor&quot;(GCP), IAM Policy(AWS)의 실제 권한 범위가 다릅니다. 이름만으로 동일한 수준이라고 가정하면 안 됩니다.&lt;/li&gt;
&lt;li&gt;Terraform 같은 IaC 도구로 멀티 클라우드 IAM을 코드화하면 일관성을 유지하기 쉬워집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;7&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;7. 보안과 비용 관점&lt;/h2&gt;
&lt;h3 id=&quot;71-trade-off&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;7.1 보안 Trade-off&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;결정&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;보안 이점&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;운영 부담&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;세밀한 Custom Role&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;최소 권한 달성&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;역할 관리 복잡도 증가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Predefined/Managed Role 사용&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;관리 단순&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;불필요 권한 포함 가능성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;JIT 접근&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;상시 노출 최소화&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;승인 프로세스 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Service Account 키 제거&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;키 유출 위험 제거&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Workload Identity 설정 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;72&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;7.2 비용 관점&lt;/h3&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;IAM 자체는 AWS, Azure, GCP 모두 무료입니다 (추가 비용 없음).&lt;/li&gt;
&lt;li&gt;다만 AWS Organizations, Azure PIM(Privileged Identity Management), GCP BeyondCorp Enterprise 등 고급 거버넌스 기능은 별도 과금이 있을 수 있습니다.&lt;/li&gt;
&lt;li&gt;권한 관리의 비용은 금전보다 운영 인력과 시간에서 발생합니다. 복잡한 Custom Role 구조는 유지보수 부담을 높입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;8&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;8. 정리: 면접에서 이렇게 설명합니다&lt;/h2&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&quot;IAM과 RBAC의 차이를 설명해주세요&quot;라는 질문에 대한 답변 구조:&lt;/p&gt;
&lt;ol style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;관계 설명:&lt;/b&gt; IAM은 접근 제어 시스템 전체이고, RBAC는 그 안에서 권한을 역할 단위로 부여하는 모델입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;클라우드별 차이:&lt;/b&gt; AWS는 Policy+Role 혼합, Azure는 RBAC를 명시적으로 분리, GCP는 Predefined Role 중심으로 구현합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설계 원칙:&lt;/b&gt; 어떤 클라우드든 최소 권한, 그룹 기반 부여, 환경 분리, 정기 감사가 핵심입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Kubernetes 연동:&lt;/b&gt; 클라우드 IAM과 K8s RBAC는 별도 계층이며, 둘 다 통과해야 실제 작업이 가능합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 id=&quot;_2&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;관련 글&lt;/h2&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;../aws-iam-role-vs-policy&quot;&gt;AWS IAM Role과 Policy 차이: 권한을 설계하는 두 가지 축&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;../../cloud/azure/azure-rbac-overview&quot;&gt;Azure RBAC란 무엇인가: Role Assignment와 Scope 이해하기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;../../cloud/gcp/gcp-iam-overview&quot;&gt;GCP IAM 기본 개념: Role, Principal, Policy 이해하기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;../../kubernetes/kubernetes-rbac-overview&quot;&gt;Kubernetes RBAC란 무엇인가: Role, ClusterRole, Binding으로 권한 설계하기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;../cloud-security-fundamentals&quot;&gt;클라우드 보안 기본 원칙: 최소 권한, 네트워크 격리, 감사 로그&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Security</category>
      <category>access control</category>
      <category>AWS</category>
      <category>AZURE</category>
      <category>Cloud Architecture</category>
      <category>GCP</category>
      <category>IAM</category>
      <category>RBAC</category>
      <category>security</category>
      <category>권한 설계</category>
      <author>wero90</author>
      <guid isPermaLink="true">https://wero90.tistory.com/74</guid>
      <comments>https://wero90.tistory.com/74#entry74comment</comments>
      <pubDate>Mon, 8 Jun 2026 10:45:05 +0900</pubDate>
    </item>
    <item>
      <title>OIDC 기반 인증 설계: GitHub Actions, EKS, Cloud Provider 연동</title>
      <link>https://wero90.tistory.com/73</link>
      <description>&lt;blockquote style=&quot;border-left: 4px solid #3b82f6; padding: 1rem 1.2rem; margin: 1.5rem 0; background: #f0f9ff; color: #1e40af; font-size: 1.05rem; line-height: 1.7; border-radius: 0 8px 8px 0;&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;GitHub Actions에서 AWS에 배포할 때 Access Key를 Repository Secret에 저장하고 있다면, 그 Key는 누가 마지막으로 교체했는지, 어떤 권한이 붙어 있는지 확인해볼 필요가 있습니다. 팀원이 퇴사해도 Key는 남아 있고, 유출되면 교체할 때까지 무제한으로 사용됩니다. OIDC(OpenID Connect)는 이 문제를 구조적으로 해결합니다. 장기 자격 증명을 저장하지 않고, 실행 시점에 단기 토큰을 발급받아 사용하는 방식입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;_1&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;핵심 요약&lt;/h2&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;OIDC 기반 인증은 장기 자격 증명(Access Key, Service Account Key) 자체를 제거하여 유출 위험을 구조적으로 줄입니다.&lt;/li&gt;
&lt;li&gt;GitHub Actions, EKS Pod, GKE Pod 모두 동일한 원리로 동작합니다: 워크로드가 OIDC 토큰을 발급받고, Cloud Provider가 검증 후 임시 자격 증명을 교환합니다.&lt;/li&gt;
&lt;li&gt;AWS는 IRSA(기존)와 Pod Identity(신규) 두 가지 방식을 제공하며, 신규 클러스터에는 Pod Identity가 권장됩니다.&lt;/li&gt;
&lt;li&gt;Azure는 Workload Identity Federation으로 GitHub Actions와 AKS Pod 모두 동일한 패턴을 사용합니다.&lt;/li&gt;
&lt;li&gt;GCP는 Workload Identity Federation으로 GitHub Actions를, Workload Identity for GKE로 Pod를 연결합니다.&lt;/li&gt;
&lt;li&gt;설계 시 핵심 판단 기준: 토큰 범위 제한(audience, subject), 최소 권한 IAM Role, 멀티 계정 구조에서의 Role Chaining입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;1-oidc&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;1. 왜 OIDC인가: 장기 자격 증명의 구조적 문제&lt;/h2&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;5명으로 구성된 DevOps 팀이 GitHub Actions로 AWS, Azure 리소스에 배포하고, EKS 클러스터에서 S3와 DynamoDB에 접근하는 상황을 생각해봅니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;장기 자격 증명(Access Key, Client Secret) 방식으로 운영하면 시간이 지날수록 다음 문제가 누적됩니다.&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;교체 부담&lt;/b&gt;: Key를 90일마다 교체하려면 모든 워크플로우와 Kubernetes Secret을 동시에 업데이트해야 합니다. 하나라도 누락하면 배포가 실패합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;유출 시 피해 범위&lt;/b&gt;: Key가 유출되면 교체 완료까지 무제한 사용됩니다. CloudTrail 로그로 발견하더라도 이미 피해가 발생한 후입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;범위 제어 한계&lt;/b&gt;: 같은 Key를 여러 워크플로우에서 공유하면, 한 워크플로우가 침해되었을 때 다른 워크플로우의 리소스까지 영향받습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;감사 추적 어려움&lt;/b&gt;: 누가 언제 Key를 생성했고 어디에 배포했는지 추적이 어렵습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;OIDC는 이 문제를 &quot;자격 증명을 저장하지 않는다&quot;는 설계로 해결합니다.&lt;/p&gt;
&lt;figure style=&quot;text-align: center; margin: 2rem 0;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dna/uuzch/dJMcagTkFTr/AAAAAAAAAAAAAAAAAAAAAFTF2-yta4Ax9Z2XGbsMX5ZpXskhWgQaCThlyyXEK-6-/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1782831599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=U0BVyCyUnBzPfR3B0YLY9zSSZZQ%3D&quot; alt=&quot;OIDC 인증 개요&quot; /&gt;
&lt;figcaption style=&quot;margin-top: 0.5rem; font-size: 0.85rem; color: #64748b;&quot;&gt;OIDC 인증 개요&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;2-oidc&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;2. OIDC 동작 원리: 토큰 교환의 구조&lt;/h2&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;OIDC 기반 인증의 핵심은 &lt;b&gt;토큰 교환(Token Exchange)&lt;/b&gt; 패턴입니다. 모든 Cloud Provider에서 동일한 3단계로 동작합니다.&lt;/p&gt;
&lt;h3 id=&quot;_2&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;토큰 교환 흐름&lt;/h3&gt;
&lt;pre class=&quot;mipsasm&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;1. 워크로드(GitHub Actions Runner, EKS Pod)가 자체 OIDC Provider로부터 JWT를 발급받음
2. JWT를 Cloud Provider의 STS(Security Token Service)에 제출
3. Cloud Provider가 JWT를 검증하고, 조건이 맞으면 임시 자격 증명(1시간)을 발급
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;jwt&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;JWT 내부 구조&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;GitHub Actions가 발급하는 OIDC 토큰의 주요 클레임:&lt;/p&gt;
&lt;pre class=&quot;json&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;{
  &quot;iss&quot;: &quot;https://token.actions.githubusercontent.com&quot;,
  &quot;sub&quot;: &quot;repo:my-org/my-repo:ref:refs/heads/main&quot;,
  &quot;aud&quot;: &quot;sts.amazonaws.com&quot;,
  &quot;ref&quot;: &quot;refs/heads/main&quot;,
  &quot;repository&quot;: &quot;my-org/my-repo&quot;,
  &quot;repository_owner&quot;: &quot;my-org&quot;,
  &quot;job_workflow_ref&quot;: &quot;my-org/my-repo/.github/workflows/deploy.yml@refs/heads/main&quot;,
  &quot;exp&quot;: 1718000000,
  &quot;iat&quot;: 1717999400
}
&lt;/code&gt;&lt;/pre&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;클레임&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;역할&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;iss&lt;/code&gt; (Issuer)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;토큰 발급자. Cloud Provider가 이 URL에서 공개 키를 가져와 서명 검증&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;sub&lt;/code&gt; (Subject)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;워크로드의 신원. Trust Policy에서 조건 매칭에 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;aud&lt;/code&gt; (Audience)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;토큰의 대상. 의도하지 않은 서비스에서 토큰 재사용을 방지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;exp&lt;/code&gt; / &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;iat&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;토큰 유효 기간. 일반적으로 5~15분&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Cloud Provider는 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;iss&lt;/code&gt;로 공개 키를 조회하고, 서명을 검증한 뒤, &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;sub&lt;/code&gt;와 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;aud&lt;/code&gt; 조건이 Trust Policy와 일치하는지 확인합니다. 모든 조건이 통과하면 임시 자격 증명을 발급합니다.&lt;/p&gt;
&lt;figure style=&quot;text-align: center; margin: 2rem 0;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dna/bPdbis/dJMcagTkFTs/AAAAAAAAAAAAAAAAAAAAAETNEkbJubYzk47w9TBqMdlL_pIigZl1PygqwSkZtwKT/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1782831599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=qCWPwUedpDsavB3ew4yuNT0unrU%3D&quot; alt=&quot;OIDC 토큰 교환 흐름&quot; /&gt;
&lt;figcaption style=&quot;margin-top: 0.5rem; font-size: 0.85rem; color: #64748b;&quot;&gt;OIDC 토큰 교환 흐름&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;_3&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;보안 설계 포인트&lt;/h3&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;토큰 수명 최소화&lt;/b&gt;: JWT 자체는 5~15분, 발급받는 임시 자격 증명은 기본 1시간입니다. 유출되어도 피해 시간이 제한됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Subject 범위 제한&lt;/b&gt;: &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;sub&lt;/code&gt; 클레임으로 특정 저장소, 브랜치, 환경만 허용할 수 있습니다. &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;repo:my-org/*&lt;/code&gt;처럼 와일드카드를 넓게 잡으면 OIDC의 보안 이점이 줄어듭니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Audience 검증&lt;/b&gt;: &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;aud&lt;/code&gt;를 명시적으로 지정해야 다른 서비스에서 토큰을 재사용하는 confused deputy 공격을 방지합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;3-github-actions-aws-oidc&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;3. GitHub Actions + AWS OIDC 연동&lt;/h2&gt;
&lt;h3 id=&quot;_4&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;설정 구조&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;GitHub Actions에서 AWS 리소스에 접근하려면 3가지를 설정합니다.&lt;/p&gt;
&lt;ol style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;AWS에 OIDC Provider 등록&lt;/b&gt;: GitHub의 토큰 발급 서버를 AWS가 신뢰하도록 등록&lt;/li&gt;
&lt;li&gt;&lt;b&gt;IAM Role 생성&lt;/b&gt;: Trust Policy로 어떤 저장소/브랜치에서 Assume 가능한지 제한&lt;/li&gt;
&lt;li&gt;&lt;b&gt;워크플로우에서 토큰 요청&lt;/b&gt;: &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;permissions: id-token: write&lt;/code&gt;를 선언하고 Action 사용&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;terraform-oidc-provider-iam-role&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;Terraform으로 OIDC Provider + IAM Role 생성&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# OIDC Provider 등록
resource &quot;aws_iam_openid_connect_provider&quot; &quot;github&quot; {
  url             = &quot;https://token.actions.githubusercontent.com&quot;
  client_id_list  = [&quot;sts.amazonaws.com&quot;]
  thumbprint_list = [&quot;1b511abead59c6ce207077c0bf0e0043b1382612&quot;]
}

# IAM Role - Trust Policy에서 저장소와 브랜치 제한
resource &quot;aws_iam_role&quot; &quot;github_actions&quot; {
  name = &quot;github-actions-deploy&quot;

  assume_role_policy = jsonencode({
    Version = &quot;2012-10-17&quot;
    Statement = [
      {
        Effect = &quot;Allow&quot;
        Principal = {
          Federated = aws_iam_openid_connect_provider.github.arn
        }
        Action = &quot;sts:AssumeRoleWithWebIdentity&quot;
        Condition = {
          StringEquals = {
            &quot;token.actions.githubusercontent.com:aud&quot; = &quot;sts.amazonaws.com&quot;
          }
          StringLike = {
            &quot;token.actions.githubusercontent.com:sub&quot; = &quot;repo:my-org/my-app:ref:refs/heads/main&quot;
          }
        }
      }
    ]
  })
}

# 필요한 권한만 부여 (예: ECR Push + ECS Deploy)
resource &quot;aws_iam_role_policy_attachment&quot; &quot;ecr_push&quot; {
  role       = aws_iam_role.github_actions.name
  policy_arn = &quot;arn:aws:iam::123456789012:policy/ECRPushPolicy&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;_5&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;워크플로우 설정&lt;/h3&gt;
&lt;pre class=&quot;less&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;name: Deploy to AWS
on:
  push:
    branches: [main]

permissions:
  id-token: write   # OIDC 토큰 요청 권한
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: aws-actions/configure-aws-credentials@v6
        with:
          role-to-assume: arn:aws:iam::123456789012:role/github-actions-deploy
          aws-region: ap-northeast-2

      - run: aws sts get-caller-identity  # 임시 자격 증명 확인
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;aws-actions/configure-aws-credentials&lt;/code&gt;의 현재 최신 버전은 v6.2.0입니다. v6부터 Node 24 런타임을 사용하며, role-chaining과 custom session tag를 지원합니다.&lt;/p&gt;
&lt;h3 id=&quot;role-chaining&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;멀티 계정 구조: Role Chaining&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;대부분의 운영 환경에서는 AWS 계정이 여러 개입니다. 하나의 Hub 계정에 OIDC Trust를 설정하고, 실제 워크로드 계정으로 Role Chaining하는 패턴이 일반적입니다.&lt;/p&gt;
&lt;pre class=&quot;less&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;steps:
  # 1단계: Hub 계정의 Role을 OIDC로 Assume
  - uses: aws-actions/configure-aws-credentials@v6
    with:
      role-to-assume: arn:aws:iam::111111111111:role/github-oidc-hub
      aws-region: ap-northeast-2

  # 2단계: Hub Role에서 워크로드 계정의 Role로 Chaining
  - uses: aws-actions/configure-aws-credentials@v6
    with:
      role-to-assume: arn:aws:iam::222222222222:role/workload-deploy
      role-chaining: true
      aws-region: ap-northeast-2
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;이 구조의 장점은 OIDC Provider를 1개 계정에만 등록하면 되므로 관리 포인트가 줄어든다는 점입니다. 다만 Hub Role의 Trust Policy가 과도하게 넓으면 어떤 저장소든 모든 워크로드 계정에 접근할 수 있으므로, Hub Role에서도 sub 조건을 저장소 단위로 제한해야 합니다.&lt;/p&gt;
&lt;h2 id=&quot;4-eks-irsa-vs-pod-identity&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;4. EKS 워크로드 인증: IRSA vs Pod Identity&lt;/h2&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;EKS에서 Pod가 AWS 리소스에 접근할 때도 OIDC 기반 인증을 사용합니다. 현재 두 가지 방식이 있습니다.&lt;/p&gt;
&lt;h3 id=&quot;irsa-iam-roles-for-service-accounts&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;IRSA (IAM Roles for Service Accounts)&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;2019년에 출시된 방식으로, EKS 클러스터의 OIDC Provider를 IAM에 등록하고 ServiceAccount와 IAM Role을 연결합니다.&lt;/p&gt;
&lt;pre class=&quot;elixir&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# ServiceAccount에 IAM Role ARN을 annotation으로 연결
apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-app
  namespace: production
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/my-app-role
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;nix&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# IAM Role Trust Policy - 특정 클러스터의 특정 ServiceAccount만 허용
resource &quot;aws_iam_role&quot; &quot;my_app&quot; {
  name = &quot;my-app-role&quot;

  assume_role_policy = jsonencode({
    Version = &quot;2012-10-17&quot;
    Statement = [
      {
        Effect = &quot;Allow&quot;
        Principal = {
          Federated = &quot;arn:aws:iam::123456789012:oidc-provider/oidc.eks.ap-northeast-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE&quot;
        }
        Action = &quot;sts:AssumeRoleWithWebIdentity&quot;
        Condition = {
          StringEquals = {
            &quot;oidc.eks.ap-northeast-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE:sub&quot; = &quot;system:serviceaccount:production:my-app&quot;
            &quot;oidc.eks.ap-northeast-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE:aud&quot; = &quot;sts.amazonaws.com&quot;
          }
        }
      }
    ]
  })
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;pod-identity-2023&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;Pod Identity (2023년 출시)&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Pod Identity는 OIDC Provider를 직접 관리하지 않고, AWS가 제공하는 Pod Identity Agent가 노드에서 자격 증명을 중개합니다.&lt;/p&gt;
&lt;pre class=&quot;dsconfig&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# Pod Identity Association 생성
aws eks create-pod-identity-association \
  --cluster-name my-cluster \
  --namespace production \
  --service-account my-app \
  --role-arn arn:aws:iam::123456789012:role/my-app-role
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;routeros&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# Terraform으로 Pod Identity Association 설정
resource &quot;aws_eks_pod_identity_association&quot; &quot;my_app&quot; {
  cluster_name    = aws_eks_cluster.main.name
  namespace       = &quot;production&quot;
  service_account = &quot;my-app&quot;
  role_arn        = aws_iam_role.my_app.arn
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Pod Identity의 IAM Role Trust Policy는 클러스터별 OIDC URL이 아닌 범용 Principal을 사용합니다:&lt;/p&gt;
&lt;pre class=&quot;json&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;{
  &quot;Version&quot;: &quot;2012-10-17&quot;,
  &quot;Statement&quot;: [
    {
      &quot;Effect&quot;: &quot;Allow&quot;,
      &quot;Principal&quot;: {
        &quot;Service&quot;: &quot;pods.eks.amazonaws.com&quot;
      },
      &quot;Action&quot;: [
        &quot;sts:AssumeRole&quot;,
        &quot;sts:TagSession&quot;
      ]
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure style=&quot;text-align: center; margin: 2rem 0;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dna/VZYSg/dJMcaar3INm/AAAAAAAAAAAAAAAAAAAAALFzs8zf0Q-CFri1t-I3VwwEw2VOIYH2kuzT8o37kbBZ/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1782831599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=gYx84ocC8kZX%2BFJ4Lni1Q31Kq%2F0%3D&quot; alt=&quot;EKS IRSA vs Pod Identity 비교&quot; /&gt;
&lt;figcaption style=&quot;margin-top: 0.5rem; font-size: 0.85rem; color: #64748b;&quot;&gt;EKS IRSA vs Pod Identity 비교&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;irsa-vs-pod-identity&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;IRSA vs Pod Identity 선택 기준&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;기준&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;IRSA&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;Pod Identity&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;OIDC Provider 관리&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;클러스터마다 등록 필요&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;불필요 (AWS 관리)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Trust Policy&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;클러스터별 OIDC URL 포함 &amp;rarr; 복잡&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;범용 Service Principal &amp;rarr; 단순&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;멀티 클러스터&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;클러스터마다 Trust Policy 수정 필요&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;1개 Role을 여러 클러스터에서 재사용 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Session Tag&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;수동 설정 필요&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;자동 태깅 (클러스터명, namespace, SA)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;ABAC 지원&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;제한적&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Session Tag 기반 ABAC 지원&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;최소 EKS 버전&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;1.14+&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;1.24+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;추가 Agent&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;불필요&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Pod Identity Agent DaemonSet 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;설계 판단&lt;/b&gt;: 신규 EKS 클러스터(1.24+)에서는 Pod Identity가 운영 복잡도를 줄여줍니다. 특히 멀티 클러스터 환경에서는 Trust Policy 관리 부담이 크게 줄어듭니다. 기존 클러스터에서 IRSA가 이미 동작 중이라면 당장 마이그레이션할 필요는 없으며, 신규 워크로드부터 Pod Identity를 적용하는 점진적 전환이 적합합니다.&lt;/p&gt;
&lt;h2 id=&quot;5-azure-workload-identity-federation&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;5. Azure Workload Identity Federation&lt;/h2&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Azure에서도 동일한 패턴으로 GitHub Actions와 AKS Pod에서 OIDC 인증을 사용합니다. Azure는 이를 &lt;b&gt;Workload Identity Federation&lt;/b&gt;이라 부릅니다.&lt;/p&gt;
&lt;h3 id=&quot;github-actions-azure&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;GitHub Actions + Azure 연동&lt;/h3&gt;
&lt;pre class=&quot;jboss-cli&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# 1. App Registration 생성
az ad app create --display-name &quot;github-actions-deploy&quot;

# 2. Federated Credential 추가 (GitHub OIDC 신뢰)
az ad app federated-credential create \
  --id &amp;lt;app-object-id&amp;gt; \
  --parameters '{
    &quot;name&quot;: &quot;github-main-branch&quot;,
    &quot;issuer&quot;: &quot;https://token.actions.githubusercontent.com&quot;,
    &quot;subject&quot;: &quot;repo:my-org/my-app:ref:refs/heads/main&quot;,
    &quot;audiences&quot;: [&quot;api://AzureADTokenExchange&quot;]
  }'

# 3. Service Principal에 역할 할당
az role assignment create \
  --assignee &amp;lt;app-client-id&amp;gt; \
  --role &quot;Contributor&quot; \
  --scope &quot;/subscriptions/&amp;lt;sub-id&amp;gt;/resourceGroups/my-rg&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;워크플로우:&lt;/p&gt;
&lt;pre class=&quot;yaml&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    steps:
      - uses: actions/checkout@v4
      - uses: azure/login@v2
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
      - run: az resource list --resource-group my-rg
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Azure의 경우 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;client-id&lt;/code&gt;, &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;tenant-id&lt;/code&gt;, &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;subscription-id&lt;/code&gt;는 Secret에 저장하지만, 이들은 식별자일 뿐 자격 증명이 아닙니다. Client Secret이나 Certificate 없이 OIDC 토큰만으로 인증이 완료됩니다.&lt;/p&gt;
&lt;h3 id=&quot;aks-workload-identity&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;AKS Workload Identity&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;AKS에서는 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;azure-workload-identity&lt;/code&gt; 프로젝트를 통해 Pod가 Azure 리소스에 접근합니다.&lt;/p&gt;
&lt;pre class=&quot;dts&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# ServiceAccount에 Workload Identity annotation
apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-app
  namespace: production
  annotations:
    azure.workload.identity/client-id: &quot;&amp;lt;managed-identity-client-id&amp;gt;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;yaml&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# Pod에 Workload Identity 라벨 추가
apiVersion: v1
kind: Pod
metadata:
  labels:
    azure.workload.identity/use: &quot;true&quot;
spec:
  serviceAccountName: my-app
  containers:
    - name: app
      image: my-app:latest
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;6-gcp-workload-identity-federation&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;6. GCP Workload Identity Federation&lt;/h2&gt;
&lt;h3 id=&quot;github-actions-gcp&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;GitHub Actions + GCP 연동&lt;/h3&gt;
&lt;pre class=&quot;dsconfig&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# 1. Workload Identity Pool 생성
gcloud iam workload-identity-pools create &quot;github-pool&quot; \
  --location=&quot;global&quot; \
  --display-name=&quot;GitHub Actions Pool&quot;

# 2. OIDC Provider 추가
gcloud iam workload-identity-pools providers create-oidc &quot;github-provider&quot; \
  --location=&quot;global&quot; \
  --workload-identity-pool=&quot;github-pool&quot; \
  --display-name=&quot;GitHub OIDC&quot; \
  --issuer-uri=&quot;https://token.actions.githubusercontent.com&quot; \
  --attribute-mapping=&quot;google.subject=assertion.sub,attribute.repository=assertion.repository&quot; \
  --attribute-condition=&quot;assertion.repository_owner == 'my-org'&quot;

# 3. Service Account에 workloadIdentityUser 역할 바인딩
gcloud iam service-accounts add-iam-policy-binding &quot;deploy@my-project.iam.gserviceaccount.com&quot; \
  --role=&quot;roles/iam.workloadIdentityUser&quot; \
  --member=&quot;principalSet://iam.googleapis.com/projects/123456/locations/global/workloadIdentityPools/github-pool/attribute.repository/my-org/my-app&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;워크플로우:&lt;/p&gt;
&lt;pre class=&quot;less&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    steps:
      - uses: actions/checkout@v4
      - uses: google-github-actions/auth@v2
        with:
          workload_identity_provider: &quot;projects/123456/locations/global/workloadIdentityPools/github-pool/providers/github-provider&quot;
          service_account: &quot;deploy@my-project.iam.gserviceaccount.com&quot;
      - uses: google-github-actions/setup-gcloud@v2
      - run: gcloud run deploy my-service --image gcr.io/my-project/my-app
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;gke-workload-identity&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;GKE Workload Identity&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;GKE에서는 Workload Identity Federation for GKE가 기본으로 활성화됩니다. Pod의 Kubernetes ServiceAccount를 GCP IAM Service Account에 연결합니다.&lt;/p&gt;
&lt;pre class=&quot;smali&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# Kubernetes ServiceAccount와 GCP Service Account 바인딩
gcloud iam service-accounts add-iam-policy-binding \
  &quot;my-app@my-project.iam.gserviceaccount.com&quot; \
  --role=&quot;roles/iam.workloadIdentityUser&quot; \
  --member=&quot;serviceAccount:my-project.svc.id.goog[production/my-app]&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;less&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-app
  namespace: production
  annotations:
    iam.gke.io/gcp-service-account: my-app@my-project.iam.gserviceaccount.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;GKE Workload Identity는 OIDC Provider를 별도로 등록할 필요 없이, 클러스터 생성 시 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;--workload-pool&lt;/code&gt; 옵션만 활성화하면 됩니다.&lt;/p&gt;
&lt;h2 id=&quot;7-oidc&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;7. 멀티 클라우드 OIDC 설계 비교&lt;/h2&gt;
&lt;figure style=&quot;text-align: center; margin: 2rem 0;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dna/qQn4h/dJMcaar3INy/AAAAAAAAAAAAAAAAAAAAACVMq0axASlphoYgpcNsGBJFGwmR-DkZqTBjWLm6-Ftw/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1782831599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=x8aO4Ql50tXgnQMjuh%2FAwR%2B1xCE%3D&quot; alt=&quot;멀티 클라우드 OIDC 비교&quot; /&gt;
&lt;figcaption style=&quot;margin-top: 0.5rem; font-size: 0.85rem; color: #64748b;&quot;&gt;멀티 클라우드 OIDC 비교&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;구분&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;AWS&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;Azure&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;GCP&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;GitHub OIDC Trust 등록&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;IAM OIDC Provider&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;App Registration + Federated Credential&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Workload Identity Pool + Provider&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;토큰 교환 엔드포인트&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;STS (AssumeRoleWithWebIdentity)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Microsoft Entra ID&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;STS (ExchangeToken)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Kubernetes Pod 인증&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;IRSA 또는 Pod Identity&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;AKS Workload Identity&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;GKE Workload Identity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Trust 범위 제한&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Condition (sub, aud)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;subject + audiences&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;attribute-condition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;임시 자격 증명 수명&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;기본 1시간 (최대 12시간)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;기본 1시간&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;기본 1시간&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;멀티 계정/구독/프로젝트&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Role Chaining&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;구독별 Role Assignment&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Cross-project IAM binding&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;_6&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;설계 시 공통 원칙&lt;/h3&gt;
&lt;ol style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Subject 범위를 최소화&lt;/b&gt;: 저장소 + 브랜치 + 환경 단위로 제한. &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;repo:my-org/*&lt;/code&gt;처럼 와일드카드를 조직 전체로 열면 안 됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;환경별 Role 분리&lt;/b&gt;: 개발/스테이징/프로덕션 환경마다 별도 Role을 생성하고, GitHub Environment Protection Rules와 연동합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;감사 로그 활성화&lt;/b&gt;: 임시 자격 증명으로 수행한 작업도 CloudTrail, Azure Activity Log, GCP Cloud Audit Logs에 기록됩니다. Session Name에 워크플로우 정보를 포함하면 추적이 쉽습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;임시 자격 증명 수명 최소화&lt;/b&gt;: 기본 1시간이지만, 빌드/배포 작업이 30분 내에 끝난다면 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;role-duration-seconds&lt;/code&gt;를 줄여 피해 시간을 제한할 수 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;8-cicd-eks&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;8. 실무 시나리오: 스타트업 CI/CD + EKS 배포 파이프라인&lt;/h2&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;3명의 DevOps 팀이 운영하는 환경을 가정합니다.&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GitHub 조직: &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;startup-corp&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;AWS 계정: Shared (Hub), Dev, Production 3개&lt;/li&gt;
&lt;li&gt;EKS 클러스터: Dev 1개, Production 1개&lt;/li&gt;
&lt;li&gt;배포 대상: 3개 마이크로서비스 (user-api, order-api, payment-api)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;_7&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;설계 구조&lt;/h3&gt;
&lt;pre class=&quot;crmsh&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;GitHub Actions (OIDC)
  └─ Hub 계정 Role (github-oidc-hub)
       ├─ Dev 계정 Role (dev-deploy) &amp;larr; Role Chaining
       └─ Prod 계정 Role (prod-deploy) &amp;larr; Role Chaining + Environment 보호 규칙

EKS Pod (Pod Identity)
  ├─ user-api ServiceAccount &amp;rarr; user-api-role (DynamoDB 읽기)
  ├─ order-api ServiceAccount &amp;rarr; order-api-role (SQS 송수신)
  └─ payment-api ServiceAccount &amp;rarr; payment-api-role (KMS 복호화 + S3 쓰기)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;_8&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;왜 이렇게 설계하는가&lt;/h3&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Hub 계정 분리&lt;/b&gt;: OIDC Provider를 1곳에만 등록하면 GitHub 인증서 thumbprint 변경 시 1곳만 업데이트합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;환경별 Role 분리&lt;/b&gt;: Dev Role에는 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;terraform plan&lt;/code&gt; 수준의 읽기 권한만, Prod Role에는 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;terraform apply&lt;/code&gt; 권한을 부여합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Pod 단위 최소 권한&lt;/b&gt;: payment-api가 침해되어도 DynamoDB에는 접근할 수 없습니다. 각 Pod는 자신의 서비스에 필요한 권한만 가집니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Pod Identity 선택 이유&lt;/b&gt;: 2개 클러스터에서 같은 Role을 재사용할 수 있어 Trust Policy 관리가 단순합니다. Session Tag로 어떤 클러스터, 어떤 namespace에서 호출했는지 CloudTrail에 자동 기록됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;9&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;9. 주의사항과 트러블슈팅&lt;/h2&gt;
&lt;h3 id=&quot;github-actions-oidc&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;GitHub Actions OIDC 관련&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;증상&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;원인&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;해결&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;Not authorized to perform: sts:AssumeRoleWithWebIdentity&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Trust Policy의 sub 조건 불일치&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;github.event_name&lt;/code&gt;, &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;github.ref&lt;/code&gt;를 확인. PR 이벤트는 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;ref:refs/pull/*/merge&lt;/code&gt; 형태&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;permissions: id-token: write&lt;/code&gt; 추가했는데 토큰이 안 나옴&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Fork된 저장소에서는 OIDC 토큰 미발급&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Fork PR 워크플로우에서는 OIDC 사용 불가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;토큰 만료 에러&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;워크플로우 실행 시간이 토큰 유효 기간 초과&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;role-duration-seconds&lt;/code&gt;를 늘리거나, 장시간 작업은 Step을 분리&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;eks-irsapod-identity&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;EKS IRSA/Pod Identity 관련&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;증상&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;원인&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;해결&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Pod에서 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;An error occurred (AccessDenied)&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;ServiceAccount annotation 누락 또는 Trust Policy 불일치&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;kubectl describe sa&lt;/code&gt; + IAM Role Trust Policy 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Pod Identity Agent가 CrashLoop&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;EKS 클러스터 버전이 1.24 미만&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;클러스터 업그레이드 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;No OpenIDConnect provider found&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;OIDC Provider가 IAM에 미등록 (IRSA)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;aws eks describe-cluster&lt;/code&gt;로 OIDC URL 확인 후 등록&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;subject&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;Subject 클레임 참고&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;GitHub Actions의 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;sub&lt;/code&gt; 클레임은 트리거 이벤트에 따라 달라집니다:&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;이벤트&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;sub 형태&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;push&lt;/code&gt; (main)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;repo:org/repo:ref:refs/heads/main&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;push&lt;/code&gt; (tag)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;repo:org/repo:ref:refs/tags/v1.0.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;pull_request&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;repo:org/repo:pull_request&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;environment&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;repo:org/repo:environment:production&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;workflow_dispatch&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;repo:org/repo:ref:refs/heads/main&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;프로덕션 배포에는 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;environment&lt;/code&gt; 기반 sub를 사용하고, GitHub Environment Protection Rules(Required Reviewers)와 결합하면 승인 없이 프로덕션에 배포되는 것을 방지할 수 있습니다.&lt;/p&gt;
&lt;h2 id=&quot;10-oidc-vs-oidc&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;10. OIDC vs 기존 방식: 언제 OIDC를 선택할 수 없는가&lt;/h2&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;OIDC가 일반적으로 더 안전한 방식이지만, 모든 상황에 적합하지는 않습니다.&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;상황&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;OIDC 사용 가능&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;대안&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;GitHub Actions &amp;rarr; AWS/Azure/GCP&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;✅&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&amp;mdash;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;EKS/AKS/GKE Pod &amp;rarr; 동일 Cloud&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;✅&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&amp;mdash;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Jenkins &amp;rarr; AWS&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;✅ (Jenkins OIDC Plugin 사용)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&amp;mdash;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;GitHub Actions &amp;rarr; 외부 SaaS API (Slack, Datadog 등)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;❌&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Repository/Environment Secret&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;On-premise 서버 &amp;rarr; Cloud&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;조건부 (IdP 필요)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;IAM User + IP 제한 + MFA&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Lambda &amp;rarr; 다른 AWS 서비스&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;불필요 (Execution Role 사용)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&amp;mdash;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;OIDC를 지원하지 않는 외부 서비스에 대해서는 여전히 Secret 기반 인증이 필요합니다. 이 경우에도 Environment Secret + Rotation Policy를 적용하여 위험을 줄입니다.&lt;/p&gt;
&lt;h2 id=&quot;_9&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;관련 글&lt;/h2&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;/entry/GitHub-Actions에서-Secret을-안전하게-관리하는-방법&quot;&gt;GitHub Actions에서 Secret을 안전하게 관리하는 방법&lt;/a&gt; &amp;mdash; OIDC를 포함한 전체 Secret 관리 전략&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/entry/GitHub-Actions로-Docker-이미지를-빌드하고-배포하기-CICD-파이프라인-실습&quot;&gt;GitHub Actions로 Docker 이미지를 빌드하고 배포하기&lt;/a&gt; &amp;mdash; OIDC를 적용한 실제 배포 파이프라인&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/entry/IAM과-RBAC-차이-AWS-Azure-GCP-기준으로-이해하기&quot;&gt;IAM과 RBAC 차이: AWS, Azure, GCP 기준으로 이해하기&lt;/a&gt; &amp;mdash; OIDC Role에 붙이는 IAM 권한 설계의 기본&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;_10&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;참고 문서&lt;/h2&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services&quot;&gt;GitHub Docs: Configuring OpenID Connect in AWS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.aws.amazon.com/eks/latest/userguide/pod-identities.html&quot;&gt;AWS: EKS Pod Identity&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation&quot;&gt;Azure: Workload Identity Federation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cloud.google.com/iam/docs/workload-identity-federation&quot;&gt;GCP: Workload Identity Federation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Security</category>
      <category>CI/CD</category>
      <category>eks</category>
      <category>github actions</category>
      <category>IAM</category>
      <category>IRSA</category>
      <category>OIDC</category>
      <category>Pod Identity</category>
      <category>security</category>
      <category>Workload Identity</category>
      <author>wero90</author>
      <guid isPermaLink="true">https://wero90.tistory.com/73</guid>
      <comments>https://wero90.tistory.com/73#entry73comment</comments>
      <pubDate>Mon, 8 Jun 2026 10:38:40 +0900</pubDate>
    </item>
    <item>
      <title>RAG 검색 품질 개선: Hybrid Search, Reranking, Query Transformation</title>
      <link>https://wero90.tistory.com/72</link>
      <description>&lt;blockquote style=&quot;border-left: 4px solid #3b82f6; padding: 1rem 1.2rem; margin: 1.5rem 0; background: #f0f9ff; color: #1e40af; font-size: 1.05rem; line-height: 1.7; border-radius: 0 8px 8px 0;&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;RAG의 응답 품질은 검색 품질에 의해 결정됩니다. 아무리 좋은 LLM을 사용해도, 검색 단계에서 관련 문서를 가져오지 못하면 정확한 답변을 생성할 수 없습니다. 이 글에서는 검색 품질을 개선하는 세 가지 핵심 전략을 다룹니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;_1&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;핵심 요약&lt;/h2&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;순수 벡터 검색(Dense Retrieval)만으로는 키워드 매칭이 필요한 질문에 취약합니다. BM25와 결합한 Hybrid Search로 커버리지를 높일 수 있습니다.&lt;/li&gt;
&lt;li&gt;검색 결과의 순위를 Cross-encoder로 재정렬하는 Reranking은 적은 비용으로 정밀도를 높이는 효과적인 방법입니다.&lt;/li&gt;
&lt;li&gt;사용자 질문을 그대로 검색하지 않고, LLM으로 변환하는 Query Transformation은 질문-문서 간 의미 격차를 줄입니다.&lt;/li&gt;
&lt;li&gt;세 전략은 독립적이지 않으며, Hybrid Search &amp;rarr; Reranking &amp;rarr; Query Transformation 순으로 조합하면 누적 효과가 큽니다.&lt;/li&gt;
&lt;li&gt;전략 도입 전에 현재 검색 품질의 baseline을 측정하고, 각 전략 추가 후 개선폭을 확인하는 방식이 실무적입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;1&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;1. 문제 상황: 왜 벡터 검색만으로는 부족한가&lt;/h2&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;RAG 시스템을 구축하고 사내 문서를 임베딩해서 질문에 답하게 만들었습니다. 그런데 운영을 시작하면 이런 문제가 반복됩니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사례 1: 키워드 매칭 실패&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;질문: &quot;ERROR-4032 에러 코드 해결 방법은?&quot;&lt;/li&gt;
&lt;li&gt;기대: ERROR-4032를 설명하는 문서&lt;/li&gt;
&lt;li&gt;실제: &quot;에러 처리 일반 가이드&quot;가 반환됨&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;벡터 검색은 의미적 유사도 기반입니다. &quot;ERROR-4032&quot;라는 구체적인 코드와 의미적으로 유사한 벡터를 찾지만, 해당 코드가 정확히 포함된 문서를 우선하지는 않습니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사례 2: 검색 결과 순위 문제&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Top-5 검색 결과 중 정답이 4번째에 있음&lt;/li&gt;
&lt;li&gt;LLM은 1~2번째 결과에 더 가중치를 두어 부정확한 답변 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;벡터 유사도 점수 차이가 미세한 경우, 실제 관련성이 높은 문서가 순위에서 밀릴 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사례 3: 질문과 문서의 표현 격차&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;질문: &quot;서버가 느려졌을 때 어떻게 해야 해?&quot;&lt;/li&gt;
&lt;li&gt;문서: &quot;Latency 증가 시 수평 확장 전략과 캐시 레이어 도입을 검토합니다&quot;&lt;/li&gt;
&lt;li&gt;의미는 같지만 표현이 달라 유사도가 낮게 측정됨&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;이 세 가지 문제를 각각 해결하는 전략이 Hybrid Search, Reranking, Query Transformation입니다.&lt;/p&gt;
&lt;figure style=&quot;text-align: center; margin: 2rem 0;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dna/z2bA7/dJMcahLsBKF/AAAAAAAAAAAAAAAAAAAAAPRnqRvz-1iJgK7-wxu4vJrlhVwtVuupgD2rjFJNrn3C/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1782831599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=Bi8yoituGqZAqXbyQFDuPw790l8%3D&quot; alt=&quot;RAG 검색 품질 개선 전략 개요&quot; /&gt;
&lt;figcaption style=&quot;margin-top: 0.5rem; font-size: 0.85rem; color: #64748b;&quot;&gt;RAG 검색 품질 개선 전략 개요&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;2-hybrid-search-bm25-dense-retrieval&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;2. Hybrid Search: BM25와 Dense Retrieval의 결합&lt;/h2&gt;
&lt;h3 id=&quot;21&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;2.1 왜 두 가지를 결합하는가&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Dense Retrieval(벡터 검색)과 Sparse Retrieval(BM25)은 서로 다른 강점을 가집니다.&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;특성&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;Dense Retrieval (벡터 검색)&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;Sparse Retrieval (BM25)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;강점&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;의미적 유사성, 동의어/패러프레이즈 처리&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;정확한 키워드 매칭, 희귀 용어 검색&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;약점&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;희귀 키워드나 코드에 취약&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;의미적 연관성을 이해하지 못함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;적합한 질문&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&quot;환불 정책 알려줘&quot; (문서에는 &quot;반품 절차&quot;)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&quot;ERROR-4032 해결 방법&quot; (정확한 코드 매칭)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;인덱스&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;벡터 인덱스 (HNSW, IVF 등)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;역인덱스 (Inverted Index)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;운영 환경에서 들어오는 질문은 한 가지 유형으로 통일되지 않습니다. 의미 기반 질문과 키워드 기반 질문이 섞여 있으므로, 두 방식을 결합해야 전체 커버리지가 높아집니다.&lt;/p&gt;
&lt;h3 id=&quot;22-hybrid-search&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;2.2 Hybrid Search 아키텍처&lt;/h3&gt;
&lt;figure style=&quot;text-align: center; margin: 2rem 0;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dna/bcX8gy/dJMcaijilS5/AAAAAAAAAAAAAAAAAAAAAEzyzdwPesDTnkrV6zr_zJtHfh4ft9_jrrAnU5_25I-p/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1782831599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=RC58iMtOe6uw1BJLDYbsGOB6kxc%3D&quot; alt=&quot;Hybrid Search 아키텍처&quot; /&gt;
&lt;figcaption style=&quot;margin-top: 0.5rem; font-size: 0.85rem; color: #64748b;&quot;&gt;Hybrid Search 아키텍처&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Hybrid Search의 기본 흐름은 다음과 같습니다.&lt;/p&gt;
&lt;ol style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;병렬 검색&lt;/b&gt;: 동일한 쿼리를 BM25 인덱스와 벡터 인덱스에 각각 전달합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;개별 결과 수집&lt;/b&gt;: BM25에서 Top-N, 벡터 검색에서 Top-N 결과를 가져옵니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;결과 융합(Fusion)&lt;/b&gt;: 두 결과 리스트를 하나로 합칩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;최종 Top-K 선택&lt;/b&gt;: 융합된 순위에서 상위 K개를 LLM에 전달합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;23-reciprocal-rank-fusion-rrf&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;2.3 결과 융합: Reciprocal Rank Fusion (RRF)&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;두 검색 결과의 점수 스케일이 다르므로(BM25는 0~수십, 코사인 유사도는 0~1), 단순 점수 합산은 효과적이지 않습니다. Reciprocal Rank Fusion(RRF)은 점수 대신 순위만 사용하여 이 문제를 해결합니다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;RRF_score(d) = &amp;Sigma;  1 / (k + rank(r, d))
               r&amp;isin;R
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;d&lt;/code&gt;: 문서&lt;/li&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;R&lt;/code&gt;: 결과 리스트 집합 (BM25 결과, 벡터 검색 결과)&lt;/li&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;rank(r, d)&lt;/code&gt;: 결과 리스트 r에서 문서 d의 순위&lt;/li&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;k&lt;/code&gt;: 스무딩 상수 (일반적으로 60)&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;예시: 문서 A가 BM25에서 3위, 벡터에서 7위인 경우&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;RRF_score(A) = 1/(60+3) + 1/(60+7) = 0.0159 + 0.0149 = 0.0308
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;RRF의 장점은 점수 보정(calibration)이 불필요하다는 것입니다. BM25 점수와 코사인 유사도의 스케일이 달라도, 순위 기반으로 동작하므로 안정적으로 융합됩니다.&lt;/p&gt;
&lt;h3 id=&quot;24-langchain-elasticsearch&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;2.4 구현 예시: LangChain + Elasticsearch&lt;/h3&gt;
&lt;pre class=&quot;nix&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;from langchain.retrievers import EnsembleRetriever
from langchain_community.retrievers import BM25Retriever
from langchain_community.vectorstores import OpenSearchVectorSearch

# BM25 Retriever 설정
bm25_retriever = BM25Retriever.from_documents(documents)
bm25_retriever.k = 20  # 상위 20개 후보

# Vector Retriever 설정
vector_store = OpenSearchVectorSearch(
    index_name=&quot;documents&quot;,
    embedding_function=embeddings,
    opensearch_url=&quot;https://opensearch-endpoint:443&quot;
)
vector_retriever = vector_store.as_retriever(search_kwargs={&quot;k&quot;: 20})

# Hybrid: EnsembleRetriever (RRF 기반 융합)
hybrid_retriever = EnsembleRetriever(
    retrievers=[bm25_retriever, vector_retriever],
    weights=[0.4, 0.6]  # BM25 40%, Vector 60% 가중치
)

results = hybrid_retriever.invoke(&quot;ERROR-4032 에러 해결 방법&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;25&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;2.5 가중치 설정 기준&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;BM25와 Dense Retrieval 간 가중치는 질문 유형 분포에 따라 조정합니다.&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;질문 유형 분포&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;권장 가중치 (BM25 : Vector)&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;이유&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;키워드/코드 질문이 많음&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;0.5 : 0.5 또는 0.6 : 0.4&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;BM25가 정확 매칭에 강함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;의미 기반 질문이 많음&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;0.3 : 0.7&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;벡터 검색이 패러프레이즈에 강함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;혼합&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;0.4 : 0.6&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;일반적인 시작점&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;운영 중에는 실제 질문 로그를 분석하여 키워드형과 의미형 비율을 측정하고, 가중치를 조정합니다.&lt;/p&gt;
&lt;h3 id=&quot;26-hybrid-search-db&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;2.6 Hybrid Search를 지원하는 벡터 DB&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;벡터 DB&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;Hybrid Search 지원 방식&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;비고&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;OpenSearch&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;네이티브 BM25 + kNN 통합&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;RRF 내장 지원&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Elasticsearch&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;BM25 + dense_vector kNN&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;RRF 내장 지원 (8.x+)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Weaviate&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;BM25 + Vector 하이브리드 모드&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;alpha 파라미터로 가중치 조정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Pinecone&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Sparse-Dense 벡터 결합&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;단일 인덱스에서 하이브리드 지원&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;pgvector&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;벡터 검색만 네이티브, BM25는 별도 구현 필요&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;pg_search 또는 별도 BM25 레이어 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;3-reranking&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;3. Reranking: 검색 결과 재정렬&lt;/h2&gt;
&lt;h3 id=&quot;31-reranking&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;3.1 왜 Reranking이 필요한가&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Hybrid Search로 후보를 가져온 후에도, Top-K 내 순위가 최적이 아닐 수 있습니다. 이유는 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Bi-encoder의 한계&lt;/b&gt;: 벡터 검색에 사용되는 Bi-encoder는 질문과 문서를 각각 독립적으로 인코딩합니다. 두 벡터 간 유사도를 계산하므로 속도는 빠르지만, 질문과 문서의 세밀한 상호작용을 포착하지 못합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Cross-encoder의 강점&lt;/b&gt;: 질문과 문서를 하나의 입력으로 결합하여 동시에 처리합니다. 질문의 각 토큰이 문서의 모든 토큰과 상호작용(attention)하므로, 관련성 판단이 훨씬 정밀합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;less&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;Bi-encoder:   query &amp;rarr; [Encoder] &amp;rarr; q_vec   }&amp;rarr; cosine(q_vec, d_vec)
              doc   &amp;rarr; [Encoder] &amp;rarr; d_vec   }

Cross-encoder: [query + doc] &amp;rarr; [Encoder] &amp;rarr; relevance_score
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Cross-encoder는 정확도가 높지만, 모든 문서에 적용하면 속도가 느립니다. 따라서 1단계에서 후보를 좁히고(Retrieval), 2단계에서 소수의 후보만 재정렬하는(Reranking) 2단계 구조가 표준입니다.&lt;/p&gt;
&lt;figure style=&quot;text-align: center; margin: 2rem 0;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dna/cvrvky/dJMcaijilTa/AAAAAAAAAAAAAAAAAAAAAIQogd7x4AqEUl8-b5JPbdXnko3Av_jda7tuCAC4kdY4/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1782831599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=Fo%2B%2Fnv0QcN2OA604itSZTzepcDs%3D&quot; alt=&quot;Reranking 파이프라인&quot; /&gt;
&lt;figcaption style=&quot;margin-top: 0.5rem; font-size: 0.85rem; color: #64748b;&quot;&gt;Reranking 파이프라인&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;32-2-retrieval-reranking&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;3.2 2단계 Retrieval-Reranking 파이프라인&lt;/h3&gt;
&lt;pre class=&quot;less&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;[전체 문서 인덱스]
    &amp;darr; 1단계: Retrieval (Hybrid Search)
[Top-50 후보] &amp;larr; 빠른 검색 (ms 단위)
    &amp;darr; 2단계: Reranking (Cross-encoder)
[Top-5 재정렬] &amp;larr; 정밀 평가 (수백 ms)
    &amp;darr;
[LLM에 전달]
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;1단계 (Retrieval)&lt;/b&gt;: Hybrid Search로 Top-50 후보를 빠르게 가져옵니다. 여기서는 Recall(관련 문서를 빠뜨리지 않는 것)이 중요합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;2단계 (Reranking)&lt;/b&gt;: 50개 후보를 Cross-encoder로 정밀 평가하여 Top-5를 선별합니다. 여기서는 Precision(상위 결과의 정확도)이 중요합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;33-reranking&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;3.3 Reranking 모델 선택지&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;모델/서비스&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;컨텍스트 윈도우&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;특징&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;비고&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Cohere Rerank 4&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;32,000 토큰&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;100+ 언어 지원, JSON 구조 데이터 처리 가능&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;rerank-v4.0 (품질 우선) / rerank-v4.0-fast (속도 우선)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Cohere Rerank 3.5&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;4,096 토큰&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;다국어 SOTA, 추론 능력 포함&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;비용 대비 효율 높음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Cross-encoder/ms-marco-MiniLM&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;512 토큰&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;오픈소스, 로컬 실행 가능&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;영어 중심, 짧은 문서에 적합&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;BGE-reranker-v2-m3&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;8,192 토큰&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;오픈소스, 다국어 지원&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;로컬 GPU 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Jina Reranker v2&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;8,192 토큰&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;코드, 다국어 지원&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;API 또는 로컬 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;34-cohere-rerank&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;3.4 구현 예시: Cohere Rerank&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;import cohere

co = cohere.ClientV2()

# 1단계: Hybrid Search로 후보 검색 (위 예시에서 가져온 결과)
candidates = hybrid_retriever.invoke(query)

# 2단계: Reranking
rerank_response = co.rerank(
    model=&quot;rerank-v4.0&quot;,
    query=&quot;ERROR-4032 에러 해결 방법&quot;,
    documents=[doc.page_content for doc in candidates],
    top_n=5,  # 상위 5개만 반환
    return_documents=True
)

# 재정렬된 결과
for result in rerank_response.results:
    print(f&quot;Score: {result.relevance_score:.4f}&quot;)
    print(f&quot;Content: {result.document.text[:100]}...&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;35-reranking-trade-off&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;3.5 Reranking 비용-성능 trade-off&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Reranking은 후보 수에 비례하여 비용과 지연 시간이 증가합니다.&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;후보 수&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;지연 시간 (일반적)&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;정밀도 향상&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;비용 영향&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;10개&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;50~100ms&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;소폭 향상&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;낮음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;20~30개&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;100~200ms&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;중간 향상&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;중간&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;50개&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;200~400ms&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;높은 향상&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;높음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;100개 이상&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;400ms+&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;추가 향상 미미&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;높음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;운영 환경에서는 후보 수를 20~50개로 설정하는 것이 일반적입니다. 그 이상에서는 비용 대비 품질 향상이 감소합니다.&lt;/p&gt;
&lt;h3 id=&quot;36-reranking-lost-in-the-middle&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;3.6 Reranking 없이 대안: Lost in the Middle 문제&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Reranking을 적용하지 않으면, LLM이 긴 컨텍스트의 중간에 있는 정보를 놓치는 &quot;Lost in the Middle&quot; 현상이 발생할 수 있습니다. 연구에 따르면, LLM은 컨텍스트의 처음과 끝에 있는 정보는 잘 활용하지만, 중간에 있는 정보는 간과하는 경향이 있습니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Reranking으로 가장 관련성 높은 문서를 상위에 배치하면, LLM이 핵심 정보를 먼저 읽게 되어 이 문제를 완화할 수 있습니다.&lt;/p&gt;
&lt;h2 id=&quot;4-query-transformation&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;4. Query Transformation: 질문을 검색에 최적화하기&lt;/h2&gt;
&lt;h3 id=&quot;41&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;4.1 왜 질문을 변환하는가&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;사용자 질문은 검색에 최적화된 형태가 아닌 경우가 많습니다.&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;모호한 질문&lt;/b&gt;: &quot;이거 어떻게 해?&quot; &amp;rarr; 무엇을 어떻게 하는 건지 불명확&lt;/li&gt;
&lt;li&gt;&lt;b&gt;대화체 질문&lt;/b&gt;: &quot;서버가 느려졌는데 왜 그런 거야?&quot; &amp;rarr; 문서에는 &quot;Latency 증가 원인&quot;으로 기술&lt;/li&gt;
&lt;li&gt;&lt;b&gt;복합 질문&lt;/b&gt;: &quot;VPC 설계하려는데 서브넷 분리 기준이랑 보안 그룹 설정 방법 알려줘&quot; &amp;rarr; 두 가지 주제가 혼합&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Query Transformation은 이런 질문을 검색에 더 적합한 형태로 바꾸는 전처리 단계입니다.&lt;/p&gt;
&lt;figure style=&quot;text-align: center; margin: 2rem 0;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dna/b3Iu95/dJMcaip3IOf/AAAAAAAAAAAAAAAAAAAAALORMCkjgXBKVR1qGxDHdB3nrVzSW0dbm4GlJG8mae-I/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1782831599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=dWkZhQ%2FjPlLL%2FZ3uExHwn6eBoD0%3D&quot; alt=&quot;Query Transformation 전략 비교&quot; /&gt;
&lt;figcaption style=&quot;margin-top: 0.5rem; font-size: 0.85rem; color: #64748b;&quot;&gt;Query Transformation 전략 비교&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;42-multi-query&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;4.2 Multi-Query: 질문을 여러 관점으로 확장&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;하나의 질문을 여러 버전으로 변환하여 각각 검색한 후 결과를 합칩니다. 하나의 표현으로 검색하면 놓칠 수 있는 관련 문서를 다양한 표현으로 커버합니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;from langchain.retrievers.multi_query import MultiQueryRetriever
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model=&quot;gpt-4o-mini&quot;, temperature=0.3)

multi_query_retriever = MultiQueryRetriever.from_llm(
    retriever=vector_retriever,
    llm=llm
)

# 원본: &quot;EKS에서 Pod가 외부 통신이 안 되는 이유는?&quot;
# 생성되는 변형:
# 1. &quot;EKS Pod outbound traffic 실패 원인&quot;
# 2. &quot;Kubernetes Pod 인터넷 접근 불가 해결 방법&quot;
# 3. &quot;EKS NAT Gateway VPC 설정 문제&quot;
results = multi_query_retriever.invoke(
    &quot;EKS에서 Pod가 외부 통신이 안 되는 이유는?&quot;
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장점&lt;/b&gt;: 원래 질문으로는 검색되지 않던 관련 문서를 추가로 찾을 수 있습니다. &lt;b&gt;단점&lt;/b&gt;: LLM 호출이 추가되므로 지연 시간과 비용이 증가합니다.&lt;/p&gt;
&lt;h3 id=&quot;43-hyde-hypothetical-document-embeddings&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;4.3 HyDE: Hypothetical Document Embeddings&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;질문을 그대로 임베딩하지 않고, LLM이 가상의 답변 문서를 먼저 생성한 뒤 그 문서를 임베딩하여 검색합니다.&lt;/p&gt;
&lt;pre class=&quot;groovy&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;일반 RAG:     질문 &amp;rarr; 임베딩 &amp;rarr; 벡터 검색 &amp;rarr; 결과
HyDE:         질문 &amp;rarr; LLM(가상 답변 생성) &amp;rarr; 가상 답변 임베딩 &amp;rarr; 벡터 검색 &amp;rarr; 결과
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;핵심 아이디어는 &quot;질문과 문서의 표현 격차(gap)를 줄이는 것&quot;입니다. 질문 벡터보다 문서와 비슷한 형태의 벡터로 검색하면, 문서 인덱스에서 더 정확한 매칭이 가능합니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;from langchain.chains import HypotheticalDocumentEmbedder
from langchain_openai import ChatOpenAI, OpenAIEmbeddings

llm = ChatOpenAI(model=&quot;gpt-4o-mini&quot;, temperature=0)
base_embeddings = OpenAIEmbeddings(model=&quot;text-embedding-3-small&quot;)

# HyDE Embedder: LLM으로 가상 문서 생성 후 임베딩
hyde_embeddings = HypotheticalDocumentEmbedder.from_llm(
    llm=llm,
    base_embeddings=base_embeddings,
    prompt_key=&quot;web_search&quot;  # 프롬프트 템플릿 유형
)

# &quot;서버가 느려졌을 때 대응 방법&quot; 질문에 대해
# LLM이 먼저 가상의 답변 문서를 생성:
# &quot;서버 응답 시간 증가 시 수평 확장, 캐시 레이어 도입,
#  DB 쿼리 최적화, CDN 적용을 검토합니다...&quot;
# &amp;rarr; 이 가상 문서의 임베딩으로 검색
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장점&lt;/b&gt;: 질문-문서 간 어휘 불일치(vocabulary mismatch) 문제를 효과적으로 해결합니다. &lt;b&gt;단점&lt;/b&gt;: LLM이 생성한 가상 답변이 부정확하면 오히려 검색 품질이 떨어질 수 있습니다. 사실 기반 질문(수치, 코드명 등)에서는 효과가 제한적입니다.&lt;/p&gt;
&lt;h3 id=&quot;44-query-decomposition&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;4.4 Query Decomposition: 복합 질문 분해&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;하나의 복합 질문을 여러 단순 질문으로 분해하여 각각 검색합니다.&lt;/p&gt;
&lt;pre class=&quot;python&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;from langchain.output_parsers import NumberedListOutputParser
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model=&quot;gpt-4o-mini&quot;, temperature=0)

decomposition_prompt = &quot;&quot;&quot;
다음 질문을 2~4개의 단순한 하위 질문으로 분해하세요.
각 하위 질문은 독립적으로 검색할 수 있어야 합니다.

질문: {question}
&quot;&quot;&quot;

# 원본: &quot;VPC 설계 시 서브넷 분리 기준과 보안 그룹 설정 방법은?&quot;
# 분해 결과:
# 1. &quot;VPC 서브넷 분리 기준은?&quot;
# 2. &quot;Public/Private 서브넷 설계 패턴은?&quot;
# 3. &quot;보안 그룹 설정 방법과 규칙 설계 기준은?&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;적합한 경우&lt;/b&gt;: 사용자가 여러 주제를 한 번에 질문하는 패턴이 많은 시스템. FAQ 챗봇보다는 기술 문서 검색, 리서치 보조 도구에 적합합니다.&lt;/p&gt;
&lt;h3 id=&quot;45-step-back-prompting&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;4.5 Step-back Prompting: 추상화된 질문으로 검색&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;구체적인 질문을 한 단계 추상화하여 더 넓은 범위의 관련 문서를 찾습니다.&lt;/p&gt;
&lt;pre class=&quot;applescript&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;원본 질문:    &quot;EKS 1.28에서 CoreDNS가 CrashLoopBackOff 되는 이유?&quot;
Step-back:   &quot;Kubernetes CoreDNS 장애 원인과 해결 방법&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;구체적인 질문으로만 검색하면 해당 버전에 국한된 문서만 찾을 수 있습니다. Step-back으로 범용적인 문서를 함께 검색하면, 배경 지식과 구체적 해결책을 모두 LLM에 제공할 수 있습니다.&lt;/p&gt;
&lt;h3 id=&quot;46&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;4.6 전략별 비교&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;전략&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;추가 LLM 호출&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;지연 시간 증가&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;효과적인 상황&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;주의할 점&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Multi-Query&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;1회 (변형 생성)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;+200~500ms&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;다양한 표현의 문서가 있을 때&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;결과 중복 제거 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;HyDE&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;1회 (가상 답변 생성)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;+300~800ms&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;질문-문서 표현 격차가 클 때&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;사실 기반 질문에는 비효과적&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Query Decomposition&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;1회 (분해)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;+200~500ms&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;복합 질문이 많을 때&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;단순 질문에는 오버헤드만 증가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Step-back Prompting&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;1회 (추상화)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;+200~500ms&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;구체적 문제에 배경 지식이 필요할 때&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;추상화가 너무 넓으면 노이즈 증가&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;5&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;5. 전략 조합: 실무 파이프라인 설계&lt;/h2&gt;
&lt;h3 id=&quot;51&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;5.1 권장 파이프라인 구성&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;세 전략을 조합한 실무 파이프라인의 일반적인 구성입니다.&lt;/p&gt;
&lt;pre class=&quot;less&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;[사용자 질문]
    &amp;darr; (선택) Query Transformation
[변환된 질문(들)]
    &amp;darr; Hybrid Search (BM25 + Dense)
[Top-50 후보]
    &amp;darr; Reranking (Cross-encoder)
[Top-5 재정렬]
    &amp;darr;
[LLM 응답 생성]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;52&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;5.2 시나리오별 권장 조합&lt;/h3&gt;
&lt;figure style=&quot;text-align: center; margin: 2rem 0;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dna/bV8hd3/dJMcabkdDXB/AAAAAAAAAAAAAAAAAAAAABkUI31vzmMXhhbcKBdqQPb59pFmU7e-uewYUUmODB34/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1782831599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=HU%2BvesMjVMNqHeEawHskppNMxac%3D&quot; alt=&quot;전략 조합 의사결정 흐름&quot; /&gt;
&lt;figcaption style=&quot;margin-top: 0.5rem; font-size: 0.85rem; color: #64748b;&quot;&gt;전략 조합 의사결정 흐름&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;시나리오 1: 사내 기술 문서 검색 (에러 코드 + 개념 질문 혼합)&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;구성 요소&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;선택&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;이유&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;검색&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Hybrid Search (BM25 0.4 : Vector 0.6)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;에러 코드는 BM25, 개념 질문은 벡터&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Reranking&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Cohere Rerank 4&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;다국어(한국어 문서) 지원, 긴 문서 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Query Transformation&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Multi-Query&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;동일 개념의 다양한 표현을 커버&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;시나리오 2: 고객 지원 FAQ 챗봇 (짧은 질문, 빠른 응답 필요)&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;구성 요소&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;선택&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;이유&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;검색&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Hybrid Search (BM25 0.3 : Vector 0.7)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;고객 질문은 대부분 의미 기반&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Reranking&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Cohere Rerank v4.0-fast 또는 경량 Cross-encoder&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;지연 시간 최소화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Query Transformation&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;미적용&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;FAQ 질문은 보통 단순하고 직접적&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;시나리오 3: 법률/계약서 분석 시스템 (정확도 최우선)&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;구성 요소&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;선택&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;이유&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;검색&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Hybrid Search (BM25 0.5 : Vector 0.5)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;조항 번호 매칭 + 의미 검색 균형&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Reranking&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Cohere Rerank 4 (Top-50 &amp;rarr; Top-5)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;정밀도 최우선, 지연 시간 허용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Query Transformation&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Query Decomposition + Step-back&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;복합 법률 질문 분해 + 배경 조항 검색&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;53&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;5.3 점진적 도입 전략&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;모든 전략을 한 번에 도입하기보다, 단계적으로 추가하며 각 단계의 효과를 측정하는 방식을 권장합니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;1단계: Dense Retrieval만 (baseline 측정)
   &amp;rarr; Recall@10: 65%, Precision@5: 40%

2단계: + BM25 Hybrid Search 추가
   &amp;rarr; Recall@10: 80% (+15%), Precision@5: 48% (+8%)

3단계: + Reranking 추가
   &amp;rarr; Recall@10: 80% (유지), Precision@5: 68% (+20%)

4단계: + Query Transformation 추가
   &amp;rarr; Recall@10: 88% (+8%), Precision@5: 72% (+4%)
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;각 단계에서 개선이 확인되면 다음 단계로 진행합니다. 특정 전략이 효과가 없으면 해당 단계를 건너뛰거나 다른 방식으로 대체합니다.&lt;/p&gt;
&lt;h2 id=&quot;6&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;6. 비용/운영 고려사항&lt;/h2&gt;
&lt;h3 id=&quot;61&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;6.1 전략별 비용 영향&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;전략&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;추가 API 호출&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;지연 시간 영향&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;인프라 요구사항&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Hybrid Search&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;없음 (인덱스 추가만)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;+10~50ms&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;BM25 인덱스 추가 (Elasticsearch 등)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Reranking&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Rerank API 1회/쿼리&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;+100~400ms&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;외부 API 또는 GPU 서버&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Multi-Query&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;LLM 1회/쿼리&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;+200~500ms&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;LLM API 비용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;HyDE&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;LLM 1회/쿼리&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;+300~800ms&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;LLM API 비용&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;62&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;6.2 비용 최적화 방법&lt;/h3&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Reranking 후보 수 제한&lt;/b&gt;: 50개 이상은 비용 대비 효과가 감소합니다. 20~30개로 시작합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Query Transformation 조건부 적용&lt;/b&gt;: 모든 질문에 HyDE를 적용하면 비용이 2배 증가합니다. 질문 유형을 분류하여 필요한 경우에만 적용합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;캐시 활용&lt;/b&gt;: 동일하거나 유사한 질문에 대한 검색 결과를 캐시하면 반복 비용을 줄일 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;경량 Reranker 사용&lt;/b&gt;: 실시간 응답이 중요한 경우 Cohere Rerank v4.0-fast나 오픈소스 경량 모델을 선택합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;63&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;6.3 모니터링 지표&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;운영 중 검색 품질을 지속적으로 관찰하기 위한 지표입니다.&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;지표&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;설명&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;목표&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Retrieval Latency&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;검색 완료까지 소요 시간 (Reranking 포함)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&amp;lt; 500ms (일반), &amp;lt; 200ms (실시간)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Reranker Score Distribution&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Top-K 결과의 relevance score 분포&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;상위 결과가 0.8+ 유지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Empty Result Rate&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;검색 결과가 비어 있는 비율&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&amp;lt; 5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;User Feedback&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;사용자가 응답에 만족했는지 (thumbs up/down)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;만족도 80%+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Fallback Rate&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Query Transformation 후에도 검색 실패하는 비율&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&amp;lt; 10%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;7&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;7. 자주 하는 실수&lt;/h2&gt;
&lt;ol style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Reranking 없이 Top-K를 바로 LLM에 전달&lt;/b&gt;: 벡터 유사도 순위가 실제 관련성 순위와 다를 수 있습니다. 특히 Top-5 내 순서가 중요한 경우 Reranking 없이는 &quot;Lost in the Middle&quot; 문제가 발생합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;모든 질문에 HyDE 적용&lt;/b&gt;: 사실 기반 질문(코드, 수치, 이름 검색)에 HyDE를 적용하면 LLM이 잘못된 가상 답변을 생성하여 오히려 검색 품질이 떨어집니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;BM25 인덱스를 관리하지 않음&lt;/b&gt;: 벡터 인덱스만 업데이트하고 BM25 인덱스 동기화를 놓치면, Hybrid Search에서 오래된 BM25 결과가 반환됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Reranking 후보를 너무 적게 설정&lt;/b&gt;: Top-5만 가져와서 Reranking하면, 원래 6위에 있던 정답 문서를 놓칩니다. Recall을 확보하려면 1단계에서 충분한 후보(20~50개)를 가져와야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;전략 효과를 측정하지 않음&lt;/b&gt;: 각 전략이 실제로 검색 품질을 개선하는지 A/B 테스트 없이 도입하면, 비용만 증가하고 품질은 개선되지 않을 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;RRF의 k값을 조정하지 않음&lt;/b&gt;: RRF의 스무딩 상수 k(기본 60)는 순위 상위 문서에 얼마나 가중치를 줄지를 결정합니다. 도메인에 따라 k값을 실험해볼 필요가 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;8&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;8. 정리&lt;/h2&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Hybrid Search&lt;/b&gt;는 키워드 매칭과 의미 검색을 결합하여 단일 방식의 맹점을 보완합니다. RRF로 점수 보정 없이 안정적으로 융합할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Reranking&lt;/b&gt;은 Cross-encoder로 검색 결과의 순위를 정밀하게 재조정합니다. 비용 대비 정밀도 향상 효과가 가장 큰 전략입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Query Transformation&lt;/b&gt;은 사용자 질문과 문서 간 표현 격차를 줄여 Recall을 높입니다. 다만 모든 질문에 적용할 필요는 없으며, 질문 유형에 따라 선택적으로 적용합니다.&lt;/li&gt;
&lt;li&gt;세 전략의 도입 순서는 일반적으로 Hybrid Search &amp;rarr; Reranking &amp;rarr; Query Transformation입니다. 각 단계에서 효과를 측정하고, 비용 대비 개선이 확인되면 다음 단계로 진행합니다.&lt;/li&gt;
&lt;li&gt;측정 없는 최적화는 불가능합니다. Baseline을 먼저 측정하고, 각 전략 추가 후 Retrieval Precision/Recall 변화를 추적하는 체계가 가장 먼저 필요합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;_2&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;참고 문서&lt;/h2&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://python.langchain.com/docs/how_to/#retrievers&quot;&gt;LangChain 공식 문서: Retrievers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.cohere.com/docs/rerank&quot;&gt;Cohere 공식 문서: Rerank&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.pinecone.io/guides/data/understanding-hybrid-search&quot;&gt;Pinecone 공식 문서: Hybrid Search&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://opensearch.org/docs/latest/search-plugins/hybrid-search/&quot;&gt;OpenSearch 공식 문서: Hybrid Search&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.ragas.io/&quot;&gt;RAGAS 공식 문서: RAG Evaluation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>AI</category>
      <category>AI Architecture</category>
      <category>bm25</category>
      <category>cross-encoder</category>
      <category>hybrid search</category>
      <category>hyde</category>
      <category>LLM</category>
      <category>Query Transformation</category>
      <category>Rag</category>
      <category>reranking</category>
      <category>vector database</category>
      <author>wero90</author>
      <guid isPermaLink="true">https://wero90.tistory.com/72</guid>
      <comments>https://wero90.tistory.com/72#entry72comment</comments>
      <pubDate>Mon, 8 Jun 2026 10:10:20 +0900</pubDate>
    </item>
    <item>
      <title>Kubernetes Secret을 안전하게 관리하는 방법: etcd 암호화부터 외부 Secret 관리 도구까지</title>
      <link>https://wero90.tistory.com/71</link>
      <description>&lt;blockquote style=&quot;border-left: 4px solid #3b82f6; padding: 1rem 1.2rem; margin: 1.5rem 0; background: #f0f9ff; color: #1e40af; font-size: 1.05rem; line-height: 1.7; border-radius: 0 8px 8px 0;&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Kubernetes Secret은 민감 데이터를 저장하는 기본 메커니즘이지만, 기본 설정만으로는 안전하지 않습니다. Base64 인코딩은 암호화가 아니며, etcd에 평문으로 저장될 수 있습니다. 운영 환경에서는 암호화, 접근 제어, 외부 Secret 관리 도구를 조합하여 보안을 강화해야 합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;_1&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;핵심 요약&lt;/h2&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Kubernetes Secret은 Base64 인코딩만 적용됩니다. 이는 난독화일 뿐 암호화가 아닙니다.&lt;/li&gt;
&lt;li&gt;기본 설정에서 Secret은 etcd에 평문으로 저장됩니다. EncryptionConfiguration을 통해 저장 시 암호화(Encryption at Rest)를 활성화해야 합니다.&lt;/li&gt;
&lt;li&gt;RBAC로 Secret 접근을 최소화하고, 감사 로그로 접근 이력을 추적합니다.&lt;/li&gt;
&lt;li&gt;운영 환경에서는 외부 Secret 관리 도구(External Secrets Operator, Sealed Secrets, HashiCorp Vault)를 사용하여 Git 저장소에 Secret이 노출되지 않도록 합니다.&lt;/li&gt;
&lt;li&gt;매니지드 Kubernetes(EKS, AKS, GKE)는 기본적으로 etcd 암호화를 제공하지만, 클러스터 내부 접근 제어는 별도로 설계해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;1-kubernetes-secret&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;1. Kubernetes Secret의 보안 한계&lt;/h2&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;팀에서 데이터베이스 비밀번호를 Kubernetes Secret으로 관리합니다. YAML 파일을 Git에 커밋하고, 새 팀원이 합류할 때마다 kubectl로 Secret을 확인합니다. 어느 날 보안 감사에서 이런 질문을 받습니다: &quot;이 Secret은 누가 언제 조회했는지 추적할 수 있나요? etcd에 암호화되어 저장되나요?&quot;&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;대부분의 경우 대답은 &quot;아니오&quot;입니다. Kubernetes Secret의 기본 보안 수준이 낮은 이유를 먼저 이해해야 합니다.&lt;/p&gt;
&lt;h3 id=&quot;11-base64&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;1.1 Base64 &amp;ne; 암호화&lt;/h3&gt;
&lt;pre class=&quot;dts&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Secret
metadata:
  name: db-credentials
  namespace: production
type: Opaque
data:
  username: YWRtaW4=        # echo -n &quot;admin&quot; | base64
  password: cEBzc3cwcmQ=    # echo -n &quot;p@ssw0rd&quot; | base64
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;base64 --decode&lt;/code&gt;로 누구나 원본 값을 복원할 수 있습니다. Git에 이 YAML이 커밋되면 사실상 평문과 동일합니다.&lt;/p&gt;
&lt;h3 id=&quot;12-etcd&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;1.2 etcd 평문 저장&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;기본 설정에서 Kubernetes API Server는 Secret을 etcd에 그대로 저장합니다. etcd 백업 파일에 접근하거나, etcd를 직접 조회할 수 있는 사람은 모든 Secret을 평문으로 읽을 수 있습니다.&lt;/p&gt;
&lt;h3 id=&quot;13&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;1.3 과도한 접근 권한&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;view&lt;/code&gt; ClusterRole을 가진 사용자는 Secret을 읽을 수 없지만, &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;edit&lt;/code&gt;이나 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;admin&lt;/code&gt; ClusterRole은 Secret 읽기/쓰기가 가능합니다. 네임스페이스에 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;edit&lt;/code&gt; 권한을 부여하면 의도치 않게 Secret 접근이 허용됩니다.&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;위험&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;원인&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;영향&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Git에 Secret YAML 노출&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Base64가 암호화라고 착각&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;리포지토리 접근자 전원이 Secret 열람 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;etcd 평문 저장&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;EncryptionConfiguration 미설정&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;etcd 백업/접근으로 전체 Secret 유출&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;과도한 RBAC 권한&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;네임스페이스 단위 edit/admin 부여&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;불필요한 서비스까지 Secret 조회 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;감사 추적 불가&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Audit Log 미설정&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;누가 언제 Secret을 읽었는지 확인 불가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Pod에서 환경 변수 노출&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;env로 Secret 주입&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;kubectl describe pod&lt;/code&gt;로 값 노출&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;2&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;2. 보안 강화 전략 개요&lt;/h2&gt;
&lt;figure style=&quot;text-align: center; margin: 2rem 0;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dna/wxIrt/dJMcad3iYg1/AAAAAAAAAAAAAAAAAAAAANsSFEGRsgBTwESqizbrHhQB-GZuIUE6dDlbR-NaXOGn/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1782831599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=Ke68sm35Qclnv7Gssbw23cj7HMU%3D&quot; alt=&quot;Kubernetes Secret 보안 계층 구조&quot; /&gt;
&lt;figcaption style=&quot;margin-top: 0.5rem; font-size: 0.85rem; color: #64748b;&quot;&gt;Kubernetes Secret 보안 계층 구조&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Secret 보안은 단일 도구로 해결되지 않습니다. 여러 계층을 조합하여 방어 깊이(Defense in Depth)를 확보해야 합니다.&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;계층&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;목적&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;도구/설정&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;저장 시 암호화&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;etcd에 암호화된 상태로 저장&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;EncryptionConfiguration, 클라우드 KMS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;접근 제어&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Secret 읽기/쓰기 권한 최소화&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;RBAC, 전용 ServiceAccount&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;전송 시 암호화&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;API Server &amp;harr; etcd 통신 암호화&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;TLS (기본 활성화)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;외부 Secret 관리&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Git에 Secret을 저장하지 않음&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;External Secrets Operator, Sealed Secrets, Vault&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;감사&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;접근 이력 추적&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Kubernetes Audit Log&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;런타임 보호&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Pod 내부에서 Secret 노출 최소화&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Volume mount 방식, &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;automountServiceAccountToken: false&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;3-etcd-encryption-at-rest&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;3. etcd 저장 시 암호화 (Encryption at Rest)&lt;/h2&gt;
&lt;figure style=&quot;text-align: center; margin: 2rem 0;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dna/sNwxQ/dJMcabLjg38/AAAAAAAAAAAAAAAAAAAAAElpTGcjJKCk2MOkMBfXgsA3GH0iiJ8lU6hyAjEzObEv/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1782831599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=MDvLvJtNLwB%2FutTXWQACmYMXw84%3D&quot; alt=&quot;etcd 암호화 동작 흐름&quot; /&gt;
&lt;figcaption style=&quot;margin-top: 0.5rem; font-size: 0.85rem; color: #64748b;&quot;&gt;etcd 암호화 동작 흐름&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;31-encryptionconfiguration&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;3.1 EncryptionConfiguration 설정&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;자체 관리 클러스터(kubeadm 등)에서는 API Server에 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;--encryption-provider-config&lt;/code&gt; 플래그로 암호화를 활성화합니다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# /etc/kubernetes/encryption-config.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
    providers:
      - aescbc:
          keys:
            - name: key1
              secret: &amp;lt;BASE64_ENCODED_32_BYTE_KEY&amp;gt;
      - identity: {}   # 암호화되지 않은 기존 Secret 읽기용 fallback
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;vala&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# 32바이트 키 생성
head -c 32 /dev/urandom | base64

# API Server 설정에 추가
# /etc/kubernetes/manifests/kube-apiserver.yaml
# --encryption-provider-config=/etc/kubernetes/encryption-config.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;암호화 Provider 선택:&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;Provider&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;특징&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;권장 용도&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;aescbc&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;AES-CBC 256비트 암호화. 키를 로컬에 저장&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;소규모 클러스터, 테스트 환경&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;aesgcm&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;AES-GCM 인증 암호화. 키 로테이션 시 주의 필요&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;성능이 중요한 경우&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;kms&lt;/code&gt; v2&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;외부 KMS와 연동 (봉투 암호화)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;운영 환경 권장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;secretbox&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;NaCl SecretBox 사용. 인증+암호화 동시 제공&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;단순한 환경에서 안전한 선택&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div style=&quot;padding: 1rem 1.2rem; margin: 1.5rem 0; border-radius: 8px; border-left: 4px solid #f59e0b; background: #fffbeb; line-height: 1.7;&quot;&gt;&lt;b&gt;주의&lt;/b&gt;&lt;br /&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;identity&lt;/code&gt; provider는 암호화를 적용하지 않습니다. providers 목록에서 첫 번째 항목이 쓰기에 사용됩니다. &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;identity&lt;/code&gt;가 첫 번째이면 암호화가 적용되지 않으므로, 반드시 암호화 provider를 먼저 배치해야 합니다.&lt;/div&gt;
&lt;h3 id=&quot;32-secret&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;3.2 기존 Secret 재암호화&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;EncryptionConfiguration을 적용해도 기존 Secret은 자동으로 재암호화되지 않습니다. 명시적으로 업데이트해야 합니다.&lt;/p&gt;
&lt;pre class=&quot;livecodeserver&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# 모든 네임스페이스의 Secret을 재암호화
kubectl get secrets --all-namespaces -o json | \
  kubectl replace -f -
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;33-kubernetes-etcd&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;3.3 매니지드 Kubernetes에서의 etcd 암호화&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;플랫폼&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;기본 암호화&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;추가 옵션&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;EKS&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;기본 비활성화, 콘솔에서 활성화 가능&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;AWS KMS 키를 지정하여 봉투 암호화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;AKS&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;기본 활성화 (Azure 관리 키)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Customer-Managed Key (CMK) 지정 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;GKE&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;기본 활성화 (Google 관리 키)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;CMEK(Customer-Managed Encryption Key) 지정 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;EKS에서 Secret 암호화를 활성화하는 경우:&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# EKS 클러스터에 KMS 기반 Secret 암호화 활성화
aws eks associate-encryption-config \
  --cluster-name my-cluster \
  --encryption-config '[{&quot;resources&quot;:[&quot;secrets&quot;],&quot;provider&quot;:{&quot;keyArn&quot;:&quot;arn:aws:kms:ap-northeast-2:123456789012:key/key-id&quot;}}]'
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;4-rbac-secret&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;4. RBAC로 Secret 접근 제한&lt;/h2&gt;
&lt;h3 id=&quot;41-secret-role&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;4.1 Secret 전용 Role 설계&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;네임스페이스 내에서도 Secret 접근은 별도 Role로 분리해야 합니다. 개발자에게 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;edit&lt;/code&gt; ClusterRole 대신 Secret을 제외한 커스텀 Role을 부여하는 패턴이 안전합니다.&lt;/p&gt;
&lt;pre class=&quot;dts&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# Secret 읽기를 제외한 개발자 Role
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: production
  name: developer-no-secrets
rules:
- apiGroups: [&quot;&quot;]
  resources: [&quot;pods&quot;, &quot;services&quot;, &quot;configmaps&quot;]
  verbs: [&quot;get&quot;, &quot;list&quot;, &quot;watch&quot;, &quot;create&quot;, &quot;update&quot;, &quot;patch&quot;, &quot;delete&quot;]
- apiGroups: [&quot;apps&quot;]
  resources: [&quot;deployments&quot;, &quot;replicasets&quot;]
  verbs: [&quot;get&quot;, &quot;list&quot;, &quot;watch&quot;, &quot;create&quot;, &quot;update&quot;, &quot;patch&quot;, &quot;delete&quot;]
# Secret에 대한 규칙이 없으므로 접근 불가
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;dts&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# Secret 읽기만 허용하는 별도 Role (필요한 사람에게만 부여)
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: production
  name: secret-reader
rules:
- apiGroups: [&quot;&quot;]
  resources: [&quot;secrets&quot;]
  verbs: [&quot;get&quot;, &quot;list&quot;]
  resourceNames: [&quot;db-credentials&quot;, &quot;api-key&quot;]  # 특정 Secret만 허용
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;42&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;4.2 감사 로그 설정&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Secret 접근 이력을 추적하려면 Kubernetes Audit Policy에서 Secret 관련 이벤트를 기록해야 합니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# audit-policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
  # Secret 읽기/쓰기 모든 동작을 RequestResponse 수준으로 기록
  - level: Metadata
    resources:
      - group: &quot;&quot;
        resources: [&quot;secrets&quot;]
    verbs: [&quot;get&quot;, &quot;list&quot;, &quot;watch&quot;, &quot;create&quot;, &quot;update&quot;, &quot;patch&quot;, &quot;delete&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;div style=&quot;padding: 1rem 1.2rem; margin: 1.5rem 0; border-radius: 8px; border-left: 4px solid #3b82f6; background: #eff6ff; line-height: 1.7;&quot;&gt;&lt;b&gt;Tip&lt;/b&gt;&lt;br /&gt;Secret 감사 로그에서 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;level: RequestResponse&lt;/code&gt;를 사용하면 Secret 값 자체가 로그에 기록될 수 있습니다. &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;level: Metadata&lt;/code&gt;는 &quot;누가, 언제, 어떤 Secret에 접근했는가&quot;만 기록하므로 보안과 감사 모두를 만족합니다.&lt;/div&gt;
&lt;h2 id=&quot;5-secret&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;5. 외부 Secret 관리 도구 비교&lt;/h2&gt;
&lt;figure style=&quot;text-align: center; margin: 2rem 0;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dna/w00Bj/dJMcab5xHuH/AAAAAAAAAAAAAAAAAAAAAATl8LA4EZbyZbU6RhukJYgm5eb28Zqpbtgw9b25m8YU/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1782831599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=U%2BqRV9CP%2BJdENjOfsQ47%2B3O0J6c%3D&quot; alt=&quot;외부 Secret 관리 도구 동작 비교&quot; /&gt;
&lt;figcaption style=&quot;margin-top: 0.5rem; font-size: 0.85rem; color: #64748b;&quot;&gt;외부 Secret 관리 도구 동작 비교&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Git 저장소에 Secret YAML을 직접 저장하지 않으려면 외부 Secret 관리 도구를 사용해야 합니다. 각 도구의 동작 방식과 trade-off가 다릅니다.&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;도구&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;방식&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;Git에 저장하는 것&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;Secret 원본 위치&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;복잡도&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Sealed Secrets&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;비대칭 암호화로 암호화된 Secret을 Git에 저장&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;암호화된 SealedSecret YAML&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;클러스터 내부 (컨트롤러가 복호화)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;낮음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;External Secrets Operator&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;외부 저장소에서 Secret을 동기화&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;ExternalSecret 참조 YAML (값 미포함)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;AWS Secrets Manager, Vault, Azure Key Vault 등&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;중간&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;HashiCorp Vault + VSO&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Vault에서 직접 Secret을 주입&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;VaultStaticSecret/VaultDynamicSecret CRD&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;HashiCorp Vault&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;높음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Secrets Store CSI Driver&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;CSI 볼륨으로 Secret을 Pod에 마운트&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;SecretProviderClass YAML (값 미포함)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;클라우드 KMS, Vault&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;중간&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;_2&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;의사결정 기준&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;Q1. Secret 원본을 어디에 저장할 것인가?
├── 클라우드 Secret 서비스 (Secrets Manager, Key Vault 등) &amp;rarr; External Secrets Operator
├── 자체 관리 Vault &amp;rarr; HashiCorp Vault + VSO
└── 별도 인프라 없이 Git 기반으로 &amp;rarr; Sealed Secrets

Q2. 동적 Secret(TTL 기반 자동 갱신)이 필요한가?
├── 예 &amp;rarr; HashiCorp Vault (Dynamic Secrets)
└── 아니오 &amp;rarr; External Secrets Operator 또는 Sealed Secrets

Q3. 팀 규모와 운영 복잡도를 감당할 수 있는가?
├── 소규모 팀, 단일 클러스터 &amp;rarr; Sealed Secrets
├── 중규모, 멀티 클라우드 &amp;rarr; External Secrets Operator
└── 대규모, 엄격한 보안 요구사항 &amp;rarr; HashiCorp Vault
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;6-sealed-secrets-git&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;6. Sealed Secrets: Git 친화적 암호화&lt;/h2&gt;
&lt;figure style=&quot;text-align: center; margin: 2rem 0;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dna/c23sgI/dJMcab5xHuK/AAAAAAAAAAAAAAAAAAAAAPjEkejB271XvSurFAE_JmdmAjJi0fv4eHwEfkHnvJ38/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1782831599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=l19eLynigMIf%2FEXFEqE3%2FMQNxMM%3D&quot; alt=&quot;Sealed Secrets 동작 흐름&quot; /&gt;
&lt;figcaption style=&quot;margin-top: 0.5rem; font-size: 0.85rem; color: #64748b;&quot;&gt;Sealed Secrets 동작 흐름&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Sealed Secrets는 Bitnami에서 개발한 오픈소스 도구입니다. 비대칭 암호화를 사용하여 클러스터의 컨트롤러만 복호화할 수 있는 SealedSecret을 생성합니다. 암호화된 YAML은 Git에 안전하게 커밋할 수 있습니다.&lt;/p&gt;
&lt;h3 id=&quot;61&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;6.1 동작 원리&lt;/h3&gt;
&lt;ol style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;클러스터에 Sealed Secrets 컨트롤러를 설치합니다. 컨트롤러가 RSA 키 쌍을 생성합니다.&lt;/li&gt;
&lt;li&gt;개발자가 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;kubeseal&lt;/code&gt; CLI로 일반 Secret을 SealedSecret으로 암호화합니다.&lt;/li&gt;
&lt;li&gt;암호화된 SealedSecret YAML을 Git에 커밋합니다.&lt;/li&gt;
&lt;li&gt;클러스터에 SealedSecret이 적용되면, 컨트롤러가 복호화하여 일반 Kubernetes Secret을 생성합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;62&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;6.2 설치 및 사용&lt;/h3&gt;
&lt;pre class=&quot;properties&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# 컨트롤러 설치 (Helm)
helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets
helm install sealed-secrets sealed-secrets/sealed-secrets \
  --namespace kube-system

# kubeseal CLI 설치 (v0.27.x 기준)
# macOS
brew install kubeseal

# Linux
wget https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.27.3/kubeseal-0.27.3-linux-amd64.tar.gz
tar -xvzf kubeseal-0.27.3-linux-amd64.tar.gz
sudo install -m 755 kubeseal /usr/local/bin/kubeseal
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;routeros&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# 일반 Secret을 SealedSecret으로 암호화
kubectl create secret generic db-credentials \
  --namespace production \
  --from-literal=username=admin \
  --from-literal=password='p@ssw0rd' \
  --dry-run=client -o yaml | \
  kubeseal --format yaml &amp;gt; sealed-db-credentials.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;dts&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# sealed-db-credentials.yaml (Git에 안전하게 커밋 가능)
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  name: db-credentials
  namespace: production
spec:
  encryptedData:
    username: AgBy3i4OJSWK+P... (암호화된 값)
    password: AgCtr8sMGh2DJKL... (암호화된 값)
  template:
    metadata:
      name: db-credentials
      namespace: production
    type: Opaque
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;63&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;6.3 키 로테이션&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Sealed Secrets 컨트롤러는 기본적으로 30일마다 새 키를 생성합니다. 이전 키는 삭제되지 않으므로 기존 SealedSecret은 계속 복호화됩니다.&lt;/p&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# 현재 사용 중인 키 확인
kubectl get secrets -n kube-system -l sealedsecrets.bitnami.com/sealed-secrets-key

# 키를 수동으로 로테이션한 후 기존 SealedSecret 재암호화
kubeseal --re-encrypt &amp;lt; sealed-db-credentials.yaml &amp;gt; sealed-db-credentials-new.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;64-trade-off&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;6.4 trade-off&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;장점&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;단점&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;설치/운영이 단순&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;클러스터 간 키가 다르므로 멀티 클러스터에서 관리 복잡&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Git에 안전하게 저장 가능&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Secret 값 변경 시 re-seal 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;외부 인프라 불필요&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;동적 Secret(자동 갱신) 미지원&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;GitOps와 자연스럽게 통합&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;컨트롤러 키 분실 시 모든 Secret 복구 불가&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div style=&quot;padding: 1rem 1.2rem; margin: 1.5rem 0; border-radius: 8px; border-left: 4px solid #ef4444; background: #fef2f2; line-height: 1.7;&quot;&gt;&lt;b&gt;Security Note&lt;/b&gt;&lt;br /&gt;Sealed Secrets 컨트롤러의 개인 키를 안전하게 백업해야 합니다. 이 키를 잃으면 기존 SealedSecret을 복호화할 수 없습니다. 키는 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;kube-system&lt;/code&gt; 네임스페이스의 Secret으로 저장되므로, 클러스터 재생성 시 이 키를 먼저 복원해야 합니다.&lt;/div&gt;
&lt;h2 id=&quot;7-external-secrets-operator&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;7. External Secrets Operator: 외부 저장소 연동&lt;/h2&gt;
&lt;figure style=&quot;text-align: center; margin: 2rem 0;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dna/bBP7Jf/dJMcacwDhwV/AAAAAAAAAAAAAAAAAAAAAI2bnrGK7vvCAy307CP2D2rGu0oVqNEwetfX_fIXIH2E/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1782831599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=IXsljdT3VbPh3Jya029mio83tQ4%3D&quot; alt=&quot;External Secrets Operator 동작 흐름&quot; /&gt;
&lt;figcaption style=&quot;margin-top: 0.5rem; font-size: 0.85rem; color: #64748b;&quot;&gt;External Secrets Operator 동작 흐름&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;External Secrets Operator(ESO)는 AWS Secrets Manager, Azure Key Vault, GCP Secret Manager, HashiCorp Vault 등 외부 Secret 저장소에서 값을 읽어와 Kubernetes Secret으로 동기화합니다.&lt;/p&gt;
&lt;h3 id=&quot;71&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;7.1 동작 원리&lt;/h3&gt;
&lt;ol style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;외부 저장소(예: AWS Secrets Manager)에 Secret을 생성합니다.&lt;/li&gt;
&lt;li&gt;클러스터에 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;SecretStore&lt;/code&gt;(또는 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;ClusterSecretStore&lt;/code&gt;)를 정의하여 외부 저장소 연결 정보를 설정합니다.&lt;/li&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;ExternalSecret&lt;/code&gt; 리소스를 생성하여 &quot;어떤 외부 Secret을 어떤 Kubernetes Secret으로 동기화할지&quot; 정의합니다.&lt;/li&gt;
&lt;li&gt;ESO 컨트롤러가 주기적으로 외부 저장소에서 값을 가져와 Kubernetes Secret을 생성/갱신합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;72&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;7.2 설치 및 설정&lt;/h3&gt;
&lt;pre class=&quot;oxygene&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# External Secrets Operator 설치 (Helm)
helm repo add external-secrets https://charts.external-secrets.io
helm install external-secrets external-secrets/external-secrets \
  --namespace external-secrets \
  --create-namespace
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;73-aws-secrets-manager&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;7.3 AWS Secrets Manager 연동 예시&lt;/h3&gt;
&lt;pre class=&quot;dts&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# SecretStore 정의 &amp;mdash; AWS Secrets Manager 연결
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: aws-secrets-manager
  namespace: production
spec:
  provider:
    aws:
      service: SecretsManager
      region: ap-northeast-2
      auth:
        jwt:
          serviceAccountRef:
            name: external-secrets-sa  # IRSA로 인증
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;yaml&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# ExternalSecret 정의 &amp;mdash; 외부 Secret을 K8s Secret으로 동기화
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: db-credentials
  namespace: production
spec:
  refreshInterval: 1h              # 동기화 주기
  secretStoreRef:
    name: aws-secrets-manager
    kind: SecretStore
  target:
    name: db-credentials           # 생성될 K8s Secret 이름
    creationPolicy: Owner
  data:
    - secretKey: username          # K8s Secret의 key
      remoteRef:
        key: production/db-creds   # AWS Secrets Manager의 Secret 이름
        property: username         # JSON 내 특정 필드
    - secretKey: password
      remoteRef:
        key: production/db-creds
        property: password
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;74-azure-key-vault&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;7.4 Azure Key Vault 연동 예시&lt;/h3&gt;
&lt;pre class=&quot;dts&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: azure-key-vault
  namespace: production
spec:
  provider:
    azurekv:
      vaultUrl: &quot;https://my-vault.vault.azure.net&quot;
      authType: WorkloadIdentity
      serviceAccountRef:
        name: external-secrets-sa
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;75-trade-off&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;7.5 trade-off&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;장점&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;단점&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Secret 원본이 클러스터 외부에 있어 분리됨&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;외부 저장소 장애 시 Secret 갱신 불가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;다양한 Provider 지원 (15+ 종류)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;초기 설정(IRSA, Workload Identity) 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;자동 동기화로 Secret 갱신 자동화&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;동기화 지연(refreshInterval)으로 즉시 반영 안 될 수 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Git에는 참조만 저장 (값 미포함)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;외부 저장소 비용 발생&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;8-hashicorp-vault&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;8. HashiCorp Vault 연동&lt;/h2&gt;
&lt;figure style=&quot;text-align: center; margin: 2rem 0;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dna/d4Qka1/dJMcab5xHuP/AAAAAAAAAAAAAAAAAAAAAL8b1l3bCU7u4Ifyqn3CP7hNHl0Nw9iSxnkZ69cL2yCb/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1782831599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=eevv6%2B3BbqjbfSaPP8zpZS9zgW4%3D&quot; alt=&quot;HashiCorp Vault Kubernetes 연동 구조&quot; /&gt;
&lt;figcaption style=&quot;margin-top: 0.5rem; font-size: 0.85rem; color: #64748b;&quot;&gt;HashiCorp Vault Kubernetes 연동 구조&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;HashiCorp Vault는 동적 Secret 생성, 자동 갱신, 세밀한 접근 정책을 제공하는 Secret 관리 플랫폼입니다. Kubernetes와의 연동 방식은 세 가지가 있습니다.&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;연동 방식&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;설명&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;특징&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Vault Secrets Operator (VSO)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;CRD 기반으로 Vault Secret을 K8s Secret으로 동기화&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;권장 방식, Operator 패턴&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Vault Agent Injector&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Init/Sidecar 컨테이너로 Secret을 Pod에 주입&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;애플리케이션 코드 변경 불필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Secrets Store CSI Driver&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;CSI 볼륨으로 Secret을 파일 시스템에 마운트&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;표준 CSI 인터페이스 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;81-vault-secrets-operator-vso&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;8.1 Vault Secrets Operator (VSO) 방식&lt;/h3&gt;
&lt;pre class=&quot;dts&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# VaultAuth &amp;mdash; Vault 인증 설정
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
  name: vault-auth
  namespace: production
spec:
  method: kubernetes
  mount: kubernetes
  kubernetes:
    role: production-app
    serviceAccount: app-sa
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;routeros&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# VaultStaticSecret &amp;mdash; 정적 Secret 동기화
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
  name: db-credentials
  namespace: production
spec:
  type: kv-v2
  mount: secret
  path: production/db-creds
  destination:
    name: db-credentials       # 생성될 K8s Secret 이름
    create: true
  refreshAfter: 30s            # 갱신 주기
  vaultAuthRef: vault-auth
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;yaml&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# VaultDynamicSecret &amp;mdash; 동적 Secret (TTL 기반 자동 갱신)
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultDynamicSecret
metadata:
  name: db-dynamic-creds
  namespace: production
spec:
  mount: database
  path: creds/production-readonly
  destination:
    name: db-dynamic-credentials
    create: true
  renewalPercent: 67           # TTL의 67% 지점에서 갱신
  vaultAuthRef: vault-auth
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;82-vault-agent-injector&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;8.2 Vault Agent Injector 방식&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Pod에 annotation을 추가하면 Vault Agent가 sidecar로 주입되어 Secret을 파일로 제공합니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  namespace: production
spec:
  template:
    metadata:
      annotations:
        vault.hashicorp.com/agent-inject: &quot;true&quot;
        vault.hashicorp.com/role: &quot;production-app&quot;
        vault.hashicorp.com/agent-inject-secret-db-creds: &quot;secret/data/production/db-creds&quot;
        vault.hashicorp.com/agent-inject-template-db-creds: |
          {{- with secret &quot;secret/data/production/db-creds&quot; -}}
          export DB_USER=&quot;{{ .Data.data.username }}&quot;
          export DB_PASS=&quot;{{ .Data.data.password }}&quot;
          {{- end }}
    spec:
      serviceAccountName: app-sa
      containers:
        - name: app
          image: my-app:1.0
          # Secret은 /vault/secrets/db-creds 파일로 마운트됨
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;83-trade-off&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;8.3 trade-off&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;장점&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;단점&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;동적 Secret으로 자동 로테이션 가능&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Vault 자체의 운영 부담 (HA, unseal, 백업)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;세밀한 접근 정책 (Policy, AppRole)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;러닝 커브가 높음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;감사 로그 내장&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;추가 인프라 비용 (Vault 클러스터)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;다양한 Secret Engine (DB, PKI, AWS 등)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;단일 장애점(SPOF) 위험 (HA 필수)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div style=&quot;padding: 1rem 1.2rem; margin: 1.5rem 0; border-radius: 8px; border-left: 4px solid #3b82f6; background: #eff6ff; line-height: 1.7;&quot;&gt;&lt;b&gt;Tip&lt;/b&gt;&lt;br /&gt;Vault를 자체 운영하기 어려운 경우, HCP Vault(HashiCorp Cloud Platform)나 클라우드 네이티브 Secret 관리 서비스(AWS Secrets Manager, Azure Key Vault)를 External Secrets Operator로 연동하는 방식이 운영 부담을 줄일 수 있습니다.&lt;/div&gt;
&lt;h2 id=&quot;9&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;9. 실무 시나리오: 스타트업 프로덕션 환경 설계&lt;/h2&gt;
&lt;h3 id=&quot;_3&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;시나리오&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;팀원 10명의 스타트업에서 EKS 클러스터를 운영합니다. 데이터베이스 비밀번호, 외부 API 키, TLS 인증서를 관리해야 합니다. GitOps(Argo CD)를 사용 중이며, Secret을 Git에 저장하면 안 됩니다.&lt;/p&gt;
&lt;h3 id=&quot;_4&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;요구사항&lt;/h3&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Secret이 Git 리포지토리에 노출되면 안 됨&lt;/li&gt;
&lt;li&gt;Secret 변경 시 Pod 재시작 없이 반영되면 좋음&lt;/li&gt;
&lt;li&gt;운영 부담이 적어야 함 (Vault 자체 운영은 부담)&lt;/li&gt;
&lt;li&gt;AWS 서비스를 이미 사용 중&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;_5&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;설계 결정&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;결정&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;이유&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;대안&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;External Secrets Operator 채택&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;AWS Secrets Manager와 직접 연동, Vault 운영 불필요&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Sealed Secrets (동기화/갱신 미지원)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;IRSA로 인증&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;IAM Role을 ServiceAccount에 직접 연결, 키 관리 불필요&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Access Key 사용 (보안 위험)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;refreshInterval: 1h 설정&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Secret 변경 시 1시간 내 자동 반영&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;수동 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;kubectl rollout restart&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;EKS Secret 암호화 활성화&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;KMS로 etcd 암호화, 추가 비용 최소&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;기본 설정 유지 (보안 위험)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;_6&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;구현&lt;/h3&gt;
&lt;pre class=&quot;dts&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# 1. IRSA용 ServiceAccount
apiVersion: v1
kind: ServiceAccount
metadata:
  name: external-secrets-sa
  namespace: production
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/ExternalSecretsRole
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;dts&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# 2. ClusterSecretStore (모든 네임스페이스에서 재사용)
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: aws-secrets-manager
spec:
  provider:
    aws:
      service: SecretsManager
      region: ap-northeast-2
      auth:
        jwt:
          serviceAccountRef:
            name: external-secrets-sa
            namespace: production
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;dts&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# 3. ExternalSecret (Git에 이것만 커밋)
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: app-secrets
  namespace: production
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: aws-secrets-manager
    kind: ClusterSecretStore
  target:
    name: app-secrets
  data:
    - secretKey: DB_PASSWORD
      remoteRef:
        key: production/app/db
        property: password
    - secretKey: API_KEY
      remoteRef:
        key: production/app/external-api
        property: key
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;이 구성에서 Git에 저장되는 것은 ExternalSecret YAML뿐입니다. 실제 Secret 값은 AWS Secrets Manager에만 존재하고, ESO가 클러스터에 동기화합니다.&lt;/p&gt;
&lt;h2 id=&quot;10-pod-secret&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;10. Pod에서 Secret을 안전하게 사용하기&lt;/h2&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Secret을 Pod에 주입하는 방법은 두 가지입니다: 환경 변수(env)와 볼륨 마운트(volume). 보안 관점에서 볼륨 마운트가 권장됩니다.&lt;/p&gt;
&lt;h3 id=&quot;101-vs&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;10.1 환경 변수 vs 볼륨 마운트&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;방식&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;장점&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;단점&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;환경 변수 (env)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;코드에서 바로 접근 가능&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;kubectl describe pod&lt;/code&gt;로 노출, 프로세스 목록에서 보일 수 있음, Secret 변경 시 Pod 재시작 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;볼륨 마운트 (volume)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;파일 시스템 권한으로 보호, 자동 갱신 가능&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;파일 읽기 로직 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;pre class=&quot;yaml&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# 권장: 볼륨 마운트 방식
apiVersion: v1
kind: Pod
metadata:
  name: my-app
spec:
  containers:
    - name: app
      image: my-app:1.0
      volumeMounts:
        - name: secrets
          mountPath: /etc/secrets
          readOnly: true
  volumes:
    - name: secrets
      secret:
        secretName: db-credentials
        defaultMode: 0400    # 소유자만 읽기 가능
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;102-secret&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;10.2 불필요한 Secret 마운트 방지&lt;/h3&gt;
&lt;pre class=&quot;yaml&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;# API 접근이 불필요한 Pod에서 ServiceAccount 토큰 마운트 비활성화
apiVersion: v1
kind: Pod
metadata:
  name: simple-worker
spec:
  automountServiceAccountToken: false
  containers:
    - name: worker
      image: worker:1.0
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;11&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;11. 보안 체크리스트&lt;/h2&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;운영 환경에서 Kubernetes Secret을 관리할 때 확인해야 하는 항목입니다.&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;#&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;항목&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;확인 방법&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;etcd 암호화가 활성화되어 있는가&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;kubectl get secrets -o json&lt;/code&gt;의 annotation 확인, 매니지드의 경우 콘솔 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Secret YAML이 Git에 커밋되어 있지 않은가&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;git log --all -p -- '*.yaml' | grep 'kind: Secret'&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Secret 접근 RBAC가 최소 권한인가&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;kubectl auth can-i get secrets --as=&amp;lt;user&amp;gt; -n &amp;lt;ns&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;감사 로그가 Secret 접근을 기록하는가&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Audit Policy에 secrets 리소스 포함 여부 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;5&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;환경 변수 대신 볼륨 마운트를 사용하는가&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Deployment YAML의 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;envFrom&lt;/code&gt; vs &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;volumeMounts&lt;/code&gt; 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;6&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;불필요한 ServiceAccount 토큰이 마운트되지 않는가&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;automountServiceAccountToken: false&lt;/code&gt; 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;7&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Secret 로테이션 전략이 있는가&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;외부 Secret 관리 도구의 refreshInterval 또는 수동 절차 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;8&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;컨트롤러 키(Sealed Secrets) 또는 Vault unseal 키를 백업했는가&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;백업 절차 및 복구 테스트 여부 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;12&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;12. 비용/운영 고려사항&lt;/h2&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;항목&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;비용&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;운영 부담&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;EKS Secret 암호화 (KMS)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;KMS API 호출당 $0.03/10,000건&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;거의 없음 (한 번 설정)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;AWS Secrets Manager&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Secret당 $0.40/월 + API 호출 비용&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Secret 생성/갱신 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Azure Key Vault&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;표준 티어 기준 $0.03/10,000 작업&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Secret 생성/갱신 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;HashiCorp Vault (자체 운영)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;인프라 비용 (EC2/EKS)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;HA 구성, unseal, 백업, 업그레이드 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;HCP Vault&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;월 $0.03/시간~ (인스턴스 기준)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;관리형으로 운영 부담 낮음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Sealed Secrets&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;무료 (오픈소스)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;키 백업, re-seal 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;External Secrets Operator&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;무료 (오픈소스) + 외부 저장소 비용&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Provider 인증 설정, 동기화 모니터링&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div style=&quot;padding: 1rem 1.2rem; margin: 1.5rem 0; border-radius: 8px; border-left: 4px solid #3b82f6; background: #eff6ff; line-height: 1.7;&quot;&gt;&lt;b&gt;Tip&lt;/b&gt;&lt;br /&gt;소규모 팀에서 시작한다면 Sealed Secrets로 충분할 수 있습니다. Secret 수가 늘어나고 자동 갱신이 필요해지면 External Secrets Operator로 전환하는 단계적 접근이 실용적입니다. Vault는 동적 Secret, PKI 인증서, 데이터베이스 자격증명 자동 로테이션 등 고급 기능이 필요한 경우에 도입을 검토합니다.&lt;/div&gt;
&lt;h2 id=&quot;13_1&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;13. 정리&lt;/h2&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Kubernetes Secret은 Base64 인코딩만 적용되며, 기본 설정에서는 etcd에 평문으로 저장됩니다. 단독으로는 보안 수준이 낮습니다.&lt;/li&gt;
&lt;li&gt;저장 시 암호화(EncryptionConfiguration 또는 클라우드 KMS)를 반드시 활성화하여 etcd 수준에서 보호해야 합니다.&lt;/li&gt;
&lt;li&gt;RBAC로 Secret 접근을 최소화하고, 감사 로그로 접근 이력을 추적합니다.&lt;/li&gt;
&lt;li&gt;Git에 Secret을 저장하지 않기 위해 외부 Secret 관리 도구를 사용합니다. 팀 규모와 요구사항에 따라 Sealed Secrets, External Secrets Operator, HashiCorp Vault 중 선택합니다.&lt;/li&gt;
&lt;li&gt;Pod에서는 환경 변수보다 볼륨 마운트 방식이 보안상 안전합니다. 불필요한 토큰 마운트는 비활성화합니다.&lt;/li&gt;
&lt;li&gt;Secret 보안은 단일 도구가 아닌 여러 계층(암호화, 접근 제어, 외부 관리, 감사)의 조합으로 달성됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;_7&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;참고 문서&lt;/h2&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://kubernetes.io/docs/concepts/configuration/secret/&quot;&gt;Kubernetes 공식 문서 &amp;mdash; Secrets&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/&quot;&gt;Kubernetes 공식 문서 &amp;mdash; Encrypting Confidential Data at Rest&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://external-secrets.io/&quot;&gt;External Secrets Operator 공식 문서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/bitnami-labs/sealed-secrets&quot;&gt;Sealed Secrets GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.hashicorp.com/vault/docs/deploy/kubernetes&quot;&gt;HashiCorp Vault &amp;mdash; Kubernetes Integration&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Kubernetes</category>
      <category>etcd encryption</category>
      <category>External Secrets</category>
      <category>kubernetes</category>
      <category>Sealed Secrets</category>
      <category>secret</category>
      <category>secret management</category>
      <category>security</category>
      <category>Vault</category>
      <author>wero90</author>
      <guid isPermaLink="true">https://wero90.tistory.com/71</guid>
      <comments>https://wero90.tistory.com/71#entry71comment</comments>
      <pubDate>Mon, 8 Jun 2026 09:58:14 +0900</pubDate>
    </item>
    <item>
      <title>GitHub Actions와 Jenkins 차이: CI/CD 도구 선택 기준</title>
      <link>https://wero90.tistory.com/70</link>
      <description>&lt;blockquote style=&quot;border-left: 4px solid #3b82f6; padding: 1rem 1.2rem; margin: 1.5rem 0; background: #f0f9ff; color: #1e40af; font-size: 1.05rem; line-height: 1.7; border-radius: 0 8px 8px 0;&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;CI/CD 도구를 선택할 때 &quot;Jenkins가 레거시니까 GitHub Actions로 가자&quot;라는 판단은 위험합니다. 팀 규모, 빌드 볼륨, 보안 요구사항, 운영 역량에 따라 적합한 도구가 다릅니다. 이 글에서는 두 도구의 구조적 차이를 분석하고, 상황별 선택 기준을 정리합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;_1&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;핵심 요약&lt;/h2&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GitHub Actions는 GitHub과 통합된 SaaS CI/CD 서비스로, 별도 인프라 운영 없이 시작할 수 있습니다.&lt;/li&gt;
&lt;li&gt;Jenkins는 자체 호스팅 오픈소스 CI/CD 서버로, 높은 유연성과 커스터마이징이 가능하지만 운영 부담이 있습니다.&lt;/li&gt;
&lt;li&gt;소규모 팀이 GitHub을 사용하고 있다면 GitHub Actions가 대부분 적합합니다.&lt;/li&gt;
&lt;li&gt;빌드 볼륨이 크거나 네트워크 격리 요구사항이 있다면 Jenkins가 비용/보안 측면에서 유리할 수 있습니다.&lt;/li&gt;
&lt;li&gt;두 도구를 조합해서 사용하는 패턴(GitHub Actions &amp;rarr; Jenkins 배포)도 실무에서 흔합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;1&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;1. 왜 이 비교가 필요한가&lt;/h2&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;스타트업에서 3명이 개발하다가 팀이 15명으로 성장했습니다. 기존에 GitHub Actions로 잘 돌아가던 파이프라인이 이제 문제를 보입니다.&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;월 CI/CD 비용이 $300을 넘어가기 시작합니다.&lt;/li&gt;
&lt;li&gt;iOS + Android + Backend 빌드를 동시에 돌리면 무료 병렬 제한에 걸립니다.&lt;/li&gt;
&lt;li&gt;보안 팀이 &quot;빌드 환경에서 소스코드가 외부로 나가면 안 된다&quot;고 요구합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;이때 Jenkins로의 마이그레이션을 검토하게 됩니다. 반대로, Jenkins를 5년간 운영하던 팀이 &quot;운영 부담이 너무 크다&quot;며 GitHub Actions로 전환을 검토하기도 합니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;어느 쪽이 더 좋은 도구가 아니라, 어떤 상황에서 어떤 도구가 적합한지를 이해하는 것이 핵심입니다.&lt;/p&gt;
&lt;h2 id=&quot;2&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;2. 아키텍처 비교&lt;/h2&gt;
&lt;figure style=&quot;text-align: center; margin: 2rem 0;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dna/dig28I/dJMcadoGnQQ/AAAAAAAAAAAAAAAAAAAAADHwOyskRHc5EjJfMeSqbZnf9X5XB_NkbdBRNjRF3fM3/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1782831599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=NmQ2rtRkGyTSi1KB7%2BMsL%2BLwTGE%3D&quot; alt=&quot;GitHub Actions와 Jenkins 아키텍처 비교&quot; /&gt;
&lt;figcaption style=&quot;margin-top: 0.5rem; font-size: 0.85rem; color: #64748b;&quot;&gt;GitHub Actions와 Jenkins 아키텍처 비교&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;github-actions&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;GitHub Actions 아키텍처&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;GitHub Actions는 SaaS 기반입니다. 사용자는 워크플로우 YAML 파일만 작성하면, GitHub이 Runner를 할당하고 실행합니다.&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;구성 요소&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Workflow 파일&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;.github/workflows/&lt;/code&gt;에 YAML로 정의&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;GitHub-hosted Runner&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;GitHub이 관리하는 일회성 VM (Ubuntu, Windows, macOS)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Self-hosted Runner&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;사용자가 직접 운영하는 실행 환경&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Marketplace Actions&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;커뮤니티가 만든 재사용 가능한 액션&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;핵심 특징:&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Ephemeral&lt;/b&gt;: 매 실행마다 깨끗한 VM이 할당되고, 실행 후 삭제됩니다. 빌드 간 오염이 없습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;관리 부담 최소&lt;/b&gt;: Runner 운영, 보안 패치, 스케일링을 GitHub이 처리합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;GitHub 종속&lt;/b&gt;: GitHub 저장소 외에서는 사용할 수 없습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;jenkins&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;Jenkins 아키텍처&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;Jenkins는 Self-hosted 오픈소스입니다. 사용자가 직접 Jenkins Controller와 Agent를 운영합니다.&lt;/p&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;구성 요소&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Controller&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;작업 스케줄링, UI, 설정 관리 (Master)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Agent (Node)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;실제 빌드를 실행하는 워커&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Plugin&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;1,800개 이상의 확장 플러그인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Jenkinsfile&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Pipeline을 코드로 정의 (Groovy DSL)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;핵심 특징:&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;완전한 통제&lt;/b&gt;: 네트워크, 스토리지, 보안 정책을 직접 설계합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;높은 유연성&lt;/b&gt;: 어떤 SCM, 어떤 클라우드, 어떤 배포 대상이든 플러그인으로 연동 가능합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;운영 부담&lt;/b&gt;: Controller 가용성, Agent 스케일링, 플러그인 업데이트, 보안 패치를 직접 해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;_2&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;구조적 차이 요약&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;비교 항목&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;GitHub Actions&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;Jenkins&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;호스팅&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;SaaS (GitHub 관리)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Self-hosted (직접 운영)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;실행 환경&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Ephemeral VM (매번 새로 생성)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Persistent Agent (상시 실행)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;설정 방식&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;YAML&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Groovy DSL (Jenkinsfile)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;확장성&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Marketplace Actions&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Plugins (1,800+)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;SCM 연동&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;GitHub 전용&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Git, SVN, Mercurial 등 다양&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;관리 대상&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Workflow 파일만&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Controller + Agent + Plugin + 인프라&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;3&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;3. 워크플로우 비교&lt;/h2&gt;
&lt;figure style=&quot;text-align: center; margin: 2rem 0;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dna/cAXpus/dJMcacDqn8C/AAAAAAAAAAAAAAAAAAAAAFws2jjf0kwtfUfUQ_KtRnH7shzzXyuJZdRpUukVrCB_/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1782831599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=e1DOxTeKbOV4duuiwsTGW0u0XJ4%3D&quot; alt=&quot;GitHub Actions와 Jenkins 워크플로우 비교&quot; /&gt;
&lt;figcaption style=&quot;margin-top: 0.5rem; font-size: 0.85rem; color: #64748b;&quot;&gt;GitHub Actions와 Jenkins 워크플로우 비교&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;같은 작업(빌드 &amp;rarr; 테스트 &amp;rarr; 배포)을 두 도구에서 어떻게 정의하는지 비교합니다.&lt;/p&gt;
&lt;h3 id=&quot;github-actions_1&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;GitHub Actions&lt;/h3&gt;
&lt;pre class=&quot;less&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;name: CI/CD Pipeline
on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci
      - run: npm run build

  test:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm test

  deploy:
    needs: test
    runs-on: ubuntu-latest
    environment: production
    steps:
      - name: Deploy to ECS
        run: aws ecs update-service --cluster prod --service api --force-new-deployment
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;특징:&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;on&lt;/code&gt; 블록으로 트리거를 선언적으로 정의합니다.&lt;/li&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;needs&lt;/code&gt;로 Job 간 의존성을 설정합니다.&lt;/li&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;environment&lt;/code&gt;로 배포 보호 규칙(수동 승인)을 적용합니다.&lt;/li&gt;
&lt;li&gt;Marketplace Action을 &lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;uses&lt;/code&gt;로 간단히 가져다 씁니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;jenkins-declarative-pipeline&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;Jenkins (Declarative Pipeline)&lt;/h3&gt;
&lt;pre class=&quot;puppet&quot; style=&quot;background: #1e293b; color: #e2e8f0; padding: 1.2rem; border-radius: 8px; overflow-x: auto; font-size: 0.9rem; font-family: Consolas,Monaco,monospace; line-height: 1.6; margin: 1.5rem 0;&quot;&gt;&lt;code&gt;pipeline {
    agent any

    stages {
        stage('Build') {
            steps {
                sh 'npm ci'
                sh 'npm run build'
            }
        }

        stage('Test') {
            steps {
                sh 'npm test'
            }
        }

        stage('Deploy') {
            when {
                branch 'main'
            }
            input {
                message &quot;프로덕션에 배포하시겠습니까?&quot;
                ok &quot;Deploy&quot;
            }
            steps {
                sh 'aws ecs update-service --cluster prod --service api --force-new-deployment'
            }
        }
    }

    post {
        failure {
            slackSend channel: '#alerts', message: &quot;빌드 실패: ${env.JOB_NAME}&quot;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;특징:&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Groovy DSL로 작성합니다. 프로그래밍 언어이므로 조건부 로직, 반복, 함수 정의가 자유롭습니다.&lt;/li&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;input&lt;/code&gt; 블록으로 수동 승인을 구현합니다.&lt;/li&gt;
&lt;li&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;post&lt;/code&gt; 블록으로 성공/실패 후 처리를 정의합니다.&lt;/li&gt;
&lt;li&gt;커스텀 로직이 복잡해질수록 Jenkins가 유리합니다. 다만 Groovy 학습 곡선이 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;_3&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;워크플로우 정의 방식 비교&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;항목&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;GitHub Actions&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;Jenkins&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;언어&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;YAML (선언적)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Groovy DSL (프로그래밍 가능)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;학습 곡선&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;낮음&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;중간~높음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;조건부 로직&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;if&lt;/code&gt; 표현식 (제한적)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Groovy 문법으로 자유롭게&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;재사용&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Reusable Workflow, Composite Action&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Shared Library&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;트리거&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;on&lt;/code&gt; 블록 (push, PR, schedule 등)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Webhook, Poll SCM, Cron, Upstream Job&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;수동 승인&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Environment Protection Rules&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;input&lt;/code&gt; 블록&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;병렬 실행&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;matrix&lt;/code&gt; 전략&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;code style=&quot;background: #f1f5f9; color: #d6336c; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; font-family: Consolas,Monaco,monospace;&quot;&gt;parallel&lt;/code&gt; 블록&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div style=&quot;padding: 1rem 1.2rem; margin: 1.5rem 0; border-radius: 8px; border-left: 4px solid #3b82f6; background: #eff6ff; line-height: 1.7;&quot;&gt;&lt;b&gt;Tip&lt;/b&gt;&lt;br /&gt;GitHub Actions의 YAML은 단순한 파이프라인에서 빠르게 작성할 수 있지만, 조건 분기가 복잡해지면 가독성이 떨어집니다. Jenkins의 Groovy는 반대로 초기 학습이 필요하지만, 복잡한 로직을 깔끔하게 표현할 수 있습니다. 파이프라인 복잡도가 판단 기준입니다.&lt;/div&gt;
&lt;h2 id=&quot;4&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;4. 비용 비교&lt;/h2&gt;
&lt;figure style=&quot;text-align: center; margin: 2rem 0;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dna/CvKwl/dJMcadoGnQV/AAAAAAAAAAAAAAAAAAAAAB5Cj4vFX0Jx4dW0OiSWtnAHTnCT1P7i7P_17PzdBrQK/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1782831599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=SJl1oP9%2BBLyKXAo0%2F%2BD1XSxF8y8%3D&quot; alt=&quot;GitHub Actions와 Jenkins 비용 구조 비교&quot; /&gt;
&lt;figcaption style=&quot;margin-top: 0.5rem; font-size: 0.85rem; color: #64748b;&quot;&gt;GitHub Actions와 Jenkins 비용 구조 비교&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;비용 구조가 근본적으로 다릅니다. GitHub Actions는 사용량 기반, Jenkins는 인프라 고정 비용입니다.&lt;/p&gt;
&lt;h3 id=&quot;github-actions_2&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;GitHub Actions 비용&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;항목&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;가격 (2026년 기준)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Public 저장소&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;무제한 무료&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Private 저장소 (Free 플랜)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;2,000분/월 무료&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Private 저장소 (Team 플랜)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;3,000분/월 무료&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;추가 사용량 (Linux)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;$0.006/분&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;추가 사용량 (Windows)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;$0.010/분&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;추가 사용량 (macOS)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;$0.048/분&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Self-hosted Runner&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;무료 (인프라 비용만)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;2026년 1월 가격 인하가 적용된 기준입니다. 최신 가격은 &lt;a href=&quot;https://docs.github.com/en/billing/managing-billing-for-your-products/about-billing-for-github-actions&quot;&gt;GitHub Actions 가격 페이지&lt;/a&gt;에서 확인할 수 있습니다.&lt;/p&gt;
&lt;h3 id=&quot;jenkins_1&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;Jenkins 비용&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;항목&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;예상 비용&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;소프트웨어 라이선스&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;무료 (오픈소스)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Controller 서버&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;$50~200/월 (EC2 t3.large 기준)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Agent 서버 (2~4대)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;$100~400/월&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;스토리지 (빌드 아티팩트)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;$20~50/월&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;운영 인력&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;DevOps 엔지니어 인건비의 일부&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;총 인프라 비용&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;$180~680/월 (규모에 따라)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;_4&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;비용 시나리오 비교&lt;/h3&gt;
&lt;h4 id=&quot;a-5-5-10&quot; data-ke-size=&quot;size20&quot;&gt;시나리오 A: 소규모 팀 (5명, 일 5회 빌드, 빌드당 10분)&lt;/h4&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;GitHub Actions&lt;/b&gt;: 월 약 1,500분 &amp;rarr; Free 플랜(2,000분)으로 충분 &amp;rarr; &lt;b&gt;$0&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Jenkins&lt;/b&gt;: Controller + Agent 최소 구성 &amp;rarr; &lt;b&gt;$180+/월&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;b-20-30-15&quot; data-ke-size=&quot;size20&quot;&gt;시나리오 B: 중규모 팀 (20명, 일 30회 빌드, 빌드당 15분)&lt;/h4&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;GitHub Actions&lt;/b&gt;: 월 약 9,000분 &amp;rarr; Team 플랜(3,000분) + 초과 6,000분 &amp;times; $0.006 &amp;rarr; &lt;b&gt;약 $36/월&lt;/b&gt; + Team 플랜 비용&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Jenkins&lt;/b&gt;: 고성능 Controller + Agent 3대 &amp;rarr; &lt;b&gt;약 $400/월&lt;/b&gt; (고정)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;c-50-100-20&quot; data-ke-size=&quot;size20&quot;&gt;시나리오 C: 대규모 팀 (50명, 일 100회 빌드, 빌드당 20분)&lt;/h4&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;GitHub Actions&lt;/b&gt;: 월 약 40,000분 &amp;rarr; 초과 37,000분 &amp;times; $0.006 &amp;rarr; &lt;b&gt;약 $222/월&lt;/b&gt; (Linux 기준)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Jenkins&lt;/b&gt;: Agent 6대 + 고가용성 구성 &amp;rarr; &lt;b&gt;약 $680/월&lt;/b&gt; (고정)&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;다만 시나리오 C에서 macOS 빌드가 포함되면 GitHub Actions 비용이 증가합니다. macOS 빌드가 월 5,000분이면 추가 $240이 발생합니다.&lt;/p&gt;
&lt;div style=&quot;padding: 1rem 1.2rem; margin: 1.5rem 0; border-radius: 8px; border-left: 4px solid #f59e0b; background: #fffbeb; line-height: 1.7;&quot;&gt;&lt;b&gt;비용 판단 기준&lt;/b&gt;&lt;br /&gt;단순히 금액만 비교하면 안 됩니다. Jenkins의 &quot;운영 인력 비용&quot;은 표에 포함되지 않습니다. DevOps 엔지니어가 Jenkins 관리에 주당 4시간을 쓴다면, 그 인건비를 고려해야 합니다. 반대로 GitHub Actions는 Self-hosted Runner를 사용하면 분당 비용을 제거할 수 있지만, Runner 관리 부담이 발생합니다.&lt;/div&gt;
&lt;h2 id=&quot;5&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;5. 운영 부담 비교&lt;/h2&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;운영 영역&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;GitHub Actions&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;Jenkins&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;초기 설정&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;YAML 파일 작성 (30분~)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;서버 프로비저닝 + Jenkins 설치 + 플러그인 설정 (반나절~)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;보안 패치&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;GitHub이 Runner에 자동 적용&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;직접 Jenkins + OS + 플러그인 업데이트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;스케일링&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;자동 (GitHub이 Runner 할당)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Agent 수동 추가 또는 Auto Scaling 구성 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;가용성&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;GitHub SLA (99.9%)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;직접 이중화, 백업, 모니터링 구성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;플러그인 관리&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Marketplace (버전 고정)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;호환성 충돌 직접 관리 (가장 큰 운영 부담)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;디버깅&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;GitHub UI에서 로그 확인&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Controller/Agent 로그 직접 분석&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;백업/복구&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;불필요 (Workflow는 Git에 저장)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Jenkins Home 디렉토리 정기 백업 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;jenkins_2&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;Jenkins 운영에서 자주 발생하는 문제&lt;/h3&gt;
&lt;ol style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;플러그인 충돌&lt;/b&gt;: A 플러그인을 업데이트하면 B 플러그인이 깨집니다. 의존성 관계가 복잡해질수록 업데이트가 두려워집니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Controller 다운&lt;/b&gt;: Jenkins Controller가 죽으면 모든 빌드가 중단됩니다. HA 구성이 없으면 단일 장애점입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Agent 디스크 부족&lt;/b&gt;: 빌드 아티팩트와 Docker 이미지가 누적되어 디스크가 가득 찹니다. 정기 정리 크론잡이 필요합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Groovy 보안 이슈&lt;/b&gt;: Pipeline에서 실행되는 Groovy 코드는 Controller에서 실행될 수 있어 Script Security 설정이 필요합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;github-actions_3&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;GitHub Actions 운영에서 주의할 점&lt;/h3&gt;
&lt;ol style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;비용 예측 어려움&lt;/b&gt;: 빌드 횟수와 시간에 따라 비용이 변동합니다. 갑자기 PR이 많아지면 예상 외 비용이 발생합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;GitHub 장애 영향&lt;/b&gt;: GitHub에 장애가 나면 CI/CD도 멈춥니다. 2023-2024년에도 수 차례 장애가 있었습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Workflow 복잡도 한계&lt;/b&gt;: 200줄 이상의 Workflow는 가독성이 급격히 떨어집니다. Reusable Workflow로 분리해야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Self-hosted Runner 보안&lt;/b&gt;: PR 이벤트에서 Self-hosted Runner를 사용하면 외부 기여자의 악성 코드가 내부 네트워크에서 실행될 수 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;6&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;6. 보안 비교&lt;/h2&gt;
&lt;div style=&quot;padding: 1rem 1.2rem; margin: 1.5rem 0; border-radius: 8px; border-left: 4px solid #ef4444; background: #fef2f2; line-height: 1.7;&quot;&gt;&lt;b&gt;Security Note&lt;/b&gt;&lt;br /&gt;CI/CD 도구는 소스코드, 인프라 자격 증명, 프로덕션 배포 권한에 접근합니다. 도구의 보안 모델을 이해하지 않으면 공급망 공격의 진입점이 될 수 있습니다.&lt;/div&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;보안 항목&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;GitHub Actions&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;Jenkins&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Secret 저장&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;GitHub Secrets (암호화)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Credentials Plugin (Jenkins 내부 암호화)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;네트워크 격리&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;GitHub-hosted Runner는 공용 네트워크&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;완전한 네트워크 통제 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;코드 실행 환경&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Ephemeral (매번 삭제) &amp;rarr; 잔존 데이터 없음&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Persistent Agent &amp;rarr; 빌드 간 데이터 잔존 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;공급망 보안&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Action SHA 고정으로 완화&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Plugin 서명 검증 가능 (제한적)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;감사 로그&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;GitHub Audit Log (Enterprise)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;별도 구성 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;접근 제어&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Repository/Organization 권한 기반&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Jenkins 자체 RBAC + LDAP/SAML 연동&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;_5&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;보안 관점 선택 기준&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GitHub Actions가 적합한 경우:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;오픈소스 프로젝트나 코드가 공개되어도 되는 서비스&lt;/li&gt;
&lt;li&gt;빌드 환경의 격리(Ephemeral VM)가 중요한 경우&lt;/li&gt;
&lt;li&gt;Secret 관리를 단순화하고 싶은 경우 (GitHub Secrets + OIDC)&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Jenkins가 적합한 경우:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;소스코드가 외부 서버로 나가면 안 되는 규제 환경 (금융, 의료, 공공)&lt;/li&gt;
&lt;li&gt;빌드 네트워크를 VPC 내부로 완전히 격리해야 하는 경우&lt;/li&gt;
&lt;li&gt;사내 인증 시스템(LDAP, AD)과 통합해야 하는 경우&lt;/li&gt;
&lt;li&gt;세밀한 권한 분리가 필요한 대규모 조직&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;7&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;7. 실무 선택 시나리오&lt;/h2&gt;
&lt;h3 id=&quot;1-github-actions&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;시나리오 1: GitHub Actions 선택이 적합한 경우&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;상황&lt;/b&gt;: SaaS 서비스를 개발하는 8명 팀. GitHub을 사용하고, AWS에 배포합니다. 별도 DevOps 엔지니어 없이 백엔드 개발자가 CI/CD를 관리합니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;선택 근거:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;별도 인프라 운영 인력이 없으므로 SaaS가 적합합니다.&lt;/li&gt;
&lt;li&gt;GitHub에 이미 코드가 있으므로 통합이 자연스럽습니다.&lt;/li&gt;
&lt;li&gt;월 빌드량이 Free/Team 플랜으로 커버됩니다.&lt;/li&gt;
&lt;li&gt;OIDC로 AWS 인증을 구성하면 장기 자격 증명이 불필요합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주의할 점:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;macOS 빌드가 필요하면 Self-hosted Runner를 검토합니다.&lt;/li&gt;
&lt;li&gt;팀이 20명 이상으로 성장하면 비용 구조를 재검토합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;2-jenkins&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;시나리오 2: Jenkins 선택이 적합한 경우&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;상황&lt;/b&gt;: 금융 서비스를 운영하는 40명 팀. BitBucket을 사용하고, 온프레미스 + AWS 하이브리드 환경입니다. 전담 DevOps 팀(3명)이 있습니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;선택 근거:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;BitBucket을 사용하므로 GitHub Actions를 쓸 수 없습니다.&lt;/li&gt;
&lt;li&gt;소스코드가 내부 네트워크 밖으로 나가면 안 되는 규제가 있습니다.&lt;/li&gt;
&lt;li&gt;온프레미스 + 클라우드 하이브리드 배포가 필요합니다.&lt;/li&gt;
&lt;li&gt;전담 DevOps 팀이 Jenkins를 운영할 역량이 있습니다.&lt;/li&gt;
&lt;li&gt;빌드 볼륨이 높아 고정 비용 모델이 유리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주의할 점:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Jenkins Controller HA 구성을 반드시 설계합니다.&lt;/li&gt;
&lt;li&gt;플러그인 업데이트 정책(월 1회 등)을 정합니다.&lt;/li&gt;
&lt;li&gt;Configuration as Code(JCasC) 플러그인으로 설정을 코드화합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;3_1&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;시나리오 3: 하이브리드 사용&lt;/h3&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;상황&lt;/b&gt;: 15명 팀이 GitHub을 사용하지만, 특정 빌드는 내부 GPU 서버에서 실행해야 합니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;구성:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CI(빌드, 테스트, 보안 스캔): GitHub Actions (GitHub-hosted Runner)&lt;/li&gt;
&lt;li&gt;ML 모델 학습/빌드: Jenkins (내부 GPU Agent)&lt;/li&gt;
&lt;li&gt;배포: GitHub Actions (Self-hosted Runner를 VPC 내부에 배치)&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 강점을 조합하는 패턴도 실무에서 흔합니다. GitHub Actions에서 Jenkins 빌드를 트리거하거나, 반대로 Jenkins에서 GitHub API를 호출하는 방식으로 연동합니다.&lt;/p&gt;
&lt;h2 id=&quot;8&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;8. 마이그레이션 고려사항&lt;/h2&gt;
&lt;h3 id=&quot;jenkins-github-actions&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;Jenkins &amp;rarr; GitHub Actions 전환 시&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;고려사항&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Shared Library&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Reusable Workflow 또는 Composite Action으로 재작성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Jenkinsfile 변환&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;YAML로 수동 전환 (자동 변환 도구는 제한적)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;플러그인 대체&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Marketplace에서 대응하는 Action 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Secret 마이그레이션&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;GitHub Secrets 또는 외부 Secret Manager로 이전&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;점진적 전환&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;신규 프로젝트부터 GitHub Actions 적용, 기존은 유지&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;github-actions-jenkins&quot; style=&quot;font-size: 1.2rem; margin-top: 2rem; margin-bottom: 0.75rem; color: #334155;&quot; data-ke-size=&quot;size23&quot;&gt;GitHub Actions &amp;rarr; Jenkins 전환 시&lt;/h3&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;고려사항&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;인프라 준비&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Controller + Agent 서버 프로비저닝&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;운영 체계&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;모니터링, 백업, 보안 패치 프로세스 수립&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;YAML &amp;rarr; Groovy&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Declarative Pipeline 구문으로 변환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;트리거 설정&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;GitHub Webhook 또는 Generic Webhook Trigger 플러그인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;팀 교육&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Groovy 기본 문법, Jenkins 관리 UI 교육&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div style=&quot;padding: 1rem 1.2rem; margin: 1.5rem 0; border-radius: 8px; border-left: 4px solid #3b82f6; background: #eff6ff; line-height: 1.7;&quot;&gt;&lt;b&gt;Tip&lt;/b&gt;&lt;br /&gt;마이그레이션은 &quot;빅뱅&quot; 방식보다 점진적 전환이 안전합니다. 신규 서비스부터 새 도구를 적용하고, 안정성이 검증되면 기존 서비스를 순차적으로 전환하는 방식을 권장합니다. 양쪽을 동시에 운영하는 기간이 발생하지만, 전환 실패 시 롤백이 쉽습니다.&lt;/div&gt;
&lt;h2 id=&quot;9&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;9. 종합 비교 테이블&lt;/h2&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.95rem; border: 1px solid #e5e7eb;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;비교 기준&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;GitHub Actions&lt;/th&gt;
&lt;th style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; background: #f8fafc; font-weight: bold; text-align: left;&quot;&gt;Jenkins&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;b&gt;호스팅&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;SaaS&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Self-hosted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;b&gt;초기 설정 시간&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;30분&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;반나절~&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;b&gt;운영 부담&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;낮음&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;높음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;b&gt;유연성&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;중간&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;매우 높음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;b&gt;학습 곡선&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;낮음 (YAML)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;중간 (Groovy)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;b&gt;비용 모델&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;사용량 기반 (분당 과금)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;고정 비용 (인프라)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;b&gt;소규모 팀&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;✅ 적합&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;❌ 과도함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;b&gt;대규모 팀&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;△ 비용 증가&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;✅ 규모의 경제&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;b&gt;네트워크 격리&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Self-hosted Runner 필요&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;기본 제공&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;b&gt;SCM 종속&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;GitHub 전용&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;무관&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;b&gt;플러그인/확장&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Marketplace (17,000+ Actions)&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;Plugins (1,800+)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;&lt;b&gt;커뮤니티&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;빠르게 성장 중&lt;/td&gt;
&lt;td style=&quot;border: 1px solid #e5e7eb; padding: 0.75rem; vertical-align: top;&quot;&gt;성숙, 안정적&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;10&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;10. 선택 의사결정 플로우&lt;/h2&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;팀 상황에 따른 선택 기준을 정리합니다.&lt;/p&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1단계: SCM 확인&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GitHub을 사용하고 있다면 &amp;rarr; GitHub Actions를 우선 검토&lt;/li&gt;
&lt;li&gt;GitLab을 사용한다면 &amp;rarr; GitLab CI를 우선 검토&lt;/li&gt;
&lt;li&gt;BitBucket이나 다른 SCM을 사용한다면 &amp;rarr; Jenkins 또는 해당 SCM의 CI 도구 검토&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2단계: 보안 요구사항 확인&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;소스코드가 외부로 나가면 안 된다 &amp;rarr; Jenkins (또는 Self-hosted Runner)&lt;/li&gt;
&lt;li&gt;네트워크 격리가 필수다 &amp;rarr; Jenkins&lt;/li&gt;
&lt;li&gt;특별한 보안 요구사항이 없다 &amp;rarr; GitHub Actions&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3단계: 운영 역량 확인&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전담 DevOps 팀이 있다 &amp;rarr; Jenkins 운영 가능&lt;/li&gt;
&lt;li&gt;개발자가 CI/CD를 겸임한다 &amp;rarr; GitHub Actions (운영 부담 최소화)&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;margin: 1rem 0; line-height: 1.8; color: #334155;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4단계: 비용 비교&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;월 빌드량이 2,000분 이하 &amp;rarr; GitHub Actions (무료)&lt;/li&gt;
&lt;li&gt;월 빌드량이 높고 macOS 빌드가 많다 &amp;rarr; Jenkins (고정 비용이 유리)&lt;/li&gt;
&lt;li&gt;빌드량이 변동이 심하다 &amp;rarr; GitHub Actions (사용한 만큼만 과금)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;11&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;11. 정리&lt;/h2&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GitHub Actions는 &quot;시작이 빠르고 운영 부담이 적은&quot; SaaS CI/CD입니다. GitHub을 사용하는 소규모~중규모 팀에 적합합니다.&lt;/li&gt;
&lt;li&gt;Jenkins는 &quot;유연성과 통제력이 높은&quot; Self-hosted CI/CD입니다. 대규모 조직, 규제 환경, 복잡한 파이프라인에 적합합니다.&lt;/li&gt;
&lt;li&gt;도구 선택은 &quot;팀 규모 &amp;times; 빌드 볼륨 &amp;times; 보안 요구사항 &amp;times; 운영 역량&quot;의 조합으로 결정합니다.&lt;/li&gt;
&lt;li&gt;하나를 선택해야 하는 것이 아닙니다. 강점을 조합하는 하이브리드 패턴도 실무에서 흔합니다.&lt;/li&gt;
&lt;li&gt;&quot;Jenkins가 레거시&quot;라는 인식은 오해입니다. 2026년 현재에도 대규모 조직에서 활발히 사용되고 있습니다. 중요한 것은 도구의 나이가 아니라 팀 상황과의 적합성입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;_6&quot; style=&quot;font-size: 1.5rem; margin-top: 3rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid #e5e7eb; color: #1e293b;&quot; data-ke-size=&quot;size26&quot;&gt;참고 문서&lt;/h2&gt;
&lt;ul style=&quot;margin: 1rem 0; padding-left: 1.5rem; line-height: 1.8;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.github.com/en/actions&quot;&gt;GitHub Actions 공식 문서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.github.com/en/billing/managing-billing-for-github-actions/about-billing-for-github-actions&quot;&gt;GitHub Actions 가격 정책&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.jenkins.io/doc/&quot;&gt;Jenkins 공식 문서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.jenkins.io/doc/book/pipeline/syntax/&quot;&gt;Jenkins Pipeline Syntax&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>DevOps</category>
      <category>automation</category>
      <category>CI/CD</category>
      <category>devops</category>
      <category>github actions</category>
      <category>jenkins</category>
      <category>Pipeline</category>
      <author>wero90</author>
      <guid isPermaLink="true">https://wero90.tistory.com/70</guid>
      <comments>https://wero90.tistory.com/70#entry70comment</comments>
      <pubDate>Mon, 8 Jun 2026 09:50:15 +0900</pubDate>
    </item>
  </channel>
</rss>