<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>jjaehyeok 님의 블로그</title>
    <link>https://jjaehyeok.tistory.com/</link>
    <description>jjaehyeok 님의 블로그 입니다.</description>
    <language>ko</language>
    <pubDate>Tue, 2 Jun 2026 18:31:07 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>jjaehyeok</managingEditor>
    <image>
      <title>jjaehyeok 님의 블로그</title>
      <url>https://tistory1.daumcdn.net/tistory/8617510/attach/0017b1a8c90d4a868609a43a92b07522</url>
      <link>https://jjaehyeok.tistory.com</link>
    </image>
    <item>
      <title>프로그래머스 Lv.3 | Python | 순위</title>
      <link>https://jjaehyeok.tistory.com/75</link>
      <description>&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;1. 문제 정보&lt;/h2&gt;
&lt;figure id=&quot;og_1779070626026&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;프로그래머스&quot; data-og-description=&quot;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/49191&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dUgIn1/dJMb81fX1ZD/2JBChhpHjKsL4zGDgkvkvK/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/VMFGm/dJMb85vUo5O/L38PX5Jh2lEXps0GbkhlVk/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/49191&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/49191&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dUgIn1/dJMb81fX1ZD/2JBChhpHjKsL4zGDgkvkvK/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/VMFGm/dJMb85vUo5O/L38PX5Jh2lEXps0GbkhlVk/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960');&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;프로그래머스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;난이도: Lv.2&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;2. 문제 설명&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;n명의 권투선수가 권투 대회에 참여했고 각각 1번부터 n번까지 번호를 받았습니다. 권투 경기는 1대1 방식으로 진행이 되고, 만약 A 선수가 B 선수보다 실력이 좋다면 A 선수는 B 선수를 항상 이깁니다. 심판은 주어진 경기 결과를 가지고 선수들의 순위를 매기려 합니다. 하지만 몇몇 경기 결과를 분실하여 정확하게 순위를 매길 수 없습니다.&lt;br /&gt;&lt;br /&gt;선수의 수 n, 경기 결과를 담은 2차원 배열 results가 매개변수로 주어질 때 정확하게 순위를 매길 수 있는 선수의 수를 return 하도록 solution 함수를 작성해주세요.&lt;/blockquote&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;267&quot; data-end=&quot;328&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;제한사항&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;선수의 수는 1명 이상 100명 이하입니다.&lt;/li&gt;
&lt;li&gt;경기 결과는 1개 이상 4,500개 이하입니다.&lt;/li&gt;
&lt;li&gt;results 배열 각 행 [A, B]는 A 선수가 B 선수를 이겼다는 의미입니다.&lt;/li&gt;
&lt;li&gt;모든 경기 결과에는 모순이 없습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;561&quot; data-origin-height=&quot;155&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0MxCW/dJMcabRJaFi/Yx7019S65BYiowJZijw5pK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0MxCW/dJMcabRJaFi/Yx7019S65BYiowJZijw5pK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0MxCW/dJMcabRJaFi/Yx7019S65BYiowJZijw5pK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0MxCW%2FdJMcabRJaFi%2FYx7019S65BYiowJZijw5pK%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;460&quot; height=&quot;127&quot; data-origin-width=&quot;561&quot; data-origin-height=&quot;155&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;입출력 예 설명&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;2번 선수는 [1, 3, 4] 선수에게 패배했고 5번 선수에게 승리했기 때문에 4위입니다.&lt;/li&gt;
&lt;li&gt;5번 선수는 4위인 2번 선수에게 패배했기 때문에 5위입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;3. 접근 기준&lt;/h3&gt;
&lt;p data-end=&quot;474&quot; data-start=&quot;427&quot; data-ke-size=&quot;size16&quot;&gt;이 문제는 직접 경기한 결과만 보는 문제가 아니다. 간접 관계까지 확인해야 한다.&lt;/p&gt;
&lt;p data-end=&quot;482&quot; data-start=&quot;476&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1이 2를 이김
2가 3을 이김&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;518&quot; data-start=&quot;515&quot; data-ke-size=&quot;size16&quot;&gt;이면:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1은 3도 이길 수 있음&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;559&quot; data-start=&quot;547&quot; data-ke-size=&quot;size16&quot;&gt;으로 판단할 수 있다. 따라서 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;각 선수마다 다음 두 가지를 확인&lt;/b&gt;&lt;/span&gt;해야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;630&quot; data-start=&quot;591&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;610&quot; data-start=&quot;591&quot;&gt;이 선수가 이길 수 있는 선수들&lt;/li&gt;
&lt;li data-end=&quot;630&quot; data-start=&quot;611&quot;&gt;이 선수를 이길 수 있는 선수들&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;651&quot; data-start=&quot;632&quot; data-ke-size=&quot;size16&quot;&gt;이를 위해&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; 그래프를 두 개 만든다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;wins  &amp;rarr; 내가 이긴 선수 방향
loses &amp;rarr; 나를 이긴 선수 방향&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;731&quot; data-start=&quot;706&quot; data-ke-size=&quot;size16&quot;&gt;그리고 각 선수마다 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;BFS를 두 번 수행&lt;/b&gt;&lt;/span&gt;한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size23&quot; data-start=&quot;403&quot; data-end=&quot;464&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;4. 사용한 패턴 / 알고리즘&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그래프&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;790&quot; data-start=&quot;771&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;790&quot; data-start=&quot;771&quot;&gt;경기 결과를 방향 그래프로 표현&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;BFS&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;828&quot; data-start=&quot;801&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;814&quot; data-start=&quot;801&quot;&gt;간접 승리 관계 탐색&lt;/li&gt;
&lt;li data-end=&quot;828&quot; data-start=&quot;815&quot;&gt;간접 패배 관계 탐색&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;5. 핵심 아이디어&lt;/h2&gt;
&lt;p data-end=&quot;886&quot; data-start=&quot;853&quot; data-ke-size=&quot;size16&quot;&gt;핵심은 한 선수 기준으로 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;관계를 양방향으로 확인&lt;/b&gt;&lt;/span&gt;하는 것이다.&lt;/p&gt;
&lt;p data-end=&quot;906&quot; data-start=&quot;888&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 i번 선수에 대해:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;1013&quot; data-start=&quot;908&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;959&quot; data-start=&quot;908&quot;&gt;wins 그래프를 따라가면&lt;b&gt; i가 이길 수 있는 선수&lt;/b&gt;들을 찾을 수 있다.&lt;/li&gt;
&lt;li data-end=&quot;1013&quot; data-start=&quot;961&quot;&gt;loses 그래프를 따라가면 &lt;b&gt;i를 이길 수 있는 선수&lt;/b&gt;들을 찾을 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-end=&quot;1032&quot; data-start=&quot;1015&quot; data-ke-size=&quot;size16&quot;&gt;이 두 개의 개수를 합쳤을 때:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;win_cnt + lose_cnt == n - 1&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1119&quot; data-start=&quot;1077&quot; data-ke-size=&quot;size16&quot;&gt;이면, 나머지 모든 선수와의 관계가 확인된 것이므로 순위를 알 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;6. 풀이 코드&lt;/h2&gt;
&lt;pre id=&quot;code_1779070915437&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from collections import deque

def solution(n, results):
    answer = 0

    # wins[a] = a가 이긴 선수 목록
    wins = [[] for _ in range(n + 1)]

    # loses[b] = b를 이긴 선수 목록
    loses = [[] for _ in range(n + 1)]

    # 경기 결과를 그래프로 저장
    for a, b in results:
        wins[a].append(b)
        loses[b].append(a)

    # 각 선수마다 순위를 확정할 수 있는지 확인
    for i in range(1, n + 1):

        # i가 이길 수 있는 선수 수
        win_cnt = 0
        visited = [False] * (n + 1)
        q = deque([i])

        while q:
            node = q.popleft()
            visited[node] = True

            # 현재 선수가 이긴 선수들을 따라감
            for nxt in wins[node]:
                if not visited[nxt]:
                    visited[nxt] = True
                    q.append(nxt)
                    win_cnt += 1

        # i를 이길 수 있는 선수 수
        lose_cnt = 0
        visited = [False] * (n + 1)
        q = deque([i])

        while q:
            node = q.popleft()

            # 현재 선수를 이긴 선수들을 따라감
            for nxt in loses[node]:
                if not visited[nxt]:
                    visited[nxt] = True
                    q.append(nxt)
                    lose_cnt += 1

        # 다른 모든 선수와 승패 관계가 확인되면 순위 확정 가능
        if win_cnt + lose_cnt == n - 1:
            answer += 1

    return answer&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-end=&quot;2453&quot; data-start=&quot;2434&quot; data-ke-size=&quot;size23&quot;&gt;6-1. 코드 추가 설명&lt;/h3&gt;
&lt;p data-end=&quot;2492&quot; data-start=&quot;2455&quot; data-ke-size=&quot;size16&quot;&gt;이 문제에서 그래프를 두 개 만드는 이유는 방향이 다르기 때문이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;wins[a].append(b)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2536&quot; data-start=&quot;2527&quot; data-ke-size=&quot;size16&quot;&gt;는 다음 의미다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;a가 b를 이겼다&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2602&quot; data-start=&quot;2561&quot; data-ke-size=&quot;size16&quot;&gt;즉, a에서 출발하면 a가 이길 수 있는 선수들을 따라갈 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;2608&quot; data-start=&quot;2604&quot; data-ke-size=&quot;size16&quot;&gt;반대로:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;loses[b].append(a)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2653&quot; data-start=&quot;2644&quot; data-ke-size=&quot;size16&quot;&gt;는 다음 의미다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;b를 이긴 선수는 a다&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2722&quot; data-start=&quot;2681&quot; data-ke-size=&quot;size16&quot;&gt;즉, b에서 출발하면 b를 이길 수 있는 선수들을 따라갈 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;2753&quot; data-start=&quot;2729&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 경기 결과가 다음과 같다고 하자.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;n = 5
results = [[4, 3], [4, 2], [3, 2], [1, 2], [2, 5]]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2825&quot; data-start=&quot;2805&quot; data-ke-size=&quot;size16&quot;&gt;그러면 그래프는 다음처럼 만들어진다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;wins = [[], [2], [5], [2], [3, 2], []]

loses = [[], [], [4, 3, 1], [4], [], [2]]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2966&quot; data-start=&quot;2952&quot; data-ke-size=&quot;size16&quot;&gt;2번 선수 기준으로 보면:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;wins[2] = [5]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;252&quot; data-start=&quot;236&quot; data-ke-size=&quot;size16&quot;&gt;2번은 5번을 이길 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;loses[2] = [4, 3, 1]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;308&quot; data-start=&quot;288&quot; data-ke-size=&quot;size16&quot;&gt;2번은 4번, 3번, 1번에게 진다.&lt;/p&gt;
&lt;p data-end=&quot;321&quot; data-start=&quot;310&quot; data-ke-size=&quot;size16&quot;&gt;따라서 2번 선수는:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;이길 수 있는 선수: 5번
질 수 있는 선수: 4번, 3번, 1번&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;406&quot; data-start=&quot;373&quot; data-ke-size=&quot;size16&quot;&gt;즉, 2번 선수는 나머지 4명과의 관계를 모두 알 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;win_cnt + lose_cnt = 1 + 3 = 4
n - 1 = 4&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;486&quot; data-start=&quot;462&quot; data-ke-size=&quot;size16&quot;&gt;따라서 2번 선수의 순위는 확정할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;7. 핵심 로직 요약&lt;/h2&gt;
&lt;p data-end=&quot;3223&quot; data-start=&quot;3166&quot; data-ke-size=&quot;size16&quot;&gt;이 문제의 핵심은 &lt;b&gt;각 선수 기준으로 이길 수 있는 선수와 질 수 있는 선수를 모두 찾는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3396&quot; data-start=&quot;3225&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3246&quot; data-start=&quot;3225&quot;&gt;경기 결과를 방향 그래프로 만든다.&lt;/li&gt;
&lt;li data-end=&quot;3270&quot; data-start=&quot;3247&quot;&gt;wins에는 이긴 방향을 저장한다.&lt;/li&gt;
&lt;li data-end=&quot;3298&quot; data-start=&quot;3271&quot;&gt;loses에는 진 방향을 반대로 저장한다.&lt;/li&gt;
&lt;li data-end=&quot;3330&quot; data-start=&quot;3299&quot;&gt;각 선수마다 wins 방향으로 BFS를 수행한다.&lt;/li&gt;
&lt;li data-end=&quot;3363&quot; data-start=&quot;3331&quot;&gt;각 선수마다 loses 방향으로 BFS를 수행한다.&lt;/li&gt;
&lt;li data-end=&quot;3396&quot; data-start=&quot;3364&quot;&gt;두 개수를 합쳐 n - 1이면 순위 확정 가능하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;3454&quot; data-start=&quot;3398&quot; data-ke-size=&quot;size16&quot;&gt;결국, &lt;b&gt;&amp;ldquo;승리 방향 탐색 + 패배 방향 탐색 + 전체 관계 확인&amp;rdquo; &lt;/b&gt;이 흐름이 핵심이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot; data-section-id=&quot;1yk7ghc&quot; data-start=&quot;2298&quot; data-end=&quot;2310&quot;&gt;8. 정리&lt;/h2&gt;
&lt;p data-end=&quot;3547&quot; data-start=&quot;3487&quot; data-ke-size=&quot;size16&quot;&gt;처음 보면 단순히 승패 수를 세면 될 것처럼 보인다. 하지만 직접 경기 결과만으로는 순위를 알 수 없다.&lt;/p&gt;
&lt;p data-end=&quot;3572&quot; data-start=&quot;3549&quot; data-ke-size=&quot;size16&quot;&gt;이 문제에서 중요한 부분은 간접 관계였다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;A가 B를 이기고, B가 C를 이기면
A는 C보다 앞에 있다&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3640&quot; data-start=&quot;3621&quot; data-ke-size=&quot;size16&quot;&gt;이 관계를 그래프로 따라가야 한다.&lt;/p&gt;
&lt;p data-end=&quot;3675&quot; data-start=&quot;3642&quot; data-ke-size=&quot;size16&quot;&gt;또 하나 중요한 점은 한 방향만 보면 안 된다는 것이다.&lt;/p&gt;
&lt;p data-end=&quot;3687&quot; data-start=&quot;3677&quot; data-ke-size=&quot;size16&quot;&gt;순위를 확정하려면:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;내가 이길 수 있는 선수
나를 이길 수 있는 선수&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3741&quot; data-start=&quot;3730&quot; data-ke-size=&quot;size16&quot;&gt;둘 다 알아야 한다.&lt;/p&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;3805&quot; data-start=&quot;3743&quot; data-ke-size=&quot;size16&quot;&gt;결국 이 문제는 &lt;b&gt;한 선수와 나머지 모든 선수의 관계를 그래프 탐색으로 확인하는 문제&lt;/b&gt;라고 볼 수 있다.&lt;/p&gt;</description>
      <category>CodingTest/문제 풀이(Lv3)</category>
      <author>jjaehyeok</author>
      <guid isPermaLink="true">https://jjaehyeok.tistory.com/75</guid>
      <comments>https://jjaehyeok.tistory.com/75#entry75comment</comments>
      <pubDate>Mon, 18 May 2026 11:27:59 +0900</pubDate>
    </item>
    <item>
      <title>미니 PC 서버 Kubernetes 배포 (2) - Airflow 기반 데이터 파이프라인 동작 검증</title>
      <link>https://jjaehyeok.tistory.com/74</link>
      <description>&lt;h2 data-end=&quot;534&quot; data-start=&quot;523&quot; data-ke-size=&quot;size26&quot;&gt;1. 문제 배경&lt;/h2&gt;
&lt;p data-end=&quot;653&quot; data-start=&quot;536&quot; data-ke-size=&quot;size16&quot;&gt;Kafka, Redis, MySQL, Spark Streaming을 포함한 기반 서비스 구성을 완료했지만, 각 구성 요소가 개별적으로 실행되는 상태만으로는 실제 데이터 흐름이 정상 동작하는지 확인하기 어려웠다.&lt;/p&gt;
&lt;p data-end=&quot;691&quot; data-start=&quot;655&quot; data-ke-size=&quot;size16&quot;&gt;특히 이번 구조에서는 다음 흐름이 실제로 연결되어 동작해야 했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;Airflow
&amp;rarr; Kafka
&amp;rarr; Spark Streaming
&amp;rarr; Redis
&amp;rarr; MySQL, S3&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;814&quot; data-start=&quot;756&quot; data-ke-size=&quot;size16&quot;&gt;즉, 단순 Pod 실행 여부가 아니라 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;실제 데이터가 생성되고 처리되는 흐름 자체를 검증할 필요&lt;/b&gt;&lt;/span&gt;가 있었다.&lt;/p&gt;
&lt;p data-end=&quot;912&quot; data-start=&quot;816&quot; data-ke-size=&quot;size16&quot;&gt;이번 단계에서는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Airflow DAG 실행을 통해 데이터가 생성된 이후, Kafka, Spark, Redis, MySQL, S3 로 이어지는 흐름이 정상적으로 연결되는지를 확인해야한다&lt;/b&gt;&lt;/span&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 data-end=&quot;927&quot; data-start=&quot;919&quot; data-ke-size=&quot;size26&quot;&gt;2. 목표&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1042&quot; data-start=&quot;929&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;956&quot; data-start=&quot;929&quot;&gt;Airflow DAG 실행을 통한 데이터 적재&lt;/li&gt;
&lt;li data-end=&quot;994&quot; data-start=&quot;957&quot;&gt;Kafka &amp;rarr; Spark &amp;rarr; Redis / MySQL,S3 흐름 검증&lt;/li&gt;
&lt;li data-end=&quot;1018&quot; data-start=&quot;995&quot;&gt;각 구성 요소 간 실제 연결 상태 확인&lt;/li&gt;
&lt;li data-end=&quot;1042&quot; data-start=&quot;1019&quot;&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-end=&quot;1070&quot; data-start=&quot;1049&quot; data-ke-size=&quot;size26&quot;&gt;3. Airflow 구성 및 실행&lt;/h2&gt;
&lt;p data-end=&quot;1112&quot; data-start=&quot;1072&quot; data-ke-size=&quot;size16&quot;&gt;데이터 적재를 위해 Airflow를 Kubernetes 환경에 배포했다.&lt;/p&gt;
&lt;p data-end=&quot;1185&quot; data-start=&quot;1114&quot; data-ke-size=&quot;size16&quot;&gt;Airflow는 Scheduler와 Webserver를 분리된 리소스로 구성했으며, 각 구성 요소는 YAML 기준으로 적용했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;kubectl apply -f airflow-rbac.yaml
kubectl apply -f airflow-init.yaml
kubectl apply -f airflow-logs-pvc.yaml
kubectl apply -f airflow-scheduler.yaml
kubectl apply -f airflow-webserver.yaml
kubectl apply -f airflow-service.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #000000; font-size: 1.44em; letter-spacing: -1px;&quot;&gt;3-1) Airflow Webserver 접속&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1527&quot; data-start=&quot;1470&quot; data-ke-size=&quot;size16&quot;&gt;Airflow UI에 접근하기 위해 Kubernetes Service 기준으로 포트 포워딩을 수행했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;kubectl port-forward svc/airflow-webserver 8080:8080 -n airflow&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;p data-end=&quot;703&quot; data-start=&quot;626&quot; data-ke-size=&quot;size16&quot;&gt;하지만 현재 Airflow는 원격&lt;b&gt; 미니 PC 서버 내부에서 실행 중&lt;/b&gt;이었기 때문에,&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; 로컬 PC 브라우저에서 직접 접근할 수는 없는 상태&lt;/b&gt;&lt;/span&gt;였다.&lt;/p&gt;
&lt;p data-end=&quot;761&quot; data-start=&quot;705&quot; data-ke-size=&quot;size16&quot;&gt;따라서 Windows 로컬 PC에서 &lt;b&gt;SSH Local Port Forwarding을 추가로 구성&lt;/b&gt;했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;ssh -L 8080:localhost:8080 &amp;lt;접속ID&amp;gt;@&amp;lt;미니PC-IP&amp;gt; -p &amp;lt;포트번호&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;863&quot; data-start=&quot;838&quot; data-ke-size=&quot;size16&quot;&gt;이후 로컬 브라우저에서 다음 주소로 접속했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;localhost:8080&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;987&quot; data-start=&quot;912&quot; data-ke-size=&quot;size16&quot;&gt;이 구조를 통해 로컬 PC 브라우저 요청이 SSH 터널을 거쳐 미니 PC 내부의 Airflow Webserver로 전달되도록 구성했다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;691&quot; data-origin-height=&quot;492&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GVTHy/dJMcah5uakA/NSGsBkcMxV5FWFtxyfyask/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GVTHy/dJMcah5uakA/NSGsBkcMxV5FWFtxyfyask/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GVTHy/dJMcah5uakA/NSGsBkcMxV5FWFtxyfyask/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGVTHy%2FdJMcah5uakA%2FNSGsBkcMxV5FWFtxyfyask%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;691&quot; height=&quot;492&quot; data-origin-width=&quot;691&quot; data-origin-height=&quot;492&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-end=&quot;1720&quot; data-start=&quot;1689&quot; data-ke-size=&quot;size20&quot;&gt;3-2) airflow hook 설정&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1243&quot; data-origin-height=&quot;462&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvom4U/dJMcabxn0Cm/MeftKlnJcK4gHcQR5DMVq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvom4U/dJMcabxn0Cm/MeftKlnJcK4gHcQR5DMVq1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvom4U/dJMcabxn0Cm/MeftKlnJcK4gHcQR5DMVq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbvom4U%2FdJMcabxn0Cm%2FMeftKlnJcK4gHcQR5DMVq1%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;1243&quot; height=&quot;462&quot; data-origin-width=&quot;1243&quot; data-origin-height=&quot;462&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;149&quot; data-start=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;Airflow에서 외부 API를 호출할 때 API 키나 기본 URL을 코드에 직접 작성하면 관리가 어려워진다.&lt;br /&gt;특히 API 키가 DAG 코드에 포함되면 Git에 노출될 위험이 있고, 환경이 바뀔 때마다 코드를 수정해야 한다.&lt;/p&gt;
&lt;p data-end=&quot;257&quot; data-start=&quot;151&quot; data-ke-size=&quot;size16&quot;&gt;이를 방지하기 위해 Airflow의 &lt;b&gt;Connection&lt;/b&gt; 기능을 사용했다.&lt;br /&gt;Connection에 API 접속 정보를 등록하고, &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;DAG에서는 Hook을 통해 필요한 값을 가져오도록 구성&lt;/b&gt;&lt;/span&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 data-end=&quot;1804&quot; data-start=&quot;1789&quot; data-ke-size=&quot;size26&quot;&gt;4. 데이터 흐름 확인&lt;/h2&gt;
&lt;h3 data-end=&quot;1828&quot; data-start=&quot;1806&quot; data-ke-size=&quot;size23&quot;&gt;4-1) Kafka 데이터 유입 확인&lt;/h3&gt;
&lt;p data-end=&quot;1883&quot; data-start=&quot;1830&quot; data-ke-size=&quot;size16&quot;&gt;Airflow DAG 실행 이후 Kafka topic에 데이터가 정상적으로 적재되는지 확인했다.&lt;/p&gt;
&lt;h4 data-end=&quot;1907&quot; data-start=&quot;1890&quot; data-ke-size=&quot;size20&quot;&gt;Kafka Pod 접속&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;kubectl exec -it kafka-0 -- bash&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-end=&quot;1989&quot; data-start=&quot;1972&quot; data-ke-size=&quot;size20&quot;&gt;Topic 데이터 확인&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;kafka-console-consumer.sh \
--bootstrap-server localhost:9092 \
--topic outbreak_topic \
--from-beginning&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1135&quot; data-origin-height=&quot;183&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmzNfv/dJMcabxn0Qo/Ch3GgpUWCycbRNfNQXbUJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmzNfv/dJMcabxn0Qo/Ch3GgpUWCycbRNfNQXbUJK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmzNfv/dJMcabxn0Qo/Ch3GgpUWCycbRNfNQXbUJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmzNfv%2FdJMcabxn0Qo%2FCh3GgpUWCycbRNfNQXbUJK%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;1135&quot; height=&quot;183&quot; data-origin-width=&quot;1135&quot; data-origin-height=&quot;183&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;2169&quot; data-start=&quot;2122&quot; data-ke-size=&quot;size16&quot;&gt;확인 결과 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;DAG 실행 이후 Kafka topic에 메시지가 적재되는 것을 확인&lt;/b&gt;&lt;/span&gt;했다.&lt;/p&gt;
&lt;p data-end=&quot;2220&quot; data-start=&quot;2171&quot; data-ke-size=&quot;size16&quot;&gt;이를 통해 데이터가 파이프라인 &lt;b&gt;시작 지점까지 정상적으로 전달되는 것을 확인&lt;/b&gt;할 수 있었다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-start=&quot;1806&quot; data-end=&quot;1828&quot; data-ke-size=&quot;size23&quot;&gt;4-2) Spark Streaming 처리 확인&lt;/h3&gt;
&lt;p data-end=&quot;2304&quot; data-start=&quot;2257&quot; data-ke-size=&quot;size16&quot;&gt;Spark Streaming Driver 및 Executor Pod 상태를 확인했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;kubectl get pods | grep streaming&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2406&quot; data-start=&quot;2365&quot; data-ke-size=&quot;size16&quot;&gt;또한 로그를 통해 Kafka 데이터를 지속적으로 소비하는 흐름을 확인했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;kubectl logs outbreak-streaming-driver&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2540&quot; data-start=&quot;2472&quot; data-ke-size=&quot;size16&quot;&gt;로그 확인 결과 Spark Streaming이 Kafka 메시지를 정상적으로 읽어서 배치 처리하는 것을 확인할 수 있었다.&lt;/p&gt;
&lt;p data-end=&quot;2641&quot; data-start=&quot;2542&quot; data-ke-size=&quot;size16&quot;&gt;특히 이번 구조에서는 Spark 작업을 DAG 실행 시마다 생성하는 방식이 아니라, Kafka topic을 지속적으로 구독하는 상시 실행 서비스 형태로 구성했다는 점이 중요했다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-start=&quot;1806&quot; data-end=&quot;1828&quot; data-ke-size=&quot;size23&quot;&gt;4-3) Redis 반영 확인&lt;/h3&gt;
&lt;p data-end=&quot;2725&quot; data-start=&quot;2668&quot; data-ke-size=&quot;size16&quot;&gt;Redis에 데이터가 정상적으로 저장되었는지 확인하기 위해 redis-cli로 key 목록을 조회했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;kubectl exec -it redis-xxxxx -- redis-cli&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;KEYS *&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2868&quot; data-start=&quot;2826&quot; data-ke-size=&quot;size16&quot;&gt;조회 결과 Spark 처리 이후 Redis에 key가 저장된 것을 확인했다.&lt;/p&gt;
&lt;p data-end=&quot;2925&quot; data-start=&quot;2870&quot; data-ke-size=&quot;size16&quot;&gt;이를 통해 Spark 처리 결과가 실시간 저장 영역까지 정상적으로 전달되는 것을 확인할 수 있었다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-start=&quot;1806&quot; data-end=&quot;1828&quot; data-ke-size=&quot;size23&quot;&gt;4-4) MySQL 반영 확인&lt;/h3&gt;
&lt;p data-end=&quot;2981&quot; data-start=&quot;2952&quot; data-ke-size=&quot;size16&quot;&gt;최종적으로 MySQL에 데이터가 저장되는지 확인했다.&lt;/p&gt;
&lt;h4 data-end=&quot;3005&quot; data-start=&quot;2988&quot; data-ke-size=&quot;size20&quot;&gt;MySQL Pod 접속&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;kubectl exec -it mysql-0 -- mysql -u user -p&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-end=&quot;3098&quot; data-start=&quot;3082&quot; data-ke-size=&quot;size20&quot;&gt;Database 확인&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;302&quot; data-origin-height=&quot;235&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6oGqZ/dJMcafmihfx/oXXMzIcKXLmo26dHdt9x7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6oGqZ/dJMcafmihfx/oXXMzIcKXLmo26dHdt9x7K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6oGqZ/dJMcafmihfx/oXXMzIcKXLmo26dHdt9x7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6oGqZ%2FdJMcafmihfx%2FoXXMzIcKXLmo26dHdt9x7K%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;302&quot; height=&quot;235&quot; data-origin-width=&quot;302&quot; data-origin-height=&quot;235&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;SHOW DATABASES;
USE toy_project;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-end=&quot;3173&quot; data-start=&quot;3162&quot; data-ke-size=&quot;size20&quot;&gt;테이블 확인&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;SHOW TABLES;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;366&quot; data-origin-height=&quot;562&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HLaYL/dJMcabc4sCX/K5qAJRXjWlV3yL9SSXM0G1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HLaYL/dJMcabc4sCX/K5qAJRXjWlV3yL9SSXM0G1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HLaYL/dJMcabc4sCX/K5qAJRXjWlV3yL9SSXM0G1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHLaYL%2FdJMcabc4sCX%2FK5qAJRXjWlV3yL9SSXM0G1%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;366&quot; height=&quot;562&quot; data-origin-width=&quot;366&quot; data-origin-height=&quot;562&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-end=&quot;3228&quot; data-start=&quot;3217&quot; data-ke-size=&quot;size20&quot;&gt;데이터 확인&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;n1ql&quot;&gt;&lt;code&gt;SELECT * FROM OUTBREAK_OCCURRENCE;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;418&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lfh8v/dJMcadBUbdQ/sSUbyQcqldqB4OkS7Scduk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lfh8v/dJMcadBUbdQ/sSUbyQcqldqB4OkS7Scduk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lfh8v/dJMcadBUbdQ/sSUbyQcqldqB4OkS7Scduk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Flfh8v%2FdJMcadBUbdQ%2FsSUbyQcqldqB4OkS7Scduk%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;682&quot; height=&quot;418&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;418&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;3348&quot; data-start=&quot;3289&quot; data-ke-size=&quot;size16&quot;&gt;확인 결과 돌발상황 ID(ACC_ID), 발생 시각, 종료 시각 데이터가 정상적으로 저장된 것을 확인했다.&lt;/p&gt;
&lt;p data-end=&quot;3348&quot; data-start=&quot;3289&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;3348&quot; data-start=&quot;3289&quot; data-ke-size=&quot;size20&quot;&gt;4-5) S3 저장 확인&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1892&quot; data-origin-height=&quot;707&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XK5gt/dJMcafGA0Tv/xxvNIihl1AQpfkIKhwPHA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XK5gt/dJMcafGA0Tv/xxvNIihl1AQpfkIKhwPHA1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XK5gt/dJMcafGA0Tv/xxvNIihl1AQpfkIKhwPHA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXK5gt%2FdJMcafGA0Tv%2FxxvNIihl1AQpfkIKhwPHA1%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;1892&quot; height=&quot;707&quot; data-origin-width=&quot;1892&quot; data-origin-height=&quot;707&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;3399&quot; data-start=&quot;3350&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3399&quot; data-start=&quot;3350&quot; data-ke-size=&quot;size16&quot;&gt;이를 통해&lt;b&gt; 최종적으로 &lt;span style=&quot;color: #ee2323;&quot;&gt;전처리된 데이터는&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;MySQL&lt;/span&gt;, &lt;span style=&quot;color: #ee2323;&quot;&gt;원본데이터는 S3&lt;/span&gt;에&lt;/span&gt;&amp;nbsp;정상적으로 전달되는 것을 검증&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 data-end=&quot;3418&quot; data-start=&quot;3406&quot; data-ke-size=&quot;size26&quot;&gt;5. 핵심 포인트&lt;/h2&gt;
&lt;p data-end=&quot;3502&quot; data-start=&quot;3420&quot; data-ke-size=&quot;size16&quot;&gt;이번 단계에서 중요한 점은 각 서비스를 단순 실행한 것이 아니라, 전체 흐름이 하나의 데이터 파이프라인으로 연결되어 동작하는지를 검증했다는 점이다.&lt;/p&gt;
&lt;p data-end=&quot;3526&quot; data-start=&quot;3504&quot; data-ke-size=&quot;size16&quot;&gt;각 서비스 역할은 다음과 같이 분리했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3694&quot; data-start=&quot;3528&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3564&quot; data-start=&quot;3528&quot;&gt;Airflow &amp;rarr; 외부 데이터 수집 및 Kafka 적재&lt;/li&gt;
&lt;li data-end=&quot;3592&quot; data-start=&quot;3566&quot;&gt;Kafka &amp;rarr; 데이터 전달 경로 역할&lt;/li&gt;
&lt;li data-end=&quot;3633&quot; data-start=&quot;3594&quot;&gt;Spark Streaming &amp;rarr; Kafka 데이터 지속 처리&lt;/li&gt;
&lt;li data-end=&quot;3666&quot; data-start=&quot;3635&quot;&gt;Redis &amp;rarr; 실시간 저장 및 중간 버퍼 역할&lt;/li&gt;
&lt;li data-end=&quot;3694&quot; data-start=&quot;3668&quot;&gt;MySQL &amp;rarr; 최종 이력 데이터 저장&lt;/li&gt;
&lt;li data-end=&quot;3694&quot; data-start=&quot;3668&quot;&gt;S3 &amp;rarr; 원본 데이터 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;3784&quot; data-start=&quot;3696&quot; data-ke-size=&quot;size16&quot;&gt;특히 Spark Streaming은 DAG 실행 시마다 생성되는 구조가 아니라, Kafka topic을 지속적으로 구독하는 상시 실행 서비스 형태로 구성했다.&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-end=&quot;3805&quot; data-start=&quot;3791&quot; data-ke-size=&quot;size26&quot;&gt;6. 전체 구조 정리&lt;/h2&gt;
&lt;p data-end=&quot;3830&quot; data-start=&quot;3807&quot; data-ke-size=&quot;size16&quot;&gt;전체 데이터 흐름은 다음과 같이 구성했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;Airflow
&amp;rarr; Kafka
&amp;rarr; Spark Streaming
&amp;rarr; Redis
&amp;rarr; MySQL&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3930&quot; data-start=&quot;3907&quot; data-ke-size=&quot;size16&quot;&gt;구체적으로는 다음과 같은 역할로 분리했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;4060&quot; data-start=&quot;3932&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3960&quot; data-start=&quot;3932&quot;&gt;Airflow에서 1분 주기로 외부 API 호출&lt;/li&gt;
&lt;li data-end=&quot;3986&quot; data-start=&quot;3961&quot;&gt;Kafka를 통해 메시지 단위 데이터 전달&lt;/li&gt;
&lt;li data-end=&quot;4022&quot; data-start=&quot;3987&quot;&gt;PySpark에서 Kafka topic 지속 구독 및 전처리&lt;/li&gt;
&lt;li data-end=&quot;4038&quot; data-start=&quot;4023&quot;&gt;Redis에 실시간 저장&lt;/li&gt;
&lt;li data-end=&quot;4060&quot; data-start=&quot;4039&quot;&gt;MySQL에 최종 이력 데이터 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;4104&quot; data-start=&quot;4062&quot; data-ke-size=&quot;size16&quot;&gt;전체적으로 수집&amp;ndash;처리&amp;ndash;저장 흐름을 분리해 각 단계 역할을 명확하게 구성했다.&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-end=&quot;4120&quot; data-start=&quot;4111&quot; data-ke-size=&quot;size26&quot;&gt;7. 마치며&lt;/h2&gt;
&lt;p data-end=&quot;4183&quot; data-start=&quot;4122&quot; data-ke-size=&quot;size16&quot;&gt;이번 단계에서는 Kubernetes 위에서 실행 중인 각 서비스를 실제 데이터 흐름 기준으로 연결해 검증했다.&lt;/p&gt;
&lt;p data-end=&quot;4214&quot; data-start=&quot;4185&quot; data-ke-size=&quot;size16&quot;&gt;특히 단순 Pod 실행 상태만 확인하는 것이 아니라,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;4271&quot; data-start=&quot;4216&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;4224&quot; data-start=&quot;4216&quot;&gt;데이터 생성&lt;/li&gt;
&lt;li data-end=&quot;4235&quot; data-start=&quot;4225&quot;&gt;Kafka 적재&lt;/li&gt;
&lt;li data-end=&quot;4246&quot; data-start=&quot;4236&quot;&gt;Spark 처리&lt;/li&gt;
&lt;li data-end=&quot;4257&quot; data-start=&quot;4247&quot;&gt;Redis 저장&lt;/li&gt;
&lt;li data-end=&quot;4271&quot; data-start=&quot;4258&quot;&gt;MySQL 최종 적재&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;4326&quot; data-start=&quot;4273&quot; data-ke-size=&quot;size16&quot;&gt;과정을 순차적으로 확인하면서 실제 데이터 파이프라인이 정상적으로 동작하는지를 검증할 수 있었다.&lt;/p&gt;
&lt;p data-end=&quot;4393&quot; data-start=&quot;4328&quot; data-ke-size=&quot;size16&quot;&gt;또한 Spark Streaming을 상시 실행 구조로 구성하면서, 실시간 데이터 처리 흐름도 함께 확인할 수 있었다.&lt;/p&gt;</description>
      <category>Navisafe/Infrastructure</category>
      <author>jjaehyeok</author>
      <guid isPermaLink="true">https://jjaehyeok.tistory.com/74</guid>
      <comments>https://jjaehyeok.tistory.com/74#entry74comment</comments>
      <pubDate>Tue, 12 May 2026 19:54:46 +0900</pubDate>
    </item>
    <item>
      <title>미니 PC 서버 Kubernetes 배포 (1) - Kafka &amp;middot; Redis &amp;middot; MySQL 기반 서비스 배포</title>
      <link>https://jjaehyeok.tistory.com/73</link>
      <description>&lt;h2 data-end=&quot;80&quot; data-start=&quot;69&quot; data-ke-size=&quot;size26&quot;&gt;1. 문제 배경&lt;/h2&gt;
