<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>LOGPOSE</title>
    <link>https://crayeji.tistory.com/</link>
    <description>다음 섬으로 향하고자 이번 섬을 기록하는 특수한 나침반</description>
    <language>ko</language>
    <pubDate>Sun, 21 Jun 2026 09:31:27 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>LOGPOSE 로그포스</managingEditor>
    <image>
      <title>LOGPOSE</title>
      <url>https://tistory1.daumcdn.net/tistory/4891436/attach/725e0abf5b0449ea968483377ad8748b</url>
      <link>https://crayeji.tistory.com</link>
    </image>
    <item>
      <title>[함수형 리팩터링] 비동기 전환을 고려한 공유 Mutable 제거</title>
      <link>https://crayeji.tistory.com/150</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;배경&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 리팩터링 목적은 코드 내 작업을 비동기적으로 바꾸기 전에 문제(race, 흐름 의존성)를 제거하는 것이다. &lt;br /&gt;그 방법은 기존 코드에 함수형 패러다임을 적용하는 것이다. 상태 변경을 줄이고, 행동을 조합 가능하게 만들 것이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;0. 기존 코드&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래&amp;nbsp;코드는&amp;nbsp;이미지&amp;nbsp;첨부파일&amp;nbsp;리스트를&amp;nbsp;가져와서&amp;nbsp;NCloud&amp;nbsp;Green&amp;nbsp;Eye를&amp;nbsp;통해&amp;nbsp;각&amp;nbsp;첨부파일에&amp;nbsp;대해&amp;nbsp;유해성&amp;nbsp;판별을&amp;nbsp;한다.&amp;nbsp;&lt;br /&gt;현재는 이 작업이 .block() 기반 순차 처리라 race가 표면화되진 않지만, 비동기 전환 시 &lt;b&gt;공유 리스트에 대한 동시 접근&lt;/b&gt;이 생길 수 있어 구조적으로 위험해진다. 또한, for문 내에서 흐름 제어와 계산 로직이 묶여 테스트가 어려웠다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;참고) 비동기 전환을 고려하는 이유는 외부 API요청에 대해 굳이 쓰레드를 점유하게 하고 싶지 않은 점이다.&lt;br /&gt;본질적으로 WebClient가 non-blocking 사용을 전제로 설계되어 이러한 전환이 바람직해 보인다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;@Override
