<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>악분의 블로그</title>
    <link>https://malwareanalysis.tistory.com/</link>
    <description>안녕하세요. 데브옵스 엔지니어입니다.
주어진 상황에 좋은 결과를 내기 위해 노력합니다. </description>
    <language>ko</language>
    <pubDate>Thu, 30 Jul 2026 12:34:04 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>악분</managingEditor>
    <image>
      <title>악분의 블로그</title>
      <url>https://tistory1.daumcdn.net/tistory/3167687/attach/c25e05047c6347bbb7627c61af23fff7</url>
      <link>https://malwareanalysis.tistory.com</link>
    </image>
    <item>
      <title>CodeBuild connection 임시 토큰을 스크립트에서 재사용하는 방법(git credential helper)</title>
      <link>https://malwareanalysis.tistory.com/948</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이 글을 이해하려면 AWS CodeBuild connection을 알고 있어야 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AWS CodeBuild connection:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://malwareanalysis.tistory.com/939&quot;&gt;https://malwareanalysis.tistory.com/939&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;풀어야-할-문제&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;풀어야 할 문제&lt;/h1&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;CodeBuild로 clone한 repo 안에 쉘 스크립트가 있습니다. 이 쉘 스크립트가 또 다른 private repo를 임시 토큰으로 clone해야 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2922&quot; data-origin-height=&quot;1202&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2pNww/dJMcagzArVf/2ecXg1GVREdsiWUQksIHbk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2pNww/dJMcagzArVf/2ecXg1GVREdsiWUQksIHbk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2pNww/dJMcagzArVf/2ecXg1GVREdsiWUQksIHbk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2pNww%2FdJMcagzArVf%2F2ecXg1GVREdsiWUQksIHbk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2922&quot; height=&quot;1202&quot; data-origin-width=&quot;2922&quot; data-origin-height=&quot;1202&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;풀-수-있는-방법-중-한-개---codebuild-connection-임시-토큰을-그대로-사용&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;풀 수 있는 방법 중 한 개 - CodeBuild connection 임시 토큰을 그대로 사용&lt;/h1&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;결국 임시 토큰만 발급받으면 되기 때문에, 임시 토큰을 발급받는 여러 방법 중 하나를 선택하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이미 CodeBuild connection을 사용하고 있다면, CodeBuild connection이 발급받은 토큰을 그대로 사용해 git clone을 할 수 있습니다.&lt;/b&gt; 물론 CodeBuild connection GitHub App이 clone할 repo에 접근 권한을 가지고 있어야 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2960&quot; data-origin-height=&quot;1306&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUwpIh/dJMcacRrsz7/S6zowiBGe5gbAQRkbcsBwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUwpIh/dJMcacRrsz7/S6zowiBGe5gbAQRkbcsBwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUwpIh/dJMcacRrsz7/S6zowiBGe5gbAQRkbcsBwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUwpIh%2FdJMcacRrsz7%2FS6zowiBGe5gbAQRkbcsBwk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2960&quot; height=&quot;1306&quot; data-origin-width=&quot;2960&quot; data-origin-height=&quot;1306&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;CodeBuild connection이 발급받은 임시 토큰을 사용하려면, &lt;b&gt;buildspec에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;git-credential-helper&lt;span&gt;&amp;nbsp;&lt;/span&gt;옵션을&lt;span&gt;&amp;nbsp;&lt;/span&gt;yes로 설정&lt;/b&gt;합니다.&lt;/p&gt;
&lt;div id=&quot;cb1&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;
&lt;pre class=&quot;http&quot;&gt;&lt;code&gt;version: 0.2

env:
  git-credential-helper: yes&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 옵션을 켜면 CodeBuild가 Git credential helper를 등록하기 때문에, HTTPS 프로토콜로 clone할 때 토큰을 직접 넣지 않아도 됩니다.&lt;/p&gt;
&lt;div id=&quot;cb2&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;git clone https://github.com/&amp;lt;OWNER&amp;gt;/&amp;lt;REPOSITORY&amp;gt;.git&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;토큰을 직접 들고 있는 경우에는 URL에&lt;span&gt;&amp;nbsp;&lt;/span&gt;x-access-token으로 넣는 방법도 있습니다.&lt;/p&gt;
&lt;div id=&quot;cb3&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;git clone https://x-access-token:&amp;lt;TOKEN&amp;gt;@github.com/&amp;lt;OWNER&amp;gt;/&amp;lt;REPOSITORY&amp;gt;.git&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;git-credential-helper&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;git-credential-helper&lt;/h1&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Git credential helper는 HTTPS 프로토콜 Git 요청이 있을 때 인증 정보(username, password)를 제공하는 인터페이스입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2052&quot; data-origin-height=&quot;1176&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0OkrW/dJMcajiOlcw/Ebg72RK6mNQriHRPgdogek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0OkrW/dJMcajiOlcw/Ebg72RK6mNQriHRPgdogek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0OkrW/dJMcajiOlcw/Ebg72RK6mNQriHRPgdogek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0OkrW%2FdJMcajiOlcw%2FEbg72RK6mNQriHRPgdogek%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2052&quot; height=&quot;1176&quot; data-origin-width=&quot;2052&quot; data-origin-height=&quot;1176&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;HTTPS 프로토콜로 git clone을 하면 credential helper가 우선순위에 따라 Git 인증 정보를 조회해 Git client에게 제공합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2456&quot; data-origin-height=&quot;944&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LDa7v/dJMcajiOlcB/q2W9fkIJwA43phBH6Q3Cb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LDa7v/dJMcajiOlcB/q2W9fkIJwA43phBH6Q3Cb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LDa7v/dJMcajiOlcB/q2W9fkIJwA43phBH6Q3Cb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLDa7v%2FdJMcajiOlcB%2Fq2W9fkIJwA43phBH6Q3Cb0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2456&quot; height=&quot;944&quot; data-origin-width=&quot;2456&quot; data-origin-height=&quot;944&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;CodeBuild는 CodeBuild를 위한 Git credential helper를 가지고 있고 AWS가 관리합니다. 다만 이 경로는 AWS 공식 문서에 나와 있지 않기 때문에, 직접 호출하는 방식으로 의존하지 않는 편이 좋습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;/codebuild/readonly/bin/git-credential-helper&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;트레이드-오프&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;트레이드 오프&lt;/h1&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;CodeBuild connection GitHub App은 org마다 1개만 설치할 수 있습니다. 그래서 org에 내 프로젝트가 아닌 다른 팀 프로젝트가 같이 있는 경우, 다른 팀이 CodeBuild connection GitHub App을 사용해 내 프로젝트 repo에 접근할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;참고자료&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;참고자료&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://git-scm.com/docs/gitcredentials&quot;&gt;https://git-scm.com/docs/gitcredentials&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>전공영역 공부 기록</category>
      <category>aws</category>
      <category>codebuild</category>
      <author>악분</author>
      <guid isPermaLink="true">https://malwareanalysis.tistory.com/948</guid>
      <comments>https://malwareanalysis.tistory.com/948#entry948comment</comments>
      <pubDate>Sun, 26 Jul 2026 17:55:17 +0900</pubDate>
    </item>
    <item>
      <title>JVM 옵션 하나로 OOM 순간의 heap dump 남기기</title>
      <link>https://malwareanalysis.tistory.com/947</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;heap OOM(OutOfMemoryError)을 분석하려면 문제가 발생한 순간의 heap dump가 필요&lt;/b&gt;합니다. 그런데 OOM이 발생하면 JVM 프로세스는 이미 종료된 뒤입니다. 프로세스가 죽는 순간에 어떻게 dump를 뜰 수 있을까요?&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;JVM은 heap OOM발생시 dump를 하는 옵션이 있습니다. 이 글에서는 JVM의 heap dump 옵션과 관련 배경에 대해 정리했습니다.&lt;/p&gt;
&lt;div id=&quot;cb1&quot; style=&quot;color: #000000; text-align: start;&quot;&gt;
&lt;pre class=&quot;haml&quot;&gt;&lt;code&gt;java -Xmx2g \
  -XX:+HeapDumpOnOutOfMemoryError \
  -XX:HeapDumpPath=/dumps/heap.hprof \
  -jar app.jar&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;heap-oom은-왜-분석하기-어려운가&quot; style=&quot;color: #000000; text-align: start;&quot;&gt;heap OOM은 왜 분석하기 어려운가&lt;/h1&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;JVM에서 실행되는 애플리케이션은 객체를 생성할 때 heap이라는 메모리를 사용합니다. heap은 유한한 자원이어서, heap보다 더 큰 메모리를 요구하는 객체를 생성하거나 GC로도 더 이상 heap을 정리하지 못하면 &lt;b&gt;OOM이 발생하고 해당 프로세스는 종료&lt;/b&gt;됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체 생성 흐름&lt;br /&gt;&amp;rarr; 자리 없으면 young generation만 정리 (minor GC)&lt;br /&gt;&amp;rarr; 그래도 자리 없으면 heap 전체 정리 (full GC)&lt;br /&gt;&amp;rarr; 그래도 자리 없으면 OOM (java.lang.OutOfMemoryError: Java heap space)&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;OOM을 분석하려면 애플리케이션 로그로 어림짐작하거나 heap dump 파일이 필요합니다. 문제는 heap dump가 문제가 발생한 시점에 떠야 의미가 있다는 점입니다. OOM이 발생하면 프로세스가 이미 종료되어, 정작 필요한 그 시점에는 dump를 뜰 수 없습니다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;oom-순간에-heap-dump하는-방법-jvm-옵션&quot; style=&quot;color: #000000; text-align: start;&quot;&gt;OOM 순간에 heap dump하는 방법: JVM 옵션&lt;/h1&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;JVM은 이 상황을 위한 옵션을 제공합니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;HeapDumpOnOutOfMemoryError 옵션을 지정하면, heap OOM이 발생하는 순간 프로세스가 종료되기 전에 JVM이 heap dump를 생성합니다.&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;HeapDumpPath 옵션으로 dump 파일 위치를 지정합니다.&lt;/p&gt;
&lt;div id=&quot;cb2&quot; style=&quot;color: #000000; text-align: start;&quot;&gt;
&lt;pre class=&quot;haml&quot;&gt;&lt;code&gt;java -Xmx2g \
  -XX:+HeapDumpOnOutOfMemoryError \
  -XX:HeapDumpPath=/dumps/heap.hprof \
  -jar app.jar&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;heap OOM과 관련된 JVM 옵션은 아래와 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;-XX:+HeapDumpOnOutOfMemoryError 첫 OOM 발생 시 heap dump&lt;/li&gt;
&lt;li&gt;-XX:HeapDumpPath=&amp;lt;경로&amp;gt; dump 저장 위치&lt;/li&gt;
&lt;li&gt;-XX:+ExitOnOutOfMemoryError 첫 OOM에서 JVM을 즉시 종료&lt;/li&gt;
&lt;li&gt;-XX:OnOutOfMemoryError=&amp;lt;명령&amp;gt; 첫 OOM에서 지정한 명령을 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;heap-dump-실습&quot; style=&quot;color: #000000; text-align: start;&quot;&gt;heap dump 실습&lt;/h1&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;실습 코드는 저의 GitHub에 공개되어 있습니다.&lt;br /&gt;- &lt;a href=&quot;https://github.com/choisungwook/portfolio/tree/master/computer_science/java/heapdump&quot;&gt;https://github.com/choisungwook/portfolio/tree/master/computer_science/java/heapdump&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;실습 코드는 간단합니다. static 컬렉션은 GC가 회수하지 못하므로, 배열에 1MiB씩 계속 데이터를 넣어 heap이 가득 찰 때까지 쌓습니다.&lt;/p&gt;
&lt;div id=&quot;cb3&quot; style=&quot;color: #000000; text-align: start;&quot;&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;import java.util.ArrayList;
import java.util.List;

/**
 * 메모리 누수를 재현하는 앱.
 * static 컬렉션은 GC가 회수하지 못하므로 heap이 가득 찰 때까지 계속 쌓인다.
 */
public class LeakApp {
  static final List&amp;lt;byte[]&amp;gt; CACHE = new ArrayList&amp;lt;&amp;gt;();