&lt;p data-end=&quot;141&quot; data-start=&quot;82&quot; data-ke-size=&quot;size16&quot;&gt;사전 준비 단계에서 이미지, 설정, 권한, 저장소를 분리했지만 실제 서비스는 아직 실행되지 않은 상태였다.&lt;/p&gt;
&lt;p data-end=&quot;214&quot; data-start=&quot;143&quot; data-ke-size=&quot;size16&quot;&gt;Kubernetes에서는 서비스를 단순히 실행하는 것이 아니라, &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;각 서비스의 역할과 성격에 맞게 리소스를 나눠서 배포&lt;/b&gt;&lt;/span&gt;해야 한다.&lt;/p&gt;
&lt;p data-end=&quot;240&quot; data-start=&quot;216&quot; data-ke-size=&quot;size16&quot;&gt;특히 이번 구조에서는 다음 기준이 중요했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;330&quot; data-start=&quot;242&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;252&quot; data-start=&quot;242&quot;&gt;상태 저장 여부&lt;/li&gt;
&lt;li data-end=&quot;271&quot; data-start=&quot;253&quot;&gt;다른 서비스의 접근 필요 여부&lt;/li&gt;
&lt;li data-end=&quot;292&quot; data-start=&quot;272&quot;&gt;지속적으로 실행되어야 하는지 여부&lt;/li&gt;
&lt;li data-end=&quot;330&quot; data-start=&quot;293&quot;&gt;후속 처리 서비스가 의존하는 리소스가 먼저 준비되어 있는지 여부&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;457&quot; data-start=&quot;332&quot; data-ke-size=&quot;size16&quot;&gt;이번 단계에서는 Kafka, Redis, PostgreSQL과 같은 기반 서비스를 먼저 배포하고, 이후 Spark Streaming 서비스가 안정적으로 구독할 수 있도록 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Kafka topic도 초기화 단계&lt;/b&gt;&lt;/span&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 data-end=&quot;472&quot; data-start=&quot;464&quot; data-ke-size=&quot;size26&quot;&gt;2. 목표&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;609&quot; data-start=&quot;474&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;516&quot; data-start=&quot;474&quot;&gt;NaviSafe 주요 서비스를 Kubernetes 리소스로 분리하여 배포&lt;/li&gt;
&lt;li data-end=&quot;533&quot; data-start=&quot;517&quot;&gt;서비스 간 연결 구조 구성&lt;/li&gt;
&lt;li data-end=&quot;575&quot; data-start=&quot;534&quot;&gt;Spark Streaming이 의존하는 Kafka topic 사전 생성&lt;/li&gt;
&lt;li data-end=&quot;609&quot; data-start=&quot;576&quot;&gt;이후 데이터 처리 흐름이 실행될 수 있는 기반 환경 준비&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 data-end=&quot;631&quot; data-start=&quot;616&quot; data-ke-size=&quot;size26&quot;&gt;3. 배포 대상 서비스&lt;/h2&gt;
&lt;p data-end=&quot;662&quot; data-start=&quot;633&quot; data-ke-size=&quot;size16&quot;&gt;이번 단계에서 배포한 주요 구성 요소는 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;829&quot; data-start=&quot;664&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;700&quot; data-start=&quot;664&quot;&gt;Zookeeper / Kafka&lt;br /&gt;&amp;rarr; 메시지 브로커 구성&lt;/li&gt;
&lt;li data-end=&quot;734&quot; data-start=&quot;702&quot;&gt;Redis&lt;br /&gt;&amp;rarr; 중간 버퍼 및 실시간 데이터 저장&lt;/li&gt;
&lt;li data-end=&quot;783&quot; data-start=&quot;736&quot;&gt;PostgreSQL + PostGIS&lt;br /&gt;&amp;rarr; 공간 데이터 및 최종 데이터 저장&lt;/li&gt;
&lt;li data-end=&quot;829&quot; data-start=&quot;785&quot;&gt;Kafka Topic&lt;br /&gt;&amp;rarr; Spark Streaming 구독 입력 경로&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;912&quot; data-start=&quot;831&quot; data-ke-size=&quot;size16&quot;&gt;이 구성 요소들은 이후&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; Spark Streaming과 Airflow가 정상적으로 동작하기 위해 먼저 준비되어 있어야 하는 기반 서비스&lt;/b&gt;&lt;/span&gt;에 해당한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 data-end=&quot;934&quot; data-start=&quot;919&quot; data-ke-size=&quot;size26&quot;&gt;4. 리소스 설계 기준&lt;/h2&gt;
&lt;p data-end=&quot;964&quot; data-start=&quot;936&quot; data-ke-size=&quot;size16&quot;&gt;서비스를 배포할 때 다음 기준으로 리소스를 나눴다.&lt;/p&gt;
&lt;h4 data-end=&quot;1001&quot; data-start=&quot;971&quot; data-ke-size=&quot;size20&quot;&gt;4-1) StatefulSet&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1035&quot; data-start=&quot;1003&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1010&quot; data-start=&quot;1003&quot;&gt;Kafka&lt;/li&gt;
&lt;li data-end=&quot;1022&quot; data-start=&quot;1011&quot;&gt;Zookeeper&lt;/li&gt;
&lt;li data-end=&quot;1035&quot; data-start=&quot;1023&quot;&gt;MySQL&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1085&quot; data-start=&quot;1037&quot; data-ke-size=&quot;size16&quot;&gt;이 리소스들은 데이터 유지가 필요하고, Pod 이름 및 순서가 의미를 가지는 서비스다. 따라서 일반 Deployment보다 StatefulSet이 더 적절했다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot; data-start=&quot;971&quot; data-end=&quot;1001&quot;&gt;4-2) Deployment&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1176&quot; data-start=&quot;1169&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1176&quot; data-start=&quot;1169&quot;&gt;Redis&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1267&quot; data-start=&quot;1178&quot; data-ke-size=&quot;size16&quot;&gt;Redis는 현재 구조에서 단일 인스턴스로 운용했고, Pod가 재생성되더라도 동일한 방식으로 다시 실행되면 되는 형태였기 때문에 Deployment로 구성했다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot; data-start=&quot;971&quot; data-end=&quot;1001&quot;&gt;4-3) Service&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1352&quot; data-start=&quot;1300&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1315&quot; data-start=&quot;1300&quot;&gt;Kafka Service&lt;/li&gt;
&lt;li data-end=&quot;1331&quot; data-start=&quot;1316&quot;&gt;Redis Service&lt;/li&gt;
&lt;li data-end=&quot;1352&quot; data-start=&quot;1332&quot;&gt;MySQL Service&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1439&quot; data-start=&quot;1354&quot; data-ke-size=&quot;size16&quot;&gt;Pod는 재생성될 때마다 IP가 변경될 수 있기 때문에, 다른 서비스들이 안정적으로 접근할 수 있도록 &lt;b&gt;Service를 통해 고정된 접근 경로를 구성&lt;/b&gt;했다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot; data-start=&quot;971&quot; data-end=&quot;1001&quot;&gt;4-4) Job&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1479&quot; data-start=&quot;1458&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1479&quot; data-start=&quot;1458&quot;&gt;Kafka Topic 초기화 Job&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1556&quot; data-start=&quot;1481&quot; data-ke-size=&quot;size16&quot;&gt;Kafka topic은 애플리케이션 로직 안에서 &lt;b&gt;매번 생성하기보다, 초기화 단계에서 먼저 준비되어 있어야 하는 기반 리소스&lt;/b&gt;에 가까웠다.&lt;/p&gt;
&lt;p data-end=&quot;1594&quot; data-start=&quot;1558&quot; data-ke-size=&quot;size16&quot;&gt;따라서 &lt;b&gt;별도 Job으로 분리해 한 번 생성하는 방식으로 구성&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 data-end=&quot;1613&quot; data-start=&quot;1601&quot; data-ke-size=&quot;size26&quot;&gt;5. 서비스 배포&lt;/h2&gt;
&lt;p data-end=&quot;1637&quot; data-start=&quot;1615&quot; data-ke-size=&quot;size16&quot;&gt;각 리소스는 YAML 기준으로 배포했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;kubectl apply -f zookeeper.yaml  
kubectl apply -f kafka.yaml

kubectl apply -f redis-service.yaml
kubectl apply -f redis-deployment.yaml

kubectl apply -f mysql-service.yaml 
kubectl apply -f mysql-statefulset.yaml

kubectl apply -f spark-rbac.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1162&quot; data-origin-height=&quot;402&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mm5nN/dJMcaiXycHM/fMY1KlwrZ2s4NKg0MEJPOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mm5nN/dJMcaiXycHM/fMY1KlwrZ2s4NKg0MEJPOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mm5nN/dJMcaiXycHM/fMY1KlwrZ2s4NKg0MEJPOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmm5nN%2FdJMcaiXycHM%2FfMY1KlwrZ2s4NKg0MEJPOK%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;1162&quot; height=&quot;402&quot; data-origin-width=&quot;1162&quot; data-origin-height=&quot;402&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1959&quot; data-start=&quot;1917&quot; data-ke-size=&quot;size16&quot;&gt;이 과정을 통해 클러스터 내부에서 기반 서비스들이 먼저 실행되도록 구성했다.&lt;/p&gt;
&lt;p data-end=&quot;2035&quot; data-start=&quot;1961&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;spark-rbac.yaml&lt;/b&gt;은 이후 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Spark 작업이 Kubernetes 리소스에 접근할 수 있도록 사전에 준비한 권한 설정&lt;/b&gt;&lt;/span&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 data-end=&quot;2063&quot; data-start=&quot;2042&quot; data-ke-size=&quot;size26&quot;&gt;6. Kafka Topic 초기화, Spark Streaming 실행 및 S3 접근 키 설정&lt;/h2&gt;
&lt;p data-end=&quot;2097&quot; data-start=&quot;2065&quot; data-ke-size=&quot;size16&quot;&gt;이번 파이프라인에서는 다음 두 개의 topic이 필요했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2139&quot; data-start=&quot;2099&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2115&quot; data-start=&quot;2099&quot;&gt;outbreak_topic&lt;/li&gt;
&lt;li data-end=&quot;2139&quot; data-start=&quot;2116&quot;&gt;emergency_alert_topic&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2259&quot; data-start=&quot;2141&quot; data-ke-size=&quot;size16&quot;&gt;초기에는 Airflow DAG에서 topic을 생성한 뒤 데이터를 적재하도록 구성했지만, &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Spark Streaming 서비스를 먼저 실행하는 구조에서는 topic이 없는 상태로 구독을 시도하면서 오류가 발생&lt;/b&gt;&lt;/span&gt;했다.&lt;/p&gt;
&lt;p data-end=&quot;2339&quot; data-start=&quot;2261&quot; data-ke-size=&quot;size16&quot;&gt;이후 &lt;b&gt;topic은 데이터 적재 이전에 미리 준비되어 있어야 하는 기반 리소스로 보고, 별도 초기화 단계에서 먼저 생성하도록 순서를 조정했다.&lt;/b&gt;&lt;/p&gt;
&lt;h4 data-end=&quot;2372&quot; data-start=&quot;2346&quot; data-ke-size=&quot;size20&quot;&gt;6-1) Kafka Topic 초기화 Job 실행&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;kubectl apply -f kafka-topic-init.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2521&quot; data-start=&quot;2438&quot; data-ke-size=&quot;size16&quot;&gt;이 방식으로 topic을 먼저 생성해두면 이후 Spark Streaming 서비스는 topic이 존재하는 상태에서 안정적으로 구독을 시작할 수 있다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot; data-start=&quot;2346&quot; data-end=&quot;2372&quot;&gt;6-2) Kafka Topic 생성 확인&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;kubectl exec -it kafka-0 -- bash
kafka-topics.sh --bootstrap-server localhost:9092 --list&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;img style=&quot;text-align: center; caret-color: transparent; letter-spacing: 0px;&quot; src=&quot;https://blog.kakaocdn.net/dna/T1KGP/dJMcagyG2JK/AAAAAAAAAAAAAAAAAAAAAJWzIJOR3AA0AohSbcqh0O57UdBk_ITCWnNeFc2FsaMj/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1780239599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=puDbY32me3nMcWrc%2Fl%2FotkCPfHg%3D&quot; data-origin-width=&quot;1158&quot; data-origin-height=&quot;107&quot; data-is-animation=&quot;false&quot; /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot; data-start=&quot;2346&quot; data-end=&quot;2372&quot;&gt;6-3) aws s3&amp;nbsp; ACCESS_KEY_ID 설정&lt;/h4&gt;
&lt;pre id=&quot;code_1778571791292&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl create secret generic aws-secret \
  --from-literal=AWS_ACCESS_KEY_ID=[] \
  --from-literal=AWS_SECRET_ACCESS_KEY=[] \
  --from-literal=AWS_REGION=ap-southeast-2 \
  --from-literal=S3_BUCKET=navisafe-data-jh \&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;2372&quot; data-start=&quot;2346&quot; data-ke-size=&quot;size20&quot;&gt;6-4) Spark Streaming 서비스 실행&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;kubectl apply -f outbreak-streaming.yaml