public List&amp;lt;FeedAttachment&amp;gt; findBadImageListByAI(List&amp;lt;FeedAttachment&amp;gt; feedAttachmentList) {
    log.info(&quot;feedAttachmentList={}&quot;, feedAttachmentList);

    WebClient client = WebClient.create(apigwUrl);
    List&amp;lt;FeedAttachment&amp;gt; badImageList = new ArrayList&amp;lt;&amp;gt;();

    // Green Eye 유해성 판별은 요청 1회당 1개의 이미지만 가능
    for (FeedAttachment feedAttachment : feedAttachmentList) {

        if (!feedAttachment.isActivated()) continue;

        String requestBody = &quot;&quot;&quot;
            {
              &quot;version&quot;: &quot;V1&quot;,
              &quot;requestId&quot;: &quot;%s&quot;,
              &quot;timestamp&quot;: 0,
              &quot;images&quot;: [
                {
                  &quot;name&quot;: &quot;%s&quot;,
                  &quot;url&quot;: &quot;%s&quot;
                }
              ]
            }
            &quot;&quot;&quot;.formatted(
                feedAttachment.getAttachmentId(),
                feedAttachment.getAttachmentId(),
                feedAttachment.getFileUrl()
            );

        try {
            JSONObject response = new JSONObject(
                client.post()
                    .header(GREENEYE_SECRET_KEY_HEADER, this.secretKey)
                    .header(&quot;Content-Type&quot;, &quot;application/json&quot;)
                    .bodyValue(requestBody)
                    .retrieve()
                    .onStatus(status -&amp;gt; status.is4xxClientError(), ClientResponse::createException)
                    .bodyToMono(String.class)
                    .block()
            );

            if (checkHarmful(response)) {
                badImageList.add(feedAttachment);
            }

        } catch (Exception e) {
            System.err.println(&quot;GreenEye 요청이 잘못됨: &quot; + e.getMessage());
        }
    }

    return badImageList;
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 리팩터링 포인트 (문제점)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 요청을 비동기적으로 바꿀 때 미리 검토할 사항이 있었다.&lt;/p&gt;
&lt;h5&gt;&lt;b&gt;1) badImageList의 공유 mutable 상태&lt;/b&gt;&lt;/h5&gt;
&lt;pre class=&quot;lasso&quot;&gt;&lt;code&gt;List&amp;lt;FeedAttachment&amp;gt; badImageList = new ArrayList&amp;lt;&amp;gt;();
...
badImageList.add(feedAttachment);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;badImageList&lt;/code&gt;는 외부에서 관찰 가능한 mutable state이며, &lt;code&gt;continue/try-catch/checkHarmful&lt;/code&gt; 분기마다 &lt;code&gt;add&lt;/code&gt; 수행 여부가 달라져 변경 지점이 분산되고 테스트가 어려워진다.(로직이 바뀌면 add 시점이 바뀌므로, 디버깅 어려움)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다르게 말하면, 이 리스트가 언제 어떤 값으로 바뀌는지 직접 추론해야한다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 비동기 처리(예: &lt;code&gt;subscribe&lt;/code&gt;, &lt;code&gt;flatMap&lt;/code&gt;, &lt;code&gt;CompletableFuture&lt;/code&gt; 등)로 전환하면, 여러 실행 흐름이 동일 리스트에 접근해 add()를 수행할 수 있어 race condition이 발생할 여지가 커진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h5&gt;&lt;b&gt;&lt;span&gt;2)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;로직이 하나의 블록으로 뭉쳐 있음&lt;/b&gt;&lt;/h5&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;외부 API 호출&lt;/li&gt;
&lt;li&gt;requestBody 생성&lt;/li&gt;
&lt;li&gt;응답 파싱&lt;/li&gt;
&lt;li&gt;harmful 판단&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;이게 모두 for 문 안에 섞여 있어, 각 단계가 다음 단계에 강하게 의존(조립 불가)한다.&lt;br /&gt;테스트가 어려운 거대한 블록 형태인데, 개별 단계를 테스트 가능한 작은 함수로 나눌 수 있어 보인다.&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 리팩터링 목표 (방향성)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;공유 mutable 상태 제거&lt;/b&gt;, 상태 변경 최소화&lt;/li&gt;
&lt;li&gt;테스트 가능한 작은 &lt;b&gt;함수로 각 단계를 분리&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;참고) Java 코드인 만큼, 모든 코드가 클래스 안에 있어 '함수'개념은 없긴 하다. 사실상 구현 형태는 전부 메서드이기 때문이다.&lt;br /&gt;다만, 여기서 함수로 분리한다는 말은&lt;b&gt; 메서드를 객체 상태에 의존하지 않도록 설계해 함수처럼 사용하는 것&lt;/b&gt;을 의미한다.&lt;/blockquote&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 리팩터링 된 코드 (결과)&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;함수형 스타일 본문&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;@Override
public List&amp;lt;FeedAttachment&amp;gt; findBadImageListByAI(List&amp;lt;FeedAttachment&amp;gt; feedAttachmentList) {

    WebClient client = WebClient.create(apigwUrl);

    return feedAttachmentList.stream()
        // 1) 활성화된 이미지만
        .filter(FeedAttachment::isActivated)

        // 2) 유해 판별 규칙(조합 가능한 Boolean Predicate)
        .filter(attachment -&amp;gt; isHarmfulAttachment(client, attachment))

        // 3) 한 번에 결과 수집 (중간 상태 변경 없음)
        .toList();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;for문 내에서 흐름 제어와 계산 로직이 묶여 가변(badImageList)되던 중간 상태를 없앴다.&lt;/li&gt;
&lt;li&gt;외부 API요청 및 유해성 판단 로직을 캡슐화하여 toList() 전까지의 파이프라인에서는 &lt;b&gt;조합 가능한(filter) Boolean 반환 로직&lt;/b&gt;만 드러나게 했다.&lt;/li&gt;
&lt;li&gt;결과 리스트를 직접&amp;nbsp;add()하지 않고 toList()에 수집을 위임해서 &lt;b&gt;마지막에만&amp;nbsp;최종&amp;nbsp;리스트를&amp;nbsp;리턴&lt;/b&gt;하게&amp;nbsp;했다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;API 호출 및 유해 여부 판별 분리&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;private boolean isHarmfulAttachment(WebClient client, FeedAttachment attachment) {
    return callGreenEye(client, attachment)
        .map(this::checkHarmful)
        .orElse(false);
}

private Optional&amp;lt;JSONObject&amp;gt; callGreenEye(WebClient client, FeedAttachment feedAttachment) {
    try {
        String requestBody = createRequestBody(feedAttachment);

        String result = client.post()
            .header(GREENEYE_SECRET_KEY_HEADER, this.secretKey)
            .header(&quot;Content-Type&quot;, &quot;application/json&quot;)
            .bodyValue(requestBody)
            .retrieve()
            .bodyToMono(String.class)
            .block();

        return Optional.of(new JSONObject(result));

    } catch (Exception e) {
        log.error(&quot;GreenEye error attachmentId={} msg={}&quot;,
            feedAttachment.getAttachmentId(),
            e.getMessage()
        );
        return Optional.empty();
    }
}&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;실패가 발생하면 예외나 null 대신 &lt;code&gt;Optional.empty()&lt;/code&gt;로 &quot;값이 없다&quot;를 표현한다.&lt;/li&gt;
&lt;li&gt;즉, callGreenEye는 성공/실패에 대한 사실만 전달한다.&lt;/li&gt;
&lt;li&gt;외부 네트워크&amp;middot;서버 상태 등 외부 변수에 의존하므로 pure function으로 만들지는 못했지만, effect 경계를 분리했으므로 조금 더 예측 가능한 형태가 되었다고 생각한다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;RequestBody 생성 분리&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;perl&quot;&gt;&lt;code&gt;private String createRequestBody(FeedAttachment feedAttachment) {
    return &quot;&quot;&quot;
       {
         &quot;version&quot;: &quot;V1&quot;,
         &quot;requestId&quot;: &quot;%s&quot;,
         &quot;timestamp&quot;: 0,
         &quot;images&quot;: [
           {
             &quot;name&quot;: &quot;%s&quot;,
             &quot;url&quot;: &quot;%s&quot;
           }
         ]
       }
       &quot;&quot;&quot;.formatted(
            feedAttachment.getAttachmentId(),
            feedAttachment.getAttachmentId(),
            feedAttachment.getFileUrl()
    );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 부분의 의의는 Pure function으로 분리했다는 점이다.&lt;/li&gt;
&lt;li&gt;생각 비용(?)이 0이다! (같은 입력에 대해 같은 출력)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&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;&lt;b&gt;공유 mutable List&lt;/b&gt;를 직접 변경하지 않는다 (&lt;code&gt;add&lt;/code&gt; 제거)&lt;/li&gt;
&lt;li&gt;각 FeedAttachment 요소는 결과 리스트를 변경하는 주체가 아니라, filter 조건을 통과할지 여부만 평가되는 값으로 취급된다.&lt;/li&gt;
&lt;li&gt;side effect가 발생하는 로직을 함수형으로 분리하고, pure function만 스트림 단계에 남겨 조합했다. 결과는 마지막에 한 번만 List로 수집해 리턴한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 아래와 같이 비동기로 작업을 수행할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public Mono&amp;lt;List&amp;lt;FeedAttachment&amp;gt;&amp;gt; findBadImageListByAIAsync(List&amp;lt;FeedAttachment&amp;gt; list) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;WebClient client = WebClient.create(apigwUrl);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return Flux.fromIterable(list)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.filter(FeedAttachment::isActivated)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.filterWhen(att -&amp;gt; callGreenEyeAsync(client, att)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.map(this::checkHarmful)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.onErrorReturn(false)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.collectList();
}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비동기 전환 시 race condition이 우려되는&amp;nbsp;&lt;b&gt;공유 mutable 상태&lt;/b&gt;를 구조적으로 제거했다.&lt;br /&gt;상태를 누적하는 방식 대신 조건을 조합하는 방식으로 로직을 재구성함으로써, 기존보다 예측 가능한 코드를 만들었다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Programming Paradigm</category>
      <category>선언형 프로그래밍</category>
      <category>프로그래밍 패러다임</category>
      <category>함수형</category>
      <category>함수형 패러다임</category>
      <category>함수형 프로그래밍</category>
      <author>LOGPOSE 로그포스</author>
      <guid isPermaLink="true">https://crayeji.tistory.com/150</guid>
      <comments>https://crayeji.tistory.com/150#entry150comment</comments>
      <pubDate>Thu, 5 Feb 2026 18:08:39 +0900</pubDate>
    </item>
    <item>
      <title>[(Kubeadm) K8s 클러스터 구축 3/3] Kubeadm init 및 Node join</title>
      <link>https://crayeji.tistory.com/148</link>
      <description>&lt;figure id=&quot;og_1760581476698&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;[(Kubeadm) K8s 클러스터 구축 2/3] Kubeadm, Kubelet, Kubectl 설치&quot; data-og-description=&quot;[(Kubeadm) K8s 클러스터 구축 1/3] Container Runtime 설치개요kubeadm 기반으로 K8s클러스터를 구축하는 과정을 3편의 글로 요약해 본다.가용 Virtual Machine이 2대 이상(각 Machine의 메모리 2GB, 2 CPU 이상) 준비됐&quot; data-og-host=&quot;crayeji.tistory.com&quot; data-og-source-url=&quot;https://crayeji.tistory.com/147&quot; data-og-url=&quot;https://crayeji.tistory.com/147&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/czNrdH/hyZLyo285I/2SEUMbYLwMoE24PK23LAHK/img.png?width=282&amp;amp;height=318&amp;amp;face=0_0_282_318,https://scrap.kakaocdn.net/dn/efa2Bz/hyZLvlyIE5/PzW7YM1BLGzJq1K8eHUQkk/img.png?width=282&amp;amp;height=318&amp;amp;face=0_0_282_318&quot;&gt;&lt;a href=&quot;https://crayeji.tistory.com/147&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://crayeji.tistory.com/147&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/czNrdH/hyZLyo285I/2SEUMbYLwMoE24PK23LAHK/img.png?width=282&amp;amp;height=318&amp;amp;face=0_0_282_318,https://scrap.kakaocdn.net/dn/efa2Bz/hyZLvlyIE5/PzW7YM1BLGzJq1K8eHUQkk/img.png?width=282&amp;amp;height=318&amp;amp;face=0_0_282_318');&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;[(Kubeadm) K8s 클러스터 구축 2/3] Kubeadm, Kubelet, Kubectl 설치&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;[(Kubeadm) K8s 클러스터 구축 1/3] Container Runtime 설치개요kubeadm 기반으로 K8s클러스터를 구축하는 과정을 3편의 글로 요약해 본다.가용 Virtual Machine이 2대 이상(각 Machine의 메모리 2GB, 2 CPU 이상) 준비됐&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;crayeji.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞선 글 [&lt;a href=&quot;https://crayeji.tistory.com/146&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;01. Container Runtime(Containerd) 구성&lt;/a&gt;]와 [&lt;a href=&quot;https://crayeji.tistory.com/147&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;02. Kubeadm, Kubelet, Kubectl 설치&lt;/a&gt;]에서 &lt;br /&gt;Container Runtime 및 kubeadm, kubelet, kubectl 설치가 마쳐져 있어야 한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;i&gt;&lt;b&gt;Kubeadm Init 하기 전 설정&lt;/b&gt;&lt;/i&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Memory Swap 비활성화 (Master, Worker Node 모두)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kubernetes v1.33에서 kubelet은 swap(스왑 메모리)이 켜져 있으면 &lt;a href=&quot;https://v1-33.docs.kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/#swap-configuration&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;기본적으로 실행을 거부한다고 언급&lt;/a&gt;되었다.&amp;nbsp;&lt;br /&gt;kubelet이 적절하게 동작하도록 swap을 비활성화했다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;sudo swapoff -a
sudo sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 방화벽 K8s관련 포트 열기 (Master, Worker Node 각각)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1702&quot; data-origin-height=&quot;1082&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oNWdW/btsQ6OoyG2m/ggaWbqsK1hXcVBApk3oYe1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oNWdW/btsQ6OoyG2m/ggaWbqsK1hXcVBApk3oYe1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oNWdW/btsQ6OoyG2m/ggaWbqsK1hXcVBApk3oYe1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoNWdW%2FbtsQ6OoyG2m%2FggaWbqsK1hXcVBApk3oYe1%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;1702&quot; height=&quot;1082&quot; data-origin-width=&quot;1702&quot; data-origin-height=&quot;1082&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# Master
sudo ufw allow 6443/tcp
sudo ufw allow 2379:2380/tcp
sudo ufw allow 10250/tcp
sudo ufw allow 10257/tcp
sudo ufw allow 10259/tcp&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# Worker
sudo ufw allow 10250/tcp
sudo ufw allow 30000:32767/tcp&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;* CNI/Pod 네트워크로 열어야 하는 포트는 CNI 플러그인(Flannel, Calico, Cilium 등)에 따라 다르므로 별도 설정이 필요할 수 있다.&lt;/i&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. IPv4 패킷 포워딩 켜기 (Master, Worker Node 모두)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;K8s는 Pod 간 네트워크를 만들기 위해 노드에서 패킷을 다른 인터페이스로 전달해야 한다.&lt;br /&gt;이때, 리눅스 커널의 IP포워딩(net.ipv4.ip_forward)이 기본적으로 비활성화되어 있으니, Pod 간 통신&amp;amp;라우팅이 가능하도록 활성화한다.&lt;/p&gt;
&lt;pre id=&quot;code_1760489036637&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 설정 파일을 열어 net.ipv4.ip_forward=1 주석 해제 또는 추가
sudo vim /etc/sysctl.conf

# 설정 파일 변경 내용을 커널에 즉시 반영
sudo sysctl -p&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;i&gt;&lt;b&gt;Kubeadm Init&lt;/b&gt;&lt;/i&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Init (Master Node)&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;sudo kubeadm init --pod-network-cidr=192.168.0.0/16&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;출력된 join 명령어를 &lt;b&gt;복사해 두자.&lt;/b&gt; (추후 Worker Node join 단계에서 복붙 할 것이다.)&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;Pod Network CIDR은 CNI에 따라 달라지는데, 이 글에서는 Calico를 사용하므로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.tigera.io/calico/latest/getting-started/kubernetes/hardway/configure-ip-pools&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Calico 공식 문서&lt;/a&gt;에서 제공하는 IP Pool 범위 192.168.0.0/16를 써줬다. (256x256=약 65000)&lt;/p&gt;
&lt;pre id=&quot;code_1760427737811&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 참고
실행 후 kubeadm이 하는 일:
- etcd 초기화 (클러스터 저장소)
- API Server / Controller Manager / Scheduler kube-system 네임스페이스에 Pod로 배포
- kubeconfig 생성 (/etc/kubernetes/admin.conf)
- Worker Node가 붙을 수 있는 kubeadm join 명령어 출력&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Kubeconfig Setting (Master Node) (선택)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kubectl은 기본적으로 &lt;a href=&quot;https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/#:~:text=By%20default%2C%20kubectl%20looks%20for%20a%20file%20named%20config%20in%20the%20%24HOME/.kube%20directory.%20You%20can%20specify%20other%20kubeconfig%20files%20by%20setting%20the%20KUBECONFIG%20environment%20variable%20or%20by%20setting%20the%20%2D%2Dkubeconfig%20flag.&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;$HOME/.kube/config 파일을 찾아&lt;/a&gt;&lt;br /&gt;클러스터의 접근 정보(클러스터 주소, 인증서, 사용자 계정 등)를 로드한다.&lt;br /&gt;따라서 해당 경로에 기본으로 사용할 kubeconfig 파일을 복사해 두면,&lt;br /&gt;kubectl이 별도 옵션(--kubeconfig) 없이 바로 클러스터와 통신할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Worker node Join&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Worker Node를 클러스터에 Join 하자.&lt;br /&gt;앞서 init시 복사해 뒀던 명령어를 복붙 한다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;#복붙해뒀던 join 명령
kubeadm join &amp;lt;MASTER_IP&amp;gt;:6443 --token &amp;lt;TOKEN&amp;gt; \
--discovery-token-ca-cert-hash sha256:&amp;lt;CA SHA256 HASH&amp;gt;&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;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;참고) 복사해 둔 게 없을 경우, 아래 명령을 통해 토큰 재발급과 동시에 join명령어를 그대로 다시 받을 수 있다. &lt;br /&gt;(물론 kubeadm token list로 기존 토큰을 조회할 수도 있다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1760429754471&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo kubeadm token create --print-join-command&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Master에서 클러스터 상태 확인&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;kubectl get nodes&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 kubectl명령으로 클러스터의 노드들을 확인할 수 있다.&lt;br /&gt;다만, Status는 NotReady로 뜰 텐데, &lt;b&gt;CNI가 아직 설치되지 않았기 때문&lt;/b&gt;이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;i&gt;&lt;b&gt;CNI(네트워크 플러그인) 설치&lt;/b&gt;&lt;/i&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NetworkPolicy를 지원하는 &lt;a href=&quot;https://docs.tigera.io/calico/latest/getting-started/kubernetes/quickstart&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Calico&lt;/a&gt;를 선택했다.&lt;/p&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;### crd 설치
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.30.3/manifests/tigera-operator.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;### cr 리소스 create
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.30.3/manifests/custom-resources.yaml&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;이제 kubectl get nodes를 해보면 노드들이 Ready로 뜨며, 작업 가능한 K8s 클러스터 구성이 완료됐다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;746&quot; data-origin-height=&quot;138&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQu3nB/btsQ94wo8rh/TPKZp7bbbu9iinLSsgnmLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQu3nB/btsQ94wo8rh/TPKZp7bbbu9iinLSsgnmLk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQu3nB/btsQ94wo8rh/TPKZp7bbbu9iinLSsgnmLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQu3nB%2FbtsQ94wo8rh%2FTPKZp7bbbu9iinLSsgnmLk%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;746&quot; height=&quot;138&quot; data-origin-width=&quot;746&quot; data-origin-height=&quot;138&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;1278&quot; data-origin-height=&quot;702&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Vfz8D/btsQ6QtajJt/yHKOJ9KAM14MRypoFnc7TK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Vfz8D/btsQ6QtajJt/yHKOJ9KAM14MRypoFnc7TK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Vfz8D/btsQ6QtajJt/yHKOJ9KAM14MRypoFnc7TK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVfz8D%2FbtsQ6QtajJt%2FyHKOJ9KAM14MRypoFnc7TK%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;1278&quot; height=&quot;702&quot; data-origin-width=&quot;1278&quot; data-origin-height=&quot;702&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p 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;</description>
      <category>Kubernetes</category>
      <author>LOGPOSE 로그포스</author>
      <guid isPermaLink="true">https://crayeji.tistory.com/148</guid>
      <comments>https://crayeji.tistory.com/148#entry148comment</comments>
      <pubDate>Thu, 16 Oct 2025 20:00:08 +0900</pubDate>
    </item>
    <item>
      <title>[(Kubeadm) K8s 클러스터 구축 2/3] Kubeadm, Kubelet, Kubectl 설치</title>
      <link>https://crayeji.tistory.com/147</link>
      <description>&lt;figure id=&quot;og_1760487304911&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;[(Kubeadm) K8s 클러스터 구축 1/3] Container Runtime 설치&quot; data-og-description=&quot;개요kubeadm 기반으로 K8s클러스터를 구축하는 과정을 3편의 글로 요약해 본다.가용 Virtual Machine이 2대 이상(각 Machine의 메모리 2GB, 2 CPU 이상) 준비됐다고 가정한다.첫 단계로 Container Runtime을 설치한&quot; data-og-host=&quot;crayeji.tistory.com&quot; data-og-source-url=&quot;https://crayeji.tistory.com/146&quot; data-og-url=&quot;https://crayeji.tistory.com/146&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/c3Td50/hyZLntullM/maGDx7ACzD2pdBC0bRsk5K/img.png?width=272&amp;amp;height=318&amp;amp;face=0_0_272_318,https://scrap.kakaocdn.net/dn/ba3nKT/hyZLeDjC4N/APXthrIv6Th0PB3zxHWzI1/img.png?width=272&amp;amp;height=318&amp;amp;face=0_0_272_318&quot;&gt;&lt;a href=&quot;https://crayeji.tistory.com/146&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://crayeji.tistory.com/146&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/c3Td50/hyZLntullM/maGDx7ACzD2pdBC0bRsk5K/img.png?width=272&amp;amp;height=318&amp;amp;face=0_0_272_318,https://scrap.kakaocdn.net/dn/ba3nKT/hyZLeDjC4N/APXthrIv6Th0PB3zxHWzI1/img.png?width=272&amp;amp;height=318&amp;amp;face=0_0_272_318');&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;[(Kubeadm) K8s 클러스터 구축 1/3] Container Runtime 설치&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;개요kubeadm 기반으로 K8s클러스터를 구축하는 과정을 3편의 글로 요약해 본다.가용 Virtual Machine이 2대 이상(각 Machine의 메모리 2GB, 2 CPU 이상) 준비됐다고 가정한다.첫 단계로 Container Runtime을 설치한&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;crayeji.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://crayeji.tistory.com/146&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;앞선 글&lt;/a&gt;에서 노드들에 Pod가 실행될 수 있게 Container Runtime을 설치했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 클러스터를 구성하고 초기화하기 위한 &lt;b&gt;kubeadm&lt;/b&gt;, 각 노드에서 K8s API 서버 명령을 받아 Pod을 관리하고 Container Runtime이 컨테이너를 실행하게 하는 에이전트 &lt;b&gt;kubelet&lt;/b&gt;, 사용자가 API 서버를 통해 명령할 수 있는 CLI인 &lt;b&gt;kubectl&lt;/b&gt;을 설치해 본다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Master Node에 kubeadm, kubelet, kubectl을 설치할 것이며&lt;br /&gt;Worker Node에는 kubeadm, kubelet을 설치할 것이다. kubectl은 설치하지 않을 것이다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;a href=&quot;https://v1-33.docs.kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;Kubernetes v1.33을 기준&lt;/span&gt;&lt;/a&gt;으로 한다.&lt;br /&gt;&lt;span style=&quot;color: #222222;&quot;&gt;kubeadm은&amp;nbsp;&lt;/span&gt;kubelet과&lt;span style=&quot;color: #222222;&quot;&gt;&amp;nbsp;&lt;/span&gt;kubectl의 설치나 관리를 책임지지 않으므로, 버전 호환성 등에 대해서는 확인해야 한다.&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;i&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;작업 위치: Master Node &amp;amp; Worker Node&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GPG 키를 등록하고 &lt;span style=&quot;color: #333333;&quot;&gt;K8s 공식 apt 저장소를 추가하여 패키지 설치를 준비한다. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;(Master Node, Worker Node모두 패키지들을 설치해야 하므로, 동일 작업을 각각에 진행한다.)&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# 패키지 설치 준비
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl gpg&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# Kubernetes 패키지(kubeadm, kubelet, kubectl)검증을 위한 공개키(GPG key)등록
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.33/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# K8s APT repository를 시스템에 등록
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.33/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;i&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;작업 위치: Master Node&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;kubeadm, kubelet, kubectl 설치&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;sudo apt-get update
sudo apt-mark unhold kubelet kubeadm kubectl
sudo apt-get install -y kubelet kubeadm kubectl# worker node는 kubectl제외
sudo apt-mark hold kubelet kubeadm kubectl# worker node는 kubectl제외&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;sudo systemctl enable --now kubelet&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;i&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;작업 위치: Worker Node&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;kubeadm, kubelet 설치&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;sudo apt-get update
sudo apt-mark unhold kubelet kubeadm
sudo apt-get install -y kubelet kubeadm 
sudo apt-mark hold kubelet kubeadm &lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;sudo systemctl enable --now kubelet&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Kubernetes</category>
      <author>LOGPOSE 로그포스</author>
      <guid isPermaLink="true">https://crayeji.tistory.com/147</guid>
      <comments>https://crayeji.tistory.com/147#entry147comment</comments>
      <pubDate>Wed, 15 Oct 2025 20:00:13 +0900</pubDate>
    </item>
    <item>
      <title>[(Kubeadm) K8s 클러스터 구축 1/3] Container Runtime 설치</title>
      <link>https://crayeji.tistory.com/146</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kubeadm 기반으로 K8s클러스터를 구축하는 과정을 3편의 글로 요약해 본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가용 Virtual Machine이 2대 이상(각 Machine의 메모리 2GB, 2 CPU 이상) 준비됐다고 가정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 단계로 Container Runtime을 설치한다. Containerd를 사용한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. Container Runtime 설치&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;&lt;i&gt;Step 1. Containerd 설치&lt;/i&gt;&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;설명&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pod가 Node에서 실행될 수 있게, 클러스터의 노드들에 &lt;b&gt;Container Runtime&lt;/b&gt;을 설치한다.&lt;br /&gt;CRI를 통해 Container Runtime에 K8s가 인터페이스 한다. 이 글에서는 Containerd라는 Container Runtime을 설치해 본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/containerd/containerd/blob/main/docs/getting-started.md&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Getting-started 문서&lt;/a&gt;의 Containerd 설치 방법 중 Option1(=Official binaries를 이용해 설치하기)을 따라 진행한다.&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;apt/dnf 패키지를 이용한 설치&lt;/li&gt;
&lt;li&gt;소스 코드로부터 빌드하여 설치&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/containerd/containerd/releases&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;아카이브&lt;/a&gt;에서 아래 버전, OS, 컴퓨터 아키텍처(ARM64, AMD64 등)를 선택해 다운로드하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 바이너리 패키지, 무결성 검증 파일 2개를 다운로드하여 무결성 검사 진행 후 압축 해제를 하자.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;설치 참고&lt;/h3&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;#파일명
containerd-&amp;lt;VERSION&amp;gt;-&amp;lt;OS&amp;gt;-&amp;lt;ARCH&amp;gt;.tar.gz #공식 바이너리
containerd-&amp;lt;VERSION&amp;gt;-&amp;lt;OS&amp;gt;-&amp;lt;ARCH&amp;gt;.tar.gz.sha256sum #무결성 검사

#예시) VM에서 Linux 운영체제로 aarch64(=ARM64)를 사용중이므로 아래와 같이 선택
containerd-2.1.4-linux-arm64.tar.gz
containerd-2.1.4-linux-arm64.tar.gz.sha256sum&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;설치 명령어&lt;/h3&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;### 다운로드
wget https://github.com/containerd/containerd/releases/download/v2.1.4/containerd-2.1.4-linux-arm64.tar.gz
wget https://github.com/containerd/containerd/releases/download/v2.1.4/containerd-2.1.4-linux-arm64.tar.gz.sha256sum

### sha256 무결성 검증
sha256sum -c containerd-2.1.4-linux-arm64.tar.gz.sha256sum

### 압축 해제
sudo tar Cxzvf /usr/local containerd-2.1.4-linux-arm64.tar.gz&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Start containerd via systemd&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;containerd를 systemd 서비스로 등록했다.&lt;br /&gt;즉, Containerd 프로세스도 systemd가 생성한 cgroup에서 실행되게 한다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# 아래 경로에 설정
sudo mkdir -p /usr/local/lib/systemd/system
cd /usr/local/lib/systemd/system

# containerd.service 파일을 링크로 설치하기
sudo wget https://raw.githubusercontent.com/containerd/containerd/main/containerd.service

# 적용 및 확인
sudo systemctl daemon-reload # 새 유닛 파일 읽기
sudo systemctl enable --now containerd # 부팅 시 자동 실행
systemctl status containerd # 확인&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Customizing containerd&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;containerd는 daemon level의 옵션을 지정하기 위해 /etc/containerd/config.toml 파일을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kubernetes.io/docs/tasks/administer-cluster/kubeadm/configure-cgroup-driver/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Cgroup 설정&lt;/a&gt;(SystemdCgroup = true)이 필요할 수 있다. &lt;span style=&quot;background-color: #ffffff; color: #222222; text-align: left;&quot;&gt;&lt;b&gt;컨테이너 런타임과 kubelet의 cgroup 드라이버를 일치&lt;/b&gt;시켜야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 명령을 통해 Default config를 만들 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;sudo mkdir -p /etc/containerd 
sudo containerd config default | sudo tee /etc/containerd/config.toml&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;자세한 사항은 아래를 참고한다.&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://github.com/containerd/containerd/blob/main/docs/man/containerd-config.toml.5.md&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;config.toml에 관한 설명 및 샘플&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://v1-33.docs.kubernetes.io/ko/docs/setup/production-environment/container-runtimes/#containerd&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;containerd를 CRI 런타임으로 사용하는 데 필요한 단계에 관한 간략한 설명&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;&lt;i&gt;Step 2. runc 설치&lt;/i&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;official binaries를 통해 설치했기 때문에, runc와 CNI를 별도로 설치해주어야 한다.&lt;br /&gt;runc는 OCI사양에 따라 Linux에서 컨테이너를 실행하기 위한 CLI 도구이다.&lt;br /&gt;runc.ARCH 바이너리를 &lt;a href=&quot;https://github.com/opencontainers/runc/releases&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;아카이브&lt;/a&gt;에서 다운한다. 마찬가지로 검증 후 /usr/local/sbin/runc에 설치한다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# 다운로드
sudo wget https://github.com/opencontainers/runc/releases/download/v1.3.1/runc.arm64
sudo wget https://github.com/opencontainers/runc/releases/download/v1.3.1/runc.arm64.asc&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# fingerprint 확인(공개키를 받아오기 위함)
gpg --verify runc.arm64.asc runc.arm64&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;모든 릴리스는 &lt;a href=&quot;https://github.com/opencontainers/runc/blob/main/runc.keyring&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식 레포지토리&lt;/a&gt;에 있는 키 중 하나로 서명된다고 하니 참고한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확인한 fingerprint를 통해 공개 키를 받아 검증 및 설치를 진행한다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# 공개키를 keyserver에서 받아옴
gpg --keyserver keyserver.ubuntu.com --recv-keys &amp;lt;fingerprint&amp;gt;

# 검증
gpg --verify runc.arm64.asc runc.arm64

# install
sudo install -m 755 runc.arm64 /usr/local/sbin/runc

# 확인
/usr/local/sbin/runc --version&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;i&gt;&lt;b&gt;Step3. CNI plugins 다운로드&lt;/b&gt;&lt;/i&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CNI 플러그인을 설치한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역시나 cni-plugins-&amp;lt;OS&amp;gt;-&amp;lt;ARCH&amp;gt;-&amp;lt;VERSION&amp;gt;.tgz를 다운로드&amp;amp;검증&amp;amp;설치한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;참고) 나는 해당 플러그인 대신 Calico를 쓸 것이며, 클러스터 생성 이후에 Calico를 설치하므로 &lt;br /&gt;여기서 CNI plugins 다운로드는 &lt;b&gt;생략한다&lt;/b&gt;. (추후 3편 글에서 Calico 설치 언급)&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# 다운
sudo wget https://github.com/containernetworking/plugins/releases/download/v1.8.0/cni-plugins-linux-arm-v1.8.0.tgz
sudo wget https://github.com/containernetworking/plugins/releases/download/v1.8.0/cni-plugins-linux-arm-v1.8.0.tgz.sha256

# 검증 (OK가 뜨면 됨)
sha256sum -c cni-plugins-linux-arm-v1.8.0.tgz.sha256

# 설치
sudo mkdir -p /opt/cni/bin
sudo tar Cxzvf /opt/cni/bin cni-plugins-linux-arm-v1.8.0.tgz&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 style=&quot;text-align: center;&quot; data-ke-size=&quot;size23&quot;&gt;Pod이 실행될 노드들에 동일 작업(Step1-3의 Container Runtime구성)을 진행한다.&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&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;/ul&gt;
&lt;figure id=&quot;og_1760581414705&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;[(Kubeadm) K8s 클러스터 구축 2/3] Kubeadm, Kubelet, Kubectl 설치&quot; data-og-description=&quot;[(Kubeadm) K8s 클러스터 구축 1/3] Container Runtime 설치개요kubeadm 기반으로 K8s클러스터를 구축하는 과정을 3편의 글로 요약해 본다.가용 Virtual Machine이 2대 이상(각 Machine의 메모리 2GB, 2 CPU 이상) 준비됐&quot; data-og-host=&quot;crayeji.tistory.com&quot; data-og-source-url=&quot;https://crayeji.tistory.com/147&quot; data-og-url=&quot;https://crayeji.tistory.com/147&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/czNrdH/hyZLyo285I/2SEUMbYLwMoE24PK23LAHK/img.png?width=282&amp;amp;height=318&amp;amp;face=0_0_282_318,https://scrap.kakaocdn.net/dn/efa2Bz/hyZLvlyIE5/PzW7YM1BLGzJq1K8eHUQkk/img.png?width=282&amp;amp;height=318&amp;amp;face=0_0_282_318&quot;&gt;&lt;a href=&quot;https://crayeji.tistory.com/147&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://crayeji.tistory.com/147&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/czNrdH/hyZLyo285I/2SEUMbYLwMoE24PK23LAHK/img.png?width=282&amp;amp;height=318&amp;amp;face=0_0_282_318,https://scrap.kakaocdn.net/dn/efa2Bz/hyZLvlyIE5/PzW7YM1BLGzJq1K8eHUQkk/img.png?width=282&amp;amp;height=318&amp;amp;face=0_0_282_318');&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;[(Kubeadm) K8s 클러스터 구축 2/3] Kubeadm, Kubelet, Kubectl 설치&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;[(Kubeadm) K8s 클러스터 구축 1/3] Container Runtime 설치개요kubeadm 기반으로 K8s클러스터를 구축하는 과정을 3편의 글로 요약해 본다.가용 Virtual Machine이 2대 이상(각 Machine의 메모리 2GB, 2 CPU 이상) 준비됐&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;crayeji.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;</description>
      <category>Kubernetes</category>
      <author>LOGPOSE 로그포스</author>
      <guid isPermaLink="true">https://crayeji.tistory.com/146</guid>
      <comments>https://crayeji.tistory.com/146#entry146comment</comments>
      <pubDate>Tue, 14 Oct 2025 20:00:55 +0900</pubDate>
    </item>
    <item>
      <title>[SigV4] 개념, 서명된 요청 생성하기(JS)</title>
      <link>https://crayeji.tistory.com/145</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS API 요청에 인증 정보를 추가하는 서명 메커니즘.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청 주체를 확인하고, 내용 또한 중간에 변조되지 않았다는 것을 증명하기 위한 서명 방식 중 하나이다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SigV4 서명 프로세스를 이용해서 요청에 Authorization header를 추가하면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청을 받는 쪽(AWS 리소스. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;API Gateway 등&lt;/span&gt;)에서도 요청 쪽에서 서명을 만든 것과 같은 원리로 서명을 만들어 서로 비교해 본다. 같으면 요청을 처리한다.&amp;nbsp;&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;br /&gt;이를 통해 요청이 정확히 그 시각&amp;amp;그 클라이언트가 보낸 것임을 보장하고, &lt;br /&gt;요청 Body까지 포함해서 무결성 확인하며, 요청 Expiration을 제한해 재사용 공격 방지도 가능해진다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;요청에 포함시킬 요소&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SigV4로 HTTP/HTTPS요청을 보낼 때는 아래 요소들을 포함해야 한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Endpoint Speciifcation&lt;/b&gt;&lt;br /&gt;요청 보낼 DNS name을 명시해야 한다. 주로 서비스 코드, 리전을 포함하는 name이다.&lt;br /&gt;예를 들어 us-east-1리전의 Amazon DynamoDB서비스는 엔드포인트가 dynamodb.us-east-1.com과 같은 형태가 된다.&lt;br /&gt;HTTP/1.1 요청의 경우 꼭 Host 헤더를 포함시켜야 한다.&lt;br /&gt;HTTP/2 요청은 :authority 헤더 또는 Host헤더를 포함시키면 된다. (:authority헤더 권장)&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Action&lt;/b&gt;&lt;br /&gt;서비스 API action을 명세한다.&lt;br /&gt;예를 들어 DynamoDB에서 CreateTable 하는 Action이라거나&lt;br /&gt;EC2를 DescribeInstance 한다는 등이 있다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Action parameters&lt;/b&gt;&lt;br /&gt;각 API Action에 필요한 파라미터 명시한다. 주로 API Version 등이 쓰인다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Date&lt;/b&gt;&lt;br /&gt;요청 시 date, time을 작성한다.&lt;br /&gt;이는 써드파티가 요청을 인터셉팅해서 나중에 대시 submitting 하는 것을 막아준다.&lt;br /&gt;credentail scope의 date는 request의 date와 무조건 매치되어야 한다. &lt;br /&gt;timestamp는 반드시 UTC, ISO8601 format: YYYYMMDD_T_HHMMSS_Z을 따라야 한다. milliseconds는 포함시키지 않는다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Authentication information&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1759193741824&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;- Algorithm
서명 프로세스에 사용하는 알고리즘. 
SigV4의 경우 AWS4-HMAC-SHA256을 사용해서 HMAC-SHA256해시 알고리즘으로 SigV4를 지정한다.

- Credentials
Access_key 및 자격 증명 범위 등을 연결하여 구성된 문자열.
SigV4에서는 액세스 키 ID, YYYYMMDD 형식의 날짜, 리전 코드, 서비스 코드, aws4_request 종료 문자열(슬래시(/)로 구분됨)이 포함된다. 
(리전 코드, 서비스 코드 및 종료 문자열에는 소문자를 사용해아함)
AKIAIOSFODNN7EXAMPLE/YYYYMMDD/region/service/aws4_request

- Signed Headers
서명에 포함할 HTTP 헤더. (;)으로 구분된다. ( host;x-amz-date ) 이런식

- Signature
계산된 서명을 나타내는 16진수 인코딩 문자열. Algorithm파라미터에서 지정했던 알고리즘으로 계산해야함.&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;인증 방법&lt;/b&gt;&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;HTTP Authorization 헤더를 사용하는 방법&lt;/b&gt;&lt;br /&gt;첫 번째 방법으로 HTTP 기본 인증 방식인 Authorization 헤더를 쓸 수 있다.&lt;br /&gt;&lt;br /&gt;아래와 같은 형태로 SigV4 헤더를 구성한다.
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;Authorization: AWS4-HMAC-SHA256
Credential=AKIAIOSFODNN7EXAMPLE/20130524/us-east-1/s3/aws4_request, 
SignedHeaders=host;range;x-amz-date, 
Signature=fe5f80f77d5fa3beca038a248ff027d0445342fe2855ddc963176630326f1024&lt;/code&gt;&lt;/pre&gt;
Authorization과 Credential사이에만 빼고 각 요소를 쉼표(,)로 구분해야 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;참고) Authorization 헤더 값 예시&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;1890&quot; data-origin-height=&quot;1078&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chgwQH/btsQVwUpYTR/ynHAUBD9Zp0aWRCk0aQYuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chgwQH/btsQVwUpYTR/ynHAUBD9Zp0aWRCk0aQYuK/img.png&quot; data-alt=&quot;출처: https://docs.aws.amazon.com/ko_kr/IAM/latest/UserGuide/reference_sigv.html&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chgwQH/btsQVwUpYTR/ynHAUBD9Zp0aWRCk0aQYuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchgwQH%2FbtsQVwUpYTR%2FynHAUBD9Zp0aWRCk0aQYuK%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;1890&quot; height=&quot;1078&quot; data-origin-width=&quot;1890&quot; data-origin-height=&quot;1078&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: https://docs.aws.amazon.com/ko_kr/IAM/latest/UserGuide/reference_sigv.html&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; start=&quot;2&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Query String Parameter를 사용하는 방법&lt;/b&gt;&lt;br /&gt;두 번째 방법은 전체 요청을 URL로 표현해 버리는 것이다.&lt;br /&gt;이 경우 인증 정보 및 요청 정보를 쿼리 파라미터에 다 포함시키는 것이다. (따라서, 서명된 URL이라고 부르기도 한다)&lt;br /&gt;X-Amz-Expires을 통해 최대 7일까지 유효한 링크를 HTML에 포함시키는 등 할 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;참고) SigV4 서명된 URL 예시
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;https://s3.amazonaws.com/amzn-s3-demo-bucket/test.txt ?
X-Amz-Algorithm=AWS4-HMAC-SHA256 &amp;amp;
X-Amz-Credential=&amp;lt;your-access-key-id&amp;gt;/20130721/&amp;lt;region&amp;gt;/s3/aws4_request &amp;amp;
X-Amz-Date=20130721T201207Z &amp;amp;
X-Amz-Expires=86400 &amp;amp;
X-Amz-SignedHeaders=host &amp;amp;X-Amz-Signature=&amp;lt;signature-value&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;요청 만들기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 설명한 요소 및 인증방법들을 통해 SigV4 서명된 요청을 생성해서 보내보자.&lt;br /&gt;SDK를 사용하면 훨씬 간단하게 구현 가능하며 권장되는 방식이지만, 학습 차원에서 단계별로 코드를 작성해 본다.&lt;br /&gt;할 일을 3단계로 요약하면 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1759136280755&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;1. Canonical request 만들기 (앞선 요청 정보들을 기반으로 표준 요청을 만든다.)
2. AWS Credentials를 통해 Signature를 계산한다.
3. Signature를 request의 Authorization header에 넣는다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정을 통해 서명된 요청을 보내면, AWS에서도 동일하게 서명을 계산해서 같은 값이면 액세스를 허용한다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;0. Date, PayloadHash 설정&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 SigV4는 요청 Body까지 포함해서 무결성 확인하며, 요청 Expiration을 제한해 재사용 공격 방지도 가능해진다고 했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그를 위한 Payload Hash와 요청 시간을 설정하는 코드이다.&lt;/p&gt;
&lt;pre id=&quot;code_1759192366404&quot; class=&quot;qml&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  // 1. 요청 시간 설정 (UTC) 
  const now = new Date();
  const date = now.toISOString().slice(0, 10).replace(/-/g, '');
  const xAmzDate = now.toISOString().slice(0, 19).replace(/[-:]/g, '').replace('.', '') + 'Z';

  // 2. Payload Hash 계산
  const payloadHash = crypto.createHash('sha256').update(body).digest('hex');&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;br /&gt;1. Canonical Request 생성&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청 내용(Host, Action, Header 등)을 표준 형식으로 정렬한다.&lt;br /&gt;Canonical Request는 나중에 String to Sign을 생성하는 데 들어가는 Input 중 하나다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Canonical Request 구조
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;HTTPMethod&amp;gt;\n
&amp;lt;CanonicalURI&amp;gt;\n
&amp;lt;CanonicalQueryString&amp;gt;\n
&amp;lt;CanonicalHeaders&amp;gt;\n
&amp;lt;SignedHeaders&amp;gt;\n
&amp;lt;HashedPayload&amp;gt;&lt;/code&gt;&lt;/pre&gt;
이런 식으로 \n 줄 바꿈 문자로 구분해서 연결하는 형태다.
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// 3. CANONICAL REQUEST 구성 코드
const headers = {
	'host': urlObj.hostname,
	'x-amz-content-sha256': payloadHash,
	'x-amz-date': xAmzDate
};

