<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>kyeongjun-dev.log</title>
        <link>https://velog.io/</link>
        <description>DevOps로 일하고 있습니다</description>
        <lastBuildDate>Sat, 04 Apr 2026 15:55:20 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. kyeongjun-dev.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/kyeongjun-dev" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[Claude Code #7] Hook — 훅 총정리]]></title>
            <link>https://velog.io/@kyeongjun-dev/Claude-Code-7-Hook-%ED%9B%85-%EC%B4%9D%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@kyeongjun-dev/Claude-Code-7-Hook-%ED%9B%85-%EC%B4%9D%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Sat, 04 Apr 2026 15:55:20 GMT</pubDate>
            <description><![CDATA[<h1 id="claude-code-입문-강의-요약-7">Claude Code 입문 강의 요약 #7</h1>
<blockquote>
<p>Anthropic SkillJar의 Claude Code 강의를 듣고 정리한 내용입니다.
<a href="https://anthropic.skilljar.com/claude-code-in-action">https://anthropic.skilljar.com/claude-code-in-action</a></p>
</blockquote>
<hr>
<h2 id="hook이란">Hook이란?</h2>
<p>Hook은 Claude Code가 도구(<code>Read</code>, <code>Write</code>, <code>Grep</code> 등)를 실행하기 <strong>전 또는 후</strong>에
특정 커맨드를 자동으로 실행할 수 있게 해주는 기능입니다.</p>
<p><img src="https://velog.velcdn.com/images/kyeongjun-dev/post/8a014f8a-51dd-4005-9486-fc8e9cc05fcf/image.png" alt=""></p>
<p>두 가지 종류가 있습니다.</p>
<table>
<thead>
<tr>
<th>Hook 종류</th>
<th>실행 시점</th>
</tr>
</thead>
<tbody><tr>
<td><code>PreToolUse</code></td>
<td>도구가 호출되기 <strong>전</strong></td>
</tr>
<tr>
<td><code>PostToolUse</code></td>
<td>도구가 호출된 <strong>후</strong></td>
</tr>
</tbody></table>
<hr>
<h2 id="hook-설정">Hook 설정</h2>
<p>Hook은 Claude settings 파일에 정의하며, 범위에 따라 아래 세 경로 중 하나에 추가합니다.</p>
<table>
<thead>
<tr>
<th>파일 경로</th>
<th>적용 범위</th>
</tr>
</thead>
<tbody><tr>
<td><code>~/.claude/settings.json</code></td>
<td>내 기기의 모든 프로젝트</td>
</tr>
<tr>
<td><code>.claude/settings.json</code></td>
<td>현재 프로젝트 (팀 공유)</td>
</tr>
<tr>
<td><code>.claude/settings.local.json</code></td>
<td>현재 프로젝트 (개인 설정)</td>
</tr>
</tbody></table>
<p>직접 파일에 작성하거나, Claude Code에서 <code>/hooks</code> 명령어로도 입력할 수 있습니다.</p>
<p>설정 예시는 아래와 같습니다.</p>
<pre><code class="language-json">{
  &quot;permissions&quot;: {
    &quot;allow&quot;: [&quot;mcp__playwright&quot;],
    &quot;deny&quot;: []
  },
  &quot;hooks&quot;: {
    &quot;PreToolUse&quot;: [
      {
        &quot;matcher&quot;: &quot;Read&quot;,
        &quot;hooks&quot;: [
          {
            &quot;type&quot;: &quot;command&quot;,
            &quot;command&quot;: &quot;node /home/hooks/read_hook.ts&quot;
          }
        ]
      }
    ],
    &quot;PostToolUse&quot;: [
      {
        &quot;matcher&quot;: &quot;Write|Edit|MultiEdit&quot;,
        &quot;hooks&quot;: [
          {
            &quot;type&quot;: &quot;command&quot;,
            &quot;command&quot;: &quot;node /home/hooks/edit_hook.ts&quot;
          }
        ]
      }
    ]
  }
}</code></pre>
<hr>
<h2 id="hook-정의하기">Hook 정의하기</h2>
<p>Hook을 정의하는 과정은 아래 순서로 진행합니다.</p>
<ol>
<li><code>PreToolUse</code> / <code>PostToolUse</code> 중 사용할 Hook 결정</li>
<li>어떤 Tool에 대해 설정할지 결정</li>
<li>Tool 호출 시 실행할 커맨드를 <code>settings.json</code>에 등록</li>
<li>필요 시, 커맨드 결과로 Claude에게 피드백 제공<ul>
<li><code>exit 0</code> → 정상, 계속 진행</li>
<li><code>exit 2</code> → 작업 차단(block) 후 에러 메시지 반환</li>
</ul>
</li>
</ol>
<blockquote>
<p>💡 어떤 Tool이 있는지 확인하고 싶다면 Claude에게 직접 물어볼 수 있습니다.
<code>&quot;너가 접근할 수 있는 tool 목록 전체를 알려줘&quot;</code></p>
</blockquote>
<p>주요 Tool 목록은 아래와 같습니다.</p>
<table>
<thead>
<tr>
<th>Tool</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>Agent</code></td>
<td>전문 서브에이전트 실행</td>
</tr>
<tr>
<td><code>Bash</code></td>
<td>쉘 명령 실행</td>
</tr>
<tr>
<td><code>Edit</code></td>
<td>파일 내용 수정 (exact string replace)</td>
</tr>
<tr>
<td><code>Glob</code></td>
<td>파일 패턴 검색</td>
</tr>
<tr>
<td><code>Grep</code></td>
<td>파일 내용 정규식 검색</td>
</tr>
<tr>
<td><code>Read</code></td>
<td>파일 읽기</td>
</tr>
<tr>
<td><code>Write</code></td>
<td>파일 쓰기 (신규 생성 또는 전체 덮어쓰기)</td>
</tr>
</tbody></table>
<hr>
<h2 id="hook-구현-예시-1-env-파일-읽기-차단">Hook 구현 예시 1: .env 파일 읽기 차단</h2>
<p><code>.env</code> 파일 읽기를 차단하는 Hook을 구현해봅니다.</p>
<h3 id="settingsjson-설정">settings.json 설정</h3>
<p><code>Read</code>와 <code>Grep</code> Tool에 대해 실행할 커맨드를 등록합니다.</p>
<pre><code class="language-json">&quot;PreToolUse&quot;: [
  {
    &quot;matcher&quot;: &quot;Read|Grep&quot;,
    &quot;hooks&quot;: [
      {
        &quot;type&quot;: &quot;command&quot;,
        &quot;command&quot;: &quot;python3.13 ./hooks/read_hook.py&quot;
      }
    ]
  }
]</code></pre>
<blockquote>
<p>💡 <code>command</code>를 <code>&quot;true&quot;</code>로 설정하면 해당 Hook은 항상 통과합니다. 테스트 시 유용합니다.</p>
</blockquote>
<h3 id="read_hookpy-작성">read_hook.py 작성</h3>
<pre><code class="language-python">import sys
import json
import os

data = json.load(sys.stdin)

tool_name = data.get(&quot;tool_name&quot;, &quot;&quot;)
tool_input = data.get(&quot;tool_input&quot;, {})

def is_env_file(path: str) -&gt; bool:
    basename = os.path.basename(path)
    return basename == &quot;.env&quot; or basename.startswith(&quot;.env.&quot;)

if tool_name == &quot;Read&quot;:
    file_path = tool_input.get(&quot;file_path&quot;, &quot;&quot;)
    if is_env_file(file_path):
        print(f&quot;.env 파일은 읽을 수 없습니다: {file_path}&quot;, file=sys.stderr)
        sys.exit(2)  # exit 2: 작업 차단

elif tool_name == &quot;Grep&quot;:
    path = tool_input.get(&quot;path&quot;, &quot;&quot;)
    if is_env_file(path):
        print(f&quot;.env 파일은 읽을 수 없습니다: {path}&quot;, file=sys.stderr)
        sys.exit(2)  # exit 2: 작업 차단

sys.exit(0)  # exit 0: 정상 통과</code></pre>
<p>Claude를 재실행한 뒤 <code>.env</code> 파일 읽기를 요청하면 차단됩니다.</p>
<pre><code>.env 파일 내용을 읽어서 보여줘</code></pre><p><img src="https://velog.velcdn.com/images/kyeongjun-dev/post/6443a01b-9081-49aa-8edc-f99bd7f2f499/image.png" alt=""></p>
<h3 id="⚠️--참조로-우회되는-문제">⚠️ @ 참조로 우회되는 문제</h3>
<p><code>@</code>으로 파일을 직접 참조하면 <code>PreToolUse</code> Hook을 우회할 수 있습니다.</p>
<pre><code>@.env 파일을 읽어서 보여줘</code></pre><p><img src="https://velog.velcdn.com/images/kyeongjun-dev/post/ca2c6e1c-27fe-42a6-a1a3-fcb24dda8d2b/image.png" alt=""></p>
<p>이를 방지하려면 <code>permissions.deny</code>에 명시적으로 차단 규칙을 추가해야 합니다.</p>
<pre><code class="language-json">{
  &quot;permissions&quot;: {
    &quot;allow&quot;: [&quot;mcp__playwright&quot;],
    &quot;deny&quot;: [&quot;Read(.env)&quot;, &quot;Read(.env.*)&quot;]
  }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/kyeongjun-dev/post/85cd20c5-4c67-467c-9efb-609d2ca6e15b/image.png" alt=""></p>
<hr>
<h2 id="hook-관련-주의사항-gotchas">Hook 관련 주의사항 (Gotchas)</h2>
<blockquote>
<p>Gotcha: 문서화된 대로 동작하지만, 직관에 반하거나 예상치 못한 결과를 초래하는 설계 요소</p>
</blockquote>
<p><a href="https://docs.anthropic.com/en/docs/claude-code/hooks">공식 문서</a>에는 아래와 같은 보안 권고사항이 있습니다.</p>
<ul>
<li>입력값 검증 및 sanitize — 입력 데이터를 무조건 신뢰하지 말 것</li>
<li>쉘 변수는 항상 따옴표로 감싸기 — <code>$VAR</code> 대신 <code>&quot;$VAR&quot;</code> 사용</li>
<li>경로 순회 차단 — 파일 경로에 <code>..</code> 포함 여부 확인</li>
<li>절대 경로 사용 — 스크립트 경로는 전체 경로로 지정, 프로젝트 루트는 <code>$CLAUDE_PROJECT_DIR</code> 활용</li>
<li>민감한 파일 제외 — <code>.env</code>, <code>.git/</code>, 키 파일 등 접근 차단</li>
</ul>
<h3 id="절대-경로-사용-시-팀-공유-문제">절대 경로 사용 시 팀 공유 문제</h3>
<p>절대 경로를 사용하면 <code>settings.json</code>을 팀과 공유하기 어려워집니다.
사용자마다 프로젝트 경로가 다르기 때문입니다.</p>
<p>이를 해결하는 한 가지 방법으로, <code>npm run setup</code> 같은 초기화 스크립트를 만들어서
<code>settings.example.json</code>의 <code>$PWD</code>를 현재 디렉토리로 자동 치환하는 방식을 활용할 수 있습니다.</p>
<hr>
<h2 id="hook-구현-예시-2-mypy-타입-체크-자동화">Hook 구현 예시 2: mypy 타입 체크 자동화</h2>
<h3 id="배경">배경</h3>
<p>아래 두 파일이 같은 디렉토리에 있다고 가정합니다.</p>
<pre><code class="language-python"># main.py
from schema import echo_string
echo_string(&quot;hello world&quot;)

# schema.py
def echo_string(s: str) -&gt; None:
    print(s)</code></pre>
<p>Claude에게 <code>schema.py</code>의 <code>echo_string</code>에 인자를 추가해달라고 요청합니다.</p>
<pre><code>@schema.py 파일의 echo_string에서 v: str argument를 추가해줘</code></pre><p>Claude는 <code>schema.py</code>는 잘 수정하지만, <code>main.py</code>의 호출부는 수정하지 않습니다.</p>
<pre><code class="language-python"># schema.py (수정 후)
def echo_string(s: str, v: str) -&gt; None:
    print(s)

# main.py (수정 안 됨 — 인자 1개만 전달 중)
echo_string(&quot;hello world&quot;)</code></pre>
<p>이런 타입 불일치는 Python의 <code>mypy</code>로 감지할 수 있습니다.</p>
<pre><code>mypy main.py
main.py:3: error: Missing positional argument &quot;v&quot; in call to &quot;echo_string&quot; [call-arg]
Found 1 error in 1 file (checked 1 source file)</code></pre><blockquote>
<p><strong>참고: Python 타입 체커 비교</strong></p>
<table>
<thead>
<tr>
<th>도구</th>
<th>만든 곳</th>
<th>특징</th>
</tr>
</thead>
<tbody><tr>
<td>mypy</td>
<td>Python 재단</td>
<td>가장 표준적, CI에서 많이 사용</td>
</tr>
<tr>
<td>pyright</td>
<td>Microsoft</td>
<td>VSCode에 내장, 빠르고 엄격</td>
</tr>
<tr>
<td>pylance</td>
<td>Microsoft</td>
<td>pyright 기반 VSCode 확장</td>
</tr>
<tr>
<td>pytype</td>
<td>Google</td>
<td>타입 힌트 없어도 추론 가능</td>
</tr>
</tbody></table>
<p>Python은 타입 힌트가 선택사항이라 강제되지 않습니다. mypy가 체크하려면 타입 힌트가 있어야 하며,
엄격하게 적용하려면 <code>--strict</code> 옵션이나 <code>pyproject.toml</code>에 strict 모드를 설정하는 것이 일반적입니다.</p>
</blockquote>
<h3 id="hook-설정-1">Hook 설정</h3>
<p>Python 파일이 수정될 때마다 자동으로 mypy를 실행하는 Hook을 추가합니다.</p>
<p><img src="https://velog.velcdn.com/images/kyeongjun-dev/post/b1a9e8fd-e5e8-43bf-9bfa-42f40022fae7/image.png" alt=""></p>
<p><strong>settings.json</strong></p>
<pre><code class="language-json">{
  &quot;hooks&quot;: {
    &quot;PostToolUse&quot;: [
      {
        &quot;matcher&quot;: &quot;Write|Edit&quot;,
        &quot;hooks&quot;: [
          {
            &quot;type&quot;: &quot;command&quot;,
            &quot;command&quot;: &quot;./.claude/hooks/pycheck&quot;,
            &quot;statusMessage&quot;: &quot;Running mypy type check...&quot;
          }
        ]
      }
    ]
  }
}</code></pre>
<p><strong>pycheck 스크립트</strong></p>
<pre><code class="language-bash">#!/bin/bash
# pycheck: mypy type checker hook for Claude Code
# Python 파일이 수정될 때마다 mypy를 실행해 타입 오류를 Claude에게 피드백합니다.

STDIN=$(cat)
FILE=$(echo &quot;$STDIN&quot; | jq -r &#39;.tool_input.file_path // empty&#39;)

# .py 파일이 아니면 통과
if [[ &quot;$FILE&quot; != *.py ]]; then
  exit 0
fi

# 가상환경이 있으면 활성화
SCRIPT_DIR=&quot;$(cd &quot;$(dirname &quot;$0&quot;)&quot; &amp;&amp; pwd)&quot;
PROJECT_ROOT=&quot;$(cd &quot;$SCRIPT_DIR/../..&quot; &amp;&amp; pwd)&quot;
if [ -f &quot;$PROJECT_ROOT/venv/bin/activate&quot; ]; then
  source &quot;$PROJECT_ROOT/venv/bin/activate&quot;
fi

# 프로젝트 전체에 mypy 실행 (호출부 오류까지 감지)
MYPY_OUTPUT=$(mypy &quot;$PROJECT_ROOT&quot; --ignore-missing-imports 2&gt;&amp;1)
EXIT_CODE=$?

if [ $EXIT_CODE -ne 0 ]; then
  # Claude에게 타입 오류를 피드백으로 전달
  jq -n \
    --arg output &quot;$MYPY_OUTPUT&quot; \
    --arg file &quot;$FILE&quot; \
    &#39;{
      &quot;hookSpecificOutput&quot;: {
        &quot;hookEventName&quot;: &quot;PostToolUse&quot;,
        &quot;additionalContext&quot;: (&quot;mypy found type errors in &quot; + $file + &quot;:\n&quot; + $output + &quot;\n\nPlease fix the type errors above.&quot;)
      }
    }&#39;
fi</code></pre>
<p>다시 인자 추가를 요청하면, 이번에는 Claude가 <code>main.py</code>의 호출부도 함께 수정합니다.</p>
<p><img src="https://velog.velcdn.com/images/kyeongjun-dev/post/4ddb0b8e-c321-4a8a-a99b-82f287a12a89/image.png" alt=""></p>
<hr>
<h2 id="추가로-알아두면-유용한-hook-종류">추가로 알아두면 유용한 Hook 종류</h2>
<p>강의에서 다룬 <code>PreToolUse</code>와 <code>PostToolUse</code> 외에도 아래와 같은 Hook이 존재합니다.</p>
<table>
<thead>
<tr>
<th>Hook 종류</th>
<th>실행 시점</th>
</tr>
</thead>
<tbody><tr>
<td><code>Notification</code></td>
<td>Claude가 도구 사용 권한을 요청하거나, 60초 이상 유휴 상태일 때</td>
</tr>
<tr>
<td><code>Stop</code></td>
<td>Claude가 응답을 완료했을 때</td>
</tr>
<tr>
<td><code>SubagentStop</code></td>
<td>서브에이전트(UI에서 &quot;Task&quot;로 표시)가 완료됐을 때</td>
</tr>
<tr>
<td><code>PreCompact</code></td>
<td><code>/compact</code> 또는 자동 compact가 실행되기 직전</td>
</tr>
<tr>
<td><code>UserPromptSubmit</code></td>
<td>사용자가 프롬프트를 제출했을 때 (Claude가 처리하기 전)</td>
</tr>
<tr>
<td><code>SessionStart</code></td>
<td>세션이 시작되거나 재개될 때</td>
</tr>
<tr>
<td><code>SessionEnd</code></td>
<td>세션이 종료될 때</td>
</tr>
</tbody></table>
<hr>
<h2 id="hook-입력값stdin-구조-주의사항">Hook 입력값(stdin) 구조 주의사항</h2>
<p>Hook을 작성할 때 한 가지 헷갈리는 점이 있습니다.</p>
<p><strong>커맨드로 전달되는 stdin 입력값의 구조가 Hook 종류와 matcher에 따라 달라집니다.</strong></p>
<p>예를 들어, <code>TodoWrite</code> Tool이 호출된 후 실행되는 <code>PostToolUse</code> Hook의 stdin은 아래와 같습니다.</p>
<pre><code class="language-json">{
  &quot;session_id&quot;: &quot;9ecf22fa-edf8-4332-ae85-b6d5456eda64&quot;,
  &quot;transcript_path&quot;: &quot;&quot;,
  &quot;hook_event_name&quot;: &quot;PostToolUse&quot;,
  &quot;tool_name&quot;: &quot;TodoWrite&quot;,
  &quot;tool_input&quot;: {
    &quot;todos&quot;: [{ &quot;content&quot;: &quot;write a readme&quot;, &quot;status&quot;: &quot;pending&quot;, &quot;priority&quot;: &quot;medium&quot;, &quot;id&quot;: &quot;1&quot; }]
  },
  &quot;tool_response&quot;: {
    &quot;oldTodos&quot;: [],
    &quot;newTodos&quot;: [{ &quot;content&quot;: &quot;write a readme&quot;, &quot;status&quot;: &quot;pending&quot;, &quot;priority&quot;: &quot;medium&quot;, &quot;id&quot;: &quot;1&quot; }]
  }
}</code></pre>
<p>반면, <code>Stop</code> Hook의 stdin은 훨씬 단순합니다.</p>
<pre><code class="language-json">{
  &quot;session_id&quot;: &quot;af9f50b6-f042-4773-b3e2-c3a4814765ce&quot;,
  &quot;transcript_path&quot;: &quot;&quot;,
  &quot;hook_event_name&quot;: &quot;Stop&quot;,
  &quot;stop_hook_active&quot;: false
}</code></pre>
<p>이처럼 Hook 종류와 matcher가 달라지면 stdin 구조도 크게 달라지기 때문에,
커맨드 스크립트를 작성하기 전에 <strong>실제로 어떤 데이터가 들어오는지 먼저 확인</strong>하는 것이 중요합니다.</p>
<hr>
<h2 id="stdin-구조-확인하는-방법">stdin 구조 확인하는 방법</h2>
<p>아래와 같이 stdin을 파일로 저장하는 헬퍼 Hook을 먼저 등록해두면,
실제 어떤 데이터가 들어오는지 쉽게 확인할 수 있습니다.</p>
<pre><code class="language-json">&quot;PostToolUse&quot;: [
  {
    &quot;matcher&quot;: &quot;*&quot;,
    &quot;hooks&quot;: [
      {
        &quot;type&quot;: &quot;command&quot;,
        &quot;command&quot;: &quot;jq . &gt; post-log.json&quot;
      }
    ]
  }
]</code></pre>
<p><code>matcher</code>를 <code>*</code>로 설정해 모든 Tool 호출을 잡고, stdin을 <code>post-log.json</code>에 저장합니다.
Hook을 실제로 실행해보면 <code>post-log.json</code>에 기록된 데이터를 보고
스크립트에서 어떤 필드를 참조해야 하는지 파악할 수 있습니다.</p>
<blockquote>
<p>💡 <code>PreToolUse</code>, <code>Stop</code> 등 다른 Hook 종류도 동일한 방법으로 확인할 수 있습니다.
스크립트를 작성하기 전에 이 헬퍼 Hook으로 구조를 먼저 파악하는 습관을 들이면
디버깅 시간을 크게 줄일 수 있습니다.</p>
</blockquote>
<hr>
<h2 id="전체-정리">전체 정리</h2>
<table>
<thead>
<tr>
<th>항목</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>Hook 종류</td>
<td><code>PreToolUse</code>, <code>PostToolUse</code>, <code>Notification</code>, <code>Stop</code>, <code>SubagentStop</code>, <code>PreCompact</code>, <code>UserPromptSubmit</code>, <code>SessionStart</code>, <code>SessionEnd</code></td>
</tr>
<tr>
<td>설정 위치</td>
<td><code>~/.claude/settings.json</code> / <code>.claude/settings.json</code> / <code>.claude/settings.local.json</code></td>
</tr>
<tr>
<td>exit 코드</td>
<td><code>exit 0</code> = 정상, <code>exit 2</code> = 차단</td>
</tr>
<tr>
<td>@ 우회 방지</td>
<td><code>permissions.deny</code>에 명시적 차단 규칙 추가</td>
</tr>
<tr>
<td>stdin 구조 확인</td>
<td><code>jq . &gt; post-log.json</code> 헬퍼 Hook으로 먼저 확인</td>
</tr>
<tr>
<td>실전 활용</td>
<td>파일 접근 제어, 타입 체크 자동화, 세션/응답 이벤트 처리 등</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Claude Code #6] 깃허브 연동하기 — 실제 적용 X]]></title>
            <link>https://velog.io/@kyeongjun-dev/Claude-Code-6-%EA%B9%83%ED%97%88%EB%B8%8C-%EC%97%B0%EB%8F%99%ED%95%98%EA%B8%B0-%EC%8B%A4%EC%A0%9C-%EC%A0%81%EC%9A%A9-X</link>
            <guid>https://velog.io/@kyeongjun-dev/Claude-Code-6-%EA%B9%83%ED%97%88%EB%B8%8C-%EC%97%B0%EB%8F%99%ED%95%98%EA%B8%B0-%EC%8B%A4%EC%A0%9C-%EC%A0%81%EC%9A%A9-X</guid>
            <pubDate>Sat, 04 Apr 2026 06:06:31 GMT</pubDate>
            <description><![CDATA[<h1 id="claude-code-입문-강의-요약-6">Claude Code 입문 강의 요약 #6</h1>
<blockquote>
<p>Anthropic SkillJar의 Claude Code 강의를 듣고 정리한 내용입니다.
<a href="https://anthropic.skilljar.com/claude-code-in-action">https://anthropic.skilljar.com/claude-code-in-action</a></p>
</blockquote>
<hr>
<h2 id="github-integration">Github Integration</h2>
<p>github repo에 claude를 연동해서 mention이나 PR review를 하도록 통합할 수 있습니다.
현재 구독중인 Plan과 별도로 API 사용 비용이 청구되며 Claude Code Review와는 별도 기능입니다.</p>
<blockquote>
<p>Claude Code Review (별도 관리형 서비스)
Anthropic이 최근 출시한 Code Review는 리뷰당 평균 $15~25가 청구되며, PR 크기와 코드베이스 복잡도에 따라 달라집니다. 이 비용은 플랜에 포함된 사용량과 별도로 extra usage로 청구됩니다. Claude
그리고 Code Review는 현재 Team 및 Enterprise 플랜에서만 research preview로 사용 가능합니다. Claude Pro 플랜에서는 이 기능을 쓸 수 없습니다.</p>
</blockquote>
<hr>
<h2 id="연동-및-사용방법">연동 및 사용방법</h2>
<p>claude를 실행 후, <code>/install-github-app</code>을 입력하면 연동을 진행할 수 있으며, claude code API Key가 필요합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Claude Code #5] MCP Server 사용하기 — MCP servers with Claude Code 정리]]></title>
            <link>https://velog.io/@kyeongjun-dev/Claude-Code-5-MCP-Server-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0-MCP-servers-with-Claude-Code-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@kyeongjun-dev/Claude-Code-5-MCP-Server-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0-MCP-servers-with-Claude-Code-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Sat, 04 Apr 2026 05:16:17 GMT</pubDate>
            <description><![CDATA[<h1 id="claude-code-입문-강의-요약-5">Claude Code 입문 강의 요약 #5</h1>
<blockquote>
<p>Anthropic SkillJar의 Claude Code 강의를 듣고 정리한 내용입니다.
<a href="https://anthropic.skilljar.com/claude-code-in-action">https://anthropic.skilljar.com/claude-code-in-action</a></p>
</blockquote>
<hr>
<h2 id="mcpmodel-context-protocol-server란">MCP(Model Context Protocol) Server란?</h2>
<p>MCP Server를 추가하면 Claude Code의 기능을 확장할 수 있습니다.
추가한 서버는 <strong>로컬 또는 원격(remote)</strong>으로 실행할 수 있으며,
Claude가 기본적으로 수행할 수 없는 작업(브라우저 제어, 외부 서비스 연동 등)을 가능하게 합니다.</p>
<hr>
<h2 id="playwright-mcp-server-설치하기">Playwright MCP Server 설치하기</h2>
<p>Claude 실행 전, 터미널에서 아래 명령어로 MCP Server를 추가합니다.</p>
<pre><code class="language-bash">claude mcp add playwright npx @playwright/mcp@latest</code></pre>
<p>이후 Claude를 실행합니다.</p>
<pre><code class="language-bash">claude</code></pre>
<blockquote>
<p>💡 Playwright MCP는 Claude가 실제 브라우저를 열고 조작할 수 있게 해주는 서버입니다.
E2E 테스트나 UI 검증 작업을 자동화할 때 유용합니다.</p>
</blockquote>
<hr>
<h2 id="mcp-server-권한-설정하기">MCP Server 권한 설정하기</h2>
<p>MCP Server가 실행되면 필요한 권한 허용을 요청합니다.
매번 승인하는 과정을 생략하려면 <code>.claude/settings.local.json</code>에 아래와 같이 권한을 미리 설정할 수 있습니다.
서버명은 언더바 두 개(<code>__</code>)를 구분자로 사용합니다.</p>
<pre><code class="language-json">{
  &quot;permissions&quot;: {
    &quot;allow&quot;: [&quot;mcp__playwright&quot;],
    &quot;deny&quot;: []
  }
}</code></pre>
<p>설정 후 Claude를 재시작하면 이후 실행부터는 권한 요청 없이 바로 동작합니다.</p>
<hr>
<h2 id="mcp-server-사용해보기-브라우저-자동화">MCP Server 사용해보기: 브라우저 자동화</h2>
<p>Claude를 실행한 뒤 아래와 같이 입력하면 실제 브라우저가 열리고 <code>localhost:5173</code>으로 이동합니다.</p>
<pre><code>open a browser and navigate to localhost:5173</code></pre><p><img src="https://velog.velcdn.com/images/kyeongjun-dev/post/f0028449-a163-40a1-8c14-699f30546e00/image.png" alt=""></p>
<p>이 상태에서 아래와 같이 요청하면, Claude가 직접 브라우저를 조작하며 작업을 수행합니다.</p>
<pre><code>제목이 &#39;TODO 6&#39;인 티켓을 하나 만들고, 생성된 티켓이 &#39;IN PROGRESS&#39;, &#39;DONE&#39;으로
상태 변경이 잘 되는지 확인해. 마지막에는 &#39;TO DO&#39;의 최상단으로 옮겨.</code></pre><p><img src="https://velog.velcdn.com/images/kyeongjun-dev/post/7bac8756-670d-46b1-b182-03ab769c8ebb/image.png" alt=""></p>
<h3 id="esc로-추가-정보-제공하기">ESC로 추가 정보 제공하기</h3>
<p>작업 중 Claude가 잘못된 방법으로 접근하는 경우, <code>ESC</code>로 작업을 중단하고 올바른 방법을 알려줄 수 있습니다.</p>
<p>이번 예시에서는 Claude가 티켓 상세 화면에서 상태를 변경하려 했지만,
실제로는 <strong>보드 화면에서 드래그</strong>로만 상태 변경이 가능했습니다.</p>
<p><code>ESC</code>로 중단한 뒤 아래와 같이 정보를 추가로 제공했습니다.</p>
<pre><code>보드 화면에서 티켓을 드래그해서 옮길 수 있어.</code></pre><p>이후 Claude는 드래그 방식으로 정상적으로 티켓을 이동하며 테스트를 완료했습니다.</p>
<blockquote>
<p>💡 Claude가 막히는 상황에서 <code>ESC</code>로 중단하고 힌트를 주는 패턴은
이전 포스팅의 <strong>ESC + Memory</strong> 활용법과도 연결됩니다.
반복되는 상황이라면 CLAUDE.md에 등록해두는 것을 고려해보세요.</p>
</blockquote>
<hr>
<h2 id="정리">정리</h2>
<table>
<thead>
<tr>
<th>항목</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>MCP Server 추가</td>
<td><code>claude mcp add &lt;이름&gt; &lt;실행명령&gt;</code></td>
</tr>
<tr>
<td>권한 설정</td>
<td><code>.claude/settings.local.json</code>의 <code>permissions.allow</code>에 등록</td>
</tr>
<tr>
<td>서버명 구분자</td>
<td>언더바 두 개 (<code>mcp__서버명</code>)</td>
</tr>
<tr>
<td>적용 시점</td>
<td>설정 후 Claude 재시작 필요</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Claude Code #4] 커스텀 커맨드 만들기 — Custom commands 정리]]></title>
            <link>https://velog.io/@kyeongjun-dev/Claude-Code-4-%EC%BB%A4%EC%8A%A4%ED%85%80-%EC%BB%A4%EB%A7%A8%EB%93%9C-%EB%A7%8C%EB%93%A4%EA%B8%B0-Custom-commands-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@kyeongjun-dev/Claude-Code-4-%EC%BB%A4%EC%8A%A4%ED%85%80-%EC%BB%A4%EB%A7%A8%EB%93%9C-%EB%A7%8C%EB%93%A4%EA%B8%B0-Custom-commands-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Sat, 04 Apr 2026 03:13:43 GMT</pubDate>
            <description><![CDATA[<h1 id="claude-code-입문-강의-요약-4">Claude Code 입문 강의 요약 #4</h1>
<blockquote>
<p>Anthropic SkillJar의 Claude Code 강의를 듣고 정리한 내용입니다.
<a href="https://anthropic.skilljar.com/claude-code-in-action">https://anthropic.skilljar.com/claude-code-in-action</a></p>
</blockquote>
<hr>
<h2 id="custom-command란">Custom Command란?</h2>
<p>자주 반복하는 작업을 마크다운 파일로 정의해두면, <code>/커맨드명</code> 형태로 빠르게 실행할 수 있습니다.
매번 긴 지시문을 입력할 필요 없이, 커맨드 한 줄로 동일한 작업을 재현할 수 있는 것이 핵심입니다.</p>
<hr>
<h2 id="custom-command-만들기">Custom Command 만들기</h2>
<p><code>.claude/commands/</code> 경로에 커맨드로 사용할 마크다운 파일을 생성합니다.
파일명이 곧 커맨드명이 되며, 생성 후에는 <strong>Claude를 재시작</strong>해야 적용됩니다.</p>
<p>예를 들어 <code>audit.md</code>를 생성하면 <code>/audit</code>으로 실행할 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/kyeongjun-dev/post/4536fa01-1ef9-4256-9de7-9f863c34b12a/image.png" alt=""></p>
<p>재시작 후 <code>/audit</code>을 입력하면 정의한 작업이 실행됩니다.</p>
<p><img src="https://velog.velcdn.com/images/kyeongjun-dev/post/fac6f295-97f3-4a6d-bcd5-2909303e2d94/image.png" alt=""></p>
<hr>
<h2 id="arguments로-동적-입력-받기">$ARGUMENTS로 동적 입력 받기</h2>
<p>커맨드 실행 시 인자를 전달하고 싶다면 파일 내용에 <code>$ARGUMENTS</code>를 추가하면 됩니다.</p>
<p>예를 들어 <code>.claude/commands/summary_file.md</code>를 아래와 같이 작성합니다.</p>
<pre><code>Show me the summary for: $ARGUMENTS
Showing conventions:

Place summary file in a summary directory in the same folder as the source file
Name files as [filename].summary</code></pre><p>실행할 때는 커맨드 뒤에 원하는 인자를 붙여서 입력합니다.
/summary_file the files in @src/ directory.</p>
<blockquote>
<p>💡 <code>$ARGUMENTS</code>에는 파일 경로뿐만 아니라 어떤 텍스트든 전달할 수 있습니다.
<code>@파일명</code> 형태로 특정 파일을 참조하는 것도 가능합니다.</p>
</blockquote>
<hr>
<h2 id="정리">정리</h2>
<table>
<thead>
<tr>
<th>항목</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>파일 위치</td>
<td><code>.claude/commands/파일명.md</code></td>
</tr>
<tr>
<td>실행 방법</td>
<td><code>/파일명</code></td>
</tr>
<tr>
<td>동적 입력</td>
<td>파일 내 <code>$ARGUMENTS</code> 사용, 실행 시 커맨드 뒤에 인자 입력</td>
</tr>
<tr>
<td>적용 시점</td>
<td>파일 생성 후 Claude 재시작 필요</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[Claude Code 명령어 정리요약]]></title>
            <link>https://velog.io/@kyeongjun-dev/Claude-Code-%EB%AA%85%EB%A0%B9%EC%96%B4-%EC%A0%95%EB%A6%AC%EC%9A%94%EC%95%BD</link>
            <guid>https://velog.io/@kyeongjun-dev/Claude-Code-%EB%AA%85%EB%A0%B9%EC%96%B4-%EC%A0%95%EB%A6%AC%EC%9A%94%EC%95%BD</guid>
            <pubDate>Sat, 04 Apr 2026 02:48:52 GMT</pubDate>
            <description><![CDATA[<h1 id="claude-code-명령어-총정리">Claude Code 명령어 총정리</h1>
<blockquote>
<p>시리즈 <a href="https://velog.io/@kyeongjun-dev/Claude-Code-%EC%9E%85%EB%AC%B8-%EA%B0%95%EC%9D%98-%EC%9A%94%EC%95%BD">#1</a>, <a href="https://velog.io/@kyeongjun-dev/Claude-Code-2-%EC%BD%94%EB%93%9C-%EC%88%98%EC%A0%95%EA%B3%BC-%EB%B3%80%EA%B2%BD-Making-Changes-%EC%99%84%EC%A0%84-%EC%A0%95%EB%A6%AC">#2</a>, <a href="https://velog.io/@kyeongjun-dev/Claude-Code-3-%EC%8B%A4%ED%96%89-%EC%A0%9C%EC%96%B4%EC%99%80-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8-%EA%B4%80%EB%A6%AC-Controlling-Context-%EC%A0%95%EB%A6%AC">#3</a>에서 다룬 내용을 명령어 중심으로 재정리한 글입니다.</p>
</blockquote>
<hr>
<h2 id="📁-프로젝트-초기화">📁 프로젝트 초기화</h2>
<h3 id="init"><code>/init</code></h3>
<p>새 프로젝트에서 처음 실행하는 명령어. 코드베이스 전체를 분석하고 <code>CLAUDE.md</code> 파일을 자동으로 생성한다.</p>
<pre><code class="language-bash">/init</code></pre>
<hr>
<h2 id="📝-컨텍스트--메모리-관리">📝 컨텍스트 &amp; 메모리 관리</h2>
<h3 id="--memory-mode"><code>#</code> — Memory Mode</h3>
<p><code>#</code>으로 시작하는 입력은 <code>CLAUDE.md</code>에 영구적으로 저장된다. 매번 반복해서 지시하지 않아도 Claude가 항상 해당 규칙을 따르게 된다.</p>
<pre><code class="language-bash"># Use comments sparingly. Only comment complex code.</code></pre>
<pre><code class="language-bash"># The database schema is defined in the @prisma/schema.prisma file.
  Reference it anytime you need to understand the structure of data stored in the database.</code></pre>
<h3 id="--파일-참조"><code>@</code> — 파일 참조</h3>
<p><code>@파일명</code>으로 특정 파일을 직접 멘션한다. Claude가 파일을 탐색하는 시간 없이 즉시 해당 파일을 기반으로 답변한다.</p>
<pre><code class="language-bash">How does the auth system work? @src/auth/middleware.ts</code></pre>
<hr>
<h2 id="⚙️-실행-모드">⚙️ 실행 모드</h2>
<h3 id="shift--tab-×-2--planning-mode"><code>Shift + Tab</code> × 2 — Planning Mode</h3>
<p>코드를 바로 수정하지 않고 실행 계획을 먼저 수립한다. 사용자의 승인 후 실제 작업을 진행한다.</p>
<ul>
<li>여러 파일에 걸친 리팩토링처럼 <strong>넓은 범위를 탐색</strong>해야 할 때 적합</li>
<li>여러 단계를 순서대로 진행해야 하는 작업에 유리</li>
</ul>
<h3 id="think-키워드--thinking-mode"><code>Think</code> 키워드 — Thinking Mode</h3>
<p>프롬프트에 키워드를 포함하면 Claude가 더 깊이 추론한 뒤 답변한다. Planning Mode와 함께 사용할 수 있다.</p>
<table>
<thead>
<tr>
<th>키워드</th>
<th>추론 수준</th>
</tr>
</thead>
<tbody><tr>
<td><code>Think</code></td>
<td>기본 추론</td>
</tr>
<tr>
<td><code>Think more</code></td>
<td>확장 추론</td>
</tr>
<tr>
<td><code>Think a lot</code></td>
<td>포괄적 추론</td>
</tr>
<tr>
<td><code>Think longer</code></td>
<td>장시간 추론</td>
</tr>
<tr>
<td><code>Ultrathink</code></td>
<td>최대 추론</td>
</tr>
</tbody></table>
<pre><code class="language-bash">This is a tough task, so ultrathink about the best way to implement it.</code></pre>
<ul>
<li>특정 로직 분석, 재현이 어려운 버그 추적처럼 <strong>깊이 있는 분석</strong>이 필요할 때 적합</li>
</ul>
<blockquote>
<p>⚠️ Planning Mode와 Thinking Mode 모두 일반 요청보다 <strong>토큰을 더 많이 소비</strong>한다. API를 직접 사용하는 경우 비용이 증가할 수 있으므로 꼭 필요한 상황에서만 사용하자.</p>
</blockquote>
<hr>
<h2 id="🛑-작업-제어">🛑 작업 제어</h2>
<h3 id="esc--작업-중단"><code>ESC</code> — 작업 중단</h3>
<p>진행 중인 작업을 즉시 중단한다. 이후 범위를 좁혀서 다시 요청하거나, <code>continue</code>로 이어서 진행할 수 있다.</p>
<pre><code class="language-bash"># ESC로 중단 후 범위를 좁혀 재요청
Write tests for the createSession function

# 또는 # 으로 CLAUDE.md 수정 후 이어서 진행
continue</code></pre>
<h3 id="esc-×-2--대화-rewind-되돌리기"><code>ESC</code> × 2 — 대화 Rewind (되돌리기)</h3>
<p>대화를 특정 시점으로 되돌린다. 디버깅 등으로 대화가 길어졌을 때 불필요한 컨텍스트를 제거하고 새 작업을 깔끔하게 시작할 수 있다.</p>
<ul>
<li>시점 선택 후 이전 입력값이 자동으로 채워짐</li>
<li>해당 텍스트를 수정해서 새 요청을 보내면 됨</li>
</ul>
<hr>
<h2 id="🧹-컨텍스트-초기화">🧹 컨텍스트 초기화</h2>
<p>대화가 길어지면 컨텍스트 윈도우가 가득 차 응답 품질이 저하될 수 있다. 아래 두 명령어로 정리할 수 있다.</p>
<table>
<thead>
<tr>
<th>명령어</th>
<th>동작</th>
<th>사용 상황</th>
</tr>
</thead>
<tbody><tr>
<td><code>/compact</code></td>
<td>중요 정보를 유지하며 대화를 요약</td>
<td>관련 작업을 계속 이어갈 때</td>
</tr>
<tr>
<td><code>/clear</code></td>
<td>대화 기록을 완전히 삭제</td>
<td>전혀 다른 작업을 시작할 때</td>
</tr>
</tbody></table>
<pre><code class="language-bash">/compact</code></pre>
<pre><code class="language-bash">/clear</code></pre>
<hr>
<h2 id="🔧-커스텀-커맨드">🔧 커스텀 커맨드</h2>
<h3 id="claudecommands--커스텀-커맨드-등록"><code>.claude/commands/</code> — 커스텀 커맨드 등록</h3>
<p><code>.claude/commands/</code> 경로에 마크다운 파일을 생성하면 <code>/파일명</code> 형태로 실행할 수 있는 커맨드가 된다. 생성 후 Claude를 재시작해야 적용된다.</p>
<pre><code># .claude/commands/audit.md 생성 후
/audit</code></pre><h3 id="arguments--동적-인자-전달"><code>$ARGUMENTS</code> — 동적 인자 전달</h3>
<p>커맨드 파일 내에 <code>$ARGUMENTS</code>를 삽입하면 실행 시 인자를 동적으로 전달할 수 있다.</p>
<pre><code># .claude/commands/summary_file.md 내용
Show me the summary for: $ARGUMENTS

#실행 시
/summary_file the files in @src/ directory.</code></pre><hr>
<h2 id="🪝-hook">🪝 Hook</h2>
<h3 id="hooks--hook-설정"><code>/hooks</code> — Hook 설정</h3>
<p>Claude Code에서 Hook을 대화형으로 입력하는 명령어. 직접 <code>settings.json</code>을 수정하는 것도 가능하다.</p>
<pre><code>/hooks</code></pre><h3 id="hook-설정-구조-settingsjson">Hook 설정 구조 (<code>settings.json</code>)</h3>
<p>도구 실행 전후에 커맨드를 자동 실행한다. <code>matcher</code>에 Tool명을 지정하고, <code>|</code>로 여러 Tool을 동시에 지정할 수 있다.</p>
<pre><code class="language-json">{
  &quot;hooks&quot;: {
    &quot;PreToolUse&quot;: [
      {
        &quot;matcher&quot;: &quot;Read|Grep&quot;,
        &quot;hooks&quot;: [{ &quot;type&quot;: &quot;command&quot;, &quot;command&quot;: &quot;python3 ./hooks/read_hook.py&quot; }]
      }
    ],
    &quot;PostToolUse&quot;: [
      {
        &quot;matcher&quot;: &quot;Write|Edit|MultiEdit&quot;,
        &quot;hooks&quot;: [{ &quot;type&quot;: &quot;command&quot;, &quot;command&quot;: &quot;./.claude/hooks/pycheck&quot; }]
      }
    ]
  }
}</code></pre>
<h3 id="permissionsdeny--파일-접근-차단"><code>permissions.deny</code> — 파일 접근 차단</h3>
<p><code>@</code> 참조로 Hook이 우회되는 경우를 방지하기 위해 명시적으로 접근을 차단한다.</p>
<pre><code class="language-json">{
  &quot;permissions&quot;: {
    &quot;deny&quot;: [&quot;Read(.env)&quot;, &quot;Read(.env.*)&quot;]
  }
}</code></pre>
<h3 id="stdin-구조-확인용-헬퍼-hook">stdin 구조 확인용 헬퍼 Hook</h3>
<p>Hook 스크립트 작성 전 실제 stdin 데이터를 파일로 저장해 구조를 파악할 때 사용한다.</p>
<pre><code class="language-json">{
  &quot;PostToolUse&quot;: [
    {
      &quot;matcher&quot;: &quot;*&quot;,
      &quot;hooks&quot;: [{ &quot;type&quot;: &quot;command&quot;, &quot;command&quot;: &quot;jq . &gt; post-log.json&quot; }]
    }
  ]
}</code></pre>
<hr>
<h2 id="📌-한눈에-보기">📌 한눈에 보기</h2>
<table>
<thead>
<tr>
<th>명령어</th>
<th>분류</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>/init</code></td>
<td>초기화</td>
<td>코드베이스 분석 + CLAUDE.md 생성</td>
</tr>
<tr>
<td><code># 내용</code></td>
<td>메모리</td>
<td>CLAUDE.md에 영구 저장</td>
</tr>
<tr>
<td><code>@파일명</code></td>
<td>참조</td>
<td>파일 직접 멘션</td>
</tr>
<tr>
<td><code>Shift+Tab</code> × 2</td>
<td>실행 모드</td>
<td>Planning Mode 진입</td>
</tr>
<tr>
<td><code>Think</code> / <code>Ultrathink</code></td>
<td>실행 모드</td>
<td>Thinking Mode (추론 깊이 조절)</td>
</tr>
<tr>
<td><code>ESC</code></td>
<td>제어</td>
<td>작업 중단</td>
</tr>
<tr>
<td><code>ESC</code> × 2</td>
<td>제어</td>
<td>대화 되돌리기 (Rewind)</td>
</tr>
<tr>
<td><code>/compact</code></td>
<td>컨텍스트</td>
<td>대화 요약 (중요 정보 유지)</td>
</tr>
<tr>
<td><code>/clear</code></td>
<td>컨텍스트</td>
<td>대화 기록 완전 삭제</td>
</tr>
<tr>
<td><code>.claude/commands/파일명.md</code> 생성</td>
<td>커스텀 커맨드</td>
<td><code>/파일명</code>으로 실행 가능한 커맨드 등록</td>
</tr>
<tr>
<td><code>$ARGUMENTS</code></td>
<td>커스텀 커맨드</td>
<td>커맨드 실행 시 동적 인자 전달</td>
</tr>
<tr>
<td><code>/hooks</code></td>
<td>Hook</td>
<td>Hook 대화형 설정 진입</td>
</tr>
<tr>
<td><code>PreToolUse</code> / <code>PostToolUse</code></td>
<td>Hook</td>
<td>도구 실행 전/후 커맨드 자동 실행</td>
</tr>
<tr>
<td><code>exit 0</code> / <code>exit 2</code></td>
<td>Hook</td>
<td>Hook 정상 통과 / 작업 차단</td>
</tr>
<tr>
<td><code>permissions.deny</code></td>
<td>Hook</td>
<td><code>@</code> 참조 우회 방지용 명시적 접근 차단</td>
</tr>
<tr>
<td><code>jq . &gt; post-log.json</code></td>
<td>Hook</td>
<td>stdin 구조 확인용 헬퍼 Hook</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Claude Code #3] 실행 제어와 컨텍스트 관리 — Controlling Context 정리]]></title>
            <link>https://velog.io/@kyeongjun-dev/Claude-Code-3-%EC%8B%A4%ED%96%89-%EC%A0%9C%EC%96%B4%EC%99%80-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8-%EA%B4%80%EB%A6%AC-Controlling-Context-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@kyeongjun-dev/Claude-Code-3-%EC%8B%A4%ED%96%89-%EC%A0%9C%EC%96%B4%EC%99%80-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8-%EA%B4%80%EB%A6%AC-Controlling-Context-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Wed, 25 Mar 2026 15:05:18 GMT</pubDate>
            <description><![CDATA[<h2 id="claude-code-입문-강의-요약-3">Claude Code 입문 강의 요약 #3</h2>
<blockquote>
<p>Anthropic SkillJar의 Claude Code 강의를 듣고 정리한 내용입니다.
<a href="https://anthropic.skilljar.com/claude-code-in-action">https://anthropic.skilljar.com/claude-code-in-action</a></p>
</blockquote>
<hr>
<h2 id="esc로-작업-제어하기">ESC로 작업 제어하기</h2>
<h3 id="작업-중단하기">작업 중단하기</h3>
<p><code>Write tests for the @src/lib/auth.ts file</code>을 입력하면 Claude는 한 번에 많은 양의 테스트 코드를 작성합니다.</p>
<p><img src="https://velog.velcdn.com/images/kyeongjun-dev/post/24cefacf-b9ed-4fdb-a197-5d5afd47e719/image.png" alt=""></p>
<p>한 번에 하나의 테스트만 작성하게 하고 싶다면, 작업 진행 중에 <code>ESC</code>로 중단할 수 있습니다.
이후 <code>Write tests for the createSession function</code>처럼 범위를 좁혀서 다시 요청하면 됩니다.</p>
<hr>
<h3 id="esc--memory-반복-에러-방지">ESC + Memory: 반복 에러 방지</h3>
<p>작업 중 Claude가 파일 경로를 잘못 인식해서 매번 같은 에러가 반복되는 경우,
<code>ESC</code>로 작업을 중단한 뒤 <code>#</code>(memory mode)으로 CLAUDE.md에 올바른 경로나 에러 대응 방법을 정의할 수 있습니다.</p>
<p>이후 <code>continue</code>를 입력하면 수정된 지시사항을 반영해서 작업을 이어갑니다.</p>
<blockquote>
<p>💡 일시적인 수정이 아니라, 같은 에러가 다시 발생하지 않도록 CLAUDE.md에 영구적으로 기록하는 것이 핵심입니다.</p>
</blockquote>
<hr>
<h3 id="esc--esc-대화-rewind되돌리기">ESC + ESC: 대화 Rewind(되돌리기)</h3>
<p><strong>왜 필요한가?</strong></p>
<p>예를 들어 <code>createSession</code> 함수의 테스트를 작성하는 과정에서 에러가 발생해 디버깅 대화가 길어진 상황을 가정합니다.
디버깅이 완료된 후, 이번엔 <code>getSession</code> 함수의 테스트를 작성하려고 합니다.</p>
<p>그런데 <code>createSession</code> 디버깅 과정의 대화는 <code>getSession</code> 테스트와 아무 관련이 없습니다.
불필요한 컨텍스트가 남아 있으면 Claude가 새 작업을 수행할 때 방해가 될 수 있습니다.
이때 <code>ESC + ESC</code>로 특정 시점의 대화로 되돌아갈 수 있습니다.</p>
<p><strong>사용 방법:</strong></p>
<p><code>whats in the @src/lib/auth.ts file?</code>을 입력하면 Claude는 아래와 같은 함수 목록을 포함한 결과를 반환합니다.</p>
<pre><code>Functions

createSession(userId: string, email: string)
- Creates a JWT token with 7-day expiration
- Sets an HTTP-only cookie with security flags
- Cookie is secure in production, lax sameSite policy

getSession(): Promise&lt;SessionPayload | null&gt;
- Reads the auth token from cookies
- Verifies and decodes the JWT
- Returns session payload or null if invalid/missing</code></pre><p><code>createSession</code> 함수 테스트를 요청하면 에러가 발생하고,</p>
<p><img src="https://velog.velcdn.com/images/kyeongjun-dev/post/7dc80bc2-8aa1-4e48-bb21-eb554b1edc9d/image.png" alt=""></p>
<p>Claude가 자동으로 디버깅을 진행합니다.</p>
<p><img src="https://velog.velcdn.com/images/kyeongjun-dev/post/04c0faaf-5b8b-48da-925d-80e6dc18dd6c/image.png" alt=""></p>
<p>디버깅 과정은 다음과 같이 진행됩니다.</p>
<ol>
<li>의존성 설치 후 <code>createSession</code> 테스트 실행</li>
<li><code>server-only</code> 모듈 오류 발생 → 테스트 환경에서 모킹하도록 파일 수정</li>
<li>테스트 재실행 및 성공</li>
</ol>
<p>디버깅이 완료된 후 <code>getSession</code> 테스트로 넘어가려면, 앞서 쌓인 디버깅 대화를 제거해야 합니다.
<code>ESC + ESC</code>를 누르면 되돌아갈 시점을 선택할 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/kyeongjun-dev/post/53e36f34-8bc3-460e-9b9a-ecdbf80832a5/image.png" alt=""></p>
<p>시점을 선택하면 이전 입력값이 입력창에 자동으로 채워지며,</p>
<p><img src="https://velog.velcdn.com/images/kyeongjun-dev/post/0cd1ae07-12da-4710-a76d-74e90b4f48cd/image.png" alt=""></p>
<p>해당 텍스트를 수정해서 새 요청을 보내면 됩니다.</p>
<p><img src="https://velog.velcdn.com/images/kyeongjun-dev/post/5895033c-b198-4fa9-bf1c-5c6d6ae2299a/image.png" alt=""></p>
<hr>
<h2 id="context-관리-명령어">Context 관리 명령어</h2>
<p>대화가 길어지면 컨텍스트 윈도우가 가득 차서 Claude의 응답 품질이 저하될 수 있습니다.
이때 아래 두 명령어로 컨텍스트를 정리할 수 있습니다.</p>
<table>
<thead>
<tr>
<th>명령어</th>
<th>동작</th>
<th>사용 상황</th>
</tr>
</thead>
<tbody><tr>
<td><code>/compact</code></td>
<td>중요 정보는 유지하면서 전체 대화를 요약</td>
<td>관련 작업을 계속 이어갈 때</td>
</tr>
<tr>
<td><code>/clear</code></td>
<td>대화 기록을 완전히 삭제</td>
<td>전혀 다른 작업을 시작할 때</td>
</tr>
</tbody></table>
<h3 id="compact">/compact</h3>
<p>Claude가 학습한 중요 정보(프로젝트 구조, 발견한 버그 등)를 유지하면서
전체 대화 기록을 요약해 컨텍스트 윈도우를 절약합니다.</p>
<p>아래와 같은 상황에서 유용합니다.</p>
<ul>
<li>Claude가 프로젝트에 대한 중요한 정보를 파악한 상태일 때</li>
<li>관련된 작업을 계속 이어서 진행하고 싶을 때</li>
<li>대화가 길어졌지만 중요한 컨텍스트가 포함되어 있을 때</li>
</ul>
<h3 id="clear">/clear</h3>
<p>대화 기록을 완전히 삭제하고 처음부터 시작합니다.</p>
<p>아래와 같은 상황에서 유용합니다.</p>
<ul>
<li>완전히 다른 작업을 시작할 때</li>
<li>이전 대화의 맥락이 새 작업에 방해가 될 때</li>
<li>깨끗한 상태에서 시작하고 싶을 때</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Claude Code #2] 코드 수정과 변경 — Making Changes 완전 정리]]></title>
            <link>https://velog.io/@kyeongjun-dev/Claude-Code-2-%EC%BD%94%EB%93%9C-%EC%88%98%EC%A0%95%EA%B3%BC-%EB%B3%80%EA%B2%BD-Making-Changes-%EC%99%84%EC%A0%84-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@kyeongjun-dev/Claude-Code-2-%EC%BD%94%EB%93%9C-%EC%88%98%EC%A0%95%EA%B3%BC-%EB%B3%80%EA%B2%BD-Making-Changes-%EC%99%84%EC%A0%84-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Sat, 21 Mar 2026 16:02:09 GMT</pubDate>
            <description><![CDATA[<h1 id="claude-code-입문-강의-요약-2">Claude Code 입문 강의 요약 #2</h1>
<blockquote>
<p>Anthropic SkillJar의 Claude Code 강의를 듣고 정리한 내용입니다.
<a href="https://anthropic.skilljar.com/claude-code-in-action">https://anthropic.skilljar.com/claude-code-in-action</a></p>
</blockquote>
<hr>
<h2 id="이미지로-ui-수정-요청하기">이미지로 UI 수정 요청하기</h2>
<p>Claude Code는 텍스트뿐만 아니라 <strong>이미지(스크린샷)를 붙여넣어 작업을 지시</strong>할 수 있습니다.
수정하고 싶은 UI 영역을 스크린샷으로 캡처한 뒤, Claude Code에 붙여넣고 원하는 작업을 설명하면 됩니다.</p>
<h3 id="예시-1-ui-요소-위치-변경">예시 1: UI 요소 위치 변경</h3>
<p>아래 화면에서 빨간 부분만 스크린샷으로 복사한 뒤,</p>
<p><img src="https://velog.velcdn.com/images/kyeongjun-dev/post/982af0d8-2b1d-4127-9567-4bd743633261/image.png" alt="before"></p>
<p>이미지를 붙여넣고 위치 변경을 요청하면,</p>
<p><img src="https://velog.velcdn.com/images/kyeongjun-dev/post/eac3939d-e13d-47dd-9148-f06f0ea3729f/image.png" alt="request"></p>
<p>해당 요소의 위치가 변경되어 적용됩니다.</p>
<p><img src="https://velog.velcdn.com/images/kyeongjun-dev/post/7a4b60a5-9f18-4c52-8fdd-b9963e457c66/image.png" alt="after"></p>
<blockquote>
<p>💡 이처럼 이미지를 활용하면 긴 설명 없이도 Claude에게 정확한 맥락을 전달할 수 있어
작업 지시가 훨씬 빠르고 직관적해집니다.</p>
</blockquote>
<h3 id="예시-2-내부-도구-메시지-개선--컴포넌트-분리--테스트-작성">예시 2: 내부 도구 메시지 개선 + 컴포넌트 분리 + 테스트 작성</h3>
<p><code>Create a card with a title and ad description.</code>을 입력하면,
Claude가 파일을 수정할 때 <code>str_replace_editor</code>라는 내부 도구 이름이 그대로 노출됩니다.</p>
<p><img src="https://velog.velcdn.com/images/kyeongjun-dev/post/109f565f-c49c-46ad-81c3-b278895c6a3e/image.png" alt="str_replace_editor"></p>
<p>이 화면을 캡처해서 아래와 같이 요청하면,</p>
<pre><code>[Image #3] Replace the &#39;str_replace_editor&#39; text with a more user friendly message 
of what this tool call is doing. For example, maybe state that a file is being 
created or edited, along with the name of the file being modified. 
Also, put this into a new component and write tests for it.</code></pre><p>Claude는 두 가지 방법을 제시하며, 컴포넌트 분리와 테스트 작성까지 한 번에 처리합니다.</p>
<hr>
<h2 id="planning-mode">Planning Mode</h2>
<p><code>Shift + Tab</code>을 두 번 누르면 Planning Mode로 진입할 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/kyeongjun-dev/post/5cd2904b-d1fa-4e5d-bbea-c91b5e96b5be/image.png" alt="planning mode"></p>
<p>Planning Mode에서 Claude는 바로 코드를 수정하지 않고, 먼저 실행 계획을 수립합니다.</p>
<ul>
<li>프로젝트에서 더 많은 파일을 읽어옴</li>
<li>상세한 실행 계획을 생성함</li>
<li>정확히 무엇을 할 것인지 보여줌</li>
<li>사용자의 승인을 받은 후 진행함</li>
</ul>
<p>실제로 Planning Mode로 이미지와 함께 아래와 같이 입력하면</p>
<pre><code>[Image #3] Replace the &#39;str_replace_editor&#39; text with a more user friendly message 
of what this tool call is doing. For example, maybe state that a file is being 
created or edited, along with the name of the file being modified. 
Also, put this into a new component and write tests for it.</code></pre><p><code>Creating App.jsx</code>로 변경된 것을 확인할 수 있습니다.
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/e8e7d90d-d87a-43cd-a706-b8752651234f/image.png" alt="planning mode result"></p>
<hr>
<h2 id="thinking-mode">Thinking Mode</h2>
<p>Thinking Mode는 Claude가 답변을 생성하기 전에 <strong>내부적으로 더 깊이 추론</strong>하도록 유도하는 기능입니다.
특정 키워드를 프롬프트에 포함하는 것만으로 활성화할 수 있으며, Planning Mode와 함께 사용할 수 있습니다.</p>
<table>
<thead>
<tr>
<th>키워드</th>
<th>추론 수준</th>
</tr>
</thead>
<tbody><tr>
<td><code>Think</code></td>
<td>기본 추론</td>
</tr>
<tr>
<td><code>Think more</code></td>
<td>확장 추론</td>
</tr>
<tr>
<td><code>Think a lot</code></td>
<td>포괄적 추론</td>
</tr>
<tr>
<td><code>Think longer</code></td>
<td>장시간 추론</td>
</tr>
<tr>
<td><code>Ultrathink</code></td>
<td>최대 추론</td>
</tr>
</tbody></table>
<p><strong>사용 예시:</strong></p>
<pre><code>This is a tough task, so ultrathink about the best way to implement it.</code></pre><hr>
<h2 id="언제-무엇을-쓸까">언제 무엇을 쓸까?</h2>
<p>어려운 작업을 수행할 때 두 모드를 적절하게 활성화하면 Claude의 문제 해결 능력을 끌어올릴 수 있습니다.</p>
<blockquote>
<p>💡 Planning Mode와 Thinking Mode는 둘 다 독립적으로 실행 가능합니다.</p>
</blockquote>
<ul>
<li>Thinking Mode만 — 특정 로직이나 버그처럼 깊은 추론이 필요할 때</li>
<li>Planning Mode만 — 넓은 범위를 탐색하고 실행 계획을 먼저 짜야 할 때</li>
<li>둘 다 — 복잡하고 범위도 넓으면서 추론도 깊게 필요한 작업일 때</li>
</ul>
<h3 id="planning-mode가-적합한-경우">Planning Mode가 적합한 경우</h3>
<p>코드베이스 <strong>전체를 파악하고 넓은 범위를 탐색</strong>해야 하는 작업에 적합합니다.
예를 들어 여러 파일에 걸친 리팩토링이나, 여러 단계를 순서대로 진행해야 하는 작업이 이에 해당합니다.</p>
<blockquote>
<p>BFS(Breadth-First Search)처럼 넓게 먼저 보는 방식의 작업에 유리합니다.</p>
</blockquote>
<h3 id="thinking-mode가-적합한-경우">Thinking Mode가 적합한 경우</h3>
<p>특정 로직에 집중하거나, 재현이 어려운 버그를 추적할 때처럼
<strong>깊이 있는 분석이 필요한 작업</strong>에 적합합니다.</p>
<hr>
<h2 id="⚠️-주의-토큰-비용">⚠️ 주의: 토큰 비용</h2>
<p>Planning Mode와 Thinking Mode는 모두 일반 요청보다 <strong>더 많은 토큰을 소비</strong>합니다.
API를 직접 사용하는 경우 비용이 증가할 수 있으므로, 꼭 필요한 상황에서만 활용하는 것을 권장합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Claude Code #1] CLAUDE.md로 AI에게 프로젝트 가르치기]]></title>
            <link>https://velog.io/@kyeongjun-dev/Claude-Code-%EC%9E%85%EB%AC%B8-%EA%B0%95%EC%9D%98-%EC%9A%94%EC%95%BD</link>
            <guid>https://velog.io/@kyeongjun-dev/Claude-Code-%EC%9E%85%EB%AC%B8-%EA%B0%95%EC%9D%98-%EC%9A%94%EC%95%BD</guid>
            <pubDate>Sat, 21 Mar 2026 08:38:23 GMT</pubDate>
            <description><![CDATA[<h1 id="claude-code-입문-강의-요약">Claude Code 입문 강의 요약</h1>
<blockquote>
<p>Anthropic SkillJar의 Claude Code 강의를 듣고 정리한 내용입니다.
<a href="https://anthropic.skilljar.com/claude-code-in-action">https://anthropic.skilljar.com/claude-code-in-action</a></p>
</blockquote>
<hr>
<h2 id="claude-code란">Claude Code란?</h2>
<p>Claude Code는 Anthropic이 만든 <strong>터미널 기반 AI 코딩 어시스턴트</strong>입니다.
단순한 코드 자동완성 도구가 아니라, 코드베이스 전체를 이해하고 실제 작업(파일 수정, 명령 실행 등)을 
에이전트처럼 수행할 수 있는 것이 특징입니다.</p>
<p>일반적인 코딩 어시스턴트(GitHub Copilot 등)가 <strong>제안(suggestion)</strong> 중심이라면,
Claude Code는 <strong>실행(action)</strong> 중심입니다.</p>
<hr>
<h2 id="프로젝트-시작-init">프로젝트 시작: <code>/init</code></h2>
<p>새 프로젝트에서 Claude Code를 처음 사용할 때는 <code>/init</code> 커맨드부터 시작하는 것을 권장합니다.</p>
<ol>
<li>코드베이스 전체를 자동으로 분석</li>
<li>구조와 주요 파일을 요약</li>
<li>요약 내용을 <code>CLAUDE.md</code> 파일에 기록</li>
</ol>
<p>이 과정을 통해 Claude는 이후 모든 요청에서 프로젝트 구조를 다시 파악할 필요 없이
즉시 관련 컨텍스트를 활용할 수 있습니다.</p>
<hr>
<h2 id="컨텍스트-추가-adding-context">컨텍스트 추가: Adding Context</h2>
<h3 id="핵심-원칙-필요한-것만-제공하라">핵심 원칙: 필요한 것만 제공하라</h3>
<p>Claude에게 작업을 시킬 때는 <strong>해당 작업과 관련된 파일이나 문서만</strong> 제공해야 합니다.
관련 없는 파일까지 넘기면 컨텍스트 윈도우가 낭비되고 노이즈가 늘어나 오히려 성능이 떨어집니다.</p>
<hr>
<h2 id="claudemd-claude의-가이드-문서">CLAUDE.md: Claude의 가이드 문서</h2>
<p><code>/init</code> 실행 시 생성되는 <code>CLAUDE.md</code>는 Claude가 프로젝트를 이해하는 핵심 문서입니다.</p>
<h3 id="claudemd의-역할">CLAUDE.md의 역할</h3>
<ol>
<li><strong>코드베이스 이해 가속화</strong> — 관련 코드를 더 빠르게 찾을 수 있게 함</li>
<li><strong>작업 가이드 제공</strong> — 코딩 스타일, 규칙, 중요 파일 위치 등을 명시</li>
</ol>
<h3 id="파일-종류별-비교">파일 종류별 비교</h3>
<table>
<thead>
<tr>
<th>파일</th>
<th>공유 여부</th>
<th>용도</th>
</tr>
</thead>
<tbody><tr>
<td><code>CLAUDE.md</code></td>
<td>✅ 코드 레포에 포함, 팀 전체 공유</td>
<td>프로젝트 공통 가이드</td>
</tr>
<tr>
<td><code>CLAUDE.local.md</code></td>
<td>❌ 공유 안 됨</td>
<td>개인 지시사항, 개인 커스터마이즈</td>
</tr>
<tr>
<td><code>~/.claude/CLAUDE.md</code></td>
<td>❌ 공유 안 됨</td>
<td>내 기기의 모든 프로젝트에 공통 적용</td>
</tr>
</tbody></table>
<hr>
<h2 id="custom-instructions--memory-mode">Custom Instructions: <code>#</code> Memory Mode</h2>
<p><code>#</code>을 입력하면 <strong>memory mode</strong>가 활성화되어 해당 내용이 <code>CLAUDE.md</code>에 자동으로 추가됩니다.</p>
<p><strong>입력:</strong></p>
<pre><code># Use comments sparingly. Only comment complex code.</code></pre><p><strong>CLAUDE.md에 추가되는 내용:</strong></p>
<pre><code class="language-markdown">## Code Style
- Use comments sparingly. Only comment complex code.</code></pre>
<p>이를 통해 대화할 때마다 반복적으로 지시하지 않아도, Claude가 항상 일관된 스타일로 코드를 작성하게 됩니다.</p>
<hr>
<h2 id="file-mention--파일-참조">File Mention: <code>@</code> 파일 참조</h2>
<p>질문할 때 <code>@파일명</code>으로 관련 파일을 직접 멘션할 수 있습니다.
Claude가 파일을 직접 탐색하는 시간을 줄여주고, 더 빠르고 정확한 답변을 받을 수 있습니다.</p>
<p><strong>예시:</strong></p>
<pre><code>How does the auth system work? @src/auth/middleware.ts</code></pre><p><img src="https://velog.velcdn.com/images/kyeongjun-dev/post/662a8616-2300-4f8c-94e5-ec000beb8f0f/image.png" alt=""></p>
<hr>
<h2 id="중요-파일을-claudemd에-등록하기">중요 파일을 CLAUDE.md에 등록하기</h2>
<p>프로젝트 전반에서 자주 참조되는 파일은 <code>CLAUDE.md</code>에 등록해두면 좋습니다.</p>
<p>예를 들어, Prisma를 사용하는 프로젝트라면 DB 스키마 파일이 거의 모든 기능에 영향을 미칩니다.
이런 파일은 매번 탐색하는 것보다 CLAUDE.md에 명시해두는 것이 효율적입니다.</p>
<p><strong>memory mode로 등록:</strong></p>
<pre><code># The database schema is defined in the @prisma/schema.prisma file.
  Reference it anytime you need to understand the structure of data stored in the database.</code></pre><p><strong>CLAUDE.md에 추가되는 내용:</strong></p>
<pre><code class="language-markdown">### Database Schema
Defined in `prisma/schema.prisma`. Reference it whenever you need to 
understand the structure of data stored in the database.</code></pre>
<h3 id="효과-비교">효과 비교</h3>
<p>등록 전에는 <code>what attrs does a user have?</code> 같은 질문에 Claude가 파일을 탐색하고 읽는 과정을 거칩니다.
등록 후에는 컨텍스트가 이미 있으므로 <strong>즉시 정확한 답변</strong>을 반환합니다.</p>
<blockquote>
<p>📷 (스크린샷: instruction 없을 때 vs 있을 때 비교)</p>
</blockquote>
<ul>
<li>없을 때
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/3e0bb046-4f18-45e3-8b7e-029ee9ed88d0/image.png" alt=""></li>
<li>있을 때
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/16d25f21-abce-415b-9207-bf4d1b9fa46b/image.png" alt=""></li>
</ul>
<hr>
<h2 id="정리">정리</h2>
<table>
<thead>
<tr>
<th>기능</th>
<th>사용법</th>
<th>효과</th>
</tr>
</thead>
<tbody><tr>
<td>프로젝트 초기화</td>
<td><code>/init</code></td>
<td>코드베이스 자동 분석 + CLAUDE.md 생성</td>
</tr>
<tr>
<td>커스텀 지시 저장</td>
<td><code># 지시내용</code></td>
<td>CLAUDE.md에 영구 기록</td>
</tr>
<tr>
<td>파일 직접 참조</td>
<td><code>@파일명</code></td>
<td>탐색 없이 즉시 해당 파일 기반 답변</td>
</tr>
<tr>
<td>중요 파일 등록</td>
<td><code># ... @파일명</code></td>
<td>매 요청마다 자동으로 컨텍스트 제공</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[내 마음대로 네트워크 시리즈 - 8]]></title>
            <link>https://velog.io/@kyeongjun-dev/%EB%82%B4-%EB%A7%88%EC%9D%8C%EB%8C%80%EB%A1%9C-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%8B%9C%EB%A6%AC%EC%A6%88-8</link>
            <guid>https://velog.io/@kyeongjun-dev/%EB%82%B4-%EB%A7%88%EC%9D%8C%EB%8C%80%EB%A1%9C-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%8B%9C%EB%A6%AC%EC%A6%88-8</guid>
            <pubDate>Thu, 01 Jan 2026 15:32:16 GMT</pubDate>
            <description><![CDATA[<h1 id="개요">개요</h1>
<p>원래라면 AWS EKS에 istio를 설치하고 테스트를 바로 진행하려고 했는데, 로컬에서 테스트하며 정리를 해야할 것 같아서
kind로 생성한 로컬 클러스터에 istio를 설치하고 간단히 동작을 테스트 해보자</p>
<h1 id="참고자료">참고자료</h1>
<ul>
<li>소스코드는 <code>13</code>디렉토리를 참고<h1 id="kind로-테스트-진행">kind로 테스트 진행</h1>
<h3 id="kind로-클러스터-생성">kind로 클러스터 생성</h3>
먼저 아래 yaml 파일을 작성한다. 로컬에서 접근할 것이기 때문에 control plane 노드에 30800 포트를 명시해준다. 이 포트를 이용해 server로 접근할 수 있다.
```
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:</li>
<li>role: control-plane
image: kindest/node:v1.33.4
extraPortMappings:<h1 id="containerport는-kind-노드컨테이너의-포트">containerPort는 Kind 노드(컨테이너)의 포트</h1>
<h1 id="이-포트는-service의-nodeport와-일치">이 포트는 Service의 nodePort와 일치</h1>
<ul>
<li>containerPort: 30800<h1 id="hostport는-로컬-머신pc의-포트">hostPort는 로컬 머신(PC)의 포트</h1>
hostPort: 30800
protocol: TCP</li>
</ul>
</li>
<li>role: worker
image: kindest/node:v1.33.4
labels:
  role: server</li>
<li>role: worker
image: kindest/node:v1.33.4
labels:
  role: istiod</li>
<li>role: worker
image: kindest/node:v1.33.4
labels:
  role: istio-gateway</li>
<li>role: worker
image: kindest/node:v1.33.4
labels:
  role: istio-gateway<pre><code></code></pre></li>
</ul>
<p>클러스터를 생성한다</p>
<pre><code>kind create cluster --name 1-33 --config cluster.yaml</code></pre><h3 id="istio-설치">istio 설치</h3>
<p><code>13/helm</code> 디렉토리에서 helmfile을 이용해 설치한다.
먼저 base를 설치한다.</p>
<pre><code>helmfile sync --selector name=istio-base</code></pre><p>다음으로 istiod를 설치한다. nodeSelector에 <code>role=istiod</code>를 명시했다.</p>
<pre><code>helmfile sync --selector name=istio-istiod</code></pre><p>다음으로 ingress gateway를 설치한다. 서비스 타입은 NodePort이고 nodeSelector와 affinity를 이용해, 서로 다른 노드에 스케줄링 되도록 했다. (각 노드에 1개씩)</p>
<pre><code>helmfile sync --selector name=istio-gateway</code></pre><h3 id="server-파드-배포">server 파드 배포</h3>
<p>server namespace에 배포한다.</p>
<pre><code>kubectl create ns server
kubectl -n server apply -f server.yaml</code></pre><h3 id="kube-sniff로-트래픽-확인">kube sniff로 트래픽 확인</h3>
<p>먼저 server, ingress gateway 이름을 변수로 저장한다.</p>
<pre><code>export SERVER=$(kubectl get pod -n server | grep server | awk &#39;{print $1}&#39;)

read GATEWAY1 GATEWAY2 &lt;&lt;&lt; $(kubectl get pods -n istio-system -l istio=gateway -o jsonpath=&#39;{.items[*].metadata.name}&#39;)
export GATEWAY1 GATEWAY2</code></pre><p>sniff로 8000포트 트래픽 기록을 시작한다. 각 파드마다 별도의 터미널에서 실행</p>
<pre><code>kubectl sniff $SERVER -n server -f &#39;port 8000&#39; --image ghcr.io/nefelim4ag/ksniff-helper:v4 --tcpdump-image ghcr.io/nefelim4ag/tcpdump:latest
kubectl sniff $GATEWAY1 -n istio-system -p -f &#39;port 8000&#39; --image ghcr.io/nefelim4ag/ksniff-helper:v4 --tcpdump-image ghcr.io/nefelim4ag/tcpdump:latest
kubectl sniff $GATEWAY2 -n istio-system -p -f &#39;port 8000&#39; --image ghcr.io/nefelim4ag/ksniff-helper:v4 --tcpdump-image ghcr.io/nefelim4ag/tcpdump:latest</code></pre><p>로컬에서 python을 이용해 트래픽을 보낸다.</p>
<pre><code>PORT=30800 ENDPOINT=&quot;/index&quot; python ./client.py 10</code></pre><p>istio ingress gateway 2개 중 1개에만 8000번 포트 트래픽을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/f0771661-1b14-48f9-aa2a-6a34803cbfaa/image.png" alt=""></p>
<p>아래는 server 파드의 트래픽이다
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/ff4210e2-2100-4ae3-a9a3-bc0aec71d1d9/image.png" alt=""></p>
<p><code>istio-system</code> 네임스페이스의 파드 ip 확인 - <code>10.244.1.5</code></p>
<pre><code>kubectl get pod -owide -n istio-system          
NAME                             READY   STATUS    RESTARTS   AGE     IP            NODE           NOMINATED NODE   READINESS GATES
istio-gateway-778cbb66c7-l65jd   1/1     Running   0          4h24m   10.244.4.7    1-33-worker3   &lt;none&gt;           &lt;none&gt;
istio-gateway-778cbb66c7-vwr4m   1/1     Running   0          4h24m   10.244.1.5    1-33-worker4   &lt;none&gt;           &lt;none&gt;
istiod-5764bfc-t25wc             1/1     Running   0          4h24m   10.244.3.3    1-33-worker2   &lt;none&gt;           &lt;none&gt;</code></pre><p>server 파드의 ip 확인 - <code>10.244.2.2</code></p>
<pre><code>kubectl get pod -owide -n server      
NAME                     READY   STATUS    RESTARTS   AGE     IP           NODE          NOMINATED NODE   READINESS GATES
ksniff-9kfzh             1/1     Running   0          40m     10.244.2.3   1-33-worker   &lt;none&gt;           &lt;none&gt;
server-66c6967c6-kjjz5   1/1     Running   0          4h20m   10.244.2.2   1-33-worker   &lt;none&gt;           &lt;none&gt;</code></pre><p>docker로 구동중인 kind 클러스터의 노드 ip 확인 <code>172.18.0.6</code></p>
<pre><code>docker inspect -f &#39;{{.Name}} - {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}&#39; $(docker ps -q)
/1-33-worker - 172.18.0.5
/1-33-worker4 - 172.18.0.4
/1-33-worker2 - 172.18.0.3
/1-33-worker3 - 172.18.0.2
/1-33-control-plane - 172.18.0.6</code></pre><p>client.py를 다시 실행하고 각 파드별로 소켓 연결 상태를 확인하면</p>
<pre><code>// server
root@server-66c6967c6-kjjz5:/app# netstat -antp
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 0.0.0.0:8000            0.0.0.0:*               LISTEN      1/python3.13        
tcp        0      0 10.244.2.2:8000         10.244.1.5:56740        ESTABLISHED 11/python3.13

// istio gateway
istio-proxy@istio-gateway-778cbb66c7-vwr4m:/$ netstat -antp
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 0.0.0.0:15090           0.0.0.0:*               LISTEN      17/envoy            
tcp        0      0 0.0.0.0:15090           0.0.0.0:*               LISTEN      17/envoy            
tcp        0      0 0.0.0.0:15021           0.0.0.0:*               LISTEN      17/envoy            
tcp        0      0 0.0.0.0:15021           0.0.0.0:*               LISTEN      17/envoy            
tcp        0      0 127.0.0.1:15000         0.0.0.0:*               LISTEN      17/envoy            
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      17/envoy            
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      17/envoy            
tcp        0      0 10.244.1.5:15021        10.244.1.1:54452        TIME_WAIT   -                   
tcp        0      0 10.244.1.5:15021        10.244.1.1:46966        TIME_WAIT   -                   
tcp        0      0 10.244.1.5:52162        10.96.225.207:15012     ESTABLISHED 1/pilot-agent       
tcp        0      0 10.244.1.5:15021        10.244.1.1:50410        TIME_WAIT   -                   
tcp        0      0 10.244.1.5:15021        10.244.1.1:59786        TIME_WAIT   -                   
tcp        0      0 127.0.0.1:48208         127.0.0.1:15020         ESTABLISHED 17/envoy            
tcp        0      0 10.244.1.5:15021        10.244.1.1:33434        TIME_WAIT   -                   
tcp        0      0 10.244.1.5:80           172.18.0.6:3609         ESTABLISHED 17/envoy            
tcp        0      0 127.0.0.1:53688         127.0.0.1:15020         ESTABLISHED 17/envoy            
tcp        0      0 10.244.1.5:56740        10.244.2.2:8000         ESTABLISHED 17/envoy            
tcp6       0      0 :::15020                :::*                    LISTEN      1/pilot-agent       
tcp6       0      0 127.0.0.1:15020         127.0.0.1:48208         ESTABLISHED 1/pilot-agent       
tcp6       0      0 127.0.0.1:15020         127.0.0.1:53688         ESTABLISHED 1/pilot-agent </code></pre><p>연결상태를 정리하면 아래와 같다.
<code>localhost:30800</code> -&gt; control-plane node(<code>172.18.0.6:3609</code>)
control-plane node(<code>172.18.0.6:3609</code>) -&gt; istio gateway(<code>10.244.1.5:80</code>)
istio gateway(<code>10.244.1.5:56740</code>) -&gt; server(<code>10.244.2.2:8000</code>)</p>
<p>이렇게 연결되어 있는 상태에서, istio gateway를 삭제해보자</p>
<pre><code>kubectl delete pod istio-gateway-778cbb66c7-vwr4m</code></pre><p>client 로그는 아래와 같이 삭제 직후 요청이 실패하는 걸 확인할 수 있다.</p>
<pre><code>--- Waiting for 10 seconds... ---
--- Sending keep-alive request #149 (GET /index) ---
--- Received response #149 ---
HTTP/1.1 200 OK

--- Waiting for 10 seconds... ---
--- Sending keep-alive request #150 (GET /index) ---
--- Received response #150 ---
HTTP/1.1 200 OK

--- Waiting for 10 seconds... ---
--- Sending keep-alive request #151 (GET /index) ---

*** TEST FAILED: Server closed connection (recv() returned 0 bytes) ***

*** TEST FAILED: Connection was reset by peer! ***
Error (Code: 54): Connection closed by peer

Socket closed.</code></pre><p>패킷을 확인해보면 아래와 같다</p>
<ul>
<li>istio gateway
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/0ff48b7f-2776-4888-99b5-fd348478ff20/image.png" alt=""></li>
<li>server
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/44f4ea54-307e-49ae-bdd4-138c3d33b9a1/image.png" alt=""></li>
</ul>
<p>client에서는 현재 graceful하게 종료를 하지 않아서 에러가 발생하지만 istio ingress gateway와 server에서는 <code>FIN</code> 패킷으로 정상 종료된 것을 확인할 수 있다.</p>
<h1 id="요약-및-정리">요약 및 정리</h1>
<p>로컬에서 테스트했을 때, client와 연결된 istio gateway 파드가 client &lt;-&gt; server 사이의 요청을 중개해주는 걸 확인할 수 있다.</p>
<p>istio gateway가 여러 개가 존재해도, 이미 연결되어 있는 client의 경우 연결 에러가 발생할 수 있다. 그래서 keep-alive 연결을 사용하는 client의 경우 retry 로직이 필요하다.</p>
<p>istio에서는 <code>preStop</code>, <code>terminationDrainDuration</code>를 활용할 수 있지만 이 또한 이미 연결된 client에서 retry를 해주는 게 바람직할 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[내 마음대로 네트워크 시리즈 - 7]]></title>
            <link>https://velog.io/@kyeongjun-dev/%EB%82%B4-%EB%A7%88%EC%9D%8C%EB%8C%80%EB%A1%9C-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%8B%9C%EB%A6%AC%EC%A6%88-7</link>
            <guid>https://velog.io/@kyeongjun-dev/%EB%82%B4-%EB%A7%88%EC%9D%8C%EB%8C%80%EB%A1%9C-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%8B%9C%EB%A6%AC%EC%A6%88-7</guid>
            <pubDate>Fri, 12 Dec 2025 09:00:31 GMT</pubDate>
            <description><![CDATA[<h1 id="개요">개요</h1>
<p>AWS에 EKS를 띄우고, aws loadbalancer controller를 설치한 후 alb로 테스트를 진행해보자.</p>
<h1 id="클러스터-만들기">클러스터 만들기</h1>
<p>소스코드: 디렉토리 12 참고</p>
<h3 id="eksctl로-클러스터-설치">eksctl로 클러스터 설치</h3>
<p>아래 yaml 파일로</p>
<pre><code>apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

metadata:
  name: my-cluster
  region: ap-northeast-2
  version: &quot;1.33&quot;

availabilityZones: [&quot;ap-northeast-2a&quot;, &quot;ap-northeast-2c&quot;]

vpc:
  clusterEndpoints:
    publicAccess: true  # kubectl exec를 위해 API 서버는 Public에서 접근 허용
    privateAccess: true
  nat:
    gateway: Single

# IAM OIDC 활성화 (LB 컨트롤러 등을 위해 필수)
iam:
  withOIDC: true

managedNodeGroups:
  # 1. Infra Node Group (모니터링, ingress controller 등)
  - name: infra
    instanceType: t3.medium
    minSize: 1
    maxSize: 2
    desiredCapacity: 1
    privateNetworking: true
    availabilityZones: [&quot;ap-northeast-2a&quot;]
    labels:
      role: infra

  # 2. Server Node Group (백엔드 API 등)
  - name: server
    instanceType: t3.medium
    minSize: 1
    maxSize: 1
    desiredCapacity: 1
    privateNetworking: true
    availabilityZones: [&quot;ap-northeast-2a&quot;]
    labels:
      role: server

  # 3. Client Node Group (테스트용 클라이언트 등)
  - name: client
    instanceType: t3.medium
    minSize: 1
    maxSize: 1
    desiredCapacity: 1
    privateNetworking: true
    availabilityZones: [&quot;ap-northeast-2a&quot;]
    labels:
      role: client</code></pre><p>eksctl로 클러스터와 노드그룹을 생성한다.</p>
<pre><code>eksctl create cluster -f cluster.yaml --profile kyeongjun-dev</code></pre><p>생성이 완료되면 context를 가져온다.</p>
<pre><code>aws eks update-kubeconfig --region ap-northeast-2 --name my-cluster --profile kyeongjun-dev</code></pre><h3 id="load-balancer-controller-설치">load balancer controller 설치</h3>
<p>curl로 iam policy를 다운로드한 뒤, 생성한다.</p>
<pre><code>curl -O https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.13.3/docs/install/iam_policy.json

aws --profile kyeongjun-dev iam create-policy \
    --policy-name AWSLoadBalancerControllerIAMPolicy \
    --policy-document file://iam_policy.json</code></pre><p><code>kube-system</code> namespace에 serviceaccount를 생성하고, iam role과 연결한다.</p>
<pre><code>eksctl create iamserviceaccount \
    --cluster=my-cluster \
    --namespace=kube-system \
    --name=aws-load-balancer-controller \
    --attach-policy-arn=arn:aws:iam::&lt;AWS 계정 ID&gt;:policy/AWSLoadBalancerControllerIAMPolicy \
    --override-existing-serviceaccounts \
    --region ap-northeast-2 \
    --approve \
    --profile kyeongjun-dev</code></pre><p>helm으로 aws lb controller를 설치한다.</p>
<pre><code>helm install aws-load-balancer-controller eks/aws-load-balancer-controller -f albc-value.yaml -n kube-system</code></pre><p>add-on 파드들의 노드 변경하기</p>
<pre><code>kubectl -n kube-system edit deployments.apps metrics-server
kubectl -n kube-system scale deployment metrics-server --replicas 1
kubectl -n kube-system edit deployments.apps coredns
kubectl -n kube-system scale deployment coredns --replicas 1</code></pre><h1 id="테스트-환경-만들기">테스트 환경 만들기</h1>
<h3 id="client-server-배포">client, server 배포</h3>
<p>client, server namespace 생성</p>
<pre><code>kubectl create ns client
kubectl create ns server</code></pre><p><code>12/client.yaml</code>, <code>12/server.yaml</code> 배포</p>
<pre><code>kubectl apply -f client.yaml -n client
kubectl apply -f server.yaml -n server</code></pre><p>아래 그림을 참고하여 tagret group을 aws 콘솔에서 생성한다. nlb 때와 다른 점은 protocol이 <code>HTTP</code>라는 점이다. http여야 alb에 연결할 수 있다.
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/1af26c94-6ec1-425a-b8ff-666527e75e9d/image.png" alt=""></p>
<p>생성한 target group의 arn을 입력해서 target group binding을 생성한다. (<code>12/tgb.yaml</code>)</p>
<pre><code>kubectl -n server create -f tgb.yaml</code></pre><h3 id="alb-만들기">ALB 만들기</h3>
<p>시리즈 6의 &#39;NLB 만들기&#39;를 참고한다. 여기서 다른 점은, nlb는 az를 하나만 선택할 수 있지만 alb는 2개를 선택해야 한다.
리스터 포트는 80으로 지정했다.</p>
<p>target group의 target이 health가 되면 테스트 환경 세팅 성공이다.
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/14b3215e-1d3d-4fa9-b747-62a8b39afc65/image.png" alt=""></p>
<h1 id="ping-pong-해보기">ping-pong 해보기</h1>
<h3 id="client-수정해서-ping-pong">client 수정해서 ping pong</h3>
<p>먼저 alb가 생성됐으니, alb의 private dns 주소를 client.yaml을 수정해준다. 헬스체크도 tcpdump에 잡혀서, <code>/index</code> 경로를 앱에 추가했다.</p>
<pre><code>apiVersion: v1
kind: Service
metadata:
  name: client
  labels:
    app: client
spec:
  ports:
    - port: 80
      targetPort: 80
  selector:
    app: client
  type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: client
  labels:
    app: client
spec:
  replicas: 1
  selector:
    matchLabels:
      app: client
  template:
    metadata:
      labels:
        app: client
    spec:
      containers:
        - name: client
          image: ghcr.io/kyeongjun-dev/network:dev
          imagePullPolicy: Always
          command: [&quot;sleep&quot;, &quot;infinity&quot;]
          env:
            - name: HOST
              value: &quot;internal-server-118699894.ap-northeast-2.elb.amazonaws.com&quot;
            - name: PORT
              value: &quot;80&quot;
            - name: HOST_HEADER
              value: &quot;internal-server-118699894.ap-northeast-2.elb.amazonaws.com&quot;
            - name: USE_TLS
              value: &quot;false&quot;
            - name: ENDPOINT
              value: &quot;/index&quot;
              # value: &quot;/slow?wait=11&quot;
      nodeSelector:
        role: client</code></pre><p>client 파드 정보를 저장하고</p>
<pre><code>export CLIENT=$(kubectl get pod -n client | grep client | awk &#39;{print $1}&#39;)</code></pre><p>간단하게 3초로 테스트 해본다.</p>
<pre><code>kubectl -n client exec -it $CLIENT -- python client.py 3</code></pre><p>잘 되는 걸 확인할 수 있다.
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/53863b99-6942-448b-a140-eaba92cd267a/image.png" alt=""></p>
<h3 id="설정-값-확인">설정 값 확인</h3>
<p>alb의 기본 Attribute 값을 확인하면 <code>Connection idle timeout</code>이 60초인 것을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/fba80399-7a9a-4c67-b879-1e865398b68b/image.png" alt=""></p>
<p>그리고 현재 server(gunicorn)의 <code>--keep-alive</code>는 20초다.</p>
<pre><code>command: [&quot;gunicorn&quot;, &quot;--workers&quot;, &quot;1&quot;, &quot;-k&quot;, &quot;gevent&quot;, &quot;--bind&quot;, &quot;0.0.0.0:8000&quot;, &quot;--timeout&quot;, &quot;10&quot;, &quot;--keep-alive&quot;, &quot;20&quot;, &quot;app:app&quot;, &quot;--log-level&quot;, &quot;debug&quot;]</code></pre><p>위 값을 고려하면서 server의 <code>--keep-alive</code> 값과 client의 request interval를 조정하며 테스트를 해보자. 현재 interval이 3초일 때는 잘 동작하는 걸 확인했다.</p>
<h3 id="keep-alive-20-interval-30">keep alive 20, interval 30</h3>
<pre><code>kubectl -n client exec -it $CLIENT -- python client.py 30</code></pre><p>3회 정도 요청을 하고 종료했다.
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/da8706b2-a18c-4962-b921-9174d247cf8d/image.png" alt=""></p>
<p>dump 파일에서 Wireshark에 아래와 같이 입력하면 <code>/index</code> 패킷만 확인이 가능하다</p>
<pre><code>http.request.uri contains &quot;/index&quot;</code></pre><p><img src="https://velog.velcdn.com/images/kyeongjun-dev/post/035d6220-5ad4-4744-b162-3620409910aa/image.png" alt=""></p>
<p>먼저 client 패킷이다. 열어둔 소켓을 이용해 계속 요청을 보내는 걸 확인할 수 있다. (0초, 30초, 60초, 90초)
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/4f487a20-8ae8-4a54-b0ce-47ea33fc3c13/image.png" alt=""></p>
<p>다음 server 패킷이다. 헬스체크 패킷이 섞여 있어서 각 시간대별로 패킷 스크린샷을 첨부했다.</p>
<ul>
<li>0초
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/108a37e2-8d5e-4db5-a9fb-574e0384021f/image.png" alt=""></li>
<li>30초
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/3ee7662d-fb8b-4629-95dd-ebb5bff5c4f5/image.png" alt=""></li>
<li>60초
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/0ee1c9a9-c114-400e-be24-565aaa40ecba/image.png" alt=""></li>
<li>90초
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/6dff4324-63a6-44d5-b872-c2725bb89291/image.png" alt=""></li>
</ul>
<p>여기서 특이한 점이 있는데, client에서는 기존 연결을 이용해 계속 요청을 보낸다. 그런데 server에서는 <code>--keep-alive 20</code>이 적용되어 있어서 연결을 끊는다.
그런데도 client의 요청은 실패하지 않는다.
패킷 내용을 보면, alb가 client의 요청이 있을 때마다 새롭게 연결을 맺어서 요청을 보내는 걸 확인할 수 있다.</p>
<p>20.xxx초로 요청 부하를 주면, 에러를 재현할 수 있다고 하는데, 재현하지 못했다.</p>
<h3 id="keep-alive-20-interval-70">keep alive 20, interval 70</h3>
<p>먼저 client 패킷이다. 60초에 alb로부터 client가 <code>FIN</code> 패킷을 받은 걸 확인할 수 있다. - 이를 이용해서 client 앱에서 예외처리를 하면, <code>RST</code> 방지가 가능하다.
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/3378a0bb-62be-4b58-bc76-a9a2aefbf307/image.png" alt=""></p>
<p>다음은 server 패킷이다. 일단 처음에 <code>/index</code>로 GET 요청이 오고, 다음부터는 <code>/index</code>로 요청된 패킷이 없다.
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/0eac6a65-b7d1-473f-b8ee-20df733830d2/image.png" alt=""></p>
<p>그리고 해당 연결은 20초 후에 제거된다.
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/a6b3c93f-e3a9-4d7e-8b45-6c4ed16c0f05/image.png" alt=""></p>
<p>여기서도 nlb와의 차이를 알 수 있다. nlb는 idle timeout이 되면 연결 종료(<code>FIN</code>)를 client에게 알리지 않고 제거해버리는데, alb는 client에게 <code>FIN</code>을 전달해서 연결이 종료됨을 알린다.</p>
<h1 id="결과-해석">결과 해석</h1>
<h3 id="alb는-어떤-값-설정이-이상적일까">alb는 어떤 값 설정이 이상적일까?</h3>
<p>답은 문서에 나와있기는 하다. <a href="https://repost.aws/knowledge-center/elb-alb-troubleshoot-502-errors">링크</a></p>
<blockquote>
<p>&quot;The target closes the connection with a TCP RST or a TCP FIN message when the load balancer has an outstanding request to the target.&quot;
The load balancer receives a request and forwards the request to the target. The target starts to process the request, but closes the connection to the load balancer too early. This occurs when the duration of the keep-alive timeout that you configured on the target is shorter than the idle timeout value of the load balancer. Make sure that the duration of the keep-alive timeout is longer than the idle timeout value.</p>
</blockquote>
<p>해당 글에서는 <code>server의 keep alive &lt; alb의 idle timeout</code>일 때도 문제가 발생하지는 않았다.
그래도 생각해보면 alb는 연결을 종료할 때, client에 <code>FIN</code>을 보내서 연결이 종료됨을 알리기 때문에 반대가 되어야 하는 게 맞다.</p>
<h3 id="다음-글에서는">다음 글에서는</h3>
<p>이렇게 client &gt; load balancer &gt; server로 이어지는 구조면 좋겠지만, k8s 환경에서는 istio 등 서비스 매시를 쓰는 경우가 많다.</p>
<p>이제 머리가 복잡해져 보자...</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[내 마음대로 네트워크 시리즈 - 6]]></title>
            <link>https://velog.io/@kyeongjun-dev/%EB%82%B4-%EB%A7%88%EC%9D%8C%EB%8C%80%EB%A1%9C-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%8B%9C%EB%A6%AC%EC%A6%88-6</link>
            <guid>https://velog.io/@kyeongjun-dev/%EB%82%B4-%EB%A7%88%EC%9D%8C%EB%8C%80%EB%A1%9C-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%8B%9C%EB%A6%AC%EC%A6%88-6</guid>
            <pubDate>Sun, 07 Dec 2025 05:47:33 GMT</pubDate>
            <description><![CDATA[<h1 id="개요">개요</h1>
<p>AWS에 EKS를 띄우고, aws loadbalancer controller를 설치한 후 nlb로 테스트를 진행해보자.</p>
<h1 id="클러스터-만들기">클러스터 만들기</h1>
<h3 id="eks-클러스터-생성-전-kind로-로컬에서-테스트하기-소스코드--레포-09-디렉토리">eks 클러스터 생성 전, kind로 로컬에서 테스트하기 (소스코드 : 레포 09 디렉토리)</h3>
<p><a href="https://kind.sigs.k8s.io/">kind</a>는 docker desktop을 설치하고, 로컬에서 클러스터를 만들어주는 도구다. 아래 <code>09/cluster.yaml</code> 파일로 클러스터를 생성해보자. </p>
<pre><code>kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  image: kindest/node:v1.33.4
- role: worker
  image: kindest/node:v1.33.4
  labels:
    role: client
- role: worker
  image: kindest/node:v1.33.4
  labels:
    role: server</code></pre><p>아래 명령어로 클러스터를 생성했다.</p>
<pre><code>kind create cluster --name 1-33 --config cluster.yaml</code></pre><p>node label을 확인해보면 <code>role=client</code>, <code>role=server</code>를 확인할 수 있다.</p>
<pre><code>kubectl get node --show-labels            
NAME                 STATUS   ROLES           AGE    VERSION   LABELS
1-33-control-plane   Ready    control-plane   2m8s   v1.33.4   beta.kubernetes.io/arch=arm64,beta.kubernetes.io/os=linux,kubernetes.io/arch=arm64,kubernetes.io/hostname=1-33-control-plane,kubernetes.io/os=linux,node-role.kubernetes.io/control-plane=,node.kubernetes.io/exclude-from-external-load-balancers=
1-33-worker          Ready    &lt;none&gt;          119s   v1.33.4   beta.kubernetes.io/arch=arm64,beta.kubernetes.io/os=linux,kubernetes.io/arch=arm64,kubernetes.io/hostname=1-33-worker,kubernetes.io/os=linux,role=client
1-33-worker2         Ready    &lt;none&gt;          119s   v1.33.4   beta.kubernetes.io/arch=arm64,beta.kubernetes.io/os=linux,kubernetes.io/arch=arm64,kubernetes.io/hostname=1-33-worker2,kubernetes.io/os=linux,role=server</code></pre><p><code>09/clinet.yaml</code>, <code>09/server.yaml</code>로 각각 다른 노드에 배포를 진행해보자.</p>
<pre><code>kubectl create ns client
kubectl create ns server

kubectl apply -f client.yaml -n client
kubectl apply -f server.yaml -n server</code></pre><p>client 파드 이름을 확인한 후, 명령어를 실행해보면</p>
<pre><code>kubectl -n client exec -it client-578c5494df-brldf -- python client.py 5</code></pre><p>요청이 잘 가는 걸 확인할 수 있다. <code>09/client.yaml</code>파일에 사용한 <code>server.server.svc.cluster.local</code> 주소는 클러스터 내부 도메인이다.
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/ff5f4e1c-f9ef-44bf-8d15-18831dc53a8f/image.png" alt=""></p>
<h3 id="eksctl로-eks-클러스터-만들기">eksctl로 eks 클러스터 만들기</h3>
<p>클러스터를 만들었다 지웠다가 하려면 eksctl, terraform 등으로 생성했다가 한번에 지우는 게 비용상으로 유리할 것 같다. <code>10/cluster.yaml</code> 파일로 eks 클러스터를 생성해보자.</p>
<pre><code>apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

metadata:
  name: my-cluster
  region: ap-northeast-2
  version: &quot;1.33&quot;

availabilityZones: [&quot;ap-northeast-2a&quot;, &quot;ap-northeast-2c&quot;]

vpc:
  clusterEndpoints:
    publicAccess: true  # kubectl exec를 위해 API 서버는 Public에서 접근 허용
    privateAccess: true
  nat:
    gateway: Single

# IAM OIDC 활성화 (LB 컨트롤러 등을 위해 필수)
iam:
  withOIDC: true

managedNodeGroups:
  # 1. Infra Node Group (모니터링, ingress controller 등)
  - name: infra
    instanceType: t3.medium
    minSize: 1
    maxSize: 2
    desiredCapacity: 1
    privateNetworking: true
    availabilityZones: [&quot;ap-northeast-2a&quot;]
    labels:
      role: infra

  # 2. Server Node Group (백엔드 API 등)
  - name: server
    instanceType: t3.medium
    minSize: 1
    maxSize: 1
    desiredCapacity: 1
    privateNetworking: true
    availabilityZones: [&quot;ap-northeast-2a&quot;]
    labels:
      role: server

  # 3. Client Node Group (테스트용 클라이언트 등)
  - name: client
    instanceType: t3.medium
    minSize: 1
    maxSize: 1
    desiredCapacity: 1
    privateNetworking: true
    availabilityZones: [&quot;ap-northeast-2a&quot;]
    labels:
      role: client</code></pre><p>현재 사용중인 profile을 확인하고</p>
<pre><code>aws sts get-caller-identity --profile kyeongjun-dev</code></pre><p>ekstctl로 설치를 진행한다.</p>
<pre><code>eksctl create cluster -f cluster.yaml --profile kyeongjun-dev</code></pre><h1 id="load-balancer-controller-사용하기">load balancer controller 사용하기</h1>
<h3 id="aws-load-balancer-controller-설치-링크">aws load balancer controller 설치 (<a href="https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/lbc-helm.html">링크</a>)</h3>
<p>policy를 다운로드 받는다. (<code>10/iam_policy.json</code>)</p>
<pre><code>curl -O https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.13.3/docs/install/iam_policy.json</code></pre><p>iam policy를 생성한다.</p>
<pre><code>aws --profile kyeongjun-dev iam create-policy \
    --policy-name AWSLoadBalancerControllerIAMPolicy \
    --policy-document file://iam_policy.json</code></pre><p>클러스터 생성이 완료되면, kubeconfig를 가져온다.</p>
<pre><code>aws eks update-kubeconfig --region ap-northeast-2 --name my-cluster --profile kyeongjun-dev</code></pre><p>eksctl로 oidc가 연동된 iam role을 생성한다.</p>
<pre><code>eksctl create iamserviceaccount \
    --cluster=my-cluster \
    --namespace=kube-system \
    --name=aws-load-balancer-controller \
    --attach-policy-arn=arn:aws:iam::&lt;AWS 계정 ID&gt;:policy/AWSLoadBalancerControllerIAMPolicy \
    --override-existing-serviceaccounts \
    --region ap-northeast-2 \
    --approve \
    --profile kyeongjun-dev</code></pre><p><code>kube-system</code>에 설치된 service account를 확인한다.</p>
<pre><code>kubectl get sa -n kube-system aws-load-balancer-controller                                 
NAME                           SECRETS   AGE
aws-load-balancer-controller   0         92s</code></pre><p>helm repo를 추가하고</p>
<pre><code>helm repo add eks https://aws.github.io/eks-charts
helm repo update eks</code></pre><p>helm 차트 수정을 위해 pull 진행</p>
<pre><code>helm pull eks/aws-load-balancer-controller
tar xf aws-load-balancer-controller-1.16.0.tgz
rm aws-load-balancer-controller-1.16.0.tgz</code></pre><p>원본 유지를 위해 values 파일 복사</p>
<pre><code>cp aws-load-balancer-controller/values.yaml albc-value.yaml</code></pre><p>아래 내용대로 수정</p>
<pre><code>diff albc-value.yaml aws-load-balancer-controller/values.yaml
5c5
&lt; replicaCount: 1
---
&gt; replicaCount: 2
32c32
&lt;   create: false
---
&gt;   create: true
37c37
&lt;   name: aws-load-balancer-controller
---
&gt;   name:
78,79c78
&lt; nodeSelector:
&lt;   role: infra
---
&gt; nodeSelector: {}
136c135
&lt; clusterName: my-cluster
---
&gt; clusterName:</code></pre><p>수정한 <code>albc-value.yaml</code>로 설치</p>
<pre><code>helm install aws-load-balancer-controller eks/aws-load-balancer-controller -f albc-value.yaml -n kube-system</code></pre><p>설치 확인</p>
<pre><code>kubectl get pod -n kube-system aws-load-balancer-controller-667f7744d6-m76vg 
NAME                                            READY   STATUS    RESTARTS   AGE
aws-load-balancer-controller-667f7744d6-m76vg   1/1     Running   0          39s</code></pre><h1 id="add-on-파드-정리하기">add-on 파드 정리하기</h1>
<h3 id="infra-노드로-옮기기">infra 노드로 옮기기</h3>
<p>현재 infra, client, server 노드그룹이 있는데 add-on 파드들이 정리되지 않고 모든 노드에 있다.</p>
<ul>
<li>client 노드<pre><code>kubectl get node -l role=client
kubectl describe node ip-192-168-78-153.ap-northeast-2.compute.internal
</code></pre></li>
</ul>
<p>Non-terminated Pods:          (6 in total)
  Namespace                   Name                               CPU Requests  CPU Limits  Memory Requests  Memory Limits  Age</p>
<hr>
<p>  kube-system                 aws-node-xbpgz                     50m (2%)      0 (0%)      0 (0%)           0 (0%)         9m18s
  kube-system                 coredns-c844dd74d-dhgnw            100m (5%)     0 (0%)      70Mi (2%)        170Mi (5%)     13m
  kube-system                 coredns-c844dd74d-z222h            100m (5%)     0 (0%)      70Mi (2%)        170Mi (5%)     13m
  kube-system                 kube-proxy-bltk7                   100m (5%)     0 (0%)      0 (0%)           0 (0%)         9m18s
  kube-system                 metrics-server-7645d75fbf-2slfp    100m (5%)     0 (0%)      200Mi (6%)       400Mi (12%)    13m
  kube-system                 metrics-server-7645d75fbf-gkznr    100m (5%)     0 (0%)      200Mi (6%)       400Mi (12%)    13m</p>
<pre><code>
- server 노드</code></pre><p>kubectl get node -l role=server
kubectl describe node ip-192-168-89-251.ap-northeast-2.compute.internal</p>
<p>Non-terminated Pods:          (2 in total)
  Namespace                   Name                CPU Requests  CPU Limits  Memory Requests  Memory Limits  Age</p>
<hr>
<p>  kube-system                 aws-node-58j4k      50m (2%)      0 (0%)      0 (0%)           0 (0%)         10m
  kube-system                 kube-proxy-zgkbn    100m (5%)     0 (0%)      0 (0%)           0 (0%)         10m</p>
<pre><code>
core dns, metrics server를 infra 노드로 옮겨주자
- core dns</code></pre><p>kubectl -n kube-system edit dep
loyments.apps coredns</p>
<p>(변경 전)
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: kubernetes.io/os
                operator: In
                values:
                - linux
              - key: kubernetes.io/arch
                operator: In
                values:
                - amd64
                - arm64</p>
<p>(변경 후)
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: kubernetes.io/os
                operator: In
                values:
                - linux
              - key: kubernetes.io/arch
                operator: In
                values:
                - amd64
                - arm64
              - key: role
                operator: In
                values:
                - infra</p>
<pre><code>
- metrics server</code></pre><p>kubectl -n kube-system edit deployments.apps metrics-server</p>
<p>(변경 전)
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: kubernetes.io/os
                operator: In
                values:
                - linux
              - key: kubernetes.io/arch
                operator: In
                values:
                - amd64
                - arm64</p>
<p>(변경 후)
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: kubernetes.io/os
                operator: In
                values:
                - linux
              - key: kubernetes.io/arch
                operator: In
                values:
                - amd64
                - arm64
              - key: role
                operator: In
                values:
                - infra</p>
<pre><code>
개수도 2개는 필요 없으니 줄여주자</code></pre><p>kubectl -n kube-system scale deployment coredns --replicas 1
kubectl -n kube-system scale deployment metrics-server --replicas 1</p>
<pre><code>
# NLB 만들고 어플리케이션 연결하기
### client, server 배포
namspace 생성</code></pre><p>kubectl create ns client
kubectl create ns server</p>
<pre><code>
`10/client.yaml`, `10/server.yaml` 생성</code></pre><p>kubectl apply -f client.yaml -n client
kubectl apply -f server.yaml -n server</p>
<pre><code>
### target group 및 target gorup binding 만들기
먼저 아래 스크린샷을 참고하여 target group을 생성한다.
![](https://velog.velcdn.com/images/kyeongjun-dev/post/d1aaa4b9-8e90-4e33-b41b-5f85aa959877/image.png)




생성한 target group의 arn을 입력해서 target group binding을 생성한다. (`10/tgb.yaml`)</code></pre><p>apiVersion: elbv2.k8s.aws/v1beta1
kind: TargetGroupBinding
metadata:
  name: server
spec:
  serviceRef:
    name: server
    port: 8000
  targetGroupARN: <arn-to-targetGroup></p>
<pre><code>
등록이 됐는지 확인한다.
![](https://velog.velcdn.com/images/kyeongjun-dev/post/0bbd0389-d651-420a-be26-93781f6104aa/image.png)


### NLB 만들기
콘솔에서 network 타입으로 생성을 시작한다. internal로 지정해준다.

![](https://velog.velcdn.com/images/kyeongjun-dev/post/ad23bd7e-5659-4519-999c-268dfb420f3e/image.png)

네트워크 설정은 eksctl로 클러스터를 생성할 때, 자동으로 생성된 vpc랑 subnet을 사용한다. az는 2a만 지정했고, subnet이 총 2개(Public, Private)있는데, public을 선택하면 경고 메시지가 발생한다. (두 번째 스크린샷 참고)
![](https://velog.velcdn.com/images/kyeongjun-dev/post/4bb6300d-e7ff-42e8-a4bb-afad79f219a8/image.png)

![](https://velog.velcdn.com/images/kyeongjun-dev/post/db01acce-4310-4b7a-84b5-a4a80482fa58/image.png)


sg도 eksctl로 생성할 때 생성된 sg 중 `eks-cluster-sg`로 시작하는 걸 지정해준다. eks에 생성된 노드들에서 사용하는 sg다.
![](https://velog.velcdn.com/images/kyeongjun-dev/post/f566732e-c62a-49fc-a531-e0c77ebfe650/image.png)


리스너는 80포트, 타겟 그룹은 위에서 생성했던 타겟 그룹을 지정해서 생성을 마무리 한다. (나머지 값은 전부 기본 값)
![](https://velog.velcdn.com/images/kyeongjun-dev/post/8dabdeef-7538-45bb-afb8-5e172f7f28a0/image.png)

Active 상태가 되는 걸 확인한다.
![](https://velog.velcdn.com/images/kyeongjun-dev/post/0ff2f84e-504b-4473-bb78-1dfabc40d8c0/image.png)


### client에서 테스트
nlb의 `DNS name`으로 client.yaml을 수정해서 다시 apply 한다.(`10/client-nlb.yaml`)</code></pre><p>apiVersion: v1
kind: Service
metadata:
  name: client
  labels:
    app: client
spec:
  ports:
    - port: 80
      targetPort: 80
  selector:
    app: client
  type: ClusterIP</p>
<hr>
<p>apiVersion: apps/v1
kind: Deployment
metadata:
  name: client
  labels:
    app: client
spec:
  replicas: 1
  selector:
    matchLabels:
      app: client
  template:
    metadata:
      labels:
        app: client
    spec:
      containers:
        - name: client
          image: ghcr.io/kyeongjun-dev/network:dev
          imagePullPolicy: Always
          command: [&quot;sleep&quot;, &quot;infinity&quot;]
          env:
            - name: HOST
              value: &quot;server-2f43e9b6339f7f95.elb.ap-northeast-2.amazonaws.com&quot;
            - name: PORT
              value: &quot;80&quot;
            - name: HOST_HEADER
              value: &quot;server-2f43e9b6339f7f95.elb.ap-northeast-2.amazonaws.com&quot;
            - name: USE_TLS
              value: &quot;false&quot;
            - name: ENDPOINT
              value: &quot;/&quot;
              # value: &quot;/slow?wait=11&quot;
      nodeSelector:
        role: client</p>
<pre><code>
파드 이름을 확인하고, 요청을 보내본다.</code></pre><p>kubectl get pod -n client
NAME                      READY   STATUS    RESTARTS   AGE
client-64cfc6c5b5-vm6vl   1/1     Running   0          52s</p>
<p>kubectl -n client exec -it client-64cfc6c5b5-vm6vl -- python client.py 5</p>
<pre><code>
요청이 잘 되는 것을 확인한다.
![](https://velog.velcdn.com/images/kyeongjun-dev/post/5d07fae0-1305-4791-9229-8fd14ffc2a9c/image.png)

# 패킷 수집 해보기
### 명령어
`11/cmd` 파일을 참고해서, 케이스마다 파드 이름을 알아내고 입력하는 과정을 간소화해보자. 그리고 nlb의 외부 포트가 80이라 tcpdump 조건에 80과 8000을 지정했다.
deployment 재시작은 pod의 `.pcap` 파일을 없애주기 위해 진행한다.(tcpdump가 원래 파일을 덮어쓰는 지를 모르겠다.)</code></pre><p>export CLIENT=$(kubectl get pod -n client | grep client | awk &#39;{print $1}&#39;)
export SERVER=$(kubectl get pod -n server | grep server | awk &#39;{print $1}&#39;)</p>
<p>kubectl -n client exec -it $CLIENT -- tcpdump -i any -n &#39;port 80 or port 8000&#39; -w /app/client.pcap
kubectl -n server exec -it $SERVER -- tcpdump -i any -n &#39;port 80 or port 8000&#39; -w /app/server.pcap</p>
<p>kubectl -n client exec -it $CLIENT -- python client.py 25
kubectl -n server logs -f $SERVER</p>
<p>kubectl -n client cp $CLIENT:/app/client.pcap client.pcap
kubectl -n server cp $SERVER:/app/server.pcap server.pcap</p>
<p>kubectl -n client rollout restart deployment client
kubectl -n server rollout restart deployment server</p>
<p>```</p>
<h3 id="client-interval-25-server-keep-alive-20">client interval 25, server keep-alive 20</h3>
<p>먼저 client에서는 25초 후에 에러가 발생하는 것을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/ae9a24dc-d4ca-407e-99a8-8388dec4d0ee/image.png" alt=""></p>
<p>client 패킷을 확인해보자. client <code>192.168.91.27</code>, server <code>192.168.72.184</code>
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/fd0bb2c2-674b-4786-9aa8-ecbde20f530d/image.png" alt=""></p>
<p>server 패킷을 확인해보자.
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/013a2e77-712c-491c-8015-4dce1adbbafa/image.png" alt=""></p>
<p>패킷을 확인해보면, client 입장에서는 destination이 NLB이고 server 입장에서는 client가 NLB이기 때문에 nlb <code>192.168.79.34</code>인 것을 확인할 수 있다.</p>
<ul>
<li>0초 : 연결 및 GET 요청 전송</li>
<li>20초 : server가 nlb에 <code>FIN</code> 전송 및 연결 해제 완료</li>
<li>25초 : client가 없어진 연결로 GET 요청 전송 후, server에서 <code>RST</code> 전송</li>
</ul>
<h3 id="client-interval-355-server-keep-alive-360">client interval 355, server keep-alive 360</h3>
<p><code>kubectl edit</code>으로 server Deployment의 <code>--keep-alive</code> 값을 360초로 변경한 뒤 테스트를 진행해보자. 원래 상황이라면 클라이언트의 요청 간격이 355초이고 서버의 keep alive가 360으로 더 길어서 문제가 없을 것이다.</p>
<p>요청을 보낸 직후 시간 기록을 위해 스크린샷
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/8bae258f-d869-4414-aace-8079cce559bf/image.png" alt=""></p>
<p>대략 400초가 지났을 때, 패킷을 확인해본다.
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/2851f9fd-2b06-4a0f-87d7-591bc9de00e9/image.png" alt=""></p>
<p>client, server 패킷은 아래와 같다. client <code>192.168.68.217</code>, server <code>192.168.81.43</code>, nlb <code>192.168.79.34</code>
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/d99d792f-5eff-4d2c-9408-7081b89d4658/image.png" alt=""></p>
<p>이전까지는 client, server 패킷이 근소한 시간차이를 두고 거의 동일했는데 nlb가 중간에 끼면서 달라진 것을 확인할 수 있다.</p>
<p>client 패킷을 먼저 분석하면</p>
<ul>
<li>0초 : 연결 수립 및 GET 요청 전송</li>
<li>355초 : interval이 지나서 다시 GET 요청을 전송, nlb가 <code>RST</code> 패킷을 client에게 전송</li>
</ul>
<p>server 패킷을 분석하면</p>
<ul>
<li>0초 : nlb로 부터 연결 수립 및 GET 요청 수신</li>
<li>360초: keep alive 360초가 지날 때까지 아무런 작업이 없어서 <code>FIN</code> 패킷을 전송했으나, nlb로부터 <code>RST</code> 패킷 수신</li>
</ul>
<p>이러한 현상이 발생하는 이유는, nlb의 기본 <code>idle timeout</code>이 350초이기 때문이다. 인터넷에 검색하면 아래 내용을 쉽게 찾을 수 있다.</p>
<blockquote>
<p>nlb는 idle timeout (default: 350초)가 지나면, 연결 정보를 삭제한다. 삭제할 때는 client에 삭제했다는 사실을 알리지 않고 조용히 삭제한다.</p>
</blockquote>
<h1 id="결론-및-다음-글에서는">결론 및 다음 글에서는</h1>
<p>NLB를 사용할 경우, 백엔드 서버의 keep alive 시간을 350초 보다 작게 설정해야 함을 패킷 분석을 통해 알아봤다.
글을 작성하는 시기 기준으로, NLB의 idle timeout도 변경이 가능하지만(<a href="https://aws.amazon.com/ko/blogs/tech/introducing-nlb-tcp-configurable-idle-timeout/">링크</a>), tls 포트에 대해서는 설정이 불가능해서 350초가 고정이다.</p>
<p>다음 글에서는 ALB로 동일하게 테스트를 진행해보려고 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[내 마음대로 네트워크 시리즈 - 5]]></title>
            <link>https://velog.io/@kyeongjun-dev/%EB%82%B4-%EB%A7%88%EC%9D%8C%EB%8C%80%EB%A1%9C-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%8B%9C%EB%A6%AC%EC%A6%88-5</link>
            <guid>https://velog.io/@kyeongjun-dev/%EB%82%B4-%EB%A7%88%EC%9D%8C%EB%8C%80%EB%A1%9C-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%8B%9C%EB%A6%AC%EC%A6%88-5</guid>
            <pubDate>Sat, 06 Dec 2025 08:16:24 GMT</pubDate>
            <description><![CDATA[<h1 id="개요">개요</h1>
<p>원래 &#39;AWS 환경에서 많이 사용되는 NLB, ALB를 사이에 두고 테스트랑 패킷 분석&#39;을 해야하지만, 백엔드 서버에서 timeout 설정도 확실히 해두면 좋을 것 같아서
timeout일 때도 패킷 분석을 해보려고 한다.</p>
<h1 id="백엔드-서버에-timeout-적용">백엔드 서버에 timeout 적용</h1>
<h3 id="소스코드-작성">소스코드 작성</h3>
<p>일단 아래 이미지를 사용하면 된다.</p>
<pre><code>docker pull ghcr.io/kyeongjun-dev/network:dev</code></pre><p><code>app/app.py</code>에 <code>/</code>에 추가로 <code>/slow</code> 엔드포인트를 추가했다. 실제 호출할 때는 <code>/slow?wait=5</code> 이런 식으로 <code>wait</code> 매개변수를 전달한다.</p>
<pre><code>from flask import Flask, request
import time

app = Flask(__name__)

@app.route(&#39;/&#39;)
def hello():
    return &quot;Hello from Flask App behind Gunicorn!&quot;

@app.route(&#39;/slow&#39;)
def slow():
    try:
        wait_time = request.args.get(&#39;wait&#39;, default=10, type=int)
    except ValueError:
        # 쿼리 파라미터가 정수가 아닌 경우 10초를 사용합니다.
        wait_time = 10

    print(f&quot;Request received. Busy waiting for {wait_time} seconds...&quot;)
    # CPU를 계속 사용하는 반복문 사용
    end_time = time.time() + wait_time
    while time.time() &lt; end_time:
        pass  # 아무것도 안 하지만 CPU는 계속 씀 (Yield 안 함)

    print(f&quot;Waited {wait_time} seconds. Sending response now.&quot;)
    return f&quot;Finally, here is your slow response...&quot;</code></pre><h3 id="timeout이-나는-원리">timeout이 나는 원리</h3>
<p>gunicorn을 아래와 같이 실행할 때, <code>--timeout</code>을 설정한다.</p>
<pre><code>gunicorn --workers 1 -k gevent --bind 0.0.0.0:8000 --timeout 10 --keep-alive 10 app:app --log-level debug</code></pre><p>이때 <code>/slow?wait=초</code>로 전달하는 초가 <code>--timeout</code>에 설정한 초보다 크면, 백엔드 서버에서 <code>woker timeout</code>이 발생한다.</p>
<p>실제로 실행시킨 후, curl로 11초를 지정해서 호출하면 <code>WORKER TIMEOUT</code>을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/69a474d2-c87d-4b58-909b-eaa187e781c0/image.png" alt=""></p>
<h1 id="timeout-패킷-캡처해보기">timeout 패킷 캡처해보기</h1>
<h3 id="docker-compose로-테스트하기-소스코드--레포-08-디렉토리">docker compose로 테스트하기 (소스코드 : 레포 08 디렉토리)</h3>
<p>아래 docker-compose를 08 디렉토리에서 실행한다.</p>
<pre><code>services:
  server:
    image: ghcr.io/kyeongjun-dev/network:dev
    hostname: server
    container_name: server
    volumes:
      - ./server_vol:/app/captures
    ports:
      - &quot;8000:8000&quot;
    command: [&quot;gunicorn&quot;, &quot;--workers&quot;, &quot;1&quot;, &quot;-k&quot;, &quot;gevent&quot;, &quot;--bind&quot;, &quot;0.0.0.0:8000&quot;, &quot;--timeout&quot;, &quot;10&quot;, &quot;--keep-alive&quot;, &quot;10&quot;, &quot;app:app&quot;, &quot;--log-level&quot;, &quot;debug&quot;]

  client:
    image: ghcr.io/kyeongjun-dev/network:dev
    container_name: client
    volumes:
      - ./client_vol:/app/captures
    command: [&quot;sleep&quot;, &quot;infinity&quot;]
    environment:
      HOST: server
      PORT: 8000
      HOST_HEADER: server
      USE_TLS: false
      ENDPOINT: /slow?wait=11</code></pre><p>이제 빌드해둔 이미지가 있으므로, ghcr에 올려놓은 이미지를 사용한다. (<code>-d</code>는 백그라운드 실행)</p>
<pre><code>docker-compose up -d</code></pre><p>tcpdump를 실행하고</p>
<pre><code>docker exec -it client tcpdump -i any -n &#39;port 8000&#39; -w /app/captures/client.pcap
docker exec -it server tcpdump -i any -n &#39;port 8000&#39; -w /app/captures/server.pcap</code></pre><p>client에서 server로 요청을 전송한다</p>
<pre><code>docker exec -it client python client.py 11</code></pre><p>server 로그를 확인해보면, <code>08:00:54</code>에 GET 요청을 받은 후, 약 10초 뒤인 <code>08:01:05</code>에 <code>WORKER TIMEOUT</code>이 발생했다.
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/1d679b81-7157-4ddd-8a92-e93c74c96439/image.png" alt=""></p>
<h3 id="패킷-분석하기">패킷 분석하기</h3>
<p>client가 <code>172.19.0.3</code>, server가 <code>172.19.0.2</code>
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/7cd10950-625a-4bdc-b817-f0915ab49483/image.png" alt=""></p>
<p>간단히 분석해보면</p>
<ol>
<li>0초 : tcp 연결 완료</li>
<li>0초대에 <code>/slow?wait=11</code>로 GET 요청 전송</li>
<li>(패킷에 안보임) 10초 대에 <code>WORKER TIMEOUT</code>으로 워커 종료</li>
<li>20초 : <code>--keep-alive 10</code> 설정에 의해 10초 뒤인 20초대에 client에 <code>FIN</code> 전송</li>
</ol>
<p>client(<code>172.19.0.3</code>), server(<code>172.19.0.2</code>)<code>--keep-alive</code>를 20초로 변경한 후, 테스트 했을 때의 패킷은 아래와 같다. 즉, <code>FIN</code> 패킷을 전송할 때까지 걸리는 시간은 <code>timeout + keep alive</code> 타임이다.
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/556a34ed-5b85-4f94-a5b0-7ed784db0752/image.png" alt=""></p>
<p>위를 보면, 워커가 작업을 수행하는 시간은 <code>keep alive</code>에 포함되지 않는 것 같다.</p>
<h1 id="정리-및-다음-글에서는">정리 및 다음 글에서는...</h1>
<p><code>timeout</code>까지 고려하려니 너무 복잡해지는 거 같다. 다음 글 부터는 <code>/slow</code>를 이용해 timeout은 후순위로 고려하려고 한다.
다음 글에서는 AWS 환경 EKS에 nlb를 두고, 패킷 테스트를 해보자.
(이제 진짜 돈이 들겠네...)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[내 마음대로 네트워크 시리즈 - 4]]></title>
            <link>https://velog.io/@kyeongjun-dev/%EB%82%B4-%EB%A7%88%EC%9D%8C%EB%8C%80%EB%A1%9C-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%8B%9C%EB%A6%AC%EC%A6%88-4</link>
            <guid>https://velog.io/@kyeongjun-dev/%EB%82%B4-%EB%A7%88%EC%9D%8C%EB%8C%80%EB%A1%9C-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%8B%9C%EB%A6%AC%EC%A6%88-4</guid>
            <pubDate>Mon, 01 Dec 2025 06:39:14 GMT</pubDate>
            <description><![CDATA[<h1 id="소스코드">소스코드</h1>
<p><a href="https://github.com/kyeongjun-dev/network">https://github.com/kyeongjun-dev/network</a></p>
<h1 id="개요">개요</h1>
<h3 id="실제-백엔드-서비스에서-테스트해보기">실제 백엔드 서비스에서 테스트해보기</h3>
<p>시리즈3에서는 파이썬으로 만든 client, server 스크립트 파일로 테스트를 진행하고 tcpdump를 이용해 패킷을 분석했다. 이번 글에서는 실제 백엔드 서버를 server 역할로 두고 테스트를 진행해본다.
사용하는 백엔드는 gunicorn과 flask다.</p>
<h1 id="백엔드-서버-만들기">백엔드 서버 만들기</h1>
<h3 id="gunicorn-flask-코드-작성-소스코드--레포-06flask-디렉토리">gunicorn, flask 코드 작성 (소스코드 : 레포 06/flask 디렉토리)</h3>
<p>먼저 flask 코드(<code>06/flask/app.py</code>)다. 정말 간단하게 <code>/</code>로 접근 시, 문자열을 return (status : 200) 한다.</p>
<pre><code>from flask import Flask

app = Flask(__name__)

@app.route(&#39;/&#39;)
def hello():
    return &quot;Hello from Flask App behind Gunicorn!&quot;</code></pre><p>설치하는 파이썬 패키지는 아래와 같다. (<code>06/flask/requirements.txt</code>)</p>
<pre><code>Flask==3.1.2
gunicorn==23.0.0
gevent==25.9.1</code></pre><p>패키지 별로 간단히 설명을 붙이자면 아래와 같다.</p>
<ul>
<li>flask : API 서버 역할을 수행.</li>
<li>gunicorn : WSGI로, flask 앞단에서 client와의 연결 수행.</li>
<li>gevent : gunicorn의 기본 워커는 동기(sync)인데, gevent를 워커타입으로 설정하여 비동기(async)로 사용 - gunicorn의 경우 워커가 비동기여야<code>keep alive</code> 설정이 적용됨 (<a href="https://docs.gunicorn.org/en/stable/settings.html#keepalive">공식문서링크</a>)</li>
</ul>
<p>다음은 실행 명령어다. (<code>06/flask/cmd</code>)</p>
<pre><code>gunicorn --workers 1 -k gevent --bind 0.0.0.0:8000 --timeout 10 --keep-alive 10 app:app --log-level debug</code></pre><p>실행 명령어 설명은 아래와 같다. 자세한 설명은 <a href="https://docs.gunicorn.org/en/stable/settings.html#worker-class">공식문서링크</a> 참고 가능</p>
<ul>
<li><code>--workers</code> : gunicorn을 실행할 때, 워커 개수를 지정한다.</li>
<li><code>-k</code>(<code>--worker-class</code>) : 실행하는 워커 타입을 지정한다. 기본값은 <code>sync</code></li>
<li><code>--bind</code> : 주소랑 포트를 지정해서 실행</li>
<li><code>--timeout</code> : 워커가 동작을 처리하는 최대 시간을 지정. 이 시간을 초과하도록 작업을 끝내지 못하면, 작업을 진행중인 워커 프로세스는 종료된다.</li>
<li><code>--keep-alive</code> : client랑 Keep-Alive 연결을 지속하는 시간을 지정. 이 시간을 초과하도록 아무런 작업(패킷교환)이 없으면, 연결을 종료한다.</li>
<li><code>--log-level</code> : gunicorn의 로그레벨을 설정. <code>debug</code>로 설정하고 gunicorn을 실행하면, 현재 설정된 값들을 확인할 수 있다.<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/04274eff-9b37-4625-a71e-55bf5e487f50/image.png" alt=""></li>
</ul>
<h1 id="클라이언트-만들기">클라이언트 만들기</h1>
<h3 id="파이썬으로-스크립트-작성">파이썬으로 스크립트 작성</h3>
<p><code>06/client.py</code> 파일로, 실행할 때 요청을 보내는 time interval을 매개변수로 입력한다. 입력하지 않으면, 기본 값으로 60초 간격으로 요청을 보낸다.</p>
<pre><code>import socket
import time
import errno
import sys  # 1. sys 모듈 임포트

# --- TIME_INTERVAL 설정 로직 ---
TIME_INTERVAL = 60  # 2. 기본값 60초

try:
    if len(sys.argv) &gt; 1:
        # 3. 커맨드라인 인자(argv[1])가 있으면, 그것을 TIME_INTERVAL로 사용
        TIME_INTERVAL = int(sys.argv[1])
    else:
        # 4. 커맨드라인 인자가 없으면, 사용자에게 입력받음
        user_input = input(&#39;insert TIME_INTERVAL (default 60): &#39;)

        if user_input:
            # 5. 사용자가 값을 입력한 경우
            TIME_INTERVAL = int(user_input)
        # (사용자가 아무것도 입력하지 않으면(Enter), 기본값 60이 사용됨)

except ValueError:
    print(f&quot;Error: Invalid input. Using default TIME_INTERVAL = 60s.&quot;)
    TIME_INTERVAL = 60
except KeyboardInterrupt:
    print(&quot;\nCanceled by user. Exiting.&quot;)
    sys.exit(0)
# ------------------------------


# --- 설정 ---
HOST = &#39;localhost&#39;
PORT = 8000
YOUR_HOST_HEADER = &#39;localhost&#39;
# -----------

# 사용할 요청 (GET /)
REQUEST_KEEP_ALIVE = (
    f&quot;GET / HTTP/1.1\r\n&quot;
    f&quot;Host: {YOUR_HOST_HEADER}\r\n&quot;
    # keep alive 연결 명시
    f&quot;Connection: keep-alive\r\n&quot;
    f&quot;\r\n&quot;
).encode(&#39;utf-8&#39;)


print(f&quot;\n--- Keep-Alive Test (Non-TLS) ---&quot;)
# 6. http:// 스키마 및 포트 번호 명시
print(f&quot;Target: http://{HOST}:{PORT}&quot;) 
print(f&quot;Sending &#39;GET /&#39; request every {TIME_INTERVAL} seconds.&quot;)
print(&quot;Press Ctrl+C to stop the test.&quot;)

s = None # finally 블록에서 s를 참조할 수 있도록 외부에 선언
try:
    # 1. 소켓 생성 연결 (Non-TLS)
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    print(f&quot;\nConnecting to {HOST}:{PORT} (TCP)...&quot;)
    s.connect((HOST, PORT))
    print(&quot;TCP Connected!&quot;)

    # 2. 첫 번째 요청 전송 (연결 확인용)
    print(&quot;\n--- Sending first request (GET /) ---&quot;)
    s.sendall(REQUEST_KEEP_ALIVE)

    # 3. 첫 번째 응답 수신
    response = s.recv(4096)
    if not response:
        print(&quot;\n*** TEST FAILED: Received empty response on first request. ***&quot;)
        raise socket.error(&quot;Server returned empty response on first request&quot;)

    print(&quot;--- Received first response ---&quot;)
    print(response.decode(&#39;utf-8&#39;, errors=&#39;ignore&#39;).split(&#39;\r\n&#39;)[0])

    # 4. TIME_INTERVAL 간격으로 요청 무한 반복
    count = 1
    while True:
        print(f&quot;\n--- Waiting for {TIME_INTERVAL} seconds... ---&quot;)
        time.sleep(TIME_INTERVAL)

        count += 1
        print(f&quot;--- Sending keep-alive request #{count} (GET /) ---&quot;)
        s.sendall(REQUEST_KEEP_ALIVE)

        # 응답 수신
        response = s.recv(4096)

        # 서버가 연결을 닫았는지 확인 (0바이트 수신)
        if not response:
            print(&quot;\n*** TEST FAILED: Server closed connection (recv() returned 0 bytes) ***&quot;)
            raise socket.error(errno.ECONNRESET, &quot;Connection closed by peer (recv() returned 0)&quot;)

        print(f&quot;--- Received response #{count} ---&quot;)
        print(response.decode(&#39;utf-8&#39;, errors=&#39;ignore&#39;).split(&#39;\r\n&#39;)[0])


except socket.error as e:
    # 5. 연결이 끊어지면 &quot;실패&quot;로 간주 (Keep-Alive 실패)
    if e.errno in (errno.ECONNRESET, errno.EPIPE):
        print(f&quot;\n*** TEST FAILED: Connection was reset by peer! ***&quot;)
        print(f&quot;Error (Code: {e.errno}): {e.strerror}&quot;)
    else:
        print(f&quot;\n*** TEST FAILED: Caught unexpected socket error ***&quot;)
        print(f&quot;Error (Code: {e.errno}): {e.strerror}&quot;)
except KeyboardInterrupt:
    # 6. 사용자가 Ctrl+C로 정상 종료
    # (루프가 시작되기 전에 중단될 경우 &#39;count&#39; 변수가 없을 수 있어 &#39;locals()&#39;로 확인)
    req_count_str = f&quot; after {count} requests&quot; if &#39;count&#39; in locals() else &quot;&quot;
    print(f&quot;\n\n--- Test manually interrupted by user (Ctrl+C){req_count_str}. ---&quot;)
    print(&quot;*** TEST STOPPED ***&quot;)
except Exception as e:
    print(f&quot;\n*** TEST FAILED: Caught a non-socket error ***&quot;)
    print(f&quot;Error: {e}&quot;)

finally:
    if s:
        s.close()
        print(&quot;\nSocket closed.&quot;)</code></pre><h1 id="ping-pong-테스트하기">ping-pong 테스트하기</h1>
<h3 id="로컬에서-테스트하기-소스코드--레포-06-디렉토리">로컬에서 테스트하기 (소스코드 : 레포 06 디렉토리)</h3>
<p>먼저 백엔드 서버를 실행한다.</p>
<pre><code>// 06/flask 디렉토리로 이동
cd 06/flask

// venv 생성
python3.13 -m venv venv

// venv 활성화
source venv/bin/activate

// 파이썬 패키지 설치
pip install -r requirements.txt

// gunicorn 백엔드 서버 실행
gunicorn --workers 1 -k gevent --bind 0.0.0.0:8000 --timeout 10 --keep-alive 10 app:app --log-level debug</code></pre><p>다음으로 <code>06/client.py</code>를 8초로 실행한다.</p>
<pre><code>python3.13 client.py 8</code></pre><p>8초 간격으로 GET 요청이 잘 전달되는 것을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/84873809-3ee4-4c8d-91f0-8f47f1c353e3/image.png" alt=""></p>
<p>다음으로 <code>06/client.py</code>를 11초롤 실행한다.</p>
<pre><code>python3.13 client.py 11</code></pre><p>8초로 실행했을 때와 달리 2번째 요청부터 바로 실패하는 것을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/fdc58e8e-d444-4a22-b3c3-7417a4515414/image.png" alt=""></p>
<h1 id="docker-compose로-패킷-확인해보기-소스코드--레포-07-디렉토리">docker compose로 패킷 확인해보기 (소스코드 : 레포 07 디렉토리)</h1>
<p><code>07/client/client.py</code>를 8초, 15초(11초로 했을 시, 요청이 성공하는 케이스가 존재함)로 실행했을 때의 패킷을 tcpdump랑 wireshark로 확인해보자. </p>
<pre><code>import socket
import time
import errno
import sys  # 1. sys 모듈 임포트

# --- TIME_INTERVAL 설정 로직 ---
TIME_INTERVAL = 60  # 2. 기본값 60초

try:
    if len(sys.argv) &gt; 1:
        # 3. 커맨드라인 인자(argv[1])가 있으면, 그것을 TIME_INTERVAL로 사용
        TIME_INTERVAL = int(sys.argv[1])
    else:
        # 4. 커맨드라인 인자가 없으면, 사용자에게 입력받음
        user_input = input(&#39;insert TIME_INTERVAL (default 60): &#39;)

        if user_input:
            # 5. 사용자가 값을 입력한 경우
            TIME_INTERVAL = int(user_input)
        # (사용자가 아무것도 입력하지 않으면(Enter), 기본값 60이 사용됨)

except ValueError:
    print(f&quot;Error: Invalid input. Using default TIME_INTERVAL = 60s.&quot;)
    TIME_INTERVAL = 60
except KeyboardInterrupt:
    print(&quot;\nCanceled by user. Exiting.&quot;)
    sys.exit(0)
# ------------------------------


# --- 설정 ---
HOST = &#39;server&#39;
PORT = 8000
YOUR_HOST_HEADER = &#39;server&#39;
# -----------

# 사용할 요청 (GET /)
REQUEST_KEEP_ALIVE = (
    f&quot;GET / HTTP/1.1\r\n&quot;
    f&quot;Host: {YOUR_HOST_HEADER}\r\n&quot;
    # keep alive 연결 명시
    f&quot;Connection: keep-alive\r\n&quot;
    f&quot;\r\n&quot;
).encode(&#39;utf-8&#39;)


print(f&quot;\n--- Keep-Alive Test (Non-TLS) ---&quot;)
# 6. http:// 스키마 및 포트 번호 명시
print(f&quot;Target: http://{HOST}:{PORT}&quot;) 
print(f&quot;Sending &#39;GET /&#39; request every {TIME_INTERVAL} seconds.&quot;)
print(&quot;Press Ctrl+C to stop the test.&quot;)

s = None # finally 블록에서 s를 참조할 수 있도록 외부에 선언
try:
    # 1. 소켓 생성 연결 (Non-TLS)
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    print(f&quot;\nConnecting to {HOST}:{PORT} (TCP)...&quot;)
    s.connect((HOST, PORT))
    print(&quot;TCP Connected!&quot;)

    # 2. 첫 번째 요청 전송 (연결 확인용)
    print(&quot;\n--- Sending first request (GET /) ---&quot;)
    s.sendall(REQUEST_KEEP_ALIVE)

    # 3. 첫 번째 응답 수신
    response = s.recv(4096)
    if not response:
        print(&quot;\n*** TEST FAILED: Received empty response on first request. ***&quot;)
        raise socket.error(&quot;Server returned empty response on first request&quot;)

    print(&quot;--- Received first response ---&quot;)
    print(response.decode(&#39;utf-8&#39;, errors=&#39;ignore&#39;).split(&#39;\r\n&#39;)[0])

    # 4. TIME_INTERVAL 간격으로 요청 무한 반복
    count = 1
    while True:
        print(f&quot;\n--- Waiting for {TIME_INTERVAL} seconds... ---&quot;)
        time.sleep(TIME_INTERVAL)

        count += 1
        print(f&quot;--- Sending keep-alive request #{count} (GET /) ---&quot;)
        s.sendall(REQUEST_KEEP_ALIVE)

        # 응답 수신
        response = s.recv(4096)

        # 서버가 연결을 닫았는지 확인 (0바이트 수신)
        if not response:
            print(&quot;\n*** TEST FAILED: Server closed connection (recv() returned 0 bytes) ***&quot;)
            raise socket.error(errno.ECONNRESET, &quot;Connection closed by peer (recv() returned 0)&quot;)

        print(f&quot;--- Received response #{count} ---&quot;)
        print(response.decode(&#39;utf-8&#39;, errors=&#39;ignore&#39;).split(&#39;\r\n&#39;)[0])


except socket.error as e:
    # 5. 연결이 끊어지면 &quot;실패&quot;로 간주 (Keep-Alive 실패)
    if e.errno in (errno.ECONNRESET, errno.EPIPE):
        print(f&quot;\n*** TEST FAILED: Connection was reset by peer! ***&quot;)
        print(f&quot;Error (Code: {e.errno}): {e.strerror}&quot;)
    else:
        print(f&quot;\n*** TEST FAILED: Caught unexpected socket error ***&quot;)
        print(f&quot;Error (Code: {e.errno}): {e.strerror}&quot;)
except KeyboardInterrupt:
    # 6. 사용자가 Ctrl+C로 정상 종료
    # (루프가 시작되기 전에 중단될 경우 &#39;count&#39; 변수가 없을 수 있어 &#39;locals()&#39;로 확인)
    req_count_str = f&quot; after {count} requests&quot; if &#39;count&#39; in locals() else &quot;&quot;
    print(f&quot;\n\n--- Test manually interrupted by user (Ctrl+C){req_count_str}. ---&quot;)
    print(&quot;*** TEST STOPPED ***&quot;)
except Exception as e:
    print(f&quot;\n*** TEST FAILED: Caught a non-socket error ***&quot;)
    print(f&quot;Error: {e}&quot;)

finally:
    if s:
        try:
            # 소켓을 닫기 전에 남은 데이터를 싹 비웁니다.
            s.settimeout(0.1) # 타임아웃을 짧게 설정
            while True:
                data = s.recv(4096)
                if not data: break
        except Exception:
            pass # 타임아웃이나 에러가 나면 그냥 무시

        s.close() # 이제 버퍼가 비었으므로 FIN을 보냄
        print(&quot;\nSocket closed.&quot;)</code></pre><h3 id="8초-간격으로-요청-보내기">8초 간격으로 요청 보내기</h3>
<p>먼저 docker-compose로 client, server 컨테이너를 실행한다.</p>
<pre><code>docker-compose up --build</code></pre><p>client, server 컨테이너에 각각 tcpdump를 실행한다. - 8000번 포트로 변경됐다.</p>
<pre><code>docker exec -it client tcpdump -i any -n &#39;port 8000&#39; -w /app/captures/client.pcap
docker exec -it server tcpdump -i any -n &#39;port 8000&#39; -w /app/captures/server.pcap</code></pre><p>먼저 client.py를 8초로 실핸한다.</p>
<pre><code>docker exec -it client python client.py 8</code></pre><p>5회만 요청을 확인한 뒤, Ctrl + C로 client를 종료한다.
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/97dcc153-7f77-4aa7-ae04-9243c266e37d/image.png" alt=""></p>
<p>tcpdump를 실행한 터미널에서도 Ctrl + C로 종료한 뒤, <code>client_vol</code>, <code>server_vol</code>에 생성된 <code>.pcap</code> 파일을 확인한다.
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/9c1876c5-cae0-461d-a878-9c61685341c3/image.png" alt=""></p>
<p>8초로 실행했을 때의 패킷을 wireshark로 확인한다.
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/0f34ab99-9d48-4135-a7a4-31438880817c/image.png" alt=""></p>
<p>패킷 분석은 간단하게, 5번 요청을 보내고 36초에 client(<code>172.22.0.2</code>)에서 server(<code>172.22.0.3</code>)에 <code>FIN</code> 패킷을 전송하고 연결이 끝난다.</p>
<h3 id="15초-간격으로-요청-보내기">15초 간격으로 요청 보내기</h3>
<p>docker compose down으로 종료한 뒤, <code>client_vol</code>, <code>server_vol</code> 디렉토리를 삭제한 뒤 동일하게 docker compose up과 tcpdump까지 실행한다.</p>
<pre><code>docker-compose down -v
rm -rf client_vol server_vol

docker-compose up --build
docker exec -it client tcpdump -i any -n &#39;port 8000&#39; -w /app/captures/client.pcap
docker exec -it server tcpdump -i any -n &#39;port 8000&#39; -w /app/captures/server.pcap
docker exec -it client python client.py 15</code></pre><p>15초 후의 요청이 바로 실패하는 것을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/aa0399c0-b41a-490d-95b8-111cdc44d4e5/image.png" alt=""></p>
<p>tcpdump한 파일을 wireshark로 확인해보면 아래와 같다.
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/ceb396b0-9419-48ad-8140-08ccbca8eeb9/image.png" alt=""></p>
<p><code>client.pcap</code> 기준, 시간대별로 해석해보면 아래와 같다. client(<code>172.22.0.2</code>), server(<code>172.22.0.3</code>)</p>
<ul>
<li>0 ~ 0.000026 : tcp 연결</li>
<li>0.000071 ~ 0.000075 : 열결 확인용 첫 번째 GET 요청 전송</li>
<li>0.001427 ~ 0.001456 : 첫 번째 응답 수신</li>
<li>10.004 : server에서 client에 <code>FIN</code> 패킷 전달</li>
<li>10.045 : client에서 server에 <code>ACK</code> 패킷 전달 &gt;&gt; tcp 연결 종료 - gunicorn의 <code>keep-alive</code> 10초 설정의 효과</li>
<li>15.004438 : client가 연결이 닫힌 줄 모르고, GET 요청을 server에 전달</li>
<li>15.004490 : server가 client에게 <code>RST</code> 패킷을 전달해서, 이미 닫힌 연결임을 알림</li>
</ul>
<h1 id="정리-및-다음-글에서는">정리 및 다음 글에서는...</h1>
<p>이번 글에서는 실제 백엔드 서버 중 하나인 gunicorn에서 <code>keep-alive</code>가 어떻게 동작하는지 확인했다. 하나 확인할 수 있는 것은 아래와 같다.</p>
<blockquote>
<p>client에서는 server의 keep-alive 설정 시간 내에 작업을 끝내고, 연결을 명시적으로 끊어줘야 한다.
keep-alive 연결을 계속 이용하려면, client에서 server에 &#39;이 연결은 유효한 연결이에요&#39;를 server의 keep-alive 시간 내에 계속 알려줘야 한다.</p>
</blockquote>
<p>지금까지는 로컬에서 docker compose를 이용해 client, server 컨테이너 2개만 이용하여 테스트를 진행했다. 하지만 실제 환경에서 위처럼 사용하는 경우는 없다. 대부분의 경우, 아래와 같이 중간에 load balancer가 쓰이기 때문이다.</p>
<ol>
<li>client &gt; load balancer &gt; server</li>
<li>client &gt; load balancer &gt; proxy server &gt; server</li>
</ol>
<p>이제 AWS 환경에서 많이 사용되는 NLB, ALB를 사이에 두고 테스트랑 패킷 분석을 해보자.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[내 마음대로 네트워크 시리즈 - 3]]></title>
            <link>https://velog.io/@kyeongjun-dev/%EB%82%B4-%EB%A7%88%EC%9D%8C%EB%8C%80%EB%A1%9C-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%8B%9C%EB%A6%AC%EC%A6%88-3</link>
            <guid>https://velog.io/@kyeongjun-dev/%EB%82%B4-%EB%A7%88%EC%9D%8C%EB%8C%80%EB%A1%9C-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%8B%9C%EB%A6%AC%EC%A6%88-3</guid>
            <pubDate>Sun, 30 Nov 2025 10:53:11 GMT</pubDate>
            <description><![CDATA[<h1 id="소스코드">소스코드</h1>
<p><a href="https://github.com/kyeongjun-dev/network">https://github.com/kyeongjun-dev/network</a></p>
<h1 id="개요">개요</h1>
<h3 id="소켓의-timeout은-어떻게-동작할까">소켓의 timeout은 어떻게 동작할까?</h3>
<p>시리즈 2에서 server 코드에 <code>server_socket.settimeout(10)</code>을 설정하여 10초동안 client에서 연결하지 않으면, server가 종료된다고 언급했다.
그런데 사실 10초동안 &#39;연결&#39;이 안되면 종료하는 것에 더하여, 연결된 후로 &#39;10초 동안 아무런 동작을 하지 않으면&#39; 조건에서도 종료된다.</p>
<p>정리하자면</p>
<ol>
<li>소켓의 timeout은 10초동안 client로부터 연결이 없으면, 종료된다.</li>
<li>소켓의 timeout은 연결된 후, 10초동안 아무런 활동이 없으면 종료된다.</li>
</ol>
<p>이를 실제로 구현하고 테스트해보자.</p>
<h1 id="실제로-해보기">실제로 해보기</h1>
<h3 id="client에서-시간차로-메시지-전송ping-pong-해보기---소스코드--레포-04-디렉토리">client에서 시간차로 메시지 전송(ping-pong) 해보기 - (소스코드 : 레포 04 디렉토리)</h3>
<p>timeout(여기선 10초)로 설정된 소켓을 생성한 뒤, client로부터 전송된 메시지를 출력한다. - <code>04/server.py</code> 파일</p>
<pre><code>import socket

timeout_duration = 10

server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind((&#39;127.0.0.1&#39;, 30000))
server_socket.listen()
print(&quot;클라이언트 접속을 기다립니다...&quot;)

# accept()는 블로킹 상태로 무한정 대기
client_socket, addr = server_socket.accept()
print(f&quot;{addr} 에서 접속했습니다. 이제부터 {timeout_duration}초 내에 데이터를 보내야 합니다.&quot;)

# &#39;accept&#39;로 생성된 클라이언트와의 통신 소켓에 타임아웃(timeout_duration) 설정
client_socket.settimeout(timeout_duration)

while True:
    try:
        # recv()는 데이터가 들어올 때까지 여기서 실행을 멈춤(blocking)
        # settimeout() 때문에 timeout_duration초가 지나면 socket.timeout 예외를 발생시킴
        data = client_socket.recv(1024)

        # 클라이언트가 연결을 정상적으로 종료한 경우
        if not data:
            print(&quot;클라이언트가 연결을 끊었습니다.&quot;)
            break

        print(f&quot;수신 메시지: {data.decode(&#39;utf-8&#39;)}&quot;)
        client_socket.sendall(&quot;메시지를 잘 받았습니다!&quot;.encode(&#39;utf-8&#39;))

    except socket.timeout:
        print(f&quot;{timeout_duration}초 동안 데이터 수신이 없어 연결을 종료합니다.&quot;)
        break # while 루프 탈출

    except ConnectionResetError:
        print(&quot;클라이언트와의 연결이 비정상적으로 끊어졌습니다.&quot;)
        break

    except Exception as e:
        print(f&quot;오류가 발생했습니다: {e}&quot;)
        break

# 소켓 정리
print(f&quot;{addr} 와의 연결을 닫습니다.&quot;)
client_socket.close()
server_socket.close()</code></pre><p>연결된 후, <code>timeout_duration</code>에 설정된 초만큼 아무것도 하지 않고 기다린다. 이때 이 값을 <code>04/server.py</code>의 timeout 시간보다 길게 한다(여기선 15초) - <code>04/client.py</code> 파일이다.</p>
<pre><code>import socket
import time

client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

try:
    client_socket.connect((&#39;127.0.0.1&#39;, 30000))
    print(&quot;서버에 접속했습니다.&quot;)
    print(&quot;아무 내용이나 입력 후 엔터를 누르면 서버로 메시지를 보냅니다.&quot;)
    print(&quot;10초 이상 아무것도 안 하면 서버에서 연결이 끊깁니다.&quot;)
    print(&quot;종료하려면 &#39;exit&#39;을 입력하세요.&quot;)

    while True:
        wait_start_time = time.time()
        # 사용자 입력 대기
        message = input(&quot;&gt; &quot;)
        print(f&quot;{time.time() - wait_start_time}초 이후에 메시지를 전송합니다.&quot;)
        if message.lower() == &#39;exit&#39;:
            break

        # 메시지 전송
        client_socket.sendall(message.encode(&#39;utf-8&#39;))

        # 서버로부터 응답 수신
        data = client_socket.recv(1024)
        print(f&quot;서버 응답: {data.decode(&#39;utf-8&#39;)}&quot;)

except ConnectionRefusedError:
    print(&quot;서버에 연결할 수 없습니다.&quot;)
except (ConnectionResetError, BrokenPipeError):
    print(&quot;서버에 의해 연결이 끊겼습니다.&quot;)
except Exception as e:
    print(f&quot;오류가 발생했습니다: {e}&quot;)

finally:
    print(&quot;클라이언트를 종료합니다.&quot;)
    client_socket.close()</code></pre><p><code>04/server.py</code>를 실행한 뒤, <code>04/client.py</code>를 실행하고 server의 timeout 이내에 메시지를 전송하면 계속 ping-pong이 되다가 timeout이 넘어서 메시지를 전송하면 server는 종료되고, client도 연결을 종료한다.
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/55548578-11ae-4bb5-93ec-4d4dc2eec8e9/image.png" alt=""></p>
<p>위 캡처에서 10.75초 이후에 메시지를 전송했을 때, 빈 문자열이 서버 응답으로 오는데
이는 서버로부터 빈 문자열 <code>b&#39;&#39;</code>를 받았기 때문이다. 이를 실제로 wireshark로 패킷 분석해보자. (소스코드 : 레포 05 디렉토리)</p>
<h1 id="패킷-분석해보기">패킷 분석해보기</h1>
<h3 id="docker-compose로-tcpdump-소스코드--레포-05-디렉토리">docker compose로 tcpdump (소스코드 : 레포 05 디렉토리)</h3>
<p><code>05</code> 디렉토리로 이동 후, docker compose를 실행한다.</p>
<pre><code>docker-compose up --build</code></pre><p><code>05/cmd</code>파일을 참고해서 clinet, server 컨테이너에서 tcpdump를 진행한다.</p>
<pre><code>docker exec -it client tcpdump -i any -n &#39;port 30000&#39; -w /app/captures/client.pcap
docker exec -it server tcpdump -i any -n &#39;port 30000&#39; -w /app/captures/server.pcap</code></pre><p><code>05/cmd</code> 파일을 참고해서 client, server 스크립트를 실행시킨 후 10초 내에 메시지 전송 및 10초 이후에 메시지 전송을 진행한다.
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/663a1077-a758-49ce-8c7a-ef5b2a0cab86/image.png" alt=""></p>
<p>tcpdump를 Ctrl + C로 종료한다. 그리고 wireshark로 패킷을 확인한다.
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/8e912493-a65e-4d82-b62e-cdf4f3dbe399/image.png" alt=""></p>
<p>분석을 해보면 (server가 <code>172.22.0.2</code>, client가 <code>172.22.0.3</code>)</p>
<ol>
<li>~0초 : tcp 연결</li>
<li>3.6초 : client가 server에 hello 메시지 전송 및 server가 client에게 응답</li>
<li>8.4초 : client가 server에 world 메시지 전송 및 server가 client에게 응답</li>
<li>18.4초 : server가 client에 FIN, ACK 전송 및 client가 server에 ACK 응답 - tcp 연결 해제</li>
<li>20.3초 : client가 server에 메시지를 전송하지만, 이미 닫힌 연결이라서 server가 client가 RST 전송 - tcp 연결은 해제되었지만, client 코드상에서는 사용자의 input을 그대로 기다림</li>
</ol>
<h1 id="정리-및-다음-글에서는">정리 및 다음 글에서는...</h1>
<p><code>05</code>정도만 되도 어느정도 ping-pong하는 시나리오를 테스트할 수 있다. 하지만 결국 실제 백엔드 서버에서 어떻게 동작하는지 테스트를 하지 않으면 와닿지(?) 않기 때문에
다음 글에서는 gunicorn + flask 조합으로 백엔드 서버를 구성하고, 패킷 분석도 해보도록 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[내 마음대로 네트워크 시리즈 - 2]]></title>
            <link>https://velog.io/@kyeongjun-dev/%EB%82%B4-%EB%A7%88%EC%9D%8C%EB%8C%80%EB%A1%9C-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%8B%9C%EB%A6%AC%EC%A6%88-2</link>
            <guid>https://velog.io/@kyeongjun-dev/%EB%82%B4-%EB%A7%88%EC%9D%8C%EB%8C%80%EB%A1%9C-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%8B%9C%EB%A6%AC%EC%A6%88-2</guid>
            <pubDate>Sun, 30 Nov 2025 08:00:33 GMT</pubDate>
            <description><![CDATA[<h1 id="소스코드">소스코드</h1>
<p><a href="https://github.com/kyeongjun-dev/network">https://github.com/kyeongjun-dev/network</a></p>
<h1 id="개요">개요</h1>
<h3 id="wireshark로-패킷-분석하기">wireshark로 패킷 분석하기</h3>
<p>쉬어가는 느낌으로 시리즈 1의 상황을 직접 패킷분석해보려고 한다.
먼저 wireshark가 설치되어 있어야 하고, tcpdump 명령어 사용이 가능해야 한다.</p>
<h1 id="실제로-해보기">실제로 해보기</h1>
<h3 id="docker-compose로-컨테이너-2개-실행">docker compose로 컨테이너 2개 실행</h3>
<p>로컬에서 해도 되지만, 구분되는게 편할 거 같아서 docker compose로 컨테이너 2개를 띄운다. (소스코드 : 레포 03 디렉토리)</p>
<p>먼저 03 디렉토리로 이동한 뒤, 아래 명령어로 컨테이너 2개를 실행한다. 컨테이너는 네트워크 관련 패키지들이 설치된 파이썬 컨테이너다.</p>
<pre><code>docker-compose up --build</code></pre><p>아래와 같이 <code>client</code>, <code>server</code> 디렉토리가 생성된 것을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/3575286e-9bba-431c-b462-20ecda4feb84/image.png" alt=""></p>
<h3 id="tcp-dump-및-client에서-server-접속">tcp dump 및 client에서 server 접속</h3>
<p>이제 server 컨테이너로 접속해서 <code>01-server.py</code>를 실행시킨다.</p>
<pre><code>docker exec -it server tcpdump -i any -n &#39;port 30000&#39; -w /app/captures/server.pcap</code></pre><p>client 컨테이너로도 접속해서 tcpdump를 실행한다.</p>
<pre><code>docker exec -it client tcpdump -i any -n &#39;port 30000&#39; -w /app/captures/client.pcap</code></pre><p>터미널을 하나 더 열어서 server 컨테이너로 접속한 뒤, <code>01-server.py</code>를 실행시킨다.</p>
<pre><code>docker exec -it server python 01-server.py</code></pre><p>터미널을 하나 더 열어서 client 컨테이너로 접속한 뒤, <code>01-client.py</code>를 실행시킨다.</p>
<pre><code>docker exec -it client python 01-client.py</code></pre><p><code>01-client.py</code>랑 <code>01-server.py</code>는 연결 주소를 <code>127.0.0.1</code>에서 <code>server</code>로 변경했다. (docker compose로 실행될 때 호스트네임이 각각 client, server로 변경된다.)</p>
<p>아래와 같이 정상 연결이 확인되면
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/059596b6-1f6e-4ca6-b263-cfda97860ccc/image.png" alt=""></p>
<p>tcpdump를 실행한 터미널에서는 Ctrl + C로 종료시켜준다.
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/9b178d27-95a8-4d9a-a92a-3c8bdb939e50/image.png" alt=""></p>
<h3 id="wireshark로-패킷-확인">wireshark로 패킷 확인</h3>
<p>이제 컨테이너랑 마운트 된 경로에 아래처럼 파일 2개가 생성된 것을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/3e7bcae2-fb06-49e7-b3f3-9d68916230e9/image.png" alt=""></p>
<p>client, server 패킷을 확인해보자. mac 기준으로 wireshark 창을 여러 개 띄우려면 <code>open -n /Applications/Wireshark.app/</code>로 여러 개를 띄운다.
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/2bd5942a-0b9c-408a-8b09-4a018f0beb19/image.png" alt=""></p>
<p><code>172.22.0.2</code>는 client, <code>172.22.0.3</code>은 server다 - <code>docker inspect server</code>, <code>docker inspect client</code> 에서 ip 주소 확인이 가능하다.</p>
<p>분석해보자면</p>
<ul>
<li>TCP 3-Way Handshake(연결)</li>
</ul>
<ol>
<li>client &gt; server : SYN 전송</li>
<li>server &gt; client : SYN, ACK 전송</li>
<li>client &gt; server : ACK 전송 - tcp 연결 완료</li>
</ol>
<ul>
<li>4-Way Handshake(종료)</li>
</ul>
<ol start="4">
<li>client &gt; server : FIN, ACK 전송 - tcp 종료 시작</li>
<li>server &gt; client : FIN, ACK 전송</li>
<li>client &gt; server : ACK 전송 - tcp 연결 종료</li>
</ol>
<hr>
<h1 id="여러-케이스-별-패킷-분석">여러 케이스 별 패킷 분석</h1>
<h3 id="client가-1초-간격으로-시도하는-도중-server-실행">client가 1초 간격으로 시도하는 도중, server 실행</h3>
<p><code>02-client.py</code>를 실행하고, 약 5초 후에 <code>02-server.py</code>를 실행했다.
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/55562731-db76-4928-9a9f-7d3334b77b06/image.png" alt=""></p>
<p>패킷은 아래와 같다. docker compose를 다시 실행했는데, 이번에는 client가 <code>172.22.0.3</code>이고, server가 <code>172.22.0.2</code> 이다.
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/77985ff0-bb5a-46af-945b-e956b0e6a0e2/image.png" alt=""></p>
<p>분석해보자면</p>
<ol>
<li>client가 server로 연결요청(SYN)을 보냄</li>
<li>서버는 연결할 수 없다며 RST, ACK 패킷을 client에 전송함</li>
<li>client는 1초 단위로 연결을 재시도 하므로, 위 1~2 과정을 총 5번 반복</li>
<li>약 5초 뒤 server가 실행되고, 정상적으로 연결을 맺음</li>
<li>연결된 직후, client가 FIN, ACK를 전송해서 tcp 연결 종료</li>
</ol>
<h1 id="정리-및-다음-글에서는">정리 및 다음 글에서는...</h1>
<p>이런 식으로 wireshark를 이용해 패킷을 분석할 수 있다.
지금은 단순히 client, server만 로컬에 띄워서 테스트해서 패킷 분석이 간단하지만, 중간에 load balancer나 istio 같은 컴포넌트가 끼게되면 더욱 복잡해진다.
힘들지만 해보자...</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[내 마음대로 네트워크 시리즈 - 1]]></title>
            <link>https://velog.io/@kyeongjun-dev/%EB%82%B4-%EB%A7%88%EC%9D%8C%EB%8C%80%EB%A1%9C-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%8B%9C%EB%A6%AC%EC%A6%88-1</link>
            <guid>https://velog.io/@kyeongjun-dev/%EB%82%B4-%EB%A7%88%EC%9D%8C%EB%8C%80%EB%A1%9C-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%8B%9C%EB%A6%AC%EC%A6%88-1</guid>
            <pubDate>Sun, 30 Nov 2025 06:24:32 GMT</pubDate>
            <description><![CDATA[<h1 id="소스코드">소스코드</h1>
<p><a href="https://github.com/kyeongjun-dev/network">https://github.com/kyeongjun-dev/network</a></p>
<h1 id="개요">개요</h1>
<h3 id="이-시리즈를-왜-쓰는가">이 시리즈를 왜 쓰는가?</h3>
<p>MSA 형태의 서비스들에서 best practice가 있다. client의 timeout과 server의 keep-alive 상태 등을 설정하는 좋은 예시들이 있다.
그러다가 문득 &#39;그럼 어떤 상태에서 의도한 에러를 발생시킬 수 있을까?&#39;라는 생각이 들었고, 이를 실천해보기 위해 이 시리즈를 작성하게 됐다.
주 목적은 미래의 내가 보기 위해서다.</p>
<p>시리즈 초반에는 실무에 도움이 될 수 있나 싶겠지만, 실제로 많은 도움이 됐고, 수많은 5xx에러를 없앴다.</p>
<h1 id="소켓-연결-테스트-해보기">소켓 연결 테스트 해보기</h1>
<h3 id="소켓-연결은-어떻게-되나">소켓 연결은 어떻게 되나?</h3>
<p>server가 소켓을 열어두고, client가 서버로 접속한다. 실제 코드로 간단하게 테스트 해보자. (소스코드 : 레포 01 디렉토리)</p>
<p>먼저 client 코드다. 간단히 로컬호스트(<code>127.0.0.1</code>)의 30000 포트로 접속한다.</p>
<pre><code>import socket

try:
    # AF_INET : ipv4 사용, SOCK_STREAM : tcp 사용
    client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    client_socket.connect((&#39;127.0.0.1&#39;, 30000))
    print(&quot;서버에 연결 시도...&quot;)
except ConnectionRefusedError:
    print(&quot;서버에 연결할 수 없습니다. 서버가 이미 종료된 것 같습니다.&quot;)
except Exception as e:
    print(f&quot;오류가 발생했습니다: {e}&quot;)
finally:
    client_socket.close()</code></pre><p>다음은 server 코드다. 간단히 30000번 포트로 소켓을 열고, client의 연결을 기다린다.</p>
<pre><code>import socket

# 서버 소켓 생성
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind((&#39;127.0.0.1&#39;, 30000))
server_socket.listen()

try:
    # accept()는 클라이언트가 연결할 때까지 여기서 실행을 멈춤(blocking)
    client_socket, addr = server_socket.accept()
    print(f&quot;{addr} 에서 접속했습니다.&quot;)

    # 연결 성공 후 로직 (여기서는 간단히 연결 종료)
    client_socket.close()
except Exception as e:
    print(f&quot;오류가 발생했습니다: {e}&quot;)

finally:
    # 소켓 정리
    server_socket.close()
    print(&quot;서버 소켓을 닫았습니다.&quot;)</code></pre><p>실제로 server.py, client.py 순서로 다른 터미널 창에서 실행해보면 아래와 같이 단순히 연결에 성공한 뒤, 바로 연결을 종료한다.</p>
<pre><code>python3.13 client.py
서버에 연결 시도...

// 이걸 먼저 실행
python3.13 server.py
(&#39;127.0.0.1&#39;, 50460) 에서 접속했습니다.
서버 소켓을 닫았습니다.</code></pre><h3 id="문제점이-뭔가">문제점이 뭔가?</h3>
<p>위의 01번 상황에서는 여러 문제가 있다.</p>
<ol>
<li>server가 기약없이 계속 client를 기다린다.</li>
<li>client는 연결에 실패하면 바로 연결을 종료한다.<pre><code>// server.py를 실행하지 않고, 바로 client.py를 실행했을 때
python3.13 client.py
서버에 연결할 수 없습니다. 서버가 이미 종료된 것 같습니다.</code></pre></li>
</ol>
<p>이를 개선해보자.</p>
<h3 id="타임아웃-설정하기">타임아웃 설정하기</h3>
<p>client와 server에 타임아웃을 설정해보자. (소스코드 : 레포 02 디렉토리)</p>
<p>먼저 server 코드다. <code>timeout_duration</code>에 설정한 10초만큼 대기한다.</p>
<pre><code>import socket

server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind((&#39;127.0.0.1&#39;, 30000))
server_socket.listen()

timeout_duration = 10  # 총 대기 시간 (초)

try:
    # 서버 소켓이 클라이언트의 접속을 기다리는 시간을 timeout_duration으로 설정
    server_socket.settimeout(timeout_duration)

    client_socket, addr = server_socket.accept()
    print(f&quot;{addr} 에서 접속했습니다.&quot;)

    client_socket.close()
except Exception as e:
    print(f&quot;오류가 발생했습니다: {e}&quot;)

finally:
    server_socket.close()
    print(&quot;서버 소켓을 닫았습니다.&quot;)</code></pre><p>실제로 <code>02/server.py</code>를 실행한 뒤, 10초동안 아무것도 안하면 아래와 같이 timeout 에러를 출력하고 종료된다.</p>
<pre><code>python3.13 server.py
오류가 발생했습니다: timed out
서버 소켓을 닫았습니다.</code></pre><p>다음은 client 코드다. <code>ConnectionRefusedError</code> 에러가 발생하면, while 반복문으로 새롭게 소켓을 생성한 뒤, 1초마다 연결을 재시도한다.</p>
<pre><code>import socket
import time

timeout_duration = 10  # 총 대기 시간 (초)
start_time = time.time() # 시작 시간 기록

# 반복문을 돌면서 소켓을 신규로 생성해서 연결 시도
while True:
    try:
        client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

        # 타임아웃 설정 (연결 과정 자체가 느린 경우를 대비)
        client_socket.settimeout(5)

        client_socket.connect((&#39;127.0.0.1&#39;, 30000))
        print(&quot;서버에 연결 시도...&quot;)

        # 연결 성공 시 반복문 탈출
        break
    except ConnectionRefusedError:
        print(&quot;서버에 연결할 수 없습니다. 1초 후 재시도 합니다.&quot;)

        # 재시도 하는 로직 추가
        time.sleep(1)
        if time.time() - start_time &gt; timeout_duration:
            print(f&quot;{timeout_duration}초를 초과해서 종료합니다.&quot;)
            break
    except Exception as e:
        print(f&quot;오류가 발생했습니다: {e}&quot;)
    finally:
        client_socket.close()

# 연결 성공 후 로직 (예: client_socket이 닫히지 않은 경우)
if client_socket and client_socket.fileno() != -1:
    # 여기서 데이터 통신 로직 수행
    try:
        # 예시: 데이터 전송
        # client_socket.sendall(b&#39;Hello&#39;)
        pass
    finally:
        client_socket.close()</code></pre><p><code>02/client.py</code>를 먼저 실행한 후, <code>02/server.py</code>를 실행하면 아래와 같이 재시도 하다가 연결에 성공하고, 연결을 종료한다.
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/8d7c76b1-dac3-4c54-b91f-49a4f30c46a8/image.png" alt=""></p>
<h3 id="언제-에러가-발생할-수-있나">언제 에러가 발생할 수 있나?</h3>
<p><code>02/server.py</code>는 10초 동안 소켓을 열어두고 대기한 뒤, 연결이 없으면 종료한다. 서버가 열었던 소켓을 닫고 종료했는데, client가 11초 뒤에 연결을 시도하면 당연히 에러가 발생할 것이다.
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/8568bb88-42d5-454f-a7b1-bfa4dfe27686/image.png" alt=""></p>
<h1 id="다음-글에서는">다음 글에서는...</h1>
<p>위 상황에서 실제로 Wireshark를 이용해 실제 패킷을 분석해본다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[nodejs, nextjs opentelemetry 적용]]></title>
            <link>https://velog.io/@kyeongjun-dev/nodejs-nextjs-opentelemetry-%EC%A0%81%EC%9A%A9</link>
            <guid>https://velog.io/@kyeongjun-dev/nodejs-nextjs-opentelemetry-%EC%A0%81%EC%9A%A9</guid>
            <pubDate>Sat, 29 Nov 2025 08:15:28 GMT</pubDate>
            <description><![CDATA[<h1 id="참고사이트">참고사이트</h1>
<ul>
<li>node : <a href="https://opentelemetry.io/docs/languages/js/getting-started/nodejs/">https://opentelemetry.io/docs/languages/js/getting-started/nodejs/</a></li>
<li>next : <a href="https://www.checklyhq.com/blog/in-depth-guide-to-monitoring-next-js-apps-with-opentelemetry/">https://www.checklyhq.com/blog/in-depth-guide-to-monitoring-next-js-apps-with-opentelemetry/</a></li>
<li>sdk 변수 : <a href="https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/">https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/</a></li>
<li>소스코드 레포 : <a href="https://github.com/kyeongjun-dev/nodejs-otel">https://github.com/kyeongjun-dev/nodejs-otel</a></li>
</ul>
<hr>
<h1 id="실행">실행</h1>
<h2 id="로컬에서-콘솔로-확인">로컬에서 콘솔로 확인</h2>
<p>레포의 README의 <a href="https://github.com/kyeongjun-dev/nodejs-otel?tab=readme-ov-file#%EB%8D%B0%EB%AA%A8-%EC%95%B1%EC%9C%BC%EB%A1%9C-%ED%85%8C%EC%8A%A4%ED%8A%B8">내용</a>을 따라서 진행하시면 됩니다.</p>
<h2 id="trace를-tempo로-전송한-뒤-grafana로-시각화">trace를 tempo로 전송한 뒤, grafana로 시각화</h2>
<p>소스코드 레포를 clone한 뒤, <code>docker-compose up --build</code> 명령어를 실행합니다.</p>
<pre><code>docker-compose up --build</code></pre><p><code>localhost:3003/?foo=bar</code>로 트래픽을 전송한 뒤, grafana(<code>localhost:3000</code>)으로 접속해서 <code>Explore</code> 메뉴로 이동한 뒤, 서비스명을 <code>my-nodejs</code>(<code>docker-compose.yaml</code> 파일에서 환경변수 <code>OTEL_SERVICE_NAME</code>로 지정한 값)로 검색하면 아래와 같이 수집되는 trace 확인이 가능합니다.
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/9b4e7862-2b59-419b-a421-d99df3d0093b/image.png" alt=""></p>
<p>아래 명령어로 데모 앱을 생성할 수 있고, 자세한 건 레포의 README에 적어두었습니다.</p>
<pre><code>npx create-next-app@latest my-app --yes</code></pre><p>otel 관련 npm 패키지를 설치하고, <code>my-app-docker/instrumentation.ts</code>, <code>my-app-docker/instrumentation.node.ts</code> 파일이 설정에 필요한 전부입니다.</p>
<p>그리고 <code>docker-compose.yaml</code>의 <code>nodejs</code> 서비스 부분의 환경변수 설정에 otlp 관련 환경변수를 간단히 지정할 수 있습니다. (<code>instrumentation.ts</code> 코드에서도 설정이 가능하지만, 더 번거롭다고 생각합니다.)</p>
<h1 id="vercelotel-미사용-이유">@vercel/otel 미사용 이유</h1>
<p>일단 유료버전이 존재하며, kubernetes 환경에 배포했을 때, service의 헬스체크마다 불필요한 로그가 발생합니다.
별도 로직으로 수집을 비활성화할 수는 있지만, 굳이라는 생각이 들어서 native otel 패키지만으로 trace 수집이 가능하도록 했습니다.</p>
<h1 id="log에서-trace-id-확인하기">log에서 trace id 확인하기</h1>
<p>실제 nextjs 데모앱에 <code>localhost:3001</code>에 접근하면 아래와 같은 로그가 출력됩니다.</p>
<pre><code> GET / 200 in 1197ms (compile: 1125ms, render: 72ms)</code></pre><p>위 로그는 nextjs 자체에서 남기는 로그라서 trace id가 <code>instrumentation.node.ts</code>의 콘솔 오버라이딩이 안됩니다.
간단하게라도 테스트를 하려면, <code>my-app-docker/app/page.tsx</code>파일의 <code>Home()</code>에 임의로 console 로그를 찍습니다.</p>
<pre><code>import Image from &quot;next/image&quot;;

export default function Home() {
  console.log(&quot;Testing Trace ID inside Component&quot;);
  return (
(생략)</code></pre><p>그러면 아래와 같이 trace id가 포함된 로그 확인이 가능합니다.</p>
<pre><code>npm run dev

&gt; my-app-docker@0.1.0 dev
&gt; next dev

 ⚠ Port 3000 is in use by process 49870, using available port 3001 instead.
[baseline-browser-mapping] The data in this module is over two months old.  To ensure accurate Baseline data, please update: `npm i baseline-browser-mapping@latest -D`
   ▲ Next.js 16.0.5 (Turbopack)
   - Local:         http://localhost:3001
   - Network:       http://192.168.219.167:3001

 ✓ Starting...
 ✓ Ready in 922ms
trace_id=3b77cd2b9b0ea27b725003a449f46141 Testing Trace ID inside Component
trace_id=91fda6f8fcab43ff7309be3c6c756fc7 Testing Trace ID inside Component
 GET / 200 in 1197ms (compile: 1125ms, render: 72ms)</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[percona-operator-for-mongodb Hidden 노드 기능 지원]]></title>
            <link>https://velog.io/@kyeongjun-dev/psmdb-practice</link>
            <guid>https://velog.io/@kyeongjun-dev/psmdb-practice</guid>
            <pubDate>Sun, 26 Oct 2025 08:32:35 GMT</pubDate>
            <description><![CDATA[<h1 id="소스">소스</h1>
<p><a href="https://github.com/kyeongjun-dev/psmdb-practice">깃허브 링크</a> 및 repo의 README 참고 가능</p>
<h1 id="개요">개요</h1>
<h3 id="몽고디비의-hidden-member">몽고디비의 hidden member</h3>
<p>hidden member는 간략히 설명하자면 클라이언트에게 노출되지 않는 멤버다. (ex 데이터 분석용으로 사용 가능)</p>
<p>mongodb에서 read member를 추가할 때, data의 sync 과정이 이루어진다. 그런데 sync 해야하는 데이터량이 너무 크면, 데이터 sync가 끝나지 않았어도 client에서 연결 및 쿼리 결과가 OK로 반환된다.</p>
<p>따라서 read member 노드를 추가할 때 <a href="https://www.mongodb.com/ko-kr/docs/manual/tutorial/configure-a-hidden-replica-set-member/">hidden node</a>로 추가하는 방식을 사용할 수 있다. hidden으로 추가한 후, read로 변경할 수 있다</p>
<h3 id="percona-operator-for-mongodb">percona operator for mongodb</h3>
<p>쿠버네티스 클러스터에서 operator 패턴으로 mongodb 클러스터를 구축해서 사용할 수 있게 해주는 툴이다.
구성 가능한 구성은 2가지다 - <a href="https://docs.percona.com/percona-operator-for-mongodb/architecture.html">링크</a></p>
<ul>
<li>replica set 구성<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.2" viewBox="3175 1269 22861 12066" preserveAspectRatio="xMidYMid" fill-rule="evenodd" stroke-width="28.222" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" xmlns:ooo="http://xml.openoffice.org/svg/export" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:presentation="http://sun.com/xmlns/staroffice/presentation" xmlns:smil="http://www.w3.org/2001/SMIL20/" xmlns:anim="urn:oasis:names:tc:opendocument:xmlns:animation:1.0" xml:space="preserve">
<defs>
<font id="EmbeddedFont_1" horiz-adv-x="2048">
 <font-face font-family="Arial embedded" units-per-em="2048" font-weight="normal" font-style="normal" ascent="1852" descent="423"/>
 <missing-glyph horiz-adv-x="2048" d="M 0,0 L 2047,0 2047,2047 0,2047 0,0 Z"/>
 <glyph unicode="t" horiz-adv-x="531" d="M 528,161 L 554,2 C 503,-9 458,-14 418,-14 353,-14 302,-4 266,17 230,38 205,65 190,99 175,132 168,203 168,311 L 168,922 36,922 36,1062 168,1062 168,1325 347,1433 347,1062 528,1062 528,922 347,922 347,301 C 347,250 350,217 357,202 363,187 373,176 388,167 402,158 422,154 449,154 469,154 495,156 528,161 Z"/>
 <glyph unicode="r" horiz-adv-x="583" d="M 133,0 L 133,1062 295,1062 295,901 C 336,976 375,1026 410,1050 445,1074 483,1086 525,1086 586,1086 647,1067 710,1028 L 648,861 C 604,887 560,900 516,900 477,900 441,888 410,865 379,841 356,808 343,766 323,702 313,632 313,556 L 313,0 133,0 Z"/>
 <glyph unicode="i" horiz-adv-x="187" d="M 136,1259 L 136,1466 316,1466 316,1259 136,1259 Z M 136,0 L 136,1062 316,1062 316,0 136,0 Z"/>
 <glyph unicode="e" horiz-adv-x="1006" d="M 862,342 L 1048,319 C 1019,210 964,126 885,66 806,6 704,-24 581,-24 426,-24 303,24 212,120 121,215 75,349 75,522 75,701 121,839 213,938 305,1037 424,1086 571,1086 713,1086 829,1038 919,941 1009,844 1054,708 1054,533 1054,522 1054,506 1053,485 L 261,485 C 268,368 301,279 360,217 419,155 493,124 582,124 648,124 704,141 751,176 798,211 835,266 862,342 Z M 271,633 L 864,633 C 856,722 833,789 796,834 739,903 664,938 573,938 490,938 421,910 365,855 308,800 277,726 271,633 Z"/>
 <glyph unicode="d" horiz-adv-x="953" d="M 824,0 L 824,134 C 757,29 658,-24 527,-24 442,-24 365,-1 294,46 223,93 168,158 129,242 90,325 70,421 70,530 70,636 88,732 123,819 158,905 211,971 282,1017 353,1063 432,1086 519,1086 583,1086 640,1073 690,1046 740,1019 781,983 812,940 L 812,1466 991,1466 991,0 824,0 Z M 255,530 C 255,394 284,292 341,225 398,158 466,124 544,124 623,124 690,156 745,221 800,285 827,383 827,515 827,660 799,767 743,835 687,903 618,937 536,937 456,937 389,904 336,839 282,774 255,671 255,530 Z"/>
 <glyph unicode="a" horiz-adv-x="1006" d="M 828,131 C 761,74 697,34 636,11 574,-12 508,-24 437,-24 320,-24 231,5 168,62 105,119 74,191 74,280 74,332 86,380 110,423 133,466 164,500 203,526 241,552 284,572 332,585 367,594 421,603 492,612 637,629 744,650 813,674 814,699 814,714 814,721 814,794 797,846 763,876 717,917 649,937 558,937 473,937 411,922 371,893 330,863 300,810 281,735 L 105,759 C 121,834 147,895 184,942 221,988 274,1024 343,1049 412,1074 493,1086 584,1086 675,1086 748,1075 805,1054 862,1033 903,1006 930,974 957,941 975,900 986,851 992,820 995,765 995,685 L 995,445 C 995,278 999,172 1007,128 1014,83 1029,41 1052,0 L 864,0 C 845,37 833,81 828,131 Z M 813,533 C 748,506 650,484 519,465 445,454 393,442 362,429 331,416 308,396 291,371 274,345 266,316 266,285 266,237 284,197 321,165 357,133 410,117 480,117 549,117 611,132 665,163 719,193 759,234 784,287 803,328 813,388 813,467 L 813,533 Z"/>
 <glyph unicode="W" horiz-adv-x="1906" d="M 414,0 L 25,1466 224,1466 447,505 C 471,404 492,304 509,205 546,362 568,452 575,476 L 854,1466 1088,1466 1298,724 C 1351,540 1389,367 1412,205 1431,298 1455,404 1485,524 L 1715,1466 1910,1466 1508,0 1321,0 1012,1117 C 986,1210 971,1268 966,1289 951,1222 936,1164 923,1117 L 612,0 414,0 Z"/>
 <glyph unicode="R" horiz-adv-x="1297" d="M 161,0 L 161,1466 811,1466 C 942,1466 1041,1453 1109,1427 1177,1400 1231,1354 1272,1287 1313,1220 1333,1147 1333,1066 1333,962 1299,874 1232,803 1165,732 1061,686 920,667 971,642 1010,618 1037,594 1094,542 1147,477 1198,399 L 1453,0 1209,0 1015,305 C 958,393 912,460 875,507 838,554 806,586 777,605 748,624 718,637 688,644 666,649 630,651 580,651 L 355,651 355,0 161,0 Z M 355,819 L 772,819 C 861,819 930,828 980,847 1030,865 1068,894 1094,935 1120,975 1133,1019 1133,1066 1133,1135 1108,1192 1058,1237 1007,1282 928,1304 819,1304 L 355,1304 355,819 Z"/>
</font>
</defs>
<defs>
<font id="EmbeddedFont_2" horiz-adv-x="2048">
 <font-face font-family="Arial embedded" units-per-em="2048" font-weight="bold" font-style="normal" ascent="1852" descent="423"/>
 <missing-glyph horiz-adv-x="2048" d="M 0,0 L 2047,0 2047,2047 0,2047 0,0 Z"/>
 <glyph unicode="v" horiz-adv-x="1112" d="M 439,0 L 11,1062 306,1062 506,520 564,339 C 579,385 589,415 593,430 602,460 612,490 623,520 L 825,1062 1114,1062 692,0 439,0 Z"/>
 <glyph unicode="t" horiz-adv-x="636" d="M 634,1062 L 634,838 442,838 442,410 C 442,323 444,273 448,259 451,244 460,232 473,223 486,214 501,209 520,209 546,209 584,218 633,236 L 657,18 C 592,-10 518,-24 435,-24 384,-24 339,-15 298,2 257,19 228,41 209,68 190,95 176,131 169,177 163,210 160,276 160,375 L 160,838 31,838 31,1062 160,1062 160,1273 442,1437 442,1062 634,1062 Z"/>
 <glyph unicode="r" horiz-adv-x="689" d="M 416,0 L 135,0 135,1062 396,1062 396,911 C 441,982 481,1029 517,1052 552,1075 593,1086 638,1086 702,1086 764,1068 823,1033 L 736,788 C 689,819 645,834 604,834 565,834 531,823 504,802 477,780 455,741 440,684 424,627 416,509 416,328 L 416,0 Z"/>
 <glyph unicode="p" horiz-adv-x="1060" d="M 139,1062 L 401,1062 401,906 C 435,959 481,1003 539,1036 597,1069 661,1086 732,1086 855,1086 960,1038 1046,941 1132,844 1175,710 1175,537 1175,360 1132,222 1045,124 958,25 853,-24 730,-24 671,-24 618,-12 571,11 523,34 473,74 420,131 L 420,-404 139,-404 139,1062 Z M 417,549 C 417,430 441,342 488,285 535,228 593,199 661,199 726,199 781,225 824,278 867,330 889,416 889,535 889,646 867,729 822,783 777,837 722,864 656,864 587,864 530,838 485,785 440,732 417,653 417,549 Z"/>
 <glyph unicode="o" horiz-adv-x="1113" d="M 82,546 C 82,639 105,730 151,817 197,904 262,971 347,1017 431,1063 525,1086 629,1086 790,1086 921,1034 1024,930 1127,825 1178,693 1178,534 1178,373 1126,240 1023,135 919,29 788,-24 631,-24 534,-24 441,-2 353,42 264,86 197,151 151,236 105,321 82,424 82,546 Z M 370,531 C 370,426 395,345 445,289 495,233 557,205 630,205 703,205 765,233 815,289 864,345 889,426 889,533 889,637 864,717 815,773 765,829 703,857 630,857 557,857 495,829 445,773 395,717 370,636 370,531 Z"/>
 <glyph unicode="n" horiz-adv-x="980" d="M 1113,0 L 832,0 832,542 C 832,657 826,731 814,765 802,798 783,824 756,843 729,862 696,871 658,871 609,871 566,858 527,831 488,804 462,769 448,725 433,681 426,600 426,481 L 426,0 145,0 145,1062 406,1062 406,906 C 499,1026 615,1086 756,1086 818,1086 875,1075 926,1053 977,1030 1016,1002 1043,967 1069,932 1087,893 1098,849 1108,805 1113,742 1113,660 L 1113,0 Z"/>
 <glyph unicode="l" horiz-adv-x="319" d="M 147,0 L 147,1466 428,1466 428,0 147,0 Z"/>
 <glyph unicode="i" horiz-adv-x="319" d="M 147,1206 L 147,1466 428,1466 428,1206 147,1206 Z M 147,0 L 147,1062 428,1062 428,0 147,0 Z"/>
 <glyph unicode="g" horiz-adv-x="1060" d="M 121,-70 L 442,-109 C 447,-146 460,-172 479,-186 506,-206 548,-216 605,-216 678,-216 733,-205 770,-183 795,-168 813,-145 826,-112 835,-89 839,-46 839,17 L 839,172 C 755,57 649,0 521,0 378,0 265,60 182,181 117,276 84,395 84,537 84,715 127,851 213,945 298,1039 405,1086 532,1086 663,1086 772,1028 857,913 L 857,1062 1120,1062 1120,109 C 1120,-16 1110,-110 1089,-172 1068,-234 1039,-283 1002,-318 965,-353 915,-381 853,-401 790,-421 711,-431 616,-431 436,-431 308,-400 233,-339 158,-277 120,-199 120,-104 120,-95 120,-83 121,-70 Z M 372,553 C 372,440 394,358 438,306 481,253 535,227 599,227 668,227 726,254 773,308 820,361 844,441 844,546 844,656 821,738 776,791 731,844 673,871 604,871 537,871 481,845 438,793 394,740 372,660 372,553 Z"/>
 <glyph unicode="e" horiz-adv-x="1006" d="M 762,338 L 1042,291 C 1006,188 949,110 872,57 794,3 697,-24 580,-24 395,-24 259,36 170,157 100,254 65,376 65,523 65,699 111,837 203,937 295,1036 411,1086 552,1086 710,1086 835,1034 926,930 1017,825 1061,665 1057,450 L 353,450 C 355,367 378,302 421,256 464,209 518,186 583,186 627,186 664,198 694,222 724,246 747,285 762,338 Z M 778,622 C 776,703 755,765 715,808 675,850 626,871 569,871 508,871 457,849 417,804 377,759 357,699 358,622 L 778,622 Z"/>
 <glyph unicode="d" horiz-adv-x="1060" d="M 1121,0 L 860,0 860,156 C 817,95 766,50 707,21 648,-9 588,-24 528,-24 406,-24 302,25 215,124 128,222 84,359 84,535 84,715 126,852 211,946 296,1039 403,1086 532,1086 651,1086 753,1037 840,938 L 840,1466 1121,1466 1121,0 Z M 371,554 C 371,441 387,359 418,308 463,235 527,198 608,198 673,198 728,226 773,281 818,336 841,418 841,527 841,649 819,737 775,791 731,844 675,871 606,871 539,871 484,845 439,792 394,739 371,659 371,554 Z"/>
 <glyph unicode="c" horiz-adv-x="1007" d="M 1073,748 L 796,698 C 787,753 766,795 733,823 700,851 657,865 604,865 534,865 478,841 437,793 395,744 374,663 374,550 374,424 395,335 438,283 480,231 537,205 608,205 661,205 705,220 739,251 773,281 797,333 811,407 L 1087,360 C 1058,233 1003,138 922,73 841,8 732,-24 595,-24 440,-24 316,25 224,123 131,221 85,357 85,530 85,705 131,842 224,940 317,1037 442,1086 600,1086 729,1086 832,1058 909,1003 985,947 1040,862 1073,748 Z"/>
 <glyph unicode="a" horiz-adv-x="1033" d="M 357,738 L 102,784 C 131,887 180,963 250,1012 320,1061 424,1086 562,1086 687,1086 781,1071 842,1042 903,1012 947,974 972,929 997,883 1009,799 1009,677 L 1006,349 C 1006,256 1011,187 1020,143 1029,98 1045,51 1070,0 L 792,0 C 785,19 776,46 765,83 760,100 757,111 755,116 707,69 656,34 601,11 546,-12 488,-24 426,-24 317,-24 231,6 168,65 105,124 73,199 73,290 73,350 87,404 116,451 145,498 185,534 237,559 288,584 363,605 460,624 591,649 682,672 733,693 L 733,721 C 733,775 720,814 693,837 666,860 616,871 542,871 492,871 453,861 425,842 397,822 374,787 357,738 Z M 733,510 C 697,498 640,484 562,467 484,450 433,434 409,418 372,392 354,359 354,319 354,280 369,246 398,217 427,188 465,174 510,174 561,174 609,191 655,224 689,249 711,280 722,317 729,341 733,387 733,454 L 733,510 Z"/>
 <glyph unicode="P" horiz-adv-x="1139" d="M 149,0 L 149,1466 624,1466 C 804,1466 921,1459 976,1444 1060,1422 1130,1374 1187,1301 1244,1227 1272,1132 1272,1015 1272,925 1256,849 1223,788 1190,727 1149,679 1099,644 1048,609 997,585 945,574 874,560 772,553 638,553 L 445,553 445,0 149,0 Z M 445,1218 L 445,802 607,802 C 724,802 802,810 841,825 880,840 911,864 934,897 956,930 967,968 967,1011 967,1064 951,1108 920,1143 889,1178 849,1199 801,1208 766,1215 695,1218 588,1218 L 445,1218 Z"/>
 <glyph unicode="M" horiz-adv-x="1430" d="M 145,0 L 145,1466 588,1466 854,466 1117,1466 1561,1466 1561,0 1286,0 1286,1154 995,0 710,0 420,1154 420,0 145,0 Z"/>
 <glyph unicode="D" horiz-adv-x="1245" d="M 148,1466 L 689,1466 C 811,1466 904,1457 968,1438 1054,1413 1128,1368 1189,1303 1250,1238 1297,1159 1329,1066 1361,972 1377,856 1377,719 1377,598 1362,494 1332,407 1295,300 1243,214 1175,148 1124,98 1054,59 967,31 902,10 814,0 705,0 L 148,0 148,1466 Z M 444,1218 L 444,247 665,247 C 748,247 807,252 844,261 892,273 932,293 964,322 995,351 1021,398 1041,464 1061,529 1071,619 1071,732 1071,845 1061,932 1041,993 1021,1054 993,1101 957,1135 921,1169 875,1192 820,1204 779,1213 698,1218 577,1218 L 444,1218 Z"/>
 <glyph unicode="C" horiz-adv-x="1298" d="M 1087,539 L 1374,448 C 1330,288 1257,169 1155,92 1052,14 922,-25 765,-25 570,-25 410,42 285,175 160,308 97,489 97,720 97,964 160,1154 286,1289 412,1424 578,1491 783,1491 962,1491 1108,1438 1220,1332 1287,1269 1337,1179 1370,1062 L 1077,992 C 1060,1068 1024,1128 969,1172 914,1216 847,1238 768,1238 659,1238 571,1199 504,1121 436,1043 402,917 402,742 402,557 435,425 502,346 569,267 655,228 762,228 841,228 908,253 965,303 1022,353 1062,432 1087,539 Z"/>
 <glyph unicode="B" horiz-adv-x="1245" d="M 150,1466 L 736,1466 C 852,1466 939,1461 996,1452 1053,1442 1104,1422 1149,1391 1194,1360 1231,1320 1261,1269 1291,1218 1306,1160 1306,1097 1306,1028 1288,965 1251,908 1214,851 1163,808 1100,779 1189,753 1258,709 1306,646 1354,583 1378,509 1378,425 1378,358 1363,293 1332,230 1301,167 1258,116 1205,79 1151,41 1085,18 1006,9 957,4 838,1 649,1 L 150,1 150,1466 Z M 446,1222 L 446,883 640,883 C 755,883 827,885 855,888 906,894 946,912 975,941 1004,970 1018,1008 1018,1055 1018,1100 1006,1137 981,1166 956,1194 918,1211 869,1217 840,1220 755,1222 616,1222 L 446,1222 Z M 446,639 L 446,247 720,247 C 827,247 894,250 923,256 967,264 1003,284 1031,315 1058,346 1072,387 1072,439 1072,483 1061,520 1040,551 1019,582 988,604 948,618 907,632 820,639 685,639 L 446,639 Z"/>
 <glyph unicode="A" horiz-adv-x="1483" d="M 1471,0 L 1149,0 1021,333 435,333 314,0 0,0 571,1466 884,1466 1471,0 Z M 926,580 L 724,1124 526,580 926,580 Z"/>
 <glyph unicode="3" horiz-adv-x="1006" d="M 77,389 L 349,422 C 358,353 381,300 419,263 457,226 503,208 557,208 615,208 664,230 704,274 743,318 763,377 763,452 763,523 744,579 706,620 668,661 622,682 567,682 531,682 488,675 438,661 L 469,888 C 545,888 603,905 643,940 683,975 703,1022 703,1079 703,1129 688,1168 659,1197 630,1226 591,1241 542,1241 494,1241 453,1224 419,1191 385,1158 364,1109 357,1045 L 98,1089 C 116,1178 143,1249 180,1302 216,1355 267,1396 332,1427 397,1457 469,1472 550,1472 688,1472 799,1428 882,1340 951,1268 985,1187 985,1096 985,967 915,865 774,788 858,770 925,730 976,667 1026,604 1051,529 1051,440 1051,311 1004,202 910,111 816,20 699,-25 559,-25 426,-25 316,13 229,90 142,166 91,266 77,389 Z"/>
 <glyph unicode="2" horiz-adv-x="1007" d="M 1036,261 L 1036,0 51,0 C 62,99 94,192 147,281 200,369 306,486 463,632 590,750 667,830 696,872 735,930 754,987 754,1044 754,1107 737,1155 704,1189 670,1222 623,1239 564,1239 505,1239 459,1221 424,1186 389,1151 369,1092 364,1010 L 84,1038 C 101,1193 153,1304 241,1371 329,1438 439,1472 571,1472 716,1472 829,1433 912,1355 995,1277 1036,1180 1036,1064 1036,998 1024,935 1001,876 977,816 939,753 888,688 854,645 793,582 704,501 615,420 559,366 536,339 512,312 493,286 478,261 L 1036,261 Z"/>
 <glyph unicode="1" horiz-adv-x="662" d="M 806,0 L 525,0 525,1059 C 422,963 301,892 162,846 L 162,1101 C 235,1125 315,1171 401,1238 487,1305 546,1383 578,1472 L 806,1472 806,0 Z"/>
 <glyph unicode=" " horiz-adv-x="556"/>
</font>
</defs>
<defs class="EmbeddedBulletChars">
<g id="bullet-char-template-57356" transform="scale(0.00048828125,-0.00048828125)">
 <path d="M 580,1141 L 1163,571 580,0 -4,571 580,1141 Z"/>
</g>
<g id="bullet-char-template-57354" transform="scale(0.00048828125,-0.00048828125)">
 <path d="M 8,1128 L 1137,1128 1137,0 8,0 8,1128 Z"/>
</g>
<g id="bullet-char-template-10146" transform="scale(0.00048828125,-0.00048828125)">
 <path d="M 174,0 L 602,739 174,1481 1456,739 174,0 Z M 1358,739 L 309,1346 659,739 1358,739 Z"/>
</g>
<g id="bullet-char-template-10132" transform="scale(0.00048828125,-0.00048828125)">
 <path d="M 2015,739 L 1276,0 717,0 1260,543 174,543 174,936 1260,936 717,1481 1274,1481 2015,739 Z"/>
</g>
<g id="bullet-char-template-10007" transform="scale(0.00048828125,-0.00048828125)">
 <path d="M 0,-2 C -7,14 -16,27 -25,37 L 356,567 C 262,823 215,952 215,954 215,979 228,992 255,992 264,992 276,990 289,987 310,991 331,999 354,1012 L 381,999 492,748 772,1049 836,1024 860,1049 C 881,1039 901,1025 922,1006 886,937 835,863 770,784 769,783 710,716 594,584 L 774,223 C 774,196 753,168 711,139 L 727,119 C 717,90 699,76 672,76 641,76 570,178 457,381 L 164,-76 C 142,-110 111,-127 72,-127 30,-127 9,-110 8,-76 1,-67 -2,-52 -2,-32 -2,-23 -1,-13 0,-2 Z"/>
</g>
<g id="bullet-char-template-10004" transform="scale(0.00048828125,-0.00048828125)">
 <path d="M 285,-33 C 182,-33 111,30 74,156 52,228 41,333 41,471 41,549 55,616 82,672 116,743 169,778 240,778 293,778 328,747 346,684 L 369,508 C 377,444 397,411 428,410 L 1163,1116 C 1174,1127 1196,1133 1229,1133 1271,1133 1292,1118 1292,1087 L 1292,965 C 1292,929 1282,901 1262,881 L 442,47 C 390,-6 338,-33 285,-33 Z"/>
</g>
<g id="bullet-char-template-9679" transform="scale(0.00048828125,-0.00048828125)">
 <path d="M 813,0 C 632,0 489,54 383,161 276,268 223,411 223,592 223,773 276,916 383,1023 489,1130 632,1184 813,1184 992,1184 1136,1130 1245,1023 1353,916 1407,772 1407,592 1407,412 1353,268 1245,161 1136,54 992,0 813,0 Z"/>
</g>
<g id="bullet-char-template-8226" transform="scale(0.00048828125,-0.00048828125)">
 <path d="M 346,457 C 273,457 209,483 155,535 101,586 74,649 74,723 74,796 101,859 155,911 209,963 273,989 346,989 419,989 480,963 531,910 582,859 608,796 608,723 608,648 583,586 532,535 482,483 420,457 346,457 Z"/>
</g>
<g id="bullet-char-template-8211" transform="scale(0.00048828125,-0.00048828125)">
 <path d="M -4,459 L 1135,459 1135,606 -4,606 -4,459 Z"/>
</g>
<g id="bullet-char-template-61548" transform="scale(0.00048828125,-0.00048828125)">
 <path d="M 173,740 C 173,903 231,1043 346,1159 462,1274 601,1332 765,1332 928,1332 1067,1274 1183,1159 1299,1043 1357,903 1357,740 1357,577 1299,437 1183,322 1067,206 928,148 765,148 601,148 462,206 346,322 231,437 173,577 173,740 Z"/>
</g>
</defs>
<g ooo:name="page3" class="Page">
<g class="com.sun.star.drawing.CustomShape">
 <g id="id3">
  <rect class="BoundingBox" stroke="none" fill="none" x="3175" y="1269" width="22861" height="12066"/>
  <path fill="rgb(255,255,255)" stroke="none" d="M 14605,13334 L 3175,13334 3175,1269 26035,1269 26035,13334 14605,13334 Z"/>
 </g>
</g>
<g class="com.sun.star.drawing.CustomShape">
 <g id="id4">
  <rect class="BoundingBox" stroke="none" fill="none" x="6684" y="3174" width="15876" height="9726"/>
  <path fill="rgb(255,255,255)" stroke="none" d="M 14622,12899 L 6684,12899 6684,3174 22559,3174 22559,12899 14622,12899 Z"/>
 </g>
</g>
<g class="com.sun.star.drawing.CustomShape">
 <g id="id5">
  <rect class="BoundingBox" stroke="none" fill="none" x="6958" y="6529" width="15296" height="6046"/>
  <path fill="rgb(212,237,251)" stroke="none" d="M 7445,6556 L 7445,6556 C 7364,6556 7285,6577 7215,6618 7145,6658 7087,6716 7047,6786 7006,6856 6985,6935 6985,7016 L 6985,12086 6985,12087 C 6985,12168 7006,12247 7047,12317 7087,12387 7145,12445 7215,12485 7285,12526 7364,12547 7445,12547 L 21765,12547 21766,12547 C 21847,12547 21926,12526 21996,12485 22066,12445 22124,12387 22164,12317 22205,12247 22226,12168 22226,12087 L 22226,7016 22226,7016 22226,7016 C 22226,6935 22205,6856 22164,6786 22124,6716 22066,6658 21996,6618 21926,6577 21847,6556 21766,6556 L 7445,6556 Z"/>
  <path fill="none" stroke="rgb(114,159,207)" stroke-width="53" stroke-linejoin="round" d="M 7445,6556 L 7445,6556 C 7364,6556 7285,6577 7215,6618 7145,6658 7087,6716 7047,6786 7006,6856 6985,6935 6985,7016 L 6985,12086 6985,12087 C 6985,12168 7006,12247 7047,12317 7087,12387 7145,12445 7215,12485 7285,12526 7364,12547 7445,12547 L 21765,12547 21766,12547 C 21847,12547 21926,12526 21996,12485 22066,12445 22124,12387 22164,12317 22205,12247 22226,12168 22226,12087 L 22226,7016 22226,7016 22226,7016 C 22226,6935 22205,6856 22164,6786 22124,6716 22066,6658 21996,6618 21926,6577 21847,6556 21766,6556 L 7445,6556 Z"/>
 </g>
</g>
<g class="com.sun.star.drawing.CustomShape">
 <g id="id6">
  <rect class="BoundingBox" stroke="none" fill="none" x="8825" y="7855" width="2667" height="3882"/>
  <path fill="rgb(50,108,229)" stroke="none" d="M 10157,7855 C 9805,7855 9474,7925 9226,8054 8992,8176 8858,8337 8858,8496 8858,8656 8992,8818 9226,8939 9474,9067 9805,9139 10157,9139 10509,9139 10840,9068 11088,8939 11322,8818 11456,8656 11456,8496 11456,8337 11322,8176 11088,8054 10840,7926 10509,7855 10157,7855 L 10157,7855 Z M 8826,8664 L 8826,8917 C 8826,9298 9422,9607 10158,9607 10894,9607 11490,9299 11490,8917 L 11490,8664 C 11432,8804 11303,8931 11111,9030 10855,9162 10517,9234 10158,9234 9798,9234 9459,9162 9205,9030 9013,8930 8883,8804 8826,8664 L 8826,8664 Z M 8825,9170 L 8825,9343 C 8825,9723 9421,10033 10157,10033 10893,10033 11490,9725 11490,9343 L 11490,9170 C 11472,9201 11452,9232 11428,9261 11355,9351 11252,9430 11122,9498 10863,9632 10520,9705 10157,9705 9794,9705 9451,9632 9192,9498 9062,9430 8960,9351 8887,9261 8862,9232 8842,9201 8825,9170 L 8825,9170 Z M 8825,9595 L 8825,9769 C 8825,10149 9421,10458 10157,10458 10893,10458 11490,10150 11490,9769 L 11490,9595 C 11472,9627 11452,9657 11428,9687 11355,9777 11252,9856 11122,9923 10863,10057 10520,10131 10157,10131 9794,10131 9451,10057 9192,9923 9062,9856 8960,9777 8887,9687 8862,9657 8842,9627 8825,9595 L 8825,9595 Z M 8825,10021 L 8825,10194 C 8825,10575 9421,10884 10157,10884 10893,10884 11490,10576 11490,10194 L 11490,10021 C 11472,10052 11452,10083 11428,10112 11355,10202 11252,10281 11122,10349 10863,10483 10520,10556 10157,10556 9794,10556 9451,10483 9192,10349 9062,10281 8960,10202 8887,10112 8862,10083 8842,10052 8825,10021 L 8825,10021 Z M 8825,10447 L 8825,10620 C 8825,11001 9421,11310 10157,11310 10893,11310 11490,11002 11490,10620 L 11490,10447 C 11472,10478 11452,10509 11428,10538 11355,10628 11252,10707 11122,10775 10863,10909 10520,10982 10157,10982 9794,10982 9451,10909 9192,10775 9062,10707 8960,10628 8887,10538 8862,10509 8842,10478 8825,10447 L 8825,10447 Z M 8825,10873 L 8825,11046 C 8825,11426 9421,11736 10157,11736 10893,11736 11490,11426 11490,11046 L 11490,10873 C 11472,10904 11452,10934 11428,10964 11355,11054 11252,11133 11122,11200 10863,11334 10520,11408 10157,11408 9794,11408 9451,11334 9192,11200 9062,11133 8960,11054 8887,10964 8862,10934 8842,10904 8825,10873 L 8825,10873 Z"/>
 </g>
</g>
<g class="com.sun.star.drawing.CustomShape">
 <g id="id7">
  <rect class="BoundingBox" stroke="none" fill="none" x="9133" y="8100" width="2044" height="750"/>
  <text class="SVGTextShape"><tspan class="TextParagraph" font-family="Arial, sans-serif" font-size="388px" font-weight="700"><tspan class="TextPosition" x="9274" y="8631"><tspan fill="rgb(255,255,255)" stroke="none" style="white-space: pre">DB Pod 1</tspan></tspan></tspan></text>
 </g>
</g>
<g class="com.sun.star.drawing.CustomShape">
 <g id="id8">
  <rect class="BoundingBox" stroke="none" fill="none" x="17816" y="7855" width="2666" height="3882"/>
  <path fill="rgb(50,108,229)" stroke="none" d="M 19148,7855 C 18796,7855 18465,7925 18217,8054 17983,8176 17849,8337 17849,8496 17849,8656 17983,8818 18217,8939 18465,9067 18796,9139 19148,9139 19500,9139 19831,9068 20079,8939 20313,8818 20447,8656 20447,8496 20447,8337 20313,8176 20079,8054 19831,7926 19500,7855 19148,7855 L 19148,7855 Z M 17817,8664 L 17817,8917 C 17817,9298 18413,9607 19149,9607 19885,9607 20481,9299 20481,8917 L 20481,8664 C 20423,8804 20294,8931 20102,9030 19846,9162 19508,9234 19149,9234 18789,9234 18450,9162 18196,9030 18004,8930 17874,8804 17817,8664 L 17817,8664 Z M 17816,9170 L 17816,9343 C 17816,9723 18412,10033 19148,10033 19884,10033 20481,9725 20481,9343 L 20481,9170 C 20463,9201 20443,9232 20419,9261 20346,9351 20243,9430 20113,9498 19854,9632 19511,9705 19148,9705 18785,9705 18442,9632 18183,9498 18053,9430 17951,9351 17878,9261 17853,9232 17833,9201 17816,9170 L 17816,9170 Z M 17816,9595 L 17816,9769 C 17816,10149 18412,10458 19148,10458 19884,10458 20481,10150 20481,9769 L 20481,9595 C 20463,9627 20443,9657 20419,9687 20346,9777 20243,9856 20113,9923 19854,10057 19511,10131 19148,10131 18785,10131 18442,10057 18183,9923 18053,9856 17951,9777 17878,9687 17853,9657 17833,9627 17816,9595 L 17816,9595 Z M 17816,10021 L 17816,10194 C 17816,10575 18412,10884 19148,10884 19884,10884 20481,10576 20481,10194 L 20481,10021 C 20463,10052 20443,10083 20419,10112 20346,10202 20243,10281 20113,10349 19854,10483 19511,10556 19148,10556 18785,10556 18442,10483 18183,10349 18053,10281 17951,10202 17878,10112 17853,10083 17833,10052 17816,10021 L 17816,10021 Z M 17816,10447 L 17816,10620 C 17816,11001 18412,11310 19148,11310 19884,11310 20481,11002 20481,10620 L 20481,10447 C 20463,10478 20443,10509 20419,10538 20346,10628 20243,10707 20113,10775 19854,10909 19511,10982 19148,10982 18785,10982 18442,10909 18183,10775 18053,10707 17951,10628 17878,10538 17853,10509 17833,10478 17816,10447 L 17816,10447 Z M 17816,10873 L 17816,11046 C 17816,11426 18412,11736 19148,11736 19884,11736 20481,11426 20481,11046 L 20481,10873 C 20463,10904 20443,10934 20419,10964 20346,11054 20243,11133 20113,11200 19854,11334 19511,11408 19148,11408 18785,11408 18442,11334 18183,11200 18053,11133 17951,11054 17878,10964 17853,10934 17833,10904 17816,10873 L 17816,10873 Z"/>
 </g>
</g>
<g class="com.sun.star.drawing.CustomShape">
 <g id="id9">
  <rect class="BoundingBox" stroke="none" fill="none" x="18125" y="8100" width="2044" height="750"/>
  <text class="SVGTextShape"><tspan class="TextParagraph" font-family="Arial, sans-serif" font-size="388px" font-weight="700"><tspan class="TextPosition" x="18266" y="8631"><tspan fill="rgb(255,255,255)" stroke="none" style="white-space: pre">DB Pod 3</tspan></tspan></tspan></text>
 </g>
</g>
<g class="com.sun.star.drawing.CustomShape">
 <g id="id10">
  <rect class="BoundingBox" stroke="none" fill="none" x="13320" y="7855" width="2666" height="3882"/>
  <path fill="rgb(50,108,229)" stroke="none" d="M 14652,7855 C 14300,7855 13969,7925 13721,8054 13487,8176 13353,8337 13353,8496 13353,8656 13487,8818 13721,8939 13969,9067 14300,9139 14652,9139 15004,9139 15335,9068 15583,8939 15817,8818 15951,8656 15951,8496 15951,8337 15817,8176 15583,8054 15335,7926 15004,7855 14652,7855 L 14652,7855 Z M 13321,8664 L 13321,8917 C 13321,9298 13917,9607 14653,9607 15389,9607 15985,9299 15985,8917 L 15985,8664 C 15927,8804 15798,8931 15606,9030 15350,9162 15012,9234 14653,9234 14293,9234 13954,9162 13700,9030 13508,8930 13378,8804 13321,8664 L 13321,8664 Z M 13320,9170 L 13320,9343 C 13320,9723 13916,10033 14652,10033 15388,10033 15985,9725 15985,9343 L 15985,9170 C 15967,9201 15947,9232 15923,9261 15850,9351 15747,9430 15617,9498 15358,9632 15015,9705 14652,9705 14289,9705 13946,9632 13687,9498 13557,9430 13455,9351 13382,9261 13357,9232 13337,9201 13320,9170 L 13320,9170 Z M 13320,9595 L 13320,9769 C 13320,10149 13916,10458 14652,10458 15388,10458 15985,10150 15985,9769 L 15985,9595 C 15967,9627 15947,9657 15923,9687 15850,9777 15747,9856 15617,9923 15358,10057 15015,10131 14652,10131 14289,10131 13946,10057 13687,9923 13557,9856 13455,9777 13382,9687 13357,9657 13337,9627 13320,9595 L 13320,9595 Z M 13320,10021 L 13320,10194 C 13320,10575 13916,10884 14652,10884 15388,10884 15985,10576 15985,10194 L 15985,10021 C 15967,10052 15947,10083 15923,10112 15850,10202 15747,10281 15617,10349 15358,10483 15015,10556 14652,10556 14289,10556 13946,10483 13687,10349 13557,10281 13455,10202 13382,10112 13357,10083 13337,10052 13320,10021 L 13320,10021 Z M 13320,10447 L 13320,10620 C 13320,11001 13916,11310 14652,11310 15388,11310 15985,11002 15985,10620 L 15985,10447 C 15967,10478 15947,10509 15923,10538 15850,10628 15747,10707 15617,10775 15358,10909 15015,10982 14652,10982 14289,10982 13946,10909 13687,10775 13557,10707 13455,10628 13382,10538 13357,10509 13337,10478 13320,10447 L 13320,10447 Z M 13320,10873 L 13320,11046 C 13320,11426 13916,11736 14652,11736 15388,11736 15985,11426 15985,11046 L 15985,10873 C 15967,10904 15947,10934 15923,10964 15850,11054 15747,11133 15617,11200 15358,11334 15015,11408 14652,11408 14289,11408 13946,11334 13687,11200 13557,11133 13455,11054 13382,10964 13357,10934 13337,10904 13320,10873 L 13320,10873 Z"/>
 </g>
</g>
<g class="com.sun.star.drawing.CustomShape">
 <g id="id11">
  <rect class="BoundingBox" stroke="none" fill="none" x="13629" y="8100" width="2044" height="750"/>
  <text class="SVGTextShape"><tspan class="TextParagraph" font-family="Arial, sans-serif" font-size="388px" font-weight="700"><tspan class="TextPosition" x="13770" y="8631"><tspan fill="rgb(255,255,255)" stroke="none" style="white-space: pre">DB Pod 2</tspan></tspan></tspan></text>
 </g>
</g>
<g class="Graphic">
 <g id="id12">
  <rect class="BoundingBox" stroke="none" fill="none" x="7633" y="5966" width="1331" height="1291"/>
  <path fill="rgb(50,108,229)" stroke="none" d="M 8284,5995 L 8284,5995 C 8273,5996 8262,5999 8252,6004 L 7816,6211 C 7814,6212 7812,6213 7810,6214 7798,6222 7787,6232 7780,6245 7776,6252 7773,6259 7771,6267 L 7664,6734 C 7663,6740 7662,6746 7662,6753 7662,6767 7666,6781 7673,6793 7674,6794 7675,6796 7676,6798 7678,6800 7679,6802 7681,6804 L 7982,7179 C 7989,7186 7996,7193 8005,7198 8018,7205 8032,7209 8047,7209 8047,7209 8047,7209 8047,7209 L 8530,7209 C 8556,7209 8579,7197 8595,7178 L 8896,6804 C 8912,6784 8918,6758 8912,6734 L 8805,6267 C 8799,6243 8782,6222 8760,6211 L 8324,6004 C 8313,5998 8300,5995 8287,5995 8286,5995 8285,5995 8284,5995 L 8284,5995 Z"/>
  <path fill="rgb(255,255,255)" stroke="none" d="M 8288,6154 C 8284,6155 8280,6156 8276,6158 8272,6160 8269,6164 8266,6168 8264,6172 8263,6177 8263,6182 8263,6182 8263,6183 8263,6183 L 8263,6191 C 8263,6199 8265,6207 8266,6215 8269,6231 8270,6247 8269,6263 8268,6265 8267,6267 8266,6269 8265,6272 8263,6274 8260,6276 L 8260,6287 C 8244,6288 8228,6291 8213,6294 8180,6302 8148,6314 8119,6331 8091,6347 8065,6368 8042,6392 L 8033,6385 C 8028,6386 8022,6386 8017,6383 8005,6374 7993,6363 7982,6352 7977,6345 7971,6339 7965,6334 L 7959,6329 C 7958,6328 7957,6327 7955,6326 7951,6324 7946,6322 7941,6322 7941,6322 7940,6322 7940,6322 7936,6322 7931,6323 7927,6325 7925,6327 7922,6329 7921,6331 7920,6332 7920,6333 7919,6334 7917,6338 7915,6342 7915,6347 7915,6352 7917,6357 7919,6361 7921,6364 7924,6367 7927,6370 L 7932,6374 C 7939,6378 7946,6382 7954,6386 7968,6394 7981,6403 7993,6414 7993,6414 7994,6415 7994,6416 7997,6420 7998,6424 7998,6429 L 8007,6436 C 8003,6442 7999,6448 7995,6455 7966,6506 7950,6564 7950,6623 7950,6639 7951,6655 7954,6671 L 7943,6674 C 7940,6679 7936,6683 7932,6686 7916,6690 7901,6692 7885,6693 7877,6694 7868,6694 7860,6695 L 7853,6697 7852,6697 C 7849,6697 7846,6698 7843,6700 7839,6702 7835,6706 7833,6710 7831,6714 7830,6718 7830,6723 7830,6727 7831,6732 7833,6736 7834,6738 7836,6740 7837,6741 7839,6743 7841,6744 7843,6745 7847,6748 7851,6749 7856,6749 7858,6749 7861,6748 7864,6748 L 7864,6748 7871,6747 C 7879,6744 7887,6742 7895,6738 7909,6732 7925,6728 7940,6725 7945,6725 7949,6726 7953,6729 7954,6729 7954,6730 7955,6730 L 7967,6728 C 7992,6807 8046,6874 8117,6916 L 8112,6926 C 8114,6930 8115,6935 8115,6940 8108,6955 8100,6970 8091,6983 8086,6990 8081,6997 8077,7004 L 8074,7011 C 8071,7015 8070,7020 8070,7024 8070,7029 8071,7033 8074,7037 8076,7041 8079,7045 8083,7047 8087,7049 8092,7050 8096,7050 8097,7050 8098,7050 8099,7050 8103,7050 8106,7049 8109,7047 8113,7045 8117,7041 8119,7037 8120,7036 8120,7035 8121,7033 L 8124,7027 C 8127,7019 8130,7011 8132,7003 8138,6986 8142,6968 8152,6956 8153,6955 8154,6954 8156,6954 8158,6952 8160,6951 8163,6951 L 8169,6940 C 8207,6955 8248,6963 8290,6963 8331,6963 8371,6955 8409,6941 L 8414,6951 C 8417,6951 8419,6952 8421,6953 8424,6955 8426,6956 8428,6959 8435,6973 8441,6987 8446,7002 8448,7011 8450,7019 8453,7026 L 8456,7033 C 8457,7034 8457,7036 8458,7037 8460,7041 8464,7044 8468,7046 8472,7049 8476,7050 8481,7050 8485,7050 8490,7049 8494,7046 8498,7044 8501,7042 8503,7038 8503,7038 8503,7037 8503,7037 8506,7033 8507,7028 8507,7024 8507,7019 8506,7015 8503,7011 8503,7011 8503,7011 8503,7011 L 8500,7004 C 8496,6996 8491,6990 8486,6983 8482,6977 8478,6971 8474,6964 8470,6957 8466,6949 8463,6942 8462,6940 8462,6938 8462,6937 8462,6933 8463,6930 8465,6927 8465,6926 8465,6926 8465,6926 8464,6923 8462,6919 8461,6915 8511,6886 8552,6844 8581,6794 8593,6773 8603,6750 8610,6727 L 8622,6729 C 8623,6728 8624,6727 8625,6726 8628,6725 8632,6724 8635,6724 8636,6724 8636,6724 8636,6724 8652,6727 8667,6731 8682,6737 8689,6741 8697,6743 8705,6746 8707,6746 8710,6747 8712,6747 L 8712,6747 C 8715,6748 8717,6749 8720,6749 8725,6749 8729,6747 8733,6745 8737,6743 8741,6739 8743,6735 8745,6731 8746,6727 8746,6722 8746,6720 8746,6717 8745,6714 8745,6712 8744,6711 8743,6709 8741,6705 8737,6702 8733,6700 8730,6698 8727,6697 8724,6697 L 8716,6695 C 8708,6694 8700,6693 8691,6693 8675,6692 8660,6689 8644,6685 8644,6685 8644,6685 8644,6685 8640,6682 8636,6679 8633,6674 8633,6674 8633,6674 8633,6674 L 8623,6671 C 8625,6656 8626,6640 8626,6625 8626,6565 8610,6507 8580,6456 8577,6449 8573,6443 8568,6436 L 8578,6428 C 8577,6423 8579,6417 8583,6413 8594,6403 8606,6394 8620,6387 8620,6387 8621,6386 8621,6386 8625,6384 8628,6382 8631,6381 8635,6378 8639,6376 8643,6373 L 8649,6369 C 8652,6366 8655,6363 8657,6360 8660,6356 8661,6351 8661,6346 8661,6342 8660,6337 8657,6333 8655,6329 8652,6326 8648,6324 8646,6323 8645,6322 8643,6322 8641,6321 8638,6320 8635,6320 8630,6320 8626,6321 8622,6324 8620,6325 8618,6326 8616,6328 L 8610,6333 C 8604,6338 8599,6344 8593,6351 8583,6363 8571,6373 8559,6383 8556,6384 8552,6385 8548,6385 8547,6385 8545,6385 8543,6385 L 8533,6392 C 8476,6332 8399,6295 8317,6287 8317,6283 8316,6278 8316,6276 8314,6274 8312,6272 8310,6269 8309,6267 8308,6265 8308,6263 8307,6247 8308,6231 8311,6216 8312,6208 8314,6199 8314,6191 L 8314,6183 C 8314,6183 8314,6182 8314,6182 8314,6177 8313,6172 8311,6168 8308,6164 8305,6160 8301,6158 8297,6156 8293,6154 8289,6154 L 8288,6154 Z M 8256,6356 L 8248,6493 8247,6493 C 8247,6501 8242,6508 8234,6513 8227,6517 8218,6515 8211,6510 L 8099,6431 C 8115,6415 8133,6401 8153,6390 8176,6377 8202,6367 8228,6361 8237,6359 8246,6357 8256,6356 L 8256,6356 Z M 8321,6356 C 8380,6364 8435,6390 8477,6431 L 8366,6510 C 8366,6511 8365,6512 8364,6512 8360,6514 8356,6515 8352,6515 8348,6515 8344,6514 8341,6512 8337,6510 8334,6507 8332,6504 8330,6500 8329,6497 8329,6493 L 8321,6356 Z M 8161,6574 L 8161,6574 C 8162,6576 8164,6578 8165,6580 8167,6584 8168,6588 8168,6592 8168,6596 8167,6600 8165,6603 8163,6607 8160,6610 8157,6612 8155,6613 8153,6613 8152,6614 L 8152,6614 8020,6652 C 8019,6642 8018,6633 8018,6623 8018,6576 8031,6529 8054,6489 8056,6487 8057,6484 8058,6482 L 8161,6574 Z M 8518,6482 C 8519,6485 8520,6487 8522,6489 8546,6530 8558,6577 8558,6625 8558,6634 8558,6643 8557,6652 L 8425,6614 8425,6613 C 8423,6613 8421,6612 8420,6611 8416,6609 8413,6606 8411,6603 8410,6601 8409,6598 8409,6596 8408,6594 8408,6593 8408,6591 8408,6587 8409,6583 8411,6579 8413,6577 8414,6575 8416,6574 L 8518,6483 8518,6482 Z M 8267,6581 L 8309,6581 8335,6613 8325,6654 8288,6672 8250,6654 8241,6613 8267,6581 Z M 8402,6692 C 8404,6692 8406,6692 8407,6692 L 8543,6715 C 8523,6771 8484,6819 8434,6851 L 8382,6724 C 8380,6722 8380,6719 8380,6715 8380,6711 8381,6707 8383,6704 8385,6700 8388,6698 8391,6695 8394,6694 8398,6693 8402,6692 L 8402,6692 Z M 8174,6692 C 8178,6693 8182,6694 8185,6696 8188,6697 8191,6700 8193,6703 8193,6703 8193,6704 8193,6704 8195,6707 8197,6711 8197,6715 8197,6718 8196,6722 8195,6724 L 8195,6725 8143,6851 C 8106,6828 8076,6796 8055,6759 8047,6745 8040,6731 8035,6716 L 8169,6693 C 8171,6693 8172,6693 8174,6693 L 8174,6692 Z M 8287,6747 C 8288,6747 8288,6747 8288,6747 8292,6747 8296,6748 8300,6750 8303,6752 8306,6755 8308,6759 8308,6759 8308,6759 8308,6759 L 8309,6759 8375,6879 8349,6887 C 8329,6891 8309,6893 8289,6893 8259,6893 8230,6888 8202,6879 L 8268,6759 C 8268,6759 8268,6759 8268,6759 8270,6756 8273,6753 8277,6751 8280,6749 8284,6748 8288,6748 L 8287,6747 Z"/>
  <path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" d="M 8288,6154 L 8288,6154 C 8284,6155 8280,6156 8276,6158 8272,6160 8269,6164 8266,6168 8264,6172 8263,6177 8263,6182 8263,6182 8263,6183 8263,6183 L 8263,6191 C 8263,6199 8265,6207 8266,6215 8269,6231 8270,6247 8269,6263 L 8269,6263 C 8268,6265 8267,6267 8266,6269 8265,6272 8263,6274 8260,6276 L 8260,6287 8260,6287 C 8244,6288 8228,6291 8213,6294 L 8213,6294 C 8180,6302 8148,6314 8119,6331 8091,6347 8065,6368 8042,6392 L 8033,6385 C 8028,6386 8022,6386 8017,6383 L 8017,6383 C 8005,6374 7993,6363 7982,6352 7977,6345 7971,6339 7965,6334 L 7959,6329 7959,6329 C 7958,6328 7957,6327 7955,6326 7951,6324 7946,6322 7941,6322 L 7941,6322 C 7941,6322 7940,6322 7940,6322 7936,6322 7931,6323 7927,6325 7925,6327 7922,6329 7921,6331 L 7921,6331 C 7920,6332 7920,6333 7919,6334 7917,6338 7915,6342 7915,6347 7915,6352 7917,6357 7919,6361 7921,6364 7924,6367 7927,6370 L 7932,6374 C 7939,6378 7946,6382 7954,6386 7968,6394 7981,6403 7993,6414 L 7993,6414 C 7993,6414 7994,6415 7994,6416 7997,6420 7998,6424 7998,6429 L 8007,6436 8007,6436 C 8003,6442 7999,6448 7995,6455 7966,6506 7950,6564 7950,6623 7950,6639 7951,6655 7954,6671 L 7943,6674 7943,6674 C 7940,6679 7936,6683 7932,6686 7916,6690 7901,6692 7885,6693 7877,6694 7868,6694 7860,6695 L 7853,6697 7852,6697 7852,6697 C 7849,6697 7846,6698 7843,6700 7839,6702 7835,6706 7833,6710 7831,6714 7830,6718 7830,6723 7830,6727 7831,6732 7833,6736 7834,6738 7836,6740 7837,6741 L 7837,6741 C 7839,6743 7841,6744 7843,6745 7847,6748 7851,6749 7856,6749 7858,6749 7861,6748 7864,6748 L 7864,6748 7871,6747 7871,6747 C 7879,6744 7887,6742 7895,6738 7909,6732 7925,6728 7940,6725 L 7940,6725 C 7945,6725 7949,6726 7953,6729 7954,6729 7954,6730 7955,6730 L 7967,6728 C 7992,6807 8046,6874 8117,6916 L 8112,6926 C 8114,6930 8115,6935 8115,6940 8108,6955 8100,6970 8091,6983 L 8091,6983 C 8086,6990 8081,6997 8077,7004 L 8074,7011 8074,7011 C 8071,7015 8070,7020 8070,7024 8070,7029 8071,7033 8074,7037 8076,7041 8079,7045 8083,7047 8087,7049 8092,7050 8096,7050 8097,7050 8098,7050 8099,7050 L 8099,7050 C 8103,7050 8106,7049 8109,7047 8113,7045 8117,7041 8119,7037 8120,7036 8120,7035 8121,7033 L 8124,7027 C 8127,7019 8130,7011 8132,7003 8138,6986 8142,6968 8152,6956 L 8152,6956 C 8153,6955 8154,6954 8156,6954 8158,6952 8160,6951 8163,6951 L 8169,6940 8169,6940 C 8207,6955 8248,6963 8290,6963 8331,6963 8371,6955 8409,6941 L 8414,6951 8414,6951 C 8417,6951 8419,6952 8421,6953 8424,6955 8426,6956 8428,6959 L 8428,6959 C 8435,6973 8441,6987 8446,7002 8448,7011 8450,7019 8453,7026 L 8456,7033 8456,7033 C 8457,7034 8457,7036 8458,7037 8460,7041 8464,7044 8468,7046 8472,7049 8476,7050 8481,7050 8485,7050 8490,7049 8494,7046 8498,7044 8501,7042 8503,7038 L 8503,7038 C 8503,7038 8503,7037 8503,7037 8506,7033 8507,7028 8507,7024 8507,7019 8506,7015 8503,7011 8503,7011 8503,7011 8503,7011 L 8500,7004 C 8496,6996 8491,6990 8486,6983 L 8486,6983 C 8482,6977 8478,6971 8474,6964 8470,6957 8466,6949 8463,6942 L 8463,6942 C 8462,6940 8462,6938 8462,6937 8462,6933 8463,6930 8465,6927 8465,6926 8465,6926 8465,6926 8464,6923 8462,6919 8461,6915 L 8461,6915 C 8511,6886 8552,6844 8581,6794 8593,6773 8603,6750 8610,6727 L 8622,6729 8622,6729 C 8623,6728 8624,6727 8625,6726 8628,6725 8632,6724 8635,6724 8636,6724 8636,6724 8636,6724 L 8636,6724 C 8652,6727 8667,6731 8682,6737 8689,6741 8697,6743 8705,6746 8707,6746 8710,6747 8712,6747 L 8712,6747 8712,6747 C 8715,6748 8717,6749 8720,6749 8725,6749 8729,6747 8733,6745 8737,6743 8741,6739 8743,6735 8745,6731 8746,6727 8746,6722 8746,6720 8746,6717 8745,6714 L 8745,6714 C 8745,6712 8744,6711 8743,6709 8741,6705 8737,6702 8733,6700 8730,6698 8727,6697 8724,6697 L 8716,6695 C 8708,6694 8700,6693 8691,6693 L 8691,6693 C 8675,6692 8660,6689 8644,6685 L 8644,6685 C 8644,6685 8644,6685 8644,6685 8640,6682 8636,6679 8633,6674 8633,6674 8633,6674 8633,6674 L 8623,6671 8623,6671 C 8625,6656 8626,6640 8626,6625 8626,6565 8610,6507 8580,6456 8577,6449 8573,6443 8568,6436 L 8578,6428 C 8577,6423 8579,6417 8583,6413 L 8583,6413 C 8594,6403 8606,6394 8620,6387 8620,6387 8621,6386 8621,6386 L 8621,6386 C 8625,6384 8628,6382 8631,6381 8635,6378 8639,6376 8643,6373 L 8649,6369 8649,6369 C 8652,6366 8655,6363 8657,6360 8660,6356 8661,6351 8661,6346 8661,6342 8660,6337 8657,6333 8655,6329 8652,6326 8648,6324 8646,6323 8645,6322 8643,6322 L 8643,6322 C 8641,6321 8638,6320 8635,6320 8630,6320 8626,6321 8622,6324 8620,6325 8618,6326 8616,6328 L 8610,6333 8610,6333 C 8604,6338 8599,6344 8593,6351 8583,6363 8571,6373 8559,6383 L 8559,6383 C 8556,6384 8552,6385 8548,6385 8547,6385 8545,6385 8543,6385 L 8533,6392 C 8476,6332 8399,6295 8317,6287 8317,6283 8316,6278 8316,6276 L 8316,6276 C 8314,6274 8312,6272 8310,6269 8309,6267 8308,6265 8308,6263 8307,6247 8308,6231 8311,6216 8312,6208 8314,6199 8314,6191 L 8314,6183 8314,6183 C 8314,6183 8314,6182 8314,6182 8314,6177 8313,6172 8311,6168 8308,6164 8305,6160 8301,6158 8297,6156 8293,6154 8289,6154 L 8288,6154 Z"/>
  <path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" d="M 8256,6356 L 8248,6493 8247,6493 C 8247,6501 8242,6508 8234,6513 8227,6517 8218,6515 8211,6510 L 8099,6431 8099,6431 C 8115,6415 8133,6401 8153,6390 8176,6377 8202,6367 8228,6361 8237,6359 8246,6357 8256,6356 L 8256,6356 Z"/>
  <path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" d="M 8321,6356 C 8380,6364 8435,6390 8477,6431 L 8366,6510 8366,6510 C 8366,6511 8365,6512 8364,6512 8360,6514 8356,6515 8352,6515 8348,6515 8344,6514 8341,6512 8337,6510 8334,6507 8332,6504 8330,6500 8329,6497 8329,6493 L 8321,6356 Z"/>
  <path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" d="M 8058,6482 L 8161,6574 8161,6574 8161,6574 C 8162,6576 8164,6578 8165,6580 8167,6584 8168,6588 8168,6592 8168,6596 8167,6600 8165,6603 8163,6607 8160,6610 8157,6612 8155,6613 8153,6613 8152,6614 L 8152,6614 8020,6652 8020,6652 C 8019,6642 8018,6633 8018,6623 8018,6576 8031,6529 8054,6489 8056,6487 8057,6484 8058,6482 L 8058,6482 Z"/>
  <path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" d="M 8518,6482 L 8518,6482 C 8519,6485 8520,6487 8522,6489 8546,6530 8558,6577 8558,6625 8558,6634 8558,6643 8557,6652 L 8425,6614 8425,6613 8425,6613 C 8423,6613 8421,6612 8420,6611 8416,6609 8413,6606 8411,6603 8410,6601 8409,6598 8409,6596 L 8409,6596 C 8408,6594 8408,6593 8408,6591 8408,6587 8409,6583 8411,6579 8413,6577 8414,6575 8416,6574 L 8518,6483 8518,6482 Z"/>
  <path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" d="M 8267,6581 L 8309,6581 8335,6613 8325,6654 8288,6672 8250,6654 8241,6613 8267,6581 Z"/>
  <path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" d="M 8402,6692 C 8404,6692 8406,6692 8407,6692 L 8543,6715 C 8523,6771 8484,6819 8434,6851 L 8382,6724 8382,6724 C 8380,6722 8380,6719 8380,6715 8380,6711 8381,6707 8383,6704 8385,6700 8388,6698 8391,6695 8394,6694 8398,6693 8402,6692 L 8402,6692 Z"/>
  <path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" d="M 8174,6692 L 8174,6692 C 8178,6693 8182,6694 8185,6696 8188,6697 8191,6700 8193,6703 L 8193,6703 C 8193,6703 8193,6704 8193,6704 8195,6707 8197,6711 8197,6715 8197,6718 8196,6722 8195,6724 L 8195,6725 8143,6851 8143,6851 C 8106,6828 8076,6796 8055,6759 8047,6745 8040,6731 8035,6716 L 8169,6693 C 8171,6693 8172,6693 8174,6693 L 8174,6692 Z"/>
  <path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" d="M 8287,6747 L 8287,6747 C 8288,6747 8288,6747 8288,6747 8292,6747 8296,6748 8300,6750 8303,6752 8306,6755 8308,6759 8308,6759 8308,6759 8308,6759 L 8309,6759 8375,6879 8349,6887 8349,6887 C 8329,6891 8309,6893 8289,6893 8259,6893 8230,6888 8202,6879 L 8268,6759 8268,6759 C 8268,6759 8268,6759 8268,6759 8270,6756 8273,6753 8277,6751 8280,6749 8284,6748 8288,6748 L 8287,6747 Z"/>
 </g>
</g>
<g class="Group">
 <g class="com.sun.star.drawing.LineShape">
  <g id="id13">
   <rect class="BoundingBox" stroke="none" fill="none" x="14415" y="4912" width="319" height="2837"/>
   <path fill="none" stroke="rgb(190,24,24)" stroke-width="106" stroke-linejoin="miter" d="M 14574,4912 L 14574,7451"/>
   <path fill="rgb(190,24,24)" stroke="none" d="M 14733,7430 L 14574,7748 14415,7430 14733,7430 Z"/>
  </g>
 </g>
 <g class="com.sun.star.drawing.CustomShape">
  <g id="id14">
   <rect class="BoundingBox" stroke="none" fill="none" x="13777" y="5646" width="833" height="1361"/>
  </g>
 </g>
 <g class="com.sun.star.drawing.CustomShape">
  <g id="id15">
   <rect class="BoundingBox" stroke="none" fill="none" x="14453" y="5882" width="1007" height="892"/>
  </g>
 </g>
</g>
<g class="Group">
 <g class="com.sun.star.drawing.LineShape">
  <g id="id16">
   <rect class="BoundingBox" stroke="none" fill="none" x="19045" y="4912" width="319" height="2836"/>
   <path fill="none" stroke="rgb(190,24,24)" stroke-width="106" stroke-linejoin="miter" d="M 19204,5209 L 19204,7747"/>
   <path fill="rgb(190,24,24)" stroke="none" d="M 19045,5230 L 19204,4912 19363,5230 19045,5230 Z"/>
  </g>
 </g>
 <g class="com.sun.star.drawing.CustomShape">
  <g id="id17">
   <rect class="BoundingBox" stroke="none" fill="none" x="18410" y="5634" width="833" height="1390"/>
  </g>
 </g>
 <g class="com.sun.star.drawing.CustomShape">
  <g id="id18">
   <rect class="BoundingBox" stroke="none" fill="none" x="19084" y="5884" width="1008" height="891"/>
  </g>
 </g>
</g>
<g class="Group">
 <g class="com.sun.star.drawing.LineShape">
  <g id="id19">
   <rect class="BoundingBox" stroke="none" fill="none" x="10014" y="4912" width="320" height="2836"/>
   <path fill="none" stroke="rgb(190,24,24)" stroke-width="106" stroke-linejoin="miter" d="M 10173,5209 L 10173,7747"/>
   <path fill="rgb(190,24,24)" stroke="none" d="M 10014,5230 L 10173,4912 10332,5230 10014,5230 Z"/>
  </g>
 </g>
 <g class="com.sun.star.drawing.CustomShape">
  <g id="id20">
   <rect class="BoundingBox" stroke="none" fill="none" x="9376" y="5635" width="833" height="1390"/>
  </g>
 </g>
 <g class="com.sun.star.drawing.CustomShape">
  <g id="id21">
   <rect class="BoundingBox" stroke="none" fill="none" x="10052" y="5882" width="1007" height="892"/>
  </g>
 </g>
</g>
<g class="Group">
 <g class="com.sun.star.drawing.LineShape">
  <g id="id22">
   <rect class="BoundingBox" stroke="none" fill="none" x="11488" y="9696" width="1682" height="528"/>
   <path fill="none" stroke="rgb(190,24,24)" stroke-width="106" stroke-linejoin="miter" d="M 11778,10073 L 13156,9748"/>
   <path fill="rgb(190,24,24)" stroke="none" d="M 11835,10223 L 11489,10141 11762,9913 11835,10223 Z"/>
  </g>
 </g>
 <g class="com.sun.star.drawing.CustomShape">
  <g id="id23">
   <rect class="BoundingBox" stroke="none" fill="none" x="11572" y="8928" width="1525" height="1142"/>
   <text class="SVGTextShape" transform="rotate(-14 11855 9809)"><tspan class="TextParagraph" font-family="Arial, sans-serif" font-size="459px" font-weight="400"><tspan class="TextPosition" x="11855" y="9809"><tspan fill="rgb(9,34,87)" stroke="none" style="white-space: pre">Write</tspan></tspan></tspan></text>
  </g>
 </g>
 <g class="com.sun.star.drawing.CustomShape">
  <g id="id24">
   <rect class="BoundingBox" stroke="none" fill="none" x="13056" y="8205" width="833" height="738"/>
  </g>
 </g>
 <g class="com.sun.star.drawing.LineShape">
  <g id="id25">
   <rect class="BoundingBox" stroke="none" fill="none" x="16082" y="9712" width="1681" height="528"/>
   <path fill="none" stroke="rgb(190,24,24)" stroke-width="106" stroke-linejoin="miter" d="M 17473,10089 L 16095,9764"/>
   <path fill="rgb(190,24,24)" stroke="none" d="M 17489,9929 L 17762,10157 17416,10239 17489,9929 Z"/>
  </g>
 </g>
 <g class="com.sun.star.drawing.CustomShape">
  <g id="id26">
   <rect class="BoundingBox" stroke="none" fill="none" x="16172" y="8930" width="1522" height="1135"/>
   <text class="SVGTextShape" transform="rotate(13.7 16365 9548)"><tspan class="TextParagraph" font-family="Arial, sans-serif" font-size="459px" font-weight="400"><tspan class="TextPosition" x="16365" y="9548"><tspan fill="rgb(9,34,87)" stroke="none" style="white-space: pre">Write</tspan></tspan></tspan></text>
  </g>
 </g>
</g>
<g class="com.sun.star.drawing.CustomShape">
 <g id="id27">
  <rect class="BoundingBox" stroke="none" fill="none" x="8894" y="1904" width="11432" height="2855"/>
  <path fill="rgb(50,108,229)" stroke="none" d="M 9113,1904 L 9113,1904 C 9075,1904 9037,1914 9004,1933 8970,1953 8943,1980 8923,2014 8904,2047 8894,2085 8894,2123 L 8894,4538 8894,4539 C 8894,4577 8904,4615 8923,4648 8943,4682 8970,4709 9004,4729 9037,4748 9075,4758 9113,4758 L 20105,4758 20106,4758 C 20144,4758 20182,4748 20215,4729 20249,4709 20276,4682 20296,4648 20315,4615 20325,4577 20325,4539 L 20324,2123 20325,2123 20325,2123 C 20325,2085 20315,2047 20296,2014 20276,1980 20249,1953 20215,1933 20182,1914 20144,1904 20106,1904 L 9113,1904 Z"/>
  <text class="SVGTextShape"><tspan class="TextParagraph" font-family="Arial, sans-serif" font-size="706px" font-weight="700"><tspan class="TextPosition" x="11618" y="2989"><tspan fill="rgb(255,255,255)" stroke="none" style="white-space: pre">Client Application</tspan></tspan></tspan><tspan class="TextParagraph" font-family="Arial, sans-serif" font-size="423px" font-weight="700"><tspan class="TextPosition" x="12962" y="4222"><tspan fill="rgb(255,255,255)" stroke="none" style="white-space: pre">MongoDB driver</tspan></tspan></tspan></text>
 </g>
</g>
<g class="com.sun.star.drawing.LineShape">
 <g id="id28">
  <rect class="BoundingBox" stroke="none" fill="none" x="7284" y="12593" width="14608" height="3"/>
  <path fill="none" stroke="rgb(255,255,255)" d="M 7285,12594 L 21890,12594"/>
 </g>
</g>
<g class="com.sun.star.drawing.LineShape">
 <g id="id29">
  <rect class="BoundingBox" stroke="none" fill="none" x="22272" y="6684" width="3" height="5521"/>
  <path fill="none" stroke="rgb(255,255,255)" d="M 22273,6685 L 22273,12203"/>
 </g>
</g>
<g class="Group">
 <g class="com.sun.star.drawing.LineShape">
  <g id="id30">
   <rect class="BoundingBox" stroke="none" fill="none" x="14415" y="5012" width="319" height="2837"/>
   <path fill="none" stroke="rgb(190,24,24)" stroke-width="106" stroke-linejoin="miter" d="M 14574,5012 L 14574,7551"/>
   <path fill="rgb(190,24,24)" stroke="none" d="M 14733,7530 L 14574,7848 14415,7530 14733,7530 Z"/>
  </g>
 </g>
 <g class="com.sun.star.drawing.CustomShape">
  <g id="id31">
   <rect class="BoundingBox" stroke="none" fill="none" x="13772" y="5746" width="834" height="1362"/>
   <text class="SVGTextShape" transform="rotate(-90 14373 6967)"><tspan class="TextParagraph" font-family="Arial, sans-serif" font-size="459px" font-weight="400"><tspan class="TextPosition" x="14373" y="6967"><tspan fill="rgb(9,34,87)" stroke="none" style="white-space: pre">Write</tspan></tspan></tspan></text>
  </g>
 </g>
 <g class="com.sun.star.drawing.CustomShape">
  <g id="id32">
   <rect class="BoundingBox" stroke="none" fill="none" x="14453" y="5982" width="1007" height="892"/>
  </g>
 </g>
</g>
<g class="Group">
 <g class="com.sun.star.drawing.LineShape">
  <g id="id33">
   <rect class="BoundingBox" stroke="none" fill="none" x="19045" y="4925" width="319" height="2836"/>
   <path fill="none" stroke="rgb(190,24,24)" stroke-width="106" stroke-linejoin="miter" d="M 19204,5222 L 19204,7760"/>
   <path fill="rgb(190,24,24)" stroke="none" d="M 19045,5243 L 19204,4925 19363,5243 19045,5243 Z"/>
  </g>
 </g>
 <g class="com.sun.star.drawing.CustomShape">
  <g id="id34">
   <rect class="BoundingBox" stroke="none" fill="none" x="18405" y="5647" width="834" height="1391"/>
   <text class="SVGTextShape" transform="rotate(-90 19007 6897)"><tspan class="TextParagraph" font-family="Arial, sans-serif" font-size="459px" font-weight="400"><tspan class="TextPosition" x="19007" y="6897"><tspan fill="rgb(9,34,87)" stroke="none" style="white-space: pre">Read</tspan></tspan></tspan></text>
  </g>
 </g>
 <g class="com.sun.star.drawing.CustomShape">
  <g id="id35">
   <rect class="BoundingBox" stroke="none" fill="none" x="19084" y="5897" width="1008" height="891"/>
  </g>
 </g>
</g>
<g class="com.sun.star.drawing.CustomShape">
 <g id="id36">
  <rect class="BoundingBox" stroke="none" fill="none" x="9371" y="5648" width="834" height="1391"/>
  <text class="SVGTextShape" transform="rotate(-90 9973 6898)"><tspan class="TextParagraph" font-family="Arial, sans-serif" font-size="459px" font-weight="400"><tspan class="TextPosition" x="9973" y="6898"><tspan fill="rgb(9,34,87)" stroke="none" style="white-space: pre">Read</tspan></tspan></tspan></text>
 </g>
</g>
</g>
</svg></li>
<li>sharding 구성<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.2" viewBox="3175 1269 22861 13336" preserveAspectRatio="xMidYMid" fill-rule="evenodd" stroke-width="28.222" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" xmlns:ooo="http://xml.openoffice.org/svg/export" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:presentation="http://sun.com/xmlns/staroffice/presentation" xmlns:smil="http://www.w3.org/2001/SMIL20/" xmlns:anim="urn:oasis:names:tc:opendocument:xmlns:animation:1.0" xml:space="preserve">
<defs>
<font id="EmbeddedFont_1" horiz-adv-x="2048">
 <font-face font-family="Arial embedded" units-per-em="2048" font-weight="normal" font-style="normal" ascent="1847" descent="423"/>
 <missing-glyph horiz-adv-x="2048" d="M 0,0 L 2047,0 2047,2047 0,2047 0,0 Z"/>
 <glyph unicode="t" horiz-adv-x="540" d="M 528,161 L 554,2 C 503,-9 458,-14 418,-14 353,-14 302,-4 266,17 230,38 205,65 190,99 175,132 168,203 168,311 L 168,922 36,922 36,1062 168,1062 168,1325 347,1433 347,1062 528,1062 528,922 347,922 347,301 C 347,250 350,217 357,202 363,187 373,176 388,167 402,158 422,154 449,154 469,154 495,156 528,161 Z"/>
 <glyph unicode="r" horiz-adv-x="598" d="M 133,0 L 133,1062 295,1062 295,901 C 336,976 375,1026 410,1050 445,1074 483,1086 525,1086 586,1086 647,1067 710,1028 L 648,861 C 604,887 560,900 516,900 477,900 441,888 410,865 379,841 356,808 343,766 323,702 313,632 313,556 L 313,0 Z"/>
 <glyph unicode="i" horiz-adv-x="193" d="M 136,1259 L 136,1466 316,1466 316,1259 Z M 136,0 L 136,1062 316,1062 316,0 Z"/>
 <glyph unicode="e" horiz-adv-x="1001" d="M 862,342 L 1048,319 C 1019,210 964,126 885,66 806,6 704,-24 581,-24 426,-24 303,24 212,120 121,215 75,349 75,522 75,701 121,839 213,938 305,1037 424,1086 571,1086 713,1086 829,1038 919,941 1009,844 1054,708 1054,533 1054,522 1054,506 1053,485 L 261,485 C 268,368 301,279 360,217 419,155 493,124 582,124 648,124 704,141 751,176 798,211 835,266 862,342 Z M 271,633 L 864,633 C 856,722 833,789 796,834 739,903 664,938 573,938 490,938 421,910 365,855 308,800 277,726 271,633 Z"/>
 <glyph unicode="d" horiz-adv-x="944" d="M 824,0 L 824,134 C 757,29 658,-24 527,-24 442,-24 365,-1 294,46 223,93 168,158 129,242 90,325 70,421 70,530 70,636 88,732 123,819 158,905 211,971 282,1017 353,1063 432,1086 519,1086 583,1086 640,1073 690,1046 740,1019 781,983 812,940 L 812,1466 991,1466 991,0 Z M 255,530 C 255,394 284,292 341,225 398,158 466,124 544,124 623,124 690,156 745,221 800,285 827,383 827,515 827,660 799,767 743,835 687,903 618,937 536,937 456,937 389,904 336,839 282,774 255,671 255,530 Z"/>
 <glyph unicode="a" horiz-adv-x="1001" d="M 828,131 C 761,74 697,34 636,11 574,-12 508,-24 437,-24 320,-24 231,5 168,62 105,119 74,191 74,280 74,332 86,380 110,423 133,466 164,500 203,526 241,552 284,572 332,585 367,594 421,603 492,612 637,629 744,650 813,674 814,699 814,714 814,721 814,794 797,846 763,876 717,917 649,937 558,937 473,937 411,922 371,893 330,863 300,810 281,735 L 105,759 C 121,834 147,895 184,942 221,988 274,1024 343,1049 412,1074 493,1086 584,1086 675,1086 748,1075 805,1054 862,1033 903,1006 930,974 957,941 975,900 986,851 992,820 995,765 995,685 L 995,445 C 995,278 999,172 1007,128 1014,83 1029,41 1052,0 L 864,0 C 845,37 833,81 828,131 Z M 813,533 C 748,506 650,484 519,465 445,454 393,442 362,429 331,416 308,396 291,371 274,345 266,316 266,285 266,237 284,197 321,165 357,133 410,117 480,117 549,117 611,132 665,163 719,193 759,234 784,287 803,328 813,388 813,467 Z"/>
 <glyph unicode="W" horiz-adv-x="1906" d="M 414,0 L 25,1466 224,1466 447,505 C 471,404 492,304 509,205 546,362 568,452 575,476 L 854,1466 1088,1466 1298,724 C 1351,540 1389,367 1412,205 1431,298 1455,404 1485,524 L 1715,1466 1910,1466 1508,0 1321,0 1012,1117 C 986,1210 971,1268 966,1289 951,1222 936,1164 923,1117 L 612,0 Z"/>
 <glyph unicode="R" horiz-adv-x="1309" d="M 161,0 L 161,1466 811,1466 C 942,1466 1041,1453 1109,1427 1177,1400 1231,1354 1272,1287 1313,1220 1333,1147 1333,1066 1333,962 1299,874 1232,803 1165,732 1061,686 920,667 971,642 1010,618 1037,594 1094,542 1147,477 1198,399 L 1453,0 1209,0 1015,305 C 958,393 912,460 875,507 838,554 806,586 777,605 748,624 718,637 688,644 666,649 630,651 580,651 L 355,651 355,0 Z M 355,819 L 772,819 C 861,819 930,828 980,847 1030,865 1068,894 1094,935 1120,975 1133,1019 1133,1066 1133,1135 1108,1192 1058,1237 1007,1282 928,1304 819,1304 L 355,1304 Z"/>
</font>
</defs>
<defs>
<font id="EmbeddedFont_2" horiz-adv-x="2048">
 <font-face font-family="Arial embedded" units-per-em="2048" font-weight="bold" font-style="normal" ascent="1847" descent="423"/>
 <missing-glyph horiz-adv-x="2048" d="M 0,0 L 2047,0 2047,2047 0,2047 0,0 Z"/>
 <glyph unicode="y" horiz-adv-x="1117" d="M 14,1062 L 313,1062 567,308 815,1062 1106,1062 731,40 664,-145 C 639,-207 616,-254 594,-287 571,-320 546,-346 517,-367 488,-387 452,-403 410,-414 367,-425 319,-431 266,-431 212,-431 159,-425 107,-414 L 82,-194 C 126,-203 166,-207 201,-207 266,-207 315,-188 346,-150 377,-111 401,-62 418,-3 Z"/>
 <glyph unicode="x" horiz-adv-x="1136" d="M 12,0 L 395,547 28,1062 371,1062 559,770 757,1062 1087,1062 727,559 1120,0 775,0 559,329 341,0 Z"/>
 <glyph unicode="u" horiz-adv-x="982" d="M 846,0 L 846,159 C 807,102 757,58 694,25 631,-8 564,-24 494,-24 423,-24 359,-8 302,23 245,54 204,98 179,155 154,212 141,290 141,390 L 141,1062 422,1062 422,574 C 422,425 427,333 438,300 448,266 467,239 494,220 521,200 556,190 598,190 646,190 689,203 727,230 765,256 791,289 805,328 819,367 826,462 826,614 L 826,1062 1107,1062 1107,0 Z"/>
 <glyph unicode="t" horiz-adv-x="655" d="M 634,1062 L 634,838 442,838 442,410 C 442,323 444,273 448,259 451,244 460,232 473,223 486,214 501,209 520,209 546,209 584,218 633,236 L 657,18 C 592,-10 518,-24 435,-24 384,-24 339,-16 298,2 257,19 228,41 209,68 190,95 176,131 169,177 163,210 160,276 160,375 L 160,838 31,838 31,1062 160,1062 160,1273 442,1437 442,1062 Z"/>
 <glyph unicode="s" horiz-adv-x="1021" d="M 48,303 L 330,346 C 342,291 366,250 403,222 440,193 491,179 557,179 630,179 684,192 721,219 746,238 758,263 758,294 758,315 751,333 738,347 724,360 693,373 644,384 417,434 274,480 213,521 129,578 87,658 87,760 87,852 123,929 196,992 269,1055 381,1086 534,1086 679,1086 787,1062 858,1015 929,968 977,898 1004,805 L 739,756 C 728,797 706,829 675,851 643,873 598,884 539,884 465,884 412,874 380,853 359,838 348,819 348,796 348,776 357,759 376,745 401,726 489,700 639,666 788,632 893,590 952,541 1011,491 1040,421 1040,332 1040,235 999,151 918,81 837,11 716,-24 557,-24 412,-24 298,5 214,64 129,123 74,202 48,303 Z"/>
 <glyph unicode="r" horiz-adv-x="693" d="M 416,0 L 135,0 135,1062 396,1062 396,911 C 441,982 481,1029 517,1052 552,1075 593,1086 638,1086 702,1086 764,1068 823,1033 L 736,788 C 689,819 645,834 604,834 565,834 531,823 504,802 477,780 455,741 440,684 424,627 416,509 416,328 Z"/>
 <glyph unicode="p" horiz-adv-x="1059" d="M 139,1062 L 401,1062 401,906 C 435,959 481,1003 539,1036 597,1069 661,1086 732,1086 855,1086 960,1038 1046,941 1132,844 1175,710 1175,537 1175,360 1132,222 1045,124 958,25 853,-24 730,-24 671,-24 618,-12 571,11 523,34 473,74 420,131 L 420,-404 139,-404 Z M 417,549 C 417,430 441,342 488,285 535,228 593,199 661,199 726,199 781,225 824,278 867,330 889,416 889,535 889,646 867,729 822,783 777,837 722,864 656,864 587,864 530,838 485,785 440,732 417,653 417,549 Z"/>
 <glyph unicode="o" horiz-adv-x="1117" d="M 82,546 C 82,639 105,730 151,817 197,904 262,971 347,1017 431,1063 525,1086 629,1086 790,1086 921,1034 1024,930 1127,825 1178,693 1178,534 1178,373 1126,240 1023,135 919,29 788,-24 631,-24 534,-24 441,-2 353,42 264,86 197,151 151,236 105,321 82,424 82,546 Z M 370,531 C 370,426 395,345 445,289 495,233 557,205 630,205 703,205 765,233 815,289 864,345 889,426 889,533 889,637 864,717 815,773 765,829 703,857 630,857 557,857 495,829 445,773 395,717 370,636 370,531 Z"/>
 <glyph unicode="n" horiz-adv-x="982" d="M 1113,0 L 832,0 832,542 C 832,657 826,731 814,765 802,798 783,824 756,843 729,862 696,871 658,871 609,871 566,858 527,831 488,804 462,769 448,725 433,681 426,600 426,481 L 426,0 145,0 145,1062 406,1062 406,906 C 499,1026 615,1086 756,1086 818,1086 875,1075 926,1053 977,1030 1016,1002 1043,967 1069,932 1087,893 1098,849 1108,805 1113,742 1113,660 Z"/>
 <glyph unicode="m" horiz-adv-x="1579" d="M 126,1062 L 385,1062 385,917 C 478,1030 588,1086 716,1086 784,1086 843,1072 893,1044 943,1016 984,974 1016,917 1063,974 1113,1016 1167,1044 1221,1072 1279,1086 1340,1086 1418,1086 1484,1070 1538,1039 1592,1007 1632,960 1659,899 1678,854 1688,780 1688,679 L 1688,0 1407,0 1407,607 C 1407,712 1397,780 1378,811 1352,851 1312,871 1258,871 1219,871 1182,859 1147,835 1112,811 1087,776 1072,730 1057,683 1049,610 1049,510 L 1049,0 768,0 768,582 C 768,685 763,752 753,782 743,812 728,834 707,849 686,864 657,871 621,871 578,871 539,859 504,836 469,813 445,779 430,735 415,691 407,618 407,516 L 407,0 126,0 Z"/>
 <glyph unicode="l" horiz-adv-x="309" d="M 147,0 L 147,1466 428,1466 428,0 Z"/>
 <glyph unicode="i" horiz-adv-x="309" d="M 147,1206 L 147,1466 428,1466 428,1206 Z M 147,0 L 147,1062 428,1062 428,0 Z"/>
 <glyph unicode="g" horiz-adv-x="1059" d="M 121,-70 L 442,-109 C 447,-146 460,-172 479,-186 506,-206 548,-216 605,-216 678,-216 733,-205 770,-183 795,-168 813,-145 826,-112 835,-89 839,-46 839,17 L 839,172 C 755,57 649,0 521,0 378,0 265,60 182,181 117,276 84,395 84,537 84,715 127,851 213,945 298,1039 405,1086 532,1086 663,1086 772,1028 857,913 L 857,1062 1120,1062 1120,109 C 1120,-16 1110,-110 1089,-172 1068,-234 1039,-283 1002,-318 965,-353 915,-381 853,-401 790,-421 711,-431 616,-431 436,-431 308,-400 233,-339 158,-277 120,-199 120,-104 120,-95 120,-83 121,-70 Z M 372,553 C 372,440 394,358 438,306 481,253 535,227 599,227 668,227 726,254 773,308 820,361 844,441 844,546 844,656 821,738 776,791 731,844 673,871 604,871 537,871 481,845 438,793 394,740 372,660 372,553 Z"/>
 <glyph unicode="e" horiz-adv-x="1021" d="M 762,338 L 1042,291 C 1006,188 949,110 872,57 794,3 697,-24 580,-24 395,-24 259,36 170,157 100,254 65,376 65,523 65,699 111,837 203,937 295,1036 411,1086 552,1086 710,1086 835,1034 926,930 1017,825 1061,665 1057,450 L 353,450 C 355,367 378,302 421,256 464,209 518,186 583,186 627,186 664,198 694,222 724,246 747,285 762,338 Z M 778,622 C 776,703 755,765 715,808 675,850 626,871 569,871 508,871 457,849 417,804 377,759 357,699 358,622 Z"/>
 <glyph unicode="d" horiz-adv-x="1059" d="M 1121,0 L 860,0 860,156 C 817,95 766,50 707,21 648,-9 588,-24 528,-24 406,-24 302,25 215,124 128,222 84,359 84,535 84,715 126,852 211,946 296,1039 403,1086 532,1086 651,1086 753,1037 840,938 L 840,1466 1121,1466 Z M 371,554 C 371,441 387,359 418,308 463,235 527,198 608,198 673,198 728,226 773,281 818,336 841,418 841,527 841,649 819,737 775,791 731,844 675,871 606,871 539,871 484,845 439,792 394,739 371,659 371,554 Z"/>
 <glyph unicode="c" horiz-adv-x="1021" d="M 1073,748 L 796,698 C 787,753 766,795 733,823 700,851 657,865 604,865 534,865 478,841 437,793 395,744 374,663 374,550 374,424 395,335 438,283 480,231 537,205 608,205 661,205 705,220 739,251 773,281 797,333 811,407 L 1087,360 C 1058,233 1003,138 922,73 841,8 732,-24 595,-24 440,-24 316,25 224,123 131,221 85,357 85,530 85,705 131,842 224,940 317,1037 442,1086 600,1086 729,1086 832,1058 909,1003 985,947 1040,862 1073,748 Z"/>
 <glyph unicode="a" horiz-adv-x="1021" d="M 357,738 L 102,784 C 131,887 180,963 250,1012 320,1061 424,1086 562,1086 687,1086 781,1071 842,1042 903,1012 947,974 972,929 997,883 1009,799 1009,677 L 1006,349 C 1006,256 1011,187 1020,143 1029,98 1045,51 1070,0 L 792,0 C 785,19 776,46 765,83 760,100 757,111 755,116 707,69 656,34 601,11 546,-12 488,-24 426,-24 317,-24 231,6 168,65 105,124 73,199 73,290 73,350 87,404 116,451 145,498 185,534 237,559 288,584 363,605 460,624 591,649 682,672 733,693 L 733,721 C 733,775 720,814 693,837 666,860 616,871 542,871 492,871 453,861 425,842 397,822 374,787 357,738 Z M 733,510 C 697,498 640,484 562,467 484,450 433,434 409,418 372,392 354,359 354,319 354,280 369,246 398,217 427,188 465,174 510,174 561,174 609,191 655,224 689,249 711,280 722,317 729,341 733,387 733,454 Z"/>
 <glyph unicode="R" horiz-adv-x="1348" d="M 150,0 L 150,1466 773,1466 C 930,1466 1044,1453 1115,1427 1186,1400 1242,1353 1285,1286 1328,1219 1349,1142 1349,1055 1349,945 1317,854 1252,783 1187,711 1091,666 962,647 1026,610 1079,569 1121,524 1162,479 1218,400 1289,286 L 1468,0 1114,0 900,319 C 824,433 772,505 744,535 716,564 686,585 655,596 624,607 574,612 506,612 L 446,612 446,0 Z M 446,846 L 665,846 C 807,846 896,852 931,864 966,876 994,897 1014,926 1034,955 1044,992 1044,1036 1044,1085 1031,1125 1005,1156 978,1186 941,1205 893,1213 869,1216 797,1218 677,1218 L 446,1218 Z"/>
 <glyph unicode="P" horiz-adv-x="1155" d="M 149,0 L 149,1466 624,1466 C 804,1466 921,1459 976,1444 1060,1422 1130,1374 1187,1301 1244,1227 1272,1132 1272,1015 1272,925 1256,849 1223,788 1190,727 1149,679 1099,644 1048,609 997,585 945,574 874,560 772,553 638,553 L 445,553 445,0 Z M 445,1218 L 445,802 607,802 C 724,802 802,810 841,825 880,840 911,864 934,897 956,930 967,968 967,1011 967,1064 951,1108 920,1143 889,1178 849,1199 801,1208 766,1215 695,1218 588,1218 Z"/>
 <glyph unicode="D" horiz-adv-x="1251" d="M 148,1466 L 689,1466 C 811,1466 904,1457 968,1438 1054,1413 1128,1368 1189,1303 1250,1238 1297,1159 1329,1066 1361,972 1377,856 1377,719 1377,598 1362,494 1332,407 1295,300 1243,214 1175,148 1124,98 1054,59 967,31 902,10 814,0 705,0 L 148,0 Z M 444,1218 L 444,247 665,247 C 748,247 807,252 844,261 892,273 932,293 964,322 995,351 1021,398 1041,464 1061,529 1071,619 1071,732 1071,845 1061,932 1041,993 1021,1054 993,1101 957,1135 921,1169 875,1192 820,1204 779,1213 698,1218 577,1218 Z"/>
 <glyph unicode="C" horiz-adv-x="1290" d="M 1087,539 L 1374,448 C 1330,288 1257,169 1155,92 1052,14 922,-25 765,-25 570,-25 410,42 285,175 160,308 97,489 97,720 97,964 160,1154 286,1289 412,1424 578,1491 783,1491 962,1491 1108,1438 1220,1332 1287,1269 1337,1179 1370,1062 L 1077,992 C 1060,1068 1024,1128 969,1172 914,1216 847,1238 768,1238 659,1238 571,1199 504,1121 436,1043 402,917 402,742 402,557 435,425 502,346 569,267 655,228 762,228 841,228 908,253 965,303 1022,353 1062,432 1087,539 Z"/>
 <glyph unicode="B" horiz-adv-x="1251" d="M 150,1466 L 736,1466 C 852,1466 939,1461 996,1452 1053,1442 1104,1422 1149,1391 1194,1360 1231,1320 1261,1269 1291,1218 1306,1160 1306,1097 1306,1028 1288,965 1251,908 1214,851 1163,808 1100,779 1189,753 1258,709 1306,646 1354,583 1378,510 1378,425 1378,358 1363,294 1332,231 1301,168 1258,117 1205,80 1151,42 1085,19 1006,10 957,5 838,1 649,0 L 150,0 Z M 446,1222 L 446,883 640,883 C 755,883 827,885 855,888 906,894 946,912 975,941 1004,970 1018,1008 1018,1055 1018,1100 1006,1137 981,1166 956,1194 918,1211 869,1217 840,1220 755,1222 616,1222 Z M 446,639 L 446,247 720,247 C 827,247 894,250 923,256 967,264 1003,284 1031,315 1058,346 1072,387 1072,439 1072,483 1061,520 1040,551 1019,582 988,604 948,618 907,632 820,639 685,639 Z"/>
 <glyph unicode="A" horiz-adv-x="1483" d="M 1471,0 L 1149,0 1021,333 435,333 314,0 0,0 571,1466 884,1466 Z M 926,580 L 724,1124 526,580 Z"/>
 <glyph unicode="3" horiz-adv-x="982" d="M 77,389 L 349,422 C 358,353 381,300 419,263 457,226 503,208 557,208 615,208 664,230 704,274 743,318 763,377 763,452 763,523 744,579 706,620 668,661 622,682 567,682 531,682 488,675 438,661 L 469,890 C 545,888 603,905 643,940 683,975 703,1021 703,1079 703,1128 688,1168 659,1197 630,1226 591,1241 542,1241 494,1241 453,1224 419,1191 385,1158 364,1109 357,1045 L 98,1089 C 116,1178 143,1249 180,1302 216,1355 267,1396 332,1427 397,1457 469,1472 550,1472 688,1472 799,1428 882,1340 951,1268 985,1187 985,1096 985,967 915,865 774,788 858,770 925,730 976,667 1026,604 1051,529 1051,440 1051,311 1004,202 910,111 816,20 699,-25 559,-25 426,-25 316,13 229,90 142,166 91,266 77,389 Z"/>
 <glyph unicode="2" horiz-adv-x="1002" d="M 1036,261 L 1036,0 51,0 C 62,99 94,192 147,281 200,369 306,486 463,632 590,750 667,830 696,872 735,930 754,987 754,1044 754,1107 737,1155 704,1189 670,1222 623,1239 564,1239 505,1239 459,1221 424,1186 389,1151 369,1092 364,1010 L 84,1038 C 101,1193 153,1304 241,1371 329,1438 439,1472 571,1472 716,1472 829,1433 912,1355 995,1277 1036,1180 1036,1064 1036,998 1024,935 1001,876 977,816 939,753 888,688 854,645 793,582 704,501 615,420 559,366 536,339 512,312 493,286 478,261 Z"/>
 <glyph unicode="1" horiz-adv-x="655" d="M 806,0 L 525,0 525,1059 C 422,963 301,892 162,846 L 162,1101 C 235,1125 315,1171 401,1238 487,1305 546,1383 578,1472 L 806,1472 Z"/>
 <glyph unicode="/" horiz-adv-x="597" d="M -3,-25 L 360,1491 571,1491 204,-25 Z"/>
 <glyph unicode=")" horiz-adv-x="520" d="M 69,-431 C 124,-312 163,-221 186,-158 209,-95 230,-22 249,61 268,144 283,222 292,297 301,371 306,447 306,525 306,684 289,834 255,976 221,1117 158,1289 67,1491 L 258,1491 C 359,1348 437,1196 493,1035 548,874 576,711 576,546 576,407 554,257 510,98 460,-81 378,-257 263,-431 Z"/>
 <glyph unicode="(" horiz-adv-x="540" d="M 613,-431 L 420,-431 C 318,-277 240,-117 187,49 134,215 107,376 107,531 107,724 140,906 206,1078 263,1227 336,1365 424,1491 L 616,1491 C 525,1289 462,1117 428,976 393,834 376,684 376,525 376,416 386,304 407,189 427,74 455,-35 490,-138 513,-206 554,-304 613,-431 Z"/>
 <glyph unicode=" " horiz-adv-x="558"/>
</font>
</defs>
<defs class="EmbeddedBulletChars">
<g id="bullet-char-template-57356" transform="scale(0.00048828125,-0.00048828125)">
 <path d="M 580,1141 L 1163,571 580,0 -4,571 580,1141 Z"/>
</g>
<g id="bullet-char-template-57354" transform="scale(0.00048828125,-0.00048828125)">
 <path d="M 8,1128 L 1137,1128 1137,0 8,0 8,1128 Z"/>
</g>
<g id="bullet-char-template-10146" transform="scale(0.00048828125,-0.00048828125)">
 <path d="M 174,0 L 602,739 174,1481 1456,739 174,0 Z M 1358,739 L 309,1346 659,739 1358,739 Z"/>
</g>
<g id="bullet-char-template-10132" transform="scale(0.00048828125,-0.00048828125)">
 <path d="M 2015,739 L 1276,0 717,0 1260,543 174,543 174,936 1260,936 717,1481 1274,1481 2015,739 Z"/>
</g>
<g id="bullet-char-template-10007" transform="scale(0.00048828125,-0.00048828125)">
 <path d="M 0,-2 C -7,14 -16,27 -25,37 L 356,567 C 262,823 215,952 215,954 215,979 228,992 255,992 264,992 276,990 289,987 310,991 331,999 354,1012 L 381,999 492,748 772,1049 836,1024 860,1049 C 881,1039 901,1025 922,1006 886,937 835,863 770,784 769,783 710,716 594,584 L 774,223 C 774,196 753,168 711,139 L 727,119 C 717,90 699,76 672,76 641,76 570,178 457,381 L 164,-76 C 142,-110 111,-127 72,-127 30,-127 9,-110 8,-76 1,-67 -2,-52 -2,-32 -2,-23 -1,-13 0,-2 Z"/>
</g>
<g id="bullet-char-template-10004" transform="scale(0.00048828125,-0.00048828125)">
 <path d="M 285,-33 C 182,-33 111,30 74,156 52,228 41,333 41,471 41,549 55,616 82,672 116,743 169,778 240,778 293,778 328,747 346,684 L 369,508 C 377,444 397,411 428,410 L 1163,1116 C 1174,1127 1196,1133 1229,1133 1271,1133 1292,1118 1292,1087 L 1292,965 C 1292,929 1282,901 1262,881 L 442,47 C 390,-6 338,-33 285,-33 Z"/>
</g>
<g id="bullet-char-template-9679" transform="scale(0.00048828125,-0.00048828125)">
 <path d="M 813,0 C 632,0 489,54 383,161 276,268 223,411 223,592 223,773 276,916 383,1023 489,1130 632,1184 813,1184 992,1184 1136,1130 1245,1023 1353,916 1407,772 1407,592 1407,412 1353,268 1245,161 1136,54 992,0 813,0 Z"/>
</g>
<g id="bullet-char-template-8226" transform="scale(0.00048828125,-0.00048828125)">
 <path d="M 346,457 C 273,457 209,483 155,535 101,586 74,649 74,723 74,796 101,859 155,911 209,963 273,989 346,989 419,989 480,963 531,910 582,859 608,796 608,723 608,648 583,586 532,535 482,483 420,457 346,457 Z"/>
</g>
<g id="bullet-char-template-8211" transform="scale(0.00048828125,-0.00048828125)">
 <path d="M -4,459 L 1135,459 1135,606 -4,606 -4,459 Z"/>
</g>
<g id="bullet-char-template-61548" transform="scale(0.00048828125,-0.00048828125)">
 <path d="M 173,740 C 173,903 231,1043 346,1159 462,1274 601,1332 765,1332 928,1332 1067,1274 1183,1159 1299,1043 1357,903 1357,740 1357,577 1299,437 1183,322 1067,206 928,148 765,148 601,148 462,206 346,322 231,437 173,577 173,740 Z"/>
</g>
</defs>
<g ooo:name="page1" class="Page">
<g class="com.sun.star.drawing.CustomShape">
 <g id="id3">
  <rect class="BoundingBox" stroke="none" fill="none" x="3175" y="1269" width="22861" height="13336"/>
  <path fill="rgb(255,255,255)" stroke="none" d="M 14605,14604 L 3175,14604 3175,1269 26035,1269 26035,14604 14605,14604 Z"/>
 </g>
</g>
<g class="com.sun.star.drawing.CustomShape">
 <g id="id4">
  <rect class="BoundingBox" stroke="none" fill="none" x="6684" y="1605" width="15876" height="12701"/>
  <path fill="rgb(255,255,255)" stroke="none" d="M 14622,14305 L 6684,14305 6684,1605 22559,1605 22559,14305 14622,14305 Z"/>
 </g>
</g>
<g class="com.sun.star.drawing.CustomShape">
 <g id="id5">
  <rect class="BoundingBox" stroke="none" fill="none" x="6958" y="4417" width="15296" height="9581"/>
  <path fill="rgb(212,237,251)" stroke="none" d="M 7464,4444 L 7465,4444 C 7381,4444 7298,4466 7225,4508 7152,4550 7091,4611 7049,4684 7007,4757 6985,4840 6985,4924 L 6985,13490 6985,13490 C 6985,13574 7007,13657 7049,13730 7091,13803 7152,13864 7225,13906 7298,13948 7381,13970 7465,13970 L 21746,13970 21746,13970 C 21830,13970 21913,13948 21986,13906 22059,13864 22120,13803 22162,13730 22204,13657 22226,13574 22226,13490 L 22226,4923 22226,4924 22226,4924 C 22226,4840 22204,4757 22162,4684 22120,4611 22059,4550 21986,4508 21913,4466 21830,4444 21746,4444 L 7464,4444 Z"/>
  <path fill="none" stroke="rgb(114,159,207)" stroke-width="53" stroke-linejoin="round" d="M 7464,4444 L 7465,4444 C 7381,4444 7298,4466 7225,4508 7152,4550 7091,4611 7049,4684 7007,4757 6985,4840 6985,4924 L 6985,13490 6985,13490 C 6985,13574 7007,13657 7049,13730 7091,13803 7152,13864 7225,13906 7298,13948 7381,13970 7465,13970 L 21746,13970 21746,13970 C 21830,13970 21913,13948 21986,13906 22059,13864 22120,13803 22162,13730 22204,13657 22226,13574 22226,13490 L 22226,4923 22226,4924 22226,4924 C 22226,4840 22204,4757 22162,4684 22120,4611 22059,4550 21986,4508 21913,4466 21830,4444 21746,4444 L 7464,4444 Z"/>
 </g>
</g>
<g class="com.sun.star.drawing.CustomShape">
 <g id="id6">
  <rect class="BoundingBox" stroke="none" fill="none" x="8825" y="9279" width="2667" height="3883"/>
  <path fill="rgb(50,108,229)" stroke="none" d="M 10157,9279 C 9805,9279 9474,9349 9226,9478 8992,9600 8858,9761 8858,9920 8858,10080 8992,10242 9226,10363 9474,10491 9805,10563 10157,10563 10509,10563 10840,10492 11088,10363 11322,10242 11456,10080 11456,9920 11456,9761 11322,9600 11088,9478 10840,9350 10509,9279 10157,9279 L 10157,9279 Z M 8826,10088 L 8826,10341 C 8826,10722 9422,11031 10158,11031 10894,11031 11490,10723 11490,10341 L 11490,10088 C 11432,10228 11303,10355 11111,10454 10855,10586 10517,10658 10158,10658 9798,10658 9459,10586 9205,10454 9013,10354 8883,10228 8826,10088 L 8826,10088 Z M 8825,10594 L 8825,10767 C 8825,11147 9421,11457 10157,11457 10893,11457 11490,11149 11490,10767 L 11490,10594 C 11472,10625 11452,10656 11428,10685 11355,10775 11252,10854 11122,10922 10863,11056 10520,11129 10157,11129 9794,11129 9451,11056 9192,10922 9062,10854 8960,10775 8887,10685 8862,10656 8842,10625 8825,10594 L 8825,10594 Z M 8825,11019 L 8825,11193 C 8825,11573 9421,11882 10157,11882 10893,11882 11490,11574 11490,11193 L 11490,11019 C 11472,11051 11452,11081 11428,11111 11355,11201 11252,11280 11122,11347 10863,11481 10520,11555 10157,11555 9794,11555 9451,11481 9192,11347 9062,11280 8960,11201 8887,11111 8862,11081 8842,11051 8825,11019 L 8825,11019 Z M 8825,11445 L 8825,11618 C 8825,11999 9421,12308 10157,12308 10893,12308 11490,12000 11490,11618 L 11490,11445 C 11472,11476 11452,11507 11428,11536 11355,11626 11252,11705 11122,11773 10863,11907 10520,11980 10157,11980 9794,11980 9451,11907 9192,11773 9062,11705 8960,11626 8887,11536 8862,11507 8842,11476 8825,11445 L 8825,11445 Z M 8825,11871 L 8825,12044 C 8825,12425 9421,12734 10157,12734 10893,12734 11490,12426 11490,12044 L 11490,11871 C 11472,11902 11452,11933 11428,11962 11355,12052 11252,12131 11122,12199 10863,12333 10520,12406 10157,12406 9794,12406 9451,12333 9192,12199 9062,12131 8960,12052 8887,11962 8862,11933 8842,11902 8825,11871 L 8825,11871 Z M 8825,12297 L 8825,12470 C 8825,12850 9421,13160 10157,13160 10893,13160 11490,12850 11490,12470 L 11490,12297 C 11472,12328 11452,12358 11428,12388 11355,12478 11252,12557 11122,12624 10863,12758 10520,12832 10157,12832 9794,12832 9451,12758 9192,12624 9062,12557 8960,12478 8887,12388 8862,12358 8842,12328 8825,12297 L 8825,12297 Z"/>
 </g>
</g>
<g class="com.sun.star.drawing.CustomShape">
 <g id="id7">
  <rect class="BoundingBox" stroke="none" fill="none" x="9133" y="9524" width="2044" height="750"/>
  <text class="SVGTextShape"><tspan class="TextParagraph" font-family="Arial, sans-serif" font-size="388px" font-weight="700"><tspan class="TextPosition" x="9274" y="10055"><tspan fill="rgb(255,255,255)" stroke="none" style="white-space: pre">DB Pod 1</tspan></tspan></tspan></text>
 </g>
</g>
<g class="com.sun.star.drawing.CustomShape">
 <g id="id8">
  <rect class="BoundingBox" stroke="none" fill="none" x="17816" y="9279" width="2666" height="3883"/>
  <path fill="rgb(50,108,229)" stroke="none" d="M 19148,9279 C 18796,9279 18465,9349 18217,9478 17983,9600 17849,9761 17849,9920 17849,10080 17983,10242 18217,10363 18465,10491 18796,10563 19148,10563 19500,10563 19831,10492 20079,10363 20313,10242 20447,10080 20447,9920 20447,9761 20313,9600 20079,9478 19831,9350 19500,9279 19148,9279 L 19148,9279 Z M 17817,10088 L 17817,10341 C 17817,10722 18413,11031 19149,11031 19885,11031 20481,10723 20481,10341 L 20481,10088 C 20423,10228 20294,10355 20102,10454 19846,10586 19508,10658 19149,10658 18789,10658 18450,10586 18196,10454 18004,10354 17874,10228 17817,10088 L 17817,10088 Z M 17816,10594 L 17816,10767 C 17816,11147 18412,11457 19148,11457 19884,11457 20481,11149 20481,10767 L 20481,10594 C 20463,10625 20443,10656 20419,10685 20346,10775 20243,10854 20113,10922 19854,11056 19511,11129 19148,11129 18785,11129 18442,11056 18183,10922 18053,10854 17951,10775 17878,10685 17853,10656 17833,10625 17816,10594 L 17816,10594 Z M 17816,11019 L 17816,11193 C 17816,11573 18412,11882 19148,11882 19884,11882 20481,11574 20481,11193 L 20481,11019 C 20463,11051 20443,11081 20419,11111 20346,11201 20243,11280 20113,11347 19854,11481 19511,11555 19148,11555 18785,11555 18442,11481 18183,11347 18053,11280 17951,11201 17878,11111 17853,11081 17833,11051 17816,11019 L 17816,11019 Z M 17816,11445 L 17816,11618 C 17816,11999 18412,12308 19148,12308 19884,12308 20481,12000 20481,11618 L 20481,11445 C 20463,11476 20443,11507 20419,11536 20346,11626 20243,11705 20113,11773 19854,11907 19511,11980 19148,11980 18785,11980 18442,11907 18183,11773 18053,11705 17951,11626 17878,11536 17853,11507 17833,11476 17816,11445 L 17816,11445 Z M 17816,11871 L 17816,12044 C 17816,12425 18412,12734 19148,12734 19884,12734 20481,12426 20481,12044 L 20481,11871 C 20463,11902 20443,11933 20419,11962 20346,12052 20243,12131 20113,12199 19854,12333 19511,12406 19148,12406 18785,12406 18442,12333 18183,12199 18053,12131 17951,12052 17878,11962 17853,11933 17833,11902 17816,11871 L 17816,11871 Z M 17816,12297 L 17816,12470 C 17816,12850 18412,13160 19148,13160 19884,13160 20481,12850 20481,12470 L 20481,12297 C 20463,12328 20443,12358 20419,12388 20346,12478 20243,12557 20113,12624 19854,12758 19511,12832 19148,12832 18785,12832 18442,12758 18183,12624 18053,12557 17951,12478 17878,12388 17853,12358 17833,12328 17816,12297 L 17816,12297 Z"/>
 </g>
</g>
<g class="com.sun.star.drawing.CustomShape">
 <g id="id9">
  <rect class="BoundingBox" stroke="none" fill="none" x="18125" y="9524" width="2044" height="750"/>
  <text class="SVGTextShape"><tspan class="TextParagraph" font-family="Arial, sans-serif" font-size="388px" font-weight="700"><tspan class="TextPosition" x="18266" y="10055"><tspan fill="rgb(255,255,255)" stroke="none" style="white-space: pre">DB Pod 3</tspan></tspan></tspan></text>
 </g>
</g>
<g class="com.sun.star.drawing.CustomShape">
 <g id="id10">
  <rect class="BoundingBox" stroke="none" fill="none" x="13320" y="9279" width="2666" height="3883"/>
  <path fill="rgb(50,108,229)" stroke="none" d="M 14652,9279 C 14300,9279 13969,9349 13721,9478 13487,9600 13353,9761 13353,9920 13353,10080 13487,10242 13721,10363 13969,10491 14300,10563 14652,10563 15004,10563 15335,10492 15583,10363 15817,10242 15951,10080 15951,9920 15951,9761 15817,9600 15583,9478 15335,9350 15004,9279 14652,9279 L 14652,9279 Z M 13321,10088 L 13321,10341 C 13321,10722 13917,11031 14653,11031 15389,11031 15985,10723 15985,10341 L 15985,10088 C 15927,10228 15798,10355 15606,10454 15350,10586 15012,10658 14653,10658 14293,10658 13954,10586 13700,10454 13508,10354 13378,10228 13321,10088 L 13321,10088 Z M 13320,10594 L 13320,10767 C 13320,11147 13916,11457 14652,11457 15388,11457 15985,11149 15985,10767 L 15985,10594 C 15967,10625 15947,10656 15923,10685 15850,10775 15747,10854 15617,10922 15358,11056 15015,11129 14652,11129 14289,11129 13946,11056 13687,10922 13557,10854 13455,10775 13382,10685 13357,10656 13337,10625 13320,10594 L 13320,10594 Z M 13320,11019 L 13320,11193 C 13320,11573 13916,11882 14652,11882 15388,11882 15985,11574 15985,11193 L 15985,11019 C 15967,11051 15947,11081 15923,11111 15850,11201 15747,11280 15617,11347 15358,11481 15015,11555 14652,11555 14289,11555 13946,11481 13687,11347 13557,11280 13455,11201 13382,11111 13357,11081 13337,11051 13320,11019 L 13320,11019 Z M 13320,11445 L 13320,11618 C 13320,11999 13916,12308 14652,12308 15388,12308 15985,12000 15985,11618 L 15985,11445 C 15967,11476 15947,11507 15923,11536 15850,11626 15747,11705 15617,11773 15358,11907 15015,11980 14652,11980 14289,11980 13946,11907 13687,11773 13557,11705 13455,11626 13382,11536 13357,11507 13337,11476 13320,11445 L 13320,11445 Z M 13320,11871 L 13320,12044 C 13320,12425 13916,12734 14652,12734 15388,12734 15985,12426 15985,12044 L 15985,11871 C 15967,11902 15947,11933 15923,11962 15850,12052 15747,12131 15617,12199 15358,12333 15015,12406 14652,12406 14289,12406 13946,12333 13687,12199 13557,12131 13455,12052 13382,11962 13357,11933 13337,11902 13320,11871 L 13320,11871 Z M 13320,12297 L 13320,12470 C 13320,12850 13916,13160 14652,13160 15388,13160 15985,12850 15985,12470 L 15985,12297 C 15967,12328 15947,12358 15923,12388 15850,12478 15747,12557 15617,12624 15358,12758 15015,12832 14652,12832 14289,12832 13946,12758 13687,12624 13557,12557 13455,12478 13382,12388 13357,12358 13337,12328 13320,12297 L 13320,12297 Z"/>
 </g>
</g>
<g class="com.sun.star.drawing.CustomShape">
 <g id="id11">
  <rect class="BoundingBox" stroke="none" fill="none" x="13629" y="9524" width="2044" height="750"/>
  <text class="SVGTextShape"><tspan class="TextParagraph" font-family="Arial, sans-serif" font-size="388px" font-weight="700"><tspan class="TextPosition" x="13770" y="10055"><tspan fill="rgb(255,255,255)" stroke="none" style="white-space: pre">DB Pod 2</tspan></tspan></tspan></text>
 </g>
</g>
<g class="Group">
 <g class="com.sun.star.drawing.LineShape">
  <g id="id12">
   <rect class="BoundingBox" stroke="none" fill="none" x="9996" y="6826" width="319" height="2346"/>
   <path fill="none" stroke="rgb(190,24,24)" stroke-width="106" stroke-linejoin="miter" d="M 10155,7123 L 10155,9171"/>
   <path fill="rgb(190,24,24)" stroke="none" d="M 9996,7144 L 10155,6826 10314,7144 9996,7144 Z"/>
  </g>
 </g>
 <g class="com.sun.star.drawing.CustomShape">
  <g id="id13">
   <rect class="BoundingBox" stroke="none" fill="none" x="9422" y="7305" width="833" height="1390"/>
   <text class="SVGTextShape" transform="rotate(-90 10022 8554)"><tspan class="TextParagraph" font-family="Arial, sans-serif" font-size="459px" font-weight="400"><tspan class="TextPosition" x="10022" y="8554"><tspan fill="rgb(9,34,87)" stroke="none" style="white-space: pre">Read</tspan></tspan></tspan></text>
  </g>
 </g>
 <g class="com.sun.star.drawing.CustomShape">
  <g id="id14">
   <rect class="BoundingBox" stroke="none" fill="none" x="10055" y="7628" width="833" height="738"/>
  </g>
 </g>
</g>
<g class="Group">
 <g class="com.sun.star.drawing.LineShape">
  <g id="id15">
   <rect class="BoundingBox" stroke="none" fill="none" x="18990" y="6826" width="319" height="2346"/>
   <path fill="none" stroke="rgb(190,24,24)" stroke-width="106" stroke-linejoin="miter" d="M 19149,7123 L 19149,9171"/>
   <path fill="rgb(190,24,24)" stroke="none" d="M 18990,7144 L 19149,6826 19308,7144 18990,7144 Z"/>
  </g>
 </g>
 <g class="com.sun.star.drawing.CustomShape">
  <g id="id16">
   <rect class="BoundingBox" stroke="none" fill="none" x="18416" y="7305" width="833" height="1390"/>
   <text class="SVGTextShape" transform="rotate(-90 19016 8554)"><tspan class="TextParagraph" font-family="Arial, sans-serif" font-size="459px" font-weight="400"><tspan class="TextPosition" x="19016" y="8554"><tspan fill="rgb(9,34,87)" stroke="none" style="white-space: pre">Read</tspan></tspan></tspan></text>
  </g>
 </g>
 <g class="com.sun.star.drawing.CustomShape">
  <g id="id17">
   <rect class="BoundingBox" stroke="none" fill="none" x="19050" y="7628" width="833" height="738"/>
  </g>
 </g>
</g>
<g class="Group">
 <g class="com.sun.star.drawing.LineShape">
  <g id="id18">
   <rect class="BoundingBox" stroke="none" fill="none" x="14462" y="3344" width="319" height="2346"/>
   <path fill="none" stroke="rgb(190,24,24)" stroke-width="106" stroke-linejoin="miter" d="M 14621,3641 L 14621,5392"/>
   <path fill="rgb(190,24,24)" stroke="none" d="M 14621,3344 L 14780,3662 14462,3662 14621,3344 Z"/>
   <path fill="rgb(190,24,24)" stroke="none" d="M 14621,5689 L 14462,5371 14780,5371 14621,5689 Z"/>
  </g>
 </g>
 <g class="com.sun.star.drawing.CustomShape">
  <g id="id19">
   <rect class="BoundingBox" stroke="none" fill="none" x="13887" y="3823" width="833" height="1390"/>
   <text class="SVGTextShape" transform="rotate(-90 14487 5072)"><tspan class="TextParagraph" font-family="Arial, sans-serif" font-size="459px" font-weight="400"><tspan class="TextPosition" x="14487" y="5072"><tspan fill="rgb(9,34,87)" stroke="none" style="white-space: pre">Read</tspan></tspan></tspan></text>
  </g>
 </g>
 <g class="com.sun.star.drawing.CustomShape">
  <g id="id20">
   <rect class="BoundingBox" stroke="none" fill="none" x="14523" y="3837" width="833" height="1361"/>
   <text class="SVGTextShape" transform="rotate(90 14755 3978)"><tspan class="TextParagraph" font-family="Arial, sans-serif" font-size="459px" font-weight="400"><tspan class="TextPosition" x="14755" y="3978"><tspan fill="rgb(9,34,87)" stroke="none" style="white-space: pre">Write</tspan></tspan></tspan></text>
  </g>
 </g>
</g>
<g class="Group">
 <g class="com.sun.star.drawing.LineShape">
  <g id="id21">
   <rect class="BoundingBox" stroke="none" fill="none" x="11489" y="11120" width="1681" height="528"/>
   <path fill="none" stroke="rgb(190,24,24)" stroke-width="106" stroke-linejoin="miter" d="M 11778,11497 L 13156,11172"/>
   <path fill="rgb(190,24,24)" stroke="none" d="M 11835,11647 L 11489,11565 11762,11337 11835,11647 Z"/>
  </g>
 </g>
 <g class="com.sun.star.drawing.CustomShape">
  <g id="id22">
   <rect class="BoundingBox" stroke="none" fill="none" x="11574" y="10353" width="1522" height="1140"/>
   <text class="SVGTextShape" transform="rotate(-14 11857 11232)"><tspan class="TextParagraph" font-family="Arial, sans-serif" font-size="459px" font-weight="400"><tspan class="TextPosition" x="11857" y="11232"><tspan fill="rgb(9,34,87)" stroke="none" style="white-space: pre">Write</tspan></tspan></tspan></text>
  </g>
 </g>
 <g class="com.sun.star.drawing.CustomShape">
  <g id="id23">
   <rect class="BoundingBox" stroke="none" fill="none" x="13056" y="9629" width="833" height="738"/>
  </g>
 </g>
 <g class="com.sun.star.drawing.LineShape">
  <g id="id24">
   <rect class="BoundingBox" stroke="none" fill="none" x="16082" y="11136" width="1681" height="528"/>
   <path fill="none" stroke="rgb(190,24,24)" stroke-width="106" stroke-linejoin="miter" d="M 17473,11513 L 16095,11188"/>
   <path fill="rgb(190,24,24)" stroke="none" d="M 17489,11353 L 17762,11581 17416,11663 17489,11353 Z"/>
  </g>
 </g>
 <g class="com.sun.star.drawing.CustomShape">
  <g id="id25">
   <rect class="BoundingBox" stroke="none" fill="none" x="16173" y="10356" width="1520" height="1132"/>
   <text class="SVGTextShape" transform="rotate(13.7 16366 10972)"><tspan class="TextParagraph" font-family="Arial, sans-serif" font-size="459px" font-weight="400"><tspan class="TextPosition" x="16366" y="10972"><tspan fill="rgb(9,34,87)" stroke="none" style="white-space: pre">Write</tspan></tspan></tspan></text>
  </g>
 </g>
</g>
<g class="Group">
 <g class="com.sun.star.drawing.LineShape">
  <g id="id26">
   <rect class="BoundingBox" stroke="none" fill="none" x="14396" y="6827" width="320" height="2346"/>
   <path fill="none" stroke="rgb(190,24,24)" stroke-width="106" stroke-linejoin="miter" d="M 14556,6827 L 14556,8875"/>
   <path fill="rgb(190,24,24)" stroke="none" d="M 14715,8854 L 14556,9172 14397,8854 14715,8854 Z"/>
  </g>
 </g>
 <g class="com.sun.star.drawing.CustomShape">
  <g id="id27">
   <rect class="BoundingBox" stroke="none" fill="none" x="13822" y="7317" width="833" height="1361"/>
   <text class="SVGTextShape" transform="rotate(-90 14423 8536)"><tspan class="TextParagraph" font-family="Arial, sans-serif" font-size="459px" font-weight="400"><tspan class="TextPosition" x="14423" y="8536"><tspan fill="rgb(9,34,87)" stroke="none" style="white-space: pre">Write</tspan></tspan></tspan></text>
  </g>
 </g>
 <g class="com.sun.star.drawing.CustomShape">
  <g id="id28">
   <rect class="BoundingBox" stroke="none" fill="none" x="14456" y="7629" width="833" height="738"/>
  </g>
 </g>
</g>
<g class="Graphic">
 <g id="id29">
  <rect class="BoundingBox" stroke="none" fill="none" x="7633" y="3821" width="1331" height="1291"/>
  <path fill="rgb(50,108,229)" stroke="none" d="M 8284,3850 L 8284,3850 C 8273,3851 8262,3854 8252,3859 L 7816,4066 C 7814,4067 7812,4068 7810,4069 7798,4077 7787,4087 7780,4100 7776,4107 7773,4114 7771,4122 L 7664,4589 C 7663,4595 7662,4601 7662,4608 7662,4622 7666,4636 7673,4648 7674,4649 7675,4651 7676,4653 7678,4655 7679,4657 7681,4659 L 7982,5034 C 7989,5041 7996,5048 8005,5053 8018,5060 8032,5064 8047,5064 8047,5064 8047,5064 8047,5064 L 8530,5064 C 8556,5064 8579,5052 8595,5033 L 8896,4659 C 8912,4639 8918,4613 8912,4589 L 8805,4122 C 8799,4098 8782,4077 8760,4066 L 8324,3859 C 8313,3853 8300,3850 8287,3850 8286,3850 8285,3850 8284,3850 L 8284,3850 Z"/>
  <path fill="rgb(255,255,255)" stroke="none" d="M 8288,4009 C 8284,4010 8280,4011 8276,4013 8272,4015 8269,4019 8266,4023 8264,4027 8263,4032 8263,4037 8263,4037 8263,4038 8263,4038 L 8263,4046 C 8263,4054 8265,4062 8266,4070 8269,4086 8270,4102 8269,4118 8268,4120 8267,4122 8266,4124 8265,4127 8263,4129 8260,4131 L 8260,4142 C 8244,4143 8228,4146 8213,4149 8180,4157 8148,4169 8119,4186 8091,4202 8065,4223 8042,4247 L 8033,4240 C 8028,4241 8022,4241 8017,4238 8005,4229 7993,4218 7982,4207 7977,4200 7971,4194 7965,4189 L 7959,4184 C 7958,4183 7957,4182 7955,4181 7951,4179 7946,4177 7941,4177 7941,4177 7940,4177 7940,4177 7936,4177 7931,4178 7927,4180 7925,4182 7922,4184 7921,4186 7920,4187 7920,4188 7919,4189 7917,4193 7915,4197 7915,4202 7915,4207 7917,4212 7919,4216 7921,4219 7924,4222 7927,4225 L 7932,4229 C 7939,4233 7946,4237 7954,4241 7968,4249 7981,4258 7993,4269 7993,4269 7994,4270 7994,4271 7997,4275 7998,4279 7998,4284 L 8007,4291 C 8003,4297 7999,4303 7995,4310 7966,4361 7950,4419 7950,4478 7950,4494 7951,4510 7954,4526 L 7943,4529 C 7940,4534 7936,4538 7932,4541 7916,4545 7901,4547 7885,4548 7877,4549 7868,4549 7860,4550 L 7853,4552 7852,4552 C 7849,4552 7846,4553 7843,4555 7839,4557 7835,4561 7833,4565 7831,4569 7830,4573 7830,4578 7830,4582 7831,4587 7833,4591 7834,4593 7836,4595 7837,4596 7839,4598 7841,4599 7843,4600 7847,4603 7851,4604 7856,4604 7858,4604 7861,4603 7864,4603 L 7864,4603 7871,4602 C 7879,4599 7887,4597 7895,4593 7909,4587 7925,4583 7940,4580 7945,4580 7949,4581 7953,4584 7954,4584 7954,4585 7955,4585 L 7967,4583 C 7992,4662 8046,4729 8117,4771 L 8112,4781 C 8114,4785 8115,4790 8115,4795 8108,4810 8100,4825 8091,4838 8086,4845 8081,4852 8077,4859 L 8074,4866 C 8071,4870 8070,4875 8070,4879 8070,4884 8071,4888 8074,4892 8076,4896 8079,4900 8083,4902 8087,4904 8092,4905 8096,4905 8097,4905 8098,4905 8099,4905 8103,4905 8106,4904 8109,4902 8113,4900 8117,4896 8119,4892 8120,4891 8120,4890 8121,4888 L 8124,4882 C 8127,4874 8130,4866 8132,4858 8138,4841 8142,4823 8152,4811 8153,4810 8154,4809 8156,4809 8158,4807 8160,4806 8163,4806 L 8169,4795 C 8207,4810 8248,4818 8290,4818 8331,4818 8371,4810 8409,4796 L 8414,4806 C 8417,4806 8419,4807 8421,4808 8424,4810 8426,4811 8428,4814 8435,4828 8441,4842 8446,4857 8448,4866 8450,4874 8453,4881 L 8456,4888 C 8457,4889 8457,4891 8458,4892 8460,4896 8464,4899 8468,4901 8472,4904 8476,4905 8481,4905 8485,4905 8490,4904 8494,4901 8498,4899 8501,4897 8503,4893 8503,4893 8503,4892 8503,4892 8506,4888 8507,4883 8507,4879 8507,4874 8506,4870 8503,4866 8503,4866 8503,4866 8503,4866 L 8500,4859 C 8496,4851 8491,4845 8486,4838 8482,4832 8478,4826 8474,4819 8470,4812 8466,4804 8463,4797 8462,4795 8462,4793 8462,4792 8462,4788 8463,4785 8465,4782 8465,4781 8465,4781 8465,4781 8464,4778 8462,4774 8461,4770 8511,4741 8552,4699 8581,4649 8593,4628 8603,4605 8610,4582 L 8622,4584 C 8623,4583 8624,4582 8625,4581 8628,4580 8632,4579 8635,4579 8636,4579 8636,4579 8636,4579 8652,4582 8667,4586 8682,4592 8689,4596 8697,4598 8705,4601 8707,4601 8710,4602 8712,4602 L 8712,4602 C 8715,4603 8717,4604 8720,4604 8725,4604 8729,4602 8733,4600 8737,4598 8741,4594 8743,4590 8745,4586 8746,4582 8746,4577 8746,4575 8746,4572 8745,4569 8745,4567 8744,4566 8743,4564 8741,4560 8737,4557 8733,4555 8730,4553 8727,4552 8724,4552 L 8716,4550 C 8708,4549 8700,4548 8691,4548 8675,4547 8660,4544 8644,4540 8644,4540 8644,4540 8644,4540 8640,4537 8636,4534 8633,4529 8633,4529 8633,4529 8633,4529 L 8623,4526 C 8625,4511 8626,4495 8626,4480 8626,4420 8610,4362 8580,4311 8577,4304 8573,4298 8568,4291 L 8578,4283 C 8577,4278 8579,4272 8583,4268 8594,4258 8606,4249 8620,4242 8620,4242 8621,4241 8621,4241 8625,4239 8628,4237 8631,4236 8635,4233 8639,4231 8643,4228 L 8649,4224 C 8652,4221 8655,4218 8657,4215 8660,4211 8661,4206 8661,4201 8661,4197 8660,4192 8657,4188 8655,4184 8652,4181 8648,4179 8646,4178 8645,4177 8643,4177 8641,4176 8638,4175 8635,4175 8630,4175 8626,4176 8622,4179 8620,4180 8618,4181 8616,4183 L 8610,4188 C 8604,4193 8599,4199 8593,4206 8583,4218 8571,4228 8559,4238 8556,4239 8552,4240 8548,4240 8547,4240 8545,4240 8543,4240 L 8533,4247 C 8476,4187 8399,4150 8317,4142 8317,4138 8316,4133 8316,4131 8314,4129 8312,4127 8310,4124 8309,4122 8308,4120 8308,4118 8307,4102 8308,4086 8311,4071 8312,4063 8314,4054 8314,4046 L 8314,4038 C 8314,4038 8314,4037 8314,4037 8314,4032 8313,4027 8311,4023 8308,4019 8305,4015 8301,4013 8297,4011 8293,4009 8289,4009 L 8288,4009 Z M 8256,4211 L 8248,4348 8247,4348 C 8247,4356 8242,4363 8234,4368 8227,4372 8218,4370 8211,4365 L 8099,4286 C 8115,4270 8133,4256 8153,4245 8176,4232 8202,4222 8228,4216 8237,4214 8246,4212 8256,4211 L 8256,4211 Z M 8321,4211 C 8380,4219 8435,4245 8477,4286 L 8366,4365 C 8366,4366 8365,4367 8364,4367 8360,4369 8356,4370 8352,4370 8348,4370 8344,4369 8341,4367 8337,4365 8334,4362 8332,4359 8330,4355 8329,4352 8329,4348 L 8321,4211 Z M 8161,4429 L 8161,4429 C 8162,4431 8164,4433 8165,4435 8167,4439 8168,4443 8168,4447 8168,4451 8167,4455 8165,4458 8163,4462 8160,4465 8157,4467 8155,4468 8153,4468 8152,4469 L 8152,4469 8020,4507 C 8019,4497 8018,4488 8018,4478 8018,4431 8031,4384 8054,4344 8056,4342 8057,4339 8058,4337 L 8161,4429 Z M 8518,4337 C 8519,4340 8520,4342 8522,4344 8546,4385 8558,4432 8558,4480 8558,4489 8558,4498 8557,4507 L 8425,4469 8425,4468 C 8423,4468 8421,4467 8420,4466 8416,4464 8413,4461 8411,4458 8410,4456 8409,4453 8409,4451 8408,4449 8408,4448 8408,4446 8408,4442 8409,4438 8411,4434 8413,4432 8414,4430 8416,4429 L 8518,4338 8518,4337 Z M 8267,4436 L 8309,4436 8335,4468 8325,4509 8288,4527 8250,4509 8241,4468 8267,4436 Z M 8402,4547 C 8404,4547 8406,4547 8407,4547 L 8543,4570 C 8523,4626 8484,4674 8434,4706 L 8382,4579 C 8380,4577 8380,4574 8380,4570 8380,4566 8381,4562 8383,4559 8385,4555 8388,4553 8391,4550 8394,4549 8398,4548 8402,4547 L 8402,4547 Z M 8174,4547 C 8178,4548 8182,4549 8185,4551 8188,4552 8191,4555 8193,4558 8193,4558 8193,4559 8193,4559 8195,4562 8197,4566 8197,4570 8197,4573 8196,4577 8195,4579 L 8195,4580 8143,4706 C 8106,4683 8076,4651 8055,4614 8047,4600 8040,4586 8035,4571 L 8169,4548 C 8171,4548 8172,4548 8174,4548 L 8174,4547 Z M 8287,4602 C 8288,4602 8288,4602 8288,4602 8292,4602 8296,4603 8300,4605 8303,4607 8306,4610 8308,4614 8308,4614 8308,4614 8308,4614 L 8309,4614 8375,4734 8349,4742 C 8329,4746 8309,4748 8289,4748 8259,4748 8230,4743 8202,4734 L 8268,4614 C 8268,4614 8268,4614 8268,4614 8270,4611 8273,4608 8277,4606 8280,4604 8284,4603 8288,4603 L 8287,4602 Z"/>
  <path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" d="M 8288,4009 L 8288,4009 C 8284,4010 8280,4011 8276,4013 8272,4015 8269,4019 8266,4023 8264,4027 8263,4032 8263,4037 8263,4037 8263,4038 8263,4038 L 8263,4046 C 8263,4054 8265,4062 8266,4070 8269,4086 8270,4102 8269,4118 L 8269,4118 C 8268,4120 8267,4122 8266,4124 8265,4127 8263,4129 8260,4131 L 8260,4142 8260,4142 C 8244,4143 8228,4146 8213,4149 L 8213,4149 C 8180,4157 8148,4169 8119,4186 8091,4202 8065,4223 8042,4247 L 8033,4240 C 8028,4241 8022,4241 8017,4238 L 8017,4238 C 8005,4229 7993,4218 7982,4207 7977,4200 7971,4194 7965,4189 L 7959,4184 7959,4184 C 7958,4183 7957,4182 7955,4181 7951,4179 7946,4177 7941,4177 L 7941,4177 C 7941,4177 7940,4177 7940,4177 7936,4177 7931,4178 7927,4180 7925,4182 7922,4184 7921,4186 L 7921,4186 C 7920,4187 7920,4188 7919,4189 7917,4193 7915,4197 7915,4202 7915,4207 7917,4212 7919,4216 7921,4219 7924,4222 7927,4225 L 7932,4229 C 7939,4233 7946,4237 7954,4241 7968,4249 7981,4258 7993,4269 L 7993,4269 C 7993,4269 7994,4270 7994,4271 7997,4275 7998,4279 7998,4284 L 8007,4291 8007,4291 C 8003,4297 7999,4303 7995,4310 7966,4361 7950,4419 7950,4478 7950,4494 7951,4510 7954,4526 L 7943,4529 7943,4529 C 7940,4534 7936,4538 7932,4541 7916,4545 7901,4547 7885,4548 7877,4549 7868,4549 7860,4550 L 7853,4552 7852,4552 7852,4552 C 7849,4552 7846,4553 7843,4555 7839,4557 7835,4561 7833,4565 7831,4569 7830,4573 7830,4578 7830,4582 7831,4587 7833,4591 7834,4593 7836,4595 7837,4596 L 7837,4596 C 7839,4598 7841,4599 7843,4600 7847,4603 7851,4604 7856,4604 7858,4604 7861,4603 7864,4603 L 7864,4603 7871,4602 7871,4602 C 7879,4599 7887,4597 7895,4593 7909,4587 7925,4583 7940,4580 L 7940,4580 C 7945,4580 7949,4581 7953,4584 7954,4584 7954,4585 7955,4585 L 7967,4583 C 7992,4662 8046,4729 8117,4771 L 8112,4781 C 8114,4785 8115,4790 8115,4795 8108,4810 8100,4825 8091,4838 L 8091,4838 C 8086,4845 8081,4852 8077,4859 L 8074,4866 8074,4866 C 8071,4870 8070,4875 8070,4879 8070,4884 8071,4888 8074,4892 8076,4896 8079,4900 8083,4902 8087,4904 8092,4905 8096,4905 8097,4905 8098,4905 8099,4905 L 8099,4905 C 8103,4905 8106,4904 8109,4902 8113,4900 8117,4896 8119,4892 8120,4891 8120,4890 8121,4888 L 8124,4882 C 8127,4874 8130,4866 8132,4858 8138,4841 8142,4823 8152,4811 L 8152,4811 C 8153,4810 8154,4809 8156,4809 8158,4807 8160,4806 8163,4806 L 8169,4795 8169,4795 C 8207,4810 8248,4818 8290,4818 8331,4818 8371,4810 8409,4796 L 8414,4806 8414,4806 C 8417,4806 8419,4807 8421,4808 8424,4810 8426,4811 8428,4814 L 8428,4814 C 8435,4828 8441,4842 8446,4857 8448,4866 8450,4874 8453,4881 L 8456,4888 8456,4888 C 8457,4889 8457,4891 8458,4892 8460,4896 8464,4899 8468,4901 8472,4904 8476,4905 8481,4905 8485,4905 8490,4904 8494,4901 8498,4899 8501,4897 8503,4893 L 8503,4893 C 8503,4893 8503,4892 8503,4892 8506,4888 8507,4883 8507,4879 8507,4874 8506,4870 8503,4866 8503,4866 8503,4866 8503,4866 L 8500,4859 C 8496,4851 8491,4845 8486,4838 L 8486,4838 C 8482,4832 8478,4826 8474,4819 8470,4812 8466,4804 8463,4797 L 8463,4797 C 8462,4795 8462,4793 8462,4792 8462,4788 8463,4785 8465,4782 8465,4781 8465,4781 8465,4781 8464,4778 8462,4774 8461,4770 L 8461,4770 C 8511,4741 8552,4699 8581,4649 8593,4628 8603,4605 8610,4582 L 8622,4584 8622,4584 C 8623,4583 8624,4582 8625,4581 8628,4580 8632,4579 8635,4579 8636,4579 8636,4579 8636,4579 L 8636,4579 C 8652,4582 8667,4586 8682,4592 8689,4596 8697,4598 8705,4601 8707,4601 8710,4602 8712,4602 L 8712,4602 8712,4602 C 8715,4603 8717,4604 8720,4604 8725,4604 8729,4602 8733,4600 8737,4598 8741,4594 8743,4590 8745,4586 8746,4582 8746,4577 8746,4575 8746,4572 8745,4569 L 8745,4569 C 8745,4567 8744,4566 8743,4564 8741,4560 8737,4557 8733,4555 8730,4553 8727,4552 8724,4552 L 8716,4550 C 8708,4549 8700,4548 8691,4548 L 8691,4548 C 8675,4547 8660,4544 8644,4540 L 8644,4540 C 8644,4540 8644,4540 8644,4540 8640,4537 8636,4534 8633,4529 8633,4529 8633,4529 8633,4529 L 8623,4526 8623,4526 C 8625,4511 8626,4495 8626,4480 8626,4420 8610,4362 8580,4311 8577,4304 8573,4298 8568,4291 L 8578,4283 C 8577,4278 8579,4272 8583,4268 L 8583,4268 C 8594,4258 8606,4249 8620,4242 8620,4242 8621,4241 8621,4241 L 8621,4241 C 8625,4239 8628,4237 8631,4236 8635,4233 8639,4231 8643,4228 L 8649,4224 8649,4224 C 8652,4221 8655,4218 8657,4215 8660,4211 8661,4206 8661,4201 8661,4197 8660,4192 8657,4188 8655,4184 8652,4181 8648,4179 8646,4178 8645,4177 8643,4177 L 8643,4177 C 8641,4176 8638,4175 8635,4175 8630,4175 8626,4176 8622,4179 8620,4180 8618,4181 8616,4183 L 8610,4188 8610,4188 C 8604,4193 8599,4199 8593,4206 8583,4218 8571,4228 8559,4238 L 8559,4238 C 8556,4239 8552,4240 8548,4240 8547,4240 8545,4240 8543,4240 L 8533,4247 C 8476,4187 8399,4150 8317,4142 8317,4138 8316,4133 8316,4131 L 8316,4131 C 8314,4129 8312,4127 8310,4124 8309,4122 8308,4120 8308,4118 8307,4102 8308,4086 8311,4071 8312,4063 8314,4054 8314,4046 L 8314,4038 8314,4038 C 8314,4038 8314,4037 8314,4037 8314,4032 8313,4027 8311,4023 8308,4019 8305,4015 8301,4013 8297,4011 8293,4009 8289,4009 L 8288,4009 Z"/>
  <path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" d="M 8256,4211 L 8248,4348 8247,4348 C 8247,4356 8242,4363 8234,4368 8227,4372 8218,4370 8211,4365 L 8099,4286 8099,4286 C 8115,4270 8133,4256 8153,4245 8176,4232 8202,4222 8228,4216 8237,4214 8246,4212 8256,4211 L 8256,4211 Z"/>
  <path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" d="M 8321,4211 C 8380,4219 8435,4245 8477,4286 L 8366,4365 8366,4365 C 8366,4366 8365,4367 8364,4367 8360,4369 8356,4370 8352,4370 8348,4370 8344,4369 8341,4367 8337,4365 8334,4362 8332,4359 8330,4355 8329,4352 8329,4348 L 8321,4211 Z"/>
  <path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" d="M 8058,4337 L 8161,4429 8161,4429 8161,4429 C 8162,4431 8164,4433 8165,4435 8167,4439 8168,4443 8168,4447 8168,4451 8167,4455 8165,4458 8163,4462 8160,4465 8157,4467 8155,4468 8153,4468 8152,4469 L 8152,4469 8020,4507 8020,4507 C 8019,4497 8018,4488 8018,4478 8018,4431 8031,4384 8054,4344 8056,4342 8057,4339 8058,4337 L 8058,4337 Z"/>
  <path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" d="M 8518,4337 L 8518,4337 C 8519,4340 8520,4342 8522,4344 8546,4385 8558,4432 8558,4480 8558,4489 8558,4498 8557,4507 L 8425,4469 8425,4468 8425,4468 C 8423,4468 8421,4467 8420,4466 8416,4464 8413,4461 8411,4458 8410,4456 8409,4453 8409,4451 L 8409,4451 C 8408,4449 8408,4448 8408,4446 8408,4442 8409,4438 8411,4434 8413,4432 8414,4430 8416,4429 L 8518,4338 8518,4337 Z"/>
  <path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" d="M 8267,4436 L 8309,4436 8335,4468 8325,4509 8288,4527 8250,4509 8241,4468 8267,4436 Z"/>
  <path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" d="M 8402,4547 C 8404,4547 8406,4547 8407,4547 L 8543,4570 C 8523,4626 8484,4674 8434,4706 L 8382,4579 8382,4579 C 8380,4577 8380,4574 8380,4570 8380,4566 8381,4562 8383,4559 8385,4555 8388,4553 8391,4550 8394,4549 8398,4548 8402,4547 L 8402,4547 Z"/>
  <path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" d="M 8174,4547 L 8174,4547 C 8178,4548 8182,4549 8185,4551 8188,4552 8191,4555 8193,4558 L 8193,4558 C 8193,4558 8193,4559 8193,4559 8195,4562 8197,4566 8197,4570 8197,4573 8196,4577 8195,4579 L 8195,4580 8143,4706 8143,4706 C 8106,4683 8076,4651 8055,4614 8047,4600 8040,4586 8035,4571 L 8169,4548 C 8171,4548 8172,4548 8174,4548 L 8174,4547 Z"/>
  <path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" d="M 8287,4602 L 8287,4602 C 8288,4602 8288,4602 8288,4602 8292,4602 8296,4603 8300,4605 8303,4607 8306,4610 8308,4614 8308,4614 8308,4614 8308,4614 L 8309,4614 8375,4734 8349,4742 8349,4742 C 8329,4746 8309,4748 8289,4748 8259,4748 8230,4743 8202,4734 L 8268,4614 8268,4614 C 8268,4614 8268,4614 8268,4614 8270,4611 8273,4608 8277,4606 8280,4604 8284,4603 8288,4603 L 8287,4602 Z"/>
 </g>
</g>
<g class="com.sun.star.drawing.CustomShape">
 <g id="id30">
  <rect class="BoundingBox" stroke="none" fill="none" x="8895" y="1905" width="11432" height="1292"/>
  <path fill="rgb(50,108,229)" stroke="none" d="M 8994,1905 L 8994,1905 C 8977,1905 8960,1910 8945,1918 8929,1927 8917,1939 8908,1955 8900,1970 8895,1987 8895,2004 L 8895,3096 8895,3097 C 8895,3114 8900,3131 8908,3146 8917,3162 8929,3174 8945,3183 8960,3191 8977,3196 8994,3196 L 20226,3196 20227,3196 C 20244,3196 20261,3191 20276,3183 20292,3174 20304,3162 20313,3146 20321,3131 20326,3114 20326,3097 L 20326,2004 20326,2004 20326,2004 C 20326,1987 20321,1970 20313,1955 20304,1939 20292,1927 20276,1918 20261,1910 20244,1905 20227,1905 L 8994,1905 Z"/>
  <text class="SVGTextShape"><tspan class="TextParagraph" font-family="Arial, sans-serif" font-size="706px" font-weight="700"><tspan class="TextPosition" x="11619" y="2796"><tspan fill="rgb(255,255,255)" stroke="none" style="white-space: pre">Client Application</tspan></tspan></tspan></text>
 </g>
</g>
<g class="com.sun.star.drawing.PolyPolygonShape">
 <g id="id31">
  <rect class="BoundingBox" stroke="none" fill="none" x="8498" y="5694" width="12226" height="978"/>
  <path fill="rgb(50,108,229)" stroke="none" d="M 8525,6644 L 9417,5721 19817,5721 20696,6644 8525,6644 Z"/>
  <path fill="none" stroke="rgb(255,255,255)" stroke-width="53" stroke-linejoin="round" d="M 8525,6644 L 9417,5721 19817,5721 20696,6644 8525,6644 Z"/>
  <text class="SVGTextShape"><tspan class="TextParagraph" font-family="Arial, sans-serif" font-size="529px" font-weight="700"><tspan class="TextPosition" x="11213" y="6367"><tspan fill="rgb(255,255,255)" stroke="none" style="white-space: pre">DB Proxy/Router (mongos)</tspan></tspan></tspan></text>
 </g>
</g>
<g class="com.sun.star.drawing.LineShape">
 <g id="id32">
  <rect class="BoundingBox" stroke="none" fill="none" x="7386" y="14019" width="14407" height="4"/>
  <path fill="none" stroke="rgb(255,255,255)" d="M 7387,14021 L 21791,14020"/>
 </g>
</g>
<g class="com.sun.star.drawing.LineShape">
 <g id="id33">
  <rect class="BoundingBox" stroke="none" fill="none" x="22256" y="4779" width="3" height="8893"/>
  <path fill="none" stroke="rgb(255,255,255)" d="M 22257,4780 L 22257,13670"/>
 </g>
</g>
<g class="com.sun.star.drawing.LineShape">
 <g id="id34">
  <rect class="BoundingBox" stroke="none" fill="none" x="22272" y="4779" width="3" height="8893"/>
  <path fill="none" stroke="rgb(255,255,255)" d="M 22273,4780 L 22273,13670"/>
 </g>
</g>
</g>
</svg>
### hidden 기능 추가
비용 절감을 위해 몽고디비도 충분히 Auto Scaling을 적용할 수 있을것 같아서, 프로젝트를 진행한 적이 있는데 데이터가 전부 sync되지 않았는데 client에게 OK query result를 보내서 중지한 적이 있다.
그래서 hidden 노드를 추가하는 기능을 추가하면 어떨가 했는데 작년 24년 9월에 이미 기능추가 요청이 있었다 ([링크](https://github.com/percona/percona-server-mongodb-operator/issues/1663))
PSMDB Operator v1.21.0 에서 해당 기능이 추가됐다.

</li>
</ul>
<h1 id="테스트">테스트</h1>
<p>github repo의 README를 참고</p>
<h3 id="kind로-클러스터-생성">kind로 클러스터 생성</h3>
<p><code>cluster.yaml</code> 파일 생성</p>
<pre><code>kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  image: kindest/node:v1.32.8
  # port forward 27017 on the host to 27017 on this node
  extraPortMappings:
  - containerPort: 27017
    hostPort: 27017
    # optional: set the bind address on the host
    # 0.0.0.0 is the current default
    listenAddress: &quot;0.0.0.0&quot;
    # optional: set the protocol to one of TCP, UDP, SCTP.
    # TCP is the default
    protocol: TCP
- role: worker
  image: kindest/node:v1.32.8
  labels:
    topology.kubernetes.io/zone: ap-northeast-2a
- role: worker
  image: kindest/node:v1.32.8
  labels:
    topology.kubernetes.io/zone: ap-northeast-2b
- role: worker
  image: kindest/node:v1.32.8
  labels:
    topology.kubernetes.io/zone: ap-northeast-2c</code></pre><h3 id="설치-링크">설치 (<a href="https://docs.percona.com/percona-operator-for-mongodb/helm.html?h=install+helm#installation">링크</a>)</h3>
<p>namespace 생성</p>
<pre><code>kubectl create ns mongodb</code></pre><p>helm repo 설치</p>
<pre><code>helm repo add percona https://percona.github.io/percona-helm-charts/
helm repo update</code></pre><p>psmdb operator 설치 (테스트 당시 최신버전 1.21.0)</p>
<pre><code>helm search repo percona/psmdb-operator --versions
helm pull percona/psmdb-operator --version 1.21.0
tar xvf psmdb-operator-1.21.0.tgz
helm install psmdb-operator charts/psmdb-operator -f values/psmdb-operator/values.yaml</code></pre><p>psmdb db 설치</p>
<pre><code>helm search repo percona/psmdb-db --versions
helm pull percona/psmdb-db --version 1.21.0
tar xvf psmdb-db-1.21.0.tgz
helm install cluster-1 charts/psmdb-db -f values/psmdb-db/values.yaml</code></pre><h3 id="mongodb-접속-및-상태확인">mongodb 접속 및 상태확인</h3>
<p>psmdb를 기본 설정으로 설치하면, 여러 secret이 생성된다.</p>
<pre><code>kubectl get secret
NAME                                        TYPE                 DATA   AGE
cluster-1-psmdb-db-mongodb-encryption-key   Opaque               1      6m2s
cluster-1-psmdb-db-mongodb-keyfile          Opaque               1      6m2s
cluster-1-psmdb-db-secrets                  Opaque               10     6m2s
cluster-1-psmdb-db-ssl                      kubernetes.io/tls    3      6m2s
cluster-1-psmdb-db-ssl-internal             kubernetes.io/tls    3      6m2s
internal-cluster-1-psmdb-db-users           Opaque               20     6m2s
sh.helm.release.v1.cluster-1.v1             helm.sh/release.v1   1      6m2s
sh.helm.release.v1.psmdb-operator.v1        helm.sh/release.v1   1      7m42s</code></pre><p>여기서 접속에 필요한 정보는 &lt;클러스터 이름&gt;-psmdb-db-secrets에서 확인할 수 있다. (<code>MONGODB_CLUSTER_ADMIN_USER</code>)</p>
<pre><code>kubectl get secret cluster-1-psmdb-db-secrets -o go-template=&#39;{{range $k,$v := .data}}{{printf &quot;%s: &quot; $k}}{{if not $v}}{{$v}}{{else}}{{$v | base64decode}}{{end}}{{&quot;\n&quot;}}{{end}}&#39;
MONGODB_BACKUP_PASSWORD: xjDLAXAfaEndA1W8w
MONGODB_BACKUP_USER: backup
MONGODB_CLUSTER_ADMIN_PASSWORD: ShdEOvY3nSLB1LXElmr
MONGODB_CLUSTER_ADMIN_USER: clusterAdmin
MONGODB_CLUSTER_MONITOR_PASSWORD: FLm5VAFHutGNyLOgE
MONGODB_CLUSTER_MONITOR_USER: clusterMonitor
MONGODB_DATABASE_ADMIN_PASSWORD: itUSHr20UuUo2M4Ih
MONGODB_DATABASE_ADMIN_USER: databaseAdmin
MONGODB_USER_ADMIN_PASSWORD: NbcMoEkJe7NeQ2QrVIC
MONGODB_USER_ADMIN_USER: userAdmin</code></pre><p>실행된 pod를 확인하고</p>
<pre><code>kubectl get pod   
NAME                              READY   STATUS    RESTARTS   AGE
cluster-1-psmdb-db-rs0-0          1/1     Running   0          6m29s
cluster-1-psmdb-db-rs0-1          1/1     Running   0          5m38s
cluster-1-psmdb-db-rs0-2          1/1     Running   0          4m54s
psmdb-operator-7d9c58954c-x9dzd   1/1     Running   0          8m9s</code></pre><p>파드 하나에 접속한다. 0번 파드가 웬만하면 <code>primary</code>일 것이다.</p>
<pre><code>kubectl exec -it cluster-1-psmdb-db-rs0-0 -- mongosh &quot;mongodb://clusterAdmin:ShdEOvY3nSLB1LXElmr@localhost:27017/admin&quot;
Defaulted container &quot;mongod&quot; out of: mongod, mongo-init (init)
Current Mongosh Log ID: 68fdc70b19fc7f4ffc248377
Connecting to:          mongodb://&lt;credentials&gt;@localhost:27017/admin?directConnection=true&amp;serverSelectionTimeoutMS=2000&amp;appName=mongosh+2.5.6
Using MongoDB:          8.0.12-4
Using Mongosh:          2.5.6
mongosh 2.5.8 is available for download: https://www.mongodb.com/try/download/shell

For mongosh info see: https://www.mongodb.com/docs/mongodb-shell/

rs0 [direct: primary] admin&gt;</code></pre><p><code>rs.status()</code>로 현재 레플리카셋의 상태를 확인할 수 있다</p>
<pre><code>rs0 [direct: primary] admin&gt; rs.status().members
[
  {
    _id: 0,
    name: &#39;cluster-1-psmdb-db-rs0-0.cluster-1-psmdb-db-rs0.mongodb.svc.cluster.local:27017&#39;,
    health: 1,
    state: 1,
    stateStr: &#39;PRIMARY&#39;,
  },
  {
    _id: 1,
    name: &#39;cluster-1-psmdb-db-rs0-1.cluster-1-psmdb-db-rs0.mongodb.svc.cluster.local:27017&#39;,
    health: 1,
    state: 2,
    stateStr: &#39;SECONDARY&#39;,
  },
  {
    _id: 2,
    name: &#39;cluster-1-psmdb-db-rs0-2.cluster-1-psmdb-db-rs0.mongodb.svc.cluster.local:27017&#39;,
    health: 1,
    state: 2,
    stateStr: &#39;SECONDARY&#39;,
  }
]</code></pre><h3 id="hidden-노드-추가해보기">hidden 노드 추가해보기</h3>
<p>먼저 CR 목록에서 현재 설치된 psmdb-db 상태를 확인할 수 있다.</p>
<pre><code>kubectl get perconaservermongodbs.psmdb.percona.com
NAME                 ENDPOINT                                           STATUS   AGE
cluster-1-psmdb-db   cluster-1-psmdb-db-rs0.mongodb.svc.cluster.local   ready    18m</code></pre><p>yaml로 출력하면 아래와 같다.</p>
<pre><code>kubectl get perconaservermongodbs.psmdb.percona.com -oyaml
apiVersion: v1
items:
- apiVersion: psmdb.percona.com/v1
  kind: PerconaServerMongoDB
  metadata:
    annotations:
      meta.helm.sh/release-name: cluster-1
      meta.helm.sh/release-namespace: mongodb
    creationTimestamp: &quot;2025-10-26T06:49:04Z&quot;
    finalizers:
    - percona.com/delete-psmdb-pods-in-order
    generation: 1
    labels:
      app.kubernetes.io/instance: cluster-1
      app.kubernetes.io/managed-by: Helm
      app.kubernetes.io/name: psmdb-db
      app.kubernetes.io/version: 1.21.0
      helm.sh/chart: psmdb-db-1.21.0
    name: cluster-1-psmdb-db
    namespace: mongodb
    resourceVersion: &quot;2733&quot;
    uid: 4fc410d9-b4d7-47b8-bb0f-03ea8c8056a4
  spec:
    backup:
      enabled: false
      image: percona/percona-backup-mongodb:2.11.0
      pitr:
        enabled: false
    crVersion: 1.21.0
    enableVolumeExpansion: false
    image: percona/percona-server-mongodb:8.0.12-4
    imagePullPolicy: Always
    logcollector:
      enabled: false
      image: percona/fluentbit:4.0.1
      resources:
        requests:
          cpu: 200m
          memory: 100M
    multiCluster:
      enabled: false
    pause: false
    pmm:
      enabled: false
      image: percona/pmm-client:3.4.1
      serverHost: monitoring-service
    replsets:
    - affinity:
        antiAffinityTopologyKey: kubernetes.io/hostname
      arbiter:
        affinity:
          antiAffinityTopologyKey: kubernetes.io/hostname
        enabled: false
        resources:
          limits:
            cpu: 600m
            memory: 1Gi
          requests:
            cpu: 300m
            memory: 1Gi
        size: 1
      expose:
        enabled: false
        type: ClusterIP
      hidden:
        affinity:
          antiAffinityTopologyKey: kubernetes.io/hostname
        enabled: false
        podDisruptionBudget:
          maxUnavailable: 1
        resources:
          limits:
            cpu: 600m
            memory: 1Gi
          requests:
            cpu: 300m
            memory: 1Gi
        size: 2
        volumeSpec:
          persistentVolumeClaim:
            resources:
              requests:
                storage: 3Gi
      name: rs0
      nonvoting:
        affinity:
          antiAffinityTopologyKey: kubernetes.io/hostname
        enabled: false
        podDisruptionBudget:
          maxUnavailable: 1
        resources:
          limits:
            cpu: 600m
            memory: 1Gi
          requests:
            cpu: 300m
            memory: 1Gi
        size: 3
        volumeSpec:
          persistentVolumeClaim:
            resources:
              requests:
                storage: 3Gi
      podDisruptionBudget:
        maxUnavailable: 1
      resources:
        limits:
          cpu: 600m
          memory: 1Gi
        requests:
          cpu: 300m
          memory: 1Gi
      size: 3
      volumeSpec:
        persistentVolumeClaim:
          resources:
            requests:
              storage: 3Gi
    secrets:
      users: cluster-1-psmdb-db-secrets
    sharding:
      balancer:
        enabled: true
      configsvrReplSet:
        affinity:
          antiAffinityTopologyKey: kubernetes.io/hostname
        expose:
          enabled: false
          type: ClusterIP
        podDisruptionBudget:
          maxUnavailable: 1
        resources:
          limits:
            cpu: 600m
            memory: 1Gi
          requests:
            cpu: 300m
            memory: 1Gi
        size: 3
        volumeSpec:
          persistentVolumeClaim:
            resources:
              requests:
                storage: 3Gi
      enabled: false
      mongos:
        affinity:
          antiAffinityTopologyKey: kubernetes.io/hostname
        expose:
          type: ClusterIP
        podDisruptionBudget:
          maxUnavailable: 1
        resources:
          limits:
            cpu: 600m
            memory: 1Gi
          requests:
            cpu: 300m
            memory: 1Gi
        size: 3
    unmanaged: false
    unsafeFlags:
      backupIfUnhealthy: false
      mongosSize: false
      replsetSize: false
      terminationGracePeriod: false
      tls: false
    updateStrategy: SmartUpdate
    upgradeOptions:
      apply: disabled
      schedule: 0 2 * * *
      setFCV: false
      versionServiceEndpoint: https://check.percona.com</code></pre><p>이제 설치할 때 사용한 helm values 파일을 수정해서 적용해본다. 먼저 레플리카셋 멤버를 3대에서 2대로 줄여보자. 기본적으로 레플리카셋의 파드는 홀수여야 하므로, <code>unsafeFlasgs</code>의 <code>replesetSize</code> 값을 <code>true</code>로 변경해준다.</p>
<pre><code>replsets:
  rs0:
    name: rs0
    size: 2 # 3에서 2로 변경

unsafeFlags:
  tls: false
  replsetSize: true</code></pre><p>수정 사항을 <a href="https://github.com/databus23/helm-diff">helm diff</a>로 확인해본 뒤, 적용한다.</p>
<pre><code>helm diff upgrade cluster-1 charts/psmdb-db -f values/psmdb-db/values.yaml | grep -E &#39;^(\+|-)&#39;     
-     size: 3
+     size: 2</code></pre><p>mongosh 쉘에 접속해서 보면, 3번째 rs0 멤버가 제거된 것을 확인할 수 있다.</p>
<pre><code>rs0 [direct: primary] admin&gt; rs.status().members
[
  {
    _id: 0,
    name: &#39;cluster-1-psmdb-db-rs0-0.cluster-1-psmdb-db-rs0.mongodb.svc.cluster.local:27017&#39;,
    health: 1,
    state: 1,
    stateStr: &#39;PRIMARY&#39;,
  },
  {
    _id: 1,
    name: &#39;cluster-1-psmdb-db-rs0-1.cluster-1-psmdb-db-rs0.mongodb.svc.cluster.local:27017&#39;,
    health: 1,
    state: 2,
    stateStr: &#39;SECONDARY&#39;,
  }
]</code></pre><p>이제 hidden 노드 추가를 위해 아래와 같이 values 파일을 수정해주자.</p>
<pre><code>    hidden:
      enabled: true
#      podSecurityContext: {}
#      containerSecurityContext: {}
      size: 1</code></pre><p>변경사항을 적용한 후, hidden 파드가 추가되는 것을 확인할 수 있다.</p>
<pre><code>kubectl get pod                                           
NAME                              READY   STATUS    RESTARTS   AGE
cluster-1-psmdb-db-rs0-0          1/1     Running   0          34m
cluster-1-psmdb-db-rs0-1          1/1     Running   0          33m
cluster-1-psmdb-db-rs0-hidden-0   0/1     Running   0          11s
psmdb-operator-7d9c58954c-x9dzd   1/1     Running   0          36m</code></pre><p>실제 <code>rs.conf()</code>에서 확인해보면 <code>hidden</code> 값이 true임을 확인할 수 있다.</p>
<pre><code>rs0 [direct: primary] admin&gt; rs.conf().members
[
  {
    _id: 0,
    host: &#39;cluster-1-psmdb-db-rs0-0.cluster-1-psmdb-db-rs0.mongodb.svc.cluster.local:27017&#39;,
    arbiterOnly: false,
    buildIndexes: true,
    hidden: false,
    priority: 2,
    tags: {
      serviceName: &#39;cluster-1-psmdb-db&#39;,
      nodeName: &#39;1.32-worker&#39;,
      podName: &#39;cluster-1-psmdb-db-rs0-0&#39;
    },
    secondaryDelaySecs: Long(&#39;0&#39;),
    votes: 1
  },
  {
    _id: 1,
    host: &#39;cluster-1-psmdb-db-rs0-1.cluster-1-psmdb-db-rs0.mongodb.svc.cluster.local:27017&#39;,
    arbiterOnly: false,
    buildIndexes: true,
    hidden: false,
    priority: 2,
    tags: {
      serviceName: &#39;cluster-1-psmdb-db&#39;,
      nodeName: &#39;1.32-worker3&#39;,
      podName: &#39;cluster-1-psmdb-db-rs0-1&#39;
    },
    secondaryDelaySecs: Long(&#39;0&#39;),
    votes: 1
  },
  {
    _id: 2,
    host: &#39;cluster-1-psmdb-db-rs0-hidden-0.cluster-1-psmdb-db-rs0.mongodb.svc.cluster.local:27017&#39;,
    arbiterOnly: false,
    buildIndexes: true,
    hidden: true,
    priority: 0,
    tags: {
      podName: &#39;cluster-1-psmdb-db-rs0-hidden-0&#39;,
      serviceName: &#39;cluster-1-psmdb-db&#39;,
      hidden: &#39;true&#39;,
      nodeName: &#39;1.32-worker2&#39;
    },
    secondaryDelaySecs: Long(&#39;0&#39;),
    votes: 1
  }
]</code></pre><h2 id="hidden-멤버를-secondary로-변경하기">hidden 멤버를 secondary로 변경하기</h2>
<p>mongosh 쉘에 접근한 상태에서 rs.conf() 를 변수에 저장한다.</p>
<pre><code>rs0 [direct: primary] admin&gt; cfg = rs.conf()</code></pre><p>hidden 값을 변경한다.</p>
<pre><code>rs0 [direct: primary] admin&gt; cfg.members[2].hidden=false
false</code></pre><p><code>cfg</code> 설정을 적용한다.</p>
<pre><code>rs0 [direct: primary] admin&gt; rs.reconfig(cfg)</code></pre><p>rs.conf() 확인</p>
<pre><code>rs0 [direct: primary] admin&gt; rs.conf().members
[
  {
    _id: 0,
    host: &#39;cluster-1-psmdb-db-rs0-0.cluster-1-psmdb-db-rs0.mongodb.svc.cluster.local:27017&#39;,
    arbiterOnly: false,
    buildIndexes: true,
    hidden: false,
    priority: 2,
    tags: {
      serviceName: &#39;cluster-1-psmdb-db&#39;,
      nodeName: &#39;1.32-worker&#39;,
      podName: &#39;cluster-1-psmdb-db-rs0-0&#39;
    },
    secondaryDelaySecs: Long(&#39;0&#39;),
    votes: 1
  },
  {
    _id: 1,
    host: &#39;cluster-1-psmdb-db-rs0-1.cluster-1-psmdb-db-rs0.mongodb.svc.cluster.local:27017&#39;,
    arbiterOnly: false,
    buildIndexes: true,
    hidden: false,
    priority: 2,
    tags: {
      serviceName: &#39;cluster-1-psmdb-db&#39;,
      nodeName: &#39;1.32-worker3&#39;,
      podName: &#39;cluster-1-psmdb-db-rs0-1&#39;
    },
    secondaryDelaySecs: Long(&#39;0&#39;),
    votes: 1
  },
  {
    _id: 2,
    host: &#39;cluster-1-psmdb-db-rs0-hidden-0.cluster-1-psmdb-db-rs0.mongodb.svc.cluster.local:27017&#39;,
    arbiterOnly: false,
    buildIndexes: true,
    hidden: false,
    priority: 0,
    tags: {
      podName: &#39;cluster-1-psmdb-db-rs0-hidden-0&#39;,
      serviceName: &#39;cluster-1-psmdb-db&#39;,
      hidden: &#39;true&#39;,
      nodeName: &#39;1.32-worker2&#39;
    },
    secondaryDelaySecs: Long(&#39;0&#39;),
    votes: 1
  }
]</code></pre><p>위 상태에서 다시 replset 개수를 3으로 변경해본다.</p>
<pre><code>replsets:
  rs0:
    name: rs0
    size: 3
---
helm upgrade cluster-1 charts/psmdb-db -f values/psmdb-db/values.yaml</code></pre><p>파드가 총 4개가 된 것을 확인할 수 있다.</p>
<pre><code>kubectl get pod                                    
NAME                              READY   STATUS    RESTARTS   AGE
cluster-1-psmdb-db-rs0-0          1/1     Running   0          88m
cluster-1-psmdb-db-rs0-1          1/1     Running   0          87m
cluster-1-psmdb-db-rs0-2          1/1     Running   0          102s
cluster-1-psmdb-db-rs0-hidden-0   1/1     Running   0          54m
psmdb-operator-7d9c58954c-x9dzd   1/1     Running   0          90m</code></pre><p>그리고 <code>rs.conf()</code>를 확인해보면, hidden이 전부 <code>false</code>인 것을 확인할 수 있다. 여기서 <code>rs.conf()</code>와 CR로 적용되는 값은 별도로 유지되는 것을 확인할 수 있다.</p>
<pre><code>rs0 [direct: primary] admin&gt; rs.conf().members
[
  {
    _id: 0,
    host: &#39;cluster-1-psmdb-db-rs0-0.cluster-1-psmdb-db-rs0.mongodb.svc.cluster.local:27017&#39;,
    arbiterOnly: false,
    buildIndexes: true,
    hidden: false,
    priority: 2,
    tags: {
      podName: &#39;cluster-1-psmdb-db-rs0-0&#39;,
      serviceName: &#39;cluster-1-psmdb-db&#39;,
      nodeName: &#39;1.32-worker&#39;
    },
    secondaryDelaySecs: Long(&#39;0&#39;),
    votes: 1
  },
  {
    _id: 1,
    host: &#39;cluster-1-psmdb-db-rs0-1.cluster-1-psmdb-db-rs0.mongodb.svc.cluster.local:27017&#39;,
    arbiterOnly: false,
    buildIndexes: true,
    hidden: false,
    priority: 2,
    tags: {
      serviceName: &#39;cluster-1-psmdb-db&#39;,
      nodeName: &#39;1.32-worker3&#39;,
      podName: &#39;cluster-1-psmdb-db-rs0-1&#39;
    },
    secondaryDelaySecs: Long(&#39;0&#39;),
    votes: 1
  },
  {
    _id: 2,
    host: &#39;cluster-1-psmdb-db-rs0-hidden-0.cluster-1-psmdb-db-rs0.mongodb.svc.cluster.local:27017&#39;,
    arbiterOnly: false,
    buildIndexes: true,
    hidden: false,
    priority: 0,
    tags: {
      serviceName: &#39;cluster-1-psmdb-db&#39;,
      hidden: &#39;true&#39;,
      nodeName: &#39;1.32-worker2&#39;,
      podName: &#39;cluster-1-psmdb-db-rs0-hidden-0&#39;
    },
    secondaryDelaySecs: Long(&#39;0&#39;),
    votes: 1
  },
  {
    _id: 3,
    host: &#39;cluster-1-psmdb-db-rs0-2.cluster-1-psmdb-db-rs0.mongodb.svc.cluster.local:27017&#39;,
    arbiterOnly: false,
    buildIndexes: true,
    hidden: false,
    priority: 0,
    tags: {
      serviceName: &#39;cluster-1-psmdb-db&#39;,
      nodeName: &#39;1.32-worker2&#39;,
      podName: &#39;cluster-1-psmdb-db-rs0-2&#39;
    },
    secondaryDelaySecs: Long(&#39;0&#39;),
    votes: 0
  }
]</code></pre><h1 id="정리-및-결론">정리 및 결론</h1>
<ul>
<li>쿠버네티스 환경의 특성을 이용해서 몽고디비도 스케일링이 가능할 것 같다.</li>
<li>쿠버네티스 환경에서 몽고디비를 직접 구성해서 관리하기는 어렵기에 Operator를 사용하면 간편하다.</li>
<li>Operator를 이용할 때, 오토스케일링을 할 경우 신규 secondary에 데이터 동기화가 안되었음에도 쿼리가 성공하는 이슈가 있었다.</li>
<li>psmdb-operator 1.21부터 hidden 노드를 추가할 수 있게 되었다.</li>
<li>hidden 노드를 추가한 뒤, 일정 시간 뒤에 <code>rs.conf()</code>, <code>rs.reconfig()</code>를 이용해 hidden 값을 <code>false</code>로 변경해주면 데이터 동기화 이슈를 방지할 수 있다.</li>
<li>hidden 멤버 외에도 <code>non-voting</code> 멤버도 존재한다. (예전에는 레플리카셋 3개 노드만 pvc를 붙이고, 오토스케일링으로 스케일아웃되는 파드는 pvc없이 로컬 volume을 사용하는 <code>non-voting</code>으로 진행했었다.)</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[tempo-cli로 trace size 크기 확인하기]]></title>
            <link>https://velog.io/@kyeongjun-dev/tempo-cli%EB%A1%9C-trace-size-%ED%81%AC%EA%B8%B0-%ED%99%95%EC%9D%B8%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@kyeongjun-dev/tempo-cli%EB%A1%9C-trace-size-%ED%81%AC%EA%B8%B0-%ED%99%95%EC%9D%B8%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 20 Mar 2025 14:20:40 GMT</pubDate>
            <description><![CDATA[<h1 id="tempo-cli-사용해보기">tempo-cli 사용해보기</h1>
<h2 id="참고사이트">참고사이트</h2>
<ul>
<li>tempo cli 사용법 docs : <a href="https://grafana.com/docs/tempo/latest/operations/tempo_cli/">https://grafana.com/docs/tempo/latest/operations/tempo_cli/</a></li>
<li>tempo cli 바이너리 파일 다운로드하는 release 페이지 : <a href="https://github.com/grafana/tempo/releases/">https://github.com/grafana/tempo/releases/</a><h2 id="개요">개요</h2>
tempo에서 크기가 큰 trace를 검색하면, <code>max_bytes_per_trace</code> 크기를 초과하는 경우 <code>trace exceeds max size</code>에러로 검색이 불가능합니다
무작정 <code>max_bytes_per_trace</code>를 올리는 것보다는 적절하게 값을 조정해주는게 좋기에 trace 크기를 tempo-cli를 이용해 확인합니다</li>
</ul>
<h2 id="설치-및-trace-id-확인">설치 및 trace id 확인</h2>
<p><a href="https://github.com/kyeongjun-dev/monitoring/tree/feat/docker-compose_tempo-cli/example/docker-compose-springboot-native-histogram-local_tempo-cli">https://github.com/kyeongjun-dev/monitoring/tree/feat/docker-compose_tempo-cli/example/docker-compose-springboot-native-histogram-local_tempo-cli</a>
repo를 clone하고 위 디렉토리로 이동 후, <code>docker-compose up -d</code> 명령어를 실행한 뒤에 루트디렉토리로 이동하여 <code>sh curl-springboot-delay.sh 8080</code>로 springboot 앱에 trace를 발생시킵니다</p>
<p><code>localhost:3000</code> 그라파나로 접속하여 trace id를 임의로 확인합니다 (여기서는 <code>3e5d8cc884b51d1b488a821fb7f3b29</code> 사용)
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/ed9dc4ae-6584-4624-b288-1e0956519133/image.png" alt=""></p>
<h2 id="tempo-cli-실행">tempo-cli 실행</h2>
<p><code>docker-compose up -d</code> 명령어를 수행한 디렉토리에서 아래 아래 사진에 표시된 (1)번과 같이 <code>tempo-data</code> 디렉토리를 확인할 수 있습니다
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/d0610039-6829-41a2-91dc-60a4632f6544/image.png" alt=""></p>
<p>clone한 root 디렉토리에 <code>tempo-cli</code> 파일이 있는데, 해당 파일(amd64)을 이용하여 tempo-cli를 실행할 수 있습니다</p>
<blockquote>
<p>arm 등 arch가 맞지 않는겨우, tempo github의 release 페이지에서 알맞은 tar 파일을 받아서 압축을 해제하면, <code>tempo-cli</code> 파일이 들어있습니다
<a href="https://github.com/grafana/tempo/releases/tag/v2.7.1">https://github.com/grafana/tempo/releases/tag/v2.7.1</a></p>
</blockquote>
<pre><code>./tempo-cli query trace-summary 3e5d8cc884b51d1b488a821fb7f3b29 single-tenant --backend=local --bucket=./example/docker-compose-springboot-native-histogram-local_tempo-cli/tempo-data/blocks/</code></pre><p>루트 디렉토리를 기준으로 <code>./tempo-cli query trace-summary</code> 명령어를 실행합니다</p>
<ul>
<li>325d~ : trace id</li>
<li>single-tenant : <code>tempo-data/blocks/single-tenant</code> 디렉토리 이름</li>
<li>backend : local (local, s3, gcs 등 설정 가능)</li>
<li>bucket : 검색할 디렉토리 위치</li>
</ul>
<p>실행결과는 아래와 같습니다 : 탐색한 block 개수, trace에 포함된 Span 개수, trcace 크기 등</p>
<pre><code>total blocks to search:  8
....0....Number of blocks: 2 
Span count: 2 
Trace size: 2944 B 
Trace duration: 5 seconds 
Root service name: demo 
Root span info:
(*v1.Span)(0xc0004864b0)({
 TraceId: ([]uint8) (len=16 cap=16) {
  00000000  03 e5 d8 cc 88 4b 51 d1  b4 88 a8 21 fb 7f 3b 29  |.....KQ....!..;)|
 },
 SpanId: ([]uint8) (len=8 cap=8) {
  00000000  5d df c3 c6 d7 de f2 f2                           |].......|
 },
 TraceState: (string) &quot;&quot;,
 ParentSpanId: ([]uint8) {
 },
 Flags: (uint32) 0,
 Name: (string) (len=10) &quot;GET /delay&quot;,
 Kind: (v1.Span_SpanKind) 2,
 StartTimeUnixNano: (uint64) 1742478694709420660,
 EndTimeUnixNano: (uint64) 1742478699710892523,
 Attributes: ([]*v1.KeyValue) (len=15 cap=16) {
  (*v1.KeyValue)(0xc00079d4d0)({
   Key: (string) (len=10) &quot;http.route&quot;,
   Value: (*v1.AnyValue)(0xc000893650)({
    Value: (*v1.AnyValue_StringValue)(0xc000893660)({
     StringValue: (string) (len=6) &quot;/delay&quot;
    })
   })
  }),
(생략)
DroppedAttributesCount: (uint32) 0,
 Events: ([]*v1.Span_Event) &lt;nil&gt;,
 DroppedEventsCount: (uint32) 0,
 Links: ([]*v1.Span_Link) &lt;nil&gt;,
 DroppedLinksCount: (uint32) 0,
 Status: (*v1.Status)(0xc00079d650)({
  Message: (string) &quot;&quot;,
  Code: (v1.Status_StatusCode) 0
 })
})
top frequent service.names: 
[demo    ]</code></pre><h2 id="마치며">마치며</h2>
<p><a href="https://grafana.com/docs/tempo/latest/operations/tempo_cli/">https://grafana.com/docs/tempo/latest/operations/tempo_cli/</a>
위 링크를 참고하면 다양한 tempo-cli 기능을 확인할 수 있습니다
s3에 tempo 데이터가 저장되는 경우, 옵션만 다르게하여 검색이 가능합니다
(minio를 docker-compose로 구성해서 tempo cli를 사용하면, <code>server gave HTTP response to HTTPS client</code> 에러가 발생하는데, tempo cli 자체가 https로 요청을 보내서 그런것으로 생각되지만 보통은 s3에 저장을 할 것이기에... 깊이 파보지는 않았습니다)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Tempo metrics-generator로 java 앱 RED metrics (Native Histogram) 수집하기]]></title>
            <link>https://velog.io/@kyeongjun-dev/Tempo-metrics-generator%EB%A1%9C-java-%EC%95%B1-RED-metrics-Native-Histogram-%EC%88%98%EC%A7%91%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@kyeongjun-dev/Tempo-metrics-generator%EB%A1%9C-java-%EC%95%B1-RED-metrics-Native-Histogram-%EC%88%98%EC%A7%91%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 19 Feb 2025 14:15:49 GMT</pubDate>
            <description><![CDATA[<h1 id="목차">목차</h1>
<p>해당 글에서는 java의 Span 데이터를 Tempo로 수집해서, Tempo의 metrics-generator로 Metrics 데이터로 변환 후 Prometheus로 전송하는 방법을 다룹니다
여기서 Prometheus로 전송하는 Metrics 데이터는 Native Histogram 입니다
구성도는 아래와 같습니다
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/da8c7241-7a42-4c38-89e2-632997d1511a/image.png" alt=""></p>
<h1 id="repo-clone">repo clone</h1>
<p>레포에 있는 helm chart와 작성된 helmfile을 주로 이용합니다</p>
<pre><code>git clone https://github.com/kyeongjun-dev/monitoring.git
cd monitoring/example/docker-compose-springboot-native-histogram</code></pre><h1 id="docker-compose-실행">docker-compose 실행</h1>
<p><code>docker-compose.yaml</code> 파일을 이용하여 컨테이너를 실행합니다</p>
<pre><code>docker-compose up -d</code></pre><p>가끔 wsl 환경에서 아래와 같이 port를 사용할 수 없다고 뜨는데,</p>
<pre><code>Error response from daemon: Ports are not available: exposing port TCP 0.0.0.0:3000 -&gt; 127.0.0.1:0: listen tcp 0.0.0.0:3000: bind: An attempt was made to access a socket in a way forbidden by its access permissions.</code></pre><p>다음 링크를 참고하여 port를 해제할 수 있습니다 : <a href="https://not-to-be-reset.tistory.com/638">https://not-to-be-reset.tistory.com/638</a></p>
<p>실행되는 컨테이너는 tempo, springboot, grafana, prometheus, opentelemetry-collector 총 5개 입니다 - 중간의 tempo가 하나 더 있는건 init 컨테이너 입니다</p>
<pre><code> docker ps -a | grep docker-compose
c175a5ed0b26   grafana/tempo:2.7.1                                                &quot;/tempo -config.file…&quot;   2 minutes ago   Up 2 minutes               0.0.0.0:4675-&gt;3200/tcp, 0.0.0.0:4676-&gt;4317/tcp   docker-compose-springboot-native-histogram-tempo-1
6cff2baaf180   kyeongjundev/springboot:39e2e243b5ddd14f47c35a91f00c068aef13b6ab   &quot;java -javaagent:ope…&quot;   2 minutes ago   Up 2 minutes               0.0.0.0:8080-&gt;8080/tcp                           docker-compose-springboot-native-histogram-springboot-1
80d8a37fdab1   grafana/grafana:11.0.0                                             &quot;/run.sh&quot;                2 minutes ago   Up 2 minutes               0.0.0.0:3000-&gt;3000/tcp                           docker-compose-springboot-native-histogram-grafana-1
5bf01910095e   grafana/tempo:2.7.1                                                &quot;chown 10001:10001 /…&quot;   2 minutes ago   Exited (0) 2 minutes ago                                                    docker-compose-springboot-native-histogram-init-1
5b666c812370   prom/prometheus:v3.1.0                                             &quot;/bin/prometheus --c…&quot;   2 minutes ago   Up 2 minutes               0.0.0.0:9090-&gt;9090/tcp                           docker-compose-springboot-native-histogram-prometheus-1
1248a61fe2fb   otel/opentelemetry-collector:0.86.0                                &quot;/otelcol --config=/…&quot;   2 minutes ago   Up 2 minutes               4317/tcp, 55678-55679/tcp                        docker-compose-springboot-native-histogram-otel-collector-1</code></pre><h1 id="설정파일-확인">설정파일 확인</h1>
<h2 id="tempo">tempo</h2>
<p><code>tempo.yaml</code> 파일에 아래와 같이 metrics-generator를 활성화 했습니다
여기서 중요한 부분은 <code>span-metrics</code> 입니다. 해당 processor가 span 데이터를 metrics 데이터로 변환합니다
<code>generate_native_histograms</code>에는 [native, classic, both] 가 설정 가능합니다</p>
<pre><code>overrides:
  defaults:
    metrics_generator:
      processors: [service-graphs, span-metrics, local-blocks] # enables metrics generator
      generate_native_histograms: native</code></pre><h2 id="prometheus">prometheus</h2>
<p><code>prometheus.yaml</code> 파일에서는 필수 설정은 아니지만 prometheus와 tempo의 metrics 데이터를 수집하도록 설정 했습니다.</p>
<pre><code>global:
  scrape_interval:     15s
  evaluation_interval: 15s

scrape_configs:
  - job_name: &#39;prometheus&#39;
    static_configs:
      - targets: [ &#39;localhost:9090&#39; ]
  - job_name: &#39;tempo&#39;
    static_configs:
      - targets: [ &#39;tempo:3200&#39; ]</code></pre><p><code>docker-compose.yaml</code> 파일의 명령어에서 prometheus의 remote write, native histogram 기능을 활성화 했습니다 - <code>prometheus.yaml</code> 파일에서 활성화해도 됩니다</p>
<pre><code>...
  prometheus:
    image: prom/prometheus:v3.1.0
    command:
      - --config.file=/etc/prometheus.yaml
      - --web.enable-remote-write-receiver
      - --enable-feature=exemplar-storage
      - --enable-feature=native-histograms
    volumes:
      - ./prometheus.yaml:/etc/prometheus.yaml
    ports:
      - &quot;9090:9090&quot;
...</code></pre><h2 id="otel-collector">otel collector</h2>
<p><code>otel-collector.yaml</code>에서는 최소한의 설정으로 springboot에서 전달받은 trace를 tempo로 전송하도록 설정했습니다
exporters에 logging을 설정하면, 실제 trace가 어떻게 전달되는지 otel-collector 컨테이너 로그로 확인할 수 있습니다</p>
<pre><code>receivers:
  otlp:
    protocols:
      grpc:
exporters:
  otlp:
    endpoint: tempo:4317
    tls:
      insecure: true
  logging:
      verbosity: detailed
service:
  pipelines:
    traces:
      receivers: [otlp]
      exporters: [otlp, logging]</code></pre><h1 id="트래픽-생성-및-prometheus-확인">트래픽 생성 및 prometheus 확인</h1>
<p>웹 브라우저에서 아래 endpoint의 8080 포트로 직접 접속해도 되고, clone한 repo의 루트에 있는 <code>sh curl-springboot-delay.sh</code> 스크립트를 실행해도 됩니다</p>
<pre><code>localhost:8080
localhost:8080/ip
localhost:8080/delay?delay=1

sh curl-springboot-delay.sh 8080</code></pre><p>prometheus의 NodePort 30080으로 접속하여 쿼리를 실행합니다</p>
<pre><code>traces_spanmetrics_latency</code></pre><p>아래와 같이 native-histogram으로 수집된 metrics 데이터를 확인할 수 있습니다
<img src="https://velog.velcdn.com/images/kyeongjun-dev/post/5b6c0d67-9512-4f7b-8638-4b2ac4690bc5/image.png" alt=""></p>
]]></description>
        </item>
    </channel>
</rss>