kubectl apply -f emergency-alert-streaming.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;3184&quot; data-start=&quot;3170&quot; data-ke-size=&quot;size26&quot;&gt;7. 실행 상태 확인&lt;/h2&gt;
&lt;p data-end=&quot;47&quot; data-start=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;배포 이후 각 서비스가 정상적으로 실행 중인지 확인했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;kubectl get pod&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1716&quot; data-origin-height=&quot;208&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpIabQ/dJMcadIFhZI/TIli3IcuTkKZr9pSJ9W5vK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpIabQ/dJMcadIFhZI/TIli3IcuTkKZr9pSJ9W5vK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpIabQ/dJMcadIFhZI/TIli3IcuTkKZr9pSJ9W5vK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpIabQ%2FdJMcadIFhZI%2FTIli3IcuTkKZr9pSJ9W5vK%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;1716&quot; height=&quot;208&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1716&quot; data-origin-height=&quot;208&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;또한 &lt;b&gt;Kafka topic 초기화 Job은 실행 완료 이후 &lt;span style=&quot;color: #ee2323;&quot;&gt;Completed&lt;/span&gt; 상태가 된 것을 확인&lt;/b&gt;했다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;3492&quot; data-start=&quot;3480&quot; data-ke-size=&quot;size26&quot;&gt;8. 핵심 포인트&lt;/h2&gt;
&lt;p data-end=&quot;3563&quot; data-start=&quot;3494&quot; data-ke-size=&quot;size16&quot;&gt;이번 단계에서 중요한 점은 서비스를 한 번에 올린 것이 아니라, &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;각 서비스의 성격에 맞게 리소스를 나눠서 배포&lt;/b&gt;&lt;/span&gt;했다는 점이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3651&quot; data-start=&quot;3565&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3590&quot; data-start=&quot;3565&quot;&gt;상태 저장 서비스 &amp;rarr; StatefulSet&lt;/li&gt;
&lt;li data-end=&quot;3615&quot; data-start=&quot;3591&quot;&gt;반복 실행 서비스 &amp;rarr; Deployment&lt;/li&gt;
&lt;li data-end=&quot;3636&quot; data-start=&quot;3616&quot;&gt;서비스 간 연결 &amp;rarr; Service&lt;/li&gt;
&lt;li data-end=&quot;3651&quot; data-start=&quot;3637&quot;&gt;초기화 작업 &amp;rarr; Job&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;3753&quot; data-start=&quot;3653&quot; data-ke-size=&quot;size16&quot;&gt;특히 Kafka topic은 단순 부가 설정이 아니라, Spark Streaming 서비스가 정상적으로 시작되기 위해 먼저 준비되어야 하는 의존 리소스라는 점을 확인할 수 있었다.&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-end=&quot;3770&quot; data-start=&quot;3760&quot; data-ke-size=&quot;size26&quot;&gt;9. 마치며&lt;/h2&gt;
&lt;p data-end=&quot;3834&quot; data-start=&quot;3772&quot; data-ke-size=&quot;size16&quot;&gt;이번 단계에서는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;실제 데이터 처리 이전에 필요한 기반 서비스를 먼저 Kubernetes 클러스터 위에 구성&lt;/b&gt;&lt;/span&gt;했다.&lt;/p&gt;
&lt;p data-end=&quot;3884&quot; data-start=&quot;3836&quot; data-ke-size=&quot;size16&quot;&gt;특히 Kafka, Redis, MySQL과 같은 서비스들은 단순 실행이 아니라&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3932&quot; data-start=&quot;3886&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3896&quot; data-start=&quot;3886&quot;&gt;상태 저장 여부&lt;/li&gt;
&lt;li data-end=&quot;3910&quot; data-start=&quot;3897&quot;&gt;서비스 간 연결 구조&lt;/li&gt;
&lt;li data-end=&quot;3919&quot; data-start=&quot;3911&quot;&gt;초기화 순서&lt;/li&gt;
&lt;li data-end=&quot;3932&quot; data-start=&quot;3920&quot;&gt;후속 서비스 의존성&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;3957&quot; data-start=&quot;3934&quot; data-ke-size=&quot;size16&quot;&gt;을 고려해 리소스를 분리할 필요가 있었다.&lt;/p&gt;
&lt;p data-end=&quot;4074&quot; data-start=&quot;3959&quot; data-ke-size=&quot;size16&quot;&gt;또한 &lt;b&gt;Spark Streaming&lt;/b&gt;이 안정적으로 실행되기 위해서는&lt;b&gt; Kafka topic 역시 사전에 준비&lt;/b&gt;되어 있어야 했고, &lt;b&gt;이를 별도 Job으로 분리하면서 초기화 작업과 지속 실행 서비스를 구분&lt;/b&gt;할 수 있었다.&lt;/p&gt;
&lt;p data-end=&quot;4131&quot; data-start=&quot;4076&quot; data-ke-size=&quot;size16&quot;&gt;이를 통해 이후 데이터 파이프라인이 &lt;b&gt;안정적으로 동작할 수 있는 기반 환경을 먼저 준비&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 data-end=&quot;7&quot; data-start=&quot;0&quot; data-ke-size=&quot;size26&quot;&gt;다음 글&lt;/h2&gt;
&lt;p data-end=&quot;131&quot; data-start=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;다음 단계에서는 Airflow를 Kubernetes 환경에 배포하고, 실제 데이터가 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Kafka &amp;rarr; Spark Streaming &amp;rarr; Redis &amp;rarr; MySQL, S3&lt;/b&gt; &lt;/span&gt;로 이어지는 전체 흐름이 정상적으로 연결되는지를 &lt;b&gt;검증&lt;/b&gt;할 예정이다.&lt;/p&gt;
&lt;p data-end=&quot;159&quot; data-start=&quot;133&quot; data-ke-size=&quot;size16&quot;&gt;특히 이번에는 단순 서비스 실행 여부가 아니라,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;268&quot; data-start=&quot;161&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;185&quot; data-start=&quot;161&quot;&gt;Airflow DAG를 통한 데이터 생성&lt;/li&gt;
&lt;li data-end=&quot;206&quot; data-start=&quot;186&quot;&gt;Kafka topic 데이터 적재&lt;/li&gt;
&lt;li data-end=&quot;237&quot; data-start=&quot;207&quot;&gt;Spark Streaming의 실시간 구독 및 처리&lt;/li&gt;
&lt;li data-end=&quot;251&quot; data-start=&quot;238&quot;&gt;Redis 저장 확인&lt;/li&gt;
&lt;li data-end=&quot;268&quot; data-start=&quot;252&quot;&gt;MySQL, S3 최종 적재 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;339&quot; data-start=&quot;270&quot; data-ke-size=&quot;size16&quot;&gt;과정을 순차적으로 확인하면서, 실제 데이터 파이프라인이 Kubernetes 환경에서 정상적으로 동작하는지를 검증할 예정이다.&lt;/p&gt;
&lt;p data-end=&quot;457&quot; data-start=&quot;341&quot; data-ke-size=&quot;size16&quot;&gt;또한 &lt;b&gt;Spark Streaming을 DAG 실행 시마다 생성하는 구조가 아니라, Kafka topic을 지속적으로 구독하는 상시 실행 서비스 형태로 구성하면서 스트리밍 기반 처리 구조도 함께 확인할 예정&lt;/b&gt;이다.&lt;/p&gt;</description>
      <category>Navisafe/Infrastructure</category>
      <author>jjaehyeok</author>
      <guid isPermaLink="true">https://jjaehyeok.tistory.com/73</guid>
      <comments>https://jjaehyeok.tistory.com/73#entry73comment</comments>
      <pubDate>Tue, 12 May 2026 16:00:04 +0900</pubDate>
    </item>
    <item>
      <title>미니 PC 서버 Kubernetes 배포 준비 - Namespace &amp;middot; Secret &amp;middot; Spark Operator 구성</title>
      <link>https://jjaehyeok.tistory.com/72</link>
      <description>&lt;h2 data-end=&quot;81&quot; data-start=&quot;70&quot; data-ke-size=&quot;size26&quot;&gt;1. 문제 배경&lt;/h2&gt;
&lt;p data-end=&quot;202&quot; data-start=&quot;83&quot; data-ke-size=&quot;size16&quot;&gt;k3s 기반 Kubernetes 클러스터 구성을 완료한 이후 바로 서비스를 배포하려 했지만, Kubernetes는 단순히 컨테이너를 실행하는 구조가 아니라 이미지, 설정, 권한, 저장소가 분리된 상태에서 동작한다.&lt;/p&gt;
&lt;p data-end=&quot;247&quot; data-start=&quot;204&quot; data-ke-size=&quot;size16&quot;&gt;이 상태에서 바로 서비스를 배포할 경우 다음과 같은 문제가 발생할 수 있었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;369&quot; data-start=&quot;249&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;274&quot; data-start=&quot;249&quot;&gt;클러스터에서 이미지를 가져오지 못하는 문제&lt;/li&gt;
&lt;li data-end=&quot;299&quot; data-start=&quot;275&quot;&gt;환경 변수와 설정이 코드에 종속되는 구조&lt;/li&gt;
&lt;li data-end=&quot;326&quot; data-start=&quot;300&quot;&gt;Pod가 외부 리소스에 접근하지 못하는 문제&lt;/li&gt;
&lt;li data-end=&quot;351&quot; data-start=&quot;327&quot;&gt;Pod 재시작 시 데이터가 유실되는 문제&lt;/li&gt;
&lt;li data-end=&quot;369&quot; data-start=&quot;352&quot;&gt;서비스 간 리소스 충돌 문제&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;438&quot; data-start=&quot;371&quot; data-ke-size=&quot;size16&quot;&gt;따라서 실제 NaviSafe 서비스를 배포하기 전에, &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Kubernetes 실행에 필요한 공통 구성 요소를 먼저 준비&lt;/b&gt;&lt;/span&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 data-end=&quot;453&quot; data-start=&quot;445&quot; data-ke-size=&quot;size26&quot;&gt;2. 목표&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;574&quot; data-start=&quot;455&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;485&quot; data-start=&quot;455&quot;&gt;Kubernetes 실행에 필요한 공통 리소스 구성&lt;/li&gt;
&lt;li data-end=&quot;497&quot; data-start=&quot;486&quot;&gt;설정과 코드 분리&lt;/li&gt;
&lt;li data-end=&quot;517&quot; data-start=&quot;498&quot;&gt;서비스별 namespace 분리&lt;/li&gt;
&lt;li data-end=&quot;535&quot; data-start=&quot;518&quot;&gt;외부 리소스 연결 정보 분리&lt;/li&gt;
&lt;li data-end=&quot;555&quot; data-start=&quot;536&quot;&gt;Spark 작업 실행 환경 구성&lt;/li&gt;
&lt;li data-end=&quot;574&quot; data-start=&quot;556&quot;&gt;데이터 유지 가능한 구조 준비&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;h2 data-end=&quot;143&quot; data-start=&quot;112&quot; data-ke-size=&quot;size26&quot;&gt;3. Docker Registry 기반 이미지 관리&lt;/h2&gt;
&lt;p data-end=&quot;258&quot; data-start=&quot;145&quot; data-ke-size=&quot;size16&quot;&gt;k3s 환경에서는 kind처럼 로컬 이미지를 직접 주입하는 방식(kind load)을 사용할 수 없기 때문에, 클러스터가&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; Docker Registry에서 이미지를 직접 가져오는 방식으로 구성&lt;/b&gt;&lt;/span&gt;했다. 이를 위해&lt;b&gt; Deployment 및 SparkApplication YAML에서 이미지 경로를 DockerHub 기준으로 지정&lt;/b&gt;했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;770&quot; data-origin-height=&quot;258&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d98oII/dJMcahxDDtO/qLsBKZFsTGSamCjE4xGGI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d98oII/dJMcahxDDtO/qLsBKZFsTGSamCjE4xGGI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d98oII/dJMcahxDDtO/qLsBKZFsTGSamCjE4xGGI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd98oII%2FdJMcahxDDtO%2FqLsBKZFsTGSamCjE4xGGI1%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;770&quot; height=&quot;258&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;770&quot; data-origin-height=&quot;258&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;1187&quot; data-start=&quot;1174&quot; data-ke-size=&quot;size26&quot;&gt;4. Helm 설치&lt;/h2&gt;
&lt;p data-end=&quot;1236&quot; data-start=&quot;1189&quot; data-ke-size=&quot;size16&quot;&gt;Spark Operator를 Kubernetes에 설치하기 위해 Helm을 사용했다.&lt;/p&gt;
&lt;p data-end=&quot;1305&quot; data-start=&quot;1238&quot; data-ke-size=&quot;size16&quot;&gt;Windows 환경에서는 실행 파일을 직접 설치했지만, &lt;b&gt;Ubuntu 서버에서는 공식 설치 스크립트를 사용해 구성&lt;/b&gt;했다.&lt;/p&gt;
&lt;h4 data-end=&quot;1323&quot; data-start=&quot;1312&quot; data-ke-size=&quot;size20&quot;&gt;4-1) Helm 설치&lt;/h4&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1167&quot; data-origin-height=&quot;187&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vkEq9/dJMcaiDhMtd/xHtxYSHIsBnM8FYzXeGlMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vkEq9/dJMcaiDhMtd/xHtxYSHIsBnM8FYzXeGlMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vkEq9/dJMcaiDhMtd/xHtxYSHIsBnM8FYzXeGlMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvkEq9%2FdJMcaiDhMtd%2FxHtxYSHIsBnM8FYzXeGlMk%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;1167&quot; height=&quot;187&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1167&quot; data-origin-height=&quot;187&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-end=&quot;1444&quot; data-start=&quot;1435&quot; data-ke-size=&quot;size20&quot;&gt;4-2) 설치 확인&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;helm version&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1167&quot; data-origin-height=&quot;100&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b18nnQ/dJMcabKZiSB/ltGD7FOZV8f3pXEtfHRp00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b18nnQ/dJMcabKZiSB/ltGD7FOZV8f3pXEtfHRp00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b18nnQ/dJMcabKZiSB/ltGD7FOZV8f3pXEtfHRp00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb18nnQ%2FdJMcabKZiSB%2FltGD7FOZV8f3pXEtfHRp00%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;1167&quot; height=&quot;100&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1167&quot; data-origin-height=&quot;100&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;1514&quot; data-start=&quot;1484&quot; data-ke-size=&quot;size16&quot;&gt;정상적으로 설치된 경우 Helm 버전 정보가 출력된다.&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-end=&quot;1544&quot; data-start=&quot;1521&quot; data-ke-size=&quot;size26&quot;&gt;5. Spark Operator 설치&lt;/h2&gt;
&lt;p data-end=&quot;1601&quot; data-start=&quot;1546&quot; data-ke-size=&quot;size16&quot;&gt;Spark 작업을 Kubernetes 리소스로 관리하기 위해 &lt;b&gt;Spark Operator를 설치&lt;/b&gt;했다. &lt;b&gt;Spark Operator를 사용하면 Spark 작업을 단순 Pod 실행이 아니라 Kubernetes 리소스 형태로 선언적으로 관리&lt;/b&gt;할 수 있다.&lt;/p&gt;
&lt;h4 data-end=&quot;1707&quot; data-start=&quot;1692&quot; data-ke-size=&quot;size20&quot;&gt;5-1) Helm 저장소 등록&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;helm repo add spark-operator https://kubeflow.github.io/spark-operator
helm repo update&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1196&quot; data-origin-height=&quot;118&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bm1fdJ/dJMcagS1O0N/r8zk7kLKPBeXqVKF6p6dW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bm1fdJ/dJMcagS1O0N/r8zk7kLKPBeXqVKF6p6dW0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bm1fdJ/dJMcagS1O0N/r8zk7kLKPBeXqVKF6p6dW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbm1fdJ%2FdJMcagS1O0N%2Fr8zk7kLKPBeXqVKF6p6dW0%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;1196&quot; height=&quot;118&quot; data-origin-width=&quot;1196&quot; data-origin-height=&quot;118&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot; data-start=&quot;1692&quot; data-end=&quot;1707&quot;&gt;5-2)Spark Operator 설치&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;helm install spark-operator spark-operator/spark-operator \
--namespace spark-operator \
--create-namespace&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;img style=&quot;text-align: center; caret-color: transparent; letter-spacing: 0px;&quot; src=&quot;https://blog.kakaocdn.net/dna/kzjAO/dJMcaf7CnIW/AAAAAAAAAAAAAAAAAAAAANUQTJ2bgZqNPEJ1B8HGiRbT0mfPLWx5kGiYoRWHdJsB/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1780239599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=prHmld3RZMQ008XjHe4%2F8KlnM64%3D&quot; data-origin-width=&quot;1172&quot; data-origin-height=&quot;163&quot; data-is-animation=&quot;false&quot; /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-end=&quot;2009&quot; data-start=&quot;1988&quot; data-ke-size=&quot;size20&quot;&gt;5-3) 왜 Operator를 사용하는가&lt;/h4&gt;
&lt;p data-end=&quot;2025&quot; data-start=&quot;2011&quot; data-ke-size=&quot;size16&quot;&gt;Operator를 사용하면&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2136&quot; data-start=&quot;2027&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2059&quot; data-start=&quot;2027&quot;&gt;SparkApplication 리소스로 작업 정의 가능&lt;/li&gt;
&lt;li data-end=&quot;2085&quot; data-start=&quot;2060&quot;&gt;Driver / Executor 자동 관리&lt;/li&gt;
&lt;li data-end=&quot;2102&quot; data-start=&quot;2086&quot;&gt;재시작 및 상태 관리 가능&lt;/li&gt;
&lt;li data-end=&quot;2136&quot; data-start=&quot;2103&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Spark 작업을 Kubernetes 방식으로 운영 가능&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2151&quot; data-start=&quot;2138&quot; data-ke-size=&quot;size16&quot;&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 data-end=&quot;2176&quot; data-start=&quot;2158&quot; data-ke-size=&quot;size26&quot;&gt;6. Namespace 구성&lt;/h2&gt;
&lt;p data-end=&quot;2216&quot; data-start=&quot;2178&quot; data-ke-size=&quot;size16&quot;&gt;서비스 역할별로 리소스를 분리하기 위해 namespace를 구성했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;kubectl create namespace airflow&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1180&quot; data-origin-height=&quot;147&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNORof/dJMcagS1PfR/EJlcWscbnjd73bYFyvWycK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNORof/dJMcagS1PfR/EJlcWscbnjd73bYFyvWycK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNORof/dJMcagS1PfR/EJlcWscbnjd73bYFyvWycK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNORof%2FdJMcagS1PfR%2FEJlcWscbnjd73bYFyvWycK%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;1180&quot; height=&quot;147&quot; data-origin-width=&quot;1180&quot; data-origin-height=&quot;147&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;style6&quot; /&gt;
&lt;h2 data-end=&quot;2572&quot; data-start=&quot;2557&quot; data-ke-size=&quot;size26&quot;&gt;7. Secret 구성&lt;/h2&gt;
&lt;p data-end=&quot;2628&quot; data-start=&quot;2574&quot; data-ke-size=&quot;size16&quot;&gt;외부 서비스 연결 정보는 코드에 직접 포함하지 않고 Kubernetes Secret으로 분리했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;kubectl create secret generic navisafe-producer-secret \
  --from-literal=KAFKA_BOOTSTRAP=&amp;lt;kafka-bootstrap-server&amp;gt; \
  --from-literal=REDIS_HOST=&amp;lt;redis-host&amp;gt; \
  --from-literal=MYSQL_HOST=&amp;lt;mysql-host&amp;gt; \
  --from-literal=MYSQL_USER=&amp;lt;username&amp;gt; \
  --from-literal=MYSQL_PASSWORD=&amp;lt;password&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2961&quot; data-start=&quot;2943&quot; data-ke-size=&quot;size16&quot;&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 data-end=&quot;3088&quot; data-start=&quot;3070&quot; data-ke-size=&quot;size26&quot;&gt;8. ConfigMap 구성&lt;/h2&gt;
&lt;p data-end=&quot;3123&quot; data-start=&quot;3090&quot; data-ke-size=&quot;size16&quot;&gt;초기 설정 파일은 ConfigMap으로 분리했다.&lt;/p&gt;
&lt;h4 data-end=&quot;3152&quot; data-start=&quot;3130&quot; data-ke-size=&quot;size20&quot;&gt;8-1) SQL 스크립트 ConfigMap&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;kubectl create configmap mysql-init-script --from-file=init.sql&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot; data-start=&quot;3130&quot; data-end=&quot;3152&quot;&gt;8-2) Airflow Pod Template ConfigMap&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;kubectl create configmap airflow-pod-template \
--from-file=airflow-pod-template.yaml \
-n airflow&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot; data-start=&quot;3130&quot; data-end=&quot;3152&quot;&gt;8-3) Spark Streaming 코드 ConfigMap&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spark Streaming 코드 역시 이미지 내부에 직접 포함하지 않고 ConfigMap으로 분리했다&lt;/p&gt;
&lt;pre id=&quot;code_1778569226804&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl create configmap spark-code --from-file=preprocessing/outbreak_streaming.py --from-file=preprocessing/emergency_streaming.py --from-file=utils/ -o yaml --dry-run=client | kubectl apply -f -&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot; data-start=&quot;3130&quot; data-end=&quot;3152&quot;&gt;8-4) 왜 ConfigMap을 사용하는가&lt;/h4&gt;
&lt;p data-end=&quot;3510&quot; data-start=&quot;3464&quot; data-ke-size=&quot;size16&quot;&gt;설정 파일을 이미지 내부에 포함할 경우 설정 변경 시 이미지를 다시 빌드해야 한다.&lt;/p&gt;
&lt;p data-end=&quot;3530&quot; data-start=&quot;3512&quot; data-ke-size=&quot;size16&quot;&gt;반면 ConfigMap을 사용하면&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3579&quot; data-start=&quot;3532&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3546&quot; data-start=&quot;3532&quot;&gt;설정만 별도 수정 가능&lt;/li&gt;
&lt;li data-end=&quot;3564&quot; data-start=&quot;3547&quot;&gt;운영 환경별 설정 분리 가능&lt;/li&gt;
&lt;li data-end=&quot;3579&quot; data-start=&quot;3565&quot;&gt;코드와 설정 분리 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;3594&quot; data-start=&quot;3581&quot; data-ke-size=&quot;size16&quot;&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 data-end=&quot;3624&quot; data-start=&quot;3601&quot; data-ke-size=&quot;size26&quot;&gt;9. ServiceAccount 구성&lt;/h2&gt;
&lt;p data-end=&quot;3683&quot; data-start=&quot;3626&quot; data-ke-size=&quot;size16&quot;&gt;Spark 작업이 Kubernetes 리소스에 접근할 수 있도록 ServiceAccount를 생성했다.&lt;/p&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;kubectl create serviceaccount spark&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #000000; font-size: 1.44em; letter-spacing: -1px;&quot;&gt;9-1) 왜 필요한가&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3814&quot; data-start=&quot;3775&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Spark Driver Pod&lt;/b&gt;&lt;/span&gt;는 Kubernetes API와 통신하면서&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3852&quot; data-start=&quot;3816&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3829&quot; data-start=&quot;3816&quot;&gt;Executor 생성&lt;/li&gt;
&lt;li data-end=&quot;3841&quot; data-start=&quot;3830&quot;&gt;Pod 상태 조회&lt;/li&gt;
&lt;li data-end=&quot;3852&quot; data-start=&quot;3842&quot;&gt;작업 상태 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;3865&quot; data-start=&quot;3854&quot; data-ke-size=&quot;size16&quot;&gt;등 작업을 수행한다. 따라서 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Kubernetes 리소스 접근 권한이 필요&lt;/b&gt;&lt;/span&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 data-end=&quot;3933&quot; data-start=&quot;3905&quot; data-ke-size=&quot;size26&quot;&gt;10. StorageClass 및 PVC 확인&lt;/h2&gt;
&lt;p data-end=&quot;3980&quot; data-start=&quot;3935&quot; data-ke-size=&quot;size16&quot;&gt;k3s에서는 기본적으로 local-path StorageClass를 제공한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;kubectl get storageclass&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4038&quot; data-start=&quot;4032&quot; data-ke-size=&quot;size16&quot;&gt;확인 결과:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;56&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/poRII/dJMcad20oi6/bOf9ZH5kxV0GOCfn47Gvy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/poRII/dJMcad20oi6/bOf9ZH5kxV0GOCfn47Gvy0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/poRII/dJMcad20oi6/bOf9ZH5kxV0GOCfn47Gvy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpoRII%2FdJMcad20oi6%2FbOf9ZH5kxV0GOCfn47Gvy0%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;1170&quot; height=&quot;56&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;56&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-end=&quot;4101&quot; data-start=&quot;4091&quot; data-ke-size=&quot;size20&quot;&gt;10-1) 왜 중요한가&lt;/h4&gt;
&lt;p data-end=&quot;4148&quot; data-start=&quot;4103&quot; data-ke-size=&quot;size16&quot;&gt;NaviSafe에서는 다음 서비스들이 &lt;b&gt;Persistent Volume을 사용&lt;/b&gt;한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;4200&quot; data-start=&quot;4150&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;4162&quot; data-start=&quot;4150&quot;&gt;PostgreSQL&lt;/li&gt;
&lt;li data-end=&quot;4170&quot; data-start=&quot;4163&quot;&gt;Kafka&lt;/li&gt;
&lt;li data-end=&quot;4184&quot; data-start=&quot;4171&quot;&gt;Redis 일부 구성&lt;/li&gt;
&lt;li data-end=&quot;4200&quot; data-start=&quot;4185&quot;&gt;Airflow 로그 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;4233&quot; data-start=&quot;4202&quot; data-ke-size=&quot;size16&quot;&gt;따라서 PVC가 정상적으로 생성 가능한 환경이 필요했다.&lt;b&gt; k3s는 기본적으로 local-path-provisioner를 포함&lt;/b&gt;하고 있기 때문에&lt;br /&gt;&lt;b&gt;별도 Storage Provisioner 설치 없이 로컬 디스크 기반 PVC를 사용&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 data-end=&quot;4355&quot; data-start=&quot;4345&quot; data-ke-size=&quot;size26&quot;&gt;11. 마치며&lt;/h2&gt;
&lt;p data-end=&quot;4422&quot; data-start=&quot;4357&quot; data-ke-size=&quot;size16&quot;&gt;이번 단계에서는 실제 서비스를 바로 배포하기보다, Kubernetes 운영에 필요한 공통 구성 요소를 먼저 준비했다.&lt;/p&gt;
&lt;p data-end=&quot;4448&quot; data-start=&quot;4424&quot; data-ke-size=&quot;size16&quot;&gt;특히 서버 환경에서는 단순 컨테이너 실행보다&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;4495&quot; data-start=&quot;4450&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;4461&quot; data-start=&quot;4450&quot;&gt;이미지 관리 방식&lt;/li&gt;
&lt;li data-end=&quot;4469&quot; data-start=&quot;4462&quot;&gt;설정 분리&lt;/li&gt;
&lt;li data-end=&quot;4477&quot; data-start=&quot;4470&quot;&gt;권한 관리&lt;/li&gt;
&lt;li data-end=&quot;4486&quot; data-start=&quot;4478&quot;&gt;저장소 구성&lt;/li&gt;
&lt;li data-end=&quot;4495&quot; data-start=&quot;4487&quot;&gt;서비스 분리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;4524&quot; data-start=&quot;4497&quot; data-ke-size=&quot;size16&quot;&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 data-end=&quot;4628&quot; data-start=&quot;4621&quot; data-ke-size=&quot;size26&quot;&gt;다음 글&lt;/h2&gt;
&lt;p data-end=&quot;4675&quot; data-start=&quot;4630&quot; data-ke-size=&quot;size16&quot;&gt;다음 단계에서는 실제 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;NaviSafe 서비스를 k3s 클러스터에 배포&lt;/b&gt;&lt;/span&gt;할 예정이다.&lt;/p&gt;
&lt;p data-end=&quot;4682&quot; data-start=&quot;4677&quot; data-ke-size=&quot;size16&quot;&gt;이를 위해&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;4773&quot; data-start=&quot;4684&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;4715&quot; data-start=&quot;4684&quot;&gt;PostgreSQL / Redis / Kafka 배포&lt;/li&gt;
&lt;li data-end=&quot;4736&quot; data-start=&quot;4716&quot;&gt;Spark Streaming 실행&lt;/li&gt;
&lt;li data-end=&quot;4749&quot; data-start=&quot;4737&quot;&gt;Airflow 연결&lt;/li&gt;
&lt;li data-end=&quot;4773&quot; data-start=&quot;4750&quot;&gt;Backend / Frontend 배포&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;4834&quot; data-start=&quot;4775&quot; data-ke-size=&quot;size16&quot;&gt;과정을 순차적으로 진행하며, 실제 서버 환경에서 데이터 파이프라인이 정상적으로 동작하는지 검증할 예정이다.&lt;/p&gt;</description>
      <category>Navisafe/Infrastructure</category>
      <author>jjaehyeok</author>
      <guid isPermaLink="true">https://jjaehyeok.tistory.com/72</guid>
      <comments>https://jjaehyeok.tistory.com/72#entry72comment</comments>
      <pubDate>Tue, 12 May 2026 15:17:11 +0900</pubDate>
    </item>
    <item>
      <title>미니 PC 서버에 k3s 기반 Kubernetes 운영 환경 구성</title>
      <link>https://jjaehyeok.tistory.com/71</link>
      <description>&lt;h2 data-end=&quot;57&quot; data-start=&quot;46&quot; data-ke-size=&quot;size26&quot;&gt;1. 문제 배경&lt;/h2&gt;