  public static void main(String[] args) throws InterruptedException {
    System.out.println(&quot;pid: &quot; + ProcessHandle.current().pid());
    while (true) {
      CACHE.add(new byte[1024 * 1024]); // 1MiB씩 누적
      if (CACHE.size() % 16 == 0) {
        System.out.println(&quot;leaked: &quot; + CACHE.size() + &quot; MiB&quot;);
      }
      Thread.sleep(10);
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Java 코드는 Docker container로 실행합니다. heap OOM을 빨리 발생시키기 위해 heap 크기를 64MB로 아주 작게 설정했습니다.&lt;/p&gt;
&lt;div id=&quot;cb4&quot; style=&quot;color: #000000; text-align: start;&quot;&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;FROM eclipse-temurin:21-jdk

WORKDIR /app
COPY app/LeakApp.java .
RUN javac LeakApp.java

CMD [&quot;java&quot;, &quot;-Xmx64m&quot;, &quot;-XX:+HeapDumpOnOutOfMemoryError&quot;, &quot;-XX:HeapDumpPath=/dumps/heap.hprof&quot;, &quot;LeakApp&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;실행한 지 1초도 안 되어 heap OOM이 발생합니다.&lt;/p&gt;
&lt;div id=&quot;cb5&quot; style=&quot;color: #000000; text-align: start;&quot;&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;java.lang.OutOfMemoryError&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2188&quot; data-origin-height=&quot;396&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DQznd/dJMb991uMPZ/oDeB8yGqqYBwrqKqW5jY61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DQznd/dJMb991uMPZ/oDeB8yGqqYBwrqKqW5jY61/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DQznd/dJMb991uMPZ/oDeB8yGqqYBwrqKqW5jY61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDQznd%2FdJMb991uMPZ%2FoDeB8yGqqYBwrqKqW5jY61%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2188&quot; height=&quot;396&quot; data-origin-width=&quot;2188&quot; data-origin-height=&quot;396&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;heap-dump-파일-분석&quot; style=&quot;color: #000000; text-align: start;&quot;&gt;heap dump 파일 분석&lt;/h1&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;heap dump 파일은 hprof라는 바이너리 포맷입니다. 그래서 분석하려면 바이너리를 해석하는 도구가 필요합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2304&quot; data-origin-height=&quot;510&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tldam/dJMcabkJyee/SYyGGejaLrY3CKm3PtLWok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tldam/dJMcabkJyee/SYyGGejaLrY3CKm3PtLWok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tldam/dJMcabkJyee/SYyGGejaLrY3CKm3PtLWok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ftldam%2FdJMcabkJyee%2FSYyGGejaLrY3CKm3PtLWok%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2304&quot; height=&quot;510&quot; data-origin-width=&quot;2304&quot; data-origin-height=&quot;510&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;heap dump 파일을 분석하는 오픈소스 도구 중 하나는 Eclipse MAT입니다.&lt;br /&gt;- &lt;a href=&quot;https://eclipse.dev/mat/&quot;&gt;https://eclipse.dev/mat/&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;MAT에서 Leak Suspects Report를 클릭하면, 어느 구간에서 memory leak이 의심되는지 분석해줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1898&quot; data-origin-height=&quot;1624&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cSdTkt/dJMcacDXdbv/mkaZjAo6og0nAqp9TrkUq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cSdTkt/dJMcacDXdbv/mkaZjAo6og0nAqp9TrkUq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cSdTkt/dJMcacDXdbv/mkaZjAo6og0nAqp9TrkUq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcSdTkt%2FdJMcacDXdbv%2FmkaZjAo6og0nAqp9TrkUq0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1898&quot; height=&quot;1624&quot; data-origin-width=&quot;1898&quot; data-origin-height=&quot;1624&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1312&quot; data-origin-height=&quot;1070&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5yXWI/dJMcaa7cols/RlzOm1AtzqpzMdeXx8TTkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5yXWI/dJMcaa7cols/RlzOm1AtzqpzMdeXx8TTkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5yXWI/dJMcaa7cols/RlzOm1AtzqpzMdeXx8TTkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5yXWI%2FdJMcaa7cols%2FRlzOm1AtzqpzMdeXx8TTkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1312&quot; height=&quot;1070&quot; data-origin-width=&quot;1312&quot; data-origin-height=&quot;1070&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;마무리&quot; style=&quot;color: #000000; text-align: start;&quot;&gt;마무리&lt;/h1&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;정리하면, OOM 순간의 heap dump는 프로세스가 죽은 뒤에 뜨는 것이 아니라, 죽기 직전에 JVM이 직접 남기게 하는 것입니다. 그 스위치가 HeapDumpOnOutOfMemoryError이고, 남은 hprof 파일은 Eclipse MAT 같은 도구로 분석하면 됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;참고자료&quot; style=&quot;color: #000000; text-align: start;&quot;&gt;참고자료&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://incheol-jung.gitbook.io/docs/q-and-a/java/heap-dump-feat.-oom#heap-dump&quot;&gt;https://incheol-jung.gitbook.io/docs/q-and-a/java/heap-dump-feat.-oom#heap-dump&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>전공영역 공부 기록</category>
      <category>jvm</category>
      <author>악분</author>
      <guid isPermaLink="true">https://malwareanalysis.tistory.com/947</guid>
      <comments>https://malwareanalysis.tistory.com/947#entry947comment</comments>
      <pubDate>Sun, 19 Jul 2026 13:14:48 +0900</pubDate>
    </item>
    <item>
      <title>Claude Code 권한 관리 - deny, Auto mode, Skill 자동 호출 제어</title>
      <link>https://malwareanalysis.tistory.com/946</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Claude Code를 쓰다 보면 하며 안되는 작업(예: 건드리면 안 되는 파일을 수정)을 하거나, 의도하지 않은 skill이 자동 호출되는 상황을 막는 방법을 정리했습니다. &lt;br /&gt;- 원본 영상:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://youtu.be/RjLlIC9InDI?si=qLwkqZQdW1ReVJeS&quot;&gt;https://youtu.be/RjLlIC9InDI?si=qLwkqZQdW1ReVJeS&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;수행하면-안-되는-작업은-permissions.deny로-막는다&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;수행하면 안 되는 작업은 permissions.deny로 막는다&lt;/h1&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;[영상 9:50] &lt;b&gt;permissions.deny에 등록된 작업은 Claude가 시도할 때마다 거부됩니다.&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;. 설정 위치는 두 곳입니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;$HOME/.claude/settings.json은 사용자 전역에,&lt;span&gt;&amp;nbsp;&lt;/span&gt;$PROJECT_WORKSPACE/.claude/settings.json은 해당 프로젝트에만 적용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 package.json 수정을 금지하려면 다음과 같이 설정합니다.&lt;/p&gt;
&lt;div id=&quot;cb1&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;permissions&quot;: {
    &quot;deny&quot;: [
      &quot;Edit(package.json)&quot;
    ]
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;694&quot; data-origin-height=&quot;498&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RKn95/dJMcah6jCl2/94kkiL77EKG5AvJOle7kpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RKn95/dJMcah6jCl2/94kkiL77EKG5AvJOle7kpK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RKn95/dJMcah6jCl2/94kkiL77EKG5AvJOle7kpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRKn95%2FdJMcah6jCl2%2F94kkiL77EKG5AvJOle7kpK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;694&quot; height=&quot;498&quot; data-origin-width=&quot;694&quot; data-origin-height=&quot;498&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1546&quot; data-origin-height=&quot;612&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/br5TKY/dJMcah6jCnx/U5zZOvWfvIxskPJFHyGWL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/br5TKY/dJMcah6jCnx/U5zZOvWfvIxskPJFHyGWL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/br5TKY/dJMcah6jCnx/U5zZOvWfvIxskPJFHyGWL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbr5TKY%2FdJMcah6jCnx%2FU5zZOvWfvIxskPJFHyGWL1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1546&quot; height=&quot;612&quot; data-origin-width=&quot;1546&quot; data-origin-height=&quot;612&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;settings.json을 직접 수정하기 번거로우면 Claude Code의&lt;span&gt;&amp;nbsp;&lt;/span&gt;/permissions&lt;span&gt;&amp;nbsp;&lt;/span&gt;커맨드나&lt;span&gt;&amp;nbsp;&lt;/span&gt;fewer-permission-prompts로 설정하는 방법도 있습니다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3360&quot; data-origin-height=&quot;310&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bExiDp/dJMcaccMLLB/kl6bec8MaA6sIQpBG2Zx3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bExiDp/dJMcaccMLLB/kl6bec8MaA6sIQpBG2Zx3k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bExiDp/dJMcaccMLLB/kl6bec8MaA6sIQpBG2Zx3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbExiDp%2FdJMcaccMLLB%2Fkl6bec8MaA6sIQpBG2Zx3k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3360&quot; height=&quot;310&quot; data-origin-width=&quot;3360&quot; data-origin-height=&quot;310&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;auto-mode-위험도-판단을-claude에게-맡긴다&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;Auto mode: 위험도 판단을 Claude에게 맡긴다&lt;/h1&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;[영상 38:00] Auto mode는 사용자가 권한 확인에 개입하지 않아도 되는 모드입니다. Claude Code가 명령어의 위험 여부를 스스로 판단해 자동 수락합니다. deny가 &amp;ldquo;무조건 막을 것&amp;rdquo;을 못박는 반면, auto mode는 나머지 판단을 Claude에게 위임합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;skill은-disable-model-invocation으로-명시적-호출만-허용한다&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;Skill은 disable-model-invocation으로 명시적 호출만 허용한다&lt;/h1&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;[영상 13:00] Skill은 name과 description을 보고 Claude가 스스로 호출을 결정합니다. 그래서 description이 넓게 걸리는 skill은 내가 원하지 않는 순간에도 호출당할 수 있습니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;이를 막는 방법이 disable-model-invocation입니다.&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;disable-model-invocation: true로 설정하면 Claude는 해당 skill을 자동으로 호출하지 못하고, 사용자만 slash command로 호출할 수 있습니다.&lt;/p&gt;
&lt;div id=&quot;cb2&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;name: example
description: example
disable-model-invocation: true&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1176&quot; data-origin-height=&quot;608&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGdsJ7/dJMcafgkWOf/ukhnAH5h6Yvk0pA2LmH6Nk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGdsJ7/dJMcafgkWOf/ukhnAH5h6Yvk0pA2LmH6Nk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGdsJ7/dJMcafgkWOf/ukhnAH5h6Yvk0pA2LmH6Nk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGdsJ7%2FdJMcafgkWOf%2FukhnAH5h6Yvk0pA2LmH6Nk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1176&quot; height=&quot;608&quot; data-origin-width=&quot;1176&quot; data-origin-height=&quot;608&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;참고자료&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;참고자료&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;claude 설명 영상:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://youtu.be/RjLlIC9InDI?si=qLwkqZQdW1ReVJeS&quot;&gt;https://youtu.be/RjLlIC9InDI?si=qLwkqZQdW1ReVJeS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>전공영역 공부 기록</category>
      <category>Claude</category>
      <author>악분</author>
      <guid isPermaLink="true">https://malwareanalysis.tistory.com/946</guid>
      <comments>https://malwareanalysis.tistory.com/946#entry946comment</comments>
      <pubDate>Fri, 17 Jul 2026 14:03:23 +0900</pubDate>
    </item>
    <item>
      <title>LiteLLM QuickStart -  guardrail(가드레일)</title>
      <link>https://malwareanalysis.tistory.com/945</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 이메일을 [EMAIL REDACTED]문자열로 변경하는  LiteLLM Guardrail을 실습합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;guardrail이란&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;Guardrail이란&lt;/h1&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Guardrail은 AI 모델를 안전하게 사용하기 위해 안전장치를 설정하는 기능입니다.&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;예를 들어 요청에 이메일, 주민번호 같은 개인정보가 들어 있다면 AI  모델로 보내기 전에 가리거나 차단해야 합니다. 이런 기능을 Guardrail이 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;LiteLLM Guardrail은 AI provier Guardrail을 쓰거나 자체 Guardrail을 사용합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2722&quot; data-origin-height=&quot;1998&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/csdWz0/dJMcabZfPxF/ZLsCE5aunShhky2kILiBg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/csdWz0/dJMcabZfPxF/ZLsCE5aunShhky2kILiBg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/csdWz0/dJMcabZfPxF/ZLsCE5aunShhky2kILiBg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcsdWz0%2FdJMcabZfPxF%2FZLsCE5aunShhky2kILiBg1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2722&quot; height=&quot;1998&quot; data-origin-width=&quot;2722&quot; data-origin-height=&quot;1998&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;LiteLLM 자체 Guardrail은 모델 호출 전후의 정책을 설정합니다.&lt;/p&gt;
&lt;table style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;pre_call&lt;/td&gt;
&lt;td&gt;LLM 호출 전&lt;/td&gt;
&lt;td&gt;입력 검사, 차단 또는 마스킹&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;post_call&lt;/td&gt;
&lt;td&gt;LLM 호출 후&lt;/td&gt;
&lt;td&gt;입력과 출력 검사, 차단 또는 마스킹&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;during_call&lt;/td&gt;
&lt;td&gt;LLM 호출과 병렬&lt;/td&gt;
&lt;td&gt;입력을 검사하며, 검사가 끝날 때까지 응답 반환 보류&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;guardrail-실습&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;Guardrail  실습&lt;/h1&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 글의 실습에서는&lt;span&gt;&amp;nbsp;&lt;/span&gt;pre_call을 사용합니다. &lt;span style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;Guardrail은 Gurailrail메뉴에서 생성할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;예제 Guardrail은 정규식으로 이메일 주소를 찾은 뒤&lt;span&gt;&amp;nbsp;&lt;/span&gt;modify()를 호출해&lt;span&gt;&amp;nbsp;&lt;/span&gt;[EMAIL REDACTED]로 바꿉니다. 모든 요청에 적용하려면&lt;span&gt;&amp;nbsp;&lt;/span&gt;Default On도  활성화했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4652&quot; data-origin-height=&quot;2464&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdYwPI/dJMcaf1ykOU/YWG9uF1cjrknKYb6RHeWR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdYwPI/dJMcaf1ykOU/YWG9uF1cjrknKYb6RHeWR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdYwPI/dJMcaf1ykOU/YWG9uF1cjrknKYb6RHeWR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdYwPI%2FdJMcaf1ykOU%2FYWG9uF1cjrknKYb6RHeWR1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;4652&quot; height=&quot;2464&quot; data-origin-width=&quot;4652&quot; data-origin-height=&quot;2464&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;test-playground에서-먼저-확인하기&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;Test Playground에서 먼저 확인하기&lt;/h1&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Guardrails &amp;rarr; Test Playground에서 만든 Guardrail을 선택하고&lt;span&gt;&amp;nbsp;&lt;/span&gt;test@example.com을 입력합니다. 실행 결과가&lt;span&gt;&amp;nbsp;&lt;/span&gt;[EMAIL REDACTED]로 바뀌면 정규식과 마스킹 로직이 정상입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3610&quot; data-origin-height=&quot;1976&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vCk2P/dJMcaa0g6DW/JVPFKY4dcT1IfjAD9xhpik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vCk2P/dJMcaa0g6DW/JVPFKY4dcT1IfjAD9xhpik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vCk2P/dJMcaa0g6DW/JVPFKY4dcT1IfjAD9xhpik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvCk2P%2FdJMcaa0g6DW%2FJVPFKY4dcT1IfjAD9xhpik%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3610&quot; height=&quot;1976&quot; data-origin-width=&quot;3610&quot; data-origin-height=&quot;1976&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;api-호출로-모델에-전달된-값-확인하기&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;API 호출로 모델에 전달된 값 확인하기&lt;/h1&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;같은 Guardrail이 실제 Chat Completions 요청에도 적용되는지 확인합니다. 아래 요청은 모델이 입력을 그대로 돌려주도록 작성했습니다.&lt;/p&gt;
&lt;div id=&quot;cb1&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;
&lt;pre class=&quot;julia&quot;&gt;&lt;code&gt;curl &quot;$LITELLM_URL/v1/chat/completions&quot; \
  -H &quot;Authorization: Bearer $LITELLM_KEY&quot; \
  -H &quot;Content-Type: application/json&quot; \
  -d '{
    &quot;model&quot;: &quot;gpt-4.1-nano&quot;,
    &quot;messages&quot;: [
      {
        &quot;role&quot;: &quot;user&quot;,
        &quot;content&quot;: &quot;Echo my request. test@example.com is my address. This is a LiteLLM Guardrail test. Reply with the message unchanged.&quot;
      }
    ]
  }'&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;응답에는&lt;span&gt;&amp;nbsp;&lt;/span&gt;test@example.com&lt;span&gt;&amp;nbsp;&lt;/span&gt;대신&lt;span&gt;&amp;nbsp;&lt;/span&gt;[EMAIL REDACTED]가 들어 있습니다.&lt;span&gt; &lt;span style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;AI모델에게 요청하기 전, &lt;/span&gt;&lt;/span&gt;pre_call&lt;span&gt;&amp;nbsp;&lt;/span&gt;Guardrail이 이메일을 마스킹했고 AI모델은 마스킹된 문장을 입력으로 받아 그대로 응답으로 전송했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;5068&quot; data-origin-height=&quot;534&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLnmDT/dJMcaaF3BAk/BueZNPw74FOTXTPrzGO8n0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLnmDT/dJMcaaF3BAk/BueZNPw74FOTXTPrzGO8n0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLnmDT/dJMcaaF3BAk/BueZNPw74FOTXTPrzGO8n0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLnmDT%2FdJMcaaF3BAk%2FBueZNPw74FOTXTPrzGO8n0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;5068&quot; height=&quot;534&quot; data-origin-width=&quot;5068&quot; data-origin-height=&quot;534&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;참고자료&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;참고자료&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.litellm.ai/docs/proxy/guardrails/quick_start&quot;&gt;https://docs.litellm.ai/docs/proxy/guardrails/quick_start&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.litellm.ai/docs/proxy/guardrails/test_playground&quot;&gt;https://docs.litellm.ai/docs/proxy/guardrails/test_playground&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>전공영역 공부 기록</category>
      <category>Ai</category>
      <category>LiteLLM</category>
      <author>악분</author>
      <guid isPermaLink="true">https://malwareanalysis.tistory.com/945</guid>
      <comments>https://malwareanalysis.tistory.com/945#entry945comment</comments>
      <pubDate>Sun, 12 Jul 2026 22:34:36 +0900</pubDate>
    </item>
    <item>
      <title>LiteLLM QuickStart -  Budget limit(비용제한)</title>
      <link>https://malwareanalysis.tistory.com/944</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 LiteLLM Budget limit 실습과 원리를 간단히 확인해봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;budget-limit이란&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;Budget limit이란&lt;/h1&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Budget limit은  AI사용금액을 제한시키는 정책입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;실습-team-budget을-설정하고-차단을-확인합니다&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;실습 Team Budget을 설정하고 차단을 확인합니다&lt;/h1&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;LiteLLM Web UI의 Team &amp;rarr; Settings &amp;rarr; Max Budget에서 Team Budget을 설정합니다. 이 실습에서는 빠르게 차단 결과를 확인하기 위해 한도를 $0.000010으로 설정했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1996&quot; data-origin-height=&quot;2122&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mi4mQ/dJMcaa67dmu/DCcely58knFe8SVQNzKRp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mi4mQ/dJMcaa67dmu/DCcely58knFe8SVQNzKRp0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mi4mQ/dJMcaa67dmu/DCcely58knFe8SVQNzKRp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmi4mQ%2FdJMcaa67dmu%2FDCcely58knFe8SVQNzKRp0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1996&quot; height=&quot;2122&quot; data-origin-width=&quot;1996&quot; data-origin-height=&quot;2122&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Budget을 설정한 Team에서 virtual key를 발급하고 LiteLLM API를 호출해봅니다.&lt;/p&gt;
&lt;div id=&quot;cb1&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;
&lt;pre class=&quot;julia&quot;&gt;&lt;code&gt;export LITELLM_URL=http://localhost:4000
export LITELLM_KEY='sk-...'

curl -i &quot;$LITELLM_URL/chat/completions&quot; \
  -H &quot;Authorization: Bearer $LITELLM_KEY&quot; \
  -H &quot;Content-Type: application/json&quot; \
  -d '{
    &quot;model&quot;:&quot;gpt-4.1-nano&quot;,
    &quot;messages&quot;:[{&quot;role&quot;:&quot;user&quot;,&quot;content&quot;:&quot;Reply only: hellooooooooooooooooooooooooooooooooooooooooo&quot;}],
    &quot;max_tokens&quot;:10
  }'&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;설정한 Budget 한도가 적어서 바로 Budget Limit이 걸립니다. LiteLLM은 AI모델을 호출하지 않고&lt;span&gt;&amp;nbsp;&lt;/span&gt;429&lt;span&gt;&amp;nbsp;&lt;/span&gt;응답과 Budget 초과 메시지를 반환합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4422&quot; data-origin-height=&quot;868&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dXFogC/dJMb99UAWBz/m4JeYLgheIHhfeiM9kVMo0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dXFogC/dJMb99UAWBz/m4JeYLgheIHhfeiM9kVMo0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dXFogC/dJMb99UAWBz/m4JeYLgheIHhfeiM9kVMo0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdXFogC%2FdJMb99UAWBz%2Fm4JeYLgheIHhfeiM9kVMo0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;4422&quot; height=&quot;868&quot; data-origin-width=&quot;4422&quot; data-origin-height=&quot;868&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;로그와-cost를-확인&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;로그와 Cost를 확인&lt;/h1&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Budget Limit 차단 로그는 LiteLLM에서 확인할 수 있습니다. Web UI의&lt;span&gt;&amp;nbsp;&lt;/span&gt;Logs &amp;rarr; Request Logs에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;429&lt;span&gt;&amp;nbsp;&lt;/span&gt;status code를 찾으면 Budget limit으로 거절된 요청을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4104&quot; data-origin-height=&quot;2204&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/coWGA1/dJMb997723O/hTA6LVkVUBHdI68KYyp7RK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/coWGA1/dJMb997723O/hTA6LVkVUBHdI68KYyp7RK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/coWGA1/dJMb997723O/hTA6LVkVUBHdI68KYyp7RK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcoWGA1%2FdJMb997723O%2FhTA6LVkVUBHdI68KYyp7RK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;4104&quot; height=&quot;2204&quot; data-origin-width=&quot;4104&quot; data-origin-height=&quot;2204&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;사용한 비용은 Usage에서 확인합니다. Global, Team, User 등의 필터를 적용하면 어떤 범위에서 비용이 누적됐는지 나눠 볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;5014&quot; data-origin-height=&quot;2464&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d3FINn/dJMcafN8LjR/KG4b7hSsAIqZ02hTuaixMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d3FINn/dJMcafN8LjR/KG4b7hSsAIqZ02hTuaixMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d3FINn/dJMcafN8LjR/KG4b7hSsAIqZ02hTuaixMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd3FINn%2FdJMcafN8LjR%2FKG4b7hSsAIqZ02hTuaixMk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;5014&quot; height=&quot;2464&quot; data-origin-width=&quot;5014&quot; data-origin-height=&quot;2464&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;budget-limit-판단하는-방법&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt; Budget limit 판단하는 방법&lt;/h1&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Budget limit의 판단에는 지금까지 사용한 비용이 필요합니다.&lt;/b&gt;&lt;br /&gt;- 지금까지 사용한 비용 &amp;gt;= budget limit&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;지금까지 사용한 비용은 어떻게 계산할까요? LiteLLM은 model cost map에 저장된 모델별 토큰 가격과 이 사용량을 조합해 Cost를 계산합니다. LLM provider가 매번 요청의 비용을 리턴하지 않고 AI모델이 사용한 input token과 output token 을 리턴하기 때문에, LiteLLM은 매요청마다 Cost를 계산해야 합니다.&lt;br /&gt;- Model cost map:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://github.com/BerriAI/litellm/blob/main/model_prices_and_context_window.json&quot;&gt;https://github.com/BerriAI/litellm/blob/main/model_prices_and_context_window.json&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;동시-요청이-오면-budget-limit을-어떻게-판단할까&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;동시 요청이 오면 Budget limit을 어떻게 판단할까?&lt;/h1&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;동시에 들어오면 지금까지 사용한 비용으로만 가지고 정확히 제한하기 어렵습니다.&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;여러 요청이 당시 budget기준으로 통과하더라도, 응답 후 계산된 전체 비용이 한도를 넘어설 수 있기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;LiteLLM은 이 간격을 줄이기 위해 Budget reservation을 사용합니다. 요청을 보내기 전에 tiktoken 토크나이저로 토큰 수와 예상 비용을 계산해 예약하고, 동시 요청을 판단할 때 이미 예약된 금액까지 반영합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1362&quot; data-origin-height=&quot;670&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cEibMN/dJMcah6fk5t/2elmhV9zjYtCwVCZo26Ww1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cEibMN/dJMcah6fk5t/2elmhV9zjYtCwVCZo26Ww1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cEibMN/dJMcah6fk5t/2elmhV9zjYtCwVCZo26Ww1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcEibMN%2FdJMcah6fk5t%2F2elmhV9zjYtCwVCZo26Ww1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1362&quot; height=&quot;670&quot; data-origin-width=&quot;1362&quot; data-origin-height=&quot;670&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Budget reservation 로직은 LiteLLM 저장소에서 확인할 수 있습니다.&lt;br /&gt;- Budget reservation:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://github.com/BerriAI/litellm/blob/v1.91.1/litellm/proxy/spend_tracking/budget_reservation.py&quot;&gt;https://github.com/BerriAI/litellm/blob/v1.91.1/litellm/proxy/spend_tracking/budget_reservation.py&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;budget-scope&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;Budget Scope&lt;/h1&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Budget은 Global, Organization, Team, User처럼 여러 Scope에 설정할 수 있습니다. 요청이 속한 범위 중 하나라도 한도에 걸리면, 더 하위 Scope에 예산이 남아 있어도 요청은 제한됩니다.&lt;br /&gt;- Global &amp;gt; Organization &amp;gt; Team &amp;gt; User&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아래 실습에서는 User Budget에는 여유가 있지만 Team Budget이 한도에 도달해 요청이 차단됐습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2516&quot; data-origin-height=&quot;868&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ILUds/dJMcadW0r58/TKJTj2hySTT306xJZ2e4s0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ILUds/dJMcadW0r58/TKJTj2hySTT306xJZ2e4s0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ILUds/dJMcadW0r58/TKJTj2hySTT306xJZ2e4s0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FILUds%2FdJMcadW0r58%2FTKJTj2hySTT306xJZ2e4s0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2516&quot; height=&quot;868&quot; data-origin-width=&quot;2516&quot; data-origin-height=&quot;868&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;참고자료&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;참고자료&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/BerriAI/litellm/blob/v1.91.1/litellm/proxy/spend_tracking/budget_reservation.py&quot;&gt;https://github.com/BerriAI/litellm/blob/v1.91.1/litellm/proxy/spend_tracking/budget_reservation.py&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.litellm.ai/docs/proxy/cost_tracking&quot;&gt;https://docs.litellm.ai/docs/proxy/cost_tracking&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/BerriAI/litellm/blob/main/model_prices_and_context_window.json&quot;&gt;https://github.com/BerriAI/litellm/blob/main/model_prices_and_context_window.json&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>전공영역 공부 기록</category>
      <category>Ai</category>
      <category>LiteLLM</category>
      <author>악분</author>
      <guid isPermaLink="true">https://malwareanalysis.tistory.com/944</guid>
      <comments>https://malwareanalysis.tistory.com/944#entry944comment</comments>
      <pubDate>Sun, 12 Jul 2026 19:02:38 +0900</pubDate>
    </item>
    <item>
      <title>LiteLLM QuickStart - 모델 등록하고 호출해보기</title>
      <link>https://malwareanalysis.tistory.com/943</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이 글은 LiteLLM에서 OpenAI 모델을 등록해보고 호출하는 QuickStart입니다. LiteLLM 설정 하나하나를 깊게 파기보다, LiteLLM에 모델을 등록과 호출과정을 &amp;ldquo;이렇게 흘러가는구나&amp;rdquo;를  훓어봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;모델-등록&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;모델 등록&lt;/h1&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;모델 등록은 LiteLLM이 호출할 수 있는 모델을 설정하는 과정입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;좌측 메뉴에서 Models + Endpoints &amp;rarr; Add Model을 선택합니다. 아래 값을 입력한 뒤 Add Model을 누릅니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2982&quot; data-origin-height=&quot;1510&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/68Not/dJMcaaMLcNP/mSa2lGstXDafNvoRVBMeU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/68Not/dJMcaaMLcNP/mSa2lGstXDafNvoRVBMeU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/68Not/dJMcaaMLcNP/mSa2lGstXDafNvoRVBMeU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F68Not%2FdJMcaaMLcNP%2FmSa2lGstXDafNvoRVBMeU1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2982&quot; height=&quot;1510&quot; data-origin-width=&quot;2982&quot; data-origin-height=&quot;1510&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;필드를 입력합니다.&lt;br /&gt;- provider: OpenAI&lt;br /&gt;- LiteLLM Model Name: OpenAI 모델, 이 예제에서는 gpt- 4.1-nano 사용&lt;br /&gt;- Mode(option): Chat, 채팅에 잘 대답하도록 설정&lt;br /&gt;- OpenAI API key: OpenAI 플랫폼에서 발급한 API key, Open AI key는 LiteLLM에서만 사용하고 클라이언트는 사용하지 않습니다. 클라이언트에는 뒤에서 만들 virtual key 를 사용합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3318&quot; data-origin-height=&quot;2300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c80I2p/dJMcaaTvSZT/iuiEivBKQ0qGrK7QePtNBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c80I2p/dJMcaaTvSZT/iuiEivBKQ0qGrK7QePtNBK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c80I2p/dJMcaaTvSZT/iuiEivBKQ0qGrK7QePtNBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc80I2p%2FdJMcaaTvSZT%2FiuiEivBKQ0qGrK7QePtNBK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3318&quot; height=&quot;2300&quot; data-origin-width=&quot;3318&quot; data-origin-height=&quot;2300&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;필드를 입력한 뒤 Test connect를 눌러 OpenAI 모델이 실제로 호출되는지 확인합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2420&quot; data-origin-height=&quot;980&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TwpBE/dJMcaaTvSZU/VZ81ng03yv07Z9oAKkVUbk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TwpBE/dJMcaaTvSZU/VZ81ng03yv07Z9oAKkVUbk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TwpBE/dJMcaaTvSZU/VZ81ng03yv07Z9oAKkVUbk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTwpBE%2FdJMcaaTvSZU%2FVZ81ng03yv07Z9oAKkVUbk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2420&quot; height=&quot;980&quot; data-origin-width=&quot;2420&quot; data-origin-height=&quot;980&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;호출이 되면 오른쪽 아래 Add model 버튼을 눌러 모델을 등록합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3314&quot; data-origin-height=&quot;2114&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZCwPe/dJMcabSn0Bi/7ipRKlZSPwCYHllRD7Nztk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZCwPe/dJMcabSn0Bi/7ipRKlZSPwCYHllRD7Nztk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZCwPe/dJMcabSn0Bi/7ipRKlZSPwCYHllRD7Nztk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZCwPe%2FdJMcabSn0Bi%2F7ipRKlZSPwCYHllRD7Nztk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3314&quot; height=&quot;2114&quot; data-origin-width=&quot;3314&quot; data-origin-height=&quot;2114&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;모델-조회&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;모델 조회&lt;/h1&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;등록한 모델은 All model에서 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1982&quot; data-origin-height=&quot;1330&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lG1Zp/dJMcaftJWdh/BKRAwkukNGo68zP3YXceGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lG1Zp/dJMcaftJWdh/BKRAwkukNGo68zP3YXceGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lG1Zp/dJMcaftJWdh/BKRAwkukNGo68zP3YXceGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlG1Zp%2FdJMcaftJWdh%2FBKRAwkukNGo68zP3YXceGK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1982&quot; height=&quot;1330&quot; data-origin-width=&quot;1982&quot; data-origin-height=&quot;1330&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;모델-호출-테스트&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;모델 호출 테스트&lt;/h1&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Playground는 LitemLLM API key없이 UI에서 곧바로 호출을 확인하는 자리입니다. 아래 API 호출로 넘어가기 전에, 등록한 모델이 응답을 돌려주는지 먼저 확인합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3864&quot; data-origin-height=&quot;2442&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cF4Dgk/dJMcahSzGLC/W44hMhTXk0pKxyKYkoQzu1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cF4Dgk/dJMcahSzGLC/W44hMhTXk0pKxyKYkoQzu1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cF4Dgk/dJMcahSzGLC/W44hMhTXk0pKxyKYkoQzu1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcF4Dgk%2FdJMcahSzGLC%2FW44hMhTXk0pKxyKYkoQzu1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3864&quot; height=&quot;2442&quot; data-origin-width=&quot;3864&quot; data-origin-height=&quot;2442&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;virtual-key로-모델-호출&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;Virtual key로 모델 호출&lt;/h1&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;LiteLLM에 등록한 모델을 외부에서 호출하려면 virtual key가 필요합니다.&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;virtual key는 LiteLLM이 발급하는 access token입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;virtual-key-생성&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;virtual key 생성&lt;/h2&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;왼쪽 메뉴의 Virtual keys에서 key를 생성합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1574&quot; data-origin-height=&quot;982&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/R3KWS/dJMcafN8uxs/gbkWTRPOt4pFro6VbTgbHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/R3KWS/dJMcafN8uxs/gbkWTRPOt4pFro6VbTgbHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/R3KWS/dJMcafN8uxs/gbkWTRPOt4pFro6VbTgbHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FR3KWS%2FdJMcafN8uxs%2FgbkWTRPOt4pFro6VbTgbHK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1574&quot; height=&quot;982&quot; data-origin-width=&quot;1574&quot; data-origin-height=&quot;982&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Owned by는 you를 선택하고, key 이름을 입력, 모델을 선택합니다. 그리고 Create key 버튼을 눌러 key를 생성합니다.\&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3166&quot; data-origin-height=&quot;1696&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kx7w5/dJMcabSocRh/L7MZTkKa8PtajH3CG9kyG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kx7w5/dJMcabSocRh/L7MZTkKa8PtajH3CG9kyG0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kx7w5/dJMcabSocRh/L7MZTkKa8PtajH3CG9kyG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fkx7w5%2FdJMcabSocRh%2FL7MZTkKa8PtajH3CG9kyG0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3166&quot; height=&quot;1696&quot; data-origin-width=&quot;3166&quot; data-origin-height=&quot;1696&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;key를 생성하면 화면에 key가 출력됩니다. 이때 한 번만 보이므로 안전한 곳에 보관합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2148&quot; data-origin-height=&quot;920&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bko25Y/dJMcadW0aAC/O1BvWxkSsKnCebGQHArmy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bko25Y/dJMcadW0aAC/O1BvWxkSsKnCebGQHArmy0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bko25Y/dJMcadW0aAC/O1BvWxkSsKnCebGQHArmy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbko25Y%2FdJMcadW0aAC%2FO1BvWxkSsKnCebGQHArmy0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2148&quot; height=&quot;920&quot; data-origin-width=&quot;2148&quot; data-origin-height=&quot;920&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;발급받은 virtual key로 LiteLLM API를 호출합니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;Authorization&lt;span&gt;&amp;nbsp;&lt;/span&gt;헤더에는 provider key가 아니라 virtual key를 넣고,&lt;span&gt;&amp;nbsp;&lt;/span&gt;model에는 등록할 때 지정한 Model Name (Public) 값을 넣습니다. 클라이언트가 아는 것은 이 두 값뿐입니다.&lt;/p&gt;
&lt;div id=&quot;cb1&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;curl http://127.0.0.1:4000/v1/chat/completions \
  -H &quot;Authorization: Bearer sk-&amp;lt;virtual-key&amp;gt;&quot; \
  -H &quot;Content-Type: application/json&quot; \
  -d '{&quot;model&quot;:&quot;gpt-4o&quot;,&quot;messages&quot;:[{&quot;role&quot;:&quot;user&quot;,&quot;content&quot;:&quot;hi&quot;}]}'&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;5092&quot; data-origin-height=&quot;348&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BP4vD/dJMcafAxrOY/CtNdbgGSnyxqpwSuUtTuM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BP4vD/dJMcafAxrOY/CtNdbgGSnyxqpwSuUtTuM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BP4vD/dJMcafAxrOY/CtNdbgGSnyxqpwSuUtTuM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBP4vD%2FdJMcafAxrOY%2FCtNdbgGSnyxqpwSuUtTuM1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;5092&quot; height=&quot;348&quot; data-origin-width=&quot;5092&quot; data-origin-height=&quot;348&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;참고자료&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;참고자료&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.litellm.ai/docs/proxy/model_management&quot;&gt;https://docs.litellm.ai/docs/proxy/model_management&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.litellm.ai/docs/proxy/ui_store_model_db_setting#zero-downtime-updates&quot;&gt;https://docs.litellm.ai/docs/proxy/ui_store_model_db_setting#zero-downtime-updates&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>전공영역 공부 기록</category>
      <category>Ai</category>
      <category>LiteLLM</category>
      <author>악분</author>
      <guid isPermaLink="true">https://malwareanalysis.tistory.com/943</guid>
      <comments>https://malwareanalysis.tistory.com/943#entry943comment</comments>
      <pubDate>Sun, 12 Jul 2026 02:35:41 +0900</pubDate>
    </item>
    <item>
      <title>RPM/DNF 설치 자동화가 lock 에러로 실패할 수 상황</title>
      <link>https://malwareanalysis.tistory.com/942</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;RPM 또는 DNF 패키지 설치 자동화 스크립트가 낮은 확률로 lock 에러로 실패할 수 있습니다. 재실행하면 성공해서 넘어가기만 시간이 지나면 어느날 또 lock 에러가 발생할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;왜 rpm/dnf는 lock을 쓸까요. 이 글은 그 원리에서 시작해 lock 확인 방법 그리고 Ansible에서 안전하게 기다리는 방법을 정리했습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;rpm-또는-dnf의-lock&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;RPM 또는 dnf의 lock&lt;/h1&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;rpm/dnf는 한 번에 한 작업만 실행되도록 lock 을 사용하는데, 설치 스크립트가 병렬로 설치하거나 cron 업데이트와 겹치면, 뒤늦게 실행된 쪽은 lock을 얻지 못하고 실패합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1672&quot; data-origin-height=&quot;941&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8W9fS/dJMcaiRtgTW/8rTCAiNn0chQmPRFeDjWB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8W9fS/dJMcaiRtgTW/8rTCAiNn0chQmPRFeDjWB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8W9fS/dJMcaiRtgTW/8rTCAiNn0chQmPRFeDjWB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8W9fS%2FdJMcaiRtgTW%2F8rTCAiNn0chQmPRFeDjWB1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1672&quot; height=&quot;941&quot; data-origin-width=&quot;1672&quot; data-origin-height=&quot;941&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;rpmdnf는-왜-lock을-쓰는가&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;RPM/DNF는 왜 lock을 쓰는가&lt;/h1&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;RPM은 database를 파일 lock으로 지킵니다.&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;RPM database는&lt;span&gt;&amp;nbsp;&lt;/span&gt;/var/lib/rpm에 있고, RPM은&lt;span&gt;&amp;nbsp;&lt;/span&gt;/var/lib/rpm/.rpm.lock&lt;span&gt;&amp;nbsp;&lt;/span&gt;파일에 fcntl write lock을 겁니다. 설치 트랜잭션(rpmtsRun)과 database 초기화&amp;middot;재빌드가 이 lock으로 직렬화됩니다. 익숙한 에러&lt;span&gt;&amp;nbsp;&lt;/span&gt;warning: waiting for transaction lock on /var/lib/rpm/.rpm.lock가 바로 이 lock을 기다리는 상황입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;왜 database 엔진 내부 lock이 아니라 파일 lock일까요.&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;패키지 설치는 database 레코드만 바꾸는 게 아니라 파일 시스템에 실제 파일을 풀고 지우는 작업까지 포함합니다. database 트랜잭션 lock은 database 변경은 보호하지만 파일 시스템 변화까지 한 단위로 묶어주지는 못합니다. 설치 전체를 하나로 직렬화하려면 파일 lock이 필요합니다.&lt;/p&gt;
&lt;div id=&quot;cb1&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;ls /var/lib/rpm&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1026&quot; data-origin-height=&quot;88&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Yz7Px/dJMcage2UuX/UEQl1Ektfvo4DFjppUggbk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Yz7Px/dJMcage2UuX/UEQl1Ektfvo4DFjppUggbk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Yz7Px/dJMcage2UuX/UEQl1Ektfvo4DFjppUggbk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYz7Px%2FdJMcage2UuX%2FUEQl1Ektfvo4DFjppUggbk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1026&quot; height=&quot;88&quot; data-origin-width=&quot;1026&quot; data-origin-height=&quot;88&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;lock-file&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;lock file&lt;/h1&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;lock file은 파일을 lock처럼 사용하는 개념입니다.&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;/tmp&lt;span&gt;&amp;nbsp;&lt;/span&gt;경로에 lock 파일을 만들고, 명령어를 실행할 때마다 lock이 안 걸려 있는지 확인한 뒤 실행하는 식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;리눅스에서는&lt;span&gt;&amp;nbsp;&lt;/span&gt;flock&lt;span&gt;&amp;nbsp;&lt;/span&gt;명령어로 이 lock file을 직접 실습할 수 있습니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;flock은 지정한 파일에 lock을 걸고, 그 lock을 잡은 채로 뒤에 오는 명령어를 실행합니다.&lt;/p&gt;
&lt;div id=&quot;cb2&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;
&lt;pre class=&quot;sqf&quot;&gt;&lt;code&gt;# lock 파일 생성
touch /tmp/akbun.lock

# sleep 명령어를 실행하기 전에 lock 획득
flock /tmp/akbun.lock sleep 60&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;누군가 이미 lock을 걸었다면 lock 획득에 실패하고, 뒤의 명령어는 실행되지 않습니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;-n은 lock을 기다리지 않고 바로 실패하라는 옵션입니다.&lt;/p&gt;
&lt;div id=&quot;cb3&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;flock -n /tmp/akbun.lock echo &quot;lock acquired&quot; || echo &quot;lock busy&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1552&quot; data-origin-height=&quot;92&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oe7L7/dJMcab5SQgP/YmVfanKsYSDq0rACOWKrvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oe7L7/dJMcab5SQgP/YmVfanKsYSDq0rACOWKrvk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oe7L7/dJMcab5SQgP/YmVfanKsYSDq0rACOWKrvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Foe7L7%2FdJMcab5SQgP%2FYmVfanKsYSDq0rACOWKrvk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1552&quot; height=&quot;92&quot; data-origin-width=&quot;1552&quot; data-origin-height=&quot;92&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;lock이-걸렸는지-확인하기&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;lock이 걸렸는지 확인하기&lt;/h1&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;lock이 걸려 있는지 확인하는 방법은 여러가지가 있고 4가지를 소개합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;첫째,&lt;span&gt;&amp;nbsp;&lt;/span&gt;fuser로 lock 파일을 누가 잡고 있는지 봅니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;lock이 안 걸려 있으면 결과가 비어 있고, 걸려 있으면 파일을 잡고 있는 프로세스가 나옵니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;708&quot; data-origin-height=&quot;90&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kWET8/dJMcabZacSY/ekNsjMHsk79hKuB4pkdjak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kWET8/dJMcabZacSY/ekNsjMHsk79hKuB4pkdjak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kWET8/dJMcabZacSY/ekNsjMHsk79hKuB4pkdjak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkWET8%2FdJMcabZacSY%2FekNsjMHsk79hKuB4pkdjak%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;708&quot; height=&quot;90&quot; data-origin-width=&quot;708&quot; data-origin-height=&quot;90&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;684&quot; data-origin-height=&quot;86&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dfCdv5/dJMcabZacS1/nq4U2jK0biUrkuR3B457g1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dfCdv5/dJMcabZacS1/nq4U2jK0biUrkuR3B457g1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dfCdv5/dJMcabZacS1/nq4U2jK0biUrkuR3B457g1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdfCdv5%2FdJMcabZacS1%2Fnq4U2jK0biUrkuR3B457g1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;684&quot; height=&quot;86&quot; data-origin-width=&quot;684&quot; data-origin-height=&quot;86&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;둘째, DNF가 남기는 pid 파일을 확인합니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;DNF는 lock을 설정한 process id를 metadata&amp;middot;rpmdb&amp;middot;download 작업별로 파일에 기록합니다.&lt;/p&gt;
&lt;div id=&quot;cb4&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;/var/cache/dnf/metadata_lock.pid
/var/lib/dnf/rpmdb_lock.pid
/var/cache/dnf/download_lock.pid&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;DNF의 lock은 실제 파일 lock이 아니라 이 pid 파일의 존재와 안에 적힌 프로세스가 살아 있는지를 확인하는 방식입니다. 그래서 프로세스가 비정상 종료하면 lock 파일이 남아 오작동할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;셋째, 로그를 확인합니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;DNF는&lt;span&gt;&amp;nbsp;&lt;/span&gt;/var/log/dnf.log에 로그를 남깁니다. RPM은 별도 로그 파일이 없어서, database에 기록된 패키지 설치&amp;middot;변경 시간을 보고 lock을 추측해야 합니다.&lt;/p&gt;
&lt;div id=&quot;cb5&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;
&lt;pre class=&quot;livecodeserver&quot;&gt;&lt;code&gt;# dnf
grep -iE 'lock|waiting for process|pid' /var/log/dnf.log
# rpm
rpm -qa --last&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;넷째, 패키지 매니저 프로세스가 실행 중인지 봅니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;스크립트가 실행 즉시 lock으로 실패했다면 process 목록에서 dnf, rpm을 찾습니다.&lt;/p&gt;
&lt;div id=&quot;cb6&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;pgrep -x 'dnf|dnf5|yum|packagekitd|rpm'&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;ansible-playbook에서-lock-대기&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;ansible playbook에서 lock 대기&lt;/h1&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;dnf 모듈을 쓴다면&lt;span&gt;&amp;nbsp;&lt;/span&gt;lock_timeout으로 lock을 기다립니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;lock이 풀릴 때까지 최대 지정한 초만큼 대기합니다.&lt;/p&gt;
&lt;div id=&quot;cb7&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;- name: install packages
  ansible.builtin.dnf:
    name:
      - nginx
      - redis
    state: present
    lock_timeout: 60&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;dnf 모듈이 아니라 rpm 등을 직접 쓴다면 프로세스 실행 여부로 대기합니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;pgrep이 프로세스를 못 찾아 종료 코드가 0이 아닐 때(=패키지 매니저가 idle) 다음 단계로 넘어갑니다.&lt;/p&gt;
&lt;div id=&quot;cb8&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;- name: wait until pkg manager is idle
  ansible.builtin.shell: pgrep -x 'dnf|dnf5|yum|packagekitd|rpm'
  register: pkg_busy
  until: pkg_busy.rc != 0
  retries: 36
  delay: 5
  changed_when: false
  failed_when: false&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;결론-rpm-또는-dnf-자동화는-lock이-걸릴-수-있다는-것을-생각해야-한다.&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;마치며, rpm 또는 dnf 자동화는 lock 이 걸릴 수 있다는 것을 생각해야 한다.&lt;/h1&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;rpm, dnf는 패키지가 꼬이지 않도록 한 번에 하나씩만 실행되게 lock을 씁니다. 손으로 명령어를 칠 때는 이 lock이 잘 보이지 않지만, 스크립트로 자동화하면 병렬 실행이나 cron 업데이트와 겹치는 순간 lock 충돌이 드러납니다. 그래서 자동화 스크립트를 짤 때는 lock을 대기하거나 패키지 작업이 겹치지 않게 하는 로직을 구현해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;참고자료&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;참고자료&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a style=&quot;color: #1a1a1a;&quot; href=&quot;https://rpm.org/user_doc/db_recovery.html&quot;&gt;rpm.org - RPM Database Recovery&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #1a1a1a;&quot; href=&quot;https://github.com/rpm-software-management/dnf/blob/master/dnf/lock.py&quot;&gt;dnf/dnf/lock.py&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #1a1a1a;&quot; href=&quot;https://utcc.utoronto.ca/~cks/space/blog/linux/DNFLogsWhatWhere&quot;&gt;Some notes on what Fedora&amp;rsquo;s DNF logs and where&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>전공영역 공부 기록</category>
      <category>Linux</category>
      <author>악분</author>
      <guid isPermaLink="true">https://malwareanalysis.tistory.com/942</guid>
      <comments>https://malwareanalysis.tistory.com/942#entry942comment</comments>
      <pubDate>Sun, 5 Jul 2026 19:16:59 +0900</pubDate>
    </item>
    <item>
      <title>같은 코드를 여러 팀이 관리할 때 테스트 환경은 어떻게 제공해야할까?(feat Argo CD Pull Request generator)</title>
      <link>https://malwareanalysis.tistory.com/941</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;같은 코드를 여러 팀이 나눠 관리하면, 배포 전 코드를 검증할 테스트 환경을 어떻게 나눠 줄지가 문제가 됩니다. 이 글은 테스트 환경을 제공하는 세 가지 방법을 비교하고, 그중 PR 단위로 임시 환경을 만드는 방법을 ArgoCD Pull Request generator로 구현하는 원리와 실습을 다룹니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;여러 팀이 하나의 코드를 관리하는 상황&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 코드를 여러 팀이 관리하는 경우는 종종 있습니다. 거대한 소프트웨어에서 기능마다 담당 팀이 나뉘어 있는 경우, 또는 버전을 올리는 동안 v1을 유지보수하는 팀과 v2를 개발하는 팀이 같은 코드베이스를 함께 보는 경우가 그렇습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이 여러 팀에게 테스트 환경을 어떻게 나눠줘야 할까요?&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1672&quot; data-origin-height=&quot;941&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/doK4kj/dJMcahdY1Hf/IOQjKSyJ3ybvhgl0YJMrJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/doK4kj/dJMcahdY1Hf/IOQjKSyJ3ybvhgl0YJMrJK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/doK4kj/dJMcahdY1Hf/IOQjKSyJ3ybvhgl0YJMrJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdoK4kj%2FdJMcahdY1Hf%2FIOQjKSyJ3ybvhgl0YJMrJK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1672&quot; height=&quot;941&quot; data-origin-width=&quot;1672&quot; data-origin-height=&quot;941&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;(경험담) 테스트 환경을 나누는 세 가지 방법&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;팀마다 별도 환경 제공&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 단순하고 이상적인 방법은 팀마다 독립된 테스트 환경(또는 물리 서버)을 주는 방식입니다. A팀에게는 A 환경을, B팀에게는 B 환경을 그대로 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제는 비용입니다.&lt;/b&gt; 팀이나 기능이 늘어날 때마다 서버와 네트워크를 함께 늘려야 해서, 비용이 팀 수에 비례해 커집니다. 네트워크도 물리적으로 분리해야 하므로 관리 포인트도 같이 늘어납니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;하나의 환경에서 배포를 순서대로 조율&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;환경은 하나만 두고, 그 환경에 어떤 팀의 코드를 배포할지 시간을 나눠 조율하는 방법도 있습니다. CPU의 time slice와 같은 방식으로, 환경을 쓸 시간만 정하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 기능 배포가 잦아지면 팀들이 환경을 쓰기 위해 대기하는 시간이 늘어나고, 그만큼 기능 출시도 늦어집니다. 배포 순서를 둘러싼 팀 간 조율 비용도 함께 커집니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1672&quot; data-origin-height=&quot;941&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mK2bg/dJMcabSiz4q/ruKQZRkapLmk5apH4v7tH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mK2bg/dJMcabSiz4q/ruKQZRkapLmk5apH4v7tH0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mK2bg/dJMcabSiz4q/ruKQZRkapLmk5apH4v7tH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmK2bg%2FdJMcabSiz4q%2FruKQZRkapLmk5apH4v7tH0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1672&quot; height=&quot;941&quot; data-origin-width=&quot;1672&quot; data-origin-height=&quot;941&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;PR 이벤트가 발생할 때만 임시 환경 생성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아니면 git branch를 merge하기 위한 이벤트, 예를 들어 GitHub PR이 열릴 때마다 그 코드를 위한 테스트 환경을 임시로 만드는 방법도 있습니다. PR이 생성되면 임시 환경이 만들어지고, PR이 close되면 그 환경은 사라집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;임시 환경을 만들 때는 workload와 네트워크를 PR 단위로 나눠야 합니다. 쿠버네티스라면 PR마다 Pod을 분리하고, 그 Pod을 호출할 엔드포인트도 따로 둡니다. 인프런 사례는 HTTP 헤더로 요청을 구분해 임시로 만든 Pod에만 트래픽이 가도록 분기했고, 네트워크 분기는 linkerd 서비스 메시로 처리했습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인프런 사례: &lt;a href=&quot;https://tech.inflab.com/20251121-pr-preview/&quot;&gt;https://tech.inflab.com/20251121-pr-preview/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;ArgoCD Pull Request generator로 PR마다 Application 만들기&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;원리: ApplicationSet의 generator&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ArgoCD Pull request generator는 ApplicationSet의 generator 중 하나입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Pull request generator의 핵심은 PR의 open/close 이벤트에 맞춰 Application의 생성과 삭제까지 이 generator가 자동으로 처리한다는 점입니다.&lt;/b&gt; PR이 열리면 그 PR을 위한 ArgoCD Application이 생기고, PR이 close되면 해당 Application이 사라집니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1182&quot; data-origin-height=&quot;294&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uPVPi/dJMcafHfMY0/oLHuqHMtue2WZ7x4BqhzrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uPVPi/dJMcafHfMY0/oLHuqHMtue2WZ7x4BqhzrK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uPVPi/dJMcafHfMY0/oLHuqHMtue2WZ7x4BqhzrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuPVPi%2FdJMcafHfMY0%2FoLHuqHMtue2WZ7x4BqhzrK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1182&quot; height=&quot;294&quot; data-origin-width=&quot;1182&quot; data-origin-height=&quot;294&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pullRequest를 설정하는 방법은 ApplicationSet에서 pullRequest 필드 를 사용하면 됩니다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: example
  namespace: argocd
spec:
  generators:
  - pullRequest:
...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;원하는 PR대상 지정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pullRequest generator만 켜두면 열려 있는 모든 PR이 감시 대상이 됩니다. 팀에서 여는 모든 PR마다 테스트 환경이 생기는 셈이라, 원치 않는 리소스가 계속 쌓일 수 있습니다. &lt;b&gt;이를 막으려면 GitHub label로 대상을 제한하면 됩니다.&lt;/b&gt; 아래 설정은 argocd-preview label이 붙은 PR만 골라서 감시합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3674&quot; data-origin-height=&quot;1262&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kwEmR/dJMcabdE71J/K64cC9XSmsCKcoceLUvv70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kwEmR/dJMcabdE71J/K64cC9XSmsCKcoceLUvv70/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kwEmR/dJMcabdE71J/K64cC9XSmsCKcoceLUvv70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkwEmR%2FdJMcabdE71J%2FK64cC9XSmsCKcoceLUvv70%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3674&quot; height=&quot;1262&quot; data-origin-width=&quot;3674&quot; data-origin-height=&quot;1262&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ApplicationSet 설정은 pullRequest.github.labels 필드입니다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: example
  namespace: argocd
spec:
  generators:
  - pullRequest:
      github:
        labels:
        - &quot;&amp;lt;PULL_REQUEST_LABEL&amp;gt;&quot;
      ...&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1122&quot; data-origin-height=&quot;268&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVoI5n/dJMcahdY1Dh/a07REnK8kzR3M6yuRKVR81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVoI5n/dJMcahdY1Dh/a07REnK8kzR3M6yuRKVR81/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVoI5n/dJMcahdY1Dh/a07REnK8kzR3M6yuRKVR81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVoI5n%2FdJMcahdY1Dh%2Fa07REnK8kzR3M6yuRKVR81%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1122&quot; height=&quot;268&quot; data-origin-width=&quot;1122&quot; data-origin-height=&quot;268&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;동작 확인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ApplicationSet을 생성하면 아래처럼 ApplicationSet 리소스가 보입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1820&quot; data-origin-height=&quot;830&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Jvfiv/dJMcahdY1Dr/IuUWB3lY0Fr2xPnxqxAFA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Jvfiv/dJMcahdY1Dr/IuUWB3lY0Fr2xPnxqxAFA1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Jvfiv/dJMcahdY1Dr/IuUWB3lY0Fr2xPnxqxAFA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJvfiv%2FdJMcahdY1Dr%2FIuUWB3lY0Fr2xPnxqxAFA1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1820&quot; height=&quot;830&quot; data-origin-width=&quot;1820&quot; data-origin-height=&quot;830&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;argocd-preview label이 붙은 PR이 열리면, ApplicationSet이 PR 번호를 이름에 포함한 ArgoCD Application을 생성합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1710&quot; data-origin-height=&quot;1258&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cbnm2l/dJMcacDL8nB/p0AkRlZ7FjWsa2eYbLV1RK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cbnm2l/dJMcacDL8nB/p0AkRlZ7FjWsa2eYbLV1RK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cbnm2l/dJMcacDL8nB/p0AkRlZ7FjWsa2eYbLV1RK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcbnm2l%2FdJMcacDL8nB%2Fp0AkRlZ7FjWsa2eYbLV1RK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1710&quot; height=&quot;1258&quot; data-origin-width=&quot;1710&quot; data-origin-height=&quot;1258&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PR이 close되면 생성됐던 Application이 함께 삭제됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3730&quot; data-origin-height=&quot;1112&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/t5omH/dJMcaccDl7X/7mJPXqXOiPtnptrTK0mzpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/t5omH/dJMcaccDl7X/7mJPXqXOiPtnptrTK0mzpk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/t5omH/dJMcaccDl7X/7mJPXqXOiPtnptrTK0mzpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ft5omH%2FdJMcaccDl7X%2F7mJPXqXOiPtnptrTK0mzpk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3730&quot; height=&quot;1112&quot; data-origin-width=&quot;3730&quot; data-origin-height=&quot;1112&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실습&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실습 코드는 github에 공개돼 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;github: &lt;a href=&quot;https://github.com/choisungwook/portfolio/tree/master/kubernetes/argocd/pull_request_generator&quot;&gt;https://github.com/choisungwook/portfolio/tree/master/kubernetes/argocd/pull_request_generator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;실습 환경: kind cluster&lt;/li&gt;
&lt;li&gt;네트워크 분리: istio ambient mode로 HTTP 헤더 분리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;적용한다면 고민할 점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Argo CD Pull Request Generator를  실제 적용한다면 몇 가지를 고민할 수 있습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;PR이 증가할 수록 그만큼 물리적인 노드수가 필요합니다. 또는 노드를 자동으로 증가시키는 노드 auto scaler를 고민해야합니다.&lt;/li&gt;
&lt;li&gt;ArgoCD 장애로 삭제 동기화가 밀리면, 닫힌 PR의 환경이 그대로 남아 있을 수 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;참고자료&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://tech.inflab.com/20251121-pr-preview/&quot;&gt;PR 환경마다 미리보기 제공하기 - 인프랩 기술 블로그&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>전공영역 공부 기록</category>
      <category>ArgoCD</category>
      <category>kubernetes</category>
      <author>악분</author>
      <guid isPermaLink="true">https://malwareanalysis.tistory.com/941</guid>
      <comments>https://malwareanalysis.tistory.com/941#entry941comment</comments>
      <pubDate>Sat, 4 Jul 2026 21:52:36 +0900</pubDate>
    </item>
    <item>
      <title>ArgoCD private repo 연동: Github App이 PAT보다 안전한 이유</title>
      <link>https://malwareanalysis.tistory.com/940</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이 글은 PAT 대신 Github App으로 연동하는 방법과, 왜 Github App이 PAT보다 덜 위험한지 그 원리를 정리합니다. 원리를 알면 &amp;ldquo;Github App을 쓰면 안전하다&amp;rdquo;는 말을 그대로 믿지 않고, 무엇이 안전해지고 무엇이 여전히 위험한지 직접 판단할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ArgoCD에서 private github repo를 연동하려면 보통 Github PAT(Personal Access Token)를 씁니다. &lt;b&gt;그런데 PAT는 한 번 발급하면 만료가 없는 영구 자격증명이라, 유출되면 그대로 보안사고로 이어집니다.&lt;/b&gt; 영구자격증명이 아닌 임시자격증명을 사용하는 Github APP을 ArgoCD에도 사용할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2270&quot; data-origin-height=&quot;2218&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eoO0oN/dJMcaglHXJ2/mSmkwyMvzrKcdZLvadNNJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eoO0oN/dJMcaglHXJ2/mSmkwyMvzrKcdZLvadNNJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eoO0oN/dJMcaglHXJ2/mSmkwyMvzrKcdZLvadNNJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeoO0oN%2FdJMcaglHXJ2%2FmSmkwyMvzrKcdZLvadNNJ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2270&quot; height=&quot;2218&quot; data-origin-width=&quot;2270&quot; data-origin-height=&quot;2218&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;Github App은 무엇으로 repo에 접근하는가&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PAT는 github user에 묶인 영구 토큰입니다. 토큰 하나로 그 user가 가진 모든 권한에 접근할 수 있고, 만료 시점을 따로 지정하지 않으면 계속 살아 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Github App은 접근 방식이 다릅니다. App은 private key를 가지고 있고, github repo에 직접 접근할 때는 &lt;b&gt;이 private key로 만든 임시 access token을 씁니다.&lt;/b&gt; 흐름은 다음과 같습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Github App이 private key로 JWT(JSON Web Token)에 서명합니다. JWT에는 App ID와 발급 시각이 담깁니다.&lt;/li&gt;
&lt;li&gt;이 JWT를 github에 보내 installation access token을 받습니다.&lt;/li&gt;
&lt;li&gt;이 installation access token으로 repo에 접근합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;Github App이 PAT보다 안전한가&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;먼저 분명히 할 점은, Github App도 완전히 안전하지 않습니다.&lt;/b&gt; App이 임시 토큰을 받기 위해 쓰는 private key가 유출되면, 공격자가 그 key로 JWT를 만들어 똑같이 임시 토큰을 받을 수 있습니다. 결국 보호해야 할 자격증명이 PAT에서 private key로 바뀌었을 뿐입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 Github App이 PAT보다 덜 위험한 이유는 세 가지라고 생각합니다.&lt;br /&gt;- 권한 범위가 다릅니다. PAT는 github user의 권한을 따라가지만, Github App은 App 자체에 부여한 권한만 가집니다. 아래 설정처럼 Contents를 Read only로 묶으면, key가 유출돼도 공격자가 할 수 있는 일이 읽기로 제한됩니다.&lt;br /&gt;- revoke가 가능합니다. installation access token은 만료 전이라도 철회할 수 있고, App 자체를 정지시키면 이후 토큰 발급을 막을 수 있습니다.&lt;br /&gt;- IP 접근 제한을 걸 수 있습니다. private key를 쓸 수 있는 server IP를 제한하면, key가 유출돼도 허용된 IP가 아니면 토큰을 받지 못합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;ArgoCD Github App 설정 과정&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ArgoCD에서 Github App으로 repo를 연동하려면 두 가지가 필요합니다.&lt;br /&gt;1. Github App 생성&lt;br /&gt;2. Github App의 private key&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Github App 생성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Github App은 개인 계정 profile 또는 Github Org에서 생성할 수 있습니다. 이 글은 소개가 목적이라 개인 계정 profile을 예제로 썼습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;github profile 방문: &lt;a href=&quot;https://github.com/settings/profile&quot;&gt;https://github.com/settings/profile&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;왼쪽 메뉴에서 Developer settings 클릭&lt;/li&gt;
&lt;li&gt;Github Apps 생성&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3788&quot; data-origin-height=&quot;1306&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpyboJ/dJMcabSdGNL/X5FvndpitjQaokJS3ftBCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpyboJ/dJMcabSdGNL/X5FvndpitjQaokJS3ftBCk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpyboJ/dJMcabSdGNL/X5FvndpitjQaokJS3ftBCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpyboJ%2FdJMcabSdGNL%2FX5FvndpitjQaokJS3ftBCk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3788&quot; height=&quot;1306&quot; data-origin-width=&quot;3788&quot; data-origin-height=&quot;1306&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Homepage URL과 Callback URL에는 ArgoCD 주소를 입력합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1998&quot; data-origin-height=&quot;1800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6yFEY/dJMcagMRH8w/UFMkYHgZaUi6TThjY1Jp7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6yFEY/dJMcagMRH8w/UFMkYHgZaUi6TThjY1Jp7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6yFEY/dJMcagMRH8w/UFMkYHgZaUi6TThjY1Jp7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6yFEY%2FdJMcagMRH8w%2FUFMkYHgZaUi6TThjY1Jp7k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1998&quot; height=&quot;1800&quot; data-origin-width=&quot;1998&quot; data-origin-height=&quot;1800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;permission에서 Contents를 Read only로 설정합니다. ArgoCD는 repo의 manifest를 읽기만 하면 되므로, 쓰기 권한을 주지 않습니다. 앞서 설명한 대로, 권한을 좁히는 것이 Github App을 PAT보다 안전하게 만드는 첫 번째 요소입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2582&quot; data-origin-height=&quot;942&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oxs2T/dJMcagsuLyO/CaZyr86agCCvpdVaY19M3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oxs2T/dJMcagsuLyO/CaZyr86agCCvpdVaY19M3k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oxs2T/dJMcagsuLyO/CaZyr86agCCvpdVaY19M3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Foxs2T%2FdJMcagsuLyO%2FCaZyr86agCCvpdVaY19M3k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2582&quot; height=&quot;942&quot; data-origin-width=&quot;2582&quot; data-origin-height=&quot;942&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. private key 생성과 IP 접근 제한&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ArgoCD가 Github App을 쓰려면 private key 등록이 필요합니다. 이 private key는 App이 github에 임시 access token을 요청할 때 JWT에 서명하는 데 쓰입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;private key는 Github App 화면의 Generate a private key 기능으로 만듭니다. private key가 유출되면 PAT와 마찬가지로 보안사고가 나므로, ArgoCD server만 private key를 쓸 수 있도록 server IP를 제한합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2352&quot; data-origin-height=&quot;2132&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cg0dK7/dJMcac4Gj0q/yFvvC1H13y8KqLXrAzWyXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cg0dK7/dJMcac4Gj0q/yFvvC1H13y8KqLXrAzWyXk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cg0dK7/dJMcac4Gj0q/yFvvC1H13y8KqLXrAzWyXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcg0dK7%2FdJMcac4Gj0q%2FyFvvC1H13y8KqLXrAzWyXk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2352&quot; height=&quot;2132&quot; data-origin-width=&quot;2352&quot; data-origin-height=&quot;2132&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. Github repo에 Github App 연동&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;App을 만들었으면 접근할 repo를 선택해 설치(install)합니다. 이 글은 개인 계정 profile을 예제로 썼습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;github profile 방문: &lt;a href=&quot;https://github.com/settings/profile&quot;&gt;https://github.com/settings/profile&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;왼쪽 메뉴에서 Applications 클릭&lt;/li&gt;
&lt;li&gt;생성한 Github App의 Configure 버튼 클릭&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2628&quot; data-origin-height=&quot;2208&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xV1GW/dJMcagF5XhS/SR3Jhy8RcQf1z6WOWiP9P0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xV1GW/dJMcagF5XhS/SR3Jhy8RcQf1z6WOWiP9P0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xV1GW/dJMcagF5XhS/SR3Jhy8RcQf1z6WOWiP9P0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxV1GW%2FdJMcagF5XhS%2FSR3Jhy8RcQf1z6WOWiP9P0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2628&quot; height=&quot;2208&quot; data-origin-width=&quot;2628&quot; data-origin-height=&quot;2208&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. Review request 버튼 클릭&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2708&quot; data-origin-height=&quot;1236&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cou5by/dJMcagF5Xjm/H83Vi298CjJJp5cBg5rPG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cou5by/dJMcagF5Xjm/H83Vi298CjJJp5cBg5rPG0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cou5by/dJMcagF5Xjm/H83Vi298CjJJp5cBg5rPG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcou5by%2FdJMcagF5Xjm%2FH83Vi298CjJJp5cBg5rPG0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2708&quot; height=&quot;1236&quot; data-origin-width=&quot;2708&quot; data-origin-height=&quot;1236&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 연동할 github repo 선택&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1456&quot; data-origin-height=&quot;1602&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WpdRS/dJMcagF5Xlt/ewS7WdFNbLbZWYWHK3fL10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WpdRS/dJMcagF5Xlt/ewS7WdFNbLbZWYWHK3fL10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WpdRS/dJMcagF5Xlt/ewS7WdFNbLbZWYWHK3fL10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWpdRS%2FdJMcagF5Xlt%2FewS7WdFNbLbZWYWHK3fL10%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1456&quot; height=&quot;1602&quot; data-origin-width=&quot;1456&quot; data-origin-height=&quot;1602&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. ArgoCD에서 Github App으로 repo 등록&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ArgoCD UI에서 등록하거나 kubernetes secret으로 등록할 수 있습니다. 아래는 secret으로 등록하는 예시입니다. PAT 대신 githubAppPrivateKey를 넣는 점이 핵심이며, ArgoCD는 이 key로 앞서 설명한 JWT 서명과 임시 토큰 발급을 내부에서 처리합니다.&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Secret
metadata:
  name: github-app-repo-creds
  namespace: argocd
  labels:
    argocd.argoproj.io/secret-type: repo-creds
    app.kubernetes.io/name: github-app-repo-creds
    app.kubernetes.io/part-of: pull-request-generator-demo
type: Opaque
stringData:
  type: git
  url: https://github.com//.git
  githubAppID: &quot;&amp;lt;GITHUB_APP_ID&amp;gt;&quot;                          # App 화면 상단의 App ID
  githubAppInstallationID: &quot;&amp;lt;GITHUB_APP_INSTALLATION_ID&amp;gt;&quot; # repo에 install할 때 부여되는 ID
  githubAppPrivateKey: |
    -----BEGIN PRIVATE KEY-----
    &amp;lt;GITHUB_APP_PRIVATE_KEY&amp;gt;
    -----END PRIVATE KEY-----&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;참고자료&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/token-expiration-and-revocation&quot;&gt;Token expiration and revocation - GitHub Docs&lt;/a&gt; (installation access token은 발급 후 1시간 만료)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-an-installation-access-token-for-a-github-app&quot;&gt;Generating an installation access token for a GitHub App - GitHub Docs&lt;/a&gt; (JWT 서명 -&amp;gt; installation access token 발급 흐름)&lt;/li&gt;
&lt;li&gt;확인 필요: ArgoCD 공식 docs의 Github App credential(repo-creds) 등록 문서&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>전공영역 공부 기록</category>
      <category>ArgoCD</category>
      <category>github</category>
      <category>security</category>
      <author>악분</author>
      <guid isPermaLink="true">https://malwareanalysis.tistory.com/940</guid>
      <comments>https://malwareanalysis.tistory.com/940#entry940comment</comments>
      <pubDate>Mon, 29 Jun 2026 00:08:51 +0900</pubDate>
    </item>
    <item>
      <title>AWS CodeBuild에서 Github 토큰도 없이 어떻게 private repository를 clone할까?</title>
      <link>https://malwareanalysis.tistory.com/939</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 Codebuild가 Github access token없이  GitHub private repository에 접근하는 방법을 설명합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;Codebuild Connection&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS CodeBuild에서 GitHub private repository를 source로 사용하려면, CodeBuild가 그 repository를 읽을 수 있어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방법은 크게 두 가지로 나뉩니다. 하나는 GitHub Personal Access Token(PAT)처럼 오래 유지되는 자격증명을 CodeBuild에 등록하는 방식이고, 다른 하나는 GitHub App connection을 만들어 AWS CodeConnections가 repository 접근을 중개하게 하는 방식입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xpI4J/dJMcaff1X5p/PPh1OkKT7qtdDAqoo5jcU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xpI4J/dJMcaff1X5p/PPh1OkKT7qtdDAqoo5jcU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xpI4J/dJMcaff1X5p/PPh1OkKT7qtdDAqoo5jcU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxpI4J%2FdJMcaff1X5p%2FPPh1OkKT7qtdDAqoo5jcU1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1448&quot; height=&quot;1086&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Github App connection은 Codebuild Connection메뉴에서 생성할 수 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4862&quot; data-origin-height=&quot;1356&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tUt8G/dJMcaaeHWxU/3JYPqXhWFKkrdUR2ImQsf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tUt8G/dJMcaaeHWxU/3JYPqXhWFKkrdUR2ImQsf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tUt8G/dJMcaaeHWxU/3JYPqXhWFKkrdUR2ImQsf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtUt8G%2FdJMcaaeHWxU%2F3JYPqXhWFKkrdUR2ImQsf0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;4862&quot; height=&quot;1356&quot; data-origin-width=&quot;4862&quot; data-origin-height=&quot;1356&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2228&quot; data-origin-height=&quot;1342&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TTBle/dJMcaay0GgG/BkNtFKNebTU2Tx1RqGplb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TTBle/dJMcaay0GgG/BkNtFKNebTU2Tx1RqGplb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TTBle/dJMcaay0GgG/BkNtFKNebTU2Tx1RqGplb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTTBle%2FdJMcaay0GgG%2FBkNtFKNebTU2Tx1RqGplb0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2228&quot; height=&quot;1342&quot; data-origin-width=&quot;2228&quot; data-origin-height=&quot;1342&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;CodeBuild는 GitHub 토큰을 어디서 받아오나요?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심부터 말하면, AWS CodeConnections는 PAT를 저장해두고 쓰는 게 아닙니다. &lt;b&gt;GitHub App의 installation access token(단기 토큰)을 그때그때 발급받아 씁니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 정리한 흐름은 이렇습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;connection을 만들 때 GitHub에 AWS Connector for GitHub 앱을 설치(install)합니다. 이때 제 계정/org에 installation이 하나 생기고, AWS는 그 installation ID와 connection을 묶어둡니다.&lt;/li&gt;
&lt;li&gt;이 GitHub App에는 private key가 있는데, 이건 제가 아니라 AWS(CodeConnections 서비스)가 보관합니다. 그래서 저는 토큰이나 키를 손에 쥐지 않습니다.&lt;/li&gt;
&lt;li&gt;CodeBuild가 repository에 접근해야 할 때, CodeConnections가 그 private key로 JWT를 서명해서 앱 자체를 인증하고, GitHub에 POST /app/installations/{id}/access_tokens를 호출합니다.&lt;/li&gt;
&lt;li&gt;GitHub가 installation access token을 돌려줍니다. 이게 임시 자격증명입니다. TTL이 약 1시간이고, 앱이 설치된 특정 repository로만 scope가 제한됩니다.&lt;/li&gt;
&lt;li&gt;CodeBuild가 이 토큰을 git credential로 써서 clone합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 흐름을 이해하려면 먼저 한 가지를 짚어야 합니다. 키를 AWS가 들고 있다는 GitHub App은 대체 무엇일까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;그래서 GitHub App이 뭔가요?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GitHub App의 핵심은 &lt;b&gt;&amp;ldquo;앱 자체가 하나의 독립된 행위자(actor)&amp;rdquo;&lt;/b&gt; 라는 점입니다. 사람 계정에 묶인 PAT와 달리, 앱이 자기 신원으로 행동합니다. 그래서 사람 입력이 필요 없는 자동화 워크플로에 잘 맞습니다. 특징을 정리하면 이렇습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;앱을 org/계정에 설치(install)하면, 그 설치 단위마다 installation ID가 생깁니다.&lt;/li&gt;
&lt;li&gt;권한을 repository 단위, 작업 단위(코드 읽기/쓰기, webhook 등)로 잘게 제한할 수 있습니다.&lt;/li&gt;
&lt;li&gt;장기 토큰을 들고 다니지 않고, 그때그때 단기 토큰을 발급받습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CodeConnections가 쓰는 AWS Connector for GitHub가 바로 이 GitHub App이고, GitHub Marketplace에서 게시자 신원이 검증된 앱입니다. 즉 제가 connection을 만들 때 한 일은, 사실 &lt;b&gt;AWS가 만들어둔 GitHub App을 제 GitHub 계정에 설치해준 것&lt;/b&gt;이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1734&quot; data-origin-height=&quot;1114&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dmzzP8/dJMcaa6RDUe/evLtkJnjngsz3iUkIfghD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dmzzP8/dJMcaa6RDUe/evLtkJnjngsz3iUkIfghD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dmzzP8/dJMcaa6RDUe/evLtkJnjngsz3iUkIfghD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdmzzP8%2FdJMcaa6RDUe%2FevLtkJnjngsz3iUkIfghD0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1734&quot; height=&quot;1114&quot; data-origin-width=&quot;1734&quot; data-origin-height=&quot;1114&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;private key를 AWS가 보관한다는 건 어떻게 알 수 있나요?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;솔직하게 말하면, AWS가 &amp;ldquo;우리가 private key를 보관한다&amp;rdquo;고 명시한 문장은 저도 못 찾았습니다. 이건 제가 GitHub App의 동작 원리에서 역추론한 부분이라, 단정이 아니라 &amp;ldquo;구조상 그렇게 될 수밖에 없다&amp;rdquo;가 맞는 표현입니다. 근거는 두 가지였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫째, GitHub 공식 문서상 installation access token을 만들려면 반드시 JWT를 RS256으로 서명해야 하고, 서명에는 &lt;b&gt;앱의 private key가 필요&lt;/b&gt;합니다. 둘째, CodeConnections 설치 흐름에서 고객인 저는 .pem private key를 받지 않습니다. 앱을 install하고 connection ARN을 받는 게 전부였고, 어디에도 키를 다운로드하는 단계가 없었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 둘을 합치면 결론이 나옵니다. JWT 서명은 제 손이 아니라 앱 소유자(AWS) 쪽 백엔드에서 일어날 수밖에 없습니다. GitHub App을 만들면 GitHub가 키 쌍을 생성하고 앱 소유자에게 private key를 .pem으로 딱 한 번 내려주는데, AWS Connector for GitHub의 소유자는 AWS이므로 그 키는 AWS가 자기 보안 인프라(보통 KMS/HSM)에 보관합니다. 앱을 설치한 저는 그 키를 절대 볼 수 없고, 제가 가진 통제권은 &amp;ldquo;어느 repository에 접근을 허용할지&amp;rdquo;뿐이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;Github app은 어떻게 Github에게 인증을 받나요?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Github app은 자기 private key로 JWT를 직접 만들어 서명합니다. 그 JWT를 GitHub에 보내서 &amp;ldquo;나 이 앱 맞다&amp;rdquo;고 증명하면, GitHub가 보관 중인 public key로 서명을 검증합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 안전한지 정리하면, private key는 백엔드 밖으로 절대 안 나가고 네트워크에는 서명된 JWT만 오갑니다. 그리고 이 JWT는 수명이 &lt;b&gt;최대 10분&lt;/b&gt;으로 아주 짧고, JWT 자체로는 repository에 접근하지도 못합니다. JWT는 &amp;ldquo;앱 인증&amp;rdquo; 용도일 뿐이고, 이걸로 다시 repository scope가 걸린 installation access token(약 1시간)을 받아야 비로소 clone이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;AWS 쪽에서 토큰을 막 받아올 수 있나요?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아닙니다. 여기에 제가 놓치기 쉬웠던 한 겹이 더 있었습니다. &lt;b&gt;&amp;ldquo;CodeBuild가 이 connection 토큰을 받을 자격이 있나&amp;rdquo;를 AWS IAM이 한 번 더 통제&lt;/b&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CodeBuild project는 service role로 실행되는데, 이 role에 connection 사용 권한이 없으면 임시 GitHub 토큰을 받는 단계까지 가지도 못합니다. 즉 GitHub가 토큰을 발급하기 전에 AWS IAM 인가가 한 겹 먼저 있는 구조입니다. 저는 아래처럼 connection ARN으로 범위를 좁힌 권한만 줬습니다.&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;data &quot;aws_iam_policy_document&quot; &quot;codebuild_connection&quot; {
  statement {
    effect = &quot;Allow&quot;

    actions = [
      &quot;codeconnections:GetConnection&quot;,
      &quot;codeconnections:GetConnectionToken&quot;,
      &quot;codeconnections:UseConnection&quot;,
    ]

    resources = [var.github_connection_arn]
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CodeBuild project source에는 clone 대상 repository URL과 connection ARN을 함께 넣습니다. URL은 &amp;ldquo;무엇을 clone할지&amp;rdquo;, connection ARN은 &amp;ldquo;어떤 인증 경로로 접근할지&amp;rdquo;를 정하기 때문에, 둘 중 하나만 있어서는 private repository clone이 동작하지 않습니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;resource &quot;aws_codebuild_project&quot; &quot;github_app&quot; {
  name         = var.project_name
  service_role = aws_iam_role.codebuild.arn

  source {
    type            = &quot;GITHUB&quot;
    location        = var.github_repository_url
    git_clone_depth = 1

    auth {
      type     = &quot;CODECONNECTIONS&quot;
      resource = var.github_connection_arn
    }
  }

  source_version = var.github_branch
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 connection 상태가 AVAILABLE이어도 GitHub App installation에 대상 repository가 빠져 있으면 clone이 실패하고, 반대로 repository URL이 맞아도 service role이 connection을 못 쓰면 GitHub source에 접근하지 못합니다. 인증 경로가 GitHub 쪽과 AWS 쪽에서 각각 한 번씩 걸러지는 셈입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;실습&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실습은 테라폼으로 진행했고 저의 github에 정리했습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;github: &lt;a href=&quot;https://github.com/choisungwook/portfolio/tree/master/aws/codebuild/github_connection&quot;&gt;https://github.com/choisungwook/portfolio/tree/master/aws/codebuild/github_connection&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;정리하면&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Codebuild connection의 임시 자격증명의 출처는 &lt;b&gt;GitHub App private key(AWS 보관) &amp;rarr; JWT 서명 &amp;rarr; GitHub가 발급한 1시간짜리 installation token&lt;/b&gt;이고, 그걸 받을 자격은 &lt;b&gt;CodeBuild의 IAM role&lt;/b&gt;이 결정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PAT 방식과의 차이가 여기서 갈립니다. PAT는 장기 토큰을 사람이 직접 들고 있어야 하지만, GitHub App connection은 단기 토큰 입니다. 그래서 토큰 유출 위험과 권한 범위가 훨씬 작아집니다. 새 CodeBuild-GitHub 연동을 한다면 저는 GitHub App connection을 먼저 검토할 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;참고자료&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.aws.amazon.com/codebuild/latest/userguide/connections-github-app.html&quot;&gt;AWS CodeBuild - GitHub App connections for GitHub and GitHub Enterprise Server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://aws.amazon.com/about-aws/whats-new/2024/08/aws-codebuild-github-apps-access-source-repositories/&quot;&gt;AWS - CodeBuild now supports accessing source repositories using GitHub Apps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/about-authentication-with-a-github-app&quot;&gt;GitHub Docs - About authentication with a GitHub App&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-json-web-token-jwt-for-a-github-app&quot;&gt;GitHub Docs - Generating a JSON Web Token (JWT) for a GitHub App&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-an-installation-access-token-for-a-github-app&quot;&gt;GitHub Docs - Generating an installation access token for a GitHub App&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/marketplace/aws-connector-for-github&quot;&gt;AWS Connector for GitHub (Marketplace)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>전공영역 공부 기록</category>
      <category>aws</category>
      <category>security</category>
      <author>악분</author>
      <guid isPermaLink="true">https://malwareanalysis.tistory.com/939</guid>
      <comments>https://malwareanalysis.tistory.com/939#entry939comment</comments>
      <pubDate>Tue, 23 Jun 2026 00:19:26 +0900</pubDate>
    </item>
    <item>
      <title>kubernetes에서 NVIDIA GPU를 여러 pod가 함께 쓰는 방법</title>
      <link>https://malwareanalysis.tistory.com/938</link>
      <description>&lt;h1 id=&quot;tldr&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;TL;DR&lt;/h1&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;kubernetes에서 NVIDIA GPU를 사용하는 기본 방식은 pod 하나가 GPU 1장을 요청하는 방식입니다. 이때&lt;span&gt;&amp;nbsp;&lt;/span&gt;nvidia.com/gpu: 1은 아래처럼&lt;span&gt;&amp;nbsp;&lt;/span&gt;resources.limits&lt;span&gt;&amp;nbsp;&lt;/span&gt;아래에 적습니다.&lt;/p&gt;
&lt;div id=&quot;cb1&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;resources:
  limits:
    nvidia.com/gpu: 1&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;여러 pod가 같은 NVIDIA GPU를 함께 쓰려면 NVIDIA GPU 공유 방식을 따로 설정&lt;/b&gt;해야 합니다. 대표적인 선택지는 세 가지입니다.&lt;br /&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;MIG&lt;/b&gt;: GPU를 하드웨어 레벨에서 여러 GPU instance로 나눕니다. 격리가 가장 좋지만, 지원 GPU와 profile 제약이 있습니다.&lt;br /&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;time-slicing&lt;/b&gt;: GPU 시간을 여러 pod가 나눠 씁니다. 설정은 비교적 쉽지만, 메모리 격리와 장애 격리가 없습니다.&lt;br /&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;MPS&lt;/b&gt;: CUDA Multi-Process Service로 GPU를 공유합니다. time-slicing보다 메모리와 compute 사용량을 더 제한할 수 있지만, kubernetes device plugin 기준으로는 운영 전 지원 상태 확인이 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;기본값-gpu-1장은-pod-하나에-할당된다&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;기본값: GPU 1장은 pod 하나에 할당된다&lt;/h1&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;NVIDIA device plugin을 설치하면 노드의 GPU가&lt;span&gt;&amp;nbsp;&lt;/span&gt;nvidia.com/gpu&lt;span&gt;&amp;nbsp;&lt;/span&gt;리소스로 노출됩니다. pod가 아래처럼 GPU 1개를 요청하면 scheduler는 GPU가 남아 있는 노드에 pod를 배치합니다.&lt;/p&gt;
&lt;div id=&quot;cb2&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  name: gpu-test
spec:
  containers:
    - name: cuda
      image: nvcr.io/nvidia/k8s/cuda-sample:vectoradd-cuda12.5.0
      resources:
        limits:
          nvidia.com/gpu: 1&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1612&quot; data-origin-height=&quot;1336&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AZfR1/dJMcahY37tM/Z75D775m6RMSZ39XbeLKPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AZfR1/dJMcahY37tM/Z75D775m6RMSZ39XbeLKPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AZfR1/dJMcahY37tM/Z75D775m6RMSZ39XbeLKPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAZfR1%2FdJMcahY37tM%2FZ75D775m6RMSZ39XbeLKPk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1612&quot; height=&quot;1336&quot; data-origin-width=&quot;1612&quot; data-origin-height=&quot;1336&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 방식의 장점은 단순함입니다. pod가 GPU 1장을 독점하므로 성능 예측이 쉽고, 다른 pod의 GPU 메모리 사용량이나 커널 실행이 내 workload에 직접 끼어들 가능성이 낮습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;단점은 활용률입니다. GPU 1장을 요청한 pod가 실제로는 GPU를 10~20%만 사용해도, kubernetes 관점에서는 GPU 1개가 이미 소비된 상태입니다. 작은 inference 서버, notebook, 개발용 workload처럼 GPU를 계속 꽉 채우지 않는 작업에서는 낭비가 커질 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;방법-1-mig로-gpu를-하드웨어-레벨에서-나누기&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;방법 1: MIG로 GPU를 하드웨어 레벨에서 나누기&lt;/h1&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;MIG는 Multi-Instance GPU의 약자입니다. NVIDIA Ampere 이후 일부 GPU에서 사용할 수 있는 기능이며, GPU 1장을 여러 개의 독립적인 GPU instance로 나눕니다. 각 instance는 정해진 compute slice와 memory slice를 가집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 H100 또는 A100 계열 GPU는&lt;span&gt;&amp;nbsp;&lt;/span&gt;1g.10gb,&lt;span&gt;&amp;nbsp;&lt;/span&gt;2g.20gb,&lt;span&gt;&amp;nbsp;&lt;/span&gt;3g.40gb&lt;span&gt;&amp;nbsp;&lt;/span&gt;같은 profile로 나눌 수 있습니다. 정확한 profile 이름과 개수는 GPU 모델, 드라이버, GPU Operator 버전에 따라 달라지므로 운영 전에 확인 필요입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1636&quot; data-origin-height=&quot;1342&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Kfz9c/dJMcagsk06B/iFfKqXf42pJTqjvoYEV3Yk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Kfz9c/dJMcagsk06B/iFfKqXf42pJTqjvoYEV3Yk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Kfz9c/dJMcagsk06B/iFfKqXf42pJTqjvoYEV3Yk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKfz9c%2FdJMcagsk06B%2FiFfKqXf42pJTqjvoYEV3Yk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1636&quot; height=&quot;1342&quot; data-origin-width=&quot;1636&quot; data-origin-height=&quot;1342&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;MIG의 핵심은 격리입니다. MIG instance는 하드웨어 레벨에서 메모리와 장애 영역을 나눕니다. 한 pod가 특정 MIG instance를 사용하더라도 다른 MIG instance를 쓰는 pod와 메모리 공간을 공유하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;MIG는 이런 상황에 잘 맞습니다.&lt;br /&gt;- 여러 팀이나 여러 서비스가 같은 물리 GPU를 나눠 써야 한다.&lt;br /&gt;- pod 간 성능 간섭을 줄여야 한다.&lt;br /&gt;- GPU memory 한도를 강하게 나눠야 한다.&lt;br /&gt;- 개발용 공유보다 운영 workload의 예측 가능성이 더 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;반대로 단점도 있습니다.&lt;br /&gt;- MIG를 지원하는 GPU에서만 사용할 수 있습니다.&lt;br /&gt;- profile이 정해져 있어서 workload 크기에 딱 맞지 않으면 자투리 리소스가 생깁니다.&lt;br /&gt;- MIG 구성을 바꿀 때 GPU workload를 비우거나 노드를 재시작해야 하는 경우가 있습니다.&lt;br /&gt;- 작은 pod를 아주 많이 올리는 용도에는 time-slicing보다 유연성이 낮을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;정리하면 MIG는 &amp;ldquo;GPU를 작은 GPU 여러 개로 쪼갠다&amp;rdquo;에 가깝습니다. 여러 pod가 같은 물리 GPU를 쓰지만, 각 pod는 자기에게 할당된 MIG instance를 GPU처럼 봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;방법-2-time-slicing으로-gpu-시간을-나눠-쓰기&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;방법 2: time-slicing으로 GPU 시간을 나눠 쓰기&lt;/h1&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;time-slicing은 GPU 1장을 시간 단위로 나눠 여러 pod가 번갈아 쓰게 하는 방식입니다. CPU time-slicing과 비슷한 느낌으로 이해하면 됩니다. NVIDIA device plugin 또는 GPU Operator 설정에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;replicas&lt;span&gt;&amp;nbsp;&lt;/span&gt;값을 늘리면, kubernetes에는 실제 GPU 개수보다 더 많은 GPU 리소스가 보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 GPU 1장이 있는 노드에&lt;span&gt;&amp;nbsp;&lt;/span&gt;replicas: 4를 설정하면, kubernetes는 이 노드에&lt;span&gt;&amp;nbsp;&lt;/span&gt;nvidia.com/gpu&lt;span&gt;&amp;nbsp;&lt;/span&gt;리소스가 4개 있는 것처럼 볼 수 있습니다.&lt;/p&gt;
&lt;div id=&quot;cb3&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;version: v1
sharing:
  timeSlicing:
    resources:
      - name: nvidia.com/gpu
        replicas: 4&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2830&quot; data-origin-height=&quot;682&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r9XaC/dJMcacXL38k/0C3RvkIRHQSDW9ptfWsXyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r9XaC/dJMcacXL38k/0C3RvkIRHQSDW9ptfWsXyk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r9XaC/dJMcacXL38k/0C3RvkIRHQSDW9ptfWsXyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr9XaC%2FdJMcacXL38k%2F0C3RvkIRHQSDW9ptfWsXyk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2830&quot; height=&quot;682&quot; data-origin-width=&quot;2830&quot; data-origin-height=&quot;682&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;time-slicing의 장점은 유연성입니다. MIG를 지원하지 않는 오래된 GPU에서도 사용할 수 있고, GPU를 조금씩 쓰는 pod를 여러 개 올리기 쉽습니다. 개발용 notebook, 테스트용 inference, 낮은 트래픽의 내부 도구처럼 GPU를 항상 꽉 채우지 않는 workload에 잘 맞습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 time-slicing은 격리 방식이 아닙니다. NVIDIA 문서 기준으로 time-slicing은 MIG처럼 메모리 격리나 장애 격리를 제공하지 않습니다. 같은 GPU를 공유하는 pod들은 같은 GPU memory와 fault domain 안에서 실행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;또한&lt;span&gt;&amp;nbsp;&lt;/span&gt;nvidia.com/gpu: 2를 요청한다고 해서 compute 시간을 두 배로 보장받는 것도 아닙니다. time-slicing에서 replica는 &amp;ldquo;공유 GPU에 접근할 수 있는 자리&amp;rdquo;에 가깝습니다. 그래서 NVIDIA 문서에서는&lt;span&gt;&amp;nbsp;&lt;/span&gt;failRequestsGreaterThanOne=true&lt;span&gt;&amp;nbsp;&lt;/span&gt;설정으로 공유 GPU 요청을 1개로 제한하는 방식을 권장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;time-slicing은 이런 상황에 잘 맞습니다.&lt;br /&gt;- GPU를 조금씩 쓰는 pod가 많다.&lt;br /&gt;- 개발자 notebook이나 실험용 workload를 여러 개 올리고 싶다.&lt;br /&gt;- MIG를 지원하지 않는 GPU를 공유해야 한다.&lt;br /&gt;- 강한 격리보다 GPU 활용률이 더 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;주의할 점은 명확합니다.&lt;br /&gt;- 메모리 격리가 없습니다.&lt;br /&gt;- 한 workload의 장애가 같은 GPU를 공유하는 다른 workload에 영향을 줄 수 있습니다.&lt;br /&gt;- pod별 GPU metric을 정확히 나눠 보기 어려울 수 있습니다.&lt;br /&gt;- latency가 중요한 production inference에는 별도 부하 테스트가 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;방법-3-mps로-cuda-process를-함께-실행하기&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;방법 3: MPS로 CUDA process를 함께 실행하기&lt;/h1&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;MPS는 Multi-Process Service의 약자입니다. CUDA application 여러 개가 같은 GPU에서 더 효율적으로 동시에 실행되도록 돕는 NVIDIA 런타임 기능입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;time-slicing은 GPU 시간을 번갈아 나눠 쓰는 방식에 가깝습니다. 반면 MPS는 MPS control daemon이 GPU 접근을 관리하고, client별 memory와 compute capacity를 제한할 수 있습니다. NVIDIA device plugin 문서 기준으로 MPS는 각 replica가 전체 GPU memory와 compute capacity의 일정 비율을 사용하도록 제한할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4EQCz/dJMcaf7Zzt8/4cpBciRcFmvHwUdTT0Anq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4EQCz/dJMcaf7Zzt8/4cpBciRcFmvHwUdTT0Anq1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4EQCz/dJMcaf7Zzt8/4cpBciRcFmvHwUdTT0Anq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4EQCz%2FdJMcaf7Zzt8%2F4cpBciRcFmvHwUdTT0Anq1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1448&quot; height=&quot;1086&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;MPS는 이런 상황에 어울립니다.&lt;br /&gt;- 여러 CUDA process를 같은 GPU에서 동시에 실행하고 싶다.&lt;br /&gt;- time-slicing보다 memory와 compute 사용량을 더 명시적으로 제한하고 싶다.&lt;br /&gt;- workload가 CUDA MPS와 잘 맞는지 검증할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;다만 kubernetes에서 MPS를 선택할 때는 조심해야 합니다.&lt;br /&gt;- NVIDIA device plugin 문서 기준으로 MPS 지원은 experimental로 표시되어 있습니다.&lt;br /&gt;- MIG가 활성화된 GPU에서는 MPS sharing을 함께 사용할 수 없습니다.&lt;br /&gt;- 현재 문서 기준으로 MPS sharing은 full GPU의&lt;span&gt;&amp;nbsp;&lt;/span&gt;nvidia.com/gpu&lt;span&gt;&amp;nbsp;&lt;/span&gt;리소스에 대해서만 지원됩니다.&lt;br /&gt;- 노드 안의 GPU별로 서로 다른 sharing 방식을 섞는 구성은 지원되지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;따라서 MPS는 &amp;ldquo;운영 기본값&amp;rdquo;이라기보다, workload 특성을 알고 있고 직접 검증할 수 있을 때 선택하는 방식에 가깝습니다. production 적용 전에는 사용 중인 device plugin 버전, GPU 모델, CUDA workload 특성, 장애 격리 요구사항을 확인 필요입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;결론&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;결론&lt;/h1&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;kubernetes에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;nvidia.com/gpu: 1을 설정하는 것만으로는 GPU 공유가 되지 않습니다. 기본 설정은 pod가 GPU 1장을 요청하는 방식입니다. 여러 pod가 같은 NVIDIA GPU를 쓰게 하려면 MIG, time-slicing, MPS 중 하나를 선택하고 NVIDIA device plugin 또는 GPU Operator 설정을 바꿔야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;참고자료&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;참고자료&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Kubernetes 공식 문서, Schedule GPUs:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://kubernetes.io/docs/tasks/manage-gpus/scheduling-gpus/&quot;&gt;https://kubernetes.io/docs/tasks/manage-gpus/scheduling-gpus/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;NVIDIA k8s-device-plugin, Shared Access to GPUs:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://github.com/NVIDIA/k8s-device-plugin&quot;&gt;https://github.com/NVIDIA/k8s-device-plugin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;NVIDIA GPU Operator, Time-Slicing GPUs in Kubernetes:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/gpu-sharing.html&quot;&gt;https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/gpu-sharing.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;NVIDIA GPU Operator, GPU Operator with MIG:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/gpu-operator-mig.html&quot;&gt;https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/gpu-operator-mig.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;NVIDIA Multi-Instance GPU User Guide:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://docs.nvidia.com/datacenter/tesla/mig-user-guide/latest/&quot;&gt;https://docs.nvidia.com/datacenter/tesla/mig-user-guide/latest/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;NVIDIA Multi-Process Service:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://docs.nvidia.com/deploy/mps/latest/index.html&quot;&gt;https://docs.nvidia.com/deploy/mps/latest/index.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>전공영역 공부 기록</category>
      <category>GPU</category>
      <category>kubernetes</category>
      <author>악분</author>
      <guid isPermaLink="true">https://malwareanalysis.tistory.com/938</guid>
      <comments>https://malwareanalysis.tistory.com/938#entry938comment</comments>
      <pubDate>Mon, 15 Jun 2026 23:30:37 +0900</pubDate>
    </item>
    <item>
      <title>heremes agent discord연결 원리와 연결방법</title>
      <link>https://malwareanalysis.tistory.com/937</link>
      <description>&lt;h1&gt;요약&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Hermes Agent는 hermes gateway를 통해 Discord 같은 메시징 플랫폼과 연결합니다.&lt;/li&gt;
&lt;li&gt;Discord 쪽에서는 Discord Gateway의 WebSocket 연결로 메시지 이벤트를 받고, Hermes Agent가 만든 응답은 Discord로 다시 전달됩니다.&lt;/li&gt;
&lt;li&gt;실제 운영에서는 Bot token만으로 충분하지 않습니다. DISCORD_ALLOWED_USERS 또는 DISCORD_ALLOWED_ROLES로 누가 Hermes Agent를 사용할 수 있는지 제한해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;Hermes Agent와 Discord가 통신하는 원리&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Hermes Agent는 메시징 플랫폼과 직접 섞이지 않고 hermes gateway를 중간 계층으로 사용합니다&lt;/b&gt;. Hermes 공식 문서에 따르면 gateway는 여러 플랫폼 adapter를 실행하고, &lt;b&gt;adapter는 들어온 메시지&lt;/b&gt;를 per-chat session store로 라우팅한 뒤 AI Agent에 전달합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3756&quot; data-origin-height=&quot;1290&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lkroK/dJMcajig0My/mWPldsSB5MqnMSAJ21jBGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lkroK/dJMcajig0My/mWPldsSB5MqnMSAJ21jBGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lkroK/dJMcajig0My/mWPldsSB5MqnMSAJ21jBGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlkroK%2FdJMcajig0My%2FmWPldsSB5MqnMSAJ21jBGK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3756&quot; height=&quot;1290&quot; data-origin-width=&quot;3756&quot; data-origin-height=&quot;1290&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Discord는 Discord Gateway API를 통해 앱과 Discord 사이에 안전한 WebSocket 연결을 엽니다.&lt;/b&gt; 이 연결은 서버나 채널에서 발생한 이벤트를 앱이 실시간으로 받기 위한 통로입니다. 리소스 생성이나 메시지 전송 같은 대부분의 작업은 HTTP API를 함께 사용한다고 이해하면 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4552&quot; data-origin-height=&quot;782&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dLVfy6/dJMcaiRcruP/EFhKcadg89dGBNl1BINKUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dLVfy6/dJMcaiRcruP/EFhKcadg89dGBNl1BINKUK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dLVfy6/dJMcaiRcruP/EFhKcadg89dGBNl1BINKUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdLVfy6%2FdJMcaiRcruP%2FEFhKcadg89dGBNl1BINKUK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;4552&quot; height=&quot;782&quot; data-origin-width=&quot;4552&quot; data-origin-height=&quot;782&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hermes Agent를 Discord와 연결하면 메시지는 아래 흐름으로 처리됩니다.&lt;/p&gt;
&lt;pre class=&quot;xl&quot;&gt;&lt;code&gt;sequenceDiagram
  participant User as Discord 사용자
  participant Discord as Discord Gateway/API
  participant Gateway as Hermes gateway
  participant Agent as Hermes Agent

  User-&amp;gt;&amp;gt;Discord: Bot 멘션 또는 DM 전송
  Discord-&amp;gt;&amp;gt;Gateway: WebSocket 이벤트 전달
  Gateway-&amp;gt;&amp;gt;Gateway: 권한 확인과 session 조회
  Gateway-&amp;gt;&amp;gt;Agent: 메시지와 session context 전달
  Agent-&amp;gt;&amp;gt;Gateway: 응답 생성
  Gateway-&amp;gt;&amp;gt;Discord: 응답 전송
  Discord-&amp;gt;&amp;gt;User: 채널 또는 DM에 표시&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요한 점은 Hermes Agent의 Discord 연동이 stateless webhook이 아니라는 점입니다. Hermes는 권한 확인, 멘션 규칙, session 조회, transcript 로딩, tool 사용, memory, slash command 실행을 모두 거친 뒤 응답합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;Discord 연동 방법&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 절차는 &lt;a href=&quot;https://hermes-agent.nousresearch.com/docs/user-guide/messaging/discord&quot;&gt;Hermes Agent 공식 Discord 연동 문서&lt;/a&gt;를 기준으로 정리했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Discord 애플리케이션 생성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://discord.com/developers/&quot;&gt;Discord Developer Portal&lt;/a&gt;에 접속합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오른쪽 위의 New Application을 클릭하고, Hermes Agent와 연결할 Discord application을 생성합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2714&quot; data-origin-height=&quot;1264&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/niFjY/dJMcafUwk52/iJ7szhBwwCIjvN8ZaNK7x0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/niFjY/dJMcafUwk52/iJ7szhBwwCIjvN8ZaNK7x0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/niFjY/dJMcafUwk52/iJ7szhBwwCIjvN8ZaNK7x0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FniFjY%2FdJMcafUwk52%2FiJ7szhBwwCIjvN8ZaNK7x0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2714&quot; height=&quot;1264&quot; data-origin-width=&quot;2714&quot; data-origin-height=&quot;1264&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. Bot 설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Application의 왼쪽 메뉴에서 Bot을 클릭합니다. &lt;b&gt;Bot은 Discord에서 사용자가 대화하게 될 대상&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1286&quot; data-origin-height=&quot;1022&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dU9oEr/dJMcabLovyN/A3bGpD3A40Lak3K8JNKAI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dU9oEr/dJMcabLovyN/A3bGpD3A40Lak3K8JNKAI0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dU9oEr/dJMcabLovyN/A3bGpD3A40Lak3K8JNKAI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdU9oEr%2FdJMcabLovyN%2FA3bGpD3A40Lak3K8JNKAI0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1286&quot; height=&quot;1022&quot; data-origin-width=&quot;1286&quot; data-origin-height=&quot;1022&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Authorization Flow에서 아래 값을 설정합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Public Bot: ON&lt;/li&gt;
&lt;li&gt;Require OAuth2 Code Grant: OFF&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Public Bot을 ON으로 두면 Installation 탭에서 Discord Provided Link를 사용할 수 있습니다. Private bot으로 운영하려면 Public Bot을 OFF로 둘 수 있지만, 이 경우 Installation 탭의 자동 초대 링크 대신 수동 OAuth2 URL을 만들어야 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4752&quot; data-origin-height=&quot;1222&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/U5Q3W/dJMcaccn5Mq/nvBs8AifAQN9LjCm0yefL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/U5Q3W/dJMcaccn5Mq/nvBs8AifAQN9LjCm0yefL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/U5Q3W/dJMcaccn5Mq/nvBs8AifAQN9LjCm0yefL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FU5Q3W%2FdJMcaccn5Mq%2FnvBs8AifAQN9LjCm0yefL1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;4752&quot; height=&quot;1222&quot; data-origin-width=&quot;4752&quot; data-origin-height=&quot;1222&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Privileged Gateway Intents에서는 아래 값을 활성화합니다. Message Content Intent가 꺼져 있으면 Bot이 메시지 이벤트를 받아도 메시지 본문을 읽지 못합니다. Server Members Intent가 꺼져 있으면 허용 사용자 확인이나 사용자 이름 해석에서 문제가 생길 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Server Members Intent&lt;/li&gt;
&lt;li&gt;Message Content Intent&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3574&quot; data-origin-height=&quot;1696&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbLbVH/dJMcagMGrfT/aX5FQSlWTOZyWuacozZmhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbLbVH/dJMcagMGrfT/aX5FQSlWTOZyWuacozZmhk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbLbVH/dJMcagMGrfT/aX5FQSlWTOZyWuacozZmhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbLbVH%2FdJMcagMGrfT%2FaX5FQSlWTOZyWuacozZmhk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3574&quot; height=&quot;1696&quot; data-origin-width=&quot;3574&quot; data-origin-height=&quot;1696&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. Bot token 생성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Bot 페이지의 Token 섹션에서 token을 생성합니다. Bot token은 Hermes Agent가 Discord Bot으로 로그인할 때 사용하는 인증 정보입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3144&quot; data-origin-height=&quot;998&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biJhDd/dJMcaffVJrR/JI6afWSlhYh7g1ORotqI8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biJhDd/dJMcaffVJrR/JI6afWSlhYh7g1ORotqI8k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biJhDd/dJMcaffVJrR/JI6afWSlhYh7g1ORotqI8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiJhDd%2FdJMcaffVJrR%2FJI6afWSlhYh7g1ORotqI8k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3144&quot; height=&quot;998&quot; data-origin-width=&quot;3144&quot; data-origin-height=&quot;998&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Token은 외부에 공개하면 안 됩니다. token을 잃어버렸거나 노출되었다면 reset token으로 새 token을 발급해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. Bot 권한 설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Installation 메뉴에서 Guild Install의 기본 권한을 설정&lt;/b&gt;합니다. Hermes Agent가 메시지를 읽고 응답하려면 최소한 아래 권한이 필요합니다. Thread 안에서 답변하게 하려면 Send Messages in Threads 권한도 함께 추가하는 것이 좋습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;View Channels: Bot이 접근 가능한 채널을 볼 수 있습니다.&lt;/li&gt;
&lt;li&gt;Send Messages: 사용자의 메시지에 답장할 수 있습니다.&lt;/li&gt;
&lt;li&gt;Embed Links: rich response를 표시할 수 있습니다.&lt;/li&gt;
&lt;li&gt;Attach Files: 이미지, 오디오, 파일 결과물을 보낼 수 있습니다.&lt;/li&gt;
&lt;li&gt;Read Message History: 대화 context를 유지할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4684&quot; data-origin-height=&quot;2308&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5u5nn/dJMcabLovCB/HvzSau7pJEzkIV4CkpfCrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5u5nn/dJMcabLovCB/HvzSau7pJEzkIV4CkpfCrK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5u5nn/dJMcabLovCB/HvzSau7pJEzkIV4CkpfCrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5u5nn%2FdJMcabLovCB%2FHvzSau7pJEzkIV4CkpfCrK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;4684&quot; height=&quot;2308&quot; data-origin-width=&quot;4684&quot; data-origin-height=&quot;2308&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. Discord 서버에 Bot 초대&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Installation 메뉴에서 Install Link를 복사한 뒤 웹브라우저에서 엽니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4718&quot; data-origin-height=&quot;1782&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxZZWa/dJMcagFUP5s/4HsCbOtCQkQG0UtU5Waank/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxZZWa/dJMcagFUP5s/4HsCbOtCQkQG0UtU5Waank/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxZZWa/dJMcagFUP5s/4HsCbOtCQkQG0UtU5Waank/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxZZWa%2FdJMcagFUP5s%2F4HsCbOtCQkQG0UtU5Waank%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;4718&quot; height=&quot;1782&quot; data-origin-width=&quot;4718&quot; data-origin-height=&quot;1782&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Add to Server를 클릭하고 Bot을 초대할 서버를 선택합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1384&quot; data-origin-height=&quot;1042&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUk1V2/dJMcacDvGoi/Hl1NOkvsNBAzsRohDWxxkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUk1V2/dJMcacDvGoi/Hl1NOkvsNBAzsRohDWxxkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUk1V2/dJMcacDvGoi/Hl1NOkvsNBAzsRohDWxxkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUk1V2%2FdJMcacDvGoi%2FHl1NOkvsNBAzsRohDWxxkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1384&quot; height=&quot;1042&quot; data-origin-width=&quot;1384&quot; data-origin-height=&quot;1042&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;권한을 확인한 뒤 Continue와 Authorize를 클릭합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1372&quot; data-origin-height=&quot;1926&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Jgu6y/dJMcadoLvfG/J7BpGkbGfexQ5SZlDFYKS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Jgu6y/dJMcadoLvfG/J7BpGkbGfexQ5SZlDFYKS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Jgu6y/dJMcadoLvfG/J7BpGkbGfexQ5SZlDFYKS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJgu6y%2FdJMcadoLvfG%2FJ7BpGkbGfexQ5SZlDFYKS1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1372&quot; height=&quot;1926&quot; data-origin-width=&quot;1372&quot; data-origin-height=&quot;1926&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1356&quot; data-origin-height=&quot;1866&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cmmjYp/dJMcaar8QjS/u3RehQVvQAj9FoKBxrI4i1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cmmjYp/dJMcaar8QjS/u3RehQVvQAj9FoKBxrI4i1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cmmjYp/dJMcaar8QjS/u3RehQVvQAj9FoKBxrI4i1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcmmjYp%2FdJMcaar8QjS%2Fu3RehQVvQAj9FoKBxrI4i1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1356&quot; height=&quot;1866&quot; data-origin-width=&quot;1356&quot; data-origin-height=&quot;1866&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1400&quot; data-origin-height=&quot;1210&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nNKlX/dJMcagy4S91/5s4Nkj0K206gN0rrFduPdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nNKlX/dJMcagy4S91/5s4Nkj0K206gN0rrFduPdK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nNKlX/dJMcagy4S91/5s4Nkj0K206gN0rrFduPdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnNKlX%2FdJMcagy4S91%2F5s4Nkj0K206gN0rrFduPdK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1400&quot; height=&quot;1210&quot; data-origin-width=&quot;1400&quot; data-origin-height=&quot;1210&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2164&quot; data-origin-height=&quot;2460&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Zyo3m/dJMcaci68SH/t4yjikzxXxzlnKzxXzTA1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Zyo3m/dJMcaci68SH/t4yjikzxXxzlnKzxXzTA1K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Zyo3m/dJMcaci68SH/t4yjikzxXxzlnKzxXzTA1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZyo3m%2FdJMcaci68SH%2Ft4yjikzxXxzlnKzxXzTA1K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2164&quot; height=&quot;2460&quot; data-origin-width=&quot;2164&quot; data-origin-height=&quot;2460&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. Discord 사용자 ID 확인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hermes Agent는 Discord 사용자 ID로 접근 권한을 제어합니다.&amp;nbsp; Developer Mode를 켠 뒤 사용자 아바타나 사용자 이름을 오른쪽 클릭하면 Copy User ID를 사용할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;경로: Settings &amp;rarr; Advanced &amp;rarr; Developer Mode &amp;rarr; ON&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;898&quot; data-origin-height=&quot;914&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r2bVP/dJMcajbqr4z/OxvYwtGPlctrbB7vYHP820/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r2bVP/dJMcajbqr4z/OxvYwtGPlctrbB7vYHP820/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r2bVP/dJMcajbqr4z/OxvYwtGPlctrbB7vYHP820/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr2bVP%2FdJMcajbqr4z%2FOxvYwtGPlctrbB7vYHP820%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;898&quot; height=&quot;914&quot; data-origin-width=&quot;898&quot; data-origin-height=&quot;914&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. Hermes Agent 설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지는 Discord 설정이었습니다. 이제 Hermes Agent 쪽에서 Discord Bot token과 허용 사용자 정보를 등록합니다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;hermes gateway setup&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1834&quot; data-origin-height=&quot;1690&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/B8cPx/dJMb997NwyP/YRzl7K3fpTk9aJsuy32uEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/B8cPx/dJMb997NwyP/YRzl7K3fpTk9aJsuy32uEK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/B8cPx/dJMb997NwyP/YRzl7K3fpTk9aJsuy32uEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FB8cPx%2FdJMb997NwyP%2FYRzl7K3fpTk9aJsuy32uEK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1834&quot; height=&quot;1690&quot; data-origin-width=&quot;1834&quot; data-origin-height=&quot;1690&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2848&quot; data-origin-height=&quot;2420&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biydd6/dJMcahSgdPF/PjD9HxzuQbUeHWcFs6gJ2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biydd6/dJMcahSgdPF/PjD9HxzuQbUeHWcFs6gJ2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biydd6/dJMcahSgdPF/PjD9HxzuQbUeHWcFs6gJ2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbiydd6%2FdJMcahSgdPF%2FPjD9HxzuQbUeHWcFs6gJ2K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2848&quot; height=&quot;2420&quot; data-origin-width=&quot;2848&quot; data-origin-height=&quot;2420&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Interactive setup에서 Discord를 선택하고 Bot token과 사용자 ID를 입력합니다. 입력한 값은 기본적으로 ~/.hermes/.env에 저장됩니다.&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;DISCORD_BOT_TOKEN=
DISCORD_ALLOWED_USERS=&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서 기준으로 DISCORD_ALLOWED_USERS 또는 DISCORD_ALLOWED_ROLES를 설정하지 않으면 gateway는 사용자를 허용하지 않습니다. 여러 명을 허용하려면 사용자 ID를 쉼표로 구분합니다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;DISCORD_ALLOWED_USERS=xxxxx,xxxxxx&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팀이나 서버 역할 기준으로 접근을 열고 싶다면 DISCORD_ALLOWED_ROLES를 사용할 수 있습니다. 이때 role 이름이 아니라 role ID를 입력해야 합니다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;DISCORD_ALLOWED_ROLES=xxxxxxx&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 연결 테스트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Discord 서버 채널에서 Bot을 멘션하면 Hermes Agent가 동작합니다. DM에서는 기본적으로 멘션 없이 모든 메시지에 응답합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3786&quot; data-origin-height=&quot;1640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bt7y3Y/dJMcaaFHyye/co8DzAgJREPCP5g1Pztrf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bt7y3Y/dJMcaaFHyye/co8DzAgJREPCP5g1Pztrf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bt7y3Y/dJMcaaFHyye/co8DzAgJREPCP5g1Pztrf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbt7y3Y%2FdJMcaaFHyye%2Fco8DzAgJREPCP5g1Pztrf1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3786&quot; height=&quot;1640&quot; data-origin-width=&quot;3786&quot; data-origin-height=&quot;1640&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;참고자료&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://hermes-agent.nousresearch.com/docs/user-guide/messaging/&quot;&gt;Hermes messaging gateway&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://hermes-agent.nousresearch.com/docs/user-guide/messaging/discord&quot;&gt;Hermes Discord integration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.discord.com/developers/events/gateway&quot;&gt;Discord Gateway&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>전공영역 공부 기록</category>
      <category>Ai</category>
      <category>discord</category>
      <category>Hermes</category>
      <author>악분</author>
      <guid isPermaLink="true">https://malwareanalysis.tistory.com/937</guid>
      <comments>https://malwareanalysis.tistory.com/937#entry937comment</comments>
      <pubDate>Sun, 14 Jun 2026 18:37:26 +0900</pubDate>
    </item>
    <item>
      <title>Hermes Agent에서 AI provider 설정하는 방법</title>
      <link>https://malwareanalysis.tistory.com/936</link>
      <description>&lt;h1&gt;AI provider 설정 유형&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hermes Agent에서 AI provider를 설정하는 방법은 크게 2종류인 것 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1번째 방법: gateway를 연동(예: OpenRouter)&lt;/li&gt;
&lt;li&gt;2번째 방법: Hermes Agent가 직접 AI provider를 연동&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3622&quot; data-origin-height=&quot;1636&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ecdGHT/dJMcadWEDPz/xUsKqijsNBSKNMXLjA2Tgk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ecdGHT/dJMcadWEDPz/xUsKqijsNBSKNMXLjA2Tgk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ecdGHT/dJMcadWEDPz/xUsKqijsNBSKNMXLjA2Tgk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FecdGHT%2FdJMcadWEDPz%2FxUsKqijsNBSKNMXLjA2Tgk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3622&quot; height=&quot;1636&quot; data-origin-width=&quot;3622&quot; data-origin-height=&quot;1636&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;token으로만 AI를 사용하고 있다면 gateway를 연동하면 좋습니다. 구독 형태로 사용하고 있는 AI가 있다면 Hermes Agent에 직접 AI provider를 연동합니다. 단, AI provider가 자사가 아닌 타사에 OAuth를 제공해야 합니다. Claude는 2026년 6월 기준 Max 구독제만 타사에 OAuth를 제공합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;참고자료: &lt;a href=&quot;https://hermes-agent.nousresearch.com/docs/integrations/providers&quot;&gt;https://hermes-agent.nousresearch.com/docs/integrations/providers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2962&quot; data-origin-height=&quot;1486&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kWsd4/dJMcadWEDPG/GpfxHCkgOduMkt3thaByGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kWsd4/dJMcadWEDPG/GpfxHCkgOduMkt3thaByGk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kWsd4/dJMcadWEDPG/GpfxHCkgOduMkt3thaByGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkWsd4%2FdJMcadWEDPG%2FGpfxHCkgOduMkt3thaByGk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2962&quot; height=&quot;1486&quot; data-origin-width=&quot;2962&quot; data-origin-height=&quot;1486&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;AI provider 설정 방법&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;model 설정은 Hermes 공식 문서를 0순위로 참고하는 게 좋습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;model 설정 문서: &lt;a href=&quot;https://hermes-agent.nousresearch.com/docs/user-guide/configuring-models&quot;&gt;https://hermes-agent.nousresearch.com/docs/user-guide/configuring-models&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Hermes Agent CLI로 설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hermes model 명령어는 AI provider를 설정합니다. hermes model 명령어는 ~/.hermes/config.yaml을 수정합니다.&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;hermes model&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2362&quot; data-origin-height=&quot;1194&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdVtK8/dJMcafUwf6m/tuY4FhqwdTLMkdiKaPpSw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdVtK8/dJMcafUwf6m/tuY4FhqwdTLMkdiKaPpSw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdVtK8/dJMcafUwf6m/tuY4FhqwdTLMkdiKaPpSw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdVtK8%2FdJMcafUwf6m%2FtuY4FhqwdTLMkdiKaPpSw1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2362&quot; height=&quot;1194&quot; data-origin-width=&quot;2362&quot; data-origin-height=&quot;1194&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hermes model 명령어에서 선택한 AI provider는 기본 AI provider로 선택됩니다. AI provider를 세부적으로 설정하고 싶으면 ~/.hermes/config.yaml 파일을 직접 수정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;설정 파일에서 직접 설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AI provider 설정은 Hermes 설정 파일인 ~/.hermes/config.yaml에 관리&lt;/b&gt;됩니다. 이 파일을 직접 수정하여 AI provider 설정을 수정할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;model 필드: 기본으로 사용하는 AI provider 설정&lt;/li&gt;
&lt;li&gt;fallback_providers 필드: 기본 AI provider 사용에 실패했을 때 사용하는 AI provider&lt;/li&gt;
&lt;li&gt;auxiliary 필드: 기본 AI provider 이외에 추가로 사용하고 싶은 보조 AI provider 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;$ cat ~/.hermes/config.yaml
model:
  default: gpt-5.5
  provider: openai-codex
  base_url: https://chatgpt.com/backend-api/codex
fallback_providers: []
auxiliary:
  vision:
    provider: auto
    model: ''
    base_url: ''
    api_key: ''
    timeout: 120
    extra_body: {}
    download_timeout: 30
  web_extract:
    provider: auto
    model: ''
    base_url: ''
    api_key: ''
    timeout: 360
    extra_body: {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;AI provider OAuth 설정 확인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;hermes auth list 명령어로 Hermes Agent가 관리하는 인증 정보를 조회할 수 있습니다.&lt;/b&gt; 저는 Codex를 OAuth로 연동해서 OAuth 인증 정보가 보입니다.&lt;/p&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;hermes auth list&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1404&quot; data-origin-height=&quot;380&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mmUhF/dJMcaffVDGL/nHY7S3inKs4816QlBworhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mmUhF/dJMcaffVDGL/nHY7S3inKs4816QlBworhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mmUhF/dJMcaffVDGL/nHY7S3inKs4816QlBworhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmmUhF%2FdJMcaffVDGL%2FnHY7S3inKs4816QlBworhK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1404&quot; height=&quot;380&quot; data-origin-width=&quot;1404&quot; data-origin-height=&quot;380&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;참고 자료&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://hermes-agent.nousresearch.com/docs/integrations/providers&quot;&gt;https://hermes-agent.nousresearch.com/docs/integrations/providers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>전공영역 공부 기록</category>
      <category>Ai</category>
      <category>Hermes</category>
      <author>악분</author>
      <guid isPermaLink="true">https://malwareanalysis.tistory.com/936</guid>
      <comments>https://malwareanalysis.tistory.com/936#entry936comment</comments>
      <pubDate>Sun, 14 Jun 2026 14:06:44 +0900</pubDate>
    </item>
    <item>
      <title>Full proxy와 TCP 종료 전파</title>
      <link>https://malwareanalysis.tistory.com/935</link>
      <description>&lt;h1 id=&quot;full-proxy란&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;Full proxy란&lt;/h1&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Full proxy는 downstream과 upstream의 세션을 각각 맺습니다.&lt;/b&gt; 따라서, proxy는 downstream socket과 upstream socket을 따로 생성합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1688&quot; data-origin-height=&quot;538&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FsRfJ/dJMcaci1ZZx/GGMSKLN1UK44gfR0LRcFu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FsRfJ/dJMcaci1ZZx/GGMSKLN1UK44gfR0LRcFu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FsRfJ/dJMcaci1ZZx/GGMSKLN1UK44gfR0LRcFu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFsRfJ%2FdJMcaci1ZZx%2FGGMSKLN1UK44gfR0LRcFu0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1688&quot; height=&quot;538&quot; data-origin-width=&quot;1688&quot; data-origin-height=&quot;538&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;raw-tcp을-사용할-때-socket-종료전파&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;raw tcp을 사용할 때, socket  종료전파&lt;/h1&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; 제가 full proxy을 접하게 된 이유가 raw tcp의 생명주기가 궁금했기 때문입니다. raw tcp통신은 http처럼 L7 프로토콜이 아니라 L4 tcp만 가지고 통신할때입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Full proxy는 어느 한쪽 socket이 종료되면, 다른편 socket을 종료합니다. 이 과정을 종료 전파(Propagate)라고 부릅니다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3834&quot; data-origin-height=&quot;924&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cihPmc/dJMcagMAYSU/i5zm7GAeHQaDu8WzUwU5i0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cihPmc/dJMcagMAYSU/i5zm7GAeHQaDu8WzUwU5i0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cihPmc/dJMcagMAYSU/i5zm7GAeHQaDu8WzUwU5i0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcihPmc%2FdJMcagMAYSU%2Fi5zm7GAeHQaDu8WzUwU5i0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3834&quot; height=&quot;924&quot; data-origin-width=&quot;3834&quot; data-origin-height=&quot;924&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;envoy proxy의 종료 전파코드는 아래와 같습니다.&lt;br /&gt;&lt;a href=&quot;https://github.com/envoyproxy/envoy/blob/main/source/common/tcp_proxy/tcp_proxy.cc#L1288-L1350&quot;&gt;https://github.com/envoyproxy/envoy/blob/main/source/common/tcp_proxy/tcp_proxy.cc#L1288-L1350&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2206&quot; data-origin-height=&quot;2258&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cHdQSh/dJMcaf7TRmz/cpF9NxNq8vRdV2Rk0gz4M0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cHdQSh/dJMcaf7TRmz/cpF9NxNq8vRdV2Rk0gz4M0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cHdQSh/dJMcaf7TRmz/cpF9NxNq8vRdV2Rk0gz4M0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcHdQSh%2FdJMcaf7TRmz%2FcpF9NxNq8vRdV2Rk0gz4M0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2206&quot; height=&quot;2258&quot; data-origin-width=&quot;2206&quot; data-origin-height=&quot;2258&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;HAproxy의  코드는 아래와 같습니다.&lt;br /&gt;&lt;a href=&quot;https://github.com/haproxy/haproxy/blob/ac776e3819f9fa54e2a005469e5a423a3d179543/src/stconn.c#L1309-L1314&quot;&gt;https://github.com/haproxy/haproxy/blob/ac776e3819f9fa54e2a005469e5a423a3d179543/src/stconn.c#L1309-L1314&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1208&quot; data-origin-height=&quot;286&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qAbsH/dJMcabkdsRV/kQRiasuIfYq4kKU5sppp7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qAbsH/dJMcabkdsRV/kQRiasuIfYq4kKU5sppp7K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qAbsH/dJMcabkdsRV/kQRiasuIfYq4kKU5sppp7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqAbsH%2FdJMcabkdsRV%2FkQRiasuIfYq4kKU5sppp7K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1208&quot; height=&quot;286&quot; data-origin-width=&quot;1208&quot; data-origin-height=&quot;286&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;종료되지-않게-구현하려면&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;종료되지 않게 구현하려면?&lt;/h1&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;upstream, downstream 생명주기를 분리하고 싶다면, full proxy를 사용하는 것보다는 &lt;b&gt;세션을 관리하는 TCP gateway를 직접 개발&lt;/b&gt;해야합니다.&lt;/p&gt;</description>
      <category>전공영역 공부 기록</category>
      <category>Proxy</category>
      <category>tcp</category>
      <author>악분</author>
      <guid isPermaLink="true">https://malwareanalysis.tistory.com/935</guid>
      <comments>https://malwareanalysis.tistory.com/935#entry935comment</comments>
      <pubDate>Sun, 7 Jun 2026 23:28:45 +0900</pubDate>
    </item>
    <item>
      <title>회고-ISO 20001, ISMS-P 인증과정에서 AWS  KMS관련 인터뷰</title>
      <link>https://malwareanalysis.tistory.com/934</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;2025, 2026년 ISO 20001인증과 ISMS-P 인증에 참여 하면서 일부 내용을 정리합니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;예상보다 KMS 질문을 많이 받았습니다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&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;데이터 암호화/복호화는 어떤 프로세스로 이루지는가?&lt;/li&gt;&lt;li&gt;기타 등등&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;제 팀은 KMS를 위한 AWS Account를 별도로 생성하고, 허용된 사람만 assume해서 들어갈 수 있습니다. 그리고 KMS key별로도 접근이 제한되어 있습니다. AWS Account를 분리한 이유는, 서비스 AWS계정에 KMS를 만드면 Administrator IAM policy를 갖는 사람이 KMS에 접근할 위험이 있기 때문입니다. 허용된 사람만 KMS에 접근하는 것을 보장하기 위해 물리적으로 AWS Account를 분리했습니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;AWS KMS의 설계 방향은 AWS문서에 자세히 설명되어 있습니다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;a href=&quot;https://docs.aws.amazon.com/prescriptive-guidance/latest/aws-kms-best-practices/key-management.html#key-management-model&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://docs.aws.amazon.com/prescriptive-guidance/latest/aws-kms-best-practices/key-management.html#key-management-model&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;</description>
      <category>회고모음</category>
      <category>aws</category>
      <category>KMS</category>
      <category>회고</category>
      <author>악분</author>
      <guid isPermaLink="true">https://malwareanalysis.tistory.com/934</guid>
      <comments>https://malwareanalysis.tistory.com/934#entry934comment</comments>
      <pubDate>Sat, 6 Jun 2026 12:52:44 +0900</pubDate>
    </item>
    <item>
      <title>claude 연간 구독제 취소방법과 후기</title>
      <link>https://malwareanalysis.tistory.com/933</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;claude pro 월간 구독을 하려고 했다가, 실수로 연간구독을 했습니다. 그래서 연간 구독취소를 했는데 그 과정을 정리했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;환불방법&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;환불방법&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메뉴얼: &lt;a href=&quot;https://support.claude.com/ko/articles/12386328&quot;&gt;https://support.claude.com/ko/articles/12386328&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;구독 취소는 AI가 진행합니다. 왼쪽 하단 프로필 클릭 -&amp;gt; 도움 받기를 통해 환불을 할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1372&quot; data-origin-height=&quot;1886&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzEgdn/dJMcagezjwB/SRbOi6kJ54eAHRCKKrcyP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzEgdn/dJMcagezjwB/SRbOi6kJ54eAHRCKKrcyP0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzEgdn/dJMcagezjwB/SRbOi6kJ54eAHRCKKrcyP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzEgdn%2FdJMcagezjwB%2FSRbOi6kJ54eAHRCKKrcyP0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1372&quot; height=&quot;1886&quot; data-origin-width=&quot;1372&quot; data-origin-height=&quot;1886&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;도움 받기를 누르면 AI agent와 환불에 관한 채팅창이 뜹니다. 개인정보이용약관 동의를 해야 환불을 진행할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1116&quot; data-origin-height=&quot;1434&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dEUoUI/dJMcahdtV9d/gSDuY7fpIMRIIe3qVKZuYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dEUoUI/dJMcahdtV9d/gSDuY7fpIMRIIe3qVKZuYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dEUoUI/dJMcahdtV9d/gSDuY7fpIMRIIe3qVKZuYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdEUoUI%2FdJMcahdtV9d%2FgSDuY7fpIMRIIe3qVKZuYK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1116&quot; height=&quot;1434&quot; data-origin-width=&quot;1116&quot; data-origin-height=&quot;1434&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;환불후기&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;환불후기&lt;/h1&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;저는 채팅을 아래처럼 진행했습니다. 저는 연간구독을 원간구독으로 변경 요청했는데, 구독변경은 못하고 취소로 진행되었습니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;중요한건 취소대상이 아니라면 구독이 취소되지 않습니다.&lt;/b&gt;&lt;br /&gt;- 주제: 플랜변경, 결제문제&lt;br /&gt;- 세부내용: 연간구독을 월간구독으로 변경&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1962&quot; data-origin-height=&quot;1724&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yaNhf/dJMcahq18gF/vgDVV9NwQgHEeWecanRzC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yaNhf/dJMcahq18gF/vgDVV9NwQgHEeWecanRzC1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yaNhf/dJMcahq18gF/vgDVV9NwQgHEeWecanRzC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyaNhf%2FdJMcahq18gF%2FvgDVV9NwQgHEeWecanRzC1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1962&quot; height=&quot;1724&quot; data-origin-width=&quot;1962&quot; data-origin-height=&quot;1724&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1756&quot; data-origin-height=&quot;588&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Edy5U/dJMcacDgyqp/SaWHbZrdeFb6oE5OnSNXP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Edy5U/dJMcacDgyqp/SaWHbZrdeFb6oE5OnSNXP0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Edy5U/dJMcacDgyqp/SaWHbZrdeFb6oE5OnSNXP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEdy5U%2FdJMcacDgyqp%2FSaWHbZrdeFb6oE5OnSNXP0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1756&quot; height=&quot;588&quot; data-origin-width=&quot;1756&quot; data-origin-height=&quot;588&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;claude 결제내역과 카드 이용내역을 확인해보면, 결제취소가 되어 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2716&quot; data-origin-height=&quot;222&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dq6bMX/dJMcacDgyqP/is0ikUbiSpx2263tHjHt3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dq6bMX/dJMcacDgyqP/is0ikUbiSpx2263tHjHt3K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dq6bMX/dJMcacDgyqP/is0ikUbiSpx2263tHjHt3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdq6bMX%2FdJMcacDgyqP%2Fis0ikUbiSpx2263tHjHt3K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2716&quot; height=&quot;222&quot; data-origin-width=&quot;2716&quot; data-origin-height=&quot;222&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>기타영역 공부 기록</category>
      <category>Claude</category>
      <author>악분</author>
      <guid isPermaLink="true">https://malwareanalysis.tistory.com/933</guid>
      <comments>https://malwareanalysis.tistory.com/933#entry933comment</comments>
      <pubDate>Mon, 25 May 2026 12:35:56 +0900</pubDate>
    </item>
    <item>
      <title>[스터디] Generative AI on Kubernetes 5장: 실험 환경부터 모델 배포까지(upyterHub, LoRA, RAG)</title>
      <link>https://malwareanalysis.tistory.com/932</link>
      <description>&lt;h1&gt;요약&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 글은 책 5장 실습을 따라 kubernetes 위에서 생성형 AI 서비스를 구성하는 과정을 정리합니다.&lt;/li&gt;
&lt;li&gt;실습 범위는 JupyterHub 기반 실험 환경, LoRA 파인튜닝, AI 모델 API 배포, Qdrant 기반 RAG, 챗봇 UI입니다.&lt;/li&gt;
&lt;li&gt;책과 달리 kubernetes 리소스는 Terraform 대신 kubectl과 Helm으로 배포했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;목표&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;학습 목표&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 장에서는 kubernetes를 사용해 AI 모델을 실험하고, 빌드하고, 배포하는 흐름을 다룹니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kubeflow 아키텍처 문서에서 설명하는 AI Lifecycle 중 Model Development, Model Training, Model Serving 단계를 실습으로 간접 경험합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2056&quot; data-origin-height=&quot;1046&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EKy9G/dJMcabdcMkk/IYStboARNhXRt4uyV20zY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EKy9G/dJMcabdcMkk/IYStboARNhXRt4uyV20zY1/img.png&quot; data-alt=&quot;출처: https://www.kubeflow.org/docs/started/architecture/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EKy9G/dJMcabdcMkk/IYStboARNhXRt4uyV20zY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEKy9G%2FdJMcabdcMkk%2FIYStboARNhXRt4uyV20zY1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2056&quot; height=&quot;1046&quot; data-origin-width=&quot;2056&quot; data-origin-height=&quot;1046&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: https://www.kubeflow.org/docs/started/architecture/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;책의 예제 목표&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책은 MyRetail이라는 e-commerce 회사를 예로 듭니다. MyRetail은 고객에게 더 개인화된 쇼핑 경험을 제공하기 위해 두 가지 AI 서비스를 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 서비스는 하나의 챗봇 UI에서 실행됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3086&quot; data-origin-height=&quot;1434&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1uTYo/dJMcabj3Qz4/bMwcikeRhQx5uqG6qtPzT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1uTYo/dJMcabj3Qz4/bMwcikeRhQx5uqG6qtPzT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1uTYo/dJMcabj3Qz4/bMwcikeRhQx5uqG6qtPzT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1uTYo%2FdJMcabj3Qz4%2FbMwcikeRhQx5uqG6qtPzT0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3086&quot; height=&quot;1434&quot; data-origin-width=&quot;3086&quot; data-origin-height=&quot;1434&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Shopping assistant: 상품 카탈로그 데이터를 기반으로 고객에게 개인화된 상품을 추천합니다. 이 기능은 RAG 방식으로 구현하며, AI 모델은 OpenAI 모델을 사용합니다.&lt;/li&gt;
&lt;li&gt;Program assistant: MyElite loyalty program FAQ에 대한 고객 질문에 답변합니다. 이 기능은 FAQ 데이터로 AI 모델을 파인튜닝해서 구현합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;실습 환경&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GPU: RTX 5060 Ti 16GB&lt;/li&gt;
&lt;li&gt;OS: Ubuntu 24.04 LTS&lt;/li&gt;
&lt;li&gt;kubernetes 설치: k3s 단일 node&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;책과 다르게 진행한 부분&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책에서는 일부 kubernetes 리소스를 Terraform으로 배포합니다. 저는 kubernetes 리소스는 Terraform보다 kubectl과 Helm으로 관리하는 편이 더 단순하다고 판단했습니다. 그래서 이 실습에서는 kubectl과 Helm으로 리소스를 배포했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;실습 자료&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GitHub: &lt;a href=&quot;https://github.com/choisungwook/portfolio/tree/master/computer_science/ai/genai-on-kubernetes/chapter5-chatbot/k3s&quot;&gt;https://github.com/choisungwook/portfolio/tree/master/computer_science/ai/genai-on-kubernetes/chapter5-chatbot/k3s&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;주제 1. JupyterHub로 실험 환경 만들기&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책은 가장 먼저 kubernetes에 JupyterHub를 배포합니다. JupyterHub는 Jupyter pod 실행을 관리하는 오픈소스입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 Jupyter IDE 사용을 요청하면 JupyterHub가 Jupyter pod를 생성합니다. 사용자는 생성된 Jupyter pod에 접속해 AI 모델을 만드는 코드를 작성합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2616&quot; data-origin-height=&quot;1430&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ygyk7/dJMcaiwFouv/0ozqLhmfK7NTlx7mIz46n0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ygyk7/dJMcaiwFouv/0ozqLhmfK7NTlx7mIz46n0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ygyk7/dJMcaiwFouv/0ozqLhmfK7NTlx7mIz46n0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYgyk7%2FdJMcaiwFouv%2F0ozqLhmfK7NTlx7mIz46n0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2616&quot; height=&quot;1430&quot; data-origin-width=&quot;2616&quot; data-origin-height=&quot;1430&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AI 모델을 만들기 위해 테스트하는 환경을 실험 환경&lt;/b&gt;이라고 부릅니다. AI 실험은 일반적인 소프트웨어 개발과 다르게 로컬 PC보다 실험 서버에서 진행하는 경우가 많습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실험 서버가 필요한 이유는 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GPU가 필요합니다.&lt;/li&gt;
&lt;li&gt;데이터셋에 접근할 수 있어야 합니다.&lt;/li&gt;
&lt;li&gt;ipynb 파일을 실행할 수 있어야 합니다.&lt;/li&gt;
&lt;li&gt;실험 기록을 남길 수 있어야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 생성형 AI 시스템을 구축하려면 먼저 실험 환경을 제공해야 합니다. 이 실험 환경을 보통 notebook이라고 부르며, 대표적인 구축 방법이 JupyterHub입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실습&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GitHub: &lt;a href=&quot;https://github.com/choisungwook/portfolio/tree/master/computer_science/ai/genai-on-kubernetes/chapter5-chatbot/k3s&quot;&gt;https://github.com/choisungwook/portfolio/tree/master/computer_science/ai/genai-on-kubernetes/chapter5-chatbot/k3s&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3390&quot; data-origin-height=&quot;1398&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dmElu9/dJMcaftaUBw/F457a0Jr0QBmvKm5VrGho1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dmElu9/dJMcaftaUBw/F457a0Jr0QBmvKm5VrGho1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dmElu9/dJMcaftaUBw/F457a0Jr0QBmvKm5VrGho1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdmElu9%2FdJMcaftaUBw%2FF457a0Jr0QBmvKm5VrGho1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3390&quot; height=&quot;1398&quot; data-origin-width=&quot;3390&quot; data-origin-height=&quot;1398&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;주제 2. LoRA 파인튜닝으로 AI 모델 만들기&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째 주제는 LoRA 파인튜닝을 사용해 AI 모델을 만드는 과정입니다. 실험 환경에서 테스트한 AI 모델을 실제로 사용하려면 모델을 만들고 모델 저장소에 저장해야 합니다. 이 예제에서는 별도의 모델 저장소를 사용하지 않습니다. 대신 LoRA 파인튜닝으로 만든 가중치만 스토리지에 저장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 모델을 만들고 저장하는 작업은 실험 환경이 아니라 빌드 서버에서 실행하는 편이 적절합니다. kubernetes에서 이 작업을 실행한다면 job 리소스가 잘 맞습니다. 모델 빌드 작업은 시작과 끝이 명확한 일회성 workload이기 때문입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실습&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1612&quot; data-origin-height=&quot;456&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcqlG5/dJMcagTa26G/X3q6ZHNUXRkbb5DJhkkRf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcqlG5/dJMcagTa26G/X3q6ZHNUXRkbb5DJhkkRf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcqlG5/dJMcagTa26G/X3q6ZHNUXRkbb5DJhkkRf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcqlG5%2FdJMcagTa26G%2FX3q6ZHNUXRkbb5DJhkkRf0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1612&quot; height=&quot;456&quot; data-origin-width=&quot;1612&quot; data-origin-height=&quot;456&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GitHub: &lt;a href=&quot;https://github.com/choisungwook/portfolio/tree/master/computer_science/ai/genai-on-kubernetes/chapter5-chatbot/k3s&quot;&gt;https://github.com/choisungwook/portfolio/tree/master/computer_science/ai/genai-on-kubernetes/chapter5-chatbot/k3s&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;주제 3. AI 모델 API 배포&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 번째 주제는 두 번째 과정에서 만든 AI 모델을 API로 배포하는 과정입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델을 서빙하는 방법은 vLLM처럼 여러 가지가 있습니다. 이 책의 예제에서는 FastAPI로 API를 만들고, API 서버가 AI 모델을 직접 호출해 응답을 생성합니다.&lt;/p&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;@app.post(&quot;/generate&quot;)
async def generate(request: Request):
    ...

    inputs = build_inputs(prompt)

    with torch.no_grad():
        outputs = generate_with_inputs(
            inputs,
            max_new_tokens=MAX_NEW_TOKENS,
            repetition_penalty=REPETITION_PENALTY,
        )

    generated_tokens = outputs[0][get_input_length(inputs):]
    response = tokenizer.decode(generated_tokens, skip_special_tokens=True)

    return {&quot;response&quot;: response}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실습&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4296&quot; data-origin-height=&quot;190&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXQoAx/dJMcahYPezF/iRZi3POKtkjkANQqtRyThk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXQoAx/dJMcahYPezF/iRZi3POKtkjkANQqtRyThk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXQoAx/dJMcahYPezF/iRZi3POKtkjkANQqtRyThk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXQoAx%2FdJMcahYPezF%2FiRZi3POKtkjkANQqtRyThk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;4296&quot; height=&quot;190&quot; data-origin-width=&quot;4296&quot; data-origin-height=&quot;190&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GitHub: &lt;a href=&quot;https://github.com/choisungwook/portfolio/tree/master/computer_science/ai/genai-on-kubernetes/chapter5-chatbot/k3s&quot;&gt;https://github.com/choisungwook/portfolio/tree/master/computer_science/ai/genai-on-kubernetes/chapter5-chatbot/k3s&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;주제 4. RAG와 상품 추천 API 배포&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네 번째 주제는 Qdrant vector store를 사용해 RAG를 구축하고, 상품 카탈로그 데이터를 기반으로 개인화된 상품 추천 서비스를 만드는 과정입니다. 이 서비스는 OpenAI 모델을 사용합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실습&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2080&quot; data-origin-height=&quot;456&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfVsHK/dJMcafNxs5y/gFAQxJKR9YPsMWNDvbvww0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfVsHK/dJMcafNxs5y/gFAQxJKR9YPsMWNDvbvww0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfVsHK/dJMcafNxs5y/gFAQxJKR9YPsMWNDvbvww0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfVsHK%2FdJMcafNxs5y%2FgFAQxJKR9YPsMWNDvbvww0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2080&quot; height=&quot;456&quot; data-origin-width=&quot;2080&quot; data-origin-height=&quot;456&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GitHub: &lt;a href=&quot;https://github.com/choisungwook/portfolio/tree/master/computer_science/ai/genai-on-kubernetes/chapter5-chatbot/k3s&quot;&gt;https://github.com/choisungwook/portfolio/tree/master/computer_science/ai/genai-on-kubernetes/chapter5-chatbot/k3s&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;주제 5. 챗봇 UI 배포&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다섯 번째 주제는 Gradio를 사용해 챗봇 UI를 만드는 과정입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 챗봇 UI에서 요청을 보내면, 챗봇 UI는 주제 3과 주제 4에서 만든 API를 호출합니다. 사용자는 하나의 UI에서 파인튜닝한 모델과 RAG 기반 추천 서비스를 함께 사용할 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실습&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3086&quot; data-origin-height=&quot;1434&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLZ71L/dJMcagey8F3/p4lDNhYC0CE5GT1dLGdHZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLZ71L/dJMcagey8F3/p4lDNhYC0CE5GT1dLGdHZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLZ71L/dJMcagey8F3/p4lDNhYC0CE5GT1dLGdHZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLZ71L%2FdJMcagey8F3%2Fp4lDNhYC0CE5GT1dLGdHZk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3086&quot; height=&quot;1434&quot; data-origin-width=&quot;3086&quot; data-origin-height=&quot;1434&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GitHub: &lt;a href=&quot;https://github.com/choisungwook/portfolio/tree/master/computer_science/ai/genai-on-kubernetes/chapter5-chatbot/k3s&quot;&gt;https://github.com/choisungwook/portfolio/tree/master/computer_science/ai/genai-on-kubernetes/chapter5-chatbot/k3s&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;참고자료&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Kubeflow Architecture: &lt;a href=&quot;https://www.kubeflow.org/docs/started/architecture/&quot;&gt;https://www.kubeflow.org/docs/started/architecture/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;실습 코드: &lt;a href=&quot;https://github.com/choisungwook/portfolio/tree/master/computer_science/ai/genai-on-kubernetes/chapter5-chatbot/k3s&quot;&gt;https://github.com/choisungwook/portfolio/tree/master/computer_science/ai/genai-on-kubernetes/chapter5-chatbot/k3s&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>전공영역 공부 기록</category>
      <category>Ai</category>
      <category>kubernetes</category>
      <category>스터디</category>
      <author>악분</author>
      <guid isPermaLink="true">https://malwareanalysis.tistory.com/932</guid>
      <comments>https://malwareanalysis.tistory.com/932#entry932comment</comments>
      <pubDate>Sun, 24 May 2026 23:31:41 +0900</pubDate>
    </item>
    <item>
      <title>스터디-로컬 RAG를 AWS로 마이그레이션 - Bedrock과 S3Vectors</title>
      <link>https://malwareanalysis.tistory.com/931</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이 글은 &lt;a href=&quot;https://malwareanalysis.tistory.com/930&quot;&gt;로컬동작하는 FAISS 기반 RAG 실습&lt;/a&gt;을 AWS Bedrock Knowledge Bases와 Amazon S3 Vectors로 옮긴 과정을 정리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;복습: 로컬 RAG에서는 무엇을 직접 했을까?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬 실습에서는 셔츠 상품 CSV를 읽고, 한 행을 하나의 문서처럼 다뤘습니다. 그다음 embedding model로 문서를 vector로 바꾸고 FAISS index에 넣었습니다. 아래 코드는 로컬 실습에서 RAG의 핵심이 되는 부분입니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;from langchain_community.document_loaders import CSVLoader
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings

loader = CSVLoader(file_path=str(data_path), encoding=&quot;utf-8&quot;)
documents = loader.load()

embeddings = OpenAIEmbeddings(
  model=&quot;text-embedding-3-small&quot;,
  dimensions=1536,
)

vector_store = FAISS.from_documents(documents, embeddings)&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CSV를 문서로 변환&lt;/li&gt;
&lt;li&gt;문서를 embedding model로 숫자 vector로  변경&lt;/li&gt;
&lt;li&gt;FAISS index에 vector를 저장&lt;/li&gt;
&lt;li&gt;질문이 들어오면 같은 embedding model로 질문도 vector로  변경&lt;/li&gt;
&lt;li&gt;가까운 문서 Top-K를 찾아 prompt context로 넘김&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;AWS 아키텍처&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS로 옮길 때는 최대한 AWS기능을 활용하는 것이 좋습니다. 그래서 Bedrock knowledge base를 RAG로 사용하고 Vector DB를 S3 Vector를 사용했습니다.&lt;br /&gt;- &lt;b&gt;RAG: Bedrock Knowledge Bases&lt;/b&gt;&lt;br /&gt;- &lt;b&gt;Vector DB는 AWS S3 Vector&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2726&quot; data-origin-height=&quot;1392&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FS5Uu/dJMcaffCNu0/gjpz7iklMmu9UgWhxXfz80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FS5Uu/dJMcaffCNu0/gjpz7iklMmu9UgWhxXfz80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FS5Uu/dJMcaffCNu0/gjpz7iklMmu9UgWhxXfz80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFS5Uu%2FdJMcaffCNu0%2Fgjpz7iklMmu9UgWhxXfz80%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2726&quot; height=&quot;1392&quot; data-origin-width=&quot;2726&quot; data-origin-height=&quot;1392&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Bedrock은 여러 foundation model을 AWS API와 권한 체계 안에서 사용할 수 있게 해주는 관리형 서비스입니다. 그중 Knowledge Bases는 RAG를 위한 문서 ingestion, chunking, embedding, retrieval을 관리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS S3 Vector는 객체 스토리지에 벡터를 저장하고 쿼리를 할 수 있게합니다. &lt;b&gt;S3 콘솔을 보면 Vector buckets이 따로 존재합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3558&quot; data-origin-height=&quot;538&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2kjzc/dJMcaiclp9y/ZqRgSYok8HkUrsF3n3qFtk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2kjzc/dJMcaiclp9y/ZqRgSYok8HkUrsF3n3qFtk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2kjzc/dJMcaiclp9y/ZqRgSYok8HkUrsF3n3qFtk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2kjzc%2FdJMcaiclp9y%2FZqRgSYok8HkUrsF3n3qFtk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3558&quot; height=&quot;538&quot; data-origin-width=&quot;3558&quot; data-origin-height=&quot;538&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Bedrock knowledge base에서 S3 Vector로 데이터를 저장하는 방법은 매우 간단합니다. Sync를 누르면 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3558&quot; data-origin-height=&quot;538&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEnlav/dJMcaa6q8jn/kpHGOfgwKTlCGj4veWHWG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEnlav/dJMcaa6q8jn/kpHGOfgwKTlCGj4veWHWG1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEnlav/dJMcaa6q8jn/kpHGOfgwKTlCGj4veWHWG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEnlav%2FdJMcaa6q8jn%2FkpHGOfgwKTlCGj4veWHWG1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3558&quot; height=&quot;538&quot; data-origin-width=&quot;3558&quot; data-origin-height=&quot;538&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;S3 Vectors를 사용한 이유?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 실습의 목적은 검색 엔진 을 이해하고 장단점을 파악하는것이 아니라 빠르게 AWS로 실습하는게 목표여서 AWS S3 vector를 선택했습니다. &lt;b&gt;AWS에서 Vector DB로 쓸 수 있는 리소스는 OpenSearch, Postgres RDS vector, S3 vector&lt;/b&gt; 등이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, &lt;b&gt;AWS S3 Vector는 사용방법이 쉬울뿐만 아니라 성능도 매우 우수합니다. AWS 공식 설명을 보면, 수식억개의 벡터를 저장하고 조회하는데 1초 미만이 걸린다고 합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1928&quot; data-origin-height=&quot;1236&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dxDU3N/dJMcacwpzo9/aNeGXVp0VT6PTU6EX2egfk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dxDU3N/dJMcacwpzo9/aNeGXVp0VT6PTU6EX2egfk/img.png&quot; data-alt=&quot;출처: &amp;amp;nbsp; https://aws.amazon.com/ko/s3/features/vectors/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dxDU3N/dJMcacwpzo9/aNeGXVp0VT6PTU6EX2egfk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdxDU3N%2FdJMcacwpzo9%2FaNeGXVp0VT6PTU6EX2egfk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1928&quot; height=&quot;1236&quot; data-origin-width=&quot;1928&quot; data-origin-height=&quot;1236&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: &amp;nbsp; https://aws.amazon.com/ko/s3/features/vectors/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;S3 vector 설정&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;S3 vector를 만드면 3가지 설정이 가능합니다.&lt;br /&gt;1. Dimension(차원)&lt;br /&gt;2. 거리측정방법: 코사인과 유클리드 중 선택&lt;br /&gt;3. Non-filterable metadata: vector 내 사용하는 인덱스&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1928&quot; data-origin-height=&quot;1236&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzJHRN/dJMcagleQaz/KhnFrOXKg3oR6nU7OQD6n0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzJHRN/dJMcagleQaz/KhnFrOXKg3oR6nU7OQD6n0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzJHRN/dJMcagleQaz/KhnFrOXKg3oR6nU7OQD6n0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzJHRN%2FdJMcagleQaz%2FKhnFrOXKg3oR6nU7OQD6n0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1928&quot; height=&quot;1236&quot; data-origin-width=&quot;1928&quot; data-origin-height=&quot;1236&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;실습&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실습자료: &lt;a href=&quot;https://github.com/choisungwook/portfolio/blob/master/aws/bedrock&quot;&gt;https://github.com/choisungwook/portfolio/blob/master/aws/bedrock&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;AWS 리소스 생성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 테라폼으로 리소스를 생성했습니다.&lt;br /&gt;- 생성한 리소스: data source S3, S3 vector, Bedrock knowledge base&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;S3 vector에 데이터를 저장&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;data source s3에 있는 products.md를 S3 vector에 넣어보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2276&quot; data-origin-height=&quot;1850&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYAQVR/dJMcagFA8Ab/U6Y0JElFqfXLYhhn4daxR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYAQVR/dJMcagFA8Ab/U6Y0JElFqfXLYhhn4daxR0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYAQVR/dJMcagFA8Ab/U6Y0JElFqfXLYhhn4daxR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYAQVR%2FdJMcagFA8Ab%2FU6Y0JElFqfXLYhhn4daxR0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2276&quot; height=&quot;1850&quot; data-origin-width=&quot;2276&quot; data-origin-height=&quot;1850&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Bedrock sync만 클릭하면 간단히 producs.md가 S3 vector에 저장됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;792&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDKUMO/dJMcaiclpZt/Wgk7y4DMdNICXfQDQTkAlk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDKUMO/dJMcaiclpZt/Wgk7y4DMdNICXfQDQTkAlk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDKUMO/dJMcaiclpZt/Wgk7y4DMdNICXfQDQTkAlk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDKUMO%2FdJMcaiclpZt%2FWgk7y4DMdNICXfQDQTkAlk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;792&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;792&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Bedrock sync는 data source의 문서를 읽고, chunking하고, embedding model을 호출한 뒤, 만들어진 vector와 metadata를 S3 Vectors index에 저장합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4396&quot; data-origin-height=&quot;1798&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dLrYLa/dJMcafzTL2s/kZGFQIUT2E4xJiE4lIyECk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dLrYLa/dJMcafzTL2s/kZGFQIUT2E4xJiE4lIyECk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dLrYLa/dJMcafzTL2s/kZGFQIUT2E4xJiE4lIyECk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdLrYLa%2FdJMcafzTL2s%2FkZGFQIUT2E4xJiE4lIyECk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;4396&quot; height=&quot;1798&quot; data-origin-width=&quot;4396&quot; data-origin-height=&quot;1798&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;S3 vector 내용 확인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Bedrock sync가 끝난 뒤에는 S3 Vectors API로 결과를 확인해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 vector key와 Bedrock metadata를 조회합니다.&lt;/p&gt;
&lt;pre class=&quot;dsconfig&quot;&gt;&lt;code&gt;aws s3vectors list-vectors \
  --index-arn &quot;{S3 vector arn}&quot; \
  --return-metadata \
  --max-items 5 \
  --region ap-northeast-2 \
  --query 'vectors[].{key:key,text:metadata.AMAZON_BEDROCK_TEXT,metadata:metadata.AMAZON_BEDROCK_METADATA}'&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1224&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3DNma/dJMcafzTL2y/PiR6jGFCMKKtoMQSrjOFUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3DNma/dJMcafzTL2y/PiR6jGFCMKKtoMQSrjOFUK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3DNma/dJMcafzTL2y/PiR6jGFCMKKtoMQSrjOFUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3DNma%2FdJMcafzTL2y%2FPiR6jGFCMKKtoMQSrjOFUK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;1224&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1224&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리를 보면 AMAZON_BEDROCK_TEXT와 AMAZON_BEDROCK_METADATA를 사용했습니다. 이 두개 메타데이터는 S3 vector에 설정한 인덱스입니다.&lt;br /&gt;- AMAZON_BEDROCK_TEXT는 Bedrock이 만든 chunk text&lt;br /&gt;- AMAZON_BEDROCK_METADATA에는 원본 문서 위치 같은 Bedrock 관리 metadata&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1616&quot; data-origin-height=&quot;1146&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cHTHS8/dJMcahYKWnU/lbzzHHrPqxckKLothkqxQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cHTHS8/dJMcahYKWnU/lbzzHHrPqxckKLothkqxQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cHTHS8/dJMcahYKWnU/lbzzHHrPqxckKLothkqxQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcHTHS8%2FdJMcahYKWnU%2FlbzzHHrPqxckKLothkqxQK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1616&quot; height=&quot;1146&quot; data-origin-width=&quot;1616&quot; data-origin-height=&quot;1146&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 vector를 다시 보고 싶으면 key를 하나 가져와 get-vectors로 조회합니다.&lt;/p&gt;
&lt;pre class=&quot;dsconfig&quot;&gt;&lt;code&gt;VECTOR_KEY=$(aws s3vectors list-vectors \
  --index-arn &quot;{S3 vector arn}&quot; \
  --max-items 1 \
  --region ap-northeast-2 \
  --query 'vectors[0].key' \
  --output text)

aws s3vectors get-vectors \
  --index-arn &quot;{S3 vector arn}&quot; \
  --keys &quot;$VECTOR_KEY&quot; \
  --return-metadata \
  --return-data \
  --region ap-northeast-2 \
  --query 'vectors[0].{key:key,text:metadata.AMAZON_BEDROCK_TEXT,vector_preview:data.float32[:10]}'&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Bedrock console에서 테스트하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS 콘솔에서 &amp;ldquo;Test Knowledge Base&amp;rdquo; 기능으로 AI모델와 RAG연동 테스트를 할 수 있습니다.&lt;br /&gt;1.Bedrock &amp;gt; Knowledge Bases&lt;br /&gt;2.생성된 Knowledge Base 선택&lt;br /&gt;3.Test knowledge base 선택&lt;br /&gt;4.generation model 선택&lt;br /&gt;5.질문 입력&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2878&quot; data-origin-height=&quot;1416&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/s3YET/dJMcahYKWn1/riCU9dGMSnIAjRwpKKVaC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/s3YET/dJMcahYKWn1/riCU9dGMSnIAjRwpKKVaC0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/s3YET/dJMcahYKWn1/riCU9dGMSnIAjRwpKKVaC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fs3YET%2FdJMcahYKWn1%2FriCU9dGMSnIAjRwpKKVaC0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2878&quot; height=&quot;1416&quot; data-origin-width=&quot;2878&quot; data-origin-height=&quot;1416&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 질문은 아래처럼 넣었습니다. 기대한 결과는 S3 vector에 저장된 데이터를 기반으로 답변을 주는겁니다.&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;regular fit blue or white formal shirt for men&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2892&quot; data-origin-height=&quot;1594&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUM0x1/dJMcaipQloB/73kv2OrGotiX13pgNvxnQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUM0x1/dJMcaipQloB/73kv2OrGotiX13pgNvxnQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUM0x1/dJMcaipQloB/73kv2OrGotiX13pgNvxnQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUM0x1%2FdJMcaipQloB%2F73kv2OrGotiX13pgNvxnQk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2892&quot; height=&quot;1594&quot; data-origin-width=&quot;2892&quot; data-origin-height=&quot;1594&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;참고자료&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AWS, What is RAG?: &lt;a href=&quot;https://aws.amazon.com/ko/what-is/retrieval-augmented-generation/&quot;&gt;https://aws.amazon.com/ko/what-is/retrieval-augmented-generation/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Amazon S3 Vectors 소개: &lt;a href=&quot;https://aws.amazon.com/ko/blogs/korea/introducing-amazon-s3-vectors-first-cloud-storage-with-native-vector-support-at-scale/&quot;&gt;https://aws.amazon.com/ko/blogs/korea/introducing-amazon-s3-vectors-first-cloud-storage-with-native-vector-support-at-scale/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;S3 Vectors with Bedrock Knowledge Bases: &lt;a href=&quot;https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-vectors-bedrock-kb.html&quot;&gt;https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-vectors-bedrock-kb.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Bedrock Knowledge Bases vector store setup: &lt;a href=&quot;https://docs.aws.amazon.com/bedrock/latest/userguide/knowledge-base-setup.html&quot;&gt;https://docs.aws.amazon.com/bedrock/latest/userguide/knowledge-base-setup.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Titan Text Embeddings V2: &lt;a href=&quot;https://docs.aws.amazon.com/bedrock/latest/userguide/titan-embedding-models.html&quot;&gt;https://docs.aws.amazon.com/bedrock/latest/userguide/titan-embedding-models.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Terraform aws_bedrockagent_knowledge_base: &lt;a href=&quot;https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/bedrockagent_knowledge_base&quot;&gt;https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/bedrockagent_knowledge_base&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>전공영역 공부 기록</category>
      <category>Ai</category>
      <category>aws</category>
      <category>bedrock</category>
      <author>악분</author>
      <guid isPermaLink="true">https://malwareanalysis.tistory.com/931</guid>
      <comments>https://malwareanalysis.tistory.com/931#entry931comment</comments>
      <pubDate>Mon, 18 May 2026 22:27:50 +0900</pubDate>
    </item>
    <item>
      <title>GenerativeAiOnKubernetes 스터디 - 챕터 4장 RAG, Lora 파인튜닝</title>
      <link>https://malwareanalysis.tistory.com/930</link>
      <description>&lt;h1 id=&quot;이-글의-주제&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;이 글의 주제&lt;/h1&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;챕터 4의 주제는 &amp;ldquo;GenAI Model Optimization for Domain-Specific Use Cases&amp;rdquo;입니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;책에서 이야기하는 건 GPT, claude 등 알려진 AI 모델(Foundation model)은 모든  분야를 다루기 때문에, 특정 도메인 문제를 해결하려면 방법이 필요하다 것입니다. 방법들은 프롬프트 엔지니어링, knowledge 연결, 파인튜닝이 있습니다. 그리고 자연스럽게 LangChain으로 Tool을 실습하고 RAG, 파인튜닝을 다룹니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;원본 예제:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions&quot;&gt;https://github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;수정한 예제:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://github.com/choisungwook/portfolio/tree/master/computer_science/ai/RAG&quot;&gt;https://github.com/choisungwook/portfolio/tree/master/computer_science/ai/RAG&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;사전지식---ai-모델을-잘-쓰기-위해-등장한-소프트웨어---ai-agent&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;사전지식 - AI 모델을 잘 쓰기 위해 등장한 소프트웨어 - AI agent&lt;/h1&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이전 블로그에 설명한 것 처럼&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;AI모델을 잘 사용하기 위해 소프트웨어가 등장&lt;/b&gt;했습니다. claude code, codex CLI 등이 이 소프트웨어에 해당이 됩니다. 2025년 12월부터 사람들에게 입소문이난 하네스엔지니어링이 소프트웨어 기능에 해당이 됩니다. AI 소프트웨어를 오늘날 AI agent라고 부릅니다.&lt;br /&gt;- AI agent 설명 이전 글:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://malwareanalysis.tistory.com/926&quot;&gt;https://malwareanalysis.tistory.com/926&lt;/a&gt;&lt;br /&gt;- &lt;b&gt;codex CLI 동작를 설명하는 글 (추천&lt;/b&gt;):&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://openai.com/index/unrolling-the-codex-agent-loop/&quot;&gt;https://openai.com/index/unrolling-the-codex-agent-loop/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;358&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cDJi40/dJMcaaL4DVM/iYfM9qnIjS3PUxDqOpqWUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cDJi40/dJMcaaL4DVM/iYfM9qnIjS3PUxDqOpqWUk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cDJi40/dJMcaaL4DVM/iYfM9qnIjS3PUxDqOpqWUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcDJi40%2FdJMcaaL4DVM%2FiYfM9qnIjS3PUxDqOpqWUk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;358&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;358&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;AI agent의 대표적인 기능은 AI 모델에게 전송할 프롬프트를 조립하는 것입니다. 사용자의 요청이 최대한 원하는 대답을 얻기 위해 AI agent는 사용자 프롬프트와 여러 context를 더해서 프롬프트를 조립합니다. 그리고 조립된 프롬프트로 AI model에 요청합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4410&quot; data-origin-height=&quot;1914&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BUVRW/dJMcagMlESD/m0HL4LdKRoTeAHbgPlym21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BUVRW/dJMcagMlESD/m0HL4LdKRoTeAHbgPlym21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BUVRW/dJMcagMlESD/m0HL4LdKRoTeAHbgPlym21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBUVRW%2FdJMcagMlESD%2Fm0HL4LdKRoTeAHbgPlym21%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;4410&quot; height=&quot;1914&quot; data-origin-width=&quot;4410&quot; data-origin-height=&quot;1914&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그 이외에 context window, tools, agent loop 등이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;langchain-이란&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;Langchain 이란&lt;/h1&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;AI agent를 처음 부터 만드는 것 입문자에게 매우 어렵습니다. 그래서 쉽게 구현하기 위한 여러 오픈소스들이 나왔는데 Langchain이 그 중 하나입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;langchain으로 AI agent를 만들려면 아래처럼 단 몇줄이면 됩니다. 아래는 openAI모델을 사용했습니다.&lt;/p&gt;
&lt;div id=&quot;cb1&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;from langchain.agents import create_agent
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model=&quot;gpt-5.5&quot;)
agent = create_agent(
  model=llm,
  system_prompt=&quot; {시스템 프롬프트}&quot;
)
result = agent.invoke({
  &quot;messages&quot;: [
    {
      &quot;role&quot;: &quot;user&quot;,
      &quot;content&quot;: f&quot;{사용자 메세지를 포함한 조립된 메세지}&quot;,
    }
  ]
})&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;ai-모델-선택&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;AI 모델 선택&lt;/h1&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 예제에서 사용하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;AI 모델은 openAI gpt-5-nano를 사용&lt;/b&gt;했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;예제-1---tools&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;예제 1 -  tools&lt;/h1&gt;
&lt;h2 id=&quot;이론&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;이론&lt;/h2&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;AI 모델이 내 컴퓨터의 파일을 조회하거나 파일을 쓰는 작업은 어떻게 할까요? 바로 조회하거나 파일을 쓰는  기능을 AI 모델에게 재공하는 겁니다. 이 기능을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;tools&lt;/b&gt;라고 부릅니다. tools는 AI agent를 실행하는 pc에서 실행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;가령 서울날씨는 조회하는 기능을 AI모델에게 제공하면, AI모델은 실시간 서울날씨를 조회할 수 있습니다.&lt;/p&gt;
&lt;div id=&quot;cb2&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;@tool
def get_seoul_weather() -&amp;gt; dict | str:
    &quot;&quot;&quot;서울의 현재 날씨를 반환한다. 기온(섭씨), 풍속, 날씨 코드를 포함.&quot;&quot;&quot;
    url = &quot;https://api.open-meteo.com/v1/forecast&quot;
    params = {&quot;latitude&quot;: 37.5665, &quot;longitude&quot;: 126.9780, &quot;current_weather&quot;: &quot;true&quot;}
    try:
        r = httpx.get(url, params=params, timeout=10)
        r.raise_for_status()
        return r.json()[&quot;current_weather&quot;]
    except (httpx.HTTPError, KeyError, ValueError) as e:
        return f&quot;ERROR: open-meteo 호출 실패 &amp;mdash; {type(e).__name__}: {e}&quot;


llm = ChatOpenAI(model=chat_model)
agent = create_agent(
  model=llm,
  tools=[get_seoul_weather],
  system_prompt=&quot;Use tools for deterministic calculations.&quot;,
  debug=True,
)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;실시간 서울 날씨는 AI agent가 실행하여 AI 모델에게 전달합니다.&lt;/p&gt;
&lt;div id=&quot;cb3&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;from langchain.agents import create_agent
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model=chat_model)
agent = create_agent(
  model=llm,
  system_prompt=&quot;Use tools for deterministic calculations.&quot;,
  debug=True,
)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;362&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biESTP/dJMcagMlESF/Uu8I7N1a2gRJZoHMiuKaPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biESTP/dJMcagMlESF/Uu8I7N1a2gRJZoHMiuKaPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biESTP/dJMcagMlESF/Uu8I7N1a2gRJZoHMiuKaPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiESTP%2FdJMcagMlESF%2FUu8I7N1a2gRJZoHMiuKaPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;362&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;362&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;624&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cGYAoF/dJMcaja6kox/YMkkxu9O6CGM10sG2pM1Pk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cGYAoF/dJMcaja6kox/YMkkxu9O6CGM10sG2pM1Pk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cGYAoF/dJMcaja6kox/YMkkxu9O6CGM10sG2pM1Pk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcGYAoF%2FdJMcaja6kox%2FYMkkxu9O6CGM10sG2pM1Pk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;624&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;624&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;실습&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;실습&lt;/h2&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;과일이 몇개인지 세는 문제를 AI에게 시키는게 예제 1번입니다. 단, tools를 사용하여 과일이 몇개인지 세야합니다.&lt;/p&gt;
&lt;div id=&quot;cb4&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;fruit_list = [&quot;Apple&quot;, &quot;Banana&quot;, &quot;Apple&quot;, &quot;Peaches&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;count_items라는 tool을 생성합니다. 이 tool은 파이썬 collection 패키지를 사용해서 key의 value가 몇개인지 셉니다.&lt;/p&gt;
&lt;pre class=&quot;python&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;&lt;code&gt;from collections import Counter

from langchain_core.tools import tool

@tool
def count_items(items: list[str]) -&amp;gt; dict[str, int]:
  &quot;&quot;&quot;Count duplicate strings in a list.&quot;&quot;&quot;
  return dict(Counter(items))&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;langchain으로 AI agent를 생성할때 tool을 설정합니다.&lt;/p&gt;
&lt;div id=&quot;cb6&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;from langchain.agents import create_agent
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model='gpt-5-nano')
agent = create_agent(
  model=llm,
  tools=[count_items],
  system_prompt=&quot;Use tools for deterministic calculations.&quot;,
  debug=True,
)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 invoke함수를 실행하여 AI모델에게 과일을 세라고 요청합니다.&lt;/p&gt;
&lt;div id=&quot;cb7&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;result = agent.invoke({
  &quot;messages&quot;: [
    {
      &quot;role&quot;: &quot;user&quot;,
      &quot;content&quot;: f&quot;Count each fruit in this list: {fruit_list}&quot;,
    }
  ]
})

result[&quot;messages&quot;][-1].content&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;결과를 출력하면 Apple:2, Banana:1, Peaches:1로 세었다고 보입니다. 그리고 debug로그로 tools을 호출하는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1488&quot; data-origin-height=&quot;754&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZGEVF/dJMcaarOFsh/dUNMMRKsg3IxtSteiza22K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZGEVF/dJMcaarOFsh/dUNMMRKsg3IxtSteiza22K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZGEVF/dJMcaarOFsh/dUNMMRKsg3IxtSteiza22K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZGEVF%2FdJMcaarOFsh%2FdUNMMRKsg3IxtSteiza22K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1488&quot; height=&quot;754&quot; data-origin-width=&quot;1488&quot; data-origin-height=&quot;754&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;LANGSMITH를 사용하면 AI모델 호출과정을 트레이싱 할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2608&quot; data-origin-height=&quot;2242&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/D2FGC/dJMcacDaYYv/cgB78SVO7ciY1DHwhnSCm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/D2FGC/dJMcacDaYYv/cgB78SVO7ciY1DHwhnSCm0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/D2FGC/dJMcacDaYYv/cgB78SVO7ciY1DHwhnSCm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FD2FGC%2FdJMcacDaYYv%2FcgB78SVO7ciY1DHwhnSCm0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2608&quot; height=&quot;2242&quot; data-origin-width=&quot;2608&quot; data-origin-height=&quot;2242&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;예제-2---rag&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;예제 2 - RAG&lt;/h1&gt;
&lt;h2 id=&quot;이론-1&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;이론&lt;/h2&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;RAG(Retrieval-Augmented Generation)는 검색증강생성으로서, &lt;b&gt;학습된 데이터 이외에 새로운 데이터를 참조하도록 하는 프로세스&lt;/b&gt;입니다. 참조하는 새로운 데이터를 knowledge source라고 부르며, 보통 vector DB에서 데이터를 관리합니다. 외부 데이터를 참조하는 행위를 retrieval(검색)이라고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2716&quot; data-origin-height=&quot;694&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8ksdX/dJMcacDaYYx/43Opju09GAHkcqCnEtNUC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8ksdX/dJMcacDaYYx/43Opju09GAHkcqCnEtNUC1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8ksdX/dJMcacDaYYx/43Opju09GAHkcqCnEtNUC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8ksdX%2FdJMcacDaYYx%2F43Opju09GAHkcqCnEtNUC1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2716&quot; height=&quot;694&quot; data-origin-width=&quot;2716&quot; data-origin-height=&quot;694&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;외부 데이터를 Vector DB에 저장하려면, RDMS처럼 데이터를 그대로 바로 저장할 수 없고 임베딩이라는 과정을 거쳐야합니다. 사람이 쓰는 자연어는 컴퓨터가 다룰 없는 자료형이기 때문에 숫자배열로 변환해야 하는데 이 변환 과정을 임베딩이라고 합니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;임베딩과정에서 임베딩 언어 AI모델이 사용&lt;/b&gt;됩니다. 또한, 임베딩할 데이터 크기가 크면 글자를 분리하는 과정이 필요한데, 이 과정을 chunk라고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4930&quot; data-origin-height=&quot;1412&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmHW5R/dJMcaiXBVfx/k5tIgi2Rkb4Oby0pgiCZR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmHW5R/dJMcaiXBVfx/k5tIgi2Rkb4Oby0pgiCZR0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmHW5R/dJMcaiXBVfx/k5tIgi2Rkb4Oby0pgiCZR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmHW5R%2FdJMcaiXBVfx%2Fk5tIgi2Rkb4Oby0pgiCZR0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;4930&quot; height=&quot;1412&quot; data-origin-width=&quot;4930&quot; data-origin-height=&quot;1412&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; AI agent에서는 RAG를 사용하면, 프롬프트를 조립할때  retrieval 결과를 추가합니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;&lt;code&gt;retrieval result(Enhanced context) + system prompt + user prompt&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2104&quot; data-origin-height=&quot;1286&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ceVrlK/dJMcagyK1Wc/oaCJ5Qok2XCGYuUyD2q3wK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ceVrlK/dJMcagyK1Wc/oaCJ5Qok2XCGYuUyD2q3wK/img.png&quot; data-alt=&quot;AWS &amp;amp;nbsp; https://aws.amazon.com/ko/what-is/retrieval-augmented-generation/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ceVrlK/dJMcagyK1Wc/oaCJ5Qok2XCGYuUyD2q3wK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FceVrlK%2FdJMcagyK1Wc%2FoaCJ5Qok2XCGYuUyD2q3wK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2104&quot; height=&quot;1286&quot; data-origin-width=&quot;2104&quot; data-origin-height=&quot;1286&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;AWS &amp;nbsp; https://aws.amazon.com/ko/what-is/retrieval-augmented-generation/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;실습-1&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;실습&lt;/h2&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;사용자에 맞는 셔츠를 추천해주는 시스템을 실습합니다.  &lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3682&quot; data-origin-height=&quot;466&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d8OK7H/dJMcah5ykJh/I6pFCm6xyjbgWDVgx68Z60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d8OK7H/dJMcah5ykJh/I6pFCm6xyjbgWDVgx68Z60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d8OK7H/dJMcah5ykJh/I6pFCm6xyjbgWDVgx68Z60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd8OK7H%2FdJMcah5ykJh%2FI6pFCm6xyjbgWDVgx68Z60%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3682&quot; height=&quot;466&quot; data-origin-width=&quot;3682&quot; data-origin-height=&quot;466&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;AI agent는 RAG를 사용해서 사용자 정보에 맞는 셔츠제품정보를 AI모델에게 전달합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4744&quot; data-origin-height=&quot;2250&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVPtHp/dJMcahqWDR5/OH73c9S2TiWl3QYk9oUWik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVPtHp/dJMcahqWDR5/OH73c9S2TiWl3QYk9oUWik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVPtHp/dJMcahqWDR5/OH73c9S2TiWl3QYk9oUWik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVPtHp%2FdJMcahqWDR5%2FOH73c9S2TiWl3QYk9oUWik%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;4744&quot; height=&quot;2250&quot; data-origin-width=&quot;4744&quot; data-origin-height=&quot;2250&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;[1. 데이터 다운로드]&lt;br /&gt;셔츠 상품 데이터는 kaggle에서 csv파일로 다운로드 받을 수 있습니다.&lt;br /&gt;- kaggle 주소:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://www.kaggle.com/datasets/shivamb/fashion-clothing-products-catalog&quot;&gt;https://www.kaggle.com/datasets/shivamb/fashion-clothing-products-catalog&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;[2. Vector DB에 데이터 저장]&lt;br /&gt;다운로드 받은 셔츠 상품 데이터는 임베딩 과정을 거쳐 Vector DB에 저장합니다. Vector DB는 FAISS를 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;임베딩 모델은 openAI text-embedding-3-small을 사용합니다. 그리고 차원은 1536으로 설정합니다.&lt;/p&gt;
&lt;div id=&quot;cb9&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;from langchain_openai import OpenAIEmbeddings

embeddings = OpenAIEmbeddings(
  model='text-embedding-3-small',
  dimensions=1536,
)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;임베딩 결과가 궁금하면 embed_query로 테스트해보세요. 아래 예제는 Apple단어를 임베딩한 결과입니다.&lt;/p&gt;
&lt;div id=&quot;cb10&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;sample_vector = embeddings.embed_query(&quot;Apple&quot;)
print(f&quot;embedding dimensions: {len(sample_vector)}&quot;)
print(f&quot;embedding preview: {sample_vector[:10]}&quot;)
print(&quot;embedding vector:&quot;)
print(sample_vector)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1942&quot; data-origin-height=&quot;1064&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3xqI0/dJMcadhLj9w/nftKbxoAdu9bN8aUCqDLPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3xqI0/dJMcadhLj9w/nftKbxoAdu9bN8aUCqDLPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3xqI0/dJMcadhLj9w/nftKbxoAdu9bN8aUCqDLPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3xqI0%2FdJMcadhLj9w%2FnftKbxoAdu9bN8aUCqDLPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1942&quot; height=&quot;1064&quot; data-origin-width=&quot;1942&quot; data-origin-height=&quot;1064&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;임베딩은 vector DB에 데이터를 넣을 때 수행합니다. 이 예제에서는 vector DB로 FAISS를 사용합니다. vector DB는 데이터를 저장할 때 인덱싱과정도 같이 수행합니다. 인덱싱 또한 vector DB마다 구현이 다릅니다.&lt;/p&gt;
&lt;div id=&quot;cb11&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;from langchain_community.vectorstores import FAISS
from langchain_community.document_loaders import CSVLoader

# csv 파일 로드
loader = CSVLoader(file_path=str(data_path), encoding=&quot;utf-8&quot;)
documents = loader.load()

vector_store = FAISS.from_documents(documents, embeddings)
vector_store.index.ntotal&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;[3. Retrieval 예시]&lt;br /&gt;사용자가 요청한 제품이 vector DB에 있는지 조회합니다. Vector DB의 데이터 검색(Retrieval) 품질은 Vector DB 알고리즘에 따라 다릅니다. 이 예제에서는 FAISS를 vector DB로 사용하니 FAISS 알고리즘이 품질을 좌지우지 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;검색 결과는 유사도 입니다. 즉, 확률입니다. 아래 예제는 유사도가 높은 것의 k개를 가져옵니다.&lt;/p&gt;
&lt;div id=&quot;cb12&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;query = (
  &quot;Shirts which are good for Men, have regular fit and not the slim fit, &quot;
  &quot;can be used for a formal occasion, and have a color of either Blue or White&quot;
)

results = vector_store.similarity_search_with_score(query, k=top_k)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2168&quot; data-origin-height=&quot;1456&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qARE6/dJMcag6B2Ay/7XPHvuF9Ba82siSf5knnX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qARE6/dJMcag6B2Ay/7XPHvuF9Ba82siSf5knnX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qARE6/dJMcag6B2Ay/7XPHvuF9Ba82siSf5knnX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqARE6%2FdJMcag6B2Ay%2F7XPHvuF9Ba82siSf5knnX0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2168&quot; height=&quot;1456&quot; data-origin-width=&quot;2168&quot; data-origin-height=&quot;1456&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;[4. 프롬프트 조립]&lt;br /&gt;Vector DB에 가져온 데이터를 AI모델에게 제공하기 위해 프롬프트를 조립합니다. 아래 예제에서는 vector DB결과를 Product context에 넣습니다. 그리고 사용자의 요청은 Question에 넣습니다.&lt;/p&gt;
&lt;div id=&quot;cb13&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;
&lt;pre class=&quot;ceylon&quot;&gt;&lt;code&gt;from IPython.display import Markdown, display
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model=chat_model)

results = vector_store.similarity_search_with_score(query, k=top_k)
context = &quot;&quot;.join(
  f&quot;[Document {rank}]\n{doc.page_content}\n&quot;
  for rank, (doc, _score) in enumerate(results, start=1)
)

prompt = f&quot;&quot;&quot;
You are a product recommendation assistant.
Use only the product context below. If the context is insufficient, say so.

Product context:
{context}

Question:
{query}

Respond in Korean. First provide a compact markdown table, then recommend one product with a reason.
&quot;&quot;&quot;.strip()

response = llm.invoke(prompt)
display(Markdown(response.content))&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;AI모델의 답변은 아래와 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3048&quot; data-origin-height=&quot;956&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PXGYg/dJMcacDaYYC/06wB77S6HwUeBttnbak0v0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PXGYg/dJMcacDaYYC/06wB77S6HwUeBttnbak0v0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PXGYg/dJMcacDaYYC/06wB77S6HwUeBttnbak0v0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPXGYg%2FdJMcacDaYYC%2F06wB77S6HwUeBttnbak0v0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3048&quot; height=&quot;956&quot; data-origin-width=&quot;3048&quot; data-origin-height=&quot;956&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;langsmith로 AI모델을 추적하면, tools하고 다르게 AI모델을 한번 호출합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2898&quot; data-origin-height=&quot;2138&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/v2YJO/dJMcaaehr6k/5KKPHYUtisBqnWhBrzZY70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/v2YJO/dJMcaaehr6k/5KKPHYUtisBqnWhBrzZY70/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/v2YJO/dJMcaaehr6k/5KKPHYUtisBqnWhBrzZY70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fv2YJO%2FdJMcaaehr6k%2F5KKPHYUtisBqnWhBrzZY70%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2898&quot; height=&quot;2138&quot; data-origin-width=&quot;2898&quot; data-origin-height=&quot;2138&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;번째-예제-파인튜닝&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;3번째 예제: 파인튜닝&lt;/h1&gt;
&lt;h2 id=&quot;이론-2&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;이론&lt;/h2&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;파인튜닝은 기존의 모델을 다시 학습하는 방법입니다. 학습을 하는 것이기 때문에 시간이 오래걸리고 GPU 자원도 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;시간이 지나면서 시간와 자원을 최소화하는 방법이 계속 연구되고 있는데,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;이 책은 Lora를 사용하여 파인튜닝 시간과 자원을 줄입니다&lt;/b&gt;. 저는 mlx.lora를 사용했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;실습-2&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;실습&lt;/h2&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;[1. 모델 다운로드]&lt;br /&gt;저는 Qwen2.5-0.5B-Instruct 모델을 다운로드 받았습니다. lm studio에서 다운로드 받았습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3280&quot; data-origin-height=&quot;1826&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bri54x/dJMcahEpTmG/d5BLwOqx0c3gZK1VguNc01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bri54x/dJMcahEpTmG/d5BLwOqx0c3gZK1VguNc01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bri54x/dJMcahEpTmG/d5BLwOqx0c3gZK1VguNc01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbri54x%2FdJMcahEpTmG%2Fd5BLwOqx0c3gZK1VguNc01%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3280&quot; height=&quot;1826&quot; data-origin-width=&quot;3280&quot; data-origin-height=&quot;1826&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;다운로드한 모델은 $HOME/.lmstudio/models에 있습니다.&lt;/p&gt;
&lt;div id=&quot;cb14&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;~/.lmstudio/models/lmstudio-community/Qwen2.5-0.5B-Instruct-MLX-4bit&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;[2. 파인튜닝 전 결과 확인]&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;저는 subprocess로 mlx를 테스트했습니다.&lt;/p&gt;
&lt;div id=&quot;cb15&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;# mlx 실행하는 함수
def run_command(command):
  result = subprocess.run(
    command,
    cwd=project_root,
    text=True,
    capture_output=True,
  )
  if result.stdout:
    print(result.stdout)
  if result.returncode != 0:
    if result.stderr:
      print(result.stderr)
    raise RuntimeError(f&quot;command failed: {' '.join(command)}&quot;)
  return result.stdout.strip()
  
base_model_id='~/.lmstudio/models/lmstudio-community/Qwen2.5-0.5B-Instruct-MLX-4bit'
test_prmopt='[MyElite Loyalty Program FAQ]: What is the cost of the MyElite Loyalty Program?
'
before_output = run_command([
  &quot;uv&quot;,
  &quot;run&quot;,
  &quot;--extra&quot;,
  &quot;mlx&quot;,
  &quot;mlx_lm.generate&quot;,
  &quot;--model&quot;,
  base_model_id,
  &quot;--prompt&quot;,
  test_prompt,
  &quot;--max-tokens&quot;,
  &quot;120&quot;,
])&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Qwen모델은 학습된 데이터가 없기 때문에 추상적으로 대답합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3546&quot; data-origin-height=&quot;1270&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cUQegh/dJMcafUbWFM/IMO2W0KqK0gonE1f7h9F90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cUQegh/dJMcafUbWFM/IMO2W0KqK0gonE1f7h9F90/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cUQegh/dJMcafUbWFM/IMO2W0KqK0gonE1f7h9F90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcUQegh%2FdJMcafUbWFM%2FIMO2W0KqK0gonE1f7h9F90%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3546&quot; height=&quot;1270&quot; data-origin-width=&quot;3546&quot; data-origin-height=&quot;1270&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;[3. 파인튜닝 학습 데이터셋 준비]&lt;br /&gt;저는 codex CLI로 학습데이터셋을 준비했습니다.&lt;/p&gt;
&lt;div id=&quot;cb16&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;
&lt;pre class=&quot;smalltalk&quot;&gt;&lt;code&gt;train_examples = [
    {
      &quot;prompt&quot;: &quot;[MyElite Loyalty Program FAQ]: What is the cost of the MyElite Loyalty Program?&quot;,
      &quot;completion&quot;: &quot;The MyElite Loyalty Program costs 99 USD per year. This fee is non-refundable, even if you cancel the membership.&quot;,
    },
    {
      &quot;prompt&quot;: &quot;[MyElite Loyalty Program FAQ]: Can I cancel my MyElite Loyalty Program membership?&quot;,
      &quot;completion&quot;: &quot;Yes, you can cancel your membership at any time. You will continue to receive benefits until the end of your current subscription period.&quot;,
    },
    {
      &quot;prompt&quot;: &quot;[MyElite Loyalty Program FAQ]: Is the MyElite annual fee refundable?&quot;,
      &quot;completion&quot;: &quot;No. The 99 USD annual fee is non-refundable, including when the membership is canceled before the subscription period ends.&quot;,
    },
    {
      &quot;prompt&quot;: &quot;[MyElite Loyalty Program FAQ]: How long do benefits continue after cancellation?&quot;,
      &quot;completion&quot;: &quot;Benefits continue until the end of the paid subscription period, even after cancellation.&quot;,
    },
    {
      &quot;prompt&quot;: &quot;[MyElite Loyalty Program FAQ]: What is the maximum cashback I can earn?&quot;,
      &quot;completion&quot;: &quot;MyElite members can earn up to 2% cashback on eligible purchases.&quot;,
    },
    {
      &quot;prompt&quot;: &quot;[MyElite Loyalty Program FAQ]: Does MyElite cashback apply to every purchase?&quot;,
      &quot;completion&quot;: &quot;No. Cashback applies only to eligible purchases under the MyElite Loyalty Program terms.&quot;,
    },
    {
      &quot;prompt&quot;: &quot;[MyElite Loyalty Program FAQ]: Can I transfer my MyElite membership to another person?&quot;,
      &quot;completion&quot;: &quot;No. MyElite membership is tied to the account holder and cannot be transferred to another person.&quot;,
    },
    {
      &quot;prompt&quot;: &quot;[MyElite Loyalty Program FAQ]: When does a MyElite subscription renew?&quot;,
      &quot;completion&quot;: &quot;A MyElite subscription renews annually unless the member cancels before the renewal date.&quot;,
    },
    {
      &quot;prompt&quot;: &quot;[MyElite Loyalty Program FAQ]: What happens if I cancel before renewal?&quot;,
      &quot;completion&quot;: &quot;If you cancel before renewal, you will not be charged for the next year and your current benefits remain active until the subscription period ends.&quot;,
    },
    {
      &quot;prompt&quot;: &quot;[MyElite Loyalty Program FAQ]: Do I lose benefits immediately after cancellation?&quot;,
      &quot;completion&quot;: &quot;No. You keep MyElite benefits until the end of the subscription period that has already been paid for.&quot;,
    },
    {
      &quot;prompt&quot;: &quot;[MyElite Loyalty Program FAQ]: Is MyElite billed monthly or yearly?&quot;,
      &quot;completion&quot;: &quot;MyElite is billed yearly at 99 USD per year.&quot;,
    },
    {
      &quot;prompt&quot;: &quot;[MyElite Loyalty Program FAQ]: What should I say if the FAQ does not contain the answer?&quot;,
      &quot;completion&quot;: &quot;If the MyElite FAQ does not contain the answer, say that the information is not available in the FAQ instead of guessing.&quot;,
    },
  ]

  valid_examples = [
    {
      &quot;prompt&quot;: &quot;[MyElite Loyalty Program FAQ]: How much is MyElite each year?&quot;,
      &quot;completion&quot;: &quot;MyElite costs 99 USD per year.&quot;,
    },
    {
      &quot;prompt&quot;: &quot;[MyElite Loyalty Program FAQ]: If I cancel today, do I get my money back?&quot;,
      &quot;completion&quot;: &quot;No. The annual fee is non-refundable, but benefits continue until the end of the subscription period.&quot;,
    },
    {
      &quot;prompt&quot;: &quot;[MyElite Loyalty Program FAQ]: What cashback rate can members receive?&quot;,
      &quot;completion&quot;: &quot;Members can earn up to 2% cashback on eligible purchases.&quot;,
    },
  ]

  test_examples = [
    {
      &quot;prompt&quot;: &quot;[MyElite Loyalty Program FAQ]: What is the cost of the MyElite Loyalty Program?&quot;,
      &quot;completion&quot;: &quot;The MyElite Loyalty Program costs 99 USD per year. The fee is non-refundable.&quot;,
    },
    {
      &quot;prompt&quot;: &quot;[MyElite Loyalty Program FAQ]: Can I cancel and still use benefits?&quot;,
      &quot;completion&quot;: &quot;Yes. After cancellation, benefits remain available until the current subscription period ends.&quot;,
    },
  ]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;[4. 파인튜닝]&lt;br /&gt;mlx_lm.lora를 사용하여 파인튜닝을 했습니다. 파인튜닝은 15초정도 걸렸습니다.&lt;/p&gt;
&lt;div id=&quot;cb17&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;train_command = [
  &quot;uv&quot;,
  &quot;run&quot;,
  &quot;--extra&quot;,
  &quot;mlx&quot;,
  &quot;mlx_lm.lora&quot;,
  &quot;--model&quot;,
  base_model_id,
  &quot;--train&quot;,
  &quot;--data&quot;,
  str(data_dir),
  &quot;--adapter-path&quot;,
  str(adapter_dir),
  &quot;--iters&quot;,
  str(max_iters),
  &quot;--batch-size&quot;,
  str(batch_size),
  &quot;--num-layers&quot;,
  str(num_layers),
  &quot;--mask-prompt&quot;,
]

if report_to != &quot;none&quot;:
  print(&quot;wandb_project is enabled&quot;)
  train_command.extend([&quot;--report-to&quot;, report_to, &quot;--project-name&quot;, wandb_project])

train_output = run_command(train_command)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;wandb가 설정되어 있다면 wandb 콘솔에서 학습을 추적할 수 있습니다. 학습 데이터에 대한 오차(train_loss), 검증 데이터에 대한 오차(val_loss)가 학습 횟수가 증가할 수록 줄어들었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4990&quot; data-origin-height=&quot;2404&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vmqo4/dJMb99M9mib/4K5Nf6yzqZbUhpedCwZCZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vmqo4/dJMb99M9mib/4K5Nf6yzqZbUhpedCwZCZ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vmqo4/dJMb99M9mib/4K5Nf6yzqZbUhpedCwZCZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fvmqo4%2FdJMb99M9mib%2F4K5Nf6yzqZbUhpedCwZCZ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;4990&quot; height=&quot;2404&quot; data-origin-width=&quot;4990&quot; data-origin-height=&quot;2404&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;[5. 파인튜닝된 모델로 다시 질문]&lt;br /&gt;파인튜닝한 모델을 사용하여 다시 질문하면, 원하는 99 USE per year 응답을 얻습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1612&quot; data-origin-height=&quot;1330&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkv5Vs/dJMcag6B2D2/YEXdyGWvGHdOo8OgMXQ9X1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkv5Vs/dJMcag6B2D2/YEXdyGWvGHdOo8OgMXQ9X1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkv5Vs/dJMcag6B2D2/YEXdyGWvGHdOo8OgMXQ9X1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbkv5Vs%2FdJMcag6B2D2%2FYEXdyGWvGHdOo8OgMXQ9X1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1612&quot; height=&quot;1330&quot; data-origin-width=&quot;1612&quot; data-origin-height=&quot;1330&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>전공영역 공부 기록</category>
      <category>Ai</category>
      <category>Finetuning</category>
      <category>rag</category>
      <author>악분</author>
      <guid isPermaLink="true">https://malwareanalysis.tistory.com/930</guid>
      <comments>https://malwareanalysis.tistory.com/930#entry930comment</comments>
      <pubDate>Mon, 18 May 2026 02:43:51 +0900</pubDate>
    </item>
    <item>
      <title>Kubernetes v1.36 업그레이드 전에 확인할 운영 영향과 핸즈온</title>
      <link>https://malwareanalysis.tistory.com/929</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이 글은 kubernetes v1.36 릴리즈 노트를 읽고, 패치 내용 요약과 몇 가지 핸즈온을 더한 글입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;기능-요약&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;새로운 기능&lt;/h1&gt;
&lt;table style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Stable&lt;/td&gt;
&lt;td&gt;&lt;b&gt;MutatingAdmissionPolicy&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;CEL 기반 in-process mutation 정책&lt;/td&gt;
&lt;td&gt;단순 label, field, default injection은 webhook보다 운영 부담이 적음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stable&lt;/td&gt;
&lt;td&gt;&lt;b&gt;User Namespaces for pods&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;컨테이너 root를 host 비권한 UID/GID로 매핑&lt;/td&gt;
&lt;td&gt;multi-tenant 환경과 breakout 방어에 유리하지만 runtime, volume,&lt;span&gt;&amp;nbsp;&lt;/span&gt;securityContext&lt;span&gt;&amp;nbsp;&lt;/span&gt;호환성 확인 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stable&lt;/td&gt;
&lt;td&gt;Fine-grained kubelet API authorization&lt;/td&gt;
&lt;td&gt;kubelet API 권한을&lt;span&gt;&amp;nbsp;&lt;/span&gt;nodes/proxy보다 세밀하게 제어&lt;/td&gt;
&lt;td&gt;monitoring agent 권한을 least privilege로 줄일 수 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stable&lt;/td&gt;
&lt;td&gt;VolumeGroupSnapshot&lt;/td&gt;
&lt;td&gt;여러 PVC를 crash-consistent하게 snapshot&lt;/td&gt;
&lt;td&gt;CSI driver와 snapshot controller 지원이 전제&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stable&lt;/td&gt;
&lt;td&gt;Mutable CSINode allocatable&lt;/td&gt;
&lt;td&gt;CSI driver가 node별 volume attach limit을 동적으로 갱신&lt;/td&gt;
&lt;td&gt;volume attach limit이 바뀌는 환경에서 scheduling 실패를 줄임&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stable&lt;/td&gt;
&lt;td&gt;DRA prioritized alternatives&lt;/td&gt;
&lt;td&gt;DRA device request에 우선순위 기반 fallback 가능&lt;/td&gt;
&lt;td&gt;GPU 모델 fallback 같은 AI/HPC scheduling에 유용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stable&lt;/td&gt;
&lt;td&gt;PSI metrics on cgroup v2&lt;/td&gt;
&lt;td&gt;kubelet이 CPU, memory, I/O stall time을 node, pod, container 수준으로 노출&lt;/td&gt;
&lt;td&gt;단순 사용률보다 node contention 분석에 도움&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alpha&lt;/td&gt;
&lt;td&gt;MemoryQoS with cgroup v2&lt;/td&gt;
&lt;td&gt;memory.high,&lt;span&gt;&amp;nbsp;&lt;/span&gt;memory.min,&lt;span&gt;&amp;nbsp;&lt;/span&gt;memory.low로 pod QoS class별 memory 보호를 조정&lt;/td&gt;
&lt;td&gt;kernel, runtime, kubelet 설정이 맞는 node에서만 실험적으로 검토&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Beta&lt;/td&gt;
&lt;td&gt;Resource health status&lt;/td&gt;
&lt;td&gt;pod status에서 device health 확인&lt;/td&gt;
&lt;td&gt;GPU/가속기 장애 원인 추적에 도움&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Beta&lt;/td&gt;
&lt;td&gt;Strict IP/CIDR validation&lt;/td&gt;
&lt;td&gt;잘못된 IP/CIDR 입력을 더 엄격하게 검증&lt;/td&gt;
&lt;td&gt;오래된 manifest에 비정상 값이 있으면 경고나 실패 가능성 확인 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Beta&lt;/td&gt;
&lt;td&gt;.kuberc&lt;/td&gt;
&lt;td&gt;cluster config와 kubectl 사용자 preference 분리&lt;/td&gt;
&lt;td&gt;개인 CLI 설정과 cluster 접근 설정을 분리 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Beta&lt;/td&gt;
&lt;td&gt;DRA device taints/tolerations&lt;/td&gt;
&lt;td&gt;node taint처럼 device 상태를 scheduling에 반영&lt;/td&gt;
&lt;td&gt;가속기 장애, 점검, 격리 운영에 유용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Beta&lt;/td&gt;
&lt;td&gt;Constrained Impersonation&lt;/td&gt;
&lt;td&gt;impersonation 권한을 수행 가능 action과 함께 제한&lt;/td&gt;
&lt;td&gt;controller 권한 위임 리스크 감소&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Beta&lt;/td&gt;
&lt;td&gt;/statusz,&lt;span&gt;&amp;nbsp;&lt;/span&gt;/flagz&lt;/td&gt;
&lt;td&gt;component 상태와 실행 flag를 HTTP endpoint로 확인&lt;/td&gt;
&lt;td&gt;장애 대응 때 control plane 설정 확인이 쉬워짐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alpha&lt;/td&gt;
&lt;td&gt;HPA scale to zero&lt;/td&gt;
&lt;td&gt;external/object metric 기반으로 replica 0까지 축소&lt;/td&gt;
&lt;td&gt;비용 절감 가능성이 있지만 feature gate와 metric 설계가 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alpha&lt;/td&gt;
&lt;td&gt;Manifest-based admission control config&lt;/td&gt;
&lt;td&gt;admission policy를 API object가 아니라 static file에서 로드&lt;/td&gt;
&lt;td&gt;etcd 장애나 policy 삭제 공격에도 admission 보호 가능성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alpha&lt;/td&gt;
&lt;td&gt;Native histogram metrics&lt;/td&gt;
&lt;td&gt;kube-apiserver 등에서 sparse histogram 기반 metric 실험&lt;/td&gt;
&lt;td&gt;SLI/SLO 해상도 개선 가능성, metric backend 호환성 확인 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;영향도&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;변경되는&amp;nbsp; 기능&lt;/h1&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;가장 눈에 띄는 변경은&lt;span&gt;&amp;nbsp;&lt;/span&gt;Service.spec.externalIPs&lt;span&gt;&amp;nbsp;&lt;/span&gt;deprecation&lt;/b&gt;입니다. v1.36부터&lt;span&gt;&amp;nbsp;&lt;/span&gt;externalIPs를 사용하는 service를 생성하거나 수정하면 warning이 표시됩니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;공식 릴리즈 글과 KEP-5707 기준으로 kube-proxy의&lt;span&gt;&amp;nbsp;&lt;/span&gt;externalIPs&lt;span&gt;&amp;nbsp;&lt;/span&gt;지원은 단계적으로 제거될 예정입니다. v1.40에서는&lt;span&gt;&amp;nbsp;&lt;/span&gt;AllowServiceExternalIPs&lt;span&gt;&amp;nbsp;&lt;/span&gt;feature gate 기본값이&lt;span&gt;&amp;nbsp;&lt;/span&gt;false로 바뀌고, v1.43에서는 feature gate가 잠기며 관련 구현이 제거될 예정입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1910&quot; data-origin-height=&quot;120&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bicdYu/dJMcajozhUT/0JlQgvGh4BjBjgt07EdSu1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bicdYu/dJMcajozhUT/0JlQgvGh4BjBjgt07EdSu1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bicdYu/dJMcajozhUT/0JlQgvGh4BjBjgt07EdSu1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbicdYu%2FdJMcajozhUT%2F0JlQgvGh4BjBjgt07EdSu1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1910&quot; height=&quot;120&quot; data-origin-width=&quot;1910&quot; data-origin-height=&quot;120&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그 외에도 일부 메트릭과 플러그인 동작이 변경되었습니다.&lt;/p&gt;
&lt;table style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ACTION REQUIRED&lt;/td&gt;
&lt;td&gt;volume_operation_total_errors&lt;span&gt;&amp;nbsp;&lt;/span&gt;metric 이름이&lt;span&gt;&amp;nbsp;&lt;/span&gt;volume_operation_errors_total로 변경&lt;/td&gt;
&lt;td&gt;Prometheus alert, Grafana dashboard, recording rule이 깨질 수 있음&lt;/td&gt;
&lt;td&gt;모니터링 repo에서 기존 metric 검색&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ACTION REQUIRED&lt;/td&gt;
&lt;td&gt;scheduler PreBind plugin 병렬 실행 인터페이스 변경&lt;/td&gt;
&lt;td&gt;custom scheduler plugin이 있으면&lt;span&gt;&amp;nbsp;&lt;/span&gt;PreBindPreFlightResult&lt;span&gt;&amp;nbsp;&lt;/span&gt;대응 필요&lt;/td&gt;
&lt;td&gt;사내 scheduler plugin 코드 검색&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ACTION REQUIRED&lt;/td&gt;
&lt;td&gt;DRA ResourceClaim status update RBAC 세분화&lt;/td&gt;
&lt;td&gt;DRA driver/controller가 403을 만날 수 있음&lt;/td&gt;
&lt;td&gt;DRA 사용 cluster의 ClusterRole 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ACTION REQUIRED&lt;/td&gt;
&lt;td&gt;kubeadm flex-volume 통합 지원 제거&lt;/td&gt;
&lt;td&gt;kubeadm이 더 이상 KCM static pod에 flex-volume 경로를 자동 mount하지 않음&lt;/td&gt;
&lt;td&gt;flex-volume 사용 여부 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ACTION REQUIRED&lt;/td&gt;
&lt;td&gt;etcd_bookmark_counts&lt;span&gt;&amp;nbsp;&lt;/span&gt;metric 이름이&lt;span&gt;&amp;nbsp;&lt;/span&gt;etcd_bookmark_total로 변경&lt;/td&gt;
&lt;td&gt;etcd/API server 관련 alert와 dashboard 수정 필요&lt;/td&gt;
&lt;td&gt;모니터링 repo에서 기존 metric 검색&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deprecation&lt;/td&gt;
&lt;td&gt;Service.spec.externalIPs&lt;span&gt;&amp;nbsp;&lt;/span&gt;deprecated&lt;/td&gt;
&lt;td&gt;v1.36부터 warning이 발생하고, KEP-5707 기준 kube-proxy 지원이 단계적으로 제거될 예정&lt;/td&gt;
&lt;td&gt;kubectl get svc -A -o yaml에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;externalIPs&lt;span&gt;&amp;nbsp;&lt;/span&gt;검색&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Removed/Disabled&lt;/td&gt;
&lt;td&gt;gitRepo&lt;span&gt;&amp;nbsp;&lt;/span&gt;volume plugin 비활성화, 다시 켤 수 없음&lt;/td&gt;
&lt;td&gt;gitRepo&lt;span&gt;&amp;nbsp;&lt;/span&gt;volume을 쓰는 pod가 더 이상 정상 동작하지 않음&lt;/td&gt;
&lt;td&gt;manifest에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;gitRepo:&lt;span&gt;&amp;nbsp;&lt;/span&gt;검색&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Removed&lt;/td&gt;
&lt;td&gt;in-tree Portworx volume plugin 제거&lt;/td&gt;
&lt;td&gt;Portworx in-tree 경로 의존 cluster는 CSI migration 상태 확인 필요&lt;/td&gt;
&lt;td&gt;PV/StorageClass provisioner 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Removed&lt;/td&gt;
&lt;td&gt;cAdvisor의&lt;span&gt;&amp;nbsp;&lt;/span&gt;container_cpu_load_average_10s,&lt;span&gt;&amp;nbsp;&lt;/span&gt;container_cpu_load_d_average_10s,&lt;span&gt;&amp;nbsp;&lt;/span&gt;cpu_tasks_state&lt;span&gt;&amp;nbsp;&lt;/span&gt;metric 제거&lt;/td&gt;
&lt;td&gt;사용 중인 dashboard panel이 빈 값이 될 수 있음&lt;/td&gt;
&lt;td&gt;Prometheus query 검색&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;실습&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;실습&lt;/h1&gt;
&lt;h2 id=&quot;쿠버네티스-설치&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;쿠버네티스 설치&lt;/h2&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Docker 컨테이너로 실습을 진행했고, &lt;b&gt;k3d로 kubernetes 클러스터를 설치&lt;/b&gt;했습니다. Docker image 버전은 2026년 5월 10일 기준 최신 이미지를 선택했습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;선택한 Docker image:&lt;span&gt;&amp;nbsp;&lt;/span&gt;rancher/k3s:v1.36.0-k3s1&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;k3d 클러스터를 설치하려면 k3d CLI가 있어야 합니다.&lt;/p&gt;
&lt;div id=&quot;cb1&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;# MacBook
curl -L -o /tmp/k3d-darwin-arm64 https://github.com/k3d-io/k3d/releases/download/v5.8.3/k3d-darwin-arm64
chmod +x /tmp/k3d-darwin-arm64
/tmp/k3d-darwin-arm64 version&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;k3d CLI로 클러스터를 생성합니다.&lt;/p&gt;
&lt;div id=&quot;cb2&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;k3d cluster create k8s-136 \
  --image rancher/k3s:v1.36.0-k3s1&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;클러스터-삭제&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;클러스터 삭제&lt;/h2&gt;
&lt;div id=&quot;cb3&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;
&lt;pre class=&quot;mel&quot;&gt;&lt;code&gt;k3d cluster delete k8s-136&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;user-namespace&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;user namespace&lt;/h2&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;user namespace는 컨테이너의 root UID가 host의 root UID를 직접 사용하지 못하게 하는 기&lt;/b&gt;능입니다. user namespace를 사용하면 컨테이너 내부의 root UID는 host에서 root가 아니라 별도의 UID로 보입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1628&quot; data-origin-height=&quot;122&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cXwUVd/dJMcaaSNnmr/q3u1eOBJ8QkGc8jWOa9VW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cXwUVd/dJMcaaSNnmr/q3u1eOBJ8QkGc8jWOa9VW1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cXwUVd/dJMcaaSNnmr/q3u1eOBJ8QkGc8jWOa9VW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcXwUVd%2FdJMcaaSNnmr%2Fq3u1eOBJ8QkGc8jWOa9VW1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1628&quot; height=&quot;122&quot; data-origin-width=&quot;1628&quot; data-origin-height=&quot;122&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;user namespace를 사용하려면&lt;span&gt;&amp;nbsp;&lt;/span&gt;hostUsers: false를 설정하면 됩니다.&lt;/p&gt;
&lt;div id=&quot;cb4&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  name: userns-root-pod
spec:
  hostUsers: false
  containers:
    - name: shell
      image: busybox:1.36.1
      command:
        - sh
        - -c
        - id &amp;amp;&amp;amp; sleep 3600
      securityContext:
        runAsUser: 0
        runAsGroup: 0&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;제약사항은 공식 문서에서 확인할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;user namespace 제약사항:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://kubernetes.io/docs/concepts/workloads/pods/user-namespaces/#limitations&quot;&gt;https://kubernetes.io/docs/concepts/workloads/pods/user-namespaces/#limitations&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;눈에 띄는 제약사항은 NFS 볼륨을 마운트할 때 user namespace를 사용할 수 없다는 점입니다. user namespace를 사용하더라도&lt;span&gt;&amp;nbsp;&lt;/span&gt;idmap mounts&lt;span&gt;&amp;nbsp;&lt;/span&gt;기능으로 컨테이너 내부에서는 고정된 UID 권한으로 파일을 사용할 수 있습니다. 하지만 NFS는 아직&lt;span&gt;&amp;nbsp;&lt;/span&gt;idmap mounts를 지원하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;externalips-deprecated-확인&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;externalIPs deprecated 확인&lt;/h2&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;externalIPs가 설정된 service를 생성하면 warning 메시지를 볼 수 있습니다.&lt;/p&gt;
&lt;div id=&quot;cb5&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Service
metadata:
  name: deprecated-external-ip-demo
  labels:
    app.kubernetes.io/name: deprecated-external-ip-demo
spec:
  type: ClusterIP
  # externalIPs 설정
  externalIPs:
    - 203.0.113.10
  selector:
    app.kubernetes.io/name: external-ip-demo
  ports:
    - name: http
      port: 80
      targetPort: 8080
      protocol: TCP&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1910&quot; data-origin-height=&quot;120&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cAggt6/dJMcacQz87y/CUP0pqdKfLiDYrIoxyv4Q1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAggt6/dJMcacQz87y/CUP0pqdKfLiDYrIoxyv4Q1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAggt6/dJMcacQz87y/CUP0pqdKfLiDYrIoxyv4Q1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAggt6%2FdJMcacQz87y%2FCUP0pqdKfLiDYrIoxyv4Q1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1910&quot; height=&quot;120&quot; data-origin-width=&quot;1910&quot; data-origin-height=&quot;120&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;mutatingadmissionpolicy&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;MutatingAdmissionPolicy&lt;/h2&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;기존에는 admission controller에서 mutation을 적용하려면 webhook과 webhook 요청을 처리할 API server가 필요했습니다. &lt;b&gt;v1.36부터는 webhook server 없이&lt;span&gt;&amp;nbsp;&lt;/span&gt;MutatingAdmissionPolicy와&lt;span&gt;&amp;nbsp;&lt;/span&gt;MutatingAdmissionPolicyBinding&lt;span&gt;&amp;nbsp;&lt;/span&gt;API resource로 단순 mutation을 정의&lt;/b&gt;할 수 있습니다. mutation 동작원리가 궁금하신 분은 이전 글을 참고바랍니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- admission controller 동작원리: &lt;a href=&quot;https://malwareanalysis.tistory.com/704&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://malwareanalysis.tistory.com/704&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1778409983519&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;쿠버네티스 Admission controller&quot; data-og-description=&quot;1. Admission controller이란? 쿠버네티스 Admission controller는 말 그대로 Admission 기능을 수행합니다. Admission이라는 영어 단어는 허가를 의미하며, 쿠버네티스 세계에서 Admission은, 쿠버네티스 요청을 수&quot; data-og-host=&quot;malwareanalysis.tistory.com&quot; data-og-source-url=&quot;https://malwareanalysis.tistory.com/704&quot; data-og-url=&quot;https://malwareanalysis.tistory.com/704&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/FOorP/dJMb8WMt3MA/UcDPm7zuLGVXg72yFVNJZ0/img.png?width=600&amp;amp;height=600&amp;amp;face=0_0_600_600,https://scrap.kakaocdn.net/dn/CfEFt/dJMb8QeqB5T/LZqJmZqKAqV8lXOh9DymKK/img.png?width=600&amp;amp;height=600&amp;amp;face=0_0_600_600,https://scrap.kakaocdn.net/dn/bLGvKj/dJMb8SXChlF/L65qlmVMjwZ7p9C2KLGzsK/img.png?width=1278&amp;amp;height=476&amp;amp;face=0_0_1278_476&quot;&gt;&lt;a href=&quot;https://malwareanalysis.tistory.com/704&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://malwareanalysis.tistory.com/704&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/FOorP/dJMb8WMt3MA/UcDPm7zuLGVXg72yFVNJZ0/img.png?width=600&amp;amp;height=600&amp;amp;face=0_0_600_600,https://scrap.kakaocdn.net/dn/CfEFt/dJMb8QeqB5T/LZqJmZqKAqV8lXOh9DymKK/img.png?width=600&amp;amp;height=600&amp;amp;face=0_0_600_600,https://scrap.kakaocdn.net/dn/bLGvKj/dJMb8SXChlF/L65qlmVMjwZ7p9C2KLGzsK/img.png?width=1278&amp;amp;height=476&amp;amp;face=0_0_1278_476');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;쿠버네티스 Admission controller&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;1. Admission controller이란? 쿠버네티스 Admission controller는 말 그대로 Admission 기능을 수행합니다. Admission이라는 영어 단어는 허가를 의미하며, 쿠버네티스 세계에서 Admission은, 쿠버네티스 요청을 수&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;malwareanalysis.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아래 예제는 pod가 생성되면 label을 추가하는 mutation을 정의합니다.&lt;/p&gt;
&lt;div id=&quot;cb6&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;apiVersion: admissionregistration.k8s.io/v1
kind: MutatingAdmissionPolicy
metadata:
  name: add-release-label.example.com
  labels:
    app.kubernetes.io/name: k8s-136-release-notes
spec:
  matchConstraints:
    resourceRules:
      - apiGroups:
          - &quot;&quot;
        apiVersions:
          - v1
        operations:
          - CREATE
        resources:
          - pods
  matchConditions:
    - name: skip-if-release-label-exists
      expression: &quot;!has(object.metadata.labels) || !('release.kubernetes.io/tested-version' in object.metadata.labels)&quot;
  failurePolicy: Fail
  reinvocationPolicy: Never
  mutations:
    - patchType: JSONPatch
      jsonPatch:
        expression: &amp;gt;
          !has(object.metadata.labels) ?
          [
            JSONPatch{
              op: &quot;add&quot;,
              path: &quot;/metadata/labels&quot;,
              value: {&quot;release.kubernetes.io/tested-version&quot;: &quot;v1.36&quot;}
            }
          ] :
          [
            JSONPatch{
              op: &quot;add&quot;,
              path: &quot;/metadata/labels/&quot; + jsonpatch.escapeKey(&quot;release.kubernetes.io/tested-version&quot;),
              value: &quot;v1.36&quot;
            }
          ]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;cb7&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;apiVersion: admissionregistration.k8s.io/v1
kind: MutatingAdmissionPolicyBinding
metadata:
  name: add-release-label-binding.example.com
  labels:
    app.kubernetes.io/name: k8s-136-release-notes
spec:
  policyName: add-release-label.example.com&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아래 pod를 생성하면&lt;span&gt;&amp;nbsp;&lt;/span&gt;MutatingAdmissionPolicy와&lt;span&gt;&amp;nbsp;&lt;/span&gt;MutatingAdmissionPolicyBinding&lt;span&gt;&amp;nbsp;&lt;/span&gt;설정 때문에 pod에 label이 추가됩니다.&lt;/p&gt;
&lt;div id=&quot;cb8&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  name: map-sample-pod
  labels:
    app.kubernetes.io/name: map-sample
spec:
  restartPolicy: Always
  containers:
    - name: pause
      image: registry.k8s.io/pause:3.10
      resources:
        requests:
          cpu: 5m
          memory: 16Mi
        limits:
          cpu: 50m
          memory: 64Mi&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1618&quot; data-origin-height=&quot;498&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhy9kw/dJMcagFuvDu/G7gmbkTBHLQXQu873q1m8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhy9kw/dJMcagFuvDu/G7gmbkTBHLQXQu873q1m8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhy9kw/dJMcagFuvDu/G7gmbkTBHLQXQu873q1m8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbhy9kw%2FdJMcagFuvDu%2FG7gmbkTBHLQXQu873q1m8K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1618&quot; height=&quot;498&quot; data-origin-width=&quot;1618&quot; data-origin-height=&quot;498&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;참고자료&quot; style=&quot;background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot;&gt;참고자료&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #fdfdfd; color: #1a1a1a; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;kubernetes v1.36 릴리즈 블로그:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://kubernetes.io/blog/2026/04/22/kubernetes-v1-36-release/&quot;&gt;https://kubernetes.io/blog/2026/04/22/kubernetes-v1-36-release/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;kubernetes v1.36 changelog:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.36.md#changelog-since-v1350&quot;&gt;https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.36.md#changelog-since-v1350&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;kubernetes cgroup v2 공식 문서:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://kubernetes.io/docs/concepts/architecture/cgroups/&quot;&gt;https://kubernetes.io/docs/concepts/architecture/cgroups/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;kubernetes PSI metrics 공식 문서:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://kubernetes.io/docs/reference/instrumentation/understand-psi-metrics/&quot;&gt;https://kubernetes.io/docs/reference/instrumentation/understand-psi-metrics/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;kubernetes v1.36 MemoryQoS 블로그:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://kubernetes.io/blog/2026/04/29/kubernetes-v1-36-memory-qos-tiered-protection/&quot;&gt;https://kubernetes.io/blog/2026/04/29/kubernetes-v1-36-memory-qos-tiered-protection/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;kubernetes release page:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://kubernetes.io/releases/&quot;&gt;https://kubernetes.io/releases/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;k3s v1.36.0+k3s1 release:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://github.com/k3s-io/k3s/releases/tag/v1.36.0%2Bk3s1&quot;&gt;https://github.com/k3s-io/k3s/releases/tag/v1.36.0%2Bk3s1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;k3d release:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://github.com/k3d-io/k3d/releases&quot;&gt;https://github.com/k3d-io/k3d/releases&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;MetalBear Kubernetes 1.36 정리:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://metalbear.com/blog/kubernetes-1-36/&quot;&gt;https://metalbear.com/blog/kubernetes-1-36/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>전공영역 공부 기록</category>
      <category>kubernetes</category>
      <author>악분</author>
      <guid isPermaLink="true">https://malwareanalysis.tistory.com/929</guid>
      <comments>https://malwareanalysis.tistory.com/929#entry929comment</comments>
      <pubDate>Sun, 10 May 2026 19:46:36 +0900</pubDate>
    </item>
  </channel>
</rss>