const canonicalHeaderParts = [];
for (const key of Object.keys(headers).sort()) {
	canonicalHeaderParts.push(`${key}:${headers[key]}`);
}

const canonicalHeaders = canonicalHeaderParts.join('\n') + '\n';
console.log('canonicalHeaders: ', canonicalHeaders);

// 4. Signed Headers 구성
const signedHeaders = Object.keys(headers).sort().join(';');

// 5. Canonical Request 구성
const canonicalRequest = [
	method,
	path,
	'', // query string 없음
	canonicalHeaders,
	signedHeaders,
	hashedPayload
].join('\n');&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. 표준&amp;nbsp;요청(Canonical&amp;nbsp;Request)의&amp;nbsp;해시&amp;nbsp;생성&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hashedPayload를&amp;nbsp;생성하는&amp;nbsp;데&amp;nbsp;사용한&amp;nbsp;것과&amp;nbsp;동일한&amp;nbsp;알고리즘을&amp;nbsp;사용하여&amp;nbsp;canonical&amp;nbsp;request를&amp;nbsp;해시한다.&amp;nbsp;&lt;br /&gt;(Canonical Request의 해시는 소문자 16진수 문자의 문자열이다.)&lt;/p&gt;
&lt;pre id=&quot;code_1759136494427&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 6. hashedCanonicalRequest 생성
const hashedCanonicalRequest = crypto.createHash('sha256').update(canonicalRequest).digest('hex');&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3.&lt;span&gt; &lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;서명할 문자열(String to Sign) 생성&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Canonical Request와 추가 정보(알고리즘, 요청 날짜, 자격 증명 범위, 표준 요청의 해시)를 사용하여 String to Sign을 생성한다.&lt;/p&gt;
&lt;pre class=&quot;autohotkey&quot;&gt;&lt;code&gt;`Algorithm` \n `RequestDateTime` \n `CredentialScope` \n `HashedCanonicalRequest`&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: right;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;* 주의: 맨 끝에는 \n를 쓰면 안 된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// 7. String to Sign 구성
const credentialScope = `${date}/${region}/${service}/aws4_request`;