&lt;p data-end=&quot;163&quot; data-start=&quot;59&quot; data-ke-size=&quot;size16&quot;&gt;로컬 Kubernetes 환경(kind)에서 데이터 파이프라인과 공간 데이터 처리 기능 검증을 완료한 이후, 이제&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; 실제 서버 환경에서 장시간 실행 가능한 구조로 전환할 필요&lt;/b&gt;&lt;/span&gt;가 있었다.&lt;/p&gt;
&lt;p data-end=&quot;189&quot; data-start=&quot;165&quot; data-ke-size=&quot;size16&quot;&gt;로컬 환경에서는 다음과 같은 한계가 있었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;263&quot; data-start=&quot;191&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;207&quot; data-start=&quot;191&quot;&gt;PC 종료 시 서비스 중단&lt;/li&gt;
&lt;li data-end=&quot;223&quot; data-start=&quot;208&quot;&gt;외부 네트워크 접근 제한&lt;/li&gt;
&lt;li data-end=&quot;244&quot; data-start=&quot;224&quot;&gt;장시간 스트리밍 처리 운영 어려움&lt;/li&gt;
&lt;li data-end=&quot;263&quot; data-start=&quot;245&quot;&gt;실제 서비스 환경과 차이 존재&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;338&quot; data-start=&quot;265&quot; data-ke-size=&quot;size16&quot;&gt;따라서 항상 실행 가능한&lt;b&gt; 미니 PC 서버에 SSH로 접속&lt;/b&gt;해 NaviSafe 서비스를 직접 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;배포하고 운영 환경을 구성&lt;/b&gt;&lt;/span&gt;하기로 했다.&lt;/p&gt;
&lt;p data-end=&quot;299&quot; data-start=&quot;226&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;376&quot; data-start=&quot;301&quot; data-ke-size=&quot;size16&quot;&gt;또한 이후 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;AWS 기반 클라우드 환경으로 확장&lt;/b&gt;&lt;/span&gt;하기 전에, 우선 &lt;b&gt;미니 PC 서버에서 실제 배포 및 운영 과정을 먼저 &lt;span style=&quot;color: #ee2323;&quot;&gt;검증&lt;/span&gt;&lt;/b&gt;할 예정이다.&lt;/p&gt;
&lt;p data-end=&quot;383&quot; data-start=&quot;378&quot; data-ke-size=&quot;size16&quot;&gt;이를 통해&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;476&quot; data-start=&quot;385&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;409&quot; data-start=&quot;385&quot;&gt;Kubernetes 기반 서비스 운영&lt;/li&gt;
&lt;li data-end=&quot;432&quot; data-start=&quot;410&quot;&gt;장시간 스트리밍 처리 안정성 확인&lt;/li&gt;
&lt;li data-end=&quot;453&quot; data-start=&quot;433&quot;&gt;네트워크 및 서비스 연결 검증&lt;/li&gt;
&lt;li data-end=&quot;476&quot; data-start=&quot;454&quot;&gt;서버 환경에서의 리소스 사용 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;514&quot; data-start=&quot;478&quot; data-ke-size=&quot;size16&quot;&gt;과정을 사전에 점검하고, &lt;b&gt;이후 클라우드 환경으로 확장할 계획&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 data-end=&quot;353&quot; data-start=&quot;345&quot; data-ke-size=&quot;size26&quot;&gt;2. 목표&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;434&quot; data-start=&quot;355&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;372&quot; data-start=&quot;355&quot;&gt;미니 PC 서버 SSH 접속&lt;/li&gt;
&lt;li data-end=&quot;391&quot; data-start=&quot;373&quot;&gt;Kubernetes 환경 구성&lt;/li&gt;
&lt;li data-end=&quot;409&quot; data-start=&quot;392&quot;&gt;NaviSafe 서비스 배포&lt;/li&gt;
&lt;li data-end=&quot;434&quot; data-start=&quot;410&quot;&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-end=&quot;27&quot; data-start=&quot;0&quot; data-ke-size=&quot;size26&quot;&gt;3. kubectl 설치 및 서버 환경 구성&lt;/h2&gt;
&lt;p data-end=&quot;109&quot; data-start=&quot;29&quot; data-ke-size=&quot;size16&quot;&gt;미니 PC 서버에 SSH로 접속한 이후, 먼저 Kubernetes 클러스터를 제어하기 위한 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;CLI 도구인 kubectl 설치를 진행&lt;/b&gt;&lt;/span&gt;했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1004&quot; data-start=&quot;977&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;1031&quot; data-start=&quot;1011&quot; data-ke-size=&quot;size20&quot;&gt;3-1) Kubernetes 저장소 등록&lt;/h4&gt;
&lt;p data-end=&quot;1060&quot; data-start=&quot;1033&quot; data-ke-size=&quot;size16&quot;&gt;이후 Kubernetes 공식 저장소를 등록했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;sudo apt update
sudo apt install -y apt-transport-https ca-certificates curl

curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.30/deb/Release.key | \
sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg

echo &quot;deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] \
https://pkgs.k8s.io/core:/stable:/v1.30/deb/ /&quot; | \
sudo tee /etc/apt/sources.list.d/kubernetes.list&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-end=&quot;1540&quot; data-start=&quot;1527&quot; data-ke-size=&quot;size20&quot;&gt;3-2) kubectl 설치&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;sudo apt update
sudo apt install -y kubectl&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1626&quot; data-start=&quot;1611&quot; data-ke-size=&quot;size16&quot;&gt;설치 이후 버전을 확인했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;kubectl version --client&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;693&quot; data-origin-height=&quot;53&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n0Kwj/dJMcagepKZx/NWOzjkbf4btnMwU9ZC3OF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n0Kwj/dJMcagepKZx/NWOzjkbf4btnMwU9ZC3OF0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n0Kwj/dJMcagepKZx/NWOzjkbf4btnMwU9ZC3OF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn0Kwj%2FdJMcagepKZx%2FNWOzjkbf4btnMwU9ZC3OF0%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;693&quot; height=&quot;53&quot; data-origin-width=&quot;693&quot; data-origin-height=&quot;53&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;1784&quot; data-start=&quot;1728&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이를 통해 미니 PC 서버에서도 Kubernetes 클러스터를 직접 제어할 수 있는 환경을 구성했다.&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 data-end=&quot;31&quot; data-start=&quot;0&quot; data-ke-size=&quot;size26&quot;&gt;4. k3s 기반 Kubernetes 클러스터 구성&lt;/h2&gt;
&lt;p data-end=&quot;128&quot; data-start=&quot;33&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;로컬 환경에서는 kind 기반으로 Kubernetes 구성을 테스트&lt;/b&gt;했지만, 미니 PC 서버에서는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;장시간 실행과 실제 운영 환경 구성&lt;/b&gt;&lt;/span&gt;을 고려해 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;k3s 기반으로 전환&lt;/b&gt;&lt;/span&gt;했다.&lt;/p&gt;
&lt;p data-end=&quot;232&quot; data-start=&quot;130&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;kind&lt;/b&gt;는 Docker 컨테이너 내부에서 Kubernetes 클러스터를 실행하는 구조이기 때문에 &lt;b&gt;로컬 테스트에는 적합&lt;/b&gt;하지만, 서버 환경에서 상시 운영하기에는 다소 무거운 구조였다.&lt;/p&gt;
&lt;p data-end=&quot;261&quot; data-start=&quot;234&quot; data-ke-size=&quot;size16&quot;&gt;반면 &lt;b&gt;k3s는 경량 Kubernetes 배포판&lt;/b&gt;으로&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;344&quot; data-start=&quot;263&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;280&quot; data-start=&quot;263&quot;&gt;단일 서버 환경 운영에 적합&lt;/li&gt;
&lt;li data-end=&quot;298&quot; data-start=&quot;281&quot;&gt;리소스 사용량이 비교적 작음&lt;/li&gt;
&lt;li data-end=&quot;319&quot; data-start=&quot;299&quot;&gt;서버 재부팅 이후 자동 복구 가능&lt;/li&gt;
&lt;li data-end=&quot;344&quot; data-start=&quot;320&quot;&gt;실제 운영 환경과 유사한 구조 구성 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;380&quot; data-start=&quot;346&quot; data-ke-size=&quot;size16&quot;&gt;장점이 있었기 때문에 &lt;b&gt;미니 PC 환경에서는 &lt;span style=&quot;color: #ee2323;&quot;&gt;k3s&lt;/span&gt;를 선택&lt;/b&gt;했다.&lt;/p&gt;
&lt;h2 data-end=&quot;396&quot; data-start=&quot;387&quot; data-ke-size=&quot;size26&quot;&gt;4-1) k3s 설치&lt;/h2&gt;
&lt;p data-end=&quot;423&quot; data-start=&quot;398&quot; data-ke-size=&quot;size16&quot;&gt;공식 설치 스크립트를 통해 k3s를 설치했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;curl -sfL https://get.k3s.io | sh -&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1042&quot; data-origin-height=&quot;335&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUU67e/dJMcafs09iI/R68e0kRrI4phmyxAdAy6gK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUU67e/dJMcafs09iI/R68e0kRrI4phmyxAdAy6gK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUU67e/dJMcafs09iI/R68e0kRrI4phmyxAdAy6gK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUU67e%2FdJMcafs09iI%2FR68e0kRrI4phmyxAdAy6gK%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;1042&quot; height=&quot;335&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1042&quot; data-origin-height=&quot;335&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;509&quot; data-start=&quot;486&quot; data-ke-size=&quot;size16&quot;&gt;설치 이후 k3s 서비스 상태를 확인했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;sudo systemctl status k3s&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1042&quot; data-origin-height=&quot;128&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AWbG7/dJMb990GrED/99fa99D6Znm7pmrc1Jl3Z0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AWbG7/dJMb990GrED/99fa99D6Znm7pmrc1Jl3Z0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AWbG7/dJMb990GrED/99fa99D6Znm7pmrc1Jl3Z0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAWbG7%2FdJMb990GrED%2F99fa99D6Znm7pmrc1Jl3Z0%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;1042&quot; height=&quot;128&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1042&quot; data-origin-height=&quot;128&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;active(running)&amp;nbsp; 정상 확인&lt;/b&gt;&lt;/span&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 data-end=&quot;26&quot; data-start=&quot;0&quot; data-ke-size=&quot;size26&quot;&gt;5. kubectl과 k3s 클러스터 연결&lt;/h2&gt;
&lt;h4 data-end=&quot;66&quot; data-start=&quot;28&quot; data-ke-size=&quot;size20&quot;&gt;5-1) k3s 설치 이후 노드 상태를 확인&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;kubectl get nodes&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1251&quot; data-origin-height=&quot;200&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/v5g55/dJMcah5tkf5/FLxevRoyhDr5rv7QvrrHK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/v5g55/dJMcah5tkf5/FLxevRoyhDr5rv7QvrrHK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/v5g55/dJMcah5tkf5/FLxevRoyhDr5rv7QvrrHK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fv5g55%2FdJMcah5tkf5%2FFLxevRoyhDr5rv7QvrrHK0%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;1251&quot; height=&quot;200&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1251&quot; data-origin-height=&quot;200&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;131&quot; data-start=&quot;111&quot; data-ke-size=&quot;size16&quot;&gt;하지만 다음과 같은 오류가 발생했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;The connection to the server localhost:8080 was refused&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;318&quot; data-start=&quot;214&quot; data-ke-size=&quot;size16&quot;&gt;이는 Kubernetes 클러스터 문제가 아니라, 일반 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;kubectl이 k3s의 kubeconfig 파일을 찾지 못해&lt;/span&gt; 기본 주소(localhost:8080)로 접근한 상태였다.&lt;/b&gt;&lt;/p&gt;
&lt;h4 data-end=&quot;345&quot; data-start=&quot;325&quot; data-ke-size=&quot;size20&quot;&gt;5-2) k3s 전용 kubectl 확인&lt;/h4&gt;
&lt;p data-end=&quot;384&quot; data-start=&quot;347&quot; data-ke-size=&quot;size16&quot;&gt;먼저 k3s 내부 kubectl을 사용해 클러스터 상태를 확인했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;sudo k3s kubectl get nodes&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1262&quot; data-origin-height=&quot;100&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhcdbd/dJMcacpx1nn/ykW0PB2cQw3kRNapmufUdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhcdbd/dJMcacpx1nn/ykW0PB2cQw3kRNapmufUdK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhcdbd/dJMcacpx1nn/ykW0PB2cQw3kRNapmufUdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbhcdbd%2FdJMcacpx1nn%2FykW0PB2cQw3kRNapmufUdK%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;1262&quot; height=&quot;100&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1262&quot; data-origin-height=&quot;100&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&lt;b&gt;정상적으로 노드가 조회되는 것을 확인&lt;/b&gt;했다. &lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;이를 통해 &lt;b&gt;k3s 클러스터 자체는 정상적으로 실행 중인 상태임을 확인&lt;/b&gt;했다.&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-end=&quot;594&quot; data-start=&quot;578&quot; data-ke-size=&quot;size20&quot;&gt;5-3) kubeconfig 연결&lt;/h4&gt;
&lt;p data-end=&quot;669&quot; data-start=&quot;596&quot; data-ke-size=&quot;size16&quot;&gt;이후 일반 kubectl에서도 동일하게 클러스터를 제어할 수 있도록 &lt;b&gt;k3s kubeconfig 파일을 사용자 환경으로 복사&lt;/b&gt;했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;mkdir -p ~/.kube
sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
sudo chown $USER:$USER ~/.kube/config
chmod 600 ~/.kube/config&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1262&quot; data-origin-height=&quot;100&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pjO6R/dJMcahRTkCV/YntgTjRtD3jyXVRuAUZU5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pjO6R/dJMcahRTkCV/YntgTjRtD3jyXVRuAUZU5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pjO6R/dJMcahRTkCV/YntgTjRtD3jyXVRuAUZU5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpjO6R%2FdJMcahRTkCV%2FYntgTjRtD3jyXVRuAUZU5K%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;1262&quot; height=&quot;100&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1262&quot; data-origin-height=&quot;100&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;h4 data-ke-size=&quot;size20&quot;&gt;5-4) 연결 확인&lt;/h4&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;930&quot; data-start=&quot;915&quot; data-ke-size=&quot;size16&quot;&gt;다시 노드 상태를 확인했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;kubectl get nodes&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;632&quot; data-origin-height=&quot;43&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cq99Dm/dJMcahqRONI/psA43TqnGOVICsl6rkWiu1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cq99Dm/dJMcahqRONI/psA43TqnGOVICsl6rkWiu1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cq99Dm/dJMcahqRONI/psA43TqnGOVICsl6rkWiu1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcq99Dm%2FdJMcahqRONI%2FpsA43TqnGOVICsl6rkWiu1%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;632&quot; height=&quot;43&quot; data-origin-width=&quot;632&quot; data-origin-height=&quot;43&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;1002&quot; data-start=&quot;975&quot; data-ke-size=&quot;size16&quot;&gt;정상적으로 k3s 노드가 조회되는 것을 확인했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-end=&quot;1002&quot; data-start=&quot;975&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-size: 1.62em; letter-spacing: -1px;&quot;&gt;왜 필요한가&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;1143&quot; data-start=&quot;1086&quot; data-ke-size=&quot;size16&quot;&gt;k3s는 자체 kubeconfig를 /etc/rancher/k3s/k3s.yaml 경로에 생성한다.&lt;/p&gt;
&lt;p data-end=&quot;1179&quot; data-start=&quot;1145&quot; data-ke-size=&quot;size16&quot;&gt;하지만 일반 kubectl은 기본적으로 다음 경로를 참조한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;arcade&quot;&gt;&lt;code&gt;~/.kube/config&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1285&quot; data-start=&quot;1221&quot; data-ke-size=&quot;size16&quot;&gt;따라서 &lt;b&gt;kubeconfig를 사용자 환경으로 복사하지 않으면 kubectl이 클러스터 정보를 찾지 못하게 된다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;1364&quot; data-start=&quot;1303&quot; data-ke-size=&quot;size16&quot;&gt;이번 과정은 단순 파일 복사가 아니라, &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;k3s 클러스터와 일반 kubectl CLI를 연결하는 작업&lt;/b&gt;&lt;/span&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 data-ke-size=&quot;size26&quot;&gt;6. k3s 기본 리소스 확인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;k3s 설치 및 kubectl 연결 이후, &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;클러스터 내부 기본 리소스가 정상적으로 실행 중인지 확인&lt;/b&gt;&lt;/span&gt;했다.&lt;/p&gt;
&lt;pre id=&quot;code_1778498041323&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl get pods -A
kubectl get svc -A
kubectl get storageclass&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1082&quot; data-origin-height=&quot;161&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEQaR5/dJMcaaZxUi9/ZvQCa5VPufyxQSOdq9hpT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEQaR5/dJMcaaZxUi9/ZvQCa5VPufyxQSOdq9hpT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEQaR5/dJMcaaZxUi9/ZvQCa5VPufyxQSOdq9hpT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEQaR5%2FdJMcaaZxUi9%2FZvQCa5VPufyxQSOdq9hpT0%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;1082&quot; height=&quot;161&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1082&quot; data-origin-height=&quot;161&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-end=&quot;287&quot; data-start=&quot;271&quot; data-ke-size=&quot;size20&quot;&gt;6-1) Pod 상태 확인&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;kubectl get pods -A&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;370&quot; data-start=&quot;334&quot; data-ke-size=&quot;size16&quot;&gt;k3s 설치 직후 &lt;b&gt;기본적으로 생성되는 시스템 Pod들을 확인&lt;/b&gt;했다.&lt;/p&gt;
&lt;p data-end=&quot;378&quot; data-start=&quot;372&quot; data-ke-size=&quot;size16&quot;&gt;확인 결과:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;441&quot; data-start=&quot;380&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;389&quot; data-start=&quot;380&quot;&gt;CoreDNS&lt;/li&gt;
&lt;li data-end=&quot;406&quot; data-start=&quot;390&quot;&gt;metrics-server&lt;/li&gt;
&lt;li data-end=&quot;431&quot; data-start=&quot;407&quot;&gt;local-path-provisioner&lt;/li&gt;
&lt;li data-end=&quot;441&quot; data-start=&quot;432&quot;&gt;traefik&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;483&quot; data-start=&quot;443&quot; data-ke-size=&quot;size16&quot;&gt;등 주요 시스템 리소스가 정상적으로 Running 상태인 것을 확인했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;501&quot; data-start=&quot;490&quot; data-ke-size=&quot;size23&quot;&gt;왜 확인하는가&lt;/h3&gt;
&lt;p data-end=&quot;548&quot; data-start=&quot;503&quot; data-ke-size=&quot;size16&quot;&gt;이 리소스들은 Kubernetes 클러스터의 기본 동작에 필요한 구성 요소들이다.&lt;/p&gt;
&lt;p data-end=&quot;555&quot; data-start=&quot;550&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;677&quot; data-start=&quot;557&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;581&quot; data-start=&quot;557&quot;&gt;CoreDNS &amp;rarr; Pod 간 DNS 통신&lt;/li&gt;
&lt;li data-end=&quot;609&quot; data-start=&quot;582&quot;&gt;metrics-server &amp;rarr; 리소스 모니터링&lt;/li&gt;
&lt;li data-end=&quot;646&quot; data-start=&quot;610&quot;&gt;local-path-provisioner &amp;rarr; PVC 동적 생성&lt;/li&gt;
&lt;li data-end=&quot;677&quot; data-start=&quot;647&quot;&gt;traefik &amp;rarr; Ingress 및 외부 접근 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;688&quot; data-start=&quot;679&quot; data-ke-size=&quot;size16&quot;&gt;역할을 담당한다. 즉, 이 리소스들이 정상 동작해야 이후 NaviSafe 서비스도 안정적으로 배포할 수 있다.&lt;/p&gt;
&lt;h4 data-end=&quot;765&quot; data-start=&quot;748&quot; data-ke-size=&quot;size20&quot;&gt;6-2) Service 확인&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;kubectl get svc -A&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;832&quot; data-start=&quot;811&quot; data-ke-size=&quot;size16&quot;&gt;클러스터 내부 서비스 상태를 확인했다. 특히 traefik 서비스가 LoadBalancer 형태로 생성된 것을 확인했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1082&quot; data-origin-height=&quot;111&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bt2lHk/dJMcacpx1Ys/5mOqyJWFUWawtm8k9DkKB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bt2lHk/dJMcacpx1Ys/5mOqyJWFUWawtm8k9DkKB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bt2lHk/dJMcacpx1Ys/5mOqyJWFUWawtm8k9DkKB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbt2lHk%2FdJMcacpx1Ys%2F5mOqyJWFUWawtm8k9DkKB1%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;1082&quot; height=&quot;111&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1082&quot; data-origin-height=&quot;111&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;이를 통해 이후 &lt;b&gt;외부 네트워크 접근 구성이 가능한 상태임을 확인&lt;/b&gt;했다.&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-end=&quot;996&quot; data-start=&quot;974&quot; data-ke-size=&quot;size20&quot;&gt;6-3) StorageClass 확인&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;kubectl get storageclass&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1082&quot; data-origin-height=&quot;56&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dspzmm/dJMcai4iogr/QcPa8ZEhDKA2LckovY38f0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dspzmm/dJMcai4iogr/QcPa8ZEhDKA2LckovY38f0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dspzmm/dJMcai4iogr/QcPa8ZEhDKA2LckovY38f0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdspzmm%2FdJMcai4iogr%2FQcPa8ZEhDKA2LckovY38f0%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;1082&quot; height=&quot;56&quot; data-origin-width=&quot;1082&quot; data-origin-height=&quot;56&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1097&quot; data-start=&quot;1048&quot; data-ke-size=&quot;size16&quot;&gt;확인 결과 기본 StorageClass로 local-path가 설정된 것을 확인했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;1160&quot; data-start=&quot;1150&quot; data-ke-size=&quot;size23&quot;&gt;왜 중요한가&lt;/h3&gt;
&lt;p data-end=&quot;1207&quot; data-start=&quot;1162&quot; data-ke-size=&quot;size16&quot;&gt;NaviSafe에서는 다음 서비스들이&lt;b&gt; Persistent Volume을 사용&lt;/b&gt;한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1259&quot; data-start=&quot;1209&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1221&quot; data-start=&quot;1209&quot;&gt;PostgreSQL&lt;/li&gt;
&lt;li data-end=&quot;1229&quot; data-start=&quot;1222&quot;&gt;Kafka&lt;/li&gt;
&lt;li data-end=&quot;1243&quot; data-start=&quot;1230&quot;&gt;Redis 일부 구성&lt;/li&gt;
&lt;li data-end=&quot;1259&quot; data-start=&quot;1244&quot;&gt;Airflow 로그 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1308&quot; data-start=&quot;1261&quot; data-ke-size=&quot;size16&quot;&gt;따라서 &lt;b&gt;PVC가 정상적으로 생성될 수 있는 StorageClass가 반드시 필요&lt;/b&gt;했다. k3s에서는 기본적으로 local-path-provisioner를 제공하기 때문에 별도 스토리지 설정 없이도 로컬 디스크 기반 PVC를 사용할 수 있었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-end=&quot;1440&quot; data-start=&quot;1418&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;이번 단계에서는 단순 설치 확인이 아니라&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1471&quot; data-start=&quot;1442&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1447&quot; data-start=&quot;1442&quot;&gt;DNS&lt;/li&gt;
&lt;li data-end=&quot;1454&quot; data-start=&quot;1448&quot;&gt;네트워크&lt;/li&gt;
&lt;li data-end=&quot;1461&quot; data-start=&quot;1455&quot;&gt;스토리지&lt;/li&gt;
&lt;li data-end=&quot;1471&quot; data-start=&quot;1462&quot;&gt;시스템 Pod&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1523&quot; data-start=&quot;1473&quot; data-ke-size=&quot;size16&quot;&gt;등 Kubernetes 기본 구성 요소가 정상적으로 동작하는지를 검증했다는 점이 중요했다. 이를 통해 이후 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;NaviSafe 서비스를 실제로 배포할 수 있는 클러스터 상태를 확인했다.&lt;/b&gt;&lt;/span&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 data-end=&quot;9&quot; data-start=&quot;0&quot; data-ke-size=&quot;size26&quot;&gt;7. 마치며&lt;/h2&gt;
&lt;p data-end=&quot;75&quot; data-start=&quot;11&quot; data-ke-size=&quot;size16&quot;&gt;이번 과정에서는 &lt;b&gt;로컬 kind 환경에서 검증했던 Kubernetes 구성을 실제 미니 PC 서버 환경으로 확장&lt;/b&gt;했다.&lt;/p&gt;
&lt;p data-end=&quot;106&quot; data-start=&quot;77&quot; data-ke-size=&quot;size16&quot;&gt;단순히 Kubernetes를 설치하는 수준이 아니라,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;209&quot; data-start=&quot;108&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;122&quot; data-start=&quot;108&quot;&gt;SSH 기반 서버 접근&lt;/li&gt;
&lt;li data-end=&quot;150&quot; data-start=&quot;123&quot;&gt;Docker 리소스 정리 및 디스크 문제 해결&lt;/li&gt;
&lt;li data-end=&quot;163&quot; data-start=&quot;151&quot;&gt;kubectl 설치&lt;/li&gt;
&lt;li data-end=&quot;177&quot; data-start=&quot;164&quot;&gt;k3s 클러스터 구성&lt;/li&gt;
&lt;li data-end=&quot;193&quot; data-start=&quot;178&quot;&gt;kubeconfig 연결&lt;/li&gt;
&lt;li data-end=&quot;209&quot; data-start=&quot;194&quot;&gt;기본 시스템 리소스 검증&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;256&quot; data-start=&quot;211&quot; data-ke-size=&quot;size16&quot;&gt;과정을 순차적으로 진행하며 실제 운영 가능한 Kubernetes 환경을 준비했다.&lt;/p&gt;
&lt;p data-end=&quot;285&quot; data-start=&quot;258&quot; data-ke-size=&quot;size16&quot;&gt;특히 로컬 테스트 환경에서는 크게 드러나지 않았던&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;350&quot; data-start=&quot;287&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;299&quot; data-start=&quot;287&quot;&gt;디스크 사용량 관리&lt;/li&gt;
&lt;li data-end=&quot;319&quot; data-start=&quot;300&quot;&gt;Kubernetes 저장소 설정&lt;/li&gt;
&lt;li data-end=&quot;335&quot; data-start=&quot;320&quot;&gt;kubeconfig 연결&lt;/li&gt;
&lt;li data-end=&quot;350&quot; data-start=&quot;336&quot;&gt;서버 환경 리소스 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;422&quot; data-start=&quot;352&quot; data-ke-size=&quot;size16&quot;&gt;문제를 직접 확인하면서, 운영 환경에서는 단순 서비스 실행 외에도 인프라 상태를 함께 관리해야 한다는 점을 확인할 수 있었다.&lt;/p&gt;
&lt;p data-end=&quot;534&quot; data-start=&quot;424&quot; data-ke-size=&quot;size16&quot;&gt;또한&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt; k3s 기반으로 전환하면서&lt;/span&gt; 단일 서버 환경에서도 비교적 가벼운 Kubernetes 구성이 가능&lt;/b&gt;했고,&lt;br /&gt;이후 &lt;b&gt;NaviSafe 서비스를 장시간 실행할 수 있는 기반 환경을 마련&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 data-end=&quot;548&quot; data-start=&quot;541&quot; data-ke-size=&quot;size26&quot;&gt;다음 글&lt;/h2&gt;
&lt;p data-end=&quot;597&quot; data-start=&quot;550&quot; data-ke-size=&quot;size16&quot;&gt;다음 단계에서는 실제 &lt;b&gt;NaviSafe 서비스를 k3s 클러스터 위에 배포할 예정&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-end=&quot;604&quot; data-start=&quot;599&quot; data-ke-size=&quot;size16&quot;&gt;이를 위해&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;725&quot; data-start=&quot;606&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;620&quot; data-start=&quot;606&quot;&gt;Namespace 구성&lt;/li&gt;
&lt;li data-end=&quot;644&quot; data-start=&quot;621&quot;&gt;Secret / ConfigMap 적용&lt;/li&gt;
&lt;li data-end=&quot;676&quot; data-start=&quot;645&quot;&gt;PostgreSQL / Redis / Kafka 배포&lt;/li&gt;
&lt;li data-end=&quot;697&quot; data-start=&quot;677&quot;&gt;Spark Streaming 연결&lt;/li&gt;
&lt;li data-end=&quot;725&quot; data-start=&quot;698&quot;&gt;Backend / Frontend 서비스 배포&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;793&quot; data-start=&quot;727&quot; data-ke-size=&quot;size16&quot;&gt;등의 과정을 순차적으로 진행하며, 로컬 테스트 환경에서 검증했던 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;데이터 파이프라인을 실제 서버 환경에서 실행할 예정&lt;/b&gt;&lt;/span&gt;이다.&lt;/p&gt;</description>
      <category>Navisafe/Infrastructure</category>
      <author>jjaehyeok</author>
      <guid isPermaLink="true">https://jjaehyeok.tistory.com/71</guid>
      <comments>https://jjaehyeok.tistory.com/71#entry71comment</comments>
      <pubDate>Mon, 11 May 2026 20:27:55 +0900</pubDate>
    </item>
    <item>
      <title>프로그래머스 Lv.2 | Python | 배달</title>
      <link>https://jjaehyeok.tistory.com/70</link>
      <description>&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;1. 문제 정보&lt;/h2&gt;
&lt;figure id=&quot;og_1778463959812&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;프로그래머스&quot; data-og-description=&quot;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/12978&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bxKXw5/dJMb8XSagOp/JKBrzNEguvBEKDBq1krBHK/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/n4z8T/dJMb9hC5JyL/irK00waQnM3k6ZDE4aChJk/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/12978&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/12978&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bxKXw5/dJMb8XSagOp/JKBrzNEguvBEKDBq1krBHK/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/n4z8T/dJMb9hC5JyL/irK00waQnM3k6ZDE4aChJk/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960');&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;프로그래머스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;난이도: Lv.2&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;2. 문제 설명&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;N개의 마을로 이루어진 나라가 있습니다. 이 나라의 각 마을에는 1부터 N까지의 번호가 각각 하나씩 부여되어 있습니다. 각 마을은 양방향으로 통행할 수 있는 도로로 연결되어 있는데, 서로 다른 마을 간에 이동할 때는 이 도로를 지나야 합니다. &lt;br /&gt;&lt;br /&gt;도로를 지날 때 걸리는 시간은 도로별로 다릅니다. 현재 1번 마을에 있는 음식점에서 각 마을로 음식 배달을 하려고 합니다. 각 마을로부터 음식 주문을 받으려고 하는데, N개의 마을 중에서 K 시간 이하로 배달이 가능한 마을에서만 주문을 받으려고 합니다. 다음은 N = 5, K = 3인 경우의 예시입니다.&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;335&quot; data-origin-height=&quot;287&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wjGVA/dJMcahdiO25/1mLPizqqBUWoKgb7M5ONg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wjGVA/dJMcahdiO25/1mLPizqqBUWoKgb7M5ONg0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wjGVA/dJMcahdiO25/1mLPizqqBUWoKgb7M5ONg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwjGVA%2FdJMcahdiO25%2F1mLPizqqBUWoKgb7M5ONg0%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;335&quot; height=&quot;287&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;335&quot; data-origin-height=&quot;287&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;위 그림에서 1번 마을에 있는 음식점은 [1, 2, 4, 5] 번 마을까지는 3 이하의 시간에 배달할 수 있습니다. 그러나 3번 마을까지는 3시간 이내로 배달할 수 있는 경로가 없으므로 3번 마을에서는 주문을 받지 않습니다. 따라서 1번 마을에 있는 음식점이 배달 주문을 받을 수 있는 마을은 4개가 됩니다.&lt;br /&gt;&lt;br /&gt;마을의 개수 N, 각 마을을 연결하는 도로의 정보 road, 음식 배달이 가능한 시간 K가 매개변수로 주어질 때, 음식 주문을 받을 수 있는 마을의 개수를 return 하도록 solution 함수를 완성해주세요.&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;제한사항&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-pm-slice=&quot;0 0 []&quot;&gt;마을의 개수 N은 1 이상 50 이하의 자연수입니다.&lt;/li&gt;
&lt;li&gt;road의 길이(도로 정보의 개수)는 1 이상 2,000 이하입니다.&lt;/li&gt;
&lt;li&gt;road의 각 원소는 마을을 연결하고 있는 각 도로의 정보를 나타냅니다.&lt;/li&gt;
&lt;li&gt;road는 길이가 3인 배열이며, 순서대로 (a, b, c)를 나타냅니다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;a, b(1 &amp;le; a, b &amp;le; N, a != b)는 도로가 연결하는 두 마을의 번호이며, c(1 &amp;le; c &amp;le; 10,000, c는 자연수)는 도로를 지나는데 걸리는 시간입니다.&lt;/li&gt;
&lt;li&gt;두 마을 a, b를 연결하는 도로는 여러 개가 있을 수 있습니다.&lt;/li&gt;
&lt;li&gt;한 도로의 정보가 여러 번 중복해서 주어지지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;K는 음식 배달이 가능한 시간을 나타내며, 1 이상 500,000 이하입니다.&lt;/li&gt;
&lt;li&gt;임의의 두 마을간에 항상 이동 가능한 경로가 존재합니다.&lt;/li&gt;
&lt;li&gt;1번 마을에 있는 음식점이 K 이하의 시간에 배달이 가능한 마을의 개수를 return 하면 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;666&quot; data-origin-height=&quot;262&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/evQNny/dJMcabc28ds/obJV5xG776IqvwGvB2qkDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/evQNny/dJMcabc28ds/obJV5xG776IqvwGvB2qkDk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/evQNny/dJMcabc28ds/obJV5xG776IqvwGvB2qkDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FevQNny%2FdJMcabc28ds%2FobJV5xG776IqvwGvB2qkDk%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;602&quot; height=&quot;237&quot; data-origin-width=&quot;666&quot; data-origin-height=&quot;262&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;3. 접근 기준&lt;/h3&gt;
&lt;p data-end=&quot;395&quot; data-start=&quot;350&quot; data-ke-size=&quot;size16&quot;&gt;이 문제의 핵심은 &lt;b&gt;1번 마을에서 각 마을까지의 최단 거리&lt;/b&gt;를 구하는 것이다. 도로에는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;이동 비용&lt;/b&gt;&lt;/span&gt;이 있고, 모든 비용이 동일하지 않다.&lt;br /&gt;따라서 단순 BFS가 아니라 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;다익스트라 알고리즘&lt;/b&gt;&lt;/span&gt;을 사용해야 한다.&lt;/p&gt;
&lt;p data-end=&quot;486&quot; data-start=&quot;473&quot; data-ke-size=&quot;size16&quot;&gt;정리하면 다음 흐름이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;579&quot; data-start=&quot;488&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;510&quot; data-start=&quot;488&quot;&gt;마을과 도로 정보를 그래프로 만든다.&lt;/li&gt;
&lt;li data-end=&quot;526&quot; data-start=&quot;511&quot;&gt;1번 마을에서 시작한다.&lt;/li&gt;
&lt;li data-end=&quot;552&quot; data-start=&quot;527&quot;&gt;각 마을까지의 최소 이동 시간을 계산한다.&lt;/li&gt;
&lt;li data-end=&quot;579&quot; data-start=&quot;553&quot;&gt;계산된 거리 중 K 이하인 마을만 카운트한다.&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 style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size23&quot; data-start=&quot;403&quot; data-end=&quot;464&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;4. 사용한 패턴 / 알고리즘&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;다익스트라 알고리즘&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;708&quot; data-start=&quot;626&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;656&quot; data-start=&quot;626&quot;&gt;시작 노드에서&lt;b&gt; 다른 모든 노드까지의 최단 거리 계산&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;677&quot; data-start=&quot;657&quot;&gt;간선 비용이 있는 그래프에서 사용&lt;/li&gt;
&lt;li data-end=&quot;708&quot; data-start=&quot;678&quot;&gt;우선순위 큐를 사용하면 더 짧은 거리부터 탐색 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;힙(heapq)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;760&quot; data-start=&quot;724&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;760&quot; data-start=&quot;724&quot;&gt;현재까지 발견한 거리 중 가장 짧은 거리부터 꺼내기 위해 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;836&quot; data-start=&quot;820&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그래프 (인접 리스트)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;858&quot; data-start=&quot;838&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;858&quot; data-start=&quot;838&quot;&gt;각 마을과 연결된 마을 정보 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;5. 핵심 아이디어&lt;/h2&gt;
&lt;h4 data-end=&quot;918&quot; data-start=&quot;903&quot; data-ke-size=&quot;size20&quot;&gt;1) dist 배열&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;dist[i]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;960&quot; data-start=&quot;955&quot; data-ke-size=&quot;size16&quot;&gt;의 의미:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1번 마을에서 i번 마을까지의 현재 최단 거리&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1036&quot; data-start=&quot;1013&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;최소 비용을 구해야기 때문에 &lt;span style=&quot;color: #ee2323;&quot;&gt;처음에는 모두 매우 큰 값으로 초기화&lt;/span&gt;한다.&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;INF = int(1e9)
dist = [INF] * (N + 1)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1121&quot; data-start=&quot;1103&quot; data-ke-size=&quot;size16&quot;&gt;그리고 시작점만 0으로 설정한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;dist[1] = 0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-end=&quot;1186&quot; data-start=&quot;1167&quot; data-ke-size=&quot;size20&quot;&gt;2) heapq 사용 이유&lt;/h4&gt;
&lt;p data-end=&quot;1195&quot; data-start=&quot;1188&quot; data-ke-size=&quot;size16&quot;&gt;다익스트라는 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;현재까지 가장 짧은 거리&lt;/span&gt; 부터 탐색&lt;/b&gt;해야 한다. &lt;b&gt;거리를 기준&lt;/b&gt;으로 하기 때문에 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;거리를 첫번째 값으로 배치&lt;/b&gt;&lt;/span&gt;한다.&lt;/p&gt;
&lt;p data-end=&quot;1256&quot; data-start=&quot;1249&quot; data-ke-size=&quot;size16&quot;&gt;그래서 힙에:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;scheme&quot;&gt;&lt;code&gt;(거리, 노드)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1303&quot; data-start=&quot;1294&quot; data-ke-size=&quot;size16&quot;&gt;형태로 저장한다.&lt;/p&gt;
&lt;p data-end=&quot;1307&quot; data-start=&quot;1305&quot; data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;(3, 5)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1346&quot; data-start=&quot;1343&quot; data-ke-size=&quot;size16&quot;&gt;의미:&lt;/p&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;5번 마을까지 현재 거리 3&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1425&quot; data-start=&quot;1389&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;heapq&lt;/b&gt;&lt;/span&gt;는 &lt;b&gt;튜플 첫 번째 값을 기준으로 정렬&lt;/b&gt;하므로 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;가장 짧은 거리부터 꺼낼 수 있다&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;6. 풀이 코드&lt;/h2&gt;
&lt;pre id=&quot;code_1778464483796&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import heapq

def solution(N, road, K):

    # 그래프 생성
    # graph[a] = [(연결 마을, 비용), ...]
    graph = [[] for _ in range(N + 1)]

    # 양방향 도로 정보 저장
    for a, b, cost in road:
        graph[a].append((b, cost))
        graph[b].append((a, cost))

    INF = int(1e9)

    # 최단 거리 저장 배열
    dist = [INF] * (N + 1)

    # 시작 마을은 1번
    dist[1] = 0

    # 우선순위 큐 생성
    heap = []

    # (현재 거리, 현재 마을)
    heapq.heappush(heap, (0, 1))

    while heap:

        # 현재 가장 짧은 거리의 마을 꺼내기
        cur_dist, cur_node = heapq.heappop(heap)

        # 이미 더 짧은 경로로 처리된 적이 있으면 무시
        if cur_dist &amp;gt; dist[cur_node]:
            continue

        # 현재 마을과 연결된 다음 마을 확인
        for next_node, cost in graph[cur_node]:

            # 다음 마을까지 거리 계산
            next_dist = cur_dist + cost

            # 더 짧은 거리라면 갱신
            if next_dist &amp;lt; dist[next_node]:

                # 최단 거리 갱신
                dist[next_node] = next_dist

                # 힙에 새로운 거리 정보 추가
                heapq.heappush(heap, (next_dist, next_node))

    # K 이하 거리인 마을 개수 반환
    return sum(d &amp;lt;= K for d in dist)&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot; data-start=&quot;0&quot; data-end=&quot;19&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h3 data-end=&quot;2627&quot; data-start=&quot;2610&quot; data-ke-size=&quot;size23&quot;&gt;6-1. 예시로 이해하기&lt;/h3&gt;
&lt;p data-end=&quot;2653&quot; data-start=&quot;2629&quot; data-ke-size=&quot;size16&quot;&gt;문제 예시와 같은 구조가 있다고 하자.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;335&quot; data-origin-height=&quot;287&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wjGVA/dJMcahdiO25/1mLPizqqBUWoKgb7M5ONg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wjGVA/dJMcahdiO25/1mLPizqqBUWoKgb7M5ONg0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wjGVA/dJMcahdiO25/1mLPizqqBUWoKgb7M5ONg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwjGVA%2FdJMcahdiO25%2F1mLPizqqBUWoKgb7M5ONg0%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;335&quot; height=&quot;287&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;335&quot; data-origin-height=&quot;287&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2703&quot; data-start=&quot;2697&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;도로 및 그래프:&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;road = [
    [1, 2, 1],
    [1, 4, 2],
    [2, 3, 3],
    [2, 5, 2],
    [4, 5, 2],
    [5, 3, 1]
]

## graph[i]: i번째 마을의 (인접 마을, 비용)
graph = [[], [(2, 1), (4, 2)], [(1, 1), (3, 3), (5, 2)], 
	[(2, 3), (5, 1)], [(1, 2), (5, 2)],
	[(2, 2), (3, 1), (4, 2)]]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2843&quot; data-start=&quot;2837&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;초기 상태:&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;dist = [INF, 0, INF, INF, INF, INF]
heap = [(0, 1)]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-end=&quot;2927&quot; data-start=&quot;2911&quot; data-ke-size=&quot;size20&quot;&gt;1) 1번 마을 탐색 (탐색 시작 마을)&lt;/h4&gt;
&lt;pre id=&quot;code_1778467423014&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;heap = [(0,1)]
heapq.heappop(heap) # (0, 1) =&amp;gt; (비용, 마을)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;2935&quot; data-start=&quot;2929&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;연결 정보:&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;407&quot; data-origin-height=&quot;352&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bc4osq/dJMcagyFPOp/BK71Y7T0VOmn6P3PafIXy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bc4osq/dJMcagyFPOp/BK71Y7T0VOmn6P3PafIXy0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bc4osq/dJMcagyFPOp/BK71Y7T0VOmn6P3PafIXy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbc4osq%2FdJMcagyFPOp%2FBK71Y7T0VOmn6P3PafIXy0%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;407&quot; height=&quot;352&quot; data-origin-width=&quot;407&quot; data-origin-height=&quot;352&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;graph[1] = [(2, 1), (4, 2)] #(1번 마을의 인접 마을, 비용)

1 &amp;rarr; 2 : 비용 1	# dist[2]=INF 이므로 1로 갱신
1 &amp;rarr; 4 : 비용 2	# dist[4]=INF 이므로 2로 갱신&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2993&quot; data-start=&quot;2988&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;갱신 후:&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;dist = [INF, 0, INF, INF, INF, INF] =&amp;gt; dist = [INF, 0, 1, INF, 2, INF]
heap = [(0, 1)] =&amp;gt; heap = [(1, 2), (2, 4)]	#(1번 마을로 부터의 거리, 현재 마을)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;1번 마을 -&amp;gt;&amp;nbsp; 2번 마을까지 거리 : 1&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;1번 마을 -&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; 4번 마을까지 거리 : 2&amp;nbsp;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-start=&quot;3124&quot; data-end=&quot;3136&quot; data-ke-size=&quot;size16&quot;&gt;현재 가장 짧은 거리:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;힙에서 첫번째 값을 기준&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;color: #333333; text-align: start;&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;heap = [(1,2),(2,4)]
heapq.heappop(heap) #(1, 2)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;828&quot; data-start=&quot;819&quot; data-ke-size=&quot;size16&quot;&gt;가 나오므로 &lt;b&gt;2번 마을 탐색&lt;/b&gt;&lt;/p&gt;
&lt;h4 data-end=&quot;3122&quot; data-start=&quot;3106&quot; data-ke-size=&quot;size20&quot;&gt;2) 2번 마을 탐색&lt;/h4&gt;
&lt;p data-end=&quot;3184&quot; data-start=&quot;3170&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2번과 연결된 마을 확인:&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;407&quot; data-origin-height=&quot;372&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dPgfbI/dJMcafzNSw1/2sodge24QsM03nszXGERkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dPgfbI/dJMcafzNSw1/2sodge24QsM03nszXGERkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dPgfbI/dJMcafzNSw1/2sodge24QsM03nszXGERkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdPgfbI%2FdJMcafzNSw1%2F2sodge24QsM03nszXGERkK%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;407&quot; height=&quot;372&quot; data-origin-width=&quot;407&quot; data-origin-height=&quot;372&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;graph[2] = [(1, 1), (3, 3), (5, 2)]	#(2번 마을의 인접 마을, 비용)
2 &amp;rarr; 1 : 1 + 1 = 2 	# dist[1]=0 이므로 갱신 x
2 &amp;rarr; 3 : 1 + 3 = 4	# dist[3]=INF이므로 4로 갱신
2 &amp;rarr; 5 : 1 + 2 = 3	# dist[5]=INF이므로 3로 갱신&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3252&quot; data-start=&quot;3247&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;갱신 후:&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;dist = [INF, 0, 1, INF, 2, INF] =&amp;gt; dist = [INF, 0, 1, 4, 2, 3]
heap = [(2, 4)] =&amp;gt; heap = [(2, 4), (4, 3), (3, 5)]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1233&quot; data-start=&quot;1224&quot; data-ke-size=&quot;size16&quot;&gt;즉 다음 탐색은:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;heap = [(2, 4), (4, 3), (3, 5)]
heapq.heappop(heap) # (2, 4) =&amp;gt; (비용, 마을)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1276&quot; data-start=&quot;1267&quot; data-ke-size=&quot;size16&quot;&gt;가 나오므로 &lt;b&gt;4번 마을 탐색&lt;/b&gt;&lt;/p&gt;
&lt;h4 data-end=&quot;3328&quot; data-start=&quot;3312&quot; data-ke-size=&quot;size20&quot;&gt;3) 4번 마을 탐색&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;4번과 연결된 마을 확인:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;361&quot; data-origin-height=&quot;312&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bx0e7r/dJMcabc3ajD/cpYeGzPVmkzp69nNcNEQIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bx0e7r/dJMcabc3ajD/cpYeGzPVmkzp69nNcNEQIK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bx0e7r/dJMcabc3ajD/cpYeGzPVmkzp69nNcNEQIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbx0e7r%2FdJMcabc3ajD%2FcpYeGzPVmkzp69nNcNEQIK%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;361&quot; height=&quot;312&quot; data-origin-width=&quot;361&quot; data-origin-height=&quot;312&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;graph[4] = [(1, 2), (5, 2)]
4 &amp;rarr; 1 : 2 + 2 = 4	# dist[1]=0 이므로 갱신 X
4 &amp;rarr; 5 : 2 + 2 = 4	# dist[5]=3 이므로 갱신 X&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3379&quot; data-start=&quot;3373&quot; data-ke-size=&quot;size16&quot;&gt;근데 이미:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;dist[1] = 0
dist[5] = 3&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3441&quot; data-start=&quot;3418&quot; data-ke-size=&quot;size16&quot;&gt;더 짧은 경로가 있으므로 갱신하지 않는다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;if next_dist &amp;lt; dist[next_node]:&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1630&quot; data-start=&quot;1617&quot; data-ke-size=&quot;size16&quot;&gt;조건을 만족하지 못하기 때문에 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;갱신 안 함, 힙에도&amp;nbsp;추가&amp;nbsp;안&amp;nbsp;함&lt;/b&gt;&lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1778469550472&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dist = [INF, 0, 1, 4, 2, 3]&lt;/code&gt;&lt;/pre&gt;
&lt;div style=&quot;color: #333333; text-align: start;&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-end=&quot;3136&quot; data-start=&quot;3124&quot; data-ke-size=&quot;size16&quot;&gt;현재 가장 짧은 거리:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;힙에서 첫번째 값을 기준&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;color: #333333; text-align: start;&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1778469205883&quot; class=&quot;angelscript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;heap = [(3, 5),(4, 3)]
heapq.heappop(heap) #(3,5)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-start=&quot;819&quot; data-end=&quot;828&quot; data-ke-size=&quot;size16&quot;&gt;가 나오므로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;5번 마을 탐색&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;1630&quot; data-start=&quot;1617&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1630&quot; data-start=&quot;1617&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;4) 5번 마을 탐색&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;1630&quot; data-start=&quot;1617&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;5번과 연결된 마을 확인:&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;408&quot; data-origin-height=&quot;402&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bL55Xd/dJMcaiQMzl7/eEi9EkbbJEvPVIKbNm2C41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bL55Xd/dJMcaiQMzl7/eEi9EkbbJEvPVIKbNm2C41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bL55Xd/dJMcaiQMzl7/eEi9EkbbJEvPVIKbNm2C41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbL55Xd%2FdJMcaiQMzl7%2FeEi9EkbbJEvPVIKbNm2C41%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;408&quot; height=&quot;402&quot; data-origin-width=&quot;408&quot; data-origin-height=&quot;402&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1778469376152&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;graph[5] = [(2, 2), (4, 2), (3, 1)]

5 &amp;rarr; 2 : 3 + 2 = 5   # dist[2]=1 이므로 갱신 X
5 &amp;rarr; 4 : 3 + 2 = 5   # dist[4]=2 이므로 갱신 X
5 &amp;rarr; 3 : 3 + 1 = 4   # dist[3]=4 이므로 갱신 X&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-start=&quot;1617&quot; data-end=&quot;1630&quot; data-ke-size=&quot;size16&quot;&gt;조건을 만족하지 못하기 때문에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;갱신 안 함, 힙에도&amp;nbsp;추가&amp;nbsp;안&amp;nbsp;함&lt;/b&gt;&lt;/span&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1778469578408&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dist = [INF, 0, 1, 4, 2, 3]&lt;/code&gt;&lt;/pre&gt;
&lt;div style=&quot;color: #333333; text-align: start;&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-start=&quot;3124&quot; data-end=&quot;3136&quot; data-ke-size=&quot;size16&quot;&gt;현재 가장 짧은 거리:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;힙에서 첫번째 값을 기준&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;color: #333333; text-align: start;&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1778469490820&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;heap = [(4, 3)]
heapq.heappop(heap) # (4, 3)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-end=&quot;828&quot; data-start=&quot;819&quot; data-ke-size=&quot;size16&quot;&gt;가 나오므로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;3번 마을 탐색&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-end=&quot;828&quot; data-start=&quot;819&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-end=&quot;828&quot; data-start=&quot;819&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;5) 3번 마을 탐색&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-end=&quot;828&quot; data-start=&quot;819&quot; data-ke-size=&quot;size18&quot;&gt;3번과 연결된 마을 확인:&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;407&quot; data-origin-height=&quot;371&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5DVJP/dJMcacJRs9D/Ydmicv9AFFLf6fuBHEaJnk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5DVJP/dJMcacJRs9D/Ydmicv9AFFLf6fuBHEaJnk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5DVJP/dJMcacJRs9D/Ydmicv9AFFLf6fuBHEaJnk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5DVJP%2FdJMcacJRs9D%2FYdmicv9AFFLf6fuBHEaJnk%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;407&quot; height=&quot;371&quot; data-origin-width=&quot;407&quot; data-origin-height=&quot;371&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1778469707084&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;graph[3] = [(2, 3), (5, 1)]
3 &amp;rarr; 2 : 4 + 3 = 7   # dist[2]=1 이므로 갱신 X
3 &amp;rarr; 5 : 4 + 1 = 5   # dist[5]=3 이므로 갱신 X&lt;/code&gt;&lt;/pre&gt;
&lt;div style=&quot;color: #333333; text-align: start;&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;조건을 만족하지 못하기 때문에&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;갱신 안 함, 힙에도&amp;nbsp;추가&amp;nbsp;안&amp;nbsp;함&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3454&quot; data-start=&quot;3448&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;최종 결과:&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;dist = [INF, 0, 1, 4, 2, 3]