const stringToSign = [
    'AWS4-HMAC-SHA256',
    xAmzDate,
    credentialScope,
    hashedCanonicalRequest
].join('\n');&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;br /&gt;4. 서명 키(Signing Key) 추출&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시크릿 액세스 키를 사용하여 요청에 서명하는 데 사용되는 키를 파생한다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;DateKey = HMAC-SHA256(&quot;AWS4&quot;+&quot;`&amp;lt;SecretAccessKey&amp;gt;`&quot;, &quot;`&amp;lt;YYYYMMDD&amp;gt;`&quot;) 
DateRegionKey = HMAC-SHA256(`&amp;lt;DateKey&amp;gt;`, &quot;`&amp;lt;aws-region&amp;gt;`&quot;) 
DateRegionServiceKey = HMAC-SHA256(`&amp;lt;DateRegionKey&amp;gt;`, &quot;`&amp;lt;aws-service&amp;gt;`&quot;) 
SigningKey = HMAC-SHA256(`&amp;lt;DateRegionServiceKey&amp;gt;`, &quot;aws4_request&quot;)&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;&lt;code&gt;Key&lt;/code&gt; - 시크릿 액세스 키를 포함하는 문자열.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Date&lt;/code&gt; - 자격 증명 범위에 사용되는 날짜(&lt;i&gt;YYYYMMDD&lt;/i&gt; 형식)를 포함하는 문자열.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Region&lt;/code&gt; - 리전 코드(예: &lt;code&gt;us-east-1&lt;/code&gt;)를 포함하는 문자열.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Service&lt;/code&gt; - 서비스 코드(예: &lt;code&gt;ec2&lt;/code&gt;)를 포함하는 문자열.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// 8. Signing Key 생성
const kDate = crypto.createHmac('sha256', 'AWS4' + secretAccessKey).update(date).digest();
const kRegion = crypto.createHmac('sha256', kDate).update(region).digest();
const kService = crypto.createHmac('sha256', kRegion).update(service).digest();
const kSigning = crypto.createHmac('sha256', kService).update('aws4_request').digest();&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;br /&gt;5. 서명(Signature) 계산&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파생된 서명 키(Signing Key)를 해시 키로 사용하여, 서명할 문자열(String to Sign)에 Key가 추가된 해시 계산을 한다.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// 9. Signature 계산
const signature = crypto.createHmac('sha256', signingKey).update(stringToSign).digest('hex');&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;br /&gt;6. 요청에 서명 추가&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP 헤더 또는 요청의 쿼리 문자열에 계산된 서명을 추가.&lt;br /&gt;나는 Authorization 헤더에 추가하는 방법을 사용했다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같은 형태를 만들어내야 한다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Authorization: AWS4-HMAC-SHA256 Credential=AKIAIOSFODNN7EXAMPLE/20220830/us-east-1/ec2/aws4_request, SignedHeaders=host;x-amz-date, Signature=`calculated-signature`&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// 10. Authorization 구성
const authorization = `AWS4-HMAC-SHA256 Credential=${accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;&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;이렇게 만든 Authorization을 최종 요청 헤더에 포함시켜 요청을 보내면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS 측에서는 이렇게 만들어진 요청을 받으면, Body와 요청 시간을 바탕으로 동일한 계산을 진행해서 동일한지 체크한다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// 11. 최종 요청 헤더 구성
const finalHeaders = {
    'content-type': 'application/json',
    'Authorization': authorization,
    'x-amz-date': xAmzDate,
    'x-amz-content-sha256': payloadHash
};

console.log(&quot;Signed Headers:&quot;, finalHeaders);

// 12. 서명된 요청 보내기
try {
const axiosResponse = await axios({
    method: method,
    url,
    headers: finalHeaders,
    data: body,
});

    console.log(&quot;Response status:&quot;, axiosResponse.status);
    console.log(&quot;Response data(OUTPUT):&quot;, axiosResponse.data);

} catch (error) {
    console.error(&quot;Request error:&quot;, error.response?.data || error.message);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고 자료&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/IAM/latest/UserGuide/reference_sigv.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.aws.amazon.com/ko_kr/IAM/latest/UserGuide/reference_sigv.html&lt;/a&gt;&lt;/p&gt;</description>
      <category>AWS</category>
      <author>LOGPOSE 로그포스</author>
      <guid isPermaLink="true">https://crayeji.tistory.com/145</guid>
      <comments>https://crayeji.tistory.com/145#entry145comment</comments>
      <pubDate>Tue, 30 Sep 2025 20:00:11 +0900</pubDate>
    </item>
    <item>
      <title>CKA 쿠버네티스 자격증 취득과정 기록 (2025. 02 변경 이후)</title>
      <link>https://crayeji.tistory.com/144</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;취득 동기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cloud,&amp;nbsp;MSA에&amp;nbsp;관심을&amp;nbsp;가진&amp;nbsp;후로&amp;nbsp;적합한&amp;nbsp;기술을&amp;nbsp;찾다 보니&amp;nbsp;쿠버네티스에 관심을 갖게 됐다&lt;br /&gt;그때부터 3개월 정도 스터디를 해보니까, 리소스를 기반으로 여러 서비스 배포는 물론이고 Security, Networking, Scheduling까지 마치 농장 운영하듯이 운영 가능한 게 굉장히 짜임새 있게 느껴졌다. 클러스터에서 필요한 개념들이 추상화가 매우 잘 되어 있어서, yaml로 이렇게 명쾌하게 시스템을 구성할 수 있구나 싶었다. 예를 들면 Ingress로 룰을 정의하고 Ingress Controller Pod가 실질적으로 처리하도록 역할이 분리되어 있으면서도, 이 모든 건 특정 종류의 리소스로 정의된 것이라는 점 등이다.&lt;br /&gt;공부를&amp;nbsp;하면&amp;nbsp;할수록,&amp;nbsp;앞으로&amp;nbsp;더&amp;nbsp;작은&amp;nbsp;단위의&amp;nbsp;더&amp;nbsp;많은&amp;nbsp;서비스를&amp;nbsp;체계적으로&amp;nbsp;운영하기&amp;nbsp;위해서&amp;nbsp;이&amp;nbsp;기술이&amp;nbsp;실질적으로&amp;nbsp;쓸모&amp;nbsp;있겠다는&amp;nbsp;생각이&amp;nbsp;들었다.&amp;nbsp;그래서&amp;nbsp;쿠버네티스&amp;nbsp;관리자의&amp;nbsp;첫&amp;nbsp;단추로&amp;nbsp;CKA를&amp;nbsp;따보기로&amp;nbsp;했다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2025. 02 이후 변경 내용&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2025. 02 이후 CKA 시험 내용 및 문제 비중이 변경됐다. (심지어 비슷한 시기에 가격도 $395 -&amp;gt;$445로 바뀌었다ㅠㅠ)&lt;br /&gt;기존 시험에서 주요 출제 내용이었던 &lt;b&gt;etcd 백업/복원, Kubernetes 버전 업그레이드&lt;/b&gt; 관련 내용은 우선순위가 내려갔다.&lt;br /&gt;(7월&amp;nbsp;중순&amp;nbsp;기준, 시험에도 출제되지 않았다.)&amp;nbsp;&lt;br /&gt;&lt;br /&gt;대신,&amp;nbsp;&lt;b&gt;Helm&lt;/b&gt;/Kustomize,&amp;nbsp;&lt;b&gt;Gateway&amp;nbsp;API&lt;/b&gt;, &lt;b&gt;HPA&lt;/b&gt;, &lt;b&gt;CRDs/Operators 등에&lt;/b&gt; 대한 이해가 더 필요하다. &lt;br /&gt;시험에서도&amp;nbsp;Helm을&amp;nbsp;통해&amp;nbsp;argo-cd차트를&amp;nbsp;설치하는&amp;nbsp;문제,&amp;nbsp;기존&amp;nbsp;Ingress를&amp;nbsp;Gateway&amp;nbsp;API&amp;amp;HttpRoute로&amp;nbsp;&lt;b&gt;Migrating 하는&lt;/b&gt;&amp;nbsp;등&amp;nbsp;형식의&amp;nbsp;문제가&amp;nbsp;출제되었다.&amp;nbsp;CNI설치&amp;nbsp;또한&amp;nbsp;CRDs/Operators기반이므로&amp;nbsp;반드시&amp;nbsp;학습해야&amp;nbsp;한다.&amp;nbsp;(Kustomize문제는&amp;nbsp;7월&amp;nbsp;중순&amp;nbsp;기준&amp;nbsp;출제되지&amp;nbsp;않았다.) &lt;br /&gt;또한, Storageclass기반으로 동적 프로비저닝하는 부분도 중요하다.&lt;br /&gt;&lt;br /&gt;그 외에는 Sidecar 구성, Configmap수정, 각종 리소스를 만들기, 서비스를 expose 등 비교적 평이하고 예상 가능한 문제들로 구성됐다.&amp;nbsp;&lt;br /&gt;추가적인 변경 정보는 아래 영상을 참고하면 좋다. K8s기본 개념이 있는 상태에서, CKA 준비 초반에 보기 좋은 영상이다.&amp;nbsp;&lt;br /&gt;타임스탬프에 카테고리가 나뉘어있으니, 특정 파트를&amp;nbsp;&amp;nbsp;간단히 확인하기 좋았다. 그리고&amp;nbsp;개그 욕심이 좀 있으신 듯&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=eGv6iPWQKyo&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/0g78o/hyZnhBH12a/LP1KS6jrh4iGsh2MhE4K9k/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=970_170_1204_424,https://scrap.kakaocdn.net/dn/2JHt6/hyZq1xcQAk/ZvuH4K9E6FS39ba1gGtx5K/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=970_170_1204_424&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;2025 CKA Exam Questions &amp;amp; Solutions UPDATE! | Full Walkthrough!&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/eGv6iPWQKyo&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&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;b&gt;어떻게&amp;nbsp;준비했나&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;기본 준비 사항&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Udemy 강의 전체 듣고 Practice test 3바퀴 돌렸다.&lt;br /&gt;1번 돌릴 때는 이런 게 있구나~였고, 2번째는 내가 뭘 모르는지 확인, 3번째는 이렇게도 될까? 하며 상세 내용을 중심으로 확인했다.&lt;br /&gt;마지막 1-2주는 Lightning Lab과 MockExam1, 2, 3을 마찬가지로 3번씩 풀었다.&amp;nbsp; 이 문제들도 이제 2025 기준으로 업데이트가 되었기 때문에 실제 시험과 개념적으로 비슷하고 분명 도움 된다.&lt;br /&gt;시험인 걸 고려해서(2시간, 17문제) 명령형으로 풀 수 있는 문제는 최대한 명령형으로 연습해야 한다.(예를 들면 create deploy / pod run / expose service / hpa autoscale / create priorityclass / create serviceaccount, create role...)&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;❗️주요 준비 사항&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 중요한 것은 &lt;b&gt;변경된 기출문제&lt;/b&gt;를 풀고 정보를 얻는 것이다.&lt;br /&gt;기존 CKA기준으로 준비하다가, 유형이 생각보다 많이 다르다는 후기를 듣고 시험을 1주일 미룬 후 신규 유형으로 바짝 다시 준비했다.&lt;br /&gt;(이게 신의 한 수였다고 생각한다...)&lt;br /&gt;&lt;br /&gt;1. 2025 기출 정보&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;My CKA Story (with New CKA 2025 February Changes)&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;I faced the Certified Kubernetes Administrator (CKA) exam on 26th May 2025. The CKA exam was changed after February 2025. I got to know&amp;hellip;&quot; data-og-host=&quot;medium.com&quot; data-og-source-url=&quot;https://medium.com/@dilancs/my-cka-story-with-new-cka-2025-february-changes-4de06a246e4f&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dorQ2m/hyZm8Y5Y9G/9Yy42Ickdm9kmYm703vqtk/img.png?width=533&amp;amp;height=478&amp;amp;face=0_0_533_478,https://scrap.kakaocdn.net/dn/bH3h8S/hyZm61eSni/0ubGdLaNuK1sghpqJvdFA1/img.png?width=487&amp;amp;height=460&amp;amp;face=0_0_487_460&quot; data-og-url=&quot;https://medium.com/@dilancs/my-cka-story-with-new-cka-2025-february-changes-4de06a246e4f&quot;&gt;&lt;a href=&quot;https://medium.com/@dilancs/my-cka-story-with-new-cka-2025-february-changes-4de06a246e4f&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://medium.com/@dilancs/my-cka-story-with-new-cka-2025-february-changes-4de06a246e4f&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dorQ2m/hyZm8Y5Y9G/9Yy42Ickdm9kmYm703vqtk/img.png?width=533&amp;amp;height=478&amp;amp;face=0_0_533_478,https://scrap.kakaocdn.net/dn/bH3h8S/hyZm61eSni/0ubGdLaNuK1sghpqJvdFA1/img.png?width=487&amp;amp;height=460&amp;amp;face=0_0_487_460');&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;My CKA Story (with New CKA 2025 February Changes)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;I faced the Certified Kubernetes Administrator (CKA) exam on 26th May 2025. The CKA exam was changed after February 2025. I got to know&amp;hellip;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;medium.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;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;The CKA Exam Changed After February 18 &amp;mdash; Here&amp;rsquo;s What You Actually Need to Practice Now&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;For the Certified Kubernetes Administrator (CKA) exam in 2025, the main thing you need is not just to memorize commands. The exam has&amp;hellip;&quot; data-og-host=&quot;medium.com&quot; data-og-source-url=&quot;https://medium.com/@DynamoDevOps/the-cka-exam-changed-after-february-18-heres-what-you-actually-need-to-practice-now-a9941213a65a&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/7NHXo/hyZqQJdvku/8rlopNc6fa5i8E5m5GkBG1/img.png?width=1024&amp;amp;height=1024&amp;amp;face=0_0_1024_1024,https://scrap.kakaocdn.net/dn/5uStv/hyZqV4PnN0/pz7eYKqiQfAEKgOFFkhxhk/img.png?width=1024&amp;amp;height=1536&amp;amp;face=0_0_1024_1536,https://scrap.kakaocdn.net/dn/AfsmS/hyZqZlR0Qm/mNGCcoVSPxk8s1DmoAbOUk/img.png?width=1024&amp;amp;height=1536&amp;amp;face=0_0_1024_1536&quot; data-og-url=&quot;https://medium.com/@DynamoDevOps/the-cka-exam-changed-after-february-18-heres-what-you-actually-need-to-practice-now-a9941213a65a&quot;&gt;&lt;a href=&quot;https://medium.com/@DynamoDevOps/the-cka-exam-changed-after-february-18-heres-what-you-actually-need-to-practice-now-a9941213a65a&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://medium.com/@DynamoDevOps/the-cka-exam-changed-after-february-18-heres-what-you-actually-need-to-practice-now-a9941213a65a&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/7NHXo/hyZqQJdvku/8rlopNc6fa5i8E5m5GkBG1/img.png?width=1024&amp;amp;height=1024&amp;amp;face=0_0_1024_1024,https://scrap.kakaocdn.net/dn/5uStv/hyZqV4PnN0/pz7eYKqiQfAEKgOFFkhxhk/img.png?width=1024&amp;amp;height=1536&amp;amp;face=0_0_1024_1536,https://scrap.kakaocdn.net/dn/AfsmS/hyZqZlR0Qm/mNGCcoVSPxk8s1DmoAbOUk/img.png?width=1024&amp;amp;height=1536&amp;amp;face=0_0_1024_1536');&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;The CKA Exam Changed After February 18 &amp;mdash; Here&amp;rsquo;s What You Actually Need to Practice Now&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;For the Certified Kubernetes Administrator (CKA) exam in 2025, the main thing you need is not just to memorize commands. The exam has&amp;hellip;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;medium.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기 기출문제를 Udemy Practice test환경(아무거나)에서 다양하게 테스트해보고,&lt;br /&gt;그 기출문제들을 기반으로 GPT에게 시나리오 및 풀이를 만들어 달라고 해서 풀어보고 관련 개념 정보를 얻었다.&amp;nbsp;&lt;br /&gt;그&amp;nbsp;과정에서&amp;nbsp;helm으로&amp;nbsp;argo&amp;nbsp;설치&amp;nbsp;시&amp;nbsp;발생&amp;nbsp;가능한&amp;nbsp;실수,&amp;nbsp;CNI설치&amp;nbsp;관련&amp;nbsp;돌발&amp;nbsp;에러&amp;nbsp;등을&amp;nbsp;미리&amp;nbsp;확인할&amp;nbsp;수&amp;nbsp;있다.&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;2.&amp;nbsp;Reddit&lt;br /&gt;Reddit에서 기출문제 / 시험 후기 / 명령어 꿀팁 / 숨은 풀이 방법을 싹싹 긁어모았다.&amp;nbsp;&lt;br /&gt;2025 변경 이후 한국 커뮤니티에는 아직 자료가 많이 없기 때문에 아주 든든한 자료가 된다.&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;r/CKAExam&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;A Subreddit to discuss and help each other prepare for the Kubernetes Certified Administrator (CKA) Exam.&quot; data-og-host=&quot;www.reddit.com&quot; data-og-source-url=&quot;https://www.reddit.com/r/CKAExam/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dzVlk2/hyZqTlDBGV/mkdqd1WmQrDP7tqMyHgDB0/img.png?width=192&amp;amp;height=192&amp;amp;face=0_0_192_192,https://scrap.kakaocdn.net/dn/bQIKQO/hyZm9DFV5q/H12kDbWPaZBwwEjKYTdh21/img.png?width=192&amp;amp;height=192&amp;amp;face=0_0_192_192&quot; data-og-url=&quot;https://www.reddit.com/r/CKAExam/&quot;&gt;&lt;a href=&quot;https://www.reddit.com/r/CKAExam/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.reddit.com/r/CKAExam/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dzVlk2/hyZqTlDBGV/mkdqd1WmQrDP7tqMyHgDB0/img.png?width=192&amp;amp;height=192&amp;amp;face=0_0_192_192,https://scrap.kakaocdn.net/dn/bQIKQO/hyZm9DFV5q/H12kDbWPaZBwwEjKYTdh21/img.png?width=192&amp;amp;height=192&amp;amp;face=0_0_192_192');&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;r/CKAExam&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A Subreddit to discuss and help each other prepare for the Kubernetes Certified Administrator (CKA) Exam.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.reddit.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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;소소한 팁&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아주 기본적이지만 유용한 팁을 혹시 몰라 기록해 둔다.&lt;br /&gt;&lt;b&gt;1. kubectl explain&lt;/b&gt;&lt;br /&gt;리소스 정의 구조를 설명해 주는 명령어이다.&amp;nbsp;&lt;br /&gt;HPA, VPA, gateway api나 비교적 새로 나온 리소스들은 공식문서에서 정확한 예시 자료를 찾기 어렵거나 없을 때가 있다.&lt;br /&gt;그 외에도 가물가물한 것들을 빠르게 파악하고 yaml을 작성하기 유용하다.&lt;br /&gt;특히, &lt;b&gt;--recursive옵션&lt;/b&gt;을 사용하면, 세부 필드들까지 형식 맞춰서 한눈에 볼 수 있기 때문에, 참고 자료가 없는(VPA 등) 리소스 정의서를 작성하기에 좋다. (참고로 시험에서도 리소스 정보 명세하라며 이 명령어의 출력 결과를 저장하라는 등 문제도 출제될 수 있다.)&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;b&gt;2. ps aux / ps -ef&lt;/b&gt;&lt;br /&gt;실행 중인 프로세스 정보 중 웬만큼 필요한 것들은 다 찾아지니까 일단 확인해 보면 여러모로 시간을 단축할 수 있다.&lt;br /&gt;예를 들면, 특정 컨트롤 플레인 컴포넌트 프로세스가 어떤 설정파일 쓰고 있는지 등.... 가닥을 잡기가 쉽고 빨라진다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;b&gt;3. k api-resource&lt;/b&gt;&lt;br /&gt;네임스페이스 스콥의 리소스인지, 단축 이름 뭔지 갑자기 헷갈릴 때 후다닥 확인하기 좋다.&lt;br /&gt;또 api version 같은 거 헷갈릴 때도 좋다.&amp;nbsp;&lt;br /&gt;물론 crd도 등록하면 이 명령어 실행 시 포함되어 확인할 수 있다. 그래서 시험에서도 crd 이름 등 간단히 확인할 때 사용했었다.&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;b&gt;4. --show-labels&lt;/b&gt;&lt;br /&gt;리소스들이 labels selector기준으로 묶이는 만큼 K8s에서 라벨 확인은 매우 중요하다.&lt;br /&gt;시험 문제를 예로 들면, Network Policy에서 PodSelector를 통해 적용되는 Pod/Namespace는 무엇일지 확인해야 될 때가 있다.&lt;br /&gt;그 외에도 Replicas, Deployment, Service 등의 기본 연결 메커니즘이 라벨이고, 이게 다르면 트래픽 전달도 스케일링도 무용지물이 되므로 labels를 확인 가능한 해당 명령어가 매우 유용했다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Killer.sh&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Killer.sh도 2025.02 변경사항이 적용됐다. 시험 날짜 결정 후, 2회 풀 수 있고 문제 set은 다르다.&lt;br /&gt;풀어보니 공부 목적보다는 &lt;b&gt;시험 환경 적응&lt;/b&gt;에 중점을 둬도 괜찮을 것 같다. 그런데 안 풀어보는 건 반대한다. 적응하는 시간이 꽤 걸렸다.&amp;nbsp;&lt;br /&gt;문제마다 ssh로 노드 바꿔서 작업하는 것, 터미널 등 여는 방법(메모장은 그냥 쓰지 않고 vim으로 충분했다), 복붙 적응에 도움 됐다.&lt;br /&gt;난이도&amp;nbsp;극악이라는&amp;nbsp;기존&amp;nbsp;버전&amp;nbsp;후기와&amp;nbsp;달리,&amp;nbsp;문제가&amp;nbsp;바뀌어서&amp;nbsp;그런가?&amp;nbsp;별로&amp;nbsp;안&amp;nbsp;어려운데!?!?라고&amp;nbsp;생각하며&amp;nbsp;풀었다&lt;br /&gt;하지만 웬 걸.. 40점대 나옴 충격... 그래도 시험이 더 쉽고, 나 또한 실전에서 2배 이상의 점수를 받았으니 너무 걱정하지 않아도 된다!!&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;시험 환경, 준비물&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;집&lt;/b&gt;에서 봤다. 원룸이라 티비, 각종 짐을 전부 부엌에 몰아넣고 부엌이 안 보이는 쪽에서 시험을 봤다.&amp;nbsp;&lt;br /&gt;행거에&amp;nbsp;걸린&amp;nbsp;옷들은&amp;nbsp;담요로&amp;nbsp;가려뒀는데,&amp;nbsp;뭐냐고&amp;nbsp;물으셔서&amp;nbsp;그대로&amp;nbsp;말씀드리고&amp;nbsp;넘어갔다.&amp;nbsp;&lt;br /&gt;티비&amp;nbsp;선을&amp;nbsp;뽑으라고&amp;nbsp;하기도&amp;nbsp;한다는데,&amp;nbsp;나는&amp;nbsp;꺼져있는&amp;nbsp;것만&amp;nbsp;보여드렸다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;신분증은&amp;nbsp;뒷면에&amp;nbsp;영문이&amp;nbsp;있는&amp;nbsp;&lt;b&gt;운전면허증&lt;/b&gt;을&amp;nbsp;준비했다.&amp;nbsp;&lt;br /&gt;한글&amp;nbsp;부분에만&amp;nbsp;얼굴이&amp;nbsp;있어서,&amp;nbsp;앞면&amp;nbsp;뒷면을&amp;nbsp;각각&amp;nbsp;보여주기를&amp;nbsp;요구받았다.&amp;nbsp;&lt;br /&gt;혹시&amp;nbsp;몰라&amp;nbsp;체크카드도&amp;nbsp;준비했는데,&amp;nbsp;필요하지는&amp;nbsp;않았다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개인적인 소감&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막 1-2주는 시험을 위한 공부로 흘러가긴 했지만, 스터디 기간 3개월을 포함하여 쿠버네티스를 공부한 5개월 간&lt;br /&gt;기본적인 쿠버네티스 운영 능력과 자신감을 얻어서 개인적으로 준비해 보길 잘했다고 생각이 드는 자격증이다.&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;마침 취득할 무렵에 회사에서도 쿠버네티스 관련 업무를 작게나마 다루게 되어서,&lt;br /&gt;역시 어떤 성취이든 일단 해 놓으면 분명 확장하고 활용하는 때가 오는구나 라는 생각을 다시금 하게 됐다.&lt;br /&gt;&lt;br /&gt;첫&amp;nbsp;단추를&amp;nbsp;꿰는데&amp;nbsp;함께해&amp;nbsp;준&amp;nbsp;스터디원들한테도&amp;nbsp;고맙고,&amp;nbsp;앞으로&amp;nbsp;이&amp;nbsp;기술로&amp;nbsp;시도해&amp;nbsp;볼&amp;nbsp;많은&amp;nbsp;여정이&amp;nbsp;기대가&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;1238&quot; data-origin-height=&quot;958&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YqJAg/btsPwGFk0IQ/k7hoWt0Nt8YoxYiohKaVO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YqJAg/btsPwGFk0IQ/k7hoWt0Nt8YoxYiohKaVO0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YqJAg/btsPwGFk0IQ/k7hoWt0Nt8YoxYiohKaVO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYqJAg%2FbtsPwGFk0IQ%2Fk7hoWt0Nt8YoxYiohKaVO0%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;1238&quot; height=&quot;958&quot; data-origin-width=&quot;1238&quot; data-origin-height=&quot;958&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>Kubernetes</category>
      <author>LOGPOSE 로그포스</author>
      <guid isPermaLink="true">https://crayeji.tistory.com/144</guid>
      <comments>https://crayeji.tistory.com/144#entry144comment</comments>
      <pubDate>Fri, 25 Jul 2025 20:00:36 +0900</pubDate>
    </item>
    <item>
      <title>Amazon S3 Vectors 출시, OpenAI embeddings 넣어보기</title>
      <link>https://crayeji.tistory.com/143</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;며칠&amp;nbsp;전&amp;nbsp;AWS에서&amp;nbsp;벡터&amp;nbsp;클라우드&amp;nbsp;스토리지를&amp;nbsp;공개했다.&amp;nbsp;S3&amp;nbsp;기반이기에&amp;nbsp;경제성과&amp;nbsp;내구성을&amp;nbsp;기대할&amp;nbsp;수&amp;nbsp;있을&amp;nbsp;것&amp;nbsp;같다.&lt;br&gt;나는&amp;nbsp;Qdrant로&amp;nbsp;벡터&amp;nbsp;DB를&amp;nbsp;구성하고&amp;nbsp;있었는데,&amp;nbsp;Amazon&amp;nbsp;S3&amp;nbsp;Vectors로&amp;nbsp;교체해볼까&amp;nbsp;한다.&amp;nbsp;&lt;br&gt;Embeddings는 우선 Bedrock 대신, OpenAI embedding model을 사용했다.&lt;/p&gt;&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;Introducing Amazon S3 Vectors: First cloud storage with native vector support at scale (preview) | Amazon Web Services&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;Amazon S3 Vectors is a new cloud object store that provides native support for storing and querying vectors at massive scale, offering up to 90% cost reduction compared to conventional approaches while seamlessly integrating with Amazon Bedrock Knowledge B&quot; data-og-host=&quot;aws.amazon.com&quot; data-og-source-url=&quot;https://aws.amazon.com/ko/blogs/aws/introducing-amazon-s3-vectors-first-cloud-storage-with-native-vector-support-at-scale/?fbclid=IwY2xjawLrs_ZleHRuA2FlbQIxMABicmlkETF2ZHdtWklXM09xZ3NOZkJ4AR5FCzSNym5IOaoipCiT5Lf5CodGUgQC51zMvqp9dxs_qg-x-Lnydsk4kZuCBg_aem_Xr8wTaiqhJpsqZSyU5VoXQ&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dXHWYC/hyZnp7pv4N/cK1LdTdthqjkpSdsyED8e1/img.png?width=800&amp;amp;height=400&amp;amp;face=0_0_800_400,https://scrap.kakaocdn.net/dn/bdlrP2/hyZnwSZ5Xq/K1RAe5zoXAMZ2SpwoflF10/img.png?width=800&amp;amp;height=400&amp;amp;face=0_0_800_400,https://scrap.kakaocdn.net/dn/cpdOcy/hyZnxxBbkQ/R3kVke6A557DwAJLiE4Pw0/img.png?width=2312&amp;amp;height=1234&amp;amp;face=0_0_2312_1234&quot; data-og-url=&quot;https://aws.amazon.com/blogs/aws/introducing-amazon-s3-vectors-first-cloud-storage-with-native-vector-support-at-scale/&quot;&gt;&lt;a href=&quot;https://aws.amazon.com/blogs/aws/introducing-amazon-s3-vectors-first-cloud-storage-with-native-vector-support-at-scale/&quot; target=&quot;_blank&quot; data-source-url=&quot;https://aws.amazon.com/ko/blogs/aws/introducing-amazon-s3-vectors-first-cloud-storage-with-native-vector-support-at-scale/?fbclid=IwY2xjawLrs_ZleHRuA2FlbQIxMABicmlkETF2ZHdtWklXM09xZ3NOZkJ4AR5FCzSNym5IOaoipCiT5Lf5CodGUgQC51zMvqp9dxs_qg-x-Lnydsk4kZuCBg_aem_Xr8wTaiqhJpsqZSyU5VoXQ&quot;&gt;&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dXHWYC/hyZnp7pv4N/cK1LdTdthqjkpSdsyED8e1/img.png?width=800&amp;amp;height=400&amp;amp;face=0_0_800_400,https://scrap.kakaocdn.net/dn/bdlrP2/hyZnwSZ5Xq/K1RAe5zoXAMZ2SpwoflF10/img.png?width=800&amp;amp;height=400&amp;amp;face=0_0_800_400,https://scrap.kakaocdn.net/dn/cpdOcy/hyZnxxBbkQ/R3kVke6A557DwAJLiE4Pw0/img.png?width=2312&amp;amp;height=1234&amp;amp;face=0_0_2312_1234')&quot;&gt; &lt;/div&gt;&lt;div class=&quot;og-text&quot;&gt;&lt;p class=&quot;og-title&quot;&gt;Introducing Amazon S3 Vectors: First cloud storage with native vector support at scale (preview) | Amazon Web Services&lt;/p&gt;&lt;p class=&quot;og-desc&quot;&gt;Amazon S3 Vectors is a new cloud object store that provides native support for storing and querying vectors at massive scale, offering up to 90% cost reduction compared to conventional approaches while seamlessly integrating with Amazon Bedrock Knowledge B&lt;/p&gt;&lt;p class=&quot;og-host&quot;&gt;aws.amazon.com&lt;/p&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot;&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;사용하기&lt;/b&gt;&lt;/h2&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. Bucket&amp;nbsp;생성&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Sydney &lt;u&gt;Region&lt;/u&gt;&lt;/b&gt;으로 테스트해본다. 현재 &lt;span style=&quot;color: #333333;&quot;&gt; &amp;nbsp;버지니아 북부(us-east-1), 오하이오(us-east-2), 오레곤(us-west-2), 프랑크푸르트(eu-central-1), 시드니(ap-southeast-2) 리전에서만 평가판으로 제공되고 있다.&amp;nbsp;&lt;/span&gt;&lt;br&gt;서울에서 &lt;span style=&quot;color: #333333;&quot;&gt;개인적으로&amp;nbsp;속도&amp;nbsp;&lt;/span&gt;테스트 해봤을때는, 오레곤 리전이 평균적으로 빠른 응답을 보였다.&amp;nbsp;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;u&gt;Encryption type&lt;/u&gt;&lt;/b&gt;은&amp;nbsp;선택하지&amp;nbsp;않으면&amp;nbsp;기본적으로 Amazon&amp;nbsp;S3&amp;nbsp;managed&amp;nbsp;keys&amp;nbsp;(SSE-S3) 기반의&amp;nbsp;server-side&amp;nbsp;encryption을&amp;nbsp;한다.&amp;nbsp;&lt;br&gt;이보다&amp;nbsp;advanced를&amp;nbsp;원하면&amp;nbsp;AWS&amp;nbsp;Key&amp;nbsp;Management&amp;nbsp;Service&amp;nbsp;키를&amp;nbsp;사용한&amp;nbsp;서버&amp;nbsp;측&amp;nbsp;암호화(&lt;b&gt;SSE-KMS&lt;/b&gt;)를&amp;nbsp;선택할&amp;nbsp;수도&amp;nbsp;있다.&amp;nbsp;&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;918&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ci3seg/btsPsZearIj/qvLqt3WSANdmoiptkaCsK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ci3seg/btsPsZearIj/qvLqt3WSANdmoiptkaCsK1/img.png&quot; data-alt=&quot;버킷 네이밍은 벡터 데이터 목적을 나타내도록 descriptive한 것이 권장된다. (product-recommendations / document-embeddings 등)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ci3seg/btsPsZearIj/qvLqt3WSANdmoiptkaCsK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fci3seg%2FbtsPsZearIj%2FqvLqt3WSANdmoiptkaCsK1%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;918&quot; data-origin-width=&quot;2616&quot; data-origin-height=&quot;918&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;버킷 네이밍은 벡터 데이터 목적을 나타내도록 descriptive한 것이 권장된다. (product-recommendations / document-embeddings 등)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. Index 생성&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;Name과 &lt;b&gt;&lt;u&gt;Dimensionality&lt;/u&gt;&lt;/b&gt;를 지정한다.&lt;br&gt;물론&amp;nbsp;입력되는&amp;nbsp;Vector값은&amp;nbsp;여기&amp;nbsp;입력되는&amp;nbsp;dimensionality와&amp;nbsp;동일해야&amp;nbsp;한다.&amp;nbsp;&lt;br&gt;&lt;b&gt;&lt;u&gt;Distance metric&lt;/u&gt;&lt;/b&gt;은 &lt;b&gt;Cosine&lt;/b&gt; / &lt;b&gt;Euclidean&lt;/b&gt;을 제공한다.&lt;br&gt;사용할 임베딩 모델의 recommended distance metric for more accurate results와 벡터 차원 수를 확인하여 선택하면 되겠다.&lt;br&gt;&lt;br&gt;나는&amp;nbsp;OpenAI&amp;nbsp;text-embedding-3-small을&amp;nbsp;사용하기&amp;nbsp;때문에&amp;nbsp;DIMENSIONS는&amp;nbsp;1536,&amp;nbsp;METRIC은&amp;nbsp;Cosine으로&amp;nbsp;지정해 주었다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3348&quot; data-origin-height=&quot;1836&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ekH7tg/btsPs2n3Bj1/HEjQGw2HWuwi5kNNHJH76K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ekH7tg/btsPs2n3Bj1/HEjQGw2HWuwi5kNNHJH76K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ekH7tg/btsPs2n3Bj1/HEjQGw2HWuwi5kNNHJH76K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FekH7tg%2FbtsPs2n3Bj1%2FHEjQGw2HWuwi5kNNHJH76K%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;3348&quot; height=&quot;1836&quot; data-origin-width=&quot;3348&quot; data-origin-height=&quot;1836&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메타데이터 구성도 보인다. 벡터 인덱스 만들고 나면 Vector data를 인덱스에 넣을 텐데, 그때 medata를 key-value 쌍 형태로 각 벡터에 붙일 수가 있다. 이는 Qdrant에서 payload의 metadata 기반으로 필터링하는 기능과 유사하게 보인다. (예를 들면 날짜/카테고리 등...)&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;span style=&quot;color: #0F141A;&quot;&gt;메타데이터는 Filterable과&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #0F141A;&quot;&gt;Non-filterable이 있는데, 위 단계에서 &lt;/span&gt;&lt;span style=&quot;color: #0F141A;&quot;&gt;Non-filterable 메타데이터 key를 추가할 수 있다. (최대 10개)&lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #0F141A;&quot;&gt;크기는 벡터 당 전체 메타데이터(&lt;/span&gt;&lt;span style=&quot;color: #0F141A;&quot;&gt;Filterable, &lt;/span&gt;&lt;span style=&quot;color: #0F141A;&quot;&gt;Non-filterable 합쳐서) 40KB까지 가능하고, &lt;/span&gt;&lt;span style=&quot;color: #0F141A;&quot;&gt;Filterable은 최대 2KB다.&lt;/span&gt;&lt;/p&gt;&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;Limitations and restrictions - Amazon Simple Storage Service&quot; data-ke-align=&quot;alignRight&quot; data-og-description=&quot;Limitations and restrictions Amazon S3 Vectors is in preview release for Amazon Simple Storage Service and is subject to change. Amazon S3 Vectors has certain limitations and restrictions that you should be aware of when planning your vector storage and se&quot; data-og-host=&quot;docs.aws.amazon.com&quot; data-og-source-url=&quot;https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-vectors-limitations.html&quot; data-og-image=&quot;&quot; data-og-url=&quot;https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-vectors-limitations.html&quot;&gt;&lt;a href=&quot;https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-vectors-limitations.html&quot; target=&quot;_blank&quot; data-source-url=&quot;https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-vectors-limitations.html&quot;&gt;&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('')&quot;&gt; &lt;/div&gt;&lt;div class=&quot;og-text&quot;&gt;&lt;p class=&quot;og-title&quot;&gt;Limitations and restrictions - Amazon Simple Storage Service&lt;/p&gt;&lt;p class=&quot;og-desc&quot;&gt;Limitations and restrictions Amazon S3 Vectors is in preview release for Amazon Simple Storage Service and is subject to change. Amazon S3 Vectors has certain limitations and restrictions that you should be aware of when planning your vector storage and se&lt;/p&gt;&lt;p class=&quot;og-host&quot;&gt;docs.aws.amazon.com&lt;/p&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;p data-ke-size=&quot;size14&quot; style=&quot;text-align: right;&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;[더 자세한 용량 / 개수 제한은 위 문서를 참고]&lt;/span&gt;&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. Vector embeddings 넣기, 쿼리&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;AWS CLI, AWS SDKs 또는 Amazon S3 REST API로 해당 작업이 가능한데,&lt;br&gt;AWS 블로그 SDK 예시 코드를 기반으로 했고, Embedding model만 Bedrock대신 openAI로 수정했다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;참고) 콘솔에서 UI로 확인할 수 있는 사항들이 제한적이다. 주로 AWS CLI를 통해 조회했고, 커맨드는 아래를 참고하자.&lt;/span&gt;&lt;/p&gt;&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;s3vectors — AWS CLI 2.27.58 Command Reference&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;Description Amazon S3 vector buckets are a bucket type to store and search vectors with sub-second search times. They are designed to provide dedicated API operations for you to interact with vectors to do similarity search. Within a vector bucket, you use&quot; data-og-host=&quot;docs.aws.amazon.com&quot; data-og-source-url=&quot;https://docs.aws.amazon.com/cli/latest/reference/s3vectors/#cli-aws-s3vectors&quot; data-og-image=&quot;&quot; data-og-url=&quot;https://docs.aws.amazon.com/cli/latest/reference/s3vectors/#cli-aws-s3vectors&quot;&gt;&lt;a href=&quot;https://docs.aws.amazon.com/cli/latest/reference/s3vectors/#cli-aws-s3vectors&quot; target=&quot;_blank&quot; data-source-url=&quot;https://docs.aws.amazon.com/cli/latest/reference/s3vectors/#cli-aws-s3vectors&quot;&gt;&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('')&quot;&gt; &lt;/div&gt;&lt;div class=&quot;og-text&quot;&gt;&lt;p class=&quot;og-title&quot;&gt;s3vectors — AWS CLI 2.27.58 Command Reference&lt;/p&gt;&lt;p class=&quot;og-desc&quot;&gt;Description Amazon S3 vector buckets are a bucket type to store and search vectors with sub-second search times. They are designed to provide dedicated API operations for you to interact with vectors to do similarity search. Within a vector bucket, you use&lt;/p&gt;&lt;p class=&quot;og-host&quot;&gt;docs.aws.amazon.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;br&gt;&lt;b&gt;Step 1. 임베딩 생성&lt;/b&gt;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;import openai
import boto3
import json
import os
from dotenv import load_dotenv

load_dotenv()

# OpenAI 클라이언트 생성
client = openai.OpenAI()

# 텍스트 입력
texts = [
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;Star Wars: A farm boy joins rebels to fight an evil empire in space&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;Jurassic Park: Scientists create dinosaurs in a theme park that goes wrong&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;Finding Nemo: A father fish searches the ocean to find his lost son&quot;
]

# 임베딩 모델 지정
model = &quot;text-embedding-3-small&quot;

# 텍스트 → 임베딩
embeddings = []
for text in texts:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;response = client.embeddings.create(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;model=model,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;input=text
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;embedding = response.data[0].embedding
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;embeddings.append(embedding)&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;Step 2. 임베딩 저장&lt;/b&gt;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;# S3Vectors 클라이언트 생성
s3vectors = boto3.client(&quot;s3vectors&quot;, region_name=&quot;ap-southeast-2&quot;)&amp;nbsp;&amp;nbsp;# S3 Vector Store 지원 리전 - Sydney

# 임베딩 저장
s3vectors.put_vectors(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;vectorBucketName=&quot;document-embeddings&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;indexName=&quot;document-embeddings-index&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;vectors=[
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;key&quot;: &quot;v1&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;data&quot;: {&quot;float32&quot;: embeddings[0]},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;metadata&quot;: {&quot;id&quot;: &quot;key1&quot;, &quot;source_text&quot;: texts[0], &quot;genre&quot;: &quot;scifi&quot;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;key&quot;: &quot;v2&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;data&quot;: {&quot;float32&quot;: embeddings[1]},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;metadata&quot;: {&quot;id&quot;: &quot;key2&quot;, &quot;source_text&quot;: texts[1], &quot;genre&quot;: &quot;scifi&quot;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;key&quot;: &quot;v3&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;data&quot;: {&quot;float32&quot;: embeddings[2]},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;metadata&quot;: {&quot;id&quot;: &quot;key3&quot;, &quot;source_text&quot;: texts[2], &quot;genre&quot;: &quot;family&quot;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;]
)&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;Step 3. 쿼리&lt;/b&gt;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;# 쿼리 텍스트를 임베딩
input_text = &quot;List the movies about adventures in space&quot;
query_response = client.embeddings.create(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;model=model,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;input=input_text
)
query_vector = query_response.data[0].embedding

# 벡터 유사도 검색
query = s3vectors.query_vectors(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;vectorBucketName=&quot;document-embeddings&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;indexName=&quot;document-embeddings-index&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;queryVector={&quot;float32&quot;: query_vector},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;topK=3,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;filter={&quot;genre&quot;: &quot;scifi&quot;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;returnDistance=True,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;returnMetadata=True
)

# 결과 출력
results = query[&quot;vectors&quot;]
print(json.dumps(results, indent=2))&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;결과는 아래와 같다. &amp;nbsp;filter={&quot;genre&quot;: &quot;scifi&quot;}에 의해 1차 '니모를 찾아서(&quot;genre&quot;: &quot;family&quot;)'는 필터링되었다.&amp;nbsp;&lt;br&gt;movies about adventures in space에 가장 가까운 Star Wars가 가장 가까운 distance로 검색됐다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;# 검색 결과
[
&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;key&quot;: &quot;v1&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;metadata&quot;: {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;source_text&quot;: &quot;Star Wars: A farm boy joins rebels to fight an evil empire in space&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;genre&quot;: &quot;scifi&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;id&quot;: &quot;key1&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;distance&quot;: 0.7688703536987305
&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;key&quot;: &quot;v2&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;metadata&quot;: {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;source_text&quot;: &quot;Jurassic Park: Scientists create dinosaurs in a theme park that goes wrong&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;genre&quot;: &quot;scifi&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;id&quot;: &quot;key2&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;distance&quot;: 0.8421887755393982
&amp;nbsp;&amp;nbsp;}
]&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot;&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;문서 Loading - Chunking -&amp;nbsp; S3 Vectors 저장 예시&lt;/b&gt;&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 Langchain으로 RAG를 구현해서 Qdrant 저장했었는데,&lt;br&gt;데이터 Ingestion 할 때만 Qdrant metatdata구성과 비슷한 구조로 S3 Vectors에 저장하도록 구현해 보았다.&lt;br&gt;물론 S3 Vectors는 지난주에 발표된 만큼, 아직 Langchain에서 Vectorstores로&amp;nbsp;제공하지는 않는다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;# 1. Loading
def load_file(file_url: str):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;response = requests.get(file_url)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;response.raise_for_status()

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;content_type = response.headers.get('Content-Type', '').split(';')[0]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&quot;[Header] Content-Type:&quot;, content_type)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# normalize to internal type: &quot;pptx&quot;, &quot;docx&quot;, &quot;pdf&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;normalized_type = get_normalized_type(content_type)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;with tempfile.NamedTemporaryFile(delete=False, suffix=f&quot;.{normalized_type}&quot;) as tmp:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;tmp.write(response.content)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;tmp_path = tmp.name
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&quot;[TempFile] 저장됨: {tmp_path}&quot;)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# 로더 매핑
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;loader_map = {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;pptx&quot;: UnstructuredPowerPointLoader,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;docx&quot;: UnstructuredWordDocumentLoader,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;pdf&quot;: PyPDFLoader
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;loader = loader_map[normalized_type](tmp_path)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return loader.load()


# 2. Chunking
def chunk_document(docs, chunk_size = 500, chunk_overlap = 100):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;splitter = RecursiveCharacterTextSplitter(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;chunk_size=chunk_size,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;chunk_overlap=chunk_overlap
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return splitter.split_documents(docs)


# 3. Embedding &amp;amp; Vector storing
def store_vectors(chunks):

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# Chunk text → Vector
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;client = openai.OpenAI()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;embeddings = []
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for chunk in chunks:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;response = client.embeddings.create(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;model=EMBEDDING_MODEL_NAME,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;input=chunk.page_content
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;embedding = response.data[0].embedding
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;embeddings.append(embedding)


&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# S3Vectors 벡터 저장
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;s3vectors = boto3.client(&quot;s3vectors&quot;, region_name=&quot;us-west-2&quot;)&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;vectors = []

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# chunks: List[Document]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# embeddings: List[List[float]]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for i, (chunk, embedding) in enumerate(zip(chunks, embeddings)):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;metadata = dict(chunk.metadata)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;metadata[&quot;source_text&quot;] = chunk.page_content&amp;nbsp;&amp;nbsp;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;vector = {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;key&quot;: f&quot;v{i+1}&quot;,&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;data&quot;: {&quot;float32&quot;: embedding},&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;metadata&quot;: metadata&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;vectors.append(vector)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# 벡터 일괄 업로드
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;s3vectors.put_vectors(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;vectorBucketName=VECTOR_BUCKET_NAME,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;indexName=INDEX_NAME,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;vectors=vectors
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&quot;\nIngestion 완료: {len(chunks)}개의 청크가 S3 Vectors에 저장되었습니다.&quot;)&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot;&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;size14&quot;&gt;Getting Started&lt;br&gt;&lt;a href=&quot;https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-vectors-getting-started.html&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-vectors-getting-started.html&lt;/span&gt;&lt;/a&gt;&lt;br&gt;&amp;nbsp;&lt;br&gt;Vector buckets 사용자 가이드&lt;br&gt;&lt;a href=&quot;https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-vectors-buckets.html&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-vectors-buckets.html&lt;/span&gt;&lt;/a&gt;&lt;br&gt;&amp;nbsp;&lt;br&gt;s3 vectors CLI 커맨드 레퍼런스&lt;br&gt;&lt;a href=&quot;https://docs.aws.amazon.com/cli/latest/reference/s3vectors/#cli-aws-s3vectors&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://docs.aws.amazon.com/cli/latest/reference/s3vectors/#cli-aws-s3vectors&lt;/span&gt;&lt;/a&gt;&lt;br&gt;&amp;nbsp;&lt;br&gt;OpenAI Vector Embeddings 가이드&lt;br&gt;&lt;a href=&quot;https://platform.openai.com/docs/guides/embeddings&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://platform.openai.com/docs/guides/embeddings&lt;/span&gt;&lt;/a&gt;&amp;nbsp;&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>AWS</category>
      <author>LOGPOSE 로그포스</author>
      <guid isPermaLink="true">https://crayeji.tistory.com/143</guid>
      <comments>https://crayeji.tistory.com/143#entry143comment</comments>
      <pubDate>Thu, 24 Jul 2025 12:00:55 +0900</pubDate>
    </item>
    <item>
      <title>PV 확장 원리, Kubernetes CSI Driver</title>
      <link>https://crayeji.tistory.com/142</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Kubernetes에서의 Volume, File System 확장&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS를 예로 들자. EC2에서 EBS 등 스토리지를 사용하는 상황에서 볼륨 용량을 늘리려면,&amp;nbsp;&lt;br /&gt;콘솔 등에서 EBS Volume 크기를 조정하고, EC2 인스턴스에서 파티션 및 File System 확장을 했었다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;그렇다면 &lt;b&gt;쿠버네티스에서는 어떤 방법&lt;/b&gt;으로 Pod에 연결된 PVC의 PV를 늘릴 수 있는지,&lt;br /&gt;Volume확장과 File System확장은 어떤 과정을 통해 일어나는지 살펴본다.&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Persistent Volume Expansion&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 PVC를 확장하려면 PVC 및 PV 삭제/재생성 같은 귀찮은 작업들이 필요했다.&lt;br /&gt;Kubernetes v1.11부터 정식 베타로 &lt;b&gt;Persistent Volume Expansion&lt;/b&gt;가 도입되었다.&lt;br /&gt;덕분에 allowVolumeExpansion필드를&amp;nbsp;True로&amp;nbsp;설정한&amp;nbsp;Strorageclass가&amp;nbsp;있을때,&lt;br /&gt;그 Storageclass를 사용하는 PVC들은 손쉽게 볼륨을&amp;nbsp;확장할 수 있다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;아래처럼 &lt;b&gt;PVC의 storage 필드(용량 부분)만 수정&lt;/b&gt;해주면 간단히 확장이 된다.&lt;br /&gt;다시 말해, &lt;b&gt;내부적으로 Volume확장과 File System 확장&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;555&quot; data-origin-height=&quot;249&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cxxQ9q/btsPcfGcwTy/seKkCbtH39Ocnd0zO5M1nk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxxQ9q/btsPcfGcwTy/seKkCbtH39Ocnd0zO5M1nk/img.png&quot; data-alt=&quot;사진 출처: https://kubernetes.io/blog/2018/07/12/resizing-persistent-volumes-using-kubernetes/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxxQ9q/btsPcfGcwTy/seKkCbtH39Ocnd0zO5M1nk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcxxQ9q%2FbtsPcfGcwTy%2FseKkCbtH39Ocnd0zO5M1nk%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;555&quot; height=&quot;249&quot; data-origin-width=&quot;555&quot; data-origin-height=&quot;249&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;사진 출처: https://kubernetes.io/blog/2018/07/12/resizing-persistent-volumes-using-kubernetes/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Container Storage Interface (CSI)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PVC만 살짝 수정하면 자동으로 Volume확장과 File System 확장이 일어난다니?&lt;br /&gt;&lt;b&gt;어떻게&lt;/b&gt; 그럴 수 있을까를 찾아봤다. 결론부터 말하자면 CSI 드라이버와 kubelet 덕분이다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;CSI는&amp;nbsp;쿠버네티스같은&amp;nbsp;Container&amp;nbsp;Orchestration&amp;nbsp;Systems&amp;nbsp;(COs)가&amp;nbsp;스토리지&amp;nbsp;시스템에&amp;nbsp;컨테이너를&amp;nbsp;노출시키는&amp;nbsp;표준&amp;nbsp;인터페이스다.&amp;nbsp;이&amp;nbsp;인터페이스는&amp;nbsp;Identity&amp;nbsp;Service,&amp;nbsp;Controller&amp;nbsp;Service,&amp;nbsp;Node&amp;nbsp;Service로&amp;nbsp;구성되어&amp;nbsp;있다.&lt;br /&gt;Controller plugin은 CSI의 Controller Service를 구현한 것이고, Node plugin은 CSI의 Node Service를 구현한 것이다. Identity Service는 두 Plugin에서 모두 구현해야 한다.&lt;br /&gt;쿠버네티스에서 일반적으로 CSI드라이버는 이 plugin을 포함하는 `&lt;b&gt;controller&amp;nbsp;component&lt;/b&gt;`, `&lt;b&gt;per-node&amp;nbsp;component&lt;/b&gt;`로&amp;nbsp;배포된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;참고) CSI Spec:&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://github.com/container-storage-interface/spec/blob/master/spec.md#rpc-interface&quot;&gt;&lt;span&gt;https://github.com/container-storage-interface/spec/blob/master/spec.md#rpc-interface&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1.&amp;nbsp;Controller&amp;nbsp;Component&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSI의 Controller Service를 구현한 '&lt;b&gt;Controller plugin&lt;/b&gt;' &amp;amp; 하나 이상의 '&lt;b&gt;Controller&lt;/b&gt; &lt;b&gt;Sidecar&lt;/b&gt;' 로 구성되는 컴포넌트이다.&amp;nbsp;&lt;br /&gt;여기서 Sidecar들의 목적은 쿠버네티스 오브젝트와 상호작용하면서 드라이버가 구현한 CSI Controller Service를 호출하는 것이다.&lt;br /&gt;가령, PVC의 Storage 용량을 늘리면 resizer Sidecar가 이를 감지해서 CSI Controller Service에 구현된 ControllerExpandVolume을 호출하는 것이다.&lt;br /&gt;&lt;br /&gt;Controller&amp;nbsp;Sidecar&amp;nbsp;종류는&amp;nbsp;아래와&amp;nbsp;같다.&amp;nbsp;&lt;br /&gt;물론 이 외에도 더 있는데, 어떤 Sidecar Conatiner를 포함할지는 Controller개발자가 필요에 따라 선택했을 것이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;provisioner: PVC 생성 요청 시 볼륨 생성&lt;/li&gt;
&lt;li&gt;attacher: 볼륨을 노드에 attach&amp;nbsp;&lt;/li&gt;
&lt;li&gt;snapshotter: 볼륨 스냅샷 기능 지원&lt;/li&gt;
&lt;li&gt;resizer: PVC 용량 증가 감지 및 확장 요청&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게&amp;nbsp;Controller&amp;nbsp;plugin과&amp;nbsp;Sidecar들로&amp;nbsp;구성된&amp;nbsp;&lt;b&gt;Controller&amp;nbsp;Component&lt;/b&gt;는&amp;nbsp;Deployment나&amp;nbsp;StaefulSet등&amp;nbsp;형태로&amp;nbsp;배포가&amp;nbsp;된다.&amp;nbsp;&lt;br /&gt;일반적으로 &lt;b&gt;CSI Controller&lt;/b&gt;와 같은 이름을 가지는 Pod에 해당한다.&lt;br /&gt;&lt;br /&gt;나는&amp;nbsp;Amazon&amp;nbsp;EBS를&amp;nbsp;사용하면서&amp;nbsp;ebs-csi-controller를&amp;nbsp;사용하고&amp;nbsp;있는데,&amp;nbsp;컨테이너&amp;nbsp;이름을&amp;nbsp;조회해&amp;nbsp;보니,&lt;br /&gt;앞서 학습한대로 controller plugin(ebs-plugin)과 Sidecar들을 포함하고 있었다.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt; kubectl get pod ebs-csi-controller-657c7f6895-ffhj7 -n kube-system -o jsonpath=&quot;{.spec.containers[*].name}&quot;
 
 # ebs-plugin csi-provisioner csi-attacher csi-snapshotter csi-resizer liveness-probe&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;br /&gt;자세한 구성은 아래 aws-ebs-csi-driver의 helm charts를 참고했다.&lt;br /&gt;&lt;a href=&quot;https://github.com/kubernetes-sigs/aws-ebs-csi-driver/tree/master/charts/aws-ebs-csi-driver&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/kubernetes-sigs/aws-ebs-csi-driver/tree/master/charts/aws-ebs-csi-driver&lt;/a&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2.&amp;nbsp;Per-Node&amp;nbsp;Component&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSI의 Node Service를 구현한 '&lt;b&gt;Node Plugin&lt;/b&gt;' &amp;amp; '&lt;b&gt;node-driver-registrar'&lt;/b&gt;(Sidecar)로 구성되는 컴포넌트이다.&lt;br /&gt;node-driver-registrar는 Node Plugin을 &lt;b&gt;Kubelet&lt;/b&gt;에 등록하기 위한 Sidecar container다. 이를 통해 CSI 드라이버와 gRPC 통신을 할 수 있게 된다. 가령, 볼륨이 확장됐을때 Kubelet이 Node Plugin에 구현된 NodeExpandVolume 요청을 함으로써 실제 파일 시스템 확장(resize2fs 등..)을 하는 등 작업이 가능해지는 것이다.&lt;br /&gt;이&amp;nbsp;컴포넌트는&amp;nbsp;kubelet과&amp;nbsp;상호작용하는&amp;nbsp;만큼,&amp;nbsp;워커&amp;nbsp;노드마다&amp;nbsp;설치되어야&amp;nbsp;할&amp;nbsp;CSI&amp;nbsp;컴포넌트다.&amp;nbsp;따라서&amp;nbsp;DaemonSet을&amp;nbsp;통한&amp;nbsp;배포가&amp;nbsp;적절하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;참고) node-driver-registrar &lt;a href=&quot;https://kubernetes-csi.github.io/docs/node-driver-registrar&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://kubernetes-csi.github.io/docs/node-driver-registrar&lt;/a&gt;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;PV Expansion 과정 확인&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 확장을 해보며 plugin 호출 등 과정을 살펴보았다. 시나리오는 아래와 같다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;1. 사용자가 PVC의 storage 요청량을 증가시킴 (예: 30Gi &amp;rarr; 40Gi)
&amp;nbsp;&amp;nbsp; &amp;darr;
2. external-resizer(csi-resizer)가 이를 감지
&amp;nbsp;&amp;nbsp; &amp;darr;
3. CSI Controller에게 ControllerExpandVolume RPC 호출
&amp;nbsp;&amp;nbsp; &amp;darr;
4. CSI Controller가 스토리지 백엔드 API 호출 (예: AWS EBS)
&amp;nbsp;&amp;nbsp; &amp;darr;
5. 디스크가 확장됨 &amp;rarr; PV 객체의 capacity.storage 필드가 업데이트됨
&amp;nbsp;&amp;nbsp; &amp;darr;
6. PVC에 FileSystemResizePending 조건이 추가됨
&amp;nbsp;&amp;nbsp; &amp;darr;
7. Kubelet이 해당 PVC를 사용하는 Pod을 통해 이를 감지
&amp;nbsp;&amp;nbsp; &amp;darr;
8. Kubelet이 CSI Node Plugin에게 NodeExpandVolume RPC 호출
&amp;nbsp;&amp;nbsp; &amp;darr;
9. CSI Node Plugin이 마운트 경로의 파일 시스템을 확장 (resize2fs 등 작업)
&amp;nbsp;&amp;nbsp; &amp;darr;
10. PVC의 Status부분이 정상화됨 &amp;rarr; 확장 완료&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;나는 EKS에서 EBS를 사용하는 statefulset Pod 3개를 배포해둔 상태이다.&amp;nbsp;&lt;br /&gt;gp3라는 Storageclass를 사용 중이며, 확장을 위해서는 Storageclass에 allowVolumeExpansion=true가 명시되어 있어야 한다.&lt;br /&gt;기존 pvc는 30Gi를 Claim하고 있는데, 40Gi로 올려보았다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;kubectl edit &amp;lt;pvc-name&amp;gt;
# storage 부분 수정&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;Edit 직후에는 status에 allocatedResources부분이 40Gi로 추가되었다.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# PVC Edit 후 status를 확인해보자
k get pvc &amp;lt;pvc-name&amp;gt; -oyaml&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2198&quot; data-origin-height=&quot;1298&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zhioO/btsPbkBTK3U/bOiskeJjtsBAW8T9LpiuDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zhioO/btsPbkBTK3U/bOiskeJjtsBAW8T9LpiuDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zhioO/btsPbkBTK3U/bOiskeJjtsBAW8T9LpiuDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzhioO%2FbtsPbkBTK3U%2FbOiskeJjtsBAW8T9LpiuDK%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;520&quot; height=&quot;307&quot; data-origin-width=&quot;2198&quot; data-origin-height=&quot;1298&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잠시 기다리면 conditions부분에 type이 FileSystemResizePending으로&amp;nbsp;바뀌었다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2298&quot; data-origin-height=&quot;1192&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k66u3/btsPaYMK2gK/lHDvVhCQaqEtmkZ2nyX9l1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k66u3/btsPaYMK2gK/lHDvVhCQaqEtmkZ2nyX9l1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k66u3/btsPaYMK2gK/lHDvVhCQaqEtmkZ2nyX9l1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk66u3%2FbtsPaYMK2gK%2FlHDvVhCQaqEtmkZ2nyX9l1%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;520&quot; height=&quot;270&quot; data-origin-width=&quot;2298&quot; data-origin-height=&quot;1192&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;즉, PV Resizing은 끝난 것이다. 실제로 매칭된 PV를 describe해보면 아래와 같이 확장이 되어있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2506&quot; data-origin-height=&quot;958&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BzKZM/btsPa2VPtDE/zfZs0CIFRc56jGkFHXcRtK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BzKZM/btsPa2VPtDE/zfZs0CIFRc56jGkFHXcRtK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BzKZM/btsPa2VPtDE/zfZs0CIFRc56jGkFHXcRtK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBzKZM%2FbtsPa2VPtDE%2FzfZs0CIFRc56jGkFHXcRtK%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;2506&quot; height=&quot;958&quot; data-origin-width=&quot;2506&quot; data-origin-height=&quot;958&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;conditions이 FileSystemResizePending이라는 것은&lt;br /&gt;다시 말해, kubelet이 CSI Node Plugin에 NodeExpandVolume요청을 통해 FileSystemResizing을 진행할 차례라는 것이다.&lt;br /&gt;여기서 조금 더 기다려보면 최종적으로 pvc의 capacity가 40Gi로 변경되었고, FileSystemResizePending도 사라졌다.&lt;br /&gt;kubelet이 제 역할을 했음을 유추할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2494&quot; data-origin-height=&quot;1498&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IvWUC/btsPcyTc400/27fM6UUiAI25K3kGxtxUIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IvWUC/btsPcyTc400/27fM6UUiAI25K3kGxtxUIk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IvWUC/btsPcyTc400/27fM6UUiAI25K3kGxtxUIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIvWUC%2FbtsPcyTc400%2F27fM6UUiAI25K3kGxtxUIk%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;2494&quot; height=&quot;1498&quot; data-origin-width=&quot;2494&quot; data-origin-height=&quot;1498&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;kubelet의 Node Plugin요청 여부를 명확히 확인하려면&amp;nbsp;&lt;br /&gt;해당 Pod를 describe해서 Events를 보면 된다.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;Events:
&amp;nbsp;&amp;nbsp;Type&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Reason&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Age&amp;nbsp;&amp;nbsp; From&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Message
&amp;nbsp;&amp;nbsp;----&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;------&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;----&amp;nbsp;&amp;nbsp;----&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; -------
&amp;nbsp;&amp;nbsp;Normal&amp;nbsp;&amp;nbsp;FileSystemResizeSuccessful&amp;nbsp;&amp;nbsp;13m&amp;nbsp;&amp;nbsp; kubelet&amp;nbsp;&amp;nbsp;MountVolume.NodeExpandVolume succeeded for volume &quot;pvc-98d52b3c-ce38-49ee-baa4-a36bf4f60b72&quot; i-0f2a5c9c120e30cbc&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FileSystemResizing이 성공했음을 알 수 있다. 또&amp;nbsp;그&amp;nbsp;주체는&amp;nbsp;From&amp;nbsp;부분의&amp;nbsp;Kubelet임을&amp;nbsp;확인했다.&amp;nbsp;&lt;br /&gt;MountVolume.NodeExpandVolume succeeded라는 메시지를 보니, NodeExpandVolume을 요청 했음도 알 수 있다.&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 Pod의 Volume Mount정보를 명확하게 확인해보자.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;kubectl exec -it &amp;lt;your-pod-name&amp;gt; -- df -h&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;2354&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bamTWb/btsPbgzHIEr/W7p0BmmlC9l9CkqLiFu080/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bamTWb/btsPbgzHIEr/W7p0BmmlC9l9CkqLiFu080/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bamTWb/btsPbgzHIEr/W7p0BmmlC9l9CkqLiFu080/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbamTWb%2FbtsPbgzHIEr%2FW7p0BmmlC9l9CkqLiFu080%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;2354&quot; height=&quot;768&quot; data-origin-width=&quot;2354&quot; data-origin-height=&quot;768&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 pod의 pv 마운트 위치는 /qdrant/storage이었다.&lt;br /&gt;해당 Mounted의 Available 부분을 보면 40G로 잘 변경되어있다.&lt;br /&gt;기존에는 해당 부분이 약 30G로 표시되어있었다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&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;&lt;a href=&quot;https://github.com/kubernetes-sigs/aws-ebs-csi-driver&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/kubernetes-sigs/aws-ebs-csi-driver&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://kubernetes.io/docs/concepts/storage/persistent-volumes/&quot;&gt;https://kubernetes.io/docs/concepts/storage/persistent-volumes/&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://kubernetes.io/blog/2018/07/12/resizing-persistent-volumes-using-kubernetes/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://kubernetes.io/blog/2018/07/12/resizing-persistent-volumes-using-kubernetes/&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://kubernetes-csi.github.io/docs/volume-expansion.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://kubernetes-csi.github.io/docs/volume-expansion.html&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;k8s csi-resizer(external-resizer)&lt;br /&gt;&lt;a href=&quot;https://github.com/kubernetes-csi/external-resizer/blob/master/cmd/csi-resizer/main.go&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://github.com/kubernetes-csi/external-resizer/blob/master/cmd/csi-resizer/main.go&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;aws-ebs-csi-driver의 헬름 차트&lt;br /&gt;&lt;a href=&quot;https://github.com/kubernetes-sigs/aws-ebs-csi-driver/tree/master/charts/aws-ebs-csi-driver&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://github.com/kubernetes-sigs/aws-ebs-csi-driver/tree/master/charts/aws-ebs-csi-driver&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;K8s에서의 CSI Driver 배포&lt;br /&gt;&lt;a href=&quot;https://kubernetes-csi.github.io/docs/deploying.html)&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://kubernetes-csi.github.io/docs/deploying.html&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Kubernetes</category>
      <author>LOGPOSE 로그포스</author>
      <guid isPermaLink="true">https://crayeji.tistory.com/142</guid>
      <comments>https://crayeji.tistory.com/142#entry142comment</comments>
      <pubDate>Wed, 9 Jul 2025 19:00:51 +0900</pubDate>
    </item>
    <item>
      <title>Dockerfile(ENTRYPOINT, CMD) =&amp;gt; YAML Definition file(command, args) 덮어쓰기 과정</title>
      <link>https://crayeji.tistory.com/141</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1.&amp;nbsp; Dockerfile의 ENTRYPOINT는 YAML Definition file의 command에 의해 덮어쓰기된다.&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 처럼 도커파일이 있고, 이 도커파일로 만들어진 이미지가 webapp-color에 올라가있는데&lt;br /&gt;그 이미지를 Pod Definition file에서 활용해서 pod을 만든다면,&lt;br /&gt;기존 도커 파일의 `ENTRYPOINT`는 Pod Definition file의 `command`에 의해 덮어쓰기 된다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Dockerfile에 설정한 ENTRYPOINT는 YAML 정의서에 command가 명시되면 무시되어 YAML의 command가 실행 대상이 된다.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1751268709263&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# DockerFile
controlplane ~/webapp-color-2 ➜  cat Dockerfile 
FROM python:3.6-alpine
RUN pip install flask
COPY . /opt/
EXPOSE 8080
WORKDIR /opt
ENTRYPOINT [&quot;python&quot;, &quot;app.py&quot;]
CMD [&quot;--color&quot;, &quot;red&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1751268729579&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Pod Definition file
controlplane ~/webapp-color-2 ➜  cat webapp-color-pod.yaml 
apiVersion: v1 
kind: Pod 
metadata:
  name: webapp-green
  labels:
      name: webapp-green 
spec:
  containers:
  - name: simple-webapp
    image: kodekloud/webapp-color
    command: [&quot;--color&quot;,&quot;green&quot;]&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;위의 경우, ENTRYPOINT인 `&lt;b&gt;python&amp;nbsp;app.py&lt;/b&gt;`가 pod Definition file의 command에 의해 덮어쓰기된다&lt;b&gt;.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ENTRYPOINT가 덮어쓰기되면 CMD도 무시된다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;최종적으로 pod definition file에서의 `--color green`만 남는 것이 결과가 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. Dockerfile과 YAML definition file의 command/args 대응 관계&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;YAML Definition file(Pod, Deployment...)에서&lt;br /&gt;`&lt;b&gt;command&lt;/b&gt;`가&amp;nbsp;실행&amp;nbsp;대상&amp;nbsp;(=&amp;nbsp;Docker&amp;nbsp;`&lt;b&gt;ENTRYPOINT&lt;/b&gt;`)&amp;nbsp;&lt;br /&gt;`&lt;b&gt;args&lt;/b&gt;`는&amp;nbsp;그에&amp;nbsp;대한&amp;nbsp;인자&amp;nbsp;(=&amp;nbsp;Docker&amp;nbsp;`&lt;b&gt;CMD&lt;/b&gt;`)&lt;/p&gt;
&lt;pre id=&quot;code_1751268855603&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# DOCKERFILE
FROM python:3.6-alpine
RUN pip install flask
COPY . /opt/
EXPOSE 8080
WORKDIR /opt
ENTRYPOINT [&quot;python&quot;, &quot;app.py&quot;]     ✅
CMD [&quot;--color&quot;, &quot;red&quot;]              ✅&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1751268869447&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Pod Definition file
controlplane ~/webapp-color-3 ➜  cat webapp-color-pod-2.yaml 
apiVersion: v1 
kind: Pod 
metadata:
  name: webapp-green
  labels:
      name: webapp-green 
spec:
  containers:
  - name: simple-webapp
    image: kodekloud/webapp-color
    command: [&quot;python&quot;, &quot;app.py&quot;]     ✅
    args: [&quot;--color&quot;, &quot;pink&quot;]         ✅&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;ENTRYPONIT는 command로 덮어쓰고&lt;br /&gt;CMD는&amp;nbsp;agrs로&amp;nbsp;덮어쓰니&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;결과는 `python app.py --color pink`&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. ENTRYPOINT는 유지하고 CMD만 바꾸고 싶을 때&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dockerfile이 아래와 같을 때&lt;/p&gt;
&lt;pre id=&quot;code_1751268969360&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Dockerfile
ENTRYPOINT [&quot;node&quot;, &quot;index.js&quot;]
CMD [&quot;--env=prod&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;Kubernetes에서 CMD만 바꾸고 싶다면&lt;/p&gt;
&lt;pre id=&quot;code_1751268980237&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;containers:
- image: my-app
  args: [&quot;--env=dev&quot;]   # ENTRYPOINT는 유지됨&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;결과는 `node index.js --env=dev`&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;참고 자료&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://learn.kodekloud.com/user/courses/udemy-labs-certified-kubernetes-administrator-with-practice-tests&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://learn.kodekloud.com/user/courses/udemy-labs-certified-kubernetes-administrator-with-practice-tests&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Kubernetes</category>
      <author>LOGPOSE 로그포스</author>
      <guid isPermaLink="true">https://crayeji.tistory.com/141</guid>
      <comments>https://crayeji.tistory.com/141#entry141comment</comments>
      <pubDate>Mon, 30 Jun 2025 19:00:55 +0900</pubDate>
    </item>
    <item>
      <title>Qdrant 구성 요소</title>
      <link>https://crayeji.tistory.com/140</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Qdrant는 벡터 유사도 검색 엔진 &amp;amp; VectorDB의 역할을 한다.&amp;nbsp;&lt;br /&gt;REST&amp;nbsp;API를&amp;nbsp;통해&amp;nbsp;벡터를&amp;nbsp;저장,&amp;nbsp;검색&amp;nbsp;할&amp;nbsp;수&amp;nbsp;있으며&amp;nbsp;페이로드를&amp;nbsp;통해&amp;nbsp;벡터를&amp;nbsp;관리할&amp;nbsp;수&amp;nbsp;있다.&lt;br /&gt;벡터&amp;nbsp;유사도&amp;nbsp;검사에서&amp;nbsp;Similarity&amp;nbsp;Metric으로&amp;nbsp;Cosine,&amp;nbsp;Dot,&amp;nbsp;Euclid&amp;nbsp;등&amp;nbsp;선택할&amp;nbsp;수&amp;nbsp;있으며,&lt;br /&gt;Collections, Points 등 몇 가지 구성요소를 기반으로 동작한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Qdrant 구성 요소&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;&lt;b&gt;1.&amp;nbsp;Points&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Qdrant 기본 Entity다. Vector + Payload + Optional ID로 구성된다.&lt;/li&gt;
&lt;li&gt;Vector: 이미지/문서/사운드/비디오 등을 벡터화 한 값&lt;/li&gt;
&lt;li&gt;Payload: 벡터에 관한 부가데이터를 JSON형태로 저장한 값.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;ID: 벡터의 식별자&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;point는&amp;nbsp;이런&amp;nbsp;형태로&amp;nbsp;넣게&amp;nbsp;된다.&lt;br /&gt;ID는&amp;nbsp;&amp;nbsp;64-bit unsigned integers 또는 UUID 로 넣을 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1750643660324&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;PUT /collections/{collection_name}/points
{
    &quot;points&quot;: [
        {
            &quot;id&quot;: &quot;5c56c793-69f3-4fbf-87e6-c4bf54c28c26&quot;,
            &quot;payload&quot;: {&quot;color&quot;: &quot;red&quot;},
            &quot;vector&quot;: [0.9, 0.1, 0.1]
        }
    ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;Langchain으로 저장한 point를 조회해보면 payload로 page_content, metadata 등이 함께 들어갔다.&lt;br /&gt;JSON을 사용하여 표현할 수 있는 어떤 정보든 넣을 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1750643695660&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;id&quot;: &quot;08af50f3-6606-4073-953f-57ae668e7b30&quot;,
  &quot;vector&quot;: [
    0.009180975, -0.0057382825, ..., 0.003189075
  ],
  &quot;payload&quot;: {
    &quot;page_content&quot;: &quot;4 \n \nQ3.  다른 국가들에 적용되는 상호관세율은? \nA3. 중국 34%, 유럽연합 20%, 일본 24% 등으로 책정되어 ...&quot;,
    &quot;metadata&quot;: {
      &quot;producer&quot;: &quot;PDFium&quot;,
      &quot;creator&quot;: &quot;PDFium&quot;,
      &quot;creationdate&quot;: &quot;&quot;,
      &quot;source&quot;: &quot;/var/folders/zz/tn93_s4p00gn/T/tmp__eaypdf&quot;,
      &quot;total_pages&quot;: 11,
      &quot;page&quot;: 3,
      &quot;page_label&quot;: &quot;4&quot;,
      ...
    }
  }
}&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 덧붙인 Payload는 아래와 같이 Search시 Filtering에 활용할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1750645676012&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;POST /collections/{collection_name}/points/query
{
    &quot;query&quot;: [0.2, 0.1, 0.9, 0.79],
    &quot;filter&quot;: {
        &quot;must&quot;: [
            {
                &quot;key&quot;: &quot;city&quot;,
                &quot;match&quot;: {
                    &quot;value&quot;: &quot;London&quot;
                }
            }
        ]
    },
    &quot;params&quot;: {
        &quot;hnsw_ef&quot;: 128,
        &quot;exact&quot;: false
    },
    &quot;limit&quot;: 3
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;&lt;b&gt;2.&amp;nbsp;Distance&amp;nbsp;Metrics&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벡터 간의 유사성을 측정하는 데 사용한다. 아래에 설명할 Collections 생성할때 함께 지정해야한다.&amp;nbsp;&lt;br /&gt;어떤&amp;nbsp;Metrics를&amp;nbsp;사용할지는&amp;nbsp;어떤&amp;nbsp;임베딩&amp;nbsp;모델을&amp;nbsp;썼느냐에&amp;nbsp;따라&amp;nbsp;달라진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3.&amp;nbsp;Collections&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Point의 집합을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Collection을 만들때는 vectorParams으로 Vector Dimension과 Distance Metrics을 넣어준다.&lt;br /&gt;즉,&amp;nbsp;한&amp;nbsp;Collection&amp;nbsp;내&amp;nbsp;Point들은&amp;nbsp;모두&amp;nbsp;동일한 Vector Dimension(= 벡터 차원 수 = 임베딩 크기)를 가지고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단일  Metric으로 비교되는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;예를&amp;nbsp;들면&amp;nbsp;1번&amp;nbsp;point의&amp;nbsp;예시에서&amp;nbsp;vector부분&amp;nbsp;배열&amp;nbsp;길이가&amp;nbsp;Collection의&amp;nbsp;벡터&amp;nbsp;차원&amp;nbsp;수와&amp;nbsp;동일해야만&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 Collection에 해당 Point를 넣을 수 있으며, Collection 내 Point들은 동일한 Distance Metrics에 의해 비교된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(다만&amp;nbsp;Named&amp;nbsp;Vector는&amp;nbsp;단일&amp;nbsp;점에&amp;nbsp;여러&amp;nbsp;Vector를&amp;nbsp;가지고&amp;nbsp;고유의&amp;nbsp;차원&amp;nbsp;수와&amp;nbsp;메트릭을&amp;nbsp;쓸&amp;nbsp;수&amp;nbsp;있다고&amp;nbsp;한다)&lt;br /&gt;&lt;br /&gt;아래는 Collection 생성 예시이다.&lt;/p&gt;
&lt;pre id=&quot;code_1750643933398&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;qdrant.create_collection(
    collection_name=collection_name,
    vectors_config=VectorParams(size=1536, distance=Distance.COSINE),
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;물론&amp;nbsp;임베딩한&amp;nbsp;벡터&amp;nbsp;차원&amp;nbsp;수가&amp;nbsp;안&amp;nbsp;맞으면&amp;nbsp;DB에&amp;nbsp;넣을때&amp;nbsp;당연히&amp;nbsp;에러가&amp;nbsp;나는데,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;임베딩&amp;nbsp;시&amp;nbsp;벡터&amp;nbsp;차원&amp;nbsp;수는&amp;nbsp;임베딩&amp;nbsp;모델마다&amp;nbsp;다르다.&lt;br /&gt;가령,&amp;nbsp;text-embedding-3-small(차원&amp;nbsp;수&amp;nbsp;1536)을&amp;nbsp;쓴다면&amp;nbsp;Qdrant&amp;nbsp;VectorParams의&amp;nbsp;size도&amp;nbsp;위&amp;nbsp;예시처럼&amp;nbsp;1536으로&amp;nbsp;맞춰줘야한다.&lt;br /&gt;&lt;br /&gt;/collection으로 Collection 목록을 조회해볼 수 있으며,&amp;nbsp;&lt;br /&gt;특정 collection에 값을 저장한 후 collection_name으로 조회해보면 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1750644358710&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;result&quot;: {
    &quot;status&quot;: &quot;green&quot;,  // 컬렉션 상태 정상
    &quot;points_count&quot;: 35,  // 현재 저장된 벡터 개수
    &quot;config&quot;: {
      &quot;params&quot;: {
        &quot;vectors&quot;: {
          &quot;size&quot;: 1536,         // 벡터 차원 수 (예: OpenAI 임베딩용)
          &quot;distance&quot;: &quot;Cosine&quot;  // 유사도 계산 방식 (코사인 거리)
        },
        &quot;on_disk_payload&quot;: true  // payload 디스크 저장 여부
      },
      &quot;hnsw_config&quot;: {
        &quot;m&quot;: 16,
        &quot;ef_construct&quot;: 100,
        &quot;on_disk&quot;: false  // 인덱스는 메모리에 존재
      },
      &quot;optimizer_config&quot;: {
        &quot;flush_interval_sec&quot;: 5,  // 디스크로 flush되는 주기 (5초)
        &quot;indexing_threshold&quot;: 20000  // 인덱싱 시작 기준 
      }
    }
  },
  &quot;status&quot;: &quot;ok&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;&lt;b&gt;4.&amp;nbsp;Storage&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벡터를 저장할 스토리지는 2종류가 제공된다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Memory 저장 (RAM 저장하니까 액세스 속도가 더 빠른 옵션이다.)&lt;/li&gt;
&lt;li&gt;Memmap 저장 (파일로 만들어서 디스크에 저장)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Collection 생성시에 아래와 같이 지정할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1750644687872&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;client.recreate_collection(
    collection_name=&quot;my_collection&quot;,
    vectors_config=VectorParams(
        size=1536,
        distance=Distance.COSINE
    ),
    on_disk_payload=True,  # payload를 디스크에 저장
    hnsw_config={
        &quot;on_disk&quot;: False       # 인덱스는 메모리에 유지
    }
)&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;번외) Install&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라우드와의&amp;nbsp;없이&amp;nbsp;자체&amp;nbsp;인프라에서&amp;nbsp;실행하고자&amp;nbsp;하는&amp;nbsp;경우&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Qdrant&amp;nbsp;Private&amp;nbsp;Cloud&amp;nbsp;Enterprise&amp;nbsp;Operator를&amp;nbsp;이용하여&amp;nbsp;Kubernetes&amp;nbsp;클러스터에&amp;nbsp;설치할&amp;nbsp;것을&amp;nbsp;권장&lt;br /&gt;&lt;br /&gt;테스트&amp;nbsp;또는&amp;nbsp;개발&amp;nbsp;환경에서는&amp;nbsp;Qdrant를&amp;nbsp;컨테이너로&amp;nbsp;실행하거나,&amp;nbsp;바이너리&amp;nbsp;실행&amp;nbsp;파일로&amp;nbsp;직접&amp;nbsp;구동할&amp;nbsp;수&amp;nbsp;있음.&amp;nbsp;&amp;nbsp;&lt;br /&gt;또한,&amp;nbsp;Kubernetes에&amp;nbsp;손쉽게&amp;nbsp;설치할&amp;nbsp;수&amp;nbsp;있도록&amp;nbsp;Helm&amp;nbsp;차트를&amp;nbsp;함께&amp;nbsp;제공함.&lt;/p&gt;
&lt;p style=&quot;color: #333333; 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;&lt;b&gt;Installation Guide&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://qdrant.tech/documentation/guides/installation/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://qdrant.tech/documentation/guides/installation/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Helm&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://artifacthub.io/packages/helm/qdrant/qdrant](https://artifacthub.io/packages/helm/qdrant/qdrant)&quot;&gt;https://artifacthub.io/packages/helm/qdrant/qdrant](https://artifacthub.io/packages/helm/qdrant/qdrant)&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;K8s&amp;nbsp;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Helm Install&lt;/p&gt;
&lt;pre id=&quot;code_1750644870224&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;k create -n qdrant

helm repo add qdrant https://qdrant.github.io/qdrant-helm
helm repo update
helm upgrade -i qdrant -n qdrant qdrant/qdrant #install

k get all -n qdrant&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;NodePort&amp;nbsp;svc로&amp;nbsp;변경&amp;nbsp;(테스트&amp;nbsp;용도)&lt;/p&gt;
&lt;pre id=&quot;code_1750644953677&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;helm upgrade -i qdrant qdrant/qdrant \
  --set service.type=NodePort \
  --namespace qdrant&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&lt;b&gt;Docker&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1750644979203&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker run -p 6333:6333 -d qdrant/qdrant&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/h4&gt;</description>
      <category>LLM</category>
      <author>LOGPOSE 로그포스</author>
      <guid isPermaLink="true">https://crayeji.tistory.com/140</guid>
      <comments>https://crayeji.tistory.com/140#entry140comment</comments>
      <pubDate>Mon, 23 Jun 2025 11:33:13 +0900</pubDate>
    </item>
  </channel>
</rss>