heap = []&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3542&quot; data-start=&quot;3536&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;힙이 비었으므로 탐색 종료.&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;K = 3 &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;이라면:&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1, 2, 4, 5&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3636&quot; data-start=&quot;3619&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;총 4개 마을에 배달 가능하다.&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;7. 핵심 로직 요약&lt;/h2&gt;
&lt;p data-end=&quot;3672&quot; data-start=&quot;3662&quot; data-ke-size=&quot;size16&quot;&gt;이 문제의 핵심은 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;가장 짧은 거리부터 탐색하면서 &lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;최단 거리 배열을 갱신하는 것 &lt;/span&gt;&lt;/b&gt;&lt;/span&gt;이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3865&quot; data-start=&quot;3738&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3757&quot; data-start=&quot;3738&quot;&gt;그래프를 인접 리스트로 만든다.&lt;/li&gt;
&lt;li data-end=&quot;3783&quot; data-start=&quot;3758&quot;&gt;dist 배열에 최단 거리를 저장한다.&lt;/li&gt;
&lt;li data-end=&quot;3805&quot; data-start=&quot;3784&quot;&gt;힙에서 가장 짧은 거리부터 꺼낸다.&lt;/li&gt;
&lt;li data-end=&quot;3820&quot; data-start=&quot;3806&quot;&gt;연결된 마을 거리 갱신&lt;/li&gt;
&lt;li data-end=&quot;3840&quot; data-start=&quot;3821&quot;&gt;더 짧은 경로면 힙에 다시 추가&lt;/li&gt;
&lt;li data-end=&quot;3865&quot; data-start=&quot;3841&quot;&gt;최종적으로 K 이하인 마을 개수 계산&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;3870&quot; data-start=&quot;3867&quot; data-ke-size=&quot;size16&quot;&gt;결국, &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;그래프 + heapq + 다익스트라&amp;nbsp;&lt;/b&gt;&lt;/span&gt;구조가 핵심이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot; data-section-id=&quot;1yk7ghc&quot; data-start=&quot;2298&quot; data-end=&quot;2310&quot;&gt;8. 정리&lt;/h2&gt;
&lt;p data-end=&quot;3983&quot; data-start=&quot;3959&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 BFS처럼 보였는데, 실제로는 비용이 있는 최단 거리 문제 라는 걸 이해해야 했다.&lt;/p&gt;
&lt;p data-end=&quot;4058&quot; data-start=&quot;4041&quot; data-ke-size=&quot;size16&quot;&gt;특히 중요한 포인트는 이거였다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;탐색&amp;nbsp;순서가&amp;nbsp;노드&amp;nbsp;번호가&amp;nbsp;아니라&amp;nbsp;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;거리&amp;nbsp;기준&lt;/b&gt;&lt;/span&gt;이다&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4148&quot; data-start=&quot;4111&quot; data-ke-size=&quot;size16&quot;&gt;그래서 &lt;b&gt;heapq&lt;/b&gt;를 사용하는 순간 &lt;b&gt;다익스트라 구조&lt;/b&gt;가 만들어진다.&lt;/p&gt;
&lt;p data-end=&quot;4161&quot; data-start=&quot;4150&quot; data-ke-size=&quot;size16&quot;&gt;또 하나 느낀 점은 같은&amp;nbsp;마을이&amp;nbsp;&lt;b&gt;힙에 여러 번 들어갈 수 있다 &lt;/b&gt;는 점이다.&lt;/p&gt;
&lt;p data-end=&quot;4224&quot; data-start=&quot;4220&quot; data-ke-size=&quot;size16&quot;&gt;그래서&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;if cur_dist &amp;gt; dist[cur_node]:
    continue&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4307&quot; data-start=&quot;4296&quot; data-ke-size=&quot;size16&quot;&gt;조건이 꼭 필요하다.&lt;/p&gt;
&lt;p data-end=&quot;4335&quot; data-start=&quot;4309&quot; data-ke-size=&quot;size16&quot;&gt;결국 이 문제는 다익스트라를 처음 익힐 때&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;4385&quot; data-start=&quot;4337&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;4350&quot; data-start=&quot;4337&quot;&gt;왜 heap을 쓰는지&lt;/li&gt;
&lt;li data-end=&quot;4366&quot; data-start=&quot;4351&quot;&gt;왜 거리 배열이 필요한지&lt;/li&gt;
&lt;li data-end=&quot;4385&quot; data-start=&quot;4367&quot;&gt;왜 더 짧은 거리만 갱신하는지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;4405&quot; data-start=&quot;4387&quot; data-ke-size=&quot;size16&quot;&gt;를 이해하기 좋은 대표 문제였다.&lt;/p&gt;</description>
      <category>CodingTest/문제 풀이(Lv1~Lv2)</category>
      <author>jjaehyeok</author>
      <guid isPermaLink="true">https://jjaehyeok.tistory.com/70</guid>
      <comments>https://jjaehyeok.tistory.com/70#entry70comment</comments>
      <pubDate>Mon, 11 May 2026 12:25:25 +0900</pubDate>
    </item>
    <item>
      <title>프로그래머스 Lv.2 | Python | 게임 맵 최단거리</title>
      <link>https://jjaehyeok.tistory.com/68</link>
      <description>&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;1. 문제 정보&lt;/h2&gt;
&lt;figure id=&quot;og_1778208405372&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;프로그래머스&quot; data-og-description=&quot;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/1844&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/E0ABE/dJMb8ZvFElG/AqKoy9SiLD18HptY4wvNkk/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/dcWEQ5/dJMb8VNzGXD/rmPjqRKwydelBEAEjeoWW1/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/1844&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/1844&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/E0ABE/dJMb8ZvFElG/AqKoy9SiLD18HptY4wvNkk/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/dcWEQ5/dJMb8VNzGXD/rmPjqRKwydelBEAEjeoWW1/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960');&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;프로그래머스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&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;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;난이도: Lv.2&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 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;2. 문제 설명(요약)&lt;/h2&gt;
&lt;p data-end=&quot;167&quot; data-start=&quot;137&quot; data-ke-size=&quot;size16&quot;&gt;게임 맵은 0과 1로 이루어진 2차원 배열이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;208&quot; data-start=&quot;169&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;186&quot; data-start=&quot;169&quot;&gt;1은 이동할 수 있는 칸&lt;/li&gt;
&lt;li data-end=&quot;208&quot; data-start=&quot;187&quot;&gt;0은 벽이라 이동할 수 없는 칸&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;265&quot; data-start=&quot;210&quot; data-ke-size=&quot;size16&quot;&gt;캐릭터는 왼쪽 위 (0, 0)에서 출발하고, 상대 팀 진영인 오른쪽 아래까지 이동해야 한다.&lt;/p&gt;
&lt;p data-end=&quot;328&quot; data-start=&quot;267&quot; data-ke-size=&quot;size16&quot;&gt;이때 상대 팀 진영까지 도착하는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;최단 거리&lt;/b&gt;&lt;/span&gt;를 구하는 문제다. &lt;b&gt;도착할 수 없다면 -1을 반환&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;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;3. 접근 기준&lt;/h3&gt;
&lt;p data-end=&quot;381&quot; data-start=&quot;351&quot; data-ke-size=&quot;size16&quot;&gt;이 문제는 2차원 격자에서 최단 거리를 구하는 문제다.&lt;/p&gt;
&lt;p data-end=&quot;410&quot; data-start=&quot;383&quot; data-ke-size=&quot;size16&quot;&gt;각 칸에서 이동할 수 있는 방향은 다음 4가지다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;상, 하, 좌, 우&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;480&quot; data-start=&quot;436&quot; data-ke-size=&quot;size16&quot;&gt;모든 이동 비용이 1로 동일하므로, 최단 거리는 &lt;b&gt;BFS로 구하는 것이 적합하다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;539&quot; data-start=&quot;482&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;DFS&lt;/b&gt;&lt;/span&gt;를 사용하면 한 경로를 끝까지 들어가기 때문에 처음 &lt;b&gt;도착한 경로가 최단 거리라는 보장이 없다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;604&quot; data-start=&quot;541&quot; data-ke-size=&quot;size16&quot;&gt;반면 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;BFS&lt;/b&gt;&lt;/span&gt;는 시작점에서 가까운 칸부터 차례대로 탐색하므로, &lt;b&gt;처음 목적지에 도착했을 때의 거리가 최단 거리다.&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #222222; text-align: start;&quot; data-end=&quot;464&quot; data-start=&quot;403&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;4. 사용한 패턴 / 알고리즘&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;BFS (너비 우선 탐색)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;708&quot; data-start=&quot;655&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;673&quot; data-start=&quot;655&quot;&gt;시작점에서 가까운 칸부터 탐색&lt;/li&gt;
&lt;li data-end=&quot;688&quot; data-start=&quot;674&quot;&gt;최단 거리 문제에 적합&lt;/li&gt;
&lt;li data-end=&quot;708&quot; data-start=&quot;689&quot;&gt;큐를 사용해서 탐색 순서를 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2차원 배열 탐색&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;758&quot; data-start=&quot;725&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;737&quot; data-start=&quot;725&quot;&gt;행, 열 범위 체크&lt;/li&gt;
&lt;li data-end=&quot;747&quot; data-start=&quot;738&quot;&gt;벽 여부 체크&lt;/li&gt;
&lt;li data-end=&quot;758&quot; data-start=&quot;748&quot;&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 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;5. 핵심 아이디어&lt;/h2&gt;
&lt;p data-end=&quot;817&quot; data-start=&quot;783&quot; data-ke-size=&quot;size16&quot;&gt;핵심은 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;maps 배열 자체에 이동 거리를 누적&lt;/b&gt;&lt;/span&gt;하는 것이다.&lt;/p&gt;
&lt;p data-end=&quot;875&quot; data-start=&quot;819&quot; data-ke-size=&quot;size16&quot;&gt;처음 시작 위치는 1이다. &lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;다음 칸으로 이동할 때마다 현재 칸 값에 1을 더해서 저장&lt;/b&gt;&lt;/span&gt;한다.&lt;/p&gt;
&lt;p data-end=&quot;879&quot; data-start=&quot;877&quot; data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;maps[nx][ny] = maps[x][y] + 1&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;955&quot; data-start=&quot;926&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 하면 별도의 거리 배열을 만들지 않아도 된다.&lt;/p&gt;
&lt;p data-end=&quot;1064&quot; data-start=&quot;957&quot; data-ke-size=&quot;size16&quot;&gt;또 이미 방문한 칸은 다시 방문하지 않도록 해야 한다. 이 코드에서는 방문한 칸의 값을 거리 값으로 바꾸기 때문에, &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;maps[nx][ny] == 1인 칸만 이동 대상으로 보면 된다.&lt;/b&gt;&lt;/span&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;6. 풀이 코드&lt;/h2&gt;
&lt;pre id=&quot;code_1778208664194&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from collections import deque

def solution(maps):
    n = len(maps)       # 행 개수
    m = len(maps[0])    # 열 개수

    # 상, 하, 좌, 우 이동 방향
    dx = [-1, 1, 0, 0]
    dy = [0, 0, -1, 1]

    # BFS를 위한 큐 생성
    q = deque()
    q.append((0, 0))  # 시작 위치

    while q:
        x, y = q.popleft()

        # 현재 위치에서 4방향 탐색
        for i in range(4):
            nx = x + dx[i]
            ny = y + dy[i]

            # 맵 범위를 벗어나면 제외
            if nx &amp;lt; 0 or nx &amp;gt;= n or ny &amp;lt; 0 or ny &amp;gt;= m:
                continue

            # 벽이거나 이미 방문한 칸이면 제외
            # 0: 벽
            # 1이 아닌 값: 이미 거리 값으로 갱신된 방문 칸
            if maps[nx][ny] != 1:
                continue

            # 다음 칸까지의 거리 저장
            maps[nx][ny] = maps[x][y] + 1

            # 다음 탐색 위치로 추가
            q.append((nx, ny))

    # 도착 지점 값이 1이면 도달하지 못한 것
    if maps[n - 1][m - 1] == 1:
        return -1

    # 도착 지점에 저장된 값이 최단 거리
    return maps[n - 1][m - 1]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;19&quot; data-start=&quot;0&quot; data-ke-size=&quot;size20&quot;&gt;6-1. 코드 추가 설명&lt;/h4&gt;
&lt;p data-end=&quot;44&quot; data-start=&quot;21&quot; data-ke-size=&quot;size16&quot;&gt;이 코드에서 가장 중요한 부분은 다음이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;maps[nx][ny] = maps[x][y] + 1&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;111&quot; data-start=&quot;91&quot; data-ke-size=&quot;size16&quot;&gt;이 부분은 단순 방문 처리가 아니라:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;현재 위치까지의 거리 + 1&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;160&quot; data-start=&quot;142&quot; data-ke-size=&quot;size16&quot;&gt;을 다음 칸에 저장하는 역할이다.&lt;/p&gt;
&lt;p data-end=&quot;184&quot; data-start=&quot;162&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 현재 위치 값이 3이라면:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;시작점에서 현재 칸까지 3칸 이동했다는 의미&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;227&quot; data-start=&quot;224&quot; data-ke-size=&quot;size16&quot;&gt;이고,&lt;/p&gt;
&lt;p data-end=&quot;235&quot; data-start=&quot;229&quot; data-ke-size=&quot;size16&quot;&gt;다음 칸은:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;3 + 1 = 4&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;267&quot; data-start=&quot;262&quot; data-ke-size=&quot;size16&quot;&gt;가 된다.&lt;/p&gt;
&lt;p data-end=&quot;286&quot; data-start=&quot;269&quot; data-ke-size=&quot;size16&quot;&gt;즉, maps 배열 자체가:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;323&quot; data-start=&quot;288&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;301&quot; data-start=&quot;288&quot;&gt;이동 가능 여부 확인&lt;/li&gt;
&lt;li data-end=&quot;312&quot; data-start=&quot;302&quot;&gt;방문 여부 확인&lt;/li&gt;
&lt;li data-end=&quot;323&quot; data-start=&quot;313&quot;&gt;최단 거리 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;343&quot; data-start=&quot;325&quot; data-ke-size=&quot;size16&quot;&gt;세 가지 역할을 동시에 수행한다.&lt;/p&gt;
&lt;p data-end=&quot;359&quot; data-start=&quot;350&quot; data-ke-size=&quot;size16&quot;&gt;또 이 코드에서:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;if maps[nx][ny] != 1:
    continue&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;420&quot; data-start=&quot;411&quot; data-ke-size=&quot;size16&quot;&gt;조건도 중요하다.&lt;/p&gt;
&lt;p data-end=&quot;430&quot; data-start=&quot;422&quot; data-ke-size=&quot;size16&quot;&gt;처음 상태에서:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;468&quot; data-start=&quot;432&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;458&quot; data-start=&quot;432&quot;&gt;1 &amp;rarr; 아직 방문하지 않은 이동 가능 칸&lt;/li&gt;
&lt;li data-end=&quot;468&quot; data-start=&quot;459&quot;&gt;0 &amp;rarr; 벽&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;473&quot; data-start=&quot;470&quot; data-ke-size=&quot;size16&quot;&gt;이다.&lt;/p&gt;
&lt;p data-end=&quot;484&quot; data-start=&quot;475&quot; data-ke-size=&quot;size16&quot;&gt;그런데 방문하면:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;maps[nx][ny] = maps[x][y] + 1&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;558&quot; data-start=&quot;531&quot; data-ke-size=&quot;size16&quot;&gt;로 값이 바뀌기 때문에 더 이상 1이 아니다.&lt;/p&gt;
&lt;p data-end=&quot;562&quot; data-start=&quot;560&quot; data-ke-size=&quot;size16&quot;&gt;즉:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1이 아닌 값 = 이미 방문했거나 벽&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;609&quot; data-start=&quot;598&quot; data-ke-size=&quot;size16&quot;&gt;이라는 의미가 된다.&lt;/p&gt;
&lt;p data-end=&quot;648&quot; data-start=&quot;611&quot; data-ke-size=&quot;size16&quot;&gt;그래서 별도의 visited 배열 없이도 방문 처리가 가능하다.&lt;/p&gt;
&lt;p data-end=&quot;682&quot; data-start=&quot;655&quot; data-ke-size=&quot;size16&quot;&gt;그리고 BFS에서 큐를 사용하는 이유도 중요하다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;q.popleft()&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;718&quot; data-start=&quot;711&quot; data-ke-size=&quot;size16&quot;&gt;를 사용하면:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;먼저 들어온 위치부터 탐색&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;754&quot; data-start=&quot;748&quot; data-ke-size=&quot;size16&quot;&gt;하게 된다.&lt;/p&gt;
&lt;p data-end=&quot;758&quot; data-start=&quot;756&quot; data-ke-size=&quot;size16&quot;&gt;즉:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;797&quot; data-start=&quot;760&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;773&quot; data-start=&quot;760&quot;&gt;시작점에서 가까운 칸&lt;/li&gt;
&lt;li data-end=&quot;785&quot; data-start=&quot;774&quot;&gt;그 다음 거리 칸&lt;/li&gt;
&lt;li data-end=&quot;797&quot; data-start=&quot;786&quot;&gt;그 다음 거리 칸&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;812&quot; data-start=&quot;799&quot; data-ke-size=&quot;size16&quot;&gt;순서로 탐색이 진행된다.&lt;/p&gt;
&lt;p data-end=&quot;823&quot; data-start=&quot;814&quot; data-ke-size=&quot;size16&quot;&gt;그래서 BFS는:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;처음 도착한 경로가 곧 최단 거리&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;862&quot; data-start=&quot;857&quot; data-ke-size=&quot;size16&quot;&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;7. 핵심 로직 요약&lt;/h2&gt;
&lt;p data-end=&quot;2107&quot; data-start=&quot;2059&quot; data-ke-size=&quot;size16&quot;&gt;이 문제의 핵심은 &lt;b&gt;BFS로 가까운 칸부터 탐색하면서 거리 값을 누적하는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2268&quot; data-start=&quot;2109&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2132&quot; data-start=&quot;2109&quot;&gt;시작점 (0, 0)을 큐에 넣는다.&lt;/li&gt;
&lt;li data-end=&quot;2157&quot; data-start=&quot;2133&quot;&gt;큐에서 하나씩 꺼내 상하좌우를 확인한다.&lt;/li&gt;
&lt;li data-end=&quot;2179&quot; data-start=&quot;2158&quot;&gt;범위를 벗어나거나 벽이면 제외한다.&lt;/li&gt;
&lt;li data-end=&quot;2213&quot; data-start=&quot;2180&quot;&gt;아직 방문하지 않은 칸이면 현재 거리 + 1로 갱신한다.&lt;/li&gt;
&lt;li data-end=&quot;2244&quot; data-start=&quot;2214&quot;&gt;도착 지점 값이 갱신되었다면 그 값이 최단 거리다.&lt;/li&gt;
&lt;li data-end=&quot;2268&quot; data-start=&quot;2245&quot;&gt;도착하지 못했다면 -1을 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2321&quot; data-start=&quot;2270&quot; data-ke-size=&quot;size16&quot;&gt;결국, &lt;b&gt;&amp;ldquo;큐 기반 BFS + 4방향 탐색 + 거리 누적&amp;rdquo; &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-end=&quot;2310&quot; data-start=&quot;2298&quot; data-section-id=&quot;1yk7ghc&quot; data-ke-size=&quot;size26&quot;&gt;8. 정리&lt;/h2&gt;
&lt;p data-end=&quot;2402&quot; data-start=&quot;2354&quot; data-ke-size=&quot;size16&quot;&gt;처음 보면 단순히 길을 찾는 문제처럼 보이지만, 핵심은 &lt;b&gt;&amp;ldquo;최단 거리&amp;rdquo;&lt;/b&gt;라는 조건이다.&lt;/p&gt;
&lt;p data-end=&quot;2432&quot; data-start=&quot;2404&quot; data-ke-size=&quot;size16&quot;&gt;이 조건 때문에 &lt;b&gt;DFS보다 BFS가 더 자연스럽다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;2450&quot; data-start=&quot;2434&quot; data-ke-size=&quot;size16&quot;&gt;특히 중요한 부분은 이거였다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;모든 이동 비용이 같으면 BFS가 최단 거리를 보장한다&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2580&quot; data-start=&quot;2496&quot; data-ke-size=&quot;size16&quot;&gt;또 별도의 visited 배열이나 distance 배열을 만들지 않고, 기존 maps에 거리 값을 직접 저장할 수 있다는 점도 중요했다.&lt;/p&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;2639&quot; data-start=&quot;2582&quot; data-ke-size=&quot;size16&quot;&gt;결국 이 문제는 &lt;b&gt;2차원 배열에서 BFS로 최단 거리를 구하는 기본 유형&lt;/b&gt;으로 정리할 수 있다.&lt;/p&gt;</description>
      <category>CodingTest/문제 풀이(Lv1~Lv2)</category>
      <author>jjaehyeok</author>
      <guid isPermaLink="true">https://jjaehyeok.tistory.com/68</guid>
      <comments>https://jjaehyeok.tistory.com/68#entry68comment</comments>
      <pubDate>Fri, 8 May 2026 11:53:54 +0900</pubDate>
    </item>
    <item>
      <title>프로그래머스 Lv.2 | Python | [1차] 뉴스 클러스터링</title>
      <link>https://jjaehyeok.tistory.com/67</link>
      <description>&lt;div style=&quot;color: #333333; text-align: start;&quot;&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;1. 문제 정보&lt;/h2&gt;
&lt;figure id=&quot;og_1778207229921&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;프로그래머스&quot; data-og-description=&quot;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/17677&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bkHcNK/dJMb89yhMSm/udkdHBK8KzckyXzkXZcUW0/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/uBseN/dJMb86O5Wzd/x9RoLKxgCD4tjCYOb96QQK/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/17677&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/17677&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bkHcNK/dJMb89yhMSm/udkdHBK8KzckyXzkXZcUW0/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/uBseN/dJMb86O5Wzd/x9RoLKxgCD4tjCYOb96QQK/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960');&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;프로그래머스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&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;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;난이도: Lv.2&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;2. 문제 설명&lt;/h2&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;여러 언론사에서 쏟아지는 뉴스, 특히 속보성 뉴스를 보면 비슷비슷한 제목의 기사가 많아 정작 필요한 기사를 찾기가 어렵다. Daum 뉴스의 개발 업무를 맡게 된 신입사원 튜브는 사용자들이 편리하게 다양한 뉴스를 찾아볼 수 있도록 문제점을 개선하는 업무를 맡게 되었다.&lt;br /&gt;&lt;br /&gt;개발의 방향을 잡기 위해 튜브는 우선 최근 화제가 되고 있는 &quot;카카오 신입 개발자 공채&quot; 관련 기사를 검색해보았다.&lt;br /&gt;카카오 첫 공채..'블라인드' 방식 채용카카오, 합병 후 첫 공채.. 블라인드 전형으로 개발자 채용카카오, 블라인드 전형으로 신입 개발자 공채카카오 공채, 신입 개발자 코딩 능력만 본다카카오, 신입 공채.. &quot;코딩 실력만 본다&quot;카카오 &quot;코딩 능력만으로 2018 신입 개발자 뽑는다&quot;&lt;br /&gt;&lt;br /&gt;기사의 제목을 기준으로 &quot;블라인드 전형&quot;에 주목하는 기사와 &quot;코딩 테스트&quot;에 주목하는 기사로 나뉘는 걸 발견했다. 튜브는 이들을 각각 묶어서 보여주면 카카오 공채 관련 기사를 찾아보는 사용자에게 유용할 듯싶었다.&lt;br /&gt;&lt;br /&gt;유사한 기사를 묶는 기준을 정하기 위해서 논문과 자료를 조사하던 튜브는 &quot;자카드 유사도&quot;라는 방법을 찾아냈다.&lt;br /&gt;자카드 유사도는 집합 간의 유사도를 검사하는 여러 방법 중의 하나로 알려져 있다. 두 집합&amp;nbsp;A,&amp;nbsp;B&amp;nbsp;사이의 자카드 유사도&amp;nbsp;J(A, B)는 두 집합의 교집합 크기를 두 집합의 합집합 크기로 나눈 값으로 정의된다.&lt;br /&gt;&lt;br /&gt;예를 들어 집합&amp;nbsp;A&amp;nbsp;= {1, 2, 3}, 집합&amp;nbsp;B&amp;nbsp;= {2, 3, 4}라고 할 때, 교집합&amp;nbsp;A &amp;cap; B&amp;nbsp;= {2, 3}, 합집합&amp;nbsp;A &amp;cup; B&amp;nbsp;= {1, 2, 3, 4}이 되므로, 집합&amp;nbsp;A,&amp;nbsp;B&amp;nbsp;사이의 자카드 유사도&amp;nbsp;J(A, B)&amp;nbsp;= 2/4 = 0.5가 된다. 집합 A와 집합 B가 모두 공집합일 경우에는 나눗셈이 정의되지 않으니 따로&amp;nbsp;J(A, B)&amp;nbsp;= 1로 정의한다.&lt;br /&gt;&lt;br /&gt;자카드 유사도는 원소의 중복을 허용하는 다중집합에 대해서 확장할 수 있다. 다중집합&amp;nbsp;A는 원소 &quot;1&quot;을 3개 가지고 있고, 다중집합&amp;nbsp;B는 원소 &quot;1&quot;을 5개 가지고 있다고 하자. 이 다중집합의 교집합&amp;nbsp;A &amp;cap; B는 원소 &quot;1&quot;을 min(3, 5)인 3개, 합집합&amp;nbsp;A &amp;cup; B는 원소 &quot;1&quot;을 max(3, 5)인 5개 가지게 된다. 다중집합&amp;nbsp;A&amp;nbsp;= {1, 1, 2, 2, 3}, 다중집합&amp;nbsp;B&amp;nbsp;= {1, 2, 2, 4, 5}라고 하면, 교집합&amp;nbsp;A &amp;cap; B&amp;nbsp;= {1, 2, 2}, 합집합&amp;nbsp;A &amp;cup; B&amp;nbsp;= {1, 1, 2, 2, 3, 4, 5}가 되므로, 자카드 유사도&amp;nbsp;J(A, B)&amp;nbsp;= 3/7, 약 0.42가 된다.&lt;br /&gt;&lt;br /&gt;이를 이용하여 문자열 사이의 유사도를 계산하는데 이용할 수 있다. 문자열 &quot;FRANCE&quot;와 &quot;FRENCH&quot;가 주어졌을 때, 이를 두 글자씩 끊어서 다중집합을 만들 수 있다. 각각 {FR, RA, AN, NC, CE}, {FR, RE, EN, NC, CH}가 되며, 교집합은 {FR, NC}, 합집합은 {FR, RA, AN, NC, CE, RE, EN, CH}가 되므로, 두 문자열 사이의 자카드 유사도&amp;nbsp;J(&quot;FRANCE&quot;, &quot;FRENCH&quot;)&amp;nbsp;= 2/8 = 0.25가 된다.&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;입력 형식&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;입력으로는 str1과 str2의 두 문자열이 들어온다. 각 문자열의 길이는 2 이상, 1,000 이하이다.&lt;/li&gt;
&lt;li&gt;입력으로 들어온 문자열은 두 글자씩 끊어서 다중집합의 원소로 만든다. 이때 영문자로 된 글자 쌍만 유효하고, 기타 공백이나 숫자, 특수 문자가 들어있는 경우는 그 글자 쌍을 버린다.&lt;/li&gt;
&lt;li&gt;예를 들어 &quot;ab+&quot;가 입력으로 들어오면, &quot;ab&quot;만 다중집합의 원소로 삼고, &quot;b+&quot;는 버린다.&lt;/li&gt;
&lt;li&gt;다중집합&amp;nbsp;원소&amp;nbsp;사이를&amp;nbsp;비교할&amp;nbsp;때,&amp;nbsp;대문자와&amp;nbsp;소문자의&amp;nbsp;차이는&amp;nbsp;무시한다.&amp;nbsp;&quot;AB&quot;와&amp;nbsp;&quot;Ab&quot;,&amp;nbsp;&quot;ab&quot;는&amp;nbsp;같은&amp;nbsp;원소로&amp;nbsp;취급한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;출력 형식&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;입력으로&amp;nbsp;들어온&amp;nbsp;두&amp;nbsp;문자열의&amp;nbsp;자카드&amp;nbsp;유사도를&amp;nbsp;출력한다.&amp;nbsp;유사도&amp;nbsp;값은&amp;nbsp;0에서&amp;nbsp;1&amp;nbsp;사이의&amp;nbsp;실수이므로,&amp;nbsp;이를&amp;nbsp;다루기&amp;nbsp;쉽도록&amp;nbsp;65536을&amp;nbsp;곱한&amp;nbsp;후에&amp;nbsp;소수점&amp;nbsp;아래를&amp;nbsp;버리고&amp;nbsp;정수부만&amp;nbsp;출력한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;예제 입출력&lt;/h4&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;427&quot; data-origin-height=&quot;243&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ebDpSL/dJMcagrTalk/n8nIuKfbUoQHZKM2ki1McK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ebDpSL/dJMcagrTalk/n8nIuKfbUoQHZKM2ki1McK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ebDpSL/dJMcagrTalk/n8nIuKfbUoQHZKM2ki1McK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FebDpSL%2FdJMcagrTalk%2Fn8nIuKfbUoQHZKM2ki1McK%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;427&quot; height=&quot;243&quot; data-origin-width=&quot;427&quot; data-origin-height=&quot;243&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;3. 접근 기준&lt;/h3&gt;
&lt;p data-end=&quot;442&quot; data-start=&quot;409&quot; data-ke-size=&quot;size16&quot;&gt;이 문제는 일반 집합 문제가 아니라 &lt;b&gt;다중집합 문제&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-end=&quot;482&quot; data-start=&quot;444&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 같은 원소가 여러 번 나오면, 그 개수까지 반영해야 한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;A = [aa, aa, ab]
B = [aa, ab, ab]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;536&quot; data-start=&quot;531&quot; data-ke-size=&quot;size16&quot;&gt;이 경우:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;교집합: aa 1개, ab 1개
합집합: aa 2개, ab 2개&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;612&quot; data-start=&quot;587&quot; data-ke-size=&quot;size16&quot;&gt;처럼 &lt;b&gt;단순히 set으로 처리하면 안 된다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;634&quot; data-start=&quot;614&quot; data-ke-size=&quot;size16&quot;&gt;따라서 다음 기준으로 접근해야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;739&quot; data-start=&quot;636&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;653&quot; data-start=&quot;636&quot;&gt;문자열을 두 글자씩 자른다.&lt;/li&gt;
&lt;li data-end=&quot;679&quot; data-start=&quot;654&quot;&gt;두 글자가 모두 알파벳인 경우만 사용한다.&lt;/li&gt;
&lt;li data-end=&quot;708&quot; data-start=&quot;680&quot;&gt;대소문자는 구분하지 않으므로 소문자로 통일한다.&lt;/li&gt;
&lt;li data-end=&quot;739&quot; data-start=&quot;709&quot;&gt;원소별 개수를 기준으로 교집합과 합집합을 계산한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-end=&quot;784&quot; data-start=&quot;741&quot; data-ke-style=&quot;style2&quot;&gt;문자열 처리 + Counter를 활용한 다중집합 계산 문제다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size23&quot; data-start=&quot;403&quot; data-end=&quot;464&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;4. 사용한 패턴 / 알고리즘&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문자열 처리&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;860&quot; data-start=&quot;827&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;839&quot; data-start=&quot;827&quot;&gt;두 글자씩 슬라이싱&lt;/li&gt;
&lt;li data-end=&quot;851&quot; data-start=&quot;840&quot;&gt;알파벳 여부 확인&lt;/li&gt;
&lt;li data-end=&quot;860&quot; data-start=&quot;852&quot;&gt;소문자 변환&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Counter&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;907&quot; data-start=&quot;875&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;887&quot; data-start=&quot;875&quot;&gt;원소별 개수 카운트&lt;/li&gt;
&lt;li data-end=&quot;907&quot; data-start=&quot;888&quot;&gt;다중집합의 교집합과 합집합 계산&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;5. 핵심 아이디어&lt;/h2&gt;
&lt;p data-end=&quot;990&quot; data-start=&quot;932&quot; data-ke-size=&quot;size16&quot;&gt;핵심은 Counter를 단순 카운트 용도가 아니라, &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;다중집합 연산 도구&lt;/b&gt;&lt;/span&gt;로 사용하는 것이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;c1 = Counter(list1)
c2 = Counter(list2)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1055&quot; data-start=&quot;1047&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 만들면:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;llvm&quot;&gt;&lt;code&gt;c1 &amp;amp; c2&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1110&quot; data-start=&quot;1080&quot; data-ke-size=&quot;size16&quot;&gt;는 각 원소 개수의 최솟값을 기준으로 교집합을 만든다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;llvm&quot;&gt;&lt;code&gt;c1 | c2&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1165&quot; data-start=&quot;1135&quot; data-ke-size=&quot;size16&quot;&gt;는 각 원소 개수의 최댓값을 기준으로 합집합을 만든다.&lt;/p&gt;
&lt;p data-end=&quot;1223&quot; data-start=&quot;1167&quot; data-ke-size=&quot;size16&quot;&gt;즉, &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;직접 min, max를 계산하지 않아도 Counter가 다중집합 기준으로 처리해준다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;6. 풀이 코드&lt;/h2&gt;
&lt;pre id=&quot;code_1778207554019&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from collections import Counter

# 문자열을 두 글자씩 끊어 다중집합 리스트로 만드는 함수
def make_multiset(s):
    s = s.lower()  # 대소문자 구분하지 않으므로 소문자로 통일
    result = []

    # 두 글자씩 확인해야 하므로 len(s) - 1까지 반복
    for i in range(len(s) - 1):
        a, b = s[i], s[i + 1]

        # 두 글자 모두 알파벳인 경우만 사용
        if a.isalpha() and b.isalpha():
            result.append(a + b)

    return result

def solution(str1, str2):
    # 두 문자열을 각각 다중집합 리스트로 변환
    list1 = make_multiset(str1)
    list2 = make_multiset(str2)

    # Counter는 각 원소가 몇 번 나왔는지 저장한다
    c1 = Counter(list1)
    c2 = Counter(list2)

    # Counter의 &amp;amp; 연산은 다중집합의 교집합이다
    # 같은 원소가 있으면 더 작은 개수를 선택한다
    inter = sum((c1 &amp;amp; c2).values())

    # Counter의 | 연산은 다중집합의 합집합이다
    # 같은 원소가 있으면 더 큰 개수를 선택한다
    union = sum((c1 | c2).values())

    # 두 집합이 모두 공집합이면 유사도는 1
    if union == 0:
        return 65536

    # 자카드 유사도 * 65536 후 소수점 버림
    return int((inter / union) * 65536)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;2230&quot; data-start=&quot;2207&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2230&quot; data-start=&quot;2207&quot; data-ke-size=&quot;size16&quot;&gt;이 코드에서 가장 중요한 부분은 다음이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;inter = sum((c1 &amp;amp; c2).values())
union = sum((c1 | c2).values())&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2395&quot; data-start=&quot;2311&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Counter는 원소별 개수를 저장하는 딕셔너리처럼 보이지만, &amp;amp;, | 연산을 사용하면 다중집합의 교집합과 합집합을 바로 구할 수 있다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;2403&quot; data-start=&quot;2397&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;c1 = Counter([&quot;aa&quot;, &quot;aa&quot;, &quot;ab&quot;])
c2 = Counter([&quot;aa&quot;, &quot;ab&quot;, &quot;ab&quot;])&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2489&quot; data-start=&quot;2486&quot; data-ke-size=&quot;size16&quot;&gt;이면:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;llvm&quot;&gt;&lt;code&gt;c1 &amp;amp; c2&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2518&quot; data-start=&quot;2514&quot; data-ke-size=&quot;size16&quot;&gt;결과는:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;Counter({&quot;aa&quot;: 1, &quot;ab&quot;: 1})&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2566&quot; data-start=&quot;2563&quot; data-ke-size=&quot;size16&quot;&gt;이고,&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;llvm&quot;&gt;&lt;code&gt;c1 | c2&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2595&quot; data-start=&quot;2591&quot; data-ke-size=&quot;size16&quot;&gt;결과는:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;Counter({&quot;aa&quot;: 2, &quot;ab&quot;: 2})&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2643&quot; data-start=&quot;2640&quot; data-ke-size=&quot;size16&quot;&gt;이다. 따라서 values()를 합치면 각각 교집합 크기와 합집합 크기가 된다.&lt;/p&gt;
&lt;h3 data-end=&quot;2723&quot; data-start=&quot;2694&quot; data-ke-size=&quot;size23&quot;&gt;번외) Counter 특성을 몰랐을 때의 풀이&lt;/h3&gt;
&lt;p data-end=&quot;2809&quot; data-start=&quot;2725&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 &lt;b&gt;Counter의 &amp;amp;, | 연산을 몰랐기 때문에 교집합과 합집합 원소를 직접 구한 뒤, min, max로 개수를 계산했다&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1778207717712&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from collections import Counter

# 문자열을 두 글자씩 끊어 다중집합 리스트 생성
def make_multiset(s):
    result = []

    # 두 글자씩 확인
    for i in range(len(s) - 1):

        # 현재 두 글자 추출
        pair = s[i:i + 2]

        # 두 글자가 모두 알파벳인 경우만 사용
        if pair.isalpha():
            result.append(pair.lower())  # 대소문자 통일

    return result


def solution(str1, str2):

    # 두 문자열을 다중집합 형태로 변환
    list1 = make_multiset(str1)
    list2 = make_multiset(str2)

    # 각 원소 개수 카운트
    count1 = Counter(list1)
    count2 = Counter(list2)

    # 중복 제거한 원소 집합 생성
    keys1 = set(count1.keys())
    keys2 = set(count2.keys())

    # 교집합 후보
    common_keys = keys1 &amp;amp; keys2

    # 합집합 후보
    all_keys = keys1 | keys2

    # 교집합 크기 계산
    # 같은 원소 개수 중 작은 값 사용
    intersection = 0
    for key in common_keys:
        intersection += min(count1[key], count2[key])

    # 합집합 크기 계산
    # 같은 원소 개수 중 큰 값 사용
    union = 0
    for key in all_keys:
        union += max(count1[key], count2[key])

    # 두 집합이 모두 공집합인 경우
    if union == 0:
        return 65536

    # 자카드 유사도 계산
    return int((intersection / union) * 65536)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;4106&quot; data-start=&quot;4094&quot; data-ke-size=&quot;size23&quot;&gt;두 풀이의 차이&lt;/h3&gt;
&lt;p data-end=&quot;4163&quot; data-start=&quot;4108&quot; data-ke-size=&quot;size16&quot;&gt;첫 번째 풀이도 정답은 맞다. 다만 Counter의 다중집합 연산을 직접 구현한 형태에 가깝다.&lt;/p&gt;
&lt;p data-end=&quot;4176&quot; data-start=&quot;4165&quot; data-ke-size=&quot;size16&quot;&gt;차이는 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;4385&quot; data-start=&quot;4178&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;4270&quot; data-start=&quot;4178&quot;&gt;직접 풀이
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;4270&quot; data-start=&quot;4190&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;4215&quot; data-start=&quot;4190&quot;&gt;set으로 교집합, 합집합 후보를 구함&lt;/li&gt;
&lt;li data-end=&quot;4246&quot; data-start=&quot;4218&quot;&gt;각 원소마다 min, max를 직접 계산&lt;/li&gt;
&lt;li data-end=&quot;4270&quot; data-start=&quot;4249&quot;&gt;다중집합 개념을 코드로 풀어쓴 형태&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;4385&quot; data-start=&quot;4272&quot;&gt;Counter 활용 풀이
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;4385&quot; data-start=&quot;4292&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;4321&quot; data-start=&quot;4292&quot;&gt;Counter &amp;amp; Counter로 교집합 계산&lt;/li&gt;
&lt;li data-end=&quot;4353&quot; data-start=&quot;4324&quot;&gt;Counter | Counter로 합집합 계산&lt;/li&gt;
&lt;li data-end=&quot;4385&quot; data-start=&quot;4356&quot;&gt;내부에서 min, max 처리를 대신 해줌&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;4397&quot; data-start=&quot;4387&quot; data-ke-size=&quot;size16&quot;&gt;한 줄로 정리하면:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;ldif&quot;&gt;&lt;code&gt;직접 풀이: 다중집합 연산을 직접 구현한 코드
Counter 풀이: Counter 기능으로 다중집합 연산을 처리한 코드&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;7. 핵심 로직 요약&lt;/h2&gt;
&lt;p data-end=&quot;4566&quot; data-start=&quot;4503&quot; data-ke-size=&quot;size16&quot;&gt;이 문제의 핵심은 &lt;b&gt;문자열을 다중집합으로 바꾼 뒤, 교집합과 합집합 크기를 개수 기준으로 계산하는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;4704&quot; data-start=&quot;4568&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;4585&quot; data-start=&quot;4568&quot;&gt;문자열을 두 글자씩 자른다.&lt;/li&gt;
&lt;li data-end=&quot;4611&quot; data-start=&quot;4586&quot;&gt;두 글자가 모두 알파벳인 경우만 사용한다.&lt;/li&gt;
&lt;li data-end=&quot;4624&quot; data-start=&quot;4612&quot;&gt;소문자로 통일한다.&lt;/li&gt;
&lt;li data-end=&quot;4647&quot; data-start=&quot;4625&quot;&gt;Counter로 원소별 개수를 센다.&lt;/li&gt;
&lt;li data-end=&quot;4667&quot; data-start=&quot;4648&quot;&gt;&amp;amp;로 교집합 크기를 구한다.&lt;/li&gt;
&lt;li data-end=&quot;4687&quot; data-start=&quot;4668&quot;&gt;|로 합집합 크기를 구한다.&lt;/li&gt;
&lt;li data-end=&quot;4704&quot; data-start=&quot;4688&quot;&gt;자카드 유사도를 계산한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;4761&quot; data-start=&quot;4706&quot; data-ke-size=&quot;size16&quot;&gt;결국, &lt;b&gt;&amp;ldquo;문자열 파싱 + 다중집합 처리 + 자카드 유사도 계산&amp;rdquo; &lt;/b&gt;이 흐름이 핵심이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot; data-section-id=&quot;1yk7ghc&quot; data-start=&quot;2298&quot; data-end=&quot;2310&quot;&gt;8. 마치며&lt;/h2&gt;
&lt;p data-end=&quot;4859&quot; data-start=&quot;4794&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 set을 쓰면 될 것처럼 보였지만, 이 문제는 같은 원소가 여러 번 나오는 &lt;b&gt;다중집합&lt;/b&gt; 문제였다.&lt;/p&gt;
&lt;p data-end=&quot;4898&quot; data-start=&quot;4861&quot; data-ke-size=&quot;size16&quot;&gt;그래서 단순 집합 연산으로 처리하면 개수가 사라져서 틀릴 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;4916&quot; data-start=&quot;4900&quot; data-ke-size=&quot;size16&quot;&gt;풀면서 가장 크게 느낀 점은:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;Counter는 단순히 개수 세는 용도만 있는 게 아니다&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4971&quot; data-start=&quot;4963&quot; data-ke-size=&quot;size16&quot;&gt;라는 점이었다.&lt;/p&gt;
&lt;p data-end=&quot;5042&quot; data-start=&quot;4973&quot; data-ke-size=&quot;size16&quot;&gt;특히 &lt;b&gt;Counter의 &amp;amp;, | 연산이 다중집합의 교집합, 합집합을 처리해준다&lt;/b&gt;는 걸 알면 코드가 훨씬 짧아진다.&lt;/p&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;5099&quot; data-start=&quot;5044&quot; data-ke-size=&quot;size16&quot;&gt;결국 이 문제는 문자열 처리보다도, &lt;b&gt;다중집합을 어떻게 코드로 표현할지&lt;/b&gt;가 핵심인 문제였다.&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;color: #333333; text-align: start;&quot;&gt;
&lt;div&gt;
&lt;div id=&quot;reaction-63&quot; data-tistory-react-app=&quot;Reaction&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;</description>
      <author>jjaehyeok</author>
      <guid isPermaLink="true">https://jjaehyeok.tistory.com/67</guid>
      <comments>https://jjaehyeok.tistory.com/67#entry67comment</comments>
      <pubDate>Fri, 8 May 2026 11:39:03 +0900</pubDate>
    </item>
    <item>
      <title>프로그래머스 Lv.2 | Python | 전화번호 목록</title>
      <link>https://jjaehyeok.tistory.com/66</link>
      <description>&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;1. 문제 정보&lt;/h2&gt;
&lt;figure id=&quot;og_1778206118636&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;프로그래머스&quot; data-og-description=&quot;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42577&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cVgKSF/dJMb9kmgSB9/d2xMlV2biy3rkoalxnIua1/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/bhxRfV/dJMb86O5WnU/SKhVSKQMUFjCufYekfWdw0/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42577&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42577&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cVgKSF/dJMb9kmgSB9/d2xMlV2biy3rkoalxnIua1/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/bhxRfV/dJMb86O5WnU/SKhVSKQMUFjCufYekfWdw0/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960');&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;프로그래머스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;난이도: Lv.2&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;2. 문제 설명&lt;/h2&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;전화번호부에 적힌 전화번호 중, 한 번호가 다른 번호의 접두어인 경우가 있는지 확인하려 합니다.전화번호가 다음과 같을 경우, 구조대 전화번호는 영석이의 전화번호의 접두사입니다.&lt;br /&gt;&lt;br /&gt;구조대 : 119박준영 : 97 674 223지영석 : 11 9552 4421&lt;br /&gt;&lt;br /&gt;전화번호부에 적힌 전화번호를 담은 배열 phone_book 이 solution 함수의 매개변수로 주어질 때, 어떤 번호가 다른 번호의 접두어인 경우가 있으면 false를 그렇지 않으면 true를 return 하도록 solution 함수를 작성해주세요.&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;501&quot; data-origin-height=&quot;248&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ddFHlI/dJMcahxApWS/C0tTjMM4kWenA0xS0hGQY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ddFHlI/dJMcahxApWS/C0tTjMM4kWenA0xS0hGQY0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ddFHlI/dJMcahxApWS/C0tTjMM4kWenA0xS0hGQY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FddFHlI%2FdJMcahxApWS%2FC0tTjMM4kWenA0xS0hGQY0%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;453&quot; height=&quot;224&quot; data-origin-width=&quot;501&quot; data-origin-height=&quot;248&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;제한 조건&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;phone_book의 길이는 1 이상 1,000,000 이하입니다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;같은 전화번호가 중복해서 들어있지 않습니다.&lt;/li&gt;
&lt;li&gt;각 전화번호의 길이는 1 이상 20 이하입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;3. 접근 기준&lt;/h3&gt;
&lt;p data-end=&quot;443&quot; data-start=&quot;383&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 모든 번호를 서로 비교해야 할 것처럼 보인다. 하지만 &lt;b&gt;실제로는 모든 조합을 비교할 필요가 없다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;459&quot; data-start=&quot;445&quot; data-ke-size=&quot;size16&quot;&gt;핵심은 문자열을 정렬하면:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;접두어 관계인 문자열끼리 서로 가까워진다&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;515&quot; data-start=&quot;509&quot; data-ke-size=&quot;size16&quot;&gt;는 점이다.&lt;/p&gt;
&lt;p data-end=&quot;523&quot; data-start=&quot;517&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[&quot;119&quot;, &quot;1195524421&quot;, &quot;97674223&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;624&quot; data-start=&quot;586&quot; data-ke-size=&quot;size16&quot;&gt;처럼 정렬되면,&lt;br /&gt;접두어 여부는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;인접한 문자열끼리만 확인&lt;/b&gt;&lt;/span&gt;해도 된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;658&quot; data-start=&quot;630&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;638&quot; data-start=&quot;630&quot;&gt;문자열 정렬&lt;/li&gt;
&lt;li data-end=&quot;658&quot; data-start=&quot;639&quot;&gt;바로 옆 문자열끼리 접두어 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;675&quot; data-start=&quot;660&quot; data-ke-size=&quot;size16&quot;&gt;이 흐름으로 접근하면 된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size23&quot; data-start=&quot;403&quot; data-end=&quot;464&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;4. 사용한 패턴 / 알고리즘&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;정렬 (Sorting)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;736&quot; data-start=&quot;724&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;736&quot; data-start=&quot;724&quot;&gt;문자열 사전순 정렬&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문자열 함수&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;769&quot; data-start=&quot;750&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;769&quot; data-start=&quot;750&quot;&gt;startswith() 활용&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;5. 핵심 아이디어&lt;/h2&gt;
&lt;p data-end=&quot;798&quot; data-start=&quot;794&quot; data-ke-size=&quot;size16&quot;&gt;핵심은 정렬하면 접두어 관계인 문자열이 서로 붙는다는 점이다.&lt;/p&gt;
&lt;p data-end=&quot;860&quot; data-start=&quot;858&quot; data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[&quot;12&quot;, &quot;123&quot;, &quot;567&quot;, &quot;88&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;921&quot; data-start=&quot;916&quot; data-ke-size=&quot;size16&quot;&gt;정렬 후:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[&quot;12&quot;, &quot;123&quot;, &quot;567&quot;, &quot;88&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1003&quot; data-start=&quot;977&quot; data-ke-size=&quot;size16&quot;&gt;여기서 &quot;123&quot;은 &quot;12&quot;로 시작한다.&lt;/p&gt;
&lt;p data-end=&quot;1007&quot; data-start=&quot;1005&quot; data-ke-size=&quot;size16&quot;&gt;즉:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;&quot;123&quot;.startswith(&quot;12&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1070&quot; data-start=&quot;1060&quot; data-ke-size=&quot;size16&quot;&gt;만 확인하면 된다. &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;전체를 전부 비교할 필요 없이, 정렬 후 바로 옆 문자열만 비교하면 접두어 여부를 찾을 수 있다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;6. 풀이 코드&lt;/h2&gt;
&lt;pre id=&quot;code_1778206532108&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def solution(phone_book):

    # 문자열 기준 정렬
    # 접두어 관계인 문자열끼리 가까워진다
    phone_book.sort()

    # 인접한 번호끼리만 비교
    for i in range(1, len(phone_book)):

        # 현재 문자열이 이전 문자열로 시작하면
        # 이전 번호가 접두어라는 뜻
        if phone_book[i].startswith(phone_book[i - 1]):
            return False

    return True&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;1523&quot; data-start=&quot;1513&quot; data-ke-size=&quot;size16&quot;&gt;이 코드의 핵심은:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;startswith()&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1578&quot; data-start=&quot;1565&quot; data-ke-size=&quot;size16&quot;&gt;함수와 정렬 아이디어다.&lt;/p&gt;
&lt;h4 data-end=&quot;1601&quot; data-start=&quot;1585&quot; data-ke-size=&quot;size20&quot;&gt;6-1) 왜 정렬하는가?&lt;/h4&gt;
&lt;p data-end=&quot;1624&quot; data-start=&quot;1603&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 다음 번호들이 있다고 하자.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[&quot;119&quot;, &quot;97674223&quot;, &quot;1195524421&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1692&quot; data-start=&quot;1687&quot; data-ke-size=&quot;size16&quot;&gt;정렬하면:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[&quot;119&quot;, &quot;1195524421&quot;, &quot;97674223&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1762&quot; data-start=&quot;1755&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 된다.&lt;/p&gt;
&lt;p data-end=&quot;1766&quot; data-start=&quot;1764&quot; data-ke-size=&quot;size16&quot;&gt;즉, 접두어 관계인 문자열들이 서로 붙게 된다&lt;/p&gt;
&lt;p data-end=&quot;1835&quot; data-start=&quot;1816&quot; data-ke-size=&quot;size16&quot;&gt;그래서 전체를 다 비교하지 않아도 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;현재&amp;nbsp;문자열&amp;nbsp;vs&amp;nbsp;바로&amp;nbsp;이전&amp;nbsp;문자열&lt;/b&gt;&lt;/span&gt;&amp;nbsp;비교하면 된다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-start=&quot;1585&quot; data-end=&quot;1601&quot; data-ke-size=&quot;size20&quot;&gt;6-2) startswith란?&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;문자열.startswith(문자열)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1979&quot; data-start=&quot;1970&quot; data-ke-size=&quot;size16&quot;&gt;형태로 사용한다.&lt;/p&gt;
&lt;p data-end=&quot;1983&quot; data-start=&quot;1981&quot; data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;&quot;1195524421&quot;.startswith(&quot;119&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2046&quot; data-start=&quot;2043&quot; data-ke-size=&quot;size16&quot;&gt;결과:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;True&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2082&quot; data-start=&quot;2080&quot; data-ke-size=&quot;size16&quot;&gt;즉, &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;현재 문자열이 특정 문자열로 시작하는가?&lt;/b&gt;&lt;/span&gt; 를 확인하는 함수다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-start=&quot;1585&quot; data-end=&quot;1601&quot; data-ke-size=&quot;size20&quot;&gt;6-3) 왜 인접한 값만 비교하는가?&lt;/h4&gt;
&lt;p data-end=&quot;2184&quot; data-start=&quot;2175&quot; data-ke-size=&quot;size16&quot;&gt;정렬 상태에서는 접두어 가능성이 있는 문자열이 반드시 근처에 위치한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[&quot;12&quot;, &quot;123&quot;, &quot;1235&quot;, &quot;88&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2335&quot; data-start=&quot;2300&quot; data-ke-size=&quot;size16&quot;&gt;여기서 &quot;12&quot;와 관련 있는 문자열은 바로 뒤에 몰려 있다.&lt;/p&gt;
&lt;p data-end=&quot;2341&quot; data-start=&quot;2337&quot; data-ke-size=&quot;size16&quot;&gt;그래서:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;phone_book[i]
phone_book[i - 1]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2414&quot; data-start=&quot;2402&quot; data-ke-size=&quot;size16&quot;&gt;만 비교해도 충분하다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;7. 핵심 로직 요약&lt;/h2&gt;
&lt;p data-end=&quot;2490&quot; data-start=&quot;2440&quot; data-ke-size=&quot;size16&quot;&gt;이 문제의 핵심은 &lt;b&gt;정렬 후 인접한 문자열만 비교해서 접두어 여부를 확인하는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2579&quot; data-start=&quot;2492&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2514&quot; data-start=&quot;2492&quot;&gt;전화번호를 문자열 기준으로 정렬한다.&lt;/li&gt;
&lt;li data-end=&quot;2531&quot; data-start=&quot;2515&quot;&gt;인접한 번호끼리 비교한다.&lt;/li&gt;
&lt;li data-end=&quot;2561&quot; data-start=&quot;2532&quot;&gt;현재 번호가 이전 번호로 시작하면 False 반환&lt;/li&gt;
&lt;li data-end=&quot;2579&quot; data-start=&quot;2562&quot;&gt;끝까지 없으면 True 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2625&quot; data-start=&quot;2581&quot; data-ke-size=&quot;size16&quot;&gt;결국, &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&amp;ldquo;정렬 + startswith 활용&amp;rdquo; &lt;/b&gt;&lt;/span&gt;이 구조가 핵심이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot; data-section-id=&quot;1yk7ghc&quot; data-start=&quot;2298&quot; data-end=&quot;2310&quot;&gt;8. 정리&lt;/h2&gt;
&lt;p data-end=&quot;2721&quot; data-start=&quot;2658&quot; data-ke-size=&quot;size16&quot;&gt;처음 보면 모든 번호를 전부 비교해야 할 것처럼 느껴진다.&lt;br /&gt;근데 실제로는 정렬 하나로 문제 구조가 단순해진다.&lt;/p&gt;
&lt;p data-end=&quot;2735&quot; data-start=&quot;2723&quot; data-ke-size=&quot;size16&quot;&gt;특히 핵심은 이거였다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;정렬하면 접두어 후보가 서로 붙는다&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2819&quot; data-start=&quot;2782&quot; data-ke-size=&quot;size16&quot;&gt;이 아이디어를 떠올리면 복잡한 탐색 없이 바로 해결할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;2862&quot; data-start=&quot;2821&quot; data-ke-size=&quot;size16&quot;&gt;또 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;startswith() 함수&lt;/b&gt;&lt;/span&gt;도 문자열 문제에서 굉장히 자주 나온다.&lt;/p&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;2928&quot; data-start=&quot;2864&quot; data-ke-size=&quot;size16&quot;&gt;결국 이 문제는 문자열 비교 문제라기보다, &lt;b&gt;정렬을 이용해 비교 범위를 줄이는 문제&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;h3 data-end=&quot;23&quot; data-start=&quot;0&quot; data-ke-size=&quot;size23&quot;&gt;번외) 해시(Set)를 활용한 풀이&lt;/h3&gt;
&lt;p data-end=&quot;51&quot; data-start=&quot;25&quot; data-ke-size=&quot;size16&quot;&gt;정렬 말고도 set을 이용해서 풀 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;def solution(phone_book):

    # 전화번호 존재 여부를 빠르게 확인하기 위해 set 사용
    # set의 in 연산은 평균 O(1)
    phone_set = set(phone_book)

    # 모든 전화번호 순회
    for phone in phone_book:

        # 자기 자신 전체 번호는 제외해야 하므로
        # len(phone) 전까지만 확인
        for i in range(1, len(phone)):

            # 앞부분(prefix) 생성
            prefix = phone[:i]

            # 접두어가 실제 전화번호 목록에 존재하면
            if prefix in phone_set:
                return False

    return True&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-end=&quot;547&quot; data-start=&quot;535&quot; data-ke-size=&quot;size23&quot;&gt;이 풀이의 핵심&lt;/h3&gt;
&lt;p data-end=&quot;555&quot; data-start=&quot;549&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;&quot;1195524421&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;608&quot; data-start=&quot;597&quot; data-ke-size=&quot;size16&quot;&gt;라는 번호가 있으면:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;1
11
119
1195 ...&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;669&quot; data-start=&quot;653&quot; data-ke-size=&quot;size16&quot;&gt;처럼 앞부분을 계속 잘라본다.&lt;/p&gt;
&lt;p data-end=&quot;675&quot; data-start=&quot;671&quot; data-ke-size=&quot;size16&quot;&gt;그리고:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;if prefix in phone_set&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;763&quot; data-start=&quot;727&quot; data-ke-size=&quot;size16&quot;&gt;를 통해 해당 접두어가 실제 전화번호 목록에 존재하는지 확인한다.&lt;/p&gt;
&lt;p data-end=&quot;767&quot; data-start=&quot;765&quot; data-ke-size=&quot;size16&quot;&gt;즉:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;801&quot; data-start=&quot;769&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;780&quot; data-start=&quot;769&quot;&gt;접두어 직접 생성&lt;/li&gt;
&lt;li data-end=&quot;801&quot; data-start=&quot;781&quot;&gt;set으로 빠르게 존재 여부 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;810&quot; data-start=&quot;803&quot; data-ke-size=&quot;size16&quot;&gt;이 흐름이다.&lt;/p&gt;
&lt;h3 data-end=&quot;831&quot; data-start=&quot;817&quot; data-ke-size=&quot;size23&quot;&gt;정렬 풀이와의 차이&lt;/h3&gt;
&lt;p data-end=&quot;839&quot; data-start=&quot;833&quot; data-ke-size=&quot;size16&quot;&gt;정렬 풀이:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;접두어 후보만 비교&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;921&quot; data-start=&quot;877&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;893&quot; data-start=&quot;877&quot;&gt;정렬 후 인접한 값만 확인&lt;/li&gt;
&lt;li data-end=&quot;906&quot; data-start=&quot;894&quot;&gt;코드가 짧고 직관적&lt;/li&gt;
&lt;li data-end=&quot;921&quot; data-start=&quot;907&quot;&gt;실전에서 더 자주 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;935&quot; data-start=&quot;928&quot; data-ke-size=&quot;size16&quot;&gt;set 풀이:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;접두어를 직접 생성해서 확인&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1029&quot; data-start=&quot;978&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;991&quot; data-start=&quot;978&quot;&gt;해시 활용 연습 가능&lt;/li&gt;
&lt;li data-end=&quot;1012&quot; data-start=&quot;992&quot;&gt;접두어 개념이 더 직접적으로 보임&lt;/li&gt;
&lt;li data-end=&quot;1029&quot; data-start=&quot;1013&quot;&gt;문자열 슬라이싱 반복 발생&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>CodingTest/문제 풀이(Lv1~Lv2)</category>
      <author>jjaehyeok</author>
      <guid isPermaLink="true">https://jjaehyeok.tistory.com/66</guid>
      <comments>https://jjaehyeok.tistory.com/66#entry66comment</comments>
      <pubDate>Fri, 8 May 2026 11:18:19 +0900</pubDate>
    </item>
    <item>
      <title>Kubernetes Secret 생성과 Pod에서 사용하는 방법</title>
      <link>https://jjaehyeok.tistory.com/65</link>
      <description>&lt;h1 data-end=&quot;264&quot; data-start=&quot;250&quot;&gt;1. Secret이란?&lt;/h1&gt;
&lt;p data-end=&quot;303&quot; data-start=&quot;266&quot; data-ke-size=&quot;size16&quot;&gt;Secret은 민감한 정보를 저장하는 Kubernetes 리소스다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;528&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEUxfq/dJMcadPo8ZW/k7HQuYKLCbsPYCNmgCpZO1/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEUxfq/dJMcadPo8ZW/k7HQuYKLCbsPYCNmgCpZO1/img.webp&quot; data-alt=&quot;https://www.in4it.com/blog/kubernetes-secrets-management.html&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEUxfq/dJMcadPo8ZW/k7HQuYKLCbsPYCNmgCpZO1/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEUxfq%2FdJMcadPo8ZW%2Fk7HQuYKLCbsPYCNmgCpZO1%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;528&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;528&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.in4it.com/blog/kubernetes-secrets-management.html&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;372&quot; data-start=&quot;330&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;339&quot; data-start=&quot;330&quot;&gt;DB 비밀번호&lt;/li&gt;
&lt;li data-end=&quot;349&quot; data-start=&quot;340&quot;&gt;API Key&lt;/li&gt;
&lt;li data-end=&quot;364&quot; data-start=&quot;350&quot;&gt;Access Token&lt;/li&gt;
&lt;li data-end=&quot;372&quot; data-start=&quot;365&quot;&gt;인증 정보&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;98&quot; data-start=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;Kubernetes에서 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Secret&lt;span style=&quot;color: #000000;&quot;&gt;과 ConfigMap&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;이 Pod에 연결되고, 이후 Kubernetes API Server를 통해 &lt;b&gt;ETCD에 저장&lt;/b&gt;되는 흐름을 나타낸다.&lt;/p&gt;
&lt;p data-end=&quot;98&quot; data-start=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;138&quot; data-start=&quot;100&quot; data-ke-size=&quot;size16&quot;&gt;먼저 사용자는 Secret 또는 ConfigMap 리소스를 생성한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;206&quot; data-start=&quot;140&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;178&quot; data-start=&quot;140&quot;&gt;&lt;b&gt;Secret&lt;/b&gt;&lt;br /&gt;&amp;rarr; 비밀번호, API Key 같은 민감 정보 저장&lt;/li&gt;
&lt;li data-end=&quot;206&quot; data-start=&quot;180&quot;&gt;&lt;b&gt;ConfigMap&lt;/b&gt;&lt;br /&gt;&amp;rarr; 일반 설정 값 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;256&quot; data-start=&quot;208&quot; data-ke-size=&quot;size16&quot;&gt;이후 Pod는 필요한 Secret이나 ConfigMap을 참조하여 값을 사용하게 된다.&lt;/p&gt;
&lt;p data-end=&quot;279&quot; data-start=&quot;258&quot; data-ke-size=&quot;size16&quot;&gt;그리고 Kubernetes 내부에서는:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Pod &amp;rarr; API Server &amp;rarr; ETCD&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;338&quot; data-start=&quot;318&quot; data-ke-size=&quot;size16&quot;&gt;구조로 리소스 정보가 저장&amp;middot;관리된다.&lt;/p&gt;
&lt;p data-end=&quot;344&quot; data-start=&quot;340&quot; data-ke-size=&quot;size16&quot;&gt;여기서:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;439&quot; data-start=&quot;346&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;385&quot; data-start=&quot;346&quot;&gt;API Server&lt;br /&gt;&amp;rarr; Kubernetes의 중앙 관리 컴포넌트&lt;/li&gt;
&lt;li data-end=&quot;439&quot; data-start=&quot;387&quot;&gt;ETCD&lt;br /&gt;&amp;rarr; Kubernetes 클러스터 상태 정보를 저장하는 Key-Value 저장소&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;450&quot; data-start=&quot;441&quot; data-ke-size=&quot;size16&quot;&gt;역할을 담당한다.&lt;/p&gt;
&lt;p data-end=&quot;450&quot; data-start=&quot;441&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;561&quot; data-start=&quot;464&quot; data-ke-size=&quot;size16&quot;&gt;다만 중요한 점은, Kubernetes &lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;Secret&lt;/span&gt;은 기본적으로 완전한 &lt;span style=&quot;color: #ee2323;&quot;&gt;암호화 저장 기능이라기보다&lt;/span&gt; 민감 정보를 일반 &lt;span style=&quot;color: #ee2323;&quot;&gt;YAML과 분리해서 관리하기 위한 기능&lt;/span&gt;에 가깝다는 점&lt;/b&gt;&lt;/span&gt;이다.&lt;/p&gt;
&lt;p data-end=&quot;644&quot; data-start=&quot;563&quot; data-ke-size=&quot;size16&quot;&gt;Secret 값은 내부적으로 &lt;b&gt;base64 인코딩&lt;/b&gt;되어 저장되며, 이는 &lt;b&gt;암호화(encryption)가 아니라&lt;/b&gt; &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;단순 인코딩(encoding) 방식&lt;/b&gt;&lt;/span&gt;이다.&lt;/p&gt;
&lt;p data-end=&quot;691&quot; data-start=&quot;646&quot; data-ke-size=&quot;size16&quot;&gt;즉 Secret만으로 강력한 보안이 보장되는 것은 아니며, 실제 운영 환경에서는:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;743&quot; data-start=&quot;693&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;710&quot; data-start=&quot;693&quot;&gt;ETCD Encryption&lt;/li&gt;
&lt;li data-end=&quot;723&quot; data-start=&quot;711&quot;&gt;RBAC 권한 관리&lt;/li&gt;
&lt;li data-end=&quot;743&quot; data-start=&quot;724&quot;&gt;외부 Secret Manager&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;773&quot; data-start=&quot;745&quot; data-ke-size=&quot;size16&quot;&gt;같은 추가 보안 구성을 함께 사용하는 경우가 많다.&lt;/p&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;533&quot; data-start=&quot;452&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;408&quot; data-start=&quot;374&quot; data-ke-size=&quot;size16&quot;&gt;아래처럼 Pod YAML에 직접 작성하는 경우&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;env:
- name: DB_PASSWORD
  value: &quot;1234&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;536&quot; data-start=&quot;489&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;504&quot; data-start=&quot;489&quot;&gt;GitHub 노출 가능성&lt;/li&gt;
&lt;li data-end=&quot;522&quot; data-start=&quot;505&quot;&gt;YAML 공유 시 정보 유출&lt;/li&gt;
&lt;li data-end=&quot;536&quot; data-start=&quot;523&quot;&gt;운영 환경 보안 문제&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;549&quot; data-start=&quot;538&quot; data-ke-size=&quot;size16&quot;&gt;가 발생할 수 있다. 그래서 Kubernetes에서는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Secret으로 분리해서 관리&lt;/b&gt;&lt;/span&gt;한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1 data-end=&quot;612&quot; data-start=&quot;595&quot;&gt;2. Secret 생성 방법&lt;/h1&gt;
&lt;p data-end=&quot;671&quot; data-start=&quot;614&quot; data-ke-size=&quot;size16&quot;&gt;가장 기본적인 방식은 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;kubectl create secret generic&lt;/b&gt; &lt;/span&gt;명령을 사용하는 것이다.&lt;/p&gt;
&lt;h4 data-end=&quot;686&quot; data-start=&quot;673&quot; data-ke-size=&quot;size20&quot;&gt;2-1) 단일 값 저장&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;kubectl create secret generic db-secret \
  --from-literal=password=1234&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1002&quot; data-origin-height=&quot;40&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/REqQi/dJMcahj5eQC/dTBSi2x7F1ZXGvOGvkNae1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/REqQi/dJMcahj5eQC/dTBSi2x7F1ZXGvOGvkNae1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/REqQi/dJMcahj5eQC/dTBSi2x7F1ZXGvOGvkNae1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FREqQi%2FdJMcahj5eQC%2FdTBSi2x7F1ZXGvOGvkNae1%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;1002&quot; height=&quot;40&quot; data-origin-width=&quot;1002&quot; data-origin-height=&quot;40&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;789&quot; data-start=&quot;786&quot; data-ke-size=&quot;size16&quot;&gt;의미:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;ldif&quot;&gt;&lt;code&gt;Secret 이름: db-secret
key: password
value: 1234&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot; data-start=&quot;673&quot; data-end=&quot;686&quot;&gt;2-2) 여러 값 저장&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;kubectl create secret generic db-secret \
  --from-literal=username=admin \
  --from-literal=password=1234&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h1 data-end=&quot;1037&quot; data-start=&quot;1020&quot;&gt;3. Secret 확인 방법&lt;/h1&gt;
&lt;h4 data-end=&quot;1054&quot; data-start=&quot;1039&quot; data-ke-size=&quot;size20&quot;&gt;3-1) Secret 목록 확인&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;kubectl get secret&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1106&quot; data-start=&quot;1100&quot; data-ke-size=&quot;size16&quot;&gt;출력:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;542&quot; data-origin-height=&quot;61&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/teWBX/dJMcagSYY6V/WIm7g7xKhvSMKHQKhiDc9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/teWBX/dJMcagSYY6V/WIm7g7xKhvSMKHQKhiDc9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/teWBX/dJMcagSYY6V/WIm7g7xKhvSMKHQKhiDc9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FteWBX%2FdJMcagSYY6V%2FWIm7g7xKhvSMKHQKhiDc9k%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;542&quot; height=&quot;61&quot; data-origin-width=&quot;542&quot; data-origin-height=&quot;61&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1188&quot; data-start=&quot;1184&quot; data-ke-size=&quot;size16&quot;&gt;여기서:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;Opaque&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1243&quot; data-start=&quot;1222&quot; data-ke-size=&quot;size16&quot;&gt;는 일반 Secret 타입을 의미한다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot; data-start=&quot;1039&quot; data-end=&quot;1054&quot;&gt;3-2) 상세 정보 확인&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;kubectl describe secret db-secret&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1325&quot; data-start=&quot;1322&quot; data-ke-size=&quot;size16&quot;&gt;출력:&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;693&quot; data-origin-height=&quot;238&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEyJ1I/dJMcacwicbf/SVSgYi19KUOoUvzww8CUsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEyJ1I/dJMcacwicbf/SVSgYi19KUOoUvzww8CUsK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEyJ1I/dJMcacwicbf/SVSgYi19KUOoUvzww8CUsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEyJ1I%2FdJMcacwicbf%2FSVSgYi19KUOoUvzww8CUsK%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;693&quot; height=&quot;238&quot; data-origin-width=&quot;693&quot; data-origin-height=&quot;238&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;Data
====
password: 4 bytes&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1420&quot; data-start=&quot;1398&quot; data-ke-size=&quot;size16&quot;&gt;실제 값은 &lt;span style=&quot;color: #ee2323;&quot;&gt;보안상 바로 출력되지 않는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 base64로 디코딩을 하는경우 바로 출력이 된다. 그렇기 때문에 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;누구나 해독이 가능&lt;/b&gt;&lt;/span&gt;하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;581&quot; data-origin-height=&quot;242&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/x5fie/dJMcaiXvwJs/2mqAHfK7PcyPS3wSLXP6f0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/x5fie/dJMcaiXvwJs/2mqAHfK7PcyPS3wSLXP6f0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/x5fie/dJMcaiXvwJs/2mqAHfK7PcyPS3wSLXP6f0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fx5fie%2FdJMcaiXvwJs%2F2mqAHfK7PcyPS3wSLXP6f0%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;581&quot; height=&quot;242&quot; data-origin-width=&quot;581&quot; data-origin-height=&quot;242&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h1 data-end=&quot;1452&quot; data-start=&quot;1427&quot;&gt;4. Pod에서 Secret 사용하는 방법&lt;/h1&gt;
&lt;p data-end=&quot;1480&quot; data-start=&quot;1454&quot; data-ke-size=&quot;size16&quot;&gt;Secret은 보통 두 가지 방식으로 사용한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1524&quot; data-start=&quot;1482&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1511&quot; data-start=&quot;1482&quot;&gt;&lt;b&gt;환경 변수(Environment Variable)&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1524&quot; data-start=&quot;1512&quot;&gt;&lt;b&gt;Volume 마운트&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h1 data-end=&quot;1550&quot; data-start=&quot;1531&quot;&gt;5. 환경 변수로 사용하는 방법&lt;/h1&gt;
&lt;p data-end=&quot;55&quot; data-start=&quot;39&quot; data-ke-size=&quot;size16&quot;&gt;먼저 Secret을 생성한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;kubectl create secret generic db-secret \
  --from-literal=username=admin \
  --from-literal=password=1234&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;225&quot; data-start=&quot;189&quot; data-ke-size=&quot;size16&quot;&gt;이후 Pod YAML에서 Secret 값을 환경 변수로 연결한다.&lt;/p&gt;
&lt;h4 data-end=&quot;1581&quot; data-start=&quot;1570&quot; data-ke-size=&quot;size20&quot;&gt;5-1) Pod YAML&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  name: secret-pod
spec:
  containers:
  - name: nginx
    image: nginx
    env:
    - name: DB_USERNAME
      valueFrom:
        secretKeyRef:
          name: db-secret
          key: username

    - name: DB_PASSWORD
      valueFrom:
        secretKeyRef:
          name: db-secret
          key: password&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot; data-start=&quot;1570&quot; data-end=&quot;1581&quot;&gt;5-2) 코드 설명&lt;/h4&gt;
&lt;p data-end=&quot;670&quot; data-start=&quot;618&quot; data-ke-size=&quot;size16&quot;&gt;위 YAML은 db-secret에 저장된 값을 컨테이너 내부 환경 변수로 주입하는 구조다.&lt;/p&gt;
&lt;p data-end=&quot;684&quot; data-start=&quot;672&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 아래 부분:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;- name: DB_USERNAME&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;752&quot; data-start=&quot;731&quot; data-ke-size=&quot;size16&quot;&gt;는 컨테이너 내부 환경 변수 이름이다.&lt;/p&gt;
&lt;p data-end=&quot;769&quot; data-start=&quot;754&quot; data-ke-size=&quot;size16&quot;&gt;즉 애플리케이션 내부에서는:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot;&gt;&lt;code&gt;echo $DB_USERNAME&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;826&quot; data-start=&quot;814&quot; data-ke-size=&quot;size16&quot;&gt;처럼 사용할 수 있다. 그리고 실제 값은 아래에서 가져온다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;secretKeyRef:
  name: db-secret
  key: username&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;939&quot; data-start=&quot;923&quot; data-ke-size=&quot;size16&quot;&gt;각 항목 의미는 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1019&quot; data-start=&quot;941&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;978&quot; data-start=&quot;941&quot;&gt;name: db-secret&lt;br /&gt;&amp;rarr; 사용할 Secret 이름&lt;/li&gt;
&lt;li data-end=&quot;1019&quot; data-start=&quot;980&quot;&gt;key: username&lt;br /&gt;&amp;rarr; Secret 내부에서 가져올 값&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1035&quot; data-start=&quot;1021&quot; data-ke-size=&quot;size16&quot;&gt;즉 Kubernetes가:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;db-secret 안의 username 값&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1092&quot; data-start=&quot;1086&quot; data-ke-size=&quot;size16&quot;&gt;을 읽어서:&lt;/p&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;DB_USERNAME=admin&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1158&quot; data-start=&quot;1137&quot; data-ke-size=&quot;size16&quot;&gt;형태로 컨테이너 내부에 자동 주입한다.&lt;/p&gt;
&lt;p data-end=&quot;1202&quot; data-start=&quot;1185&quot; data-ke-size=&quot;size16&quot;&gt;최종적으로 컨테이너 내부에서는:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;DB_USERNAME=admin
DB_PASSWORD=1234&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1283&quot; data-start=&quot;1264&quot; data-ke-size=&quot;size16&quot;&gt;환경 변수를 사용할 수 있게 된다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot; data-start=&quot;1570&quot; data-end=&quot;1581&quot;&gt;5-3) 환경변수 확인&lt;/h4&gt;
&lt;p data-end=&quot;2063&quot; data-start=&quot;2045&quot; data-ke-size=&quot;size16&quot;&gt;Pod 내부에서 확인할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;kubectl exec -it secret-pod -- env&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2131&quot; data-start=&quot;2125&quot; data-ke-size=&quot;size16&quot;&gt;출력 예시:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;DB_USERNAME=admin
DB_PASSWORD=1234&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h1 data-end=&quot;2219&quot; data-start=&quot;2198&quot;&gt;6. Volume으로 사용하는 방법&lt;/h1&gt;
&lt;p data-end=&quot;2245&quot; data-start=&quot;2221&quot; data-ke-size=&quot;size16&quot;&gt;Secret을 파일처럼 마운트할 수도 있다.&lt;/p&gt;
&lt;h4 data-end=&quot;2258&quot; data-start=&quot;2247&quot; data-ke-size=&quot;size20&quot;&gt;6-1) Pod YAML&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  name: secret-volume-pod
spec:
  containers:
  - name: nginx
    image: nginx
    volumeMounts:
    - name: secret-volume
      mountPath: &quot;/etc/secret&quot;

  volumes:
  - name: secret-volume
    secret:
      secretName: db-secret&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot; data-start=&quot;2247&quot; data-end=&quot;2258&quot;&gt;6-2) Secret 파일 확인&lt;/h4&gt;
&lt;p data-end=&quot;2586&quot; data-start=&quot;2574&quot; data-ke-size=&quot;size16&quot;&gt;Pod 내부에서 확인:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;kubectl exec -it secret-volume-pod -- ls /etc/secret&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2669&quot; data-start=&quot;2666&quot; data-ke-size=&quot;size16&quot;&gt;출력:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;password
username&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2723&quot; data-start=&quot;2714&quot; data-ke-size=&quot;size16&quot;&gt;파일 내용 확인:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;kubectl exec -it secret-volume-pod -- cat /etc/secret/password&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2816&quot; data-start=&quot;2813&quot; data-ke-size=&quot;size16&quot;&gt;출력:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1234&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2872&quot; data-start=&quot;2848&quot; data-ke-size=&quot;size16&quot;&gt;즉 Secret 값이 파일 형태로 저장된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h1 data-end=&quot;2905&quot; data-start=&quot;2879&quot;&gt;7. Secret이 내부적으로 저장되는 방식&lt;/h1&gt;
&lt;p data-end=&quot;2939&quot; data-start=&quot;2907&quot; data-ke-size=&quot;size16&quot;&gt;Secret은 내부적으로 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;base64 인코딩&lt;/b&gt;&lt;/span&gt;되어 저장된다.&lt;/p&gt;
&lt;p data-end=&quot;2943&quot; data-start=&quot;2941&quot; data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;echo -n &quot;1234&quot; | base64&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2997&quot; data-start=&quot;2994&quot; data-ke-size=&quot;size16&quot;&gt;출력:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;MTIzNA==&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3040&quot; data-start=&quot;3033&quot; data-ke-size=&quot;size16&quot;&gt;중요한 점은: &lt;b&gt;base64는 암호화가 아니라 &lt;span style=&quot;color: #ee2323;&quot;&gt;단순 인코딩&lt;/span&gt;이라는 점이다. Secret만으로 완전한 보안이 보장되는 것은 아니다&lt;/b&gt;.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h1 data-end=&quot;3418&quot; data-start=&quot;3409&quot;&gt;8. 마치며&lt;/h1&gt;
&lt;p data-end=&quot;3496&quot; data-start=&quot;3420&quot; data-ke-size=&quot;size16&quot;&gt;Kubernetes에서는 &lt;b&gt;비밀번호나 API Key&lt;/b&gt; 같은 민감 정보를 직접 YAML에 작성하기보다 &lt;b&gt;Secret 리소스로 분리&lt;/b&gt;해서 관리한다.&lt;/p&gt;
&lt;p data-end=&quot;3509&quot; data-start=&quot;3498&quot; data-ke-size=&quot;size16&quot;&gt;그리고 Pod에서는:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3527&quot; data-start=&quot;3511&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3518&quot; data-start=&quot;3511&quot;&gt;환경 변수&lt;/li&gt;
&lt;li data-end=&quot;3527&quot; data-start=&quot;3519&quot;&gt;Volume&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;3553&quot; data-start=&quot;3529&quot; data-ke-size=&quot;size16&quot;&gt;방식으로 Secret 값을 주입해 사용한다.&lt;/p&gt;
&lt;p data-end=&quot;3566&quot; data-start=&quot;3555&quot; data-ke-size=&quot;size16&quot;&gt;이 구조를 이해하면:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3659&quot; data-start=&quot;3568&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3596&quot; data-start=&quot;3568&quot;&gt;왜 ConfigMap과 Secret이 분리되는지&lt;/li&gt;
&lt;li data-end=&quot;3624&quot; data-start=&quot;3597&quot;&gt;왜 운영 환경에서 Secret 관리가 중요한지&lt;/li&gt;
&lt;li data-end=&quot;3659&quot; data-start=&quot;3625&quot;&gt;Kubernetes가 설정과 실행 환경을 어떻게 분리하는지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;3678&quot; data-start=&quot;3661&quot; data-ke-size=&quot;size16&quot;&gt;를 자연스럽게 이해할 수 있다.&lt;/p&gt;</description>
      <category>Data Engineering/Kubernetes</category>
      <author>jjaehyeok</author>
      <guid isPermaLink="true">https://jjaehyeok.tistory.com/65</guid>
      <comments>https://jjaehyeok.tistory.com/65#entry65comment</comments>
      <pubDate>Thu, 7 May 2026 17:17:04 +0900</pubDate>
    </item>
  </channel>
</rss>