<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Tae._.goo</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Wed, 19 Aug 2026 14:12:12 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>Tae._.goo</title>
            <url>https://velog.velcdn.com/images/taekyu_/profile/5ba3e490-7331-4209-b85d-8ff71a4e27c0/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. Tae._.goo. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/taekyu_" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[K8s에서 GPU 쪼개기: Time Slicing에서 HAMi로]]></title>
            <link>https://velog.io/@taekyu_/K8s%EC%97%90%EC%84%9C-GPU-%EC%AA%BC%EA%B0%9C%EA%B8%B0-Time-Slicing%EC%97%90%EC%84%9C-HAMi%EB%A1%9C-hbbvh1nk</link>
            <guid>https://velog.io/@taekyu_/K8s%EC%97%90%EC%84%9C-GPU-%EC%AA%BC%EA%B0%9C%EA%B8%B0-Time-Slicing%EC%97%90%EC%84%9C-HAMi%EB%A1%9C-hbbvh1nk</guid>
            <pubDate>Wed, 19 Aug 2026 14:12:12 GMT</pubDate>
            <description><![CDATA[<hr>

<h2 id="span-stylebackground-color-mediumaquamarine1-기존-방식의-문제span"><span style='background-color: MediumAquamarine'>1. 기존 방식의 문제</span></h2>
<hr>

<h3 id="span-stylecolor-slateblue1-1-기존-구성-time-slicing--소프트웨어-vram-제한-주입span"><span style="color: SlateBlue">1-1. 기존 구성: Time Slicing + 소프트웨어 VRAM 제한 주입</span></h3>
<p>학과 GPU 클러스터는 RTX 4070 Ti 4대 + RTX 4090 2대를 NVIDIA GPU Operator로 묶어, 학생들에게 JupyterHub 기반 딥러닝 실습 환경으로 서비스하고 있다.</p>
<p>GPU는 노드당 1장인데 학생은 그보다 많다. 그래서 GPU 하나를 여러 명이 나눠 쓰게 해야 하고, 지금까지는 Time Slicing으로 해결해왔다.</p>
<pre><code class="language-bash">$ kubectl get cm -n nvidia-gpu-operator time-slicing-config -o jsonpath=&#39;{.data}&#39;
{&quot;any&quot;:&quot;version: v1
flags:
  migStrategy: none
sharing:
  timeSlicing:
    resources:
    - name: nvidia.com/gpu
      replicas: 3&quot;}</code></pre>
<p><code>replicas: 3</code>  -&gt; Time Slicing을 사용하여, 물리 GPU 1장을 쿠버네티스에 3장으로 뻥튀기해서 보고한다.</p>
<pre><code class="language-bash">$ kubectl get nodes -o custom-columns=&quot;NODE:.metadata.name,GPU:.status.allocatable.nvidia\.com/gpu&quot;
NODE     GPU
gpu-01   3
gpu-02   3
gpu-03   3
gpu-04   3</code></pre>
<p>이렇게 하면 <code>nvidia.com/gpu: 1</code>을 요청하는 파드 3개가 같은 물리 GPU에 스케줄될 수 있다.
<br></p>
<p>그리고 여기에 내가 추가적으로 주입해놓은 소프트웨어 VRAM 제한이 한 겹 더 있다. 
JupyterHub 배포 레포의 학생 세션 프로필을 보면, </p>
<pre><code class="language-yaml"># jupyterhub/values.yaml — 학생 세션 프로필 (HAMi 전환 전)
- display_name: &quot;GPU Server (RTX 4070 Ti)&quot;
  description: &quot;딥러닝 GPU 서버 (GPU 메모리 4GB 제한)&quot;
  kubespawner_override:
    extra_resource_guarantees:
      nvidia.com/gpu: 1              # Time Slicing으로 뻥튀기된 GPU 1개
    node_selector:
      gpu-type: &quot;rtx4070ti&quot;
    environment:
      GPU_MEMORY_LIMIT_MB: &quot;3600&quot;    # 제한값은 환경변수로 전달
    lifecycle_hooks:
      postStart:                     # 컨테이너 시작 직후 제한 스크립트 실행
        exec:
          command:
            - /bin/bash
            - -c
            - |
              cp /mnt/gpu-memory-script/gpu_memory_limit.sh /tmp/gpu_memory_limit.sh
              chmod +x /tmp/gpu_memory_limit.sh
              /tmp/gpu_memory_limit.sh
    extra_container_config:
      livenessProbe:                 # 제한 파일이 지워지면 컨테이너 재시작
        exec:
          command:
            - /bin/bash
            - -c
            - test -f /home/jovyan/.ipython/profile_default/startup/00-gpu-memory-limit.py</code></pre>
<p>세션 파드가 뜨면 postStart 훅이 제한 스크립트를 실행하고, livenessProbe가 제한 파일이 살아있는지를 5분마다 감시한다. 학생이 파일을 지우면 컨테이너를 재시작시키는 것까지 해놨다.</p>
<p>그리고, 해당 제한 스크립트의 핵심부는 이렇다.</p>
<pre><code class="language-python"># jupyterhub/scripts/gpu_memory_limit.sh (핵심만 발췌)

# IPython startup 스크립트 생성 
cat &gt; /home/jovyan/.ipython/profile_default/startup/00-gpu-memory-limit.py &lt;&lt; EOF

# TensorFlow VRAM 하드 리밋 적용
tf.config.set_logical_device_configuration(
    gpu, [tf.config.LogicalDeviceConfiguration(memory_limit=GPU_MEMORY_LIMIT_MB)]
)
# Monkey Patch to prevent confusion/retries
tf.config.set_logical_device_configuration = locked_set_logical_device_configuration

# PyTorch VRAM 소프트 리밋 적용
fraction = min(GPU_MEMORY_LIMIT_MB / total_memory, 1.0)
torch.cuda.set_per_process_memory_fraction(fraction, 0)

# Prevent overriding the limit via Monkey Patching
def locked_set_memory_fraction(*args, **kwargs):
    print(f&quot;⚠️  GPU Memory Limit is strictly locked by administrator to {GPU_MEMORY_LIMIT_MB}MB.&quot;)
torch.cuda.set_per_process_memory_fraction = locked_set_memory_fraction
EOF</code></pre>
<p>ipython startup 디렉토리에 파이썬 스크립트를 심어서, 커널이 뜰 때마다 TensorFlow는 logical device 설정으로, PyTorch는 <strong><code>set_per_process_memory_fraction</code></strong>으로 상한을 걸고, 학생이 그 함수를 다시 호출해 풀지 못하도록 몽키패치까지 해두었다.</p>
<p>정리하면: <span style="color: indianred"><strong>스케줄링은 Time Slicing이 뻥튀기로, VRAM 제한은 컨테이너 안의 스크립트가 프레임워크 설정으로 1년 넘게 이 구조로 서비스했다.</strong></span></p>
<hr>
<h3 id="span-stylecolor-slateblue1-2-time-slicing의-한계-시간은-쪼개지만-격리가-없다span"><span style="color: SlateBlue">1-2. Time Slicing의 한계: 시간은 쪼개지만 격리가 없다</span></h3>
<p>Time Slicing은 이름 그대로 시간만 쪼갠다. GPU의 컨텍스트 스위칭으로 여러 프로세스가 번갈아 연산하는 것뿐이고, 격리라는 개념 자체가 없다.</p>
<p><img src="https://velog.velcdn.com/images/taekyu_/post/cb471a25-f87e-4343-94b3-0106caef893b/image.png" alt=""></p>
<ul>
<li><p><span style="color: indianred"><strong>VRAM 격리 없음</strong> </span></p>
<ul>
<li><p>파드 3개가 같은 GPU에 올라가도 각자에게 보이는 건 12GB 전부다. </p>
</li>
<li><p>한 명이 12GB를 다 잡으면 나머지 둘은 다음 할당에서 CUDA OOM을 맞는다.</p>
</li>
<li><p>누가 먼저 할당하느냐의 경쟁이다.</p>
</li>
</ul>
</li>
<li><p><span style="color: indianred"><strong>연산 격리 없음</strong> </span></p>
<ul>
<li><p>한 명이 무거운 학습을 돌리면 나머지의 연산이 그만큼 밀린다. </p>
</li>
<li><p>보장된 몫이라는 게 없다.</p>
</li>
</ul>
</li>
<li><p><span style="color: indianred"><strong>장애 전파</strong></span></p>
<ul>
<li><p>GPU 자체가 리셋되는 하드웨어 오류 (Xid 등)는 같은 카드 위 세션 전부에 영향을 준다.</p>
</li>
<li><p>해당 문제는 하드웨어를 물리적으로 나누는 MIG 말고는 어느 방식도 막지 못한다.</p>
<br>

</li>
</ul>
</li>
</ul>
<p>쿠버네티스 입장에서는 <code>nvidia.com/gpu: 1</code>이 할당된 정상 상태인데, 실제로는 동일한 GPU를 사용하고 있는 옆 사람 코드에 따라 내 세션이 죽는 환경이다. 실습 시간에 &quot;제 코드는 맞는데 CUDA OOM이 떠요&quot;가 나오면, 코드 문제인지 옆 세션 문제인지 구분할 방법이 없다.</p>
<p>*<em>이 중 제일 자주 터지는 VRAM 경쟁만이라도 막아보려고 넣은 게 1-1 섹션에서 제시한 스크립트로, 세션 시작 시 VRAM 제한 주입이다. *</em></p>
<p>하지만 이 방식에는 두 가지 문제가 있다.</p>
<blockquote>
<p><strong>1. 주입된 제한 자체가 강제가 아니라는 것 <br>
2. 연산 간섭과 장애 전파는 VRAM 제한과 무관하게 발생하므로, 컨테이너 안에서 뭘 주입하든 근본적으로 해결할 수 없다는 것</strong></p>
</blockquote>
<hr>
<h3 id="span-stylecolor-slateblue1-3-소프트웨어-제한의-한계-부탁이지-강제가-아니다span"><span style="color: SlateBlue">1-3. 소프트웨어 제한의 한계: 부탁이지 강제가 아니다</span></h3>
<p><strong>주입된 VRAM 제한은 GPU가 아니라 사용자 컨테이너 안에서, 프레임워크 설정으로 걸린다.</strong></p>
<ul>
<li><p>PyTorch의 <code>set_per_process_memory_fraction</code>은 PyTorch의 캐싱 할당자에만 적용된다.</p>
<ul>
<li>PyTorch를 안 거치는 할당인 직접 CUDA 호출, 다른 프레임워크, ipython startup을 안 타는 실행 경로은 그냥 통과되게 된다.</li>
</ul>
</li>
<li><p>몽키패치로 재설정을 막아놨지만, 이것도 결국 같은 컨테이너 안에서 파이썬 레벨로 막는 것이다. </p>
<ul>
<li>새 커널을 다른 방식으로 띄우면 그만이다.<br>


</li>
</ul>
</li>
</ul>
<p>*<em>즉, 지금 구조는 지켜주면 고마운 부탁이지 강제가 아니다. *</em></p>
<p>잘 돌아간 건 학생들이 굳이 우회하지 않았기 때문이지, 우회할 수 없었기 때문이 아니다.</p>
<p><span style="color: indianred"><strong>그리고 두 번째 문제는 연산 간섭과 장애 전파는 이 스크립트가 어떻게 진화하든 못 막는다.</strong></span></p>
<p>제한을 아무리 정교하게 주입해도, 같은 GPU 위의 세션들이 서로 간섭하는 건 컨테이너 바깥에 있는 GPU 레벨의 일이기 때문이다. </p>
<p><strong>결론적으로, 제한은 사용자 컨테이너 안이 아니라 밖에서 걸어야 한다.</strong></p>
<hr>
<h3 id="span-stylecolor-slateblue-1-4-대안-비교-mig--mps--time-slicing--hamispan"><span style="color: SlateBlue"> 1-4. 대안 비교: MIG / MPS / Time Slicing / HAMi</span></h3>
<p>그래서 GPU 공유 방식 자체를 갈아타기로 했다. 선택지를 놓고 비교하면 이렇다.</p>
<table>
<thead>
<tr>
<th></th>
<th>Time Slicing</th>
<th>MPS</th>
<th>MIG</th>
<th>HAMi (vGPU)</th>
</tr>
</thead>
<tbody><tr>
<td>방식</td>
<td>시간 분할</td>
<td>CUDA 컨텍스트 공유</td>
<td>하드웨어 물리 분할</td>
<td>CUDA 호출 인터셉트</td>
</tr>
<tr>
<td>VRAM 격리</td>
<td>X</td>
<td>△ 제한적</td>
<td>O (하드웨어 수준)</td>
<td>O (하드 리밋)</td>
</tr>
<tr>
<td>연산 격리</td>
<td>X</td>
<td>△ 비율 지정 가능</td>
<td>O (하드웨어 수준)</td>
<td>△ (SW 비율 조절)</td>
</tr>
<tr>
<td>장애 격리</td>
<td>△ (프로세스 수준)</td>
<td>X 클라이언트 간 전파</td>
<td>O</td>
<td>△ (프로세스 수준)</td>
</tr>
<tr>
<td>지원 GPU</td>
<td>전부</td>
<td>전부</td>
<td><strong>A100/H100 등 데이터센터 전용</strong></td>
<td>전부</td>
</tr>
<tr>
<td>비용</td>
<td>0</td>
<td>0</td>
<td>GPU 가격</td>
<td>0 (오픈소스)</td>
</tr>
</tbody></table>
<ul>
<li><p><span style="color: indianred"><strong>MIG</strong></span>:  하드웨어를 물리적으로 쪼개기 때문에, 격리 수준은 제일 좋다. 하지만 A100/H100 같은 데이터센터 GPU 전용 기능이다. <strong>RTX 4070 Ti와 4090은 MIG를 지원하지 않는다.</strong> </p>
</li>
<li><p><span style="color: indianred"><strong>MPS</strong></span>: 여러 프로세스의 커널을 병렬로 섞어 돌릴 수 있고 연산 비율 지정도 되지만, 메모리 격리가 약하고 한 클라이언트의 비정상 종료가 같은 GPU의 다른 클라이언트에 전파된다. 신뢰할 수 있는 워크로드끼리 쓰는 물건이지, 학생들이 아무 코드나 돌리는 멀티테넌트 환경용이 아니다.</p>
</li>
<li><p><span style="color: indianred"><strong>Time Slicing</strong></span>: 지금까지 쓴 것. </p>
</li>
<li><p><span style="color: indianred"><strong>HAMi</strong></span>: 소프트웨어(CUDA 호출 인터셉트) 방식이라 컨슈머 GPU에서도 동작하고, VRAM과 연산 비율을 컨테이너 밖에서 강제한다. 사용자 컨테이너 안의 설정이 아니므로 프레임워크와 무관하고 우회도 안 된다.</p>
</li>
</ul>
<p>Consumer GPU라는 하드웨어 제약에서 MIG가 빠지고, 멀티테넌시 요구에서 MPS가 빠지면 남는 건 HAMi다. 소거법으로도 답이고, 요구사항(VRAM 하드 리밋 + 연산 비율 보장)으로도 답이다.</p>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine2-hami란span"><span style='background-color: MediumAquamarine'>2. HAMi란?</span></h2>
<hr>

<h3 id="span-stylecolor-slateblue2-1-정의span"><span style="color: SlateBlue">2-1. 정의</span></h3>
<p><strong>HAMi(Heterogeneous AI Computing Virtualization Middleware)</strong>는 쿠버네티스에서 GPU를 비롯한 이기종 연산 장치를 가상화해서 나눠 쓰게 해주는 오픈소스 미들웨어다. </p>
<p>CNCF Incubating 프로젝트로, 예전 이름인 k8s-vGPU-scheduler로도 알려져 있다. </p>
<p><span style="color: indianred"><strong>HAMi는 물리 GPU 1장을 여러 개의 vGPU로 쪼개고, 각 vGPU에 VRAM 용량과 연산 비율을 하드 리밋으로 걸 수 있게 해준다.</strong> </span></p>
<p><img src="https://velog.velcdn.com/images/taekyu_/post/5b2f4e4f-67c4-4b34-886f-7a077e345217/image.png" alt=""></p>
<p>MIG처럼 하드웨어 지원이 필요한 게 아니라 순수 소프트웨어 방식이라, 우리처럼 Consumer GPU(RTX 4070 Ti/4090)를 쓰는 클러스터에서도 동작한다.</p>
<p>파드 입장에서의 사용법은 기존과 거의 같고, 리소스 요청에 몇 줄 추가만 하면 된다.</p>
<pre><code class="language-yaml">resources:
  limits:
    nvidia.com/gpu: 1                # vGPU 1개
    nvidia.com/gpumem-percentage: 50 # 물리 VRAM의 50%
    nvidia.com/gpucores: 50          # 연산(SM)의 50%</code></pre>
<p>*<em>VRAM은 <code>gpumem</code>(절대값 MB)이나 <code>gpumem-percentage</code>(물리 VRAM 대비 비율) 중 하나로 지정할 수 있다. *</em></p>
<p>왜 우리가 percentage를 쓰게 됐는지는 트러블슈팅에서 다룬다.</p>
<hr>
<h3 id="span-stylecolor-slateblue2-2-동작-원리-libvgpuso로-cuda-호출을-가로챈다span"><span style="color: SlateBlue">2-2. 동작 원리: libvgpu.so로 CUDA 호출을 가로챈다</span></h3>
<p>HAMi의 핵심은 HAMi-core라고 부르는 <strong><code>libvgpu.so</code></strong> 라이브러리다. </p>
<p>애플리케이션이 GPU를 쓸 때의 호출 경로는 이렇다.</p>
<p><img src="https://velog.velcdn.com/images/taekyu_/post/c2b6827b-8890-4555-998f-93a23bbcf360/image.png" alt=""></p>
<p>HAMi는 컨테이너가 시작될 때 <code>libvgpu.so</code>를 컨테이너 안에 마운트하고 <code>LD_PRELOAD</code>로 끼워넣는다. 이후 컨테이너 안에서 일어나는 모든 CUDA Driver API 호출과 NVML 호출은 <code>libvgpu.so</code>를 먼저 거친다.</p>
<p>이 인터셉트 레이어에서 두 가지 제한이 걸린다.</p>
<p><span style="color: indianred"><strong>1. VRAM 하드 리밋</strong> </span></p>
<ul>
<li><p><code>cuMemAlloc</code> 같은 메모리 할당 호출을 가로채서 컨테이너별 누적 사용량을 추적한다. </p>
</li>
<li><p>할당 요청이 한도를 넘으면 호출 자체를 CUDA OOM으로 실패시킨다. </p>
</li>
<li><p>어떤 프레임워크를 쓰든, 프레임워크 없이 CUDA를 직접 호출하든, 결국 이 경로를 지나므로 우회가 안 된다. </p>
</li>
<li><p>NVML 조회도 가로채기 때문에 컨테이너 안에서 <code>nvidia-smi</code>를 치면 물리 VRAM이 아니라 할당된 한도가 전체 용량으로 보인다.</p>
</li>
<li><p>결국에는, 사용자 입장에선 그냥 작은 GPU 1장이다.</p>
<br>

</li>
</ul>
<p><span style="color: indianred"><strong>2. 연산 비율 리밋</strong> </span></p>
<ul>
<li><p>GPU 사용률(SM utilization)을 주기적으로 모니터링하면서, 컨테이너가 지정된 비율(<code>gpucores</code>)을 초과해서 쓰고 있으면 커널 런치를 지연시켜 사용률을 한도 안으로 밀어넣는다. </p>
</li>
<li><p>Time Slicing처럼 알아서 나눠 갖길 기대하는 게 아니라, 비율을 초과하는 쪽을 실제로 억제한다.</p>
</li>
<li><p>다만 메모리와 달리 연산 제한은 사후 조절이다. GPU 자체는 여전히 프로세스 간 Time-Slicing으로 돌고, HAMi는 사용률을 주기적으로 재서 비율을 넘긴 쪽의 커널 런치를 늦추는 방식이라, 옆 세션이 놀고 있으면 지정 비율 이상을 쓰기도 한다. 할당 시점에 확정적으로 잘리는 메모리 제한보다는 약한 보장이다.</p>
<br>

</li>
</ul>
<p>따라서, 1장에서 내가 구축해서 사용했던 소프트웨어 제한과 비교하게 되면 이러하다.</p>
<table>
<thead>
<tr>
<th></th>
<th>기존 (스크립트 주입)</th>
<th>HAMi (libvgpu.so)</th>
</tr>
</thead>
<tbody><tr>
<td>제한이 걸리는 위치</td>
<td>프레임워크 설정 (파이썬 레벨)</td>
<td>CUDA Driver API (라이브러리 레벨)</td>
</tr>
<tr>
<td>프레임워크 의존</td>
<td>PyTorch/TF 각각 처리</td>
<td>무관 (모든 CUDA 호출이 지나감)</td>
</tr>
<tr>
<td>우회 가능성</td>
<td>ipython 안 타면 끝</td>
<td>LD_PRELOAD 아래로 내려가지 않는 한 불가</td>
</tr>
<tr>
<td>관리 주체</td>
<td>사용자 컨테이너 안</td>
<td>클러스터 인프라</td>
</tr>
</tbody></table>
<hr>
<h3 id="span-stylecolor-slateblue2-3-구성-컴포넌트span"><span style="color: SlateBlue">2-3. 구성 컴포넌트</span></h3>
<p>HAMi를 설치하면 크게 세 개의 컴포넌트가 클러스터에 추가되고, 이들이 파드 생성부터 실행까지 각 단계에 개입한다.</p>
<p><img src="https://velog.velcdn.com/images/taekyu_/post/94efe866-4543-43e2-b5d5-a0e1f785bd2c/image.png" alt=""></p>
<p><span style="color: indianred"><strong>1. hami-device-plugin (DaemonSet, GPU 노드마다)</strong></span></p>
<ul>
<li><p>기존 NVIDIA device plugin을 대체한다.</p>
</li>
<li><p>노드의 물리 GPU를 <code>deviceSplitCount</code> 배수만큼의 vGPU로 등록하고, GPU의 UUID/VRAM/코어 정보를 노드 어노테이션(<code>hami.io/node-nvidia-register</code>)으로 보고한다.</p>
</li>
<li><p>스케줄러는 이 어노테이션을 보고 각 GPU에 남은 VRAM과 코어가 얼마인지 판단한다.</p>
</li>
</ul>
<p><span style="color: indianred"><strong>2. hami-scheduler (Deployment)</strong></span></p>
<ul>
<li><p>kube-scheduler의 extender로 동작한다. </p>
</li>
<li><p>HAMi 리소스를 요청한 파드의 스케줄링 때 Filter/Score 단계에 개입해서, <strong>노드 안의 어느 물리 GPU에 배치할지까지 결정한다.</strong> </p>
</li>
<li><p>기본 스케줄러는 노드에 GPU가 남았는가만 보지만, hami-scheduler는 이 GPU에 VRAM 50%를 더 얹을 수 있는가를 본다. 이로인해, <strong>GPU별 잔여량 기반 스케줄링이 가능해진다.</strong></p>
</li>
</ul>
<p><span style="color: indianred"><strong>3. mutating webhook</strong></span></p>
<ul>
<li><p>파드 생성 요청을 가로채는 admission webhook이다. </p>
</li>
<li><p>리소스에 <code>nvidia.com/gpu</code>가 있는 파드를 발견하면 <code>schedulerName</code>을 <code>hami-scheduler</code>로 바꿔치기한다. </p>
</li>
<li><p>사용자(JupyterHub 같은 상위 시스템)는 스케줄러를 지정할 필요 없이, 평소처럼 파드를 만들면 자동으로 HAMi 경로를 타게 된다.</p>
</li>
<li><p>해당 Webhook은 hami-scheduler 파드안에 속하게 된다.</p>
</li>
</ul>
<p><br>여기에 실행 단계의 <strong>HAMi-core(libvgpu.so)</strong>까지 합치면 전체 흐름은 이렇다.</p>
<p><img src="https://velog.velcdn.com/images/taekyu_/post/ea0717b7-5124-4c9c-8139-1821c6186c30/image.png" alt=""></p>
<pre><code>파드 생성 요청
 → [webhook] HAMi 리소스 감지 → schedulerName을 hami-scheduler로 변경
 → [hami-scheduler] 노드 + 물리 GPU 선택 (GPU별 잔여 VRAM/코어 계산)
 → [device-plugin] 컨테이너에 GPU 할당 + libvgpu.so 마운트
   + 한도를 환경변수로 주입 (CUDA_DEVICE_MEMORY_LIMIT, CUDA_DEVICE_SM_LIMIT)
 → [컨테이너 실행] 모든 CUDA 호출이 libvgpu.so를 거침 → VRAM/연산 하드 리밋</code></pre><p>즉, 네 단계 (<code>파드 생성 → 스케줄링 → 할당 → 실행</code>) 전부에 HAMi가 끼어 있다. </p>
<hr>
<h3 id="span-stylecolor-slateblue2-4-이번-전환의-목표-설계span"><span style="color: SlateBlue">2-4. 이번 전환의 목표 설계</span></h3>
<p>우리 환경에 맞춘 설계는 이렇다.</p>
<table>
<thead>
<tr>
<th>항목</th>
<th>값</th>
<th>이유</th>
</tr>
</thead>
<tbody><tr>
<td>GPU당 분할 수 (<code>deviceSplitCount</code>)</td>
<td>2</td>
<td>기존 Time Slicing은 3분할이었지만, 12GB급 GPU를 3분할하면 1인당 4GB로 실습용으로도 빠듯했다. 2분할 × 6GB가 현실적</td>
</tr>
<tr>
<td>VRAM (<code>gpumem-percentage</code>)</td>
<td>50</td>
<td>4070 Ti(12GB)는 1인당 약 6GB, 4090(24GB)은 약 12GB. 비율로 걸면 카드가 달라도 프로필 하나로 커버</td>
</tr>
<tr>
<td>연산 (<code>gpucores</code>)</td>
<td>50</td>
<td>2분할이니 연산도 절반씩 보장</td>
</tr>
<tr>
<td>노드 분리</td>
<td><code>gpu-type</code> 라벨</td>
<td>4070 Ti는 딥러닝 실습 학생용, 4090은 졸업 논문작성자/관리자 전용 (기존 정책 유지)</td>
</tr>
</tbody></table>
<p>기존 구조와 비교하면:</p>
<pre><code class="language-bash">[기존]
스케줄링: Time Slicing replicas=3 
VRAM:   postStart 스크립트 주입 (3600MB, 프레임워크 설정, 소프트)
연산:    없음

[전환 후]
스케줄링: hami-scheduler (GPU별 잔여량 계산, 2분할)
VRAM:   libvgpu.so 인터셉트 (물리 VRAM의 50%, CUDA 레벨, 하드)
연산:    libvgpu.so 인터셉트 (50%, 하드)</code></pre>
<p>따라서, 내가 구축한 약 200줄짜리 제한 스크립트, postStart 훅, livenessProbe 감시는 전부 삭제 대상이 된다. </p>
<p><span style="color: indianred"><strong>추가적으로, 한 가지 짚고 갈 것은 내 학과 클러스터는 GPU Operator의 컨테이너형 드라이버 (NVIDIA GPU Operator)를 쓴다.</strong></span></p>
<p>HAMi 문서의 기본 설치는 호스트에 드라이버가 깔려 있다고 가정하기 때문에, 이 조합에서는 손봐야 할 게 몇 가지 있다. </p>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine3-전환-작업span"><span style='background-color: MediumAquamarine'>3. 전환 작업</span></h2>
<hr>

<h3 id="span-stylecolor-slateblue3-1-time-slicing-걷어내기span"><span style="color: SlateBlue">3-1. Time Slicing 걷어내기</span></h3>
<p>새 체계를 올리기 전에 기존 체계부터 걷어냈다. </p>
<p>Time Slicing은 GPU Operator의 device plugin 설정으로 들어가 있으므로, ClusterPolicy에서 제거한다.</p>
<pre><code class="language-bash">$ kubectl patch clusterpolicy cluster-policy --type=json \
    -p=&#39;[{&quot;op&quot;:&quot;remove&quot;,&quot;path&quot;:&quot;/spec/devicePlugin/config&quot;}]&#39;
clusterpolicy.nvidia.com/cluster-policy patched

$ kubectl -n nvidia-gpu-operator delete cm time-slicing-config
configmap &quot;time-slicing-config&quot; deleted from nvidia-gpu-operator namespace

$ kubectl patch clusterpolicy cluster-policy --type=merge \
    -p &#39;{&quot;spec&quot;:{&quot;devicePlugin&quot;:{&quot;enabled&quot;:false}}}&#39;
clusterpolicy.nvidia.com/cluster-policy patched</code></pre>
<p><strong>이때, Time Slicing 설정만 지우는 게 아니라 GPU Operator의 device plugin 자체를 꺼야 한다.</strong> </p>
<p>vGPU 등록은 이제 HAMi의 device plugin이 맡을 건데, 만약 둘 다 살아 있으면 같은 GPU를 두 플러그인이 서로 다른 개수로 등록하려 드는 충돌 상태가 된다.</p>
<pre><code class="language-bash">$ kubectl -n nvidia-gpu-operator get pods --no-headers | grep device-plugin
nvidia-device-plugin-daemonset-s2m6w    0/1   Terminating   0    30s

$ kubectl get nodes -o custom-columns=&quot;NODE:.metadata.name,GPU:.status.allocatable.nvidia\.com/gpu&quot; --no-headers | grep -v cp
gpu-01   0
gpu-02   0
gpu-03   0
gpu-04   0
gpu-05   0</code></pre>
<p>따라서, NVIDIA GPU Operator의 device plugin이 내려가면서 GPU allocatable이 전 노드 0이 됐다. </p>
<p>이 시점에 클러스터는 GPU가 한 장도 없는 상태이고, 이제 해당 역할을 HAMi가 하게 될 예정이다.</p>
<p><span style="color: indianred">*<em>참고로 GPU Operator에서 끄는 건 device plugin뿐이고, Driver(컨테이너형), Container Toolkit, DCGM 모니터링 등 나머지 컴포넌트는 그대로 GPU Operator가 관리한다. *</em></span></p>
<blockquote>
<p><strong>드라이버 공급은 GPU Operator, GPU 등록/분할/스케줄링은 HAMi</strong> </p>
</blockquote>
<hr>
<h3 id="span-stylecolor-slateblue-3-2-hami-설치span"><span style="color: SlateBlue"> 3-2. HAMi 설치</span></h3>
<p>HELM으로 설치한다.</p>
<pre><code class="language-bash">$ helm repo add hami-charts https://project-hami.github.io/HAMi/
&quot;hami-charts&quot; has been added to your repositories

$ helm search repo hami-charts/hami --versions | head -3
NAME             CHART VERSION  APP VERSION  DESCRIPTION
hami-charts/hami 2.9.0          2.9.0        Heterogeneous AI Computing Virtualization Middl...
hami-charts/hami 2.8.3          2.8.3        Heterogeneous AI Computing Virtualization Middl...</code></pre>
<p>이때, HAMi device plugin은 <code>gpu=on</code> 라벨이 붙은 노드에만 배포되므로, GPU 노드에 라벨부터 붙인다.</p>
<pre><code class="language-bash">$ kubectl label node gpu-01 gpu-02 gpu-03 gpu-04 gpu-05 gpu=on --overwrite
node/gpu-01 labeled
node/gpu-02 labeled
node/gpu-03 labeled
node/gpu-04 labeled
node/gpu-05 labeled</code></pre>
<p>설치 후, 최종적으로 안착한 값은 이러하다.</p>
<pre><code class="language-bash">$ helm install hami hami-charts/hami -n kube-system \
    --set scheduler.kubeScheduler.imageTag=v1.35.0 \
    --set devicePlugin.deviceSplitCount=2 \
    --set devicePlugin.runtimeClassName=nvidia</code></pre>
<p>옵션 세 개의 의미:</p>
<ul>
<li><p><code>scheduler.kubeScheduler.imageTag=v1.35.0</code></p>
<ul>
<li>hami-scheduler는 내부에 kube-scheduler 이미지를 품고 돌아간다. 클러스터 버전(v1.35)과 맞춰주지 않으면 스케줄러 동작이 어긋날 수 있어 명시적으로 고정했다.</li>
</ul>
</li>
<li><p><code>devicePlugin.deviceSplitCount=2</code> </p>
<ul>
<li>GPU당 vGPU 2개. 2-4 섹션에서 결정한 환경의 설계값이다. (설계자에 따라 다르게 설정 가능)</li>
</ul>
</li>
<li><p><code>devicePlugin.runtimeClassName=nvidia</code> </p>
<ul>
<li>컨테이너형 드라이버 환경에서 필요한 옵션 </li>
<li>사실 처음 설치할 때 이 옵션 없이 올렸다가 device plugin 전체가 CrashLoopBackOff에 빠졌다. 왜 그런지는 트러블슈팅(4-1 섹션)에서 다룬다.</li>
</ul>
</li>
</ul>
<p>설치 후 상태는 이러하다.</p>
<pre><code class="language-bash">$ kubectl -n kube-system get pods --no-headers | grep hami
hami-device-plugin-8nh6m          2/2   Running   0     54s
hami-device-plugin-jbz9f          2/2   Running   0     54s
hami-device-plugin-tg8xx          2/2   Running   0     54s
hami-device-plugin-vhn6f          2/2   Running   0     54s
hami-device-plugin-zx6hs          2/2   Running   0     54s
hami-scheduler-645b59d5cb-mdbls   2/2   Running   0     54s</code></pre>
<p><span style="color: indianred"><strong>GPU 노드 5대에 device plugin이 하나씩(각 파드는 device-plugin + vgpu-monitor 컨테이너 2개), hami-scheduler가 떴다. 참고로, HAMi mutating webhook은 hami-scheduler 파드에 함께 들어 있다.</strong></span></p>
<hr>
<h3 id="span-stylecolor-slateblue-3-3-등록-확인-클러스터가-gpu를-어떻게-보게-됐나span"><span style="color: SlateBlue"> 3-3. 등록 확인: 클러스터가 GPU를 어떻게 보게 됐나</span></h3>
<p>3-1 섹션에서 0이 됐던 GPU allocatable이 어떻게 바뀌었는지 확인한다.</p>
<pre><code class="language-bash">$ kubectl get nodes -o custom-columns=&quot;NODE:.metadata.name,vGPU:.status.allocatable.nvidia\.com/gpu&quot; --no-headers | grep -v cp
gpu-01   2
gpu-02   2
gpu-03   2
gpu-04   2
gpu-05   2</code></pre>
<p>물리 GPU 1장 → vGPU 2개. Time Slicing 때의 3이 아니라 2다. (<code>deviceSplitCount=2</code>)</p>
<p>숫자만 보면 Time Slicing의 뻥튀기와 다를 게 없어 보이지만, 노드 어노테이션을 보면 차이가 드러난다.</p>
<pre><code class="language-bash">$ kubectl get node gpu-01 gpu-05 -o jsonpath=&#39;{range .items[*]}{.metadata.name}: {.metadata.annotations.hami\.io/node-nvidia-register}{&quot;\n&quot;}{end}&#39;

gpu-01: [{&quot;id&quot;:&quot;GPU-550ccdd7-4f0e-99a8-17f1-87ec56026e91&quot;,&quot;count&quot;:2,&quot;devmem&quot;:12282,&quot;devcore&quot;:100,&quot;type&quot;:&quot;NVIDIA GeForce RTX 4070 Ti&quot;,&quot;mode&quot;:&quot;hami-core&quot;, ...
gpu-05: [{&quot;id&quot;:&quot;GPU-bdd1c304-e063-26b6-90f0-ae0f40b98f96&quot;,&quot;count&quot;:2,&quot;devmem&quot;:24564,&quot;devcore&quot;:100,&quot;type&quot;:&quot;NVIDIA GeForce RTX 4090&quot;,&quot;mode&quot;:&quot;hami-core&quot;, ...</code></pre>
<p>*<em>HAMi device plugin이 각 노드의 물리 GPU를 UUID 단위로, 실측 VRAM과 함께 등록해놨다. *</em></p>
<p>4070 Ti의 VRAM은 12282MB, 4090은 24564MB으로, hami-scheduler는 이 정보를 보고 &quot;이 GPU에 남은 VRAM이 얼마인가&quot;를 계산해서 파드를 특정 물리 GPU에 배치한다. </p>
<p><span style="color: indianred"><strong>즉, Time Slicing에는 없던, GPU별 잔여량 기반 스케줄링이 가능해진 것이다.</strong></span></p>
<p>여기서 눈여겨볼 값이 하나 있다. 4070 Ti의 실측 VRAM이 12288이 아니라 12282MB라는 것이다. 스펙상 12GB(12288MB)지만 실제 가용량은 6MB 모자란다. 이 6MB가 뒤에서 트러블슈팅 하나(4-3 섹션)를 만들어낸다.</p>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine4-트러블슈팅span"><span style='background-color: MediumAquamarine'>4. 트러블슈팅</span></h2>
<hr>



<h3 id="span-stylecolor-slateblue4-1-device-plugin-crashloopbackoff-nvml-error_library_not_foundspan"><span style="color: SlateBlue">4-1. device-plugin CrashLoopBackOff: NVML ERROR_LIBRARY_NOT_FOUND</span></h3>
<p>앞서 3-2 섹션에서 말한 대로, 처음엔 <code>runtimeClassName</code> 옵션 없이 설치했다. </p>
<pre><code class="language-bash">$ kubectl -n kube-system get pods -o wide --no-headers | grep hami
hami-device-plugin-2nb4d          0/2   CrashLoopBackOff     6 (49s ago)   112s   gpu-03
hami-device-plugin-gbbqc          0/2   CrashLoopBackOff     8 (8s ago)    112s   gpu-04
hami-device-plugin-r7rzh          0/2   CrashLoopBackOff     6 (45s ago)   112s   gpu-01
hami-device-plugin-swbs4          0/2   CrashLoopBackOff     6 (47s ago)   112s   gpu-02
hami-device-plugin-xm8bq          0/2   PostStartHookError   6 (49s ago)   112s   gpu-05
hami-scheduler-7b44c774dd-6fpx6   2/2   Running              0             112s   gpu-05</code></pre>
<p>스케줄러는 멀쩡한데 device plugin이 전 노드에서 CrashLoopBackOff 에러가 발생하였다.</p>
<p>로그를 보면,</p>
<pre><code class="language-bash">$ kubectl -n kube-system logs hami-device-plugin-2nb4d -c device-plugin --tail=8
E0819 08:10:19.293117  factory.go:135] Incompatible strategy detected auto
E0819 08:10:19.293126  factory.go:136] If this is a GPU node, did you configure the NVIDIA Container Toolkit?
E0819 08:10:19.297989  main.go:201] error starting plugins: ... invalid device discovery strategy

$ kubectl -n kube-system logs hami-device-plugin-2nb4d -c vgpu-monitor --tail=3
E0819 08:10:19.567548  main.go:129] Received error: failed to initialize NVML: ERROR_LIBRARY_NOT_FOUND</code></pre>
<p><strong><code>NVML: ERROR_LIBRARY_NOT_FOUND</code> 로그로, NVML 라이브러리(libnvidia-ml.so)를 찾지 못하고 있다.</strong></p>
<p>해당 에러는 우리 클러스터의 컨테이너형 드라이버 구조와 연결된다. </p>
<p>호스트에는 NVIDIA 드라이버가 설치되어 있지 않고, 드라이버 라이브러리는 전부 드라이버 파드가 관리하는 <code>/run/nvidia/driver</code> 아래에만 있다. </p>
<p>그런데 HAMi device plugin은 기본적으로 일반 런타임으로 뜨기 때문에, 컨테이너 안에서 이 라이브러리들이 보이지 않는다. </p>
<p><span style="color: indianred">*<em>해결은 device plugin 파드를 <code>nvidia</code> RuntimeClass로 띄우는 것이다. *</em></span></p>
<p>이 런타임으로 뜨면 NVIDIA Container Toolkit이 <code>/run/nvidia/driver</code>의 드라이버 라이브러리를 컨테이너에 주입해준다. </p>
<p>마침 GPU Operator가 만들어둔 RuntimeClass가 클러스터에 이미 있기에 적용하였다.</p>
<pre><code class="language-bash">$ kubectl get runtimeclass
NAME            HANDLER         AGE
nvidia          nvidia          197d
nvidia-cdi      nvidia-cdi      197d
nvidia-legacy   nvidia-legacy   197d</code></pre>
<p><code>nvidia</code> 런타임으로 뜨면 NVIDIA Container Toolkit이 <code>/run/nvidia/driver</code>의 드라이버 라이브러리를 컨테이너에 주입해준다.</p>
<pre><code class="language-bash">$ helm upgrade hami hami-charts/hami -n kube-system \
    --set scheduler.kubeScheduler.imageTag=v1.35.0 \
    --set devicePlugin.deviceSplitCount=2 \
    --set devicePlugin.runtimeClassName=nvidia
STATUS: deployed

$ kubectl -n kube-system get pods --no-headers | grep hami
hami-device-plugin-8nh6m          2/2   Running   0     54s
hami-device-plugin-jbz9f          2/2   Running   0     54s
hami-device-plugin-tg8xx          2/2   Running   0     54s
hami-device-plugin-vhn6f          2/2   Running   0     54s
hami-device-plugin-zx6hs          2/2   Running   0     54s
hami-scheduler-645b59d5cb-mdbls   2/2   Running   0     54s</code></pre>
<hr>
<h3 id="span-stylecolor-slateblue4-2-워크로드-starterror-unresolvable-cdi-devicesspan"><span style="color: SlateBlue">4-2. 워크로드 StartError: unresolvable CDI devices</span></h3>
<p>이제 vGPU가 등록됐으니 테스트 파드를 띄워보았다.</p>
<p>한 GPU에 여러 파드가 실제로 들어가는지 보는 게 목적이므로, nodeSelector로 스케줄 대상을 gpu-01 하나로 고정하고 같은 스펙의 파드 3개(<code>gpumem: 6144</code> + <code>gpucores: 50</code> )를 만들었다. </p>
<p>GPU 1장을 2분할했으니 2개는 돌고 1개는 Pending이 되어야 정상이다.</p>
<pre><code class="language-bash">$ kubectl get pods -o wide | grep hami-test
hami-test-1   0/1   StartError   0   60s   10.0.1.133   gpu-01
hami-test-2   0/1   StartError   0   60s   10.0.1.202   gpu-01
hami-test-3   0/1   StartError   0   60s   10.0.1.91    gpu-01</code></pre>
<p>3개의 Pod 모두다 StartError가 떠서 Describe 결과</p>
<pre><code class="language-bash">$ kubectl describe pod hami-test-1 | tail -8
  Normal   Scheduled         95s   hami-scheduler  Successfully assigned default/hami-test-1 to gpu-01
  Normal   FilteringSucceed  97s   hami-scheduler  find fit node(gpu-01), 0 nodes not fit, 1 nodes fit(gpu-01:0.00)
  Normal   BindingSucceed    96s   hami-scheduler  Successfully binding node [gpu-01] to default/hami-test-1
  Normal   Pulled            91s   kubelet         Successfully pulled image &quot;nvidia/cuda:12.2.0-base-ubuntu22.04&quot;
  Normal   Created           91s   kubelet         Container created
  Warning  Failed            90s   kubelet         Error: failed to create containerd task: failed to create
    shim task: OCI runtime create failed: could not apply required modification to OCI specification:
    error modifying OCI spec: failed to inject CDI devices: unresolvable CDI devices
    management.nvidia.com/gpu=GPU-550ccdd7-4f0e-99a8-17f1-87ec56026e91</code></pre>
<p>이벤트를 보면 스케줄링까지는 정상이다. </p>
<p>hami-scheduler가 gpu-01을 골라 바인딩까지 마쳤고, 실패는 컨테이너를 실제로 만드는 런타임 단계에서 났다. </p>
<p><code>unresolvable CDI devices</code>: CDI 방식으로 GPU 장치를 주입하려 했는데 해당 CDI 디바이스 정의를 찾지 못했다는 뜻이다.</p>
<p>CDI 얘기가 왜 나오는지 GPU Operator 설정을 확인해보니, CDI 모드가 켜져 있었다.</p>
<pre><code class="language-bash">$ kubectl get clusterpolicy cluster-policy -o jsonpath=&#39;{.spec.cdi}&#39;
{&quot;default&quot;:false,&quot;enabled&quot;:true}</code></pre>
<p>CDI(Container Device Interface)는 GPU 같은 장치를 컨테이너에 주입하는 표준 스펙이다. </p>
<p>문제는 GPU Operator의 CDI 모드와 HAMi의 장치 할당 방식이 서로 맞물리지 않는다는 점이다. </p>
<p>HAMi가 파드에 붙여준 장치 이름 (<code>management.nvidia.com/gpu=GPU-...</code>)을 런타임이 CDI 정의에서 찾으려다 실패한다. </p>
<p><span style="color: indianred"><strong>HAMi는 CDI가 아니라 환경변수 기반으로 장치를 주입하므로, 공존 구성에서는 CDI를 끄는 게 맞다.</strong></span></p>
<pre><code class="language-bash">$ kubectl patch clusterpolicy cluster-policy --type=merge \
    -p &#39;{&quot;spec&quot;:{&quot;cdi&quot;:{&quot;enabled&quot;:false,&quot;default&quot;:false}}}&#39;
clusterpolicy.nvidia.com/cluster-policy patched

$ kubectl -n nvidia-gpu-operator rollout status ds/nvidia-container-toolkit-daemonset
daemon set &quot;nvidia-container-toolkit-daemonset&quot; successfully rolled out</code></pre>
<p>container toolkit이 CDI 없는 설정으로 재배포되면서 이 에러는 사라졌다.</p>
<hr>
<h3 id="span-stylecolor-slateblue4-3-cardinsufficientmemory-6144-×-2--12288--12282span"><span style="color: SlateBlue">4-3. CardInsufficientMemory: 6144 × 2 = 12288 &gt; 12282?</span></h3>
<p>CDI를 고치고 테스트 파드를 다시 만들었다.</p>
<pre><code class="language-bash">$ kubectl get pods -o wide | grep hami-test
hami-test-1   1/1   Running   0   45s   10.0.1.21   gpu-01
hami-test-2   0/1   Pending   0   45s   &lt;none&gt;      &lt;none&gt;
hami-test-3   0/1   Pending   0   45s   &lt;none&gt;      &lt;none&gt;</code></pre>
<p>첫 파드는 Running인데,  두 번째가 Pending이다. </p>
<p>이론상 2분할이기때문에 2개의 Pod까지는 gpu-01에 들어가야한다.</p>
<pre><code class="language-bash">$ kubectl describe pod hami-test-2 | tail -2
  Warning  FilteringFailed   45s (x11 over 60s)  hami-scheduler  1 nodes CardInsufficientMemory(gpu-01)
  Warning  FilteringFailed   45s (x11 over 60s)  hami-scheduler  no available node, 1 nodes do not meet</code></pre>
<p>로그에서 확인해보니 <code>CardInsufficientMemory</code> 원인으로, GPU에 VRAM이 모자란게 문제였다.</p>
<p>12GB 카드에 6GB짜리 두 개가 왜 안 들어가나 싶지만, <strong>4070 Ti의 실측 등록값은 12288이 아니라 12282MB다.</strong></p>
<pre><code>요청: 6144MB × 2 = 12288MB
실측: 12282MB
→ 6MB 초과. 두 번째 파드는 영원히 못 들어간다.</code></pre><p>12GB는 마케팅 용량이고 실제 가용 VRAM은 그보다 약간(6MB)작다. 절대값(MB)으로 반반을 나누면 이 오차 때문에 딱 두 번째부터 막힌다.</p>
<p><span style="color: indianred"><strong>해결은 절대값을 버리고 비율로 가는 것이므로, <code>nvidia.com/gpumem-percentage</code>으로 바꾸었다.</strong></span></p>
<pre><code class="language-yaml"># 변경 전
nvidia.com/gpumem: 6144
# 변경 후
nvidia.com/gpumem-percentage: 50</code></pre>
<p>percentage는 실측 VRAM 기준으로 계산되므로 4070 Ti는 6141MB, 4090은 12282MB로 정확히 절반이 잡힌다. 카드별 하드코딩이 없어지니 나중에 다른 모델이 증설돼도 프로필을 수정할 필요가 없다는 부수 효과도 있다.</p>
<pre><code class="language-bash">$ kubectl get pods -o wide | grep hami-test     # percentage 50으로 재생성 후
hami-test-1   1/1   Running   0   40s   10.0.1.72    gpu-01
hami-test-2   1/1   Running   0   40s   10.0.1.250   gpu-01
hami-test-3   0/1   Pending   0   40s   &lt;none&gt;       &lt;none&gt;</code></pre>
<p>두 파드가 같은 노드에 들어갔고 세 번째는 Pending으로 2분할 초과가 정확히 차단된다. </p>
<hr>
<h3 id="span-stylecolor-slateblue4-4-jupyterhub-스폰-실패-user-scheduler가-hami-웹훅을-우회한다span"><span style="color: SlateBlue">4-4. JupyterHub 스폰 실패: user-scheduler가 HAMi 웹훅을 우회한다</span></h3>
<p>테스트 파드까지 확인했으니, 실제 서비스 중인 JupyterHub에 적용할 차례다.</p>
<p>프로필을 HAMi 리소스로 바꾸고(6장에서 정리) 세션을 스폰해봤는데, 스케줄 실패가 떴다.</p>
<pre><code class="language-bash">0/6 nodes are available: 1 node(s) didn&#39;t match Pod&#39;s node affinity/selector,
1 node(s) had untolerated taint {node-role.kubernetes.io/control-plane: },
4 Insufficient nvidia.com/gpucores, 4 Insufficient nvidia.com/gpumem-percentage.</code></pre>
<p>GPU 노드 4대 전부에서 <code>Insufficient nvidia.com/gpucores</code> 로그가 떴고, HAMi 리소스가 부족하다고 한다. </p>
<p>방금 테스트 파드는 잘만 스케줄됐는데 세션 파드만 안 되는 상황이라, 세션 파드가 어떻게 만들어졌는지부터 확인했다.</p>
<pre><code class="language-bash">$ kubectl get pod jupyter-test -n jupyterhub -o jsonpath=&#39;schedulerName: {.spec.schedulerName}&#39;

schedulerName: jupyterhub-user-scheduler

limits: {&quot;cpu&quot;:&quot;6&quot;,&quot;memory&quot;:&quot;21474836480&quot;,&quot;nvidia.com/gpu&quot;:&quot;1&quot;,
         &quot;nvidia.com/gpucores&quot;:&quot;50&quot;,&quot;nvidia.com/gpumem-percentage&quot;:&quot;50&quot;}</code></pre>
<p>리소스 요청은 제대로 들어갔으나, schedulerName이 다른것을 확인하였다. hami-scheduler가 아니라 jupyterhub-user-scheduler로 되어 있다.</p>
<p><code>z2jh(JupyterHub 헬름 차트)</code>는 세션 파드를 효율적으로 몰아 배치하기 위한 자체 스케줄러인 user-scheduler를 기본으로 켜두고, hub가 세션 파드를 만들 때 schedulerName을 직접 지정한다. </p>
<pre><code class="language-bash">$ kubectl get cm hub -n jupyterhub -o jsonpath=&#39;{.data.jupyterhub_config\.py}&#39; | grep -B1 -A1 &quot;scheduler_name&quot;
# scheduling:
if get_config(&quot;scheduling.userScheduler.enabled&quot;):
    c.KubeSpawner.scheduler_name = get_name(&quot;user-scheduler&quot;)</code></pre>
<p>이때, HAMi 웹훅의 동작 방식과 충돌이 생긴다. </p>
<p>웹훅은 이미 커스텀 스케줄러가 지정된 파드는 건드리지 않는다. (다른 스케줄링 체계를 존중하는 설계) </p>
<p>그래서 세션 파드는 hami-scheduler에 도달하지 못한 채 user-scheduler로 갔고, user-scheduler는 HAMi 가상 리소스를 노드 allocatable에서 찾다가 Insufficient로 실패한 것이다.</p>
<p><span style="color: indianred"><strong>따라서, user-scheduler를 끄면 hub가 schedulerName을 지정하지 않게 되고, 그러면 웹훅이 정상적으로 개입할 수 있다.</strong></span> </p>
<p>values.yaml에 두 줄을 추가했다.</p>
<pre><code class="language-yaml">scheduling:
  userScheduler:
    enabled: false</code></pre>
<p>ArgoCD sync 후 확인</p>
<pre><code class="language-bash">$ kubectl -n jupyterhub get deploy user-scheduler
Error from server (NotFound): deployments.apps &quot;user-scheduler&quot; not found

$ kubectl -n jupyterhub get pods --no-headers | grep &quot;^hub&quot;
hub-79948b49c7-57psh   1/1   Running   0   48s</code></pre>
<p>user-scheduler가 제거되었고 hub가 새 설정으로 재기동되었다. 이후 스폰된 세션 파드는 웹훅을 거쳐 hami-scheduler로 간다.</p>
<p>참고로, user-scheduler의 원래 역할은 세션 파드를 한 노드에 몰아서 배치하는 역할이다. 
따라서, hami-scheduler의 GPU 잔여량 기반 배치가 사실상 대체하므로 기능적인 손실은 없다.</p>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine5-검증span"><span style='background-color: MediumAquamarine'>5. 검증</span></h2>
<hr>

<p>트러블슈팅을 마무리 짓고 검증을 진행하였으며, 검증은 총 5개 항목으로 진행하였다.</p>
<blockquote>
<p><strong>1. 같은 GPU를 두 파드가 나눠 쓰는가
2. 분할 수를 넘으면 차단되는가
3. 실전(JupyterHub 세션)에서 학습이 도는가 
4. 한도를 초과하면 정말 막히는가
5.  모니터링은 유지되는가</strong></p>
</blockquote>
<h3 id="span-stylecolor-slateblue5-1-같은-gpu를-두-파드가-나눠-쓰는가span"><span style="color: SlateBlue">5-1. 같은 GPU를 두 파드가 나눠 쓰는가</span></h3>
<p>gpu-01에 올라간 테스트 파드 두 개에서 각각 nvidia-smi를 쳐봤다.</p>
<pre><code class="language-bash">$ kubectl exec hami-test-1 -- nvidia-smi --query-gpu=name,uuid,memory.total --format=csv,noheader
NVIDIA GeForce RTX 4070 Ti, GPU-550ccdd7-4f0e-99a8-17f1-87ec56026e91, 6141 MiB

$ kubectl exec hami-test-2 -- nvidia-smi --query-gpu=name,uuid,memory.total --format=csv,noheader
NVIDIA GeForce RTX 4070 Ti, GPU-550ccdd7-4f0e-99a8-17f1-87ec56026e91, 6141 MiB</code></pre>
<p>이 출력 한 쌍이 HAMi의 동작을 통째로 보여준다.</p>
<ul>
<li><p><span style="color: indianred"><strong>UUID가 동일하다</strong></span></p>
<ul>
<li>두 파드는 진짜로 같은 물리 GPU를 공유하고 있다.</li>
</ul>
</li>
<li><p><span style="color: indianred"><strong>각자의 memory.total이 6141 MiB다</strong></span></p>
<ul>
<li><p>물리 VRAM은 12282MB인데, 컨테이너 안에서는 절반이 전체 용량으로 보인다. NVML 인터셉트가 실제로 동작하는 장면이다. </p>
</li>
<li><p>파드 입장에선 그냥 6GB짜리 GPU 1장이다.</p>
</li>
</ul>
</li>
</ul>
<hr>
<h3 id="span-stylecolor-slateblue5-2-분할-수를-넘으면-차단되는가span"><span style="color: SlateBlue">5-2. 분할 수를 넘으면 차단되는가</span></h3>
<p>같은 조건의 세 번째 파드는 어떻게 되는가.</p>
<pre><code>$ kubectl get pods -o wide | grep hami-test
hami-test-1   1/1   Running   0   40s   10.0.1.72    gpu-01
hami-test-2   1/1   Running   0   40s   10.0.1.250   gpu-01
hami-test-3   0/1   Pending   0   40s   &lt;none&gt;       &lt;none&gt;</code></pre><p>GPU 1장 = vGPU 2개이므로 두 개까지만 배치되고, 세 번째는 hami-scheduler가 잔여 VRAM 부족으로 Pending 상태가 된다. </p>
<p><strong>몇개의 세션까지 허용할지가 이제 스케줄러 레벨에서 강제된다.</strong></p>
<hr>
<h3 id="span-stylecolor-slateblue5-3-실전에서-학습이-도는가span"><span style="color: SlateBlue">5-3. 실전에서 학습이 도는가</span></h3>
<p>앞선 테스트 파드는 단순히 sleep만 수행하므로, 온전한 검증을 위해서는 실제 JupyterHub 세션 환경을 확인해 보아야 한다. </p>
<p>이에 실제 사용자 계정으로 로그인하여 4070 Ti 프로필로 세션을 스폰했다. 가장 먼저 세션 터미널에서 nvidia-smi를 실행해 보았다.</p>
<p><img src="https://velog.velcdn.com/images/taekyu_/post/29bca355-c000-416d-82a3-757e0f99c4ab/image.png" alt=""></p>
<p>실제 물리 카드는 12GB의 VRAM을 가진 4070 Ti지만, 세션 내부에서는 전체 VRAM이 6141MiB로 인식된다. *<em>즉, 학생 입장에서는 처음부터 6GB 용량의 GPU를 단독으로 배정받은 것과 같은 환경이다. *</em></p>
<p>이 한도 안에서 학습이 정상적으로 도는지 확인하기 위해, 이 상태에서 PyTorch 기반의 CNN 모델 학습을 진행해 보았다.</p>
<p><img src="https://velog.velcdn.com/images/taekyu_/post/6cf891e8-4a8e-4073-82e8-feb8e91f0fdc/image.png" alt=""></p>
<p>loss가 정상적으로 수렴한다. </p>
<hr>
<h3 id="span-stylecolor-slateblue5-4-한도를-초과하면-정말-막히는가span"><span style="color: SlateBlue">5-4. 한도를 초과하면 정말 막히는가</span></h3>
<p>가장 중요한 검증 단계는 할당된 VRAM 한도의 강제성이다. </p>
<p>기존 시스템의 PyTorch 제한은 한도를 초과해도 강제로 막지 않는 소프트 리밋이었으나, HAMi 환경에서는 실제 VRAM 초과 사용 시 시스템이 이를 어떻게 제어하는지 확인해 보았다.</p>
<p>같은 세션에서 6GB를 초과하는 대형 워크로드를 의도적으로 돌려보았다.</p>
<p><img src="https://velog.velcdn.com/images/taekyu_/post/140a0c41-9f02-4cbe-a278-16bb5e73d1c1/image.png" alt=""></p>
<p><code>libvgpu.so</code>가 CUDA 할당 호출을 가로채서 리소스의 요청과 한도를 비교하고 할당을 거부한 것이다. PyTorch의 OutOfMemoryError는 거부를 전달받은 결과이다.</p>
<p>이때, 노트북 셸에서는 PyTorch의 OutOfMemoryError만 보이는데 이건 파이썬 레벨의 결과이며, 같은 코드를 터미널에서 돌려보면 그 앞단에서 실제로 할당을 거부한 쪽이 찍힌다.
<img src="https://velog.velcdn.com/images/taekyu_/post/bb986575-ed51-43a1-b87f-c6dbac6d51ba/image.png" alt=""></p>
<p><span style="color: indianred"><strong>이를 통해, 실제로 할당을 거부한 쪽은 HAMI-core쪽임을 확인할 수 있다.</strong></span></p>
<p>프레임워크 설정이 아니라 CUDA 레벨에서 막히므로, 학생이 무슨 프레임워크를쓰든, ipython을 타든 안 타든 이 한도는 우회되지 않는다. </p>
<p> <strong>그리고 한 명이 한도에 부딪혀도 같은 GPU의 옆 세션은 아무 영향이 없다.</strong> </p>
<hr>
<h3 id="span-stylecolor-slateblue5-5-모니터링은-유지되는가span"><span style="color: SlateBlue">5-5. 모니터링은 유지되는가</span></h3>
<p><img src="https://velog.velcdn.com/images/taekyu_/post/40d66c51-3da7-4ec2-97a4-c60d87afc1d4/image.png" alt=""></p>
<p><strong>DCGM exporter는 HAMi와 무관하게 물리 GPU 단위로 메트릭을 계속 수집한다.</strong></p>
<p>HAMi가 아무리 vGPU로 쪼개서 보여줘도, 관리자 관점의 진실은 물리 GPU 5장이 각각 얼마나 바쁜가이고, 그건 DCGM이 그대로 보여준다.</p>
<p>이 구분이 운영에서 꽤 유용하다.</p>
<ul>
<li><p><span style="color: indianred"><strong>학생(컨테이너 안)</strong></span>: </p>
<ul>
<li><p>nvidia-smi를 치면 자기 몫(6141MiB)만 보인다</p>
</li>
<li><p>HAMi의 NVML 인터셉트</p>
</li>
</ul>
</li>
<li><p><span style="color: indianred"><strong>관리자(Grafana)</strong></span>: </p>
<ul>
<li><p>물리 GPU 전체의 사용률/VRAM/온도가 보인다</p>
</li>
<li><p>DCGM은 호스트 레벨에서 수집하므로 인터셉트의 영향을 받지 않는다</p>
</li>
</ul>
</li>
</ul>
<p>예를 들어 두 세션이 같은 GPU에서 각자 학습을 돌리면, 학생들 화면에는 각자의 사용량만 보이지만 Grafana에는 그 합이 물리 GPU 한 장의 사용률로 잡힌다.</p>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine6-gitops-반영span"><span style='background-color: MediumAquamarine'>6. GitOps 반영</span></h2>
<hr>

<p>클러스터에 직접 적용한 변경들은 아직 손으로 맞춘 상태이다. </p>
<p>해당 클러스터는 2개의 레포로 관리된다. JupyterHub 배포는 ArgoCD가 바라보는 helm values 레포, 클러스터구축은 Ansible 레포이며, 둘 다 현재 상태와 일치시켜야 재구축 시에도 같은 결과가 나온다.</p>
<h3 id="span-stylecolor-slateblue6-1-jupyterhub-helm-values-수정span"><span style="color: SlateBlue">6-1. JupyterHub helm values 수정</span></h3>
<p>프로필을 HAMi 리소스 기반으로 변경했다.</p>
<pre><code class="language-yaml"># jupyterhub/values.yaml — 변경 후 프로필 (핵심만)
- display_name: &quot;GPU Server (RTX 4070 Ti)&quot;
  slug: gpu-4070ti
  default: true
  kubespawner_override:
    extra_resource_guarantees:
      nvidia.com/gpu: 1
      nvidia.com/gpumem-percentage: 50
      nvidia.com/gpucores: 50
    extra_resource_limits:            # 확장 리소스는 limits에도 필수
      nvidia.com/gpu: 1
      nvidia.com/gpumem-percentage: 50
      nvidia.com/gpucores: 50
    node_selector:
      gpu-type: &quot;rtx4070ti&quot;           # 4090 프로필은 rtx4090</code></pre>
<p>기존 프로필에 있던 <code>GPU_MEMORY_LIMIT_MB</code> 환경변수, gpu-memory-script 볼륨/마운트, postStart 훅, livenessProbe 감시는 전부 삭제했다. 제한 스크립트를 배포하던 ConfigMap 템플릿도 함께 제거했다.</p>
<pre><code class="language-bash">$ git diff --stat
 jupyterhub/config.py                   |  12 --
 jupyterhub/scripts/gpu_memory_limit.sh | 202 -----------------------
 jupyterhub/templates/extra-config.yaml |  10 +-
 jupyterhub/values.yaml                 |  87 ++--------
 4 files changed, 12 insertions(+), 299 deletions(-)</code></pre>
<p>제한 스크립트와 주입 훅이 삭제되고 리소스 선언 몇 줄이 그 자리를 대신했다. 격리가 인프라 레이어로 내려가면서 애플리케이션 레이어에서 유지하던 장치들이 필요 없어진 결과다.</p>
<hr>
<h3 id="span-stylecolor-slateblue6-2-ansible-반영span"><span style="color: SlateBlue">6-2. Ansible 반영</span></h3>
<p>클러스터 구축 자동화 레포도 수정했다. 기존 코드는 Time Slicing 클러스터를 만들도록 되어 있어서, 이대로 재구축하면 전환 이전 상태로 돌아간다.</p>
<p><span style="color: indianred"><strong>1. GPU Operator 롤 수정</strong></span>
Time Slicing ConfigMap 생성 태스크를 삭제하고, 설치 옵션을 HAMi 공존 구성으로 변경했다.</p>
<pre><code class="language-yaml"># roles/11-nvidia-gpu-operator/tasks/main.yml (수정 후)
- name: Install NVIDIA GPU Operator (device-plugin/CDI disabled for HAMi)
  command: &gt;-
    helm upgrade --install gpu-operator nvidia/gpu-operator
    -n nvidia-gpu-operator
    --create-namespace
    --set driver.enabled=true
    --set toolkit.enabled=true
    --set devicePlugin.enabled=false
    --set cdi.enabled=false
    --set cdi.default=false
    --set dcgmExporter.enabled={{ enable_gpu_exporter | default(true) }}</code></pre>
<br>

<p><span style="color: indianred"><strong>2. HAMi 롤 신설</strong></span>
노드 라벨링부터 설치까지 roles에 추가하였다.</p>
<pre><code class="language-yaml"># roles/14-hami-vgpu/tasks/main.yml (요지)
- name: Label GPU nodes (gpu=on, gpu-type)
  # inventory의 gpu_workers_4070ti / gpu_workers_4090 그룹 기준으로
  # gpu=on + gpu-type=rtx4070ti|rtx4090 라벨 부여

- name: Install HAMi
  command: &gt;-
    helm upgrade --install hami hami-charts/hami -n kube-system
    --set scheduler.kubeScheduler.imageTag=v{{ k8s_version }}
    --set devicePlugin.deviceSplitCount={{ hami_device_split_count | default(2) }}
    --set devicePlugin.runtimeClassName=nvidia</code></pre>
<p><code>runtimeClassName=nvidia</code>도 포함했고, 분할 수는 변수로 뺐다.</p>
<pre><code class="language-yaml"># group_vars/gpu_cluster.yml
# gpu_time_slicing_replicas: 3   ← 삭제
hami_device_split_count: 2</code></pre>
<hr>
<h3 id="span-stylecolor-slateblue6-3-최종-아키텍처span"><span style="color: SlateBlue">6-3. 최종 아키텍처</span></h3>
<p>전환이 끝난 시점의 전체 그림이다. </p>
<p><img src="https://velog.velcdn.com/images/taekyu_/post/1358b21e-100a-4124-a779-3a973f676672/image.png" alt=""></p>
<ol>
<li><p>학생이 JupyterHub에 로그인해 프로필을 고른다. </p>
</li>
<li><p>Jupyterhub가 세션 파드를 생성하며, 프로필에 정의된 대로 파드에는 <code>nvidia.com/gpu: 1</code>, <code>gpumem-percentage: 50</code>, <code>gpucores: 50</code>이 붙는다. 
이때, userScheduler를 꺼뒀으므로 schedulerName은 비어 있는 상태다.</p>
</li>
<li><p><span style="color: indianred"><strong>파드 생성 요청이 API 서버에 도착하면 hami-webhook이 가로챈다. 
GPU 리소스가 있는 걸 확인하고 schedulerName을 hami-scheduler로 바꾼다.</strong></span></p>
</li>
<li><p>hami-scheduler가 노드 어노테이션(<code>hami.io/node-nvidia-register</code>)에 등록된 물리 GPU별 잔여 VRAM/코어를 계산해서, 여유가 있는 카드를 골라 파드를 바인딩한다. 모든 카드가 차 있으면 여기서 Pending으로 막힌다.</p>
</li>
<li><p>배정된 노드의 kubelet이 컨테이너를 만든다. nvidia RuntimeClass를 통해 container toolkit이 컨테이너형 드라이버(<code>/run/nvidia/driver</code>)의 라이브러리를 주입하고, <span style="color: indianred"><strong>hami-device-plugin이 libvgpu.so를 마운트하면서 VRAM/연산 한도를 환경변수로 넣는다.</strong></span></p>
</li>
<li><p>세션이 뜨게 되면서, 컨테이너 안에서 nvidia-smi를 치면 NVML 인터셉트 때문에 자기 몫이 전체 용량으로 보인다. <span style="color: indianred"><strong>이후 학생이 돌리는 모든 CUDA 호출은 libvgpu.so를 거친다.</strong></span></p>
</li>
<li><p>학습 중 메모리 할당이 한도를 넘으면 HAMI-core가 CUDA 레벨에서 할당을 거부한다. 이때, 같은 GPU의 옆 세션은 영향받지 않으며, 연산도 마찬가지로 사용률이 50%를 넘으면 커널 런치가 조절된다.</p>
</li>
<li><p>이 모든 과정과 무관하게 DCGM exporter는 물리 GPU 단위 메트릭을 계속 수집하고, 관리자는 Grafana에서 카드 5장의 전체 사용률을 본다.</p>
</li>
</ol>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine7-마치며span"><span style='background-color: MediumAquamarine'>7. 마치며</span></h2>
<hr>

<blockquote>
<p>Time Slicing + 소프트웨어 VRAM 제한 주입으로 1년 넘게 버텨온 GPU 공유 체계를 HAMi로 전환하였다. <br>
격리가 컨테이너 안의 소프트웨어 리밋에서 CUDA 레벨의 하드 리밋으로 바뀌었고, 학생 세션은 이제 처음부터 6GB짜리 GPU 1장을 배정받게 되었으며, 한 명이 한도를 넘어도 옆 세션은 영향받지 않게 되었다.</p>
</blockquote>
<p>이번 작업을 진행하며 배운점은 2가지가 있다.</p>
<ul>
<li><p><span style="color: indianred"><strong>제한은 거는 위치가 중요하다.</strong></span></p>
<p>기존 방식은 몽키패치와 livenessProbe 등을 통해 우회를 막고자 했으나, 애플리케이션 레벨에서의 제한은 본질적인 한계가 있었다. </p>
<p>이를 인프라 레이어의 제어로 전환하자 수백 줄에 달하던 제어 스크립트가 단 몇 줄의 리소스 선언으로 간소화되었다. </p>
<p>제한 로직을 어느 레이어에 구현하느냐가 아키텍처의 복잡도와 안정성을 크게 좌우한다는 것을 깨닫게 되었다.</p>
</li>
<li><p><span style="color: indianred"><strong>문제는 도구가 아니라 접점에서 터진다.</strong></span></p>
<p>발생한 4건의 트러블슈팅 중 HAMi 자체의 결함은 없었다. </p>
<p>컨테이너형 Driver, GPU Operator의 CDI 모드, 실제 VRAM 오차, z2jh의 user-scheduler 등 모두 기존 환경과 새로운 도구가 맞물리는 시스템 접점에서 발생한 호환성 이슈였다. </p>
<p>결국 공식 문서의 기본 가이드보다, 현재 운용 중인 클러스터 환경의 특수성을 정확히 파악하는 것이 도입의 핵심이라는 것을 깨닫게 되었다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[죽은 GPU 노드 살리기: nouveau 드라이버 충돌 트러블슈팅]]></title>
            <link>https://velog.io/@taekyu_/%EC%A3%BD%EC%9D%80-GPU-%EB%85%B8%EB%93%9C-%EC%82%B4%EB%A6%AC%EA%B8%B0-nouveau-%EB%93%9C%EB%9D%BC%EC%9D%B4%EB%B2%84-%EC%B6%A9%EB%8F%8C-%ED%8A%B8%EB%9F%AC%EB%B8%94%EC%8A%88%ED%8C%85-gwe714yd</link>
            <guid>https://velog.io/@taekyu_/%EC%A3%BD%EC%9D%80-GPU-%EB%85%B8%EB%93%9C-%EC%82%B4%EB%A6%AC%EA%B8%B0-nouveau-%EB%93%9C%EB%9D%BC%EC%9D%B4%EB%B2%84-%EC%B6%A9%EB%8F%8C-%ED%8A%B8%EB%9F%AC%EB%B8%94%EC%8A%88%ED%8C%85-gwe714yd</guid>
            <pubDate>Wed, 19 Aug 2026 14:09:40 GMT</pubDate>
            <description><![CDATA[<hr>

<h2 id="span-stylebackground-color-mediumaquamarine1-무슨-일이-있었는가-span"><span style='background-color: MediumAquamarine'>1. 무슨 일이 있었는가 </span></h2>
<hr>

<h3 id="span-stylecolor-slateblue1-1-한-달간의-서버실-공사-그리고-재가동span"><span style="color: SlateBlue">1-1. 한 달간의 서버실 공사, 그리고 재가동</span></h3>
<p>서버실 공사와 전력 공사가 겹치면서 학과 GPU K8s 클러스터가 약 한 달간 내려가 있었다.</p>
<p>공사가 끝나고 서버를 다시 올렸는데, 노드 하나가 돌아오지 않았다.</p>
<pre><code class="language-bash">$ kubectl get nodes -o wide
NAME     STATUS                        ROLES          
gpu-01   Ready                         worker          
gpu-02   Ready                         worker          
gpu-03   NotReady                      worker          
gpu-04   Ready                         worker          
gpu-05   NotReady,SchedulingDisabled   worker          
gpu-cp   Ready                         control-plane   </code></pre>
<p>*<em>gpu-03이 NotReady다. *</em></p>
<p>(gpu-05도 NotReady지만 이건 2월부터 꺼져 있던 별개 건으로, 이날 OS를 새로 설치해 재편입했다)</p>
<p>노드 상태를 까보니 더 이상한 점이 있었다.</p>
<pre><code class="language-bash">$ kubectl describe node gpu-03
Conditions:
  Type                 Status    LastHeartbeatTime                 Reason              Message
  ----                 ------    -----------------                 ------              -------
  NetworkUnavailable   False     Tue, 03 Feb 2026 15:08:52 +0900   CiliumIsUp          Cilium is running on this node
  MemoryPressure       Unknown   Fri, 05 Jun 2026 19:26:45 +0900   NodeStatusUnknown   Kubelet stopped posting node status.
  DiskPressure         Unknown   Fri, 05 Jun 2026 19:26:45 +0900   NodeStatusUnknown   Kubelet stopped posting node status.
  PIDPressure          Unknown   Fri, 05 Jun 2026 19:26:45 +0900   NodeStatusUnknown   Kubelet stopped posting node status.
  Ready                Unknown   Fri, 05 Jun 2026 19:26:45 +0900   NodeStatusUnknown   Kubelet stopped posting node status.</code></pre>
<p>마지막 HeartBeat가 6월 5일</p>
<p>공사는 7월에 시작했는데, 이 노드는 공사 전부터 이미 죽어 있었다는 얘기다. 클러스터가 통째로 꺼져 있던 기간에 묻혀서 몰랐을 뿐, 실제로는 약 75일째 NotReady였다.</p>
<p>이 노드 위에 있던 파드 수십 개는 Terminating/Unknown 상태의 유령으로 남아 있었고, 이 노드의 디스크를 쓰는 Rook-Ceph OSD 두 개도 40일째 Pending이었다.</p>
<hr>
<h3 id="span-stylecolor-slateblue1-2-켜보니-죽어있는-gpu-노드span"><span style="color: SlateBlue">1-2. 켜보니 죽어있는 GPU 노드</span></h3>
<p>원격으로 할 수 있는 게 없었다. ping도 안 가고 ssh도 안 됐다. 결국 서버실에 직접 갔다.</p>
<p>서버실에서 확인해보니 전원은 들어와 있고 팬도 돈다. </p>
<p>즉, 노드 자체가 죽은 건 아니다. 모니터를 연결하고 재부팅해보니 증상이 특이했다.</p>
<ul>
<li>부팅 화면이 1초 정도 보였다가 블랙아웃. 화면은 안 나오는데 머신은 계속 돌아간다.</li>
<li>콘솔 로그인은 가능한데, NetworkManager를 보니 유선 연결이 Wired off 상태</li>
</ul>
<p>ping이 안 갔던 이유는 케이블도 꽂혀 있고 머신도 살아 있는데, 단순히 네트워크 연결 자체가 비활성 상태였던 것이다.</p>
<p>&quot;뭐야, 그럼 연결만 켜면 되는 거 아닌가?&quot; 하고 가볍게 연결을 올렸다.</p>
<hr>
<h3 id="span-stylecolor-slateblue1-3-현장에서-발견한-증상-네트워크를-열면-무한-재부팅span"><span style="color: SlateBlue">1-3. 현장에서 발견한 증상: 네트워크를 열면 무한 재부팅</span></h3>
<p>연결을 켜자 ssh도 들어와졌다. </p>
<p>&quot;이게 왜 죽어있었지?&quot; 싶던 그 순간 머신이 통째로 재부팅됐다. 네트워크 연결 후 정확히 10~20초 뒤였다.</p>
<p>그리고 부팅 후엔 다시 Wired off로 돌아와 있다. 다시 연결하면 또 10~20초 뒤 재부팅이 되는 현상이 지속되었다.</p>
<p>이때의 재부팅 기록이 그대로 남아 있다.</p>
<pre><code class="language-bash">$ last -x reboot | head -5
reboot   system boot  6.8.0-124-generi Wed Aug 19 13:42   still running
reboot   system boot  6.8.0-124-generi Wed Aug 19 13:41   still running
reboot   system boot  6.8.0-124-generi Wed Aug 19 13:27   still running
reboot   system boot  6.8.0-124-generi Wed Aug 19 13:26   still running
reboot   system boot  6.8.0-124-generi Wed Aug 19 13:17   still running</code></pre>
<p>13:26에 부팅한 머신이 13:27에 또 부팅되어 있다. 네트워크 연결 실험을 할 때마다 1~5분 간격으로 재부팅이 쌓였다. 이 노드는 죽어 있던 게 아니라, <span style="color: indianred"><strong>네트워크가 붙는 순간마다 크래시하는 무한 재부팅 루프에 갇혀 있었던 것이다.</strong></span>
<br></p>
<p>더 이상한 건 로그였다. 부팅 이력을 보면,</p>
<pre><code class="language-bash">$ journalctl --list-boots | tail -3
-2 64323b58... Wed 2026-08-19 13:37:12 KST—Wed 2026-08-19 13:37:12 KST
-1 6d3405a3... Wed 2026-08-19 13:42:11 KST—Wed 2026-08-19 13:42:11 KST
 0 831f0b6b... Wed 2026-08-19 13:47:09 KST—Wed 2026-08-19 13:47:40 KST</code></pre>
<p>크래시한 부팅들은 시작 시각과 마지막 로그 시각이 같다. 부팅 직후 한 번 flush된 뒤로 단 한 줄도 디스크에 기록되지 못하고 죽었다는 뜻이다. </p>
<p>커널 패닉 메시지도, 정상 종료 기록도 없다. 패닉 덤프가 남는 pstore도 확인했다.</p>
<pre><code class="language-bash">$ ls -la /sys/fs/pstore/
# (비어 있음)

$ journalctl -b -1 -k | grep -iE &quot;mce|panic|watchdog|thermal|nvrm|oops|hardware error&quot;
# (출력 없음)</code></pre>
<p>아무것도 없다. </p>
<p>로그가 없다는 건 보통 문제가 없었다는 뜻이지만, 이 경우는 커널이 로그 버퍼를 디스크에 flush할 틈조차 없이 즉사했다는 뜻이다.</p>
<pre><code class="language-bash">$ cat /proc/sys/kernel/panic
10</code></pre>
<p>그리고 이 머신에는 이런 해당 설정이 존재하였는데, 커널 패닉이 나면 10초 뒤 자동 재부팅을 한다는 의미이다. 
<br></p>
<p>정리하면 이렇다.</p>
<blockquote>
<p><strong>1. 네트워크만 안 켜면 멀쩡하다
2. 네트워크를 켜면 15~20초 뒤 죽는다
3. 죽는 순간의 로그는 어디에도 없다</strong></p>
</blockquote>
<p>네트워크가 원인처럼 보이지만, 네트워크 자체가 커널을 죽일 이유는 없다. </p>
<p>NIC 드라이버 문제였다면 링크가 붙는 순간 죽어야지 20초를 기다릴 이유가 없다.</p>
<p>이 20초는 뭔가가 준비되는 시간인것 같다는 생각이 들었고, 범인은 네트워크가 아니라, 네트워크가 연결된 뒤에 시작되는 무언가였다고 생각하였다.</p>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine2-nouveau와-nvidia-gpu-operator-span"><span style='background-color: MediumAquamarine'>2. nouveau와 NVIDIA GPU Operator </span></h2>
<hr>


<p>원인을 파악하기 위해서는 클러스터의 GPU 드라이버 관리 방식과 nouveau의 개념에 대한 이해가 필요하다.</p>
<h3 id="span-stylecolor-slateblue2-1-nvidia-gpu-operator란span"><span style="color: SlateBlue">2-1. NVIDIA GPU Operator란?</span></h3>
<p><strong>GPU Operator는 쿠버네티스에서 GPU를 쓰기 위해 필요한 컴포넌트들인 Driver, Container Toolkit, Device Plugin, Monitoring 등을 오퍼레이터 패턴으로 한 번에 배포와 관리해주는 NVIDIA의 공식 프로젝트다.</strong></p>
<p><img src="https://velog.velcdn.com/images/taekyu_/post/679f58a9-da3d-4f2e-94cb-77a81b1880e0/image.png" alt=""></p>
<p>여기서 중요한 건 <strong>컨테이너형 드라이버 (driver daemonset)</strong> 방식이다. 해당 클러스터는 호스트에 NVIDIA 드라이버를 직접 설치하지 않는다. </p>
<p>대신 <code>nvidia-driver-daemonset</code> 파드가 각 GPU 노드에 떠서, 부팅할 때마다 컨테이너 안에서 커널 모듈을 로드한다.
<img src="https://velog.velcdn.com/images/taekyu_/post/b3da1ec6-3020-4134-91fd-f2f7bf4f631d/image.png" alt=""></p>
<p>드라이버 파일은 호스트가 아니라 <code>/run/nvidia/driver</code> 아래에만 존재한다.</p>
<pre><code class="language-bash">$ kubectl get clusterpolicy -o jsonpath=&#39;{.items[0].spec.driver.version}&#39;
580.105.08

$ kubectl -n nvidia-gpu-operator get ds nvidia-driver-daemonset \
    -o jsonpath=&#39;{.spec.template.spec.containers[0].image}&#39;
nvcr.io/nvidia/driver:580.105.08-ubuntu22.04</code></pre>
<p>해당 방식은 노드 5대의 드라이버 버전을 apt로 하나하나 맞추는 게 아니라, ClusterPolicy 하나로 전 노드를 일괄 관리할 수 있다는 장점이 존재한다.</p>
<p>그런데 이 구조에는 전제가 하나 깔려 있다. </p>
<p><span style="color: indianred"><strong>드라이버 파드가 커널 모듈을 로드하는 시점에, GPU가 비어 있어야 한다.</strong></span></p>
<hr>
<h3 id="span-stylecolor-slateblue2-2-nouveau란span"><span style="color: SlateBlue">2-2. nouveau란?</span></h3>
<p><strong>nouveau는 리눅스 커뮤니티가 리버스 엔지니어링으로 만든 오픈소스 NVIDIA 그래픽 드라이버다.</strong> </p>
<p>NVIDIA가 드라이버 소스를 공개하지 않기 때문에 커뮤니티가 만든 대체품으로, 우분투를 포함한 대부분의 배포판에 기본 내장되어 있다.</p>
<ul>
<li>NVIDIA GPU가 장착된 채로 OS를 설치하면, 공식 드라이버가 없어도 화면은 나와야 하므로, 커널이 부팅 시 nouveau를 자동으로 로드한다</li>
<li>화면 출력용으로는 동작하지만 CUDA는 지원하지 않고, 성능도 공식 드라이버 대비 크게 떨어진다. GPU 연산 서버 입장에서는 사실상 방해물이다.</li>
</ul>
<p>솔직히 이번 일을 겪기 전까지, 리눅스를 설치하면 nouveau가 기본으로 들어간다는 사실 자체를 몰랐다. 공식 드라이버를 깔면 알아서 대체되는 줄 알았다.</p>
<hr>
<h3 id="span-stylecolor-slateblue2-3-왜-하나의-gpu에-두-드라이버가-공존할-수-없는가span"><span style="color: SlateBlue">2-3. 왜 하나의 GPU에 두 드라이버가 공존할 수 없는가</span></h3>
<p>*<em>하나의 PCI 디바이스는 하나의 커널 드라이버만 바인딩할 수 있다. *</em></p>
<p><span style="color: indianred"><strong>nouveau가 부팅 때 먼저 GPU를 점유하게 되면, 나중에 공식 nvidia 모듈이 로드되려면 nouveau를 먼저 언로드해야 한다.</strong></span></p>
<p>관건은 nouveau의 점유 해제 가능 여부다. </p>
<p>유휴 상태일 경우 정상적으로 언로드되지만, GNOME 같은 데스크톱이 nouveau 위에서 화면을 그리고 있으면, 사용 중인 드라이버를 강제로 뽑아내는 셈이 된다.</p>
<p><span style="color: indianred"><strong>화면 출력이 실시간으로 돌아가는 드라이버를 커널에서 제거하는 순간, 커널은 로그도 못 남기고 죽을 수 있다.</strong></span></p>
<p>그래서 NVIDIA 드라이버를 쓰는 시스템은 관례적으로 nouveau를 블랙리스트에 등록해서 부팅 단계부터 로드 자체를 막는다.</p>
<pre><code class="language-bash"># /etc/modprobe.d/blacklist-nouveau.conf
blacklist nouveau
install nouveau /bin/false</code></pre>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine3-원인-추적기-span"><span style='background-color: MediumAquamarine'>3. 원인 추적기 </span></h2>
<hr>

<h3 id="span-stylecolor-slateblue3-1-격리-테스트-kubelet과-containerd를-끄니-재부팅이-멈췄다span"><span style="color: SlateBlue">3-1. 격리 테스트: kubelet과 containerd를 끄니 재부팅이 멈췄다</span></h3>
<p>네트워크 연결 후에 시작되는 무언가를 찾기 위해 격리 테스트를 했다. </p>
<p>쿠버네티스 관련 데몬(kubelet, containerd)을 전부 내리고 네트워크를 연결해봤다.</p>
<pre><code class="language-bash">sudo systemctl stop kubelet containerd
# 이후 유선 연결 ON</code></pre>
<p>그렇게 해보니, 재부팅이 안 되면서 해당 상태로는 ssh로 접근이 가능하였다.</p>
<pre><code class="language-bash">$ ssh gpu-03 &#39;echo CONNECTED: $(hostname); uptime&#39;
CONNECTED: gpu-03
 13:47:09 up 4 min,  1 user,  load average: 0.24, 0.14, 0.06</code></pre>
<p>이는 네트워크 연결 직후 kubelet이 API 서버에 노드를 등록하면서 실행되는 파드 중 하나가 크래시를 유발함을 의미한다.</p>
<p>그리고, GPU 노드에서 커널 모듈을 직접 제어하여 시스템 레벨의 충돌을 일으킬 수 있는 파드는 <strong><code>nvidia-driver-daemonset</code></strong>뿐이다.</p>
<h3 id="span-stylecolor-slateblue-3-2-정상-노드와의-비교-gpu-03만-nouveau가-gpu를-점유하고-있었다span"><span style="color: SlateBlue"> 3-2. 정상 노드와의 비교: gpu-03만 nouveau가 GPU를 점유하고 있었다</span></h3>
<p>가설을 확인하기 위해 gpu-03의 드라이버 상태를 동일한 GPU를 사용하는 정상 노드와 비교했다.</p>
<pre><code class="language-bash"># --- gpu-03 (문제 노드) ---
$ lsmod | grep -E &quot;^nouveau|^nvidia&quot;; lspci -nnk -d 10de: | grep -E &quot;VGA|Kernel driver&quot;
nouveau              3100672  4
01:00.0 VGA compatible controller [0300]: NVIDIA Corporation Device [10de:2782] (rev a1)
      Kernel driver in use: nouveau

# --- gpu-04 (정상 노드) ---
$ lsmod | grep -E &quot;^nouveau|^nvidia&quot;; lspci -nnk -d 10de: | grep -E &quot;VGA|Kernel driver&quot;
nvidia_modeset       1814528  1
nvidia_uvm           2158592  8
nvidia              14385152  26 nvidia_uvm,nvidia_modeset
01:00.0 VGA compatible controller [0300]: NVIDIA Corporation Device [10de:2782] (rev a1)
      Kernel driver in use: nvidia</code></pre>
<p>같은 하드웨어인데 잡고 있는 드라이버가 다르다. </p>
<p><strong>gpu-03은 nouveau가 GPU를 잡고있다.</strong> 이는 GNOME 데스크톱이 nouveau 위에서 화면을 그리고 있다는 뜻이다. </p>
<p>부팅 화면이 1초 보이고 블랙아웃되던 증상도 nouveau의 커널모드 설정 핸드오버 문제로 같은 뿌리다.</p>
<p>왜 이런 차이가 나는지 <code>/etc/modprobe.d/</code>를 비교해보면:</p>
<pre><code class="language-bash">#--- gpu-03 ---
$ ls /etc/modprobe.d/ | grep -i -E &quot;nouveau|blacklist&quot;
amd64-microcode-blacklist.conf
blacklist-ath_pci.conf
blacklist.conf
blacklist-firewire.conf
blacklist-framebuffer.conf
blacklist-modem.conf
blacklist-oss.conf
blacklist-rare-network.conf
intel-microcode-blacklist.conf

#--- 정상 노드 ---
$ ls /etc/modprobe.d/ | grep -i -E &quot;nouveau|blacklist&quot;
amd64-microcode-blacklist.conf
blacklist-ath_pci.conf
blacklist.conf
blacklist-firewire.conf
blacklist-framebuffer.conf
blacklist-modem.conf
blacklist-nouveau.conf          #← 정상 노드들은 해당 blacklist가 존재
blacklist-oss.conf
blacklist-rare-network.conf
intel-microcode-blacklist.conf

$ cat /etc/modprobe.d/blacklist-nouveau.conf   # 정상 노드
blacklist nouveau
install nouveau /bin/false</code></pre>
<p>정상 노드들은 nouveau가 블랙리스트로 차단되어 있어 nvidia 드라이버가 GPU를 온전히 쓰고 있었고, gpu-03에는 해당 파일이 없었다.
<br></p>
<p>덤으로, 재부팅 후 네트워크가 매번 꺼져 있던 이유도 이때 확인됐다.</p>
<pre><code class="language-bash">$ nmcli -t -f NAME,DEVICE,AUTOCONNECT con show
Profile 1:enp7s0:no</code></pre>
<p>autoconnect값이 no이므로, 부팅해도 스스로 네트워크에 붙지 않았다.</p>
<hr>
<h3 id="span-stylecolor-slateblue-3-3-재구성한-크래시-시나리오span"><span style="color: SlateBlue"> 3-3. 재구성한 크래시 시나리오</span></h3>
<pre><code class="language-bash">부팅
 → nouveau가 GPU 선점, GNOME이 그 위에서 렌더링 (부팅 화면 1초 후 블랙아웃)
 → 유선 연결 ON
 → kubelet이 API 서버에 노드 등록
 → GPU Operator의 nvidia-driver-daemonset 파드 스케줄
 → 드라이버 컨테이너가 nvidia 모듈 로드 전에 nouveau 언로드 시도
 → 화면 출력 중인 드라이버를 강제 제거 → 커널 즉사 (로그 flush할 틈도 없음)
 → kernel.panic=10 설정으로 10초 후 자동 재부팅
 → NetworkManager autoconnect=no라 부팅 후 다시 &quot;Wired off&quot;
 → (수동으로 연결하면 처음부터 반복)</code></pre>
<p>네트워크 연결 후 10~20초는 kubelet 등록부터 드라이버 파드가 뜨기까지의 시간이었고, 로그가 없는 이유는 커널이 flush할 틈도 없이 리셋됐기 때문이었다.</p>
<p>추가적으로, 재부팅 후 Wired off는 autoconnect 설정 문제였다. </p>
<hr>
<h3 id="span-stylecolor-slateblue-3-4-왜-1년간-멀쩡했는가span"><span style="color: SlateBlue"> 3-4. 왜 1년간 멀쩡했는가?</span></h3>
<p>그렇다면 여기서 의문이 하나 남았다. 이 클러스터는 1년 넘게 GPU Operator로 JupyterHub를 서비스해왔다. 이런 시한폭탄이 있었는데 왜 이제야 터졌는지가 의문이었다.</p>
<p>답은 두 가지다.</p>
<ul>
<li><p><strong>첫째, 이 충돌은 부팅 시점에만 발생한다.</strong> 
서버는 재부팅할 일이 거의 없다. 한번 nvidia 드라이버가 GPU를 잡은 뒤에는 nouveau가 끼어들 틈이 없으니, 가동 중에는 아무 문제가 없다.</p>
</li>
<li><p><strong>둘째, headless 상태에서는 nouveau가 로드돼 있어도 조용히 언로드된다.</strong> 
모니터 없이 부팅하면 아무도 nouveau를 사용하지 않으므로, 드라이버 파드가 큰 충돌 없이 nouveau를 내리고 자기가 GPU를 잡을 수 있다. 즉 그동안은 구조적으로 안전했던 게 아니라 운이 좋았던 것이다.</p>
</li>
</ul>
<p>그러다 gpu-03에서 문제가 재현된 것이다. 설치된 커널 목록을 보면</p>
<pre><code class="language-bash">$ ls -l --time-style=long-iso /boot/vmlinuz-*
2026-01-28 /boot/vmlinuz-6.8.0-40-generic
2026-05-26 /boot/vmlinuz-6.8.0-124-generic

$ zgrep -hA1 &quot;Start-Date: 2026-06&quot; /var/log/apt/history.log.*.gz | head -4
Start-Date: 2026-06-03  06:35:30
Commandline: /usr/bin/unattended-upgrade
--
Start-Date: 2026-06-05  06:58:57
Commandline: /usr/bin/unattended-upgrade</code></pre>
<p>5월 26일에 unattended-upgrade가 새 커널(6.8.0-124)을 설치했고, 6월 5일에 재부팅되면서 새 커널로 올라왔다. </p>
<p>이때부터 nouveau이 GPU를 놓지 않는 상태가 되었고, 드라이버 파드의 언로드 시도가 커널 크래시로 이어지는 루프에 진입했다.</p>
<p>사람이 아무 짓도 안 했는데 노드가 죽은 이유가 이거였다. </p>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine4-해결-방법-span"><span style='background-color: MediumAquamarine'>4. 해결 방법 </span></h2>
<hr>

<h3 id="span-stylecolor-slateblue4-1-nouveau-블랙리스트-등록--initramfs-재생성span"><span style="color: SlateBlue">4-1. nouveau 블랙리스트 등록 + initramfs 재생성</span></h3>
<p>gpu-03에서</p>
<pre><code class="language-bash">$ sudo nmcli con mod &quot;Profile 1&quot; connection.autoconnect yes &amp;&amp; echo &quot;1. autoconnect ON&quot;
1. autoconnect ON

$ printf &quot;blacklist nouveau\ninstall nouveau /bin/false\n&quot; | \
    sudo tee /etc/modprobe.d/blacklist-nouveau.conf
blacklist nouveau
install nouveau /bin/false

$ sudo update-initramfs -u
update-initramfs: Generating /boot/initrd.img-6.8.0-124-generic

$ sudo reboot</code></pre>
<p>블랙리스트 파일만 만들면 끝이 아니고 <code>update-initramfs -u</code>까지 해야 한다.</p>
<p>nouveau는 부팅 극초반 initramfs 단계에서 로드될 수 있어서, initramfs 안에 블랙리스트가 반영되어 있지 않으면 소용이 없다.</p>
<hr>
<h3 id="span-stylecolor-slateblue4-2-복구-확인span"><span style="color: SlateBlue">4-2. 복구 확인</span></h3>
<p>재부팅하고 10초 만에 네트워크가 스스로 돌아왔다. (autoconnect 효과)</p>
<pre><code class="language-bash">$ uptime -p; lsmod | grep nouveau &amp;&amp; echo &quot;0&quot; || echo &quot;1&quot;
up 1 minute
1</code></pre>
<p>nouveau가 사라졌다. 이제 드라이버 파드 배포를 다시 허용하고 지켜봤다.</p>
<pre><code class="language-bash">$ kubectl label node gpu-03 nvidia.com/gpu.deploy.driver=true --overwrite
node/gpu-03 labeled

$ kubectl get pods -n nvidia-gpu-operator -o wide --field-selector spec.nodeName=gpu-03
NAME                                               READY   STATUS     RESTARTS   AGE
gpu-feature-discovery-lrsvq                        0/2     Init:0/2   0          17s
nvidia-container-toolkit-daemonset-ddjkj           0/1     Init:0/1   0          17s
nvidia-dcgm-exporter-k82x8                         0/1     Init:0/1   0          17s
nvidia-device-plugin-daemonset-d2sq4               0/2     Init:0/2   0          17s
nvidia-driver-daemonset-c2794                      0/1     Running    0          20s
nvidia-operator-validator-wcqfc                    0/1     Init:0/4   0          17s</code></pre>
<p>예전 같으면 <code>nvidia-driver-daemonset</code>이 뜨는 순간 커널이 죽었어야 한다.</p>
<p>이번엔 죽지 않았다.</p>
<pre><code class="language-bash">$ kubectl -n nvidia-gpu-operator get pods --field-selector spec.nodeName=gpu-03 --no-headers
gpu-feature-discovery-lrsvq                        2/2   Running     0    2m6s
nvidia-container-toolkit-daemonset-ddjkj           1/1   Running     0    2m6s
nvidia-cuda-validator-kq28j                        0/1   Completed   0    35s
nvidia-dcgm-exporter-k82x8                         1/1   Running     0    2m6s
nvidia-device-plugin-daemonset-d2sq4               2/2   Running     0    2m6s
nvidia-driver-daemonset-c2794                      1/1   Running     0    2m9s
nvidia-operator-validator-wcqfc                    1/1   Running     0    2m6s

$ kubectl get node gpu-03
gpu-03   Ready   worker   196d   v1.35.0</code></pre>
<p>GPU 스택 전체가 Running이고 노드도 Ready 인것을 확인했다.</p>
<p>이후에, 드라이버가 실제로 GPU를 잡았는지도 확인하였다.</p>
<pre><code class="language-bash">$ kubectl -n nvidia-gpu-operator exec nvidia-driver-daemonset-c2794 -- \
    nvidia-smi --query-gpu=name,driver_version,temperature.gpu,power.draw --format=csv
name, driver_version, temperature.gpu, power.draw [W]
NVIDIA GeForce RTX 4070 Ti, 580.105.08, 36, 11.10 W</code></pre>
<p>이때 실시간으로 스트리밍해두고 있던 dmesg에서 로그를 확인해보니, nvidia 모듈이 올라오는 것을 확인할 수 있었다.</p>
<pre><code class="language-bash">[수  8월 19 14:10:03 2026] nvidia: loading out-of-tree module taints kernel.
[수  8월 19 14:10:03 2026] NVRM: loading NVIDIA UNIX Open Kernel Module for x86_64  580.105.08  Release Build
[수  8월 19 14:10:03 2026] nvidia-modeset: Loading NVIDIA UNIX Open Kernel Mode Setting Driver for x86_64  580.105.08</code></pre>
<p>이렇게 문제가 있는 노드는 네트워크를 연결해도 재부팅이 없고, 드라이버가 정상 로드되며 GPU allocatable이 복원되었다. </p>
<blockquote>
<p><strong>디버깅 팁</strong>: 이번처럼 크래시가 로그를 못 남기는 경우, 원격 머신에서
<code>ssh &lt;node&gt; &#39;sudo dmesg -wT&#39; &gt; crash.log</code>로 커널 로그를 실시간 스트리밍해두면
디스크에 기록되지 못한 마지막 메시지를 SSH 스트림으로 건질 수 있다.
디스크는 못 살려도 이미 전송된 패킷은 남는다.</p>
</blockquote>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine5-재발-방지-span"><span style='background-color: MediumAquamarine'>5. 재발 방지 </span></h2>
<hr>

<h3 id="span-stylecolor-slateblue5-1-ansible-롤로-신규-노드-자동-적용span"><span style="color: SlateBlue">5-1. Ansible 롤로 신규 노드 자동 적용</span></h3>
<p>구축할 때 손으로 해준 설정은 새 노드가 들어올 때 반드시 누락된다. </p>
<p>그래서 기존에 학과 클러스터를 관리하기 위해 구축해 놓은 클러스터 구축 자동화 레포(Using Ansible)에 GPU 노드 사전 준비 롤을 추가했다.</p>
<pre><code class="language-yaml"># roles/00-gpu-node-prep/tasks/main.yml 
- name: Blacklist nouveau
  copy:
    dest: /etc/modprobe.d/blacklist-nouveau.conf
    content: |
      blacklist nouveau
      install nouveau /bin/false
  register: blacklist_result

- name: Rebuild initramfs
  command: update-initramfs -u
  when: blacklist_result.changed

- name: Warn if nouveau is currently loaded
  ...</code></pre>
<p>블랙리스트 배포와 initramfs 재생성을 워커 조인 롤보다 앞 단계에 배치해서, 플레이북으로 노드를 추가하는 한 이 문제는 구조적으로 재발할 수 없게 했다.</p>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine6-마치며-span"><span style='background-color: MediumAquamarine'>6. 마치며 </span></h2>
<hr>

<blockquote>
<p><strong>GPU Operator의 컨테이너형 드라이버는 부팅 시 GPU가 비어 있다는 전제 위에서 동작하는데, 리눅스에 기본 내장된 nouveau가 그 전제를 깨뜨릴 수 있고, 그 결과가 로그 한 줄 없는 커널 크래시 무한 루프였다.</strong></p>
</blockquote>
<p>개인적으로 이번 트러블슈팅에서 얻은 게 두 가지다.</p>
<ul>
<li><p><span style="color: indianred"><strong>잘 돌아가는 시스템과 안전한 시스템이 다르다.</strong></span></p>
<ul>
<li><p>해당 클러스터는 1년간 멀쩡히 서비스되었지만, 그건 재부팅을 안 했기 때문이지 구성이 완전했기 때문이 아니었다. </p>
</li>
<li><p>평소에 안 터지는 문제일수록 부팅이나 재시작 같은 드문 이벤트에 숨어 있다.</p>
</li>
</ul>
</li>
<li><p><span style="color: indianred"><strong>로그가 없는 것도 단서다.</strong></span></p>
<ul>
<li>부팅 기록의 시작과 끝이 같은 시각이라는것, pstore가 비어 있다는 것 자체가 &quot;커널이 flush할 틈도 없이 죽었다&quot;는 정보였고, 그게 하드웨어 문제가 아닌 커널 레벨 충돌 쪽으로 방향을 잡게 해줬다.</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[90DaysOfDevOps을 정리하며]]></title>
            <link>https://velog.io/@taekyu_/90DaysOfDevOps%EC%9D%84-%EC%A0%95%EB%A6%AC%ED%95%98%EB%A9%B0-d6gq3r8j</link>
            <guid>https://velog.io/@taekyu_/90DaysOfDevOps%EC%9D%84-%EC%A0%95%EB%A6%AC%ED%95%98%EB%A9%B0-d6gq3r8j</guid>
            <pubDate>Fri, 20 Feb 2026 14:37:40 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>해당 스터디는 90DaysOfDevOps
<a href="https://github.com/MichaelCade/90DaysOfDevOps">https://github.com/MichaelCade/90DaysOfDevOps</a>
를 기반으로 진행한 내용입니다.</strong></p>
</blockquote>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine1-90daysofdevops-스터디를-마무리하며span"><span style='background-color: MediumAquamarine'>1. 90DaysOfDevOps 스터디를 마무리하며</span></h2>
<hr>

<p>90DaysOfDevOps 스터디의 모든 강의 수강과 정리를 마쳤다. </p>
<p>방대한 DevOps 생태계와 클라우드 네이티브 기술들을 폭넓게 훑어볼 수 있는 시간이었다. 스터디를 리딩하며 함께 기술을 탐구하고, 실제 쿠버네티스 클러스터와 인프라를 운영하며 마주했던 여러 고민에 대한 해답을 엿볼 수 있었다.</p>
<p><strong>마지막 챕터인 만큼, 전체 강의 중에서도 내용이 특히 훌륭했거나 기술적으로 흥미로웠던 주제, 그리고 DevOps 엔지니어링 관점에서 앞으로 더 깊게 파고들어 공부해 볼 가치가 있는 강의들을 아래와 같이 추려보았다.</strong>
<br></p>
<p>📌 <span style="color: indianred"><strong>인상 깊었던 핵심 강의 리스트</strong></span></p>
<ul>
<li><p><span style="color: SlateBlue"><strong>Day 6.</strong></span> Kubernetes RBAC with Ansible: 쿠버네티스 RBAC 정책과 Ansible</p>
</li>
<li><p><span style="color: SlateBlue"><strong>Day 8.</strong></span> Culinary Coding: Crafting Infrastructure Recipes with OpenTofu: Terraform과 OpenTofu</p>
</li>
<li><p><span style="color: SlateBlue"><strong>Day 9.</strong></span> Why should developers care about container security?: 컨테이너 보안</p>
</li>
<li><p><span style="color: SlateBlue"><strong>Day 10.</strong></span> Is Kubernetes Too Complicated?: 쿠버네티스란?</p>
</li>
<li><p><span style="color: SlateBlue"><strong>Day 14.</strong></span> An introduction to API Security in Kubernetes: 웹 애플리케이션 방화벽(WAF)</p>
</li>
<li><p><span style="color: SlateBlue"><strong>Day 17.</strong></span> From Chaos to Resilience: Decoding the Secrets of Production Readiness: 서비스메시와 Istio</p>
</li>
<li><p><span style="color: SlateBlue"><strong>Day 19.</strong></span> Building Efficient and Secure Docker Images with Multi-Stage Builds: 멀티스테이지 빌드 (Multi-Stage Build)</p>
</li>
<li><p><span style="color: SlateBlue"><strong>Day 22.</strong></span> Test in Production with Kubernetes and Telepresence: 텔레프레즌스</p>
</li>
<li><p><span style="color: SlateBlue"><strong>Day 24.</strong></span> DevSecOps - Defined, Explained &amp; Explored: DevSecOps란</p>
</li>
<li><p><span style="color: SlateBlue"><strong>Day 25.</strong></span> Kube-Nation: Exploring the Land of Kubernetes: 쿠버네티스 아키텍처의 비유화 (국가)</p>
</li>
<li><p><span style="color: SlateBlue"><strong>Day 27.</strong></span> From Automated to Automatic - Event-Driven Infrastructure Management with Ansible: Ansible이란?</p>
</li>
<li><p><span style="color: SlateBlue"><strong>Day 33.</strong></span> GitOps made simple with ArgoCD and GitHub Actions: ArgoCD와 Github Actions을 이용한 CI/CD</p>
</li>
<li><p><span style="color: SlateBlue"><strong>Day 38.</strong></span> Open Standards: Empowering Cloud-Native Innovation: OCI (Open Container Initiative)와 K8s Interface</p>
</li>
<li><p><span style="color: SlateBlue"><strong>Day 39.</strong></span> Is TLS in Kubernetes really that hard to understand?: 쿠버네티스 인증서</p>
</li>
<li><p><span style="color: SlateBlue"><strong>Day 43.</strong></span> Let&#39;s go sidecarless in Ambient Mesh: Istio와 Ambient Mesh</p>
</li>
<li><p><span style="color: SlateBlue"><strong>Day 44.</strong></span> Exploring Firecracker: AWS Lambda와 Firecracker</p>
</li>
<li><p><span style="color: SlateBlue"><strong>Day 50.</strong></span> State of Cloud Native 2024: 주목받은 CNCF 생태계 툴</p>
</li>
<li><p><span style="color: SlateBlue"><strong>Day 52.</strong></span> Creating a custom Dev Container for your GitHub Codespace to start with Terraform on Azure: Github Codespace와 Dev Container</p>
</li>
<li><p><span style="color: SlateBlue"><strong>Day 55.</strong></span> Bringing Together IaC and CM with Terraform Provider for Ansible: Terraform과 Ansible</p>
</li>
<li><p><span style="color: SlateBlue"><strong>Day 60.</strong></span> Migrating a monolith to Cloud-Native and the stumbling blocks that you don’t know about: 마이그레이션을 굳이 해야 하는가?</p>
</li>
<li><p><span style="color: SlateBlue"><strong>Day 63.</strong></span> Diving into Container Network Namespaces: 쿠버네티스 CNI를 리눅스 커널로 구현하기</p>
</li>
<li><p><span style="color: SlateBlue"><strong>Day 68.</strong></span> Service Mesh for Kubernetes 101: The Secret Sauce to Effortless Microservices Management: Service Mesh (Istio)</p>
</li>
<li><p><span style="color: SlateBlue"><strong>Day 69.</strong></span> Enhancing Kubernetes security, visibility, and networking control logic at the Linux kernel: eBPF와 Cilium (K8s CNI)이란?</p>
</li>
<li><p><span style="color: SlateBlue"><strong>Day 70.</strong></span> Simplified Cloud Adoption with Microsoft&#39;s Terraforms Azure Landing Zone Module: 랜딩존이란?</p>
</li>
<li><p><span style="color: SlateBlue"><strong>Day 72.</strong></span> Infrastructure as Code with Pulumi: Pulumi란?</p>
</li>
<li><p><span style="color: SlateBlue"><strong>Day 73.</strong></span> Introducing the Terraform Test Framework: IaC 테스트</p>
</li>
<li><p><span style="color: SlateBlue"><strong>Day 76.</strong></span> All you need to know about AWS CDK: AWS CDK</p>
</li>
<li><p><span style="color: SlateBlue"><strong>Day 80.</strong></span> Unlocking K8s Troubleshooting Best Practices with Botkube: Botkube</p>
</li>
<li><p><span style="color: SlateBlue"><strong>Day 83.</strong></span> Saving Cloud Costs Using Existing Prometheus Metrics: KRR (Kubernetes Resource Recommender)</p>
</li>
<li><p><span style="color: SlateBlue"><strong>Day 84.</strong></span> Hacking Kubernetes For Beginners: 쿠버네티스 해킹</p>
</li>
<li><p><span style="color: SlateBlue"><strong>Day 87.</strong></span> Hands-on Performance Testing with k6: k6</p>
</li>
<li><p><span style="color: SlateBlue"><strong>Day 89.</strong></span> Seeding Infrastructures: Merging Terraform with Generative AI for Effortless DevOps Gardens: 시딩 엔진 (Seeding Engine)</p>
</li>
</ul>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine2-마치며span"><span style='background-color: MediumAquamarine'>2. 마치며</span></h2>
<hr>

<p>이번 스터디는 단순히 툴의 사용법을 익히는 것을 넘어, 왜 해당 기술이 등장했고 어떤 아키텍처적 이점을 주는지 깊이 고민해 볼 수 있는 계기가 되었다. </p>
<p><strong>특히 인프라 자동화(Ansible, Terraform)와 보안, 네트워크(eBPF, Istio) 영역은 단편적인 이해를 넘어 실질적인 구축 경험이 필수적임을 느꼈다.</strong></p>
<p>이 리스트에 포함된 주제들은 일회성 학습으로 끝내지 않고, 실제 인프라 환경이나 앞으로 진행할 프로젝트에 지속적으로 적용해 보며 완전히 내 것으로 만들어갈 계획이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[90DaysOfDevOps (Day 91)]]></title>
            <link>https://velog.io/@taekyu_/90DaysOfDevOps-Day-91-9ze5y1uv</link>
            <guid>https://velog.io/@taekyu_/90DaysOfDevOps-Day-91-9ze5y1uv</guid>
            <pubDate>Sun, 08 Feb 2026 07:45:09 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>해당 스터디는 90DaysOfDevOps
<a href="https://github.com/MichaelCade/90DaysOfDevOps">https://github.com/MichaelCade/90DaysOfDevOps</a>
를 기반으로 진행한 내용입니다.</strong></p>
</blockquote>
<p>** Day91 - Team Topologies and Platform Engineering **</p>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine1-빠른-흐름의-본질과-역-컨웨이-전략의-필요성span"><span style='background-color: MediumAquamarine'>1. 빠른 흐름의 본질과 역 컨웨이 전략의 필요성</span></h2>
<hr>

<p>데브옵스는 소규모 자율 팀이 피드백을 기반으로 빠르게 소프트웨어를 배포하는 것의 가치를 증명했다. </p>
<p><strong>하지만 진정한 의미의 빠른 흐름을 달성하기 위한 조직적, 문화적 변화는 여전히 미진한 상태다.</strong> </p>
<p><span style="color: indianred"><strong>우리는 기술보다 팀 구조와 상호작용에 주목해야 한다.</strong></span></p>
<h3 id="span-stylecolor-slateblue1-1-흐름과-사회-기술적-복잡성span"><span style="color: SlateBlue">1-1. 흐름과 사회-기술적 복잡성</span></h3>
<p>*<em>여기서 말하는 흐름이란 단순히 코드를 빨리 짜는 것이 아니라, 코드가 프로덕션 환경에 도달하기까지의 경로에 있는 모든 장애물을 제거하는 것을 의미한다. *</em></p>
<p>이는 서로 다른 팀과 도메인 간의 의존성을 해결하고, 팀 외부로 뻗어 있는 복잡한 기술의 덤불을 정리하는 과정이다.</p>
<p><span style="color: indianred"><strong>즉, 빠른 흐름은 주변의 사회-기술적 환경 전체를 단순화하여 가치를 더 빠르고 일관되게 전달하는 것이다.</strong></span>
<img src="https://velog.velcdn.com/images/taekyu_/post/91786c79-7bdf-46a1-b80b-2a340abe622e/image.png" width="50%" height="n%"></p>
<p>이러한 환경의 복잡성은 벤더의 제약, 예산 문제, 기술의 역사적 상태, 기존 팀 구조의 한계, 기술 부채 등 타당하고 역사적인 이유들로 인해 발생한다. </p>
<p><strong>따라서 모든 조직은 고유하며, 남들이 하는 방식을 그대로 따르는 것이 아니라 각 조직의 맥락에 맞는 독자적인 접근 방식이 필요하다.</strong>
<br></p>
<h3 id="span-stylecolor-slateblue1-2-컨웨이의-법칙과-역-컨웨이-전략span"><span style="color: SlateBlue">1-2. 컨웨이의 법칙과 역 컨웨이 전략</span></h3>
<p>*<em>조직 구조를 논할 때 컨웨이의 법칙(Conway&#39;s Law)은 매우 중요하다. *</em>
<img src="https://velog.velcdn.com/images/taekyu_/post/6e8c1bd1-8f92-48eb-99b6-6a72fa10e8f6/image.png" width="50%" height="n%"></p>
<p><span style="color: indianred"><strong>이 법칙은 조직은 그 조직의 의사소통 구조를 반영한 시스템을 설계하게 된다는 것을 의미한다. 쉽게 말해 여러분은 조직도를 소프트웨어로 배포한다는 것이다.</strong></span></p>
<p>문제는 실제 업무 현장에서는 사람들이 깔끔한 조직도에 구애받지 않고, 일을 처리하기 위해 필요한 사람과 무분별하게 소통한다는 점이다.</p>
<p>이는 조직도가 실제 소프트웨어 전달 흐름을 돕기는커녕, 서로 필요한 사람들을 멀리 떨어뜨려 놓음으로써 상황을 악화시킬 수 있음을 시사한다.
<br></p>
<p><span style="color: indianred"><strong>팀 토폴로지는 이를 뒤집는 역 컨웨이 전략(Reverse Conway Maneuver)을 제안한다.</strong> </span></p>
<p><strong>이는 만들고자 하는 소프트웨어 아키텍처와 시장에 전달하려는 가치에 맞춰 조직도를 역으로 설계하는 것이다.</strong></p>
<p>소프트웨어의 설계는 더 이상 팀의 설계와 분리될 수 없다. </p>
<p><strong>기술보다 팀을 우선순위에 두고, 팀 간의 불필요한 의존성을 줄여야 더 좋은 소프트웨어가 나온다는 것이 이 전략의 핵심이다.</strong></p>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine2-인지-부하의-한계와-플랫폼-팀의-역할span"><span style='background-color: MediumAquamarine'>2. 인지 부하의 한계와 플랫폼 팀의 역할</span></h2>
<hr>

<p>*<em>팀 토폴로지에서 가장 중요한 개념 중 하나는 팀이 감당할 수 있는 인지 부하(Cognitive Load)를 관리하는 것이다. *</em></p>
<p>이는 플랫폼 엔지니어링이 등장하게 된 직접적인 배경이 된다.</p>
<h3 id="span-stylecolor-slateblue2-1-스트림-정렬-팀stream-aligned-teams의-딜레마span"><span style="color: SlateBlue">2-1. 스트림 정렬 팀(Stream-aligned Teams)의 딜레마</span></h3>
<p><strong>스트림 정렬 팀은 비즈니스 가치 흐름에 맞춰 구성된 팀으로, 회사의 수익 창출에 가장 가까이 위치한다.</strong></p>
<p>이들은 상류 의존성은 적고 하류 의존성은 많은 특징을 가진다.</p>
<p>하지만 이 팀들이 모든 것을 다 할 수는 없다. </p>
<p><span style="color: indianred"><strong>던바의 수(Dunbar&#39;s number)에 따르면 인간이 맺을 수 있는 사회적 관계에는 한계가 있으며, 이는 팀이 감당할 수 있는 인지 부하의 총량에도 한계가 있음을 의미한다.</strong></span></p>
<p>스트림 정렬 팀이 비즈니스 로직 개발뿐만 아니라 클라우드 인프라, 네트워킹, 보안, CI/CD 등 모든 기술적 세부 사항까지 신경 써야 한다면, 그들의 인지 부하는 한계치를 초과하게 되고 결국 비즈니스 가치 전달 속도는 느려질 수밖에 없다.
<br></p>
<h3 id="span-stylecolor-slateblue2-2플랫폼-팀platform-teams-인지-부하를-줄이는-해결사span"><span style="color: SlateBlue">2-2.플랫폼 팀(Platform Teams): 인지 부하를 줄이는 해결사</span></h3>
<p><span style="color: indianred"><strong>여기서 플랫폼 팀이 등장한다.</strong></span></p>
<p><strong>플랫폼 팀의 존재 목적은 하부 인프라(클라우드, K8s, IAM, DB 등)를 담당하여 스트림 정렬 팀이 비즈니스 문제 해결에만 집중할 수 있도록 돕는 것이다.</strong></p>
<ul>
<li><p><span style="color: indianred"><strong>자동차와 타이어의 비유:</strong></span> 자동차 제조사(스트림 정렬 팀)는 타이어를 직접 만들지 않고 전문 타이어 제조사(플랫폼 팀)의 제품을 받아 조립한다. 제조사는 여전히 타이어를 차에 장착할 책임이 있지만, 고무의 배합 비율이나 트레드 패턴 제조법까지 알 필요는 없다.</p>
</li>
<li><p><span style="color: indianred"><strong>DevOps의 유지:</strong></span> 이는 Dev과 Ops을 다시 분리하는 것이 아니다. &quot;직접 만든 사람이 직접 운영한다(You build it, you run it)&quot;는 원칙은 여전히 유효하다. 다만, 운영하기 쉬운 컴포넌트를 제공받아 운영의 난이도를 낮추는 것이다.</p>
<br>

</li>
</ul>
<h3 id="span-stylecolor-slateblue2-3-플랫폼-팀이-제공하는-가치와-상호작용-모드span"><span style="color: SlateBlue">2-3. 플랫폼 팀이 제공하는 가치와 상호작용 모드</span></h3>
<p><strong>플랫폼 팀은 복잡한 기술을 추상화하여 서비스로서(As-a-Service) 제공한다.</strong></p>
<ol>
<li><p><span style="color: indianred"><strong>속도와 품질:</strong></span> 이미 검증된 상용 컴포넌트를 사용하므로 직접 구축할 필요가 없어 전달 속도가 빨라지고 품질이 보장된다.</p>
</li>
<li><p><span style="color: indianred"><strong>자율성:</strong></span> API와 문서 기반의 셀프 서비스로 제공되므로, 플랫폼 팀에 티켓을 보내거나 회의를 요청할 필요 없이 즉시 사용할 수 있다. (AWS를 쓸 때 아마존 직원에게 허락받지 않는 것과 같다.)</p>
</li>
<li><p><span style="color: indianred"><strong>조력(Facilitating):</strong></span> 서비스 제공 외에도, 플랫폼 팀의 전문가는 스트림 정렬 팀이 해당 서비스를 잘 활용할 수 있도록 코칭하고 베스트 프랙티스를 공유하는 &#39;조력자&#39; 역할을 수행한다. 이는 일시적인 상호작용이다.</p>
</li>
</ol>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine3-슬로이피케이션slow-ification과-지속적-진화span"><span style='background-color: MediumAquamarine'>3. 슬로이피케이션(Slow-ification)과 지속적 진화</span></h2>
<hr>

<p><strong>플랫폼 엔지니어링을 조직에 성공적으로 도입하기 위해서는 단순한 기술적 구현을 넘어선 전략적 접근과 리더십의 지원이 필수적이다.</strong></p>
<h3 id="span-stylecolor-slateblue3-1시작점-찾기-공통된-과제span"><span style="color: SlateBlue">3-1.시작점 찾기: 공통된 과제</span></h3>
<p>시작은 여러 팀이 공통적으로 겪고 있는 고통스러운 지점(Pain Point)을 찾는 것이다. </p>
<p>보통 기술 스택의 하단부인 컨테이너, 가상 머신(VM), 데이터베이스, IAM 등이 좋은 후보가 된다.</p>
<p><strong>적은 수의 팀이 의존하는 작은 컴포넌트부터 시작하여 성공 사례를 만들고, 이를 조직 전체로 확산시키는전략이 유효하다.</strong>
<br></p>
<h3 id="span-stylecolor-slateblue3-2핵심-전략-슬로이피케이션slow-ificationspan"><span style="color: SlateBlue">3-2.핵심 전략: 슬로이피케이션(Slow-ification)</span></h3>
<p>*<em>발표자는 책 *『Wiring the Winning Organization』</em>에서 소개된 &#39;슬로이피케이션(Slow-ification)&#39; 개념을 강조한다. **
<img src="https://velog.velcdn.com/images/taekyu_/post/7df96508-b493-44c9-8d49-4e06e4d6a69b/image.png" width="50%" height="n%"></p>
<p><strong>이는 &quot;속도를 높이기 위해 일시적으로 속도를 늦추는 것&quot;을 의미한다.</strong></p>
<ul>
<li><p><span style="color: indianred"><strong>시스템 2 사고(System 2 Thinking):</strong></span> 일상적인 업무 처리(시스템 1 사고)를 멈추고, 문제의 근본적인 해결책을 만들기 위해 깊고 창의적이며 노력이 필요한 사고(시스템 2 사고)를 해야 한다. 이는 톱질을 멈추고 톱날을 가는 시간과 같다.</p>
</li>
<li><p><span style="color: indianred"><strong>리더십의 지원:</strong></span> 이 과정은 외부에서 볼 때 &quot;당장 필요한 일을 안 하고 있는 것&quot;처럼 보일 수 있다. 따라서 매니저의 강력한 하향식(Top-down) 지원과 보호가 없으면, 팀은 압박을 이기지 못하고 다시 예전의 비효율적인 방식으로 돌아가게 된다.</p>
<br>

</li>
</ul>
<h3 id="span-stylecolor-slateblue3-3진화하는-조직과-도메인-주도-설계dddspan"><span style="color: SlateBlue">3-3.진화하는 조직과 도메인 주도 설계(DDD)</span></h3>
<p>*<em>팀 구조와 책임은 한 번 정하면 끝나는 정적인 것이 아니다. *</em></p>
<p><strong>조직이 성숙하고 시장 상황이 변함에 따라 팀 토폴로지는 계속 진화해야 한다.</strong></p>
<p>완벽한 팀 구조는 존재하지 않으며, 현실은 언제나 복잡하고 중복과 비효율이 존재한다. 중요한 것은 완벽함이 아니라 시간이 지남에 따라 흐름이 개선되는 방향성이다.</p>
<p><span style="color: indianred"><strong>조직이 커질수록 올바른 팀 경계를 찾기 위해 도메인 주도 설계(DDD)를 활용해야 한다.</strong> </span>
<img src="https://velog.velcdn.com/images/taekyu_/post/9d4b01f3-7715-4e2a-ac05-bf6829f96773/image.png" alt=""></p>
<p>특히 <strong>&#39;독립 서비스 휴리스틱(Independent Service Heuristic)&#39;</strong>을 활용해 볼 수 있다. </p>
<p>이는 &quot;이 컴포넌트나 기능을 독립적인 SaaS 서비스로 판매할 수 있는가?&quot;라고 자문해 봄으로써 팀과 도메인의 경계를 명확히 설정하는 데 도움을 준다.</p>
<p>결론적으로, 팀 토폴로지와 플랫폼 엔지니어링은 인지 부하를 관리하고 빠른 흐름을 만들어내기 위해 조직 구조와 기술 아키텍처를 일치시키는 끊임없는 여정이다.</p>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine4-결론span"><span style='background-color: MediumAquamarine'>4. 결론</span></h2>
<hr>

<p><strong>팀 토폴로지와 플랫폼 엔지니어링은 단순한 조직 개편이 아니라, 인지 부하를 관리하여 지속 가능한 빠른 흐름을 만드는 전략이다.</strong></p>
<p>완벽한 구조는 없기에, 조직은 끊임없이 진화하며 상황에 맞는 유연한 팀 구조를 찾아야 한다.</p>
<p><span style="color: indianred"></span>
<span style="color: SlateBlue"></span></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[90DaysOfDevOps (Day 90)]]></title>
            <link>https://velog.io/@taekyu_/90DaysOfDevOps-Day-90-42nkvx25</link>
            <guid>https://velog.io/@taekyu_/90DaysOfDevOps-Day-90-42nkvx25</guid>
            <pubDate>Sun, 08 Feb 2026 06:41:59 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>해당 스터디는 90DaysOfDevOps
<a href="https://github.com/MichaelCade/90DaysOfDevOps">https://github.com/MichaelCade/90DaysOfDevOps</a>
를 기반으로 진행한 내용입니다.</strong></p>
</blockquote>
<p>** Day 90 - Fighting fire with fire: Why we cannot always prevent technical issues with more tech **</p>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine1-무계획적-기술도입과-과잉-엔지니어링-span"><span style='background-color: MediumAquamarine'>1. 무계획적 기술도입과 과잉 엔지니어링 </span></h2>
<hr>

<p>새로운 기능을 개발하거나 버그를 수정해야 할 때, 우리는 종종 계획이나 문서화 단계를 건너뛰고 곧바로 코드를 짜거나 도구를 도입하려는 유혹에 빠진다. </p>
<p><strong>이러한 &#39;일단 만들자&#39; 식의 접근은 초기에는 빠르게 보일지 모르나, 장기적으로는 심각한 부작용을 초래한다.</strong></p>
<h3 id="span-stylecolor-slateblue1-1-이해할-수-없는-인프라와-전이적-의존성span"><span style="color: SlateBlue">1-1. 이해할 수 없는 인프라와 전이적 의존성</span></h3>
<p>문서화 없이 즉흥적으로(Ad-hoc) 문제를 해결하다 보면, 우리의 인프라는 마치 아이들의 장난감 블록처럼 변해버린다. </p>
<p><span style="color: indianred"><strong>여러 구성 요소가 연결은 되어 있지만, 도대체 어떻게, 왜 연결되어 있는지 아무도 명확히 설명할 수 없는 상태가 되는 것이다.</strong></span></p>
<p>이 과정에서 우리가 인지조차 하지 못하는 전이적 의존성 문제가 발생한다. 가장 대표적인 사례가 &#39;left-pad&#39; 사건이다.</p>
<ul>
<li><p><span style="color: indianred"><strong>사건 개요:</strong></span> 2016년, 한 개발자가 npm 및 &#39;Kick&#39;이라는 회사와의 이름 분쟁 끝에 홧김에 자신이 만든 패키지들을 npm에서 모두 삭제해버렸다.</p>
</li>
<li><p><span style="color: indianred"><strong>영향:</strong></span> 그중 &#39;left-pad&#39;라는 패키지는 공백을 포함해 고작 11~12줄짜리 코드에 불과했지만, 수많은 대형 프로젝트들이 이 패키지를 간접적으로 의존하고 있었기에 전 세계 인터넷 서비스들이 줄줄이 마비되는 사태가 벌어졌다.</p>
</li>
<li><p><span style="color: indianred"><strong>교훈:</strong></span> 내가 직접 사용하지 않더라도 내 프로젝트가 의존하는 도구가 또 다른 도구에 의존하고 있을 수 있다. 문서화와 파악이 되지 않은 의존성은 시한폭탄과 같다.</p>
<br>


</li>
</ul>
<h3 id="span-stylecolor-slateblue1-2-과잉-엔지니어링의-늪span"><span style="color: SlateBlue">1-2. 과잉 엔지니어링의 늪</span></h3>
<p>소프트웨어 개발 초기에는 기능적/비기능적 요구사항을 완벽히 알 수 없다. </p>
<p>문제는 원래 해결하려던 문제가 무엇이었는지에 대한 원본 기록이 없을 때 발생한다.</p>
<p>초기의 목적을 잊어버린 채 개발을 진행하다 보면, <span style="color: indianred"><strong>단순한 해결책이 존재함에도 불구하고 불필요하게 복잡하고 정교한 솔루션을 만드는 과잉 엔지니어링(Over-engineering)에 빠지게 된다.</strong></span></p>
<p><strong>이는 시스템의 복잡도를 불필요하게 높여 유지보수를 어렵게 만든다.</strong>
<br></p>
<h3 id="span-stylecolor-slateblue1-3-나한테-물어봐-문화의-위험성span"><span style="color: SlateBlue">1-3. &quot;나한테 물어봐&quot; 문화의 위험성</span></h3>
<p>*<em>문서화가 부재한 조직에서 흔히 발생하는 또 다른 문제는 구전에 의존하는 온보딩이다. *</em></p>
<p>팀장이나 시니어 엔지니어는 선의로 &quot;문서는 내 머릿속에 다 있으니 모르는 게 있으면 언제든 물어봐&quot;라고 말한다.</p>
<p><span style="color: indianred">*<em>하지만 이는 친절해 보일지 몰라도, 실제로는 주니어 엔지니어나 신규 입사자의 자기 주도성을 뺏는 행위다. *</em></span></p>
<p>스스로 정보를 찾아 해결할 수 없게 만듦으로써, 사소한 것 하나까지 질문해야 하는 의존적인 환경을 조성하고, 결국 팀 전체의 생산성을 저하시킨다.</p>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine2-문서화-주도-개발의-가치와-전략-span"><span style='background-color: MediumAquamarine'>2. 문서화 주도 개발의 가치와 전략 </span></h2>
<hr>

<p>*<em>프레젠테이션에서는 기술적 문제 해결의 대안으로 문서화 주도 소프트웨어 개발을 제안한다. *</em></p>
<p>문서화는 단순한 기록이 아니라, 비용을 절감하고 비즈니스 연속성을 보장하는 핵심 전략이다.</p>
<h3 id="span-stylecolor-slateblue2-1-문서화의-비즈니스적-가치span"><span style="color: SlateBlue">2-1. 문서화의 비즈니스적 가치</span></h3>
<p>*<em>장애는 곧 돈이다. *</em></p>
<p>2017년 영국 항공은 시스템 장애로 인해 약 8,000만 파운드(한화 약 1,300억 원 이상)의 손실을 입었다.</p>
<p><span style="color: indianred"><strong>잘 정리된 런북(Runbook)과 장애 대응 문서는 온콜(On-call) 담당자의 스트레스를 줄여주고, 대응 시간을 단축시킨다.</strong> </span></p>
<p>특히 미들 레벨 엔지니어가 야간 장애 대응 시, 언제 시니어를 호출해야 할지 명확한 기준을 제공함으로써 심리적 안정감을 주고 불필요한 호출을 줄여준다.
<br></p>
<h3 id="span-stylecolor-slateblue2-2-문서의-유형과-작성-전략span"><span style="color: SlateBlue">2-2. 문서의 유형과 작성 전략</span></h3>
<p><strong>문서는 크게 개발자 문서(Developer Documentation)와 사용자 문서(User Documentation)</strong>로 나뉜다. </p>
<p>문서를 작성할 때는 독자의 경험 수준과 문서의 목적(설치, 업그레이드, 기능 활용 등)을 반드시 고려해야 한다.</p>
<ul>
<li><p><span style="color: indianred"><strong>튜토리얼/하우투(How-to):</strong> </span> 특정 시나리오나 구현을 다루며, 수명이 짧을 수 있다.</p>
</li>
<li><p><span style="color: indianred"><strong>공식 문서:</strong> </span> 여러 유지보수자에 의해 장기간 관리되며, 보다 범용적인 설정을 다룬다.</p>
<br>

</li>
</ul>
<p><span style="color: SlateBlue"><strong>[추천 프레임워크: Diátaxis]</strong></span></p>
<p><strong>발표자는 문서 작성의 체계를 잡기 위해 Diátaxis 프레임워크를 추천한다.</strong> </p>
<p>이는 문서를 4가지로 구분하여 작성하는 방식이다.</p>
<ul>
<li><p><span style="color: indianred"><strong>튜토리얼(Tutorials):</strong> </span> 학습 지향 (직접 따라 하며 배우기)</p>
</li>
<li><p><span style="color: indianred"><strong>하우투 가이드(How-to Guides):</strong> </span> 문제 해결 지향 (특정 목표 달성)</p>
</li>
<li><p><span style="color: indianred"><strong>설명(Explanation):</strong> </span> 이해 지향 (배경 지식 및 개념 설명)</p>
</li>
<li><p><span style="color: indianred"><strong>레퍼런스(Reference):</strong> </span> 정보 지향 (API 명세 등)</p>
<br>

</li>
</ul>
<h3 id="span-stylecolor-slateblue2-3-기술을-활용한-문서화-k8sgptspan"><span style="color: SlateBlue">2-3. 기술을 활용한 문서화: K8sGPT</span></h3>
<p><strong>문서화에도 기술을 접목할 수 있다.</strong></p>
<p><img src="https://velog.velcdn.com/images/taekyu_/post/9280a6d0-db5c-4038-ba55-86c0ea08a33a/image.png" alt=""></p>
<p><strong>K8sGPT 같은 도구는 SRE의 지식을 코드로 내재화하고 AI를 활용하여 쿠버네티스 클러스터의 문제를 진단하고 설명해 준다.</strong></p>
<p>이는 마치 살아있는 문서처럼 작동하여, 주니어 엔지니어가 선배에게 묻지 않고도 에러의 원인과 해결책을 파악할 수 있게 돕는다.</p>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine3-카오스-엔지니어링-알-수-없는-것을-아는-것으로-span"><span style='background-color: MediumAquamarine'>3. 카오스 엔지니어링: 알 수 없는 것을 아는 것으로 </span></h2>
<hr>

<p>*<em>마지막으로, 문서화와 프로세스의 중요성은 카오스 엔지니어링(Chaos Engineering)에 적용될 때 더욱 빛을 발한다. *</em></p>
<p><span style="color: indianred"><strong>카오스 엔지니어링은 시스템에 의도적인 결함을 주입하여 회복 탄력성을 테스트하는 것이다.</strong></span></p>
<h3 id="span-stylecolor-slateblue3-1-지식의-4분면과-카오스-엔지니어링의-목표span"><span style="color: SlateBlue">3-1. 지식의 4분면과 카오스 엔지니어링의 목표</span></h3>
<p><strong>우리가 시스템에 대해 아는 지식은 네 가지 영역으로 나뉜다.</strong>
<img src="https://velog.velcdn.com/images/taekyu_/post/e42b2a2f-8fd0-4691-8448-115c5dfe0271/image.png" width="60%" height="n%"></p>
<ul>
<li><p><span style="color: indianred"><strong>Known Knowns (알고 있는 아는 것):</strong> </span> 우리가 인지하고 이해하는 영역. (문서화 대상)</p>
</li>
<li><p><span style="color: indianred"><strong>Known Unknowns (알고 있는 모르는 것):</strong> </span> 문제는 인지했지만 원인은 모르는 것. (예: 스토리지 레이어에 이슈가 있다는 건 알지만 정확한 원인은 모름)</p>
</li>
<li><p><span style="color: indianred"><strong>Unknown Knowns (모르고 있는 아는 것):</strong> </span> 이해는 하고 있지만 인지하지 못한 것. (직관이나 경험적으로 대처 가능하나 명시되지 않음)</p>
</li>
<li><p><span style="color: indianred"><strong>Unknown Unknowns (모르고 있는 모르는 것):</strong> </span> 가장 위험한 블랙박스 영역. 인지조차 못하고 있는 잠재적 위험.</p>
</li>
</ul>
<blockquote>
<p><strong>카오스 엔지니어링의 궁극적인 목표는 실험을 통해 Unknown Unknowns(블랙박스) 영역을 발견하고, 이를 실험을 통해 분석하여 Known Knowns(문서화된 지식) 영역으로 옮겨오는 것이다.</strong></p>
</blockquote>
<br>

<h3 id="span-stylecolor-slateblue3-2-실험의-프로세스와-사후-분석span"><span style="color: SlateBlue">3-2. 실험의 프로세스와 사후 분석</span></h3>
<p><strong>성공적인 카오스 엔지니어링을 위해서는 다음과 같은 체계적인 프로세스가 필요하다.</strong></p>
<ol>
<li><p><span style="color: indianred"><strong>매핑(Mapping):</strong> </span> 현재 인프라의 상태와 서비스 간의 내/외부 의존성을 시각화하여 파악한다.</p>
</li>
<li><p><span style="color: indianred"><strong>가설 수립 및 계획:</strong> </span> &quot;포드를 삭제하면 레플리카셋이 자동으로 복구할 것이다&quot;와 같은 가설을 세우고, 폭발 반경(Blast Radius)을 최소화하여 실험을 설계한다.</p>
</li>
<li><p><span style="color: indianred"><strong>실험 실행:</strong> </span> <code>Chaos Mesh</code>, <code>LitmusChaos</code> 등의 도구를 활용한다.</p>
</li>
<li><p><span style="color: indianred"><strong>사후 분석(Post-mortem):</strong> </span> 실험 후 무엇이 발생했고, 어떻게 대응했는지를 반드시 기록해야 한다. 이는 팀 전체의 학습 기회이자, 미래의 장애를 예방하는 자산이 된다. 
(도구: <code>incident.io</code>, <code>Rootly</code> 등 활용 가능)</p>
<br>

</li>
</ol>
<h3 id="span-stylecolor-slateblue3-3-결론-일단-적어라span"><span style="color: SlateBlue">3-3. 결론: &quot;일단 적어라&quot;</span></h3>
<p><strong>기술적 해결책을 찾기 전에 기록하는 습관을 들이라는 것이다.</strong></p>
<p>잘못된 내용을 적는 것이 아무것도 적지 않는 것보다 낫다. </p>
<p>과거에 작동했던 명령어가 지금은 작동하지 않을지라도, 그것은 미래의 팀원에게 훌륭한 단서가 된다. </p>
<p>거창한 문서가 아니더라도, 홈랩(Home lab)이나 개인 학습 노트부터 시작하여 자신이 겪은 문제와 해결 과정을 기록하고 공유하는 것이야말로 진정한 엔지니어링의 시작이다.</p>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine4-정리-및-결론-span"><span style='background-color: MediumAquamarine'>4. 정리 및 결론 </span></h2>
<hr>

<p>기술적 부채와 복잡성은 더 많은 도구가 아닌, 명확한 문서화와 체계적인 프로세스로 해결해야한다.</p>
<p><strong>&#39;일단 만들자&#39;는 유혹을 뿌리치고, 우리가 아는 것과 모르는 것을 기록하며 Unknown 영역을 줄여나가는 것이 진정한 엔지니어링이다.</strong></p>
<p>완벽한 문서를 쓰려 하기보다, 사소한 트러블슈팅 과정이라도 기록하고 공유하는 습관을 들여보자.</p>
<p><span style="color: indianred"><strong>이러한 기록들이 모여 팀의 리스크를 줄이고, 더 견고하고 회복 탄력성 있는 인프라를 만드는 가장 강력한 무기가 될 것이다.</strong></span></p>
<p><span style="color: indianred"></span>
<span style="color: SlateBlue"></span></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[90DaysOfDevOps (Day 89)]]></title>
            <link>https://velog.io/@taekyu_/90DaysOfDevOps-Day-89</link>
            <guid>https://velog.io/@taekyu_/90DaysOfDevOps-Day-89</guid>
            <pubDate>Sun, 08 Feb 2026 03:21:34 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>해당 스터디는 90DaysOfDevOps
<a href="https://github.com/MichaelCade/90DaysOfDevOps">https://github.com/MichaelCade/90DaysOfDevOps</a>
를 기반으로 진행한 내용입니다.</strong></p>
</blockquote>
<p>** Day 89 - Seeding Infrastructures: Merging Terraform with Generative AI for Effortless DevOps Gardens **</p>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine1-테라폼과-생성형-ai-결합-span"><span style='background-color: MediumAquamarine'>1. 테라폼과 생성형 AI 결합 </span></h2>
<hr>

<p><strong>기존의 Terraform 사용 방식이 인간이 코드를 한 줄씩 작성하고 검증하는 방식이었다면, 생성형 AI의 도입은 이 프로세스를 근본적으로 변화시킨다.</strong></p>
<ul>
<li><p><span style="color: indianred"><strong>코드 생성의 가속화:</strong></span> Amazon Bedrock의 Claude v2나 OpenAI의 GPT 모델들은 자연어 요구사항을 완벽한 테라폼 HCL(HashiCorp Configuration Language) 코드로 변환 가능</p>
</li>
<li><p><span style="color: indianred"><strong>단계별 추론:</strong></span> </p>
<ul>
<li><p>AI는 단순히 결과물만 내놓는 것이 아니라, &quot;먼저 VPC를 구성하고, 그 다음 서브넷을 나눈 뒤, EC2를 배치하라&quot;는 식의 순차적 사고가 가능함. </p>
</li>
<li><p>AutoGPT나 LangChain과 같은 프레임워크와 결합했을 때 더욱 강력해짐.</p>
</li>
</ul>
</li>
<li><p><span style="color: indianred"><strong><code>인간</code>-<code>AI</code>-<code>인프라</code>의 조화:</strong></span> AI가 초안을 빠르고 방대하게 작성하면, 인간은 이를 검수하고 전략적 의사결정을 내리는 구조로 업무가 재편</p>
</li>
</ul>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine2-seeding-engine이란-span"><span style='background-color: MediumAquamarine'>2. Seeding Engine이란? </span></h2>
<hr>

<p>*<em>우리가 흔히 사용하는 ChatGPT나 베드락(Bedrock) 콘솔은 기본적으로 수동적이다. *</em></p>
<p>개발자가 &quot;EC2 인스턴스 하나 만들어줘&quot;라고 입력해야만 비로소 코드를 뱉어낸다.</p>
<p><span style="color: indianred"><strong>하지만 시딩 엔진은 능동적이고 지속적이다.</strong></span></p>
<ul>
<li><p><span style="color: indianred"><strong>정의:</strong></span> 조직의 요구사항과 Best Practice를 입력받아, Terraform 코드를 생성(Seeding)할 뿐만 아니라, 배포된 인프라가 건강하게 유지되도록 지속적으로 관리하고 치유하는 통합 자동화 시스템</p>
</li>
<li><p><span style="color: indianred"><strong>비유:</strong></span> 마트에서 다 자란 화분을 사 오는 것이 아니라, 유능한 정원사(AI Engine)를 고용하여 씨앗을 심고, 물을 주고, 잡초를 뽑게 하는 것과 같다.</p>
</li>
</ul>
<p><strong>프레젠테이션에서는 해당 시스템이 단순한 질의응답 도구를 넘어선다고 강조한다.</strong></p>
<p>그 차별점은 다음 세 가지로 요약된다.</p>
<ol>
<li><p><span style="color: SlateBlue"><strong>Context을 이해하는 맞춤형 인프라 (RAG 활용)</strong></span></p>
<ul>
<li><p>일반적인 AI 모델은 인터넷에 있는 범용적인 지식으로 코드를 짠다. 하지만 시딩 엔진은 RAG(검색 증강 생성) 기술을 접목하여 조직의 특수성을 이해한다.</p>
</li>
<li><p><span style="color: indianred"><strong>일반 AI:</strong></span> &quot;보안 그룹 만들어줘&quot;라고 하면 0.0.0.0/0을 허용하는 등 범용적이지만 보안에 취약한 코드를 생성할 가능성이 있다.</p>
</li>
<li><p><span style="color: indianred"><strong>시딩 엔진:</strong></span> *<em>기업 내부의 보안 가이드라인, 기존 아키텍처 문서, 레거시 코드를 참조한다. *</em>
따라서 &quot;우리 회사 보안 정책인 22번 포트 폐쇄 및 사내 VPN 대역만 허용&quot;하는 규칙을 준수하며 코드를 생성한다. 즉, 조직의 가드레일 안에서 안전하게 작동한다.</p>
<br>
</li>
</ul>
</li>
<li><p><span style="color: SlateBlue"><strong>생성(Day 1)을 넘어선 운영(Day 2)의 자동화</strong></span></p>
<ul>
<li><p>코드를 짜고 배포하는 것에서 끝나는 것이 아니라, 인프라가 배포된 이후의 삶(Day 2 Operations)까지 관여한다.</p>
</li>
<li><p><span style="color: indianred"><strong>프로액티브 관리:</strong></span> 발표된 아키텍처에서 보았듯, 시딩 엔진은 CloudWatch 및 Lambda와 유기적으로 연결되어 있다.</p>
</li>
<li><p><span style="color: indianred"><strong>작동 시나리오:</strong></span> 트래픽이 급증하여 사이트가 느려지는 상황을 가정해 보자.</p>
<ol>
<li><p>CloudWatch가 이상 징후를 감지한다.</p>
</li>
<li><p>시딩 엔진(Lambda)으로 신호를 보낸다.</p>
</li>
<li><p>엔진이 이를 분석하여 &quot;Auto Scaling Group의 Max Capacity를 늘려야 합니다&quot;라고 판단한다.</p>
</li>
<li><p>테라폼 코드를 스스로 수정하고 재배포를 자동화한다.</p>
<br>
</li>
</ol>
</li>
</ul>
</li>
<li><p><span style="color: SlateBlue"><strong>인간과 AI의 협업 프로세스 내재화</strong></span></p>
<ul>
<li><p>시딩 엔진은 AI가 독단적으로 행동하지 않도록 설계된다.</p>
</li>
<li><p><span style="color: indianred"><strong>초안 작성:</strong></span> AI가 복잡한 인프라 구성의 80~90%를 순식간에 구축한다.</p>
</li>
<li><p><span style="color: indianred"><strong>인간의 검수:</strong></span> 엔지니어는 처음부터 코드를 짜는 것이 아니라, AI가 만든 구조가 전략적으로 맞는지, 보안상 치명적인 문제는 없는지 검토하는 역할로 전환된다.</p>
</li>
<li><p><span style="color: indianred"><strong>피드백 루프:</strong></span> 인간이 코드를 수정하면, 엔진은 이를 학습하여 다음 배포 때 더 정확한 코드를 생성한다.</p>
<br>


</li>
</ul>
</li>
</ol>
<p><strong>발표자는 이 개념을 실제 AWS 환경에 구현했다. 그 기술적 흐름은 다음과 같이 이루어진다.</strong>
<img src="https://velog.velcdn.com/images/taekyu_/post/e2591b15-7f89-40e6-bee2-e089cd5ddae4/image.png" width="70%" height="n%"></p>
<ol>
<li><p><span style="color: SlateBlue"><strong>입력:</strong></span> 엔지니어가 확장 가능한 웹 앱 인프라 필요라는 자연어 요구사항이나 설정 파일(JSON/YAML)을 입력한다.</p>
</li>
<li><p><span style="color: SlateBlue"><strong>생성 (Generation via Bedrock):</strong></span> EC2에 호스팅된 엔진(Python 애플리케이션)이 Amazon Bedrock(Claude v2)을 호출한다. 이때 프롬프트 엔지니어링을 통해 테라폼 HCL 문법과 사내 규칙을 준수하도록 강제한다.</p>
</li>
<li><p><span style="color: SlateBlue"><strong>검증 및 배포 :</strong></span> 생성된 코드는 S3에 저장되고, AWS CodePipeline을 통해 <code>terraform plan</code> -&gt; <code>validate</code> -&gt; <code>apply</code> 과정을 거친다.</p>
</li>
<li><p><span style="color: SlateBlue"><strong>감지 및 대응 :</strong></span> 인프라에 변경이 필요하거나 문제가 생기면, CloudWatch 알람이 다시 엔진(Lambda)을 깨워 코드를 수정하고 인프라를 치유한다.</p>
</li>
</ol>
<p><strong>발표에서 공개된 시딩 엔진의 아키텍처는 AWS의 관리형 서비스들을 유기적으로 결합하여 생성-배포-감지-대응의 완전한 파이프라인을 구축했다.</strong></p>
<ul>
<li><p><span style="color: SlateBlue"><strong>아키텍처 구성 요소 상세</strong></span></p>
<ul>
<li><p><span style="color: indianred"><strong>Amazon EC2 (Host):</strong></span> 시딩 엔진의 본체(Python 애플리케이션)가 구동되는 서버다. 전체 워크플로우를 조율하는 두뇌 역할을 한다.</p>
</li>
<li><p><span style="color: indianred"><strong>Amazon Bedrock (Generative AI):</strong></span> Anthropic Claude v2 모델을 사용하여, 보안성을 유지한 채 내부 네트워크로 AI 모델을 호출하고 테라폼 코드를 생성한다.</p>
</li>
<li><p><span style="color: indianred"><strong>Amazon S3 (Storage &amp; Trigger):</strong></span> 생성된 테라폼 템플릿(<code>.tf</code>)이 저장된다. 파일이 업로드되거나 수정되는 이벤트가 발생하면, 이를 트리거로 후속 자동화 작업이 시작된다.</p>
</li>
<li><p><span style="color: indianred"><strong>AWS CodeBuild &amp; CodePipeline (CI/CD):</strong></span> S3의 변경 사항을 감지하여 실제로 <code>terraform plan</code>, <code>terraform apply</code>를 수행하는 배포 파이프라인이다.</p>
</li>
<li><p><span style="color: indianred"><strong>Amazon CloudWatch &amp; AWS Lambda:</strong></span></p>
<ul>
<li><p>CloudWatch는 인프라의 상태를 실시간으로 감시한다.</p>
</li>
<li><p>이상이 감지되면 Lambda가 트리거되어, EC2에 있는 시딩 엔진에게 &quot;코드를 수정하라&quot;는 신호를 보내거나 즉각적인 복구 스크립트를 실행한다. </p>
</li>
</ul>
</li>
<li><p><span style="color: indianred"><strong>AWS Secrets Manager:</strong></span> Bedrock API 키나 DB 접근 정보 등 민감한 데이터를 코드에서 분리하여 안전하게 관리한다.</p>
</li>
</ul>
</li>
</ul>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine3-프롬프트-엔지니어링과-코드-구현-전략-span"><span style='background-color: MediumAquamarine'>3. 프롬프트 엔지니어링과 코드 구현 전략 </span></h2>
<hr>

<p><strong>시딩 엔진의 아키텍처를 갖췄다면, 이제는 이 엔진을 실제로 똑똑하게 움직이게 할 두뇌를 설계해야 한다.</strong></p>
<p><span style="color: indianred"><strong>프레젠테이션에서는 이 과정에서 가장 중요한 것은 AI에게 작업을 지시하는 프롬프트 엔지니어링과 결과를 검증하는 피드백 루프라고 강조한다.</strong></span></p>
<p>AI의 창의성을 기술적으로 통제하고, 결과물을 엄격하게 검증하는 과정이 없다면 자동화는 오히려 리스크가 될 수 있기 때문이다.</p>
<ul>
<li><p><span style="color: indianred"><strong>구체적 프롬프트:</strong></span> &quot;웹 앱 만들어줘&quot; 같은 추상적 지시는 지양한다. 대신 &quot;EC2, Auto Scaling Group, ELB, RDS, S3, IAM Role, VPC 등을 포함한 아키텍처&quot;와 같이 필요한 구성 요소를 명확히 나열하여 청사진을 제시해야 AI가 정확한 코드를 작성한다.</p>
</li>
<li><p><span style="color: indianred"><strong>파라미터 튜닝:</strong></span> 인프라 코드는 소설이 아니므로 창의성보다는 팩트와 정확성이 필수다. 모델의 <code>Temperature</code>와 <code>Top_P</code> 값을 0에 가깝게 설정하여, 존재하지 않는 리소스를 만들어내는 Hallucination 현상을 차단하고 일관된 결과를 얻어야 한다.</p>
</li>
<li><p><span style="color: indianred"><strong>파이썬 구현:</strong></span> <code>boto3 라이브러리</code>로 <code>Bedrock(Claude v2)</code>을 호출하고, 응답받은 JSON에서 코드를 추출해 <code>.tf</code> 파일로 저장한다. 이후 <code>subprocess</code> 모듈로 <code>terraform init</code>과 <code>apply</code>를 연쇄 실행하여 코드 작성부터 배포까지를 하나의 함수로 자동화한다.</p>
</li>
<li><p><span style="color: indianred"><strong>자동화된 테스트:</strong></span> 문법적으로 옳은 코드가 보안적으로도 안전하다는 보장은 없다. 배포 전 <code>Checkov</code>, <code>TFLint</code>, <code>Terratest</code> 같은 도구를 파이프라인에 심어 SSH 포트 개방이나 리소스 제한 같은 정책 위반을 기계적으로 걸러내야 한다.</p>
</li>
<li><p><span style="color: indianred"><strong>인간 개입:</strong></span> 기계적인 검증을 넘어 조직적인 피드백이 필요하다. 데이터 분석가나 아키텍트 등 다양한 직군의 요구사항을 반영해 프롬프트를 개선하고, 특히 민감한 보안 설정은 최종적으로 인간의 승인을 거치도록 프로세스를 설계해야 한다.</p>
</li>
</ul>
<p>이처럼 시딩 엔진은 단순히 코드를 생성하는 것을 넘어, <strong>&quot;믿어라, 하지만 검증하라(Trust, but Verify)&quot;</strong>는 원칙 아래 정교한 지시와 다층적인 검증 과정을 거쳐야 신뢰할 수 있는 시스템으로 완성된다.</p>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine4-케이스-스터디-및-결론-span"><span style='background-color: MediumAquamarine'>4. 케이스 스터디 및 결론 </span></h2>
<hr>

<p>프레젠에이션에서는 급성장하는 웹사이트 프로젝트에 이 시딩 엔진을 도입했다. </p>
<p>*<em>그 결과 트래픽 변동에 따라 인프라가 자동으로 확장되는 Adaptive Scaling 환경을 구축했으며, 다운타임과 유지보수 비용을 획기적으로 절감했다. *</em></p>
<p>엔지니어는 단순 코딩 시간은 줄이고 전략적 설계에 집중할 수 있게 되었다.</p>
<p><span style="color: SlateBlue"><strong>핵심 요약</strong></span></p>
<ul>
<li><p><span style="color: indianred"><strong>나만의 엔진 구축:</strong></span> 베드락과 같은 파운데이션 모델을 그대로 쓰지 말고, 조직의 데이터와 가이드라인을 학습시켜 최적화해야 한다.</p>
</li>
<li><p><span style="color: indianred"><strong>협업 AI :</strong></span> 미래의 데브옵스는 AI가 인간을 대체하는 것이 아니라, 인간이 AI라는 강력한 파트너를 감독하고 협업하는 형태로 진화할 것이다.</p>
</li>
<li><p><span style="color: indianred"><strong>보안 타협 금지:</strong></span> 자동화의 편리함에 취해 보안을 놓치면 안 된다. 보안 검수는 AI와 인간의 이중 레이어로 철저히 수행되어야 한다.</p>
</li>
</ul>
<p><span style="color: indianred">*<em>결론적으로, 시딩 엔진은 인프라를 심고 스스로 자라나게 관리하는 새로운 데브옵스의 지평을 열고 있다. *</em></span></p>
<p><strong>이제 엔지니어는 Coder에서 AI 엔진의 설계자(Architect)로 역할을 확장해야 할 때다.</strong></p>
<p><span style="color: indianred"></span>
<span style="color: SlateBlue"></span></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[90DaysOfDevOps (Day 88)]]></title>
            <link>https://velog.io/@taekyu_/90DaysOfDevOps-Day-88-8x1dn2jm</link>
            <guid>https://velog.io/@taekyu_/90DaysOfDevOps-Day-88-8x1dn2jm</guid>
            <pubDate>Sat, 07 Feb 2026 06:56:43 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>해당 스터디는 90DaysOfDevOps
<a href="https://github.com/MichaelCade/90DaysOfDevOps">https://github.com/MichaelCade/90DaysOfDevOps</a>
를 기반으로 진행한 내용입니다.</strong></p>
</blockquote>
<p>** Day 88 - What Developers Want from Internal Developer Portals **</p>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine1-idp의-배경-span"><span style='background-color: MediumAquamarine'>1. IDP의 배경 </span></h2>
<hr>

<p><strong>지난 5년 동안 마이크로서비스, 쿠버네티스, 클라우드 등의 기술은 조직의 효율성을 높여주었지만, 동시에 그만큼의 부작용인 복잡성을 초래했다.</strong></p>
<ul>
<li><p><span style="color: indianred"><strong>마이크로서비스와 쿠버네티스:</strong></span> 병렬 작업을 가능하게 하여 배포 속도를 높였지만, 너무 많은 서비스가 파편화되면서 소유권을 파악하고 전체 생태계를 이해하는 것이 불가능에 가까워졌다.</p>
</li>
<li><p><span style="color: indianred"><strong>클라우드 호스팅:</strong></span> 리소스 생성이 쉬워지고 비용이 저렴해졌지만, 이로 인해 정체를 알 수 없는 리소스가 방치되는 Sprawl 현상이 발생했다.</p>
</li>
<li><p><span style="color: indianred"><strong>도구의 홍수:</strong></span> 더 빠른 개발을 위해 수많은 도구를 도입했지만, 잦은 컨텍스트 스위칭과 알림 공해로 인해 오히려 개발자의 집중력을 저해하고 있다.</p>
</li>
</ul>
<br>

<p><span style="color: SlateBlue"><strong>기존 도구들의 한계: Context의 실종</strong></span></p>
<p>현재 시장의 도구들은 각자의 영역에서는 훌륭하지만, 전체를 꿰뚫는 통찰력을 제공하지 못한다.</p>
<ul>
<li><p><span style="color: indianred"><strong>APM/모니터링 (Datadog 등):</strong></span> Quality를 측정하지만, 해당 서비스가 무엇이며 왜 존재하는지에 대한 맥락은 없다.</p>
</li>
<li><p><span style="color: indianred"><strong>엔지니어링 메트릭 (DORA 등):</strong></span> 병목 현상은 알려주지만, 데이터가 정적이라 실제 Action으로 이어지기 어렵다.</p>
</li>
<li><p><span style="color: indianred"><strong>워크플로우 도구 (Git, Jira):</strong></span> 프로세스를 관리하지만, 우리가 올바른 방식으로 구축하고 있는지에 대한 가시성은 부족하다.</p>
</li>
</ul>
<p>결국 가장 큰 문제는 이 모든 파편화된 정보를 하나로 연결하여 <strong>&quot;이 서비스는 누가 만들었고, 어떤 리소스와 연결되며, 현재 상태는 어떠한가?&quot;</strong>를 보여주는 Entity Graph의 부재다. </p>
<p><span style="color: indianred"><strong>IDP는 바로 이 끊어진 연결고리를 잇는 역할을 해야 한다.</strong></span></p>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine2-개발자가-idp에서-진정으로-원하는-3가지-span"><span style='background-color: MediumAquamarine'>2. 개발자가 IDP에서 진정으로 원하는 3가지 </span></h2>
<hr>

<p><strong>Cortex가 50명의 엔지니어를 대상으로 &quot;IDP에서 무엇을 원하는가?&quot;를 설문한 결과, 개발자들의 고통과 니즈는 다음 세 가지로 명확히 요약된다.</strong></p>
<ol>
<li><p><span style="color: SlateBlue"><strong>정보 탐색의 고통</strong></span></p>
<ul>
<li><p>개발자들은 서비스, 리소스, 인프라 정보가 수십 개의 도구에 흩어져 있어 필요한 정보를 찾는 데 엄청난 시간을 낭비한다.</p>
</li>
<li><p><span style="color: indianred"><strong>온보딩의 어려움:</strong></span> 신규 입사자가 전체 시스템 구조를 파악하는 데 오랜 시간이 걸린다.</p>
</li>
<li><p><span style="color: indianred"><strong>장애 대응 지연:</strong></span> 한밤중에 모르는 서비스의 알림을 받았을 때, 소유자가 누구인지, Runbook은 어디에 있는지 찾을 수 없다.</p>
</li>
<li><p><span style="color: indianred"><strong>협업의 장벽:</strong></span> 내 기능이 다른 팀의 서비스와 상호작용할 때, 누구와 소통해야 하는지 파악하기 어렵다.</p>
</li>
</ul>
</li>
<li><p><span style="color: SlateBlue">*<em>우선순위의 불명확성 *</em></span></p>
<ul>
<li><p>보안(Snyk), 모니터링(Datadog), 기획(Jira), 코드 품질(SonarQube) 등 사방에서 알림과 요구사항이 쏟아진다.</p>
</li>
<li><p>이 소음 속에서 개발자는 <strong>&quot;지금 당장 내가 무엇을 먼저 처리해야 하는가?&quot;</strong>를 판단하기 어렵다. </p>
</li>
<li><p>개발자는 주관적인 판단이 아닌, 조직 차원의 명확한 우선순위 가이드를 원한다.</p>
</li>
</ul>
</li>
<li><p><span style="color: SlateBlue">*<em>템플릿과 셀프 서비스 *</em></span></p>
<ul>
<li><p><span style="color: indianred"><strong>개발자가 꼽은 IDP의 가장 필요한 기능 1위는 템플릿(Templates)이다.</strong></span></p>
</li>
<li><p>개발자는 새로운 프로젝트를 시작할 때마다 복잡한 초기 설정을 반복하고 싶어 하지 않는다.</p>
</li>
<li><p>조직의 Best Practice가 이미 적용된 골든 패스(Golden Path)를 통해, 버튼 클릭 몇 번으로 즉시 개발 가능한 환경을 구축하기를 원한다.</p>
</li>
</ul>
</li>
</ol>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine3-cortex의-해법-실행-가능성-중심의-기능span"><span style='background-color: MediumAquamarine'>3. Cortex의 해법: 실행 가능성 중심의 기능</span></h2>
<hr>

<p><span style="color: indianred">*<em>개발자 포털이 실패하는 가장 큰 이유는 정보의 무덤이 되기 때문이다. *</em></span></p>
<p>Cortex는 정보 제공을 넘어 <strong>그래서 지금 무엇을 해야 하는가?</strong>에 대한 답을 주고, 개발자가 즉시 행동할 수 있도록 돕는 5가지 핵심 기능을 제공한다.</p>
<ol>
<li><p><span style="color: SlateBlue"><strong>기록 시스템 (System of Record)과 자동 발견</strong></span></p>
<ul>
<li><p>많은 조직이 엑셀이나 Wiki에 서비스 목록을 수동으로 정리하지만, 이는 금방 낡은 데이터가 된다. Cortex는 이를 자동화한다.</p>
</li>
<li><p><span style="color: indianred"><strong>진실의 원천 (Source of Truth):</strong></span> Cortex는 기존 도구(Jira, GitHub, Snyk 등)를 대체하지 않는다. <strong>대신 50개 이상의 도구와 연동하여 현재 상태가 가장 정확한 데이터를 실시간으로 가져온다.</strong> 팀 정보는 HR 시스템(Workday)에서, 보안 정보는 보안 도구(Snyk)에서 가져와 하나로 합친다.</p>
</li>
<li><p><span style="color: indianred"><strong>자동 발견:</strong></span> 개발자가 일일이 &quot;이 서비스는 제가 만들었어요&quot;라고 입력할 필요가 없다. Cortex가 도구 간의 관계를 분석하여 <strong>&quot;이 GitHub 레포지토리는 저 쿠버네티스 클러스터에 배포되어 있고, 소유권은 A팀에게 있다&quot;</strong>는 의존성 그래프를 자동으로 생성하고 최신 상태로 유지한다.</p>
<br>
</li>
</ul>
</li>
<li><p><span style="color: SlateBlue">*<em>강력한 쿼리 빌더 *</em></span></p>
<ul>
<li><p>수집된 데이터가 단순히 쌓여만 있으면 의미가 없다. Cortex는 흩어진 데이터를 조합하여 복잡한 질문에 즉시 답할 수 있는 능력을 제공한다.</p>
</li>
<li><p><span style="color: indianred"><strong>복합 질문 해결:</strong></span> 과거에는 보안팀, 인프라팀, 개발팀에게 각각 물어봐야 했던 질문을 한 번에 해결한다.</p>
</li>
<li><p><span style="color: indianred"><strong>실전 예시:</strong></span></p>
<ul>
<li><p>&quot;Log4j 취약점 버전이 포함된 라이브러리를 쓰면서(보안), 최근 24시간 동안 배포되지 않은(인프라) 서비스 목록을 줘.&quot;</p>
</li>
<li><p>&quot;개인정보(PII)가 포함된 DB를 사용하는데(데이터), 백업 설정이 없는(인프라) 위험한 서비스는 어디인가?&quot;</p>
<br>
</li>
</ul>
</li>
</ul>
</li>
<li><p><span style="color: SlateBlue">*<em>스코어카드(Scorecards)와 게임화 *</em></span></p>
<ul>
<li><p>조직의 표준을 지키라고 강요하는 대신, 게임처럼 레벨을 올리도록 유도하는 기능이다. 리더십에게는 현황판이 되고, 개발자에게는 명확한 가이드가 된다.</p>
</li>
<li><p><span style="color: indianred"><strong>표준의 자동화:</strong></span> 배포 전 체크리스트 같은 문서를 자동화된 규칙으로 만든다. 사람이 검사하는 것이 아니라, 시스템이 데이터(테스트 커버리지, 보안 점수 등)를 보고 자동으로 점수를 매긴다.</p>
</li>
<li><p><span style="color: indianred"><strong>단계별 성숙도:</strong></span> 등급을 나누어 동기를 부여한다.</p>
<ul>
<li><p>🥉 <strong>브론즈 (기본):</strong> 서비스 소유자가 등록되어 있고, Readme 파일이 있는가?</p>
</li>
<li><p>🥈 <strong>실버 (중간):</strong> 유닛 테스트가 자동화되어 있고, 당번(On-call) 로테이션이 설정되어 있는가?</p>
</li>
<li><p>🥇 <strong>골드 (최상):</strong> SLO(서비스 수준 목표)를 추적하고 있으며, 치명적인 취약점이 0개인가?</p>
</li>
</ul>
</li>
<li><p><span style="color: indianred"><strong>리더십 리포팅:</strong></span> 경영진은 &quot;우리 조직의 서비스 건전성이 지난달 대비 얼마나 좋아졌는가?&quot;, &quot;어떤 팀이 리스크가 가장 높은가?&quot;를 데이터로 한눈에 파악할 수 있다.</p>
<br>
</li>
</ul>
</li>
<li><p><span style="color: SlateBlue"><strong>Self-Service와 스캐폴더(Scaffolder)</strong></span></p>
<ul>
<li><p>*<em>개발자가 가장 원하는 기능 1위 *</em></p>
</li>
<li><p><strong>새로운 프로젝트를 시작할 때, 고민하지 않고 버튼 하나로 완벽한 환경을 만드는 것</strong></p>
</li>
<li><p><span style="color: indianred"><strong>스캐폴더 (Scaffolder):</strong></span> 발판이라는 뜻처럼, 프로젝트의 뼈대를 자동으로 만들어준다. 오픈 소스인 쿠키커터(Cookiecutter)를 기반으로 하여 보일러플레이트(기본 코드) 생성을 자동화한다.</p>
</li>
<li><p><span style="color: indianred"><strong>피드백 루프와 골든 패스:</strong></span> 단순히 코드만 생성하는 게 아니다. Cortex의 템플릿을 사용하면 <strong><code>Git 저장소 생성 + 기본 코드 작성 + 카탈로그 등록</code></strong>이 한 번에 끝난다.</p>
</li>
<li><p><span style="color: indianred"><strong>동기 부여:</strong></span> 개발자에게 <strong>&quot;이 템플릿을 쓰면, 스코어카드에서 자동으로 실버등급을 달성합니다&quot;</strong>라고 안내한다. 개발자는 편해서 좋고, 조직은 자연스럽게 표준을 준수시킬 수 있다.</p>
<br>
</li>
</ul>
</li>
<li><p><span style="color: SlateBlue"><strong>개발자 홈페이지와 플러그인</strong></span></p>
<ul>
<li><p>개발자에게 불필요한 알림을 줄이고, 나에게 중요한 정보만 보여주는 개인화된 공간이다.</p>
</li>
<li><p><span style="color: indianred"><strong>개인화된 대시보드:</strong></span> 로그인하자마자 &quot;내가 당번인가?&quot;, &quot;내가 리뷰해야 할 코드는 무엇인가?&quot;, &quot;내 서비스 점수는 몇 점인가?&quot;를 보여준다. 또한 Slack 등으로 <strong>&quot;지금 이것부터 처리하세요&quot;</strong>라고 넛지(Nudge, 부드러운 개입)를 보내 행동을 유도한다.</p>
</li>
<li><p><span style="color: indianred"><strong>플러그인 아키텍처:</strong></span> Cortex가 기본으로 제공하지 않는 기능도 문제없다. React/TypeScript를 사용해 조직 고유의 도구를 포털 안에 심을 수 있다. (예: 사내 전용 배포 UI나 K8s 제어판을 포털 메뉴로 통합).</p>
</li>
</ul>
</li>
</ol>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine4-성공적인-idp-도입을-위한-4단계-로드맵span"><span style='background-color: MediumAquamarine'>4. 성공적인 IDP 도입을 위한 4단계 로드맵</span></h2>
<hr>

<p><span style="color: indianred">*<em>IDP 도입에 실패하는 가장 큰 이유는 데이터 없이 셀프 서비스부터 구축하려 하기 때문이다. *</em></span></p>
<p>Cortex는 다음의 순서로 도입할 것을 강력히 권장한다.</p>
<p><span style="color: SlateBlue"><strong>1단계: 정보 수집 및 집계 (Inventory &amp; Aggregation)</strong></span></p>
<ul>
<li><p>가장 먼저 <strong>무엇이 존재하는지를 파악</strong>해야 한다. </p>
</li>
<li><p>서비스, 인프라, 리소스, 소유권 정보를 긁어모아 카탈로그를 구축하라. </p>
</li>
<li><p>현재 상태를 모르면 개선할 수 없다.</p>
</li>
</ul>
<p><span style="color: SlateBlue"><strong>2단계: 평가 및 목표 설정 (Assess &amp; North Star)</strong></span></p>
<ul>
<li><p>데이터가 모였다면 <strong>우리는 어디에 있는가?를 평가</strong>해야 한다. </p>
</li>
<li><p>조직이 중요하게 생각하는 가치(신뢰성, 보안, 생산성 등)에 대한 Baseline을 설정하고, 목표를 정하라.</p>
</li>
</ul>
<p><span style="color: SlateBlue"><strong>3단계: 조치 처방 (Prescribe Action)</strong></span></p>
<ul>
<li><p>목표 대비 현재의 격차를 파악했다면, 개발자에게 무엇을 해야 하는지 알려주어야 한다. </p>
</li>
<li><p>스코어카드나 우선순위 알림을 통해 개발자가 집중해야 할 작업을 명확히 제시하라.</p>
</li>
</ul>
<p><span style="color: SlateBlue"><strong>4단계: 경험 최적화 (Optimize Experiences)</strong></span></p>
<ul>
<li><p><span style="color: indianred">*<em>마찰이 발생하는 지점이 명확해졌을 때 비로소 셀프 서비스를 도입하라. *</em></span></p>
</li>
<li><p>예를 들어, &quot;SLO 설정이 어렵다&quot;는 데이터가 나오면 그때 SLO 설정 자동화 템플릿을 제공하는 식이다.</p>
</li>
</ul>
<p><span style="color: SlateBlue"><strong>결론: 플라이휠(Flywheel) 효과</strong></span></p>
<ul>
<li><p>이 과정을 거치면 <code>측정 -&gt; 평가 -&gt; 조치 -&gt; 최적화</code>라는 피드백 루프가 형성된다. </p>
</li>
<li><p>이 루프가 반복될수록 IDP는 단순한 도구를 넘어 조직의 생산성을 지속적으로 가속화하는 플라이휠이 된다. 이것이 바로 성공적인 조직들이 IDP를 통해 가치를 창출하는 방식이다.</p>
</li>
</ul>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine5-정리-및-결론span"><span style='background-color: MediumAquamarine'>5. 정리 및 결론</span></h2>
<hr>

<p><span style="color: indianred">*<em>성공적인 내부 개발자 포털(IDP)은 플랫폼 팀의 관점이 아닌, 실제 사용자인 개발자의 핵심 니즈(정보 탐색, 우선순위 명확화, 템플릿)에서 출발해야 한다. *</em></span></p>
<p>Cortex는 단순한 정보 통합을 넘어, 스코어카드와 스캐폴딩을 통해 개발자가 데이터를 보고 즉시 행동(Action)할 수 있는 실행 가능한 환경을 제공하는 데 초점을 맞춘다. </p>
<p>조직은 무작정 기능을 도입하기보다 <span style="color: indianred"><strong><code>정보 수집 → 평가 → 조치 처방 → 경험 최적화</code></strong></span>의 단계적 접근을 통해 선순환 피드백 루프를 형성해야 한다. </p>
<p><strong>결국 IDP는 단순한 관리 도구가 아니라, 조직의 엔지니어링 문화를 성숙시키고 생산성을 지속적으로 가속화하는 강력한 플라이휠(Flywheel)이 되어야 한다.</strong></p>
<p><span style="color: indianred"></span>
<span style="color: SlateBlue"></span></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[90DaysOfDevOps (Day 87)]]></title>
            <link>https://velog.io/@taekyu_/90DaysOfDevOps-Day-87-sp3zkv76</link>
            <guid>https://velog.io/@taekyu_/90DaysOfDevOps-Day-87-sp3zkv76</guid>
            <pubDate>Sat, 07 Feb 2026 04:53:54 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>해당 스터디는 90DaysOfDevOps
<a href="https://github.com/MichaelCade/90DaysOfDevOps">https://github.com/MichaelCade/90DaysOfDevOps</a>
를 기반으로 진행한 내용입니다.</strong></p>
</blockquote>
<p>** Day 87 - Hands-on Performance Testing with k6 **</p>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine1-성능-테스트performance-testing-개요span"><span style='background-color: MediumAquamarine'>1. 성능 테스트(Performance Testing) 개요</span></h2>
<hr>

<h3 id="span-stylecolor-slateblue1-1-성능-테스트의-목적span"><span style="color: SlateBlue">1-1. 성능 테스트의 목적</span></h3>
<p><strong>성능 테스트의 궁극적인 목표는 테스트 대상 시스템의 실생활에서의 신뢰성을 검증하는 것이다.</strong></p>
<p><span style="color: indianred"><strong>단순히 기능이 작동하는지를 넘어, 실제 사용 환경과 유사한 부하 상황에서 시스템이 어떻게 반응하는지를 확인해야 한다.</strong> </span></p>
<p>이를 위해 주로 다음 세 가지 측면을 중점적으로 살펴본다.</p>
<ol>
<li><p><span style="color: SlateBlue"><strong>지연 시간 (응답 속도) :</strong></span> 시스템이 사용자 요청에 얼마나 빠르게 응답하는가? </p>
</li>
<li><p><span style="color: SlateBlue"><strong>오류 :</strong></span> 부하가 가해졌을 때 시스템이 에러를 반환하거나 내부적인 오류가 발생하지 않는가?</p>
</li>
<li><p><span style="color: SlateBlue"><strong>기능적 무결성 :</strong></span> 부하 상태에서도 통합 테스트나 End-to-End 사용자 흐름이 정상적으로 작동하는가?</p>
<ul>
<li><span style="color: indianred"><strong>중요:</strong></span> *<em>기능 테스트만으로는 부하 상황에서의 동작을 보장할 수 없다. *</em>
시스템은 사용자 10명일 때와 10,000명일 때 전혀 다르게 동작하기 때문이다.<br>

</li>
</ul>
</li>
</ol>
<h3 id="span-stylecolor-slateblue1-2-부하-테스트load-testing와-성능-테스트의-관계span"><span style="color: SlateBlue">1-2. 부하 테스트(Load Testing)와 성능 테스트의 관계</span></h3>
<p>성능 테스트와 부하 테스트는 매우 밀접한 관계가 있다. </p>
<p><strong>사실상 부하 테스트는 부하(Load)를 동반한 성능 테스트라고 정의할 수 있다.</strong></p>
<p><span style="color: indianred">*<em>부하 테스트는 크게 테스트 지속 시간(Duration)과 트래픽 부하량(Load)이라는 두 가지 변수에 따라 다음과 같이 세분화된다. *</em></span></p>
<img src="https://velog.velcdn.com/images/taekyu_/post/c381540e-52c9-47e4-b057-ba91d359e83f/image.png" width="70%" height="n%">

<ol>
<li><p><span style="color: SlateBlue"><strong>평균 부하 테스트 (Average Load Test):</strong></span> </p>
<ul>
<li><p>평소 운영 환경에서 발생하는 평균적인 트래픽 수준을 시뮬레이션</p>
</li>
<li><p>일상적인 성능 기준을 잡는 데 사용</p>
</li>
</ul>
</li>
<li><p><span style="color: SlateBlue"><strong>스트레스 테스트 (Stress Test):</strong></span> </p>
<ul>
<li>시스템의 한계를 확인하기 위해 평소보다 높은 트래픽을 발생시킴.</li>
</ul>
</li>
<li><p><span style="color: SlateBlue"><strong>스파이크 테스트 (Spike Test):</strong></span> </p>
<ul>
<li><p>특정 이벤트(예: 티켓 예매 오픈) 시점처럼, 트래픽이 매우 낮은 상태에서 갑자기 매우 높은 상태(Peak)로 급증하는 상황을 시뮬레이션</p>
</li>
<li><p>시스템의 급격한 확장에 대한 대응력을 테스트</p>
</li>
</ul>
</li>
<li><p><span style="color: SlateBlue"><strong>스모크 테스트 (Smoke Test):</strong></span> </p>
<ul>
<li><p>최소한의 부하로 아주 짧은 시간 동안 수행</p>
</li>
<li><p>본격적인 테스트 전에 스크립트 오류 여부와 시스템의 기본 가용성을 빠르게 점검하는 것이 목적</p>
</li>
</ul>
</li>
<li><p><span style="color: SlateBlue"><strong>소크 테스트 (Soak Test):</strong></span> </p>
<ul>
<li><p>장시간(2시간, 4시간, 혹은 그 이상) 동안 부하를 지속</p>
</li>
<li><p>메모리 누수(Memory Leak), 리소스 고갈, 디스크 공간 부족 등 장기 운영 시에만 드러나는 문제를 찾아내는 테스트</p>
</li>
</ul>
</li>
<li><p><span style="color: SlateBlue"><strong>브레이크포인트 테스트 (Breakpoint Test):</strong></span> </p>
<ul>
<li><p>시스템이 과부하로 멈추거나 오류를 낼 때까지 부하를 지속적으로 증가시킴.</p>
</li>
<li><p>시스템의 물리적 한계점을 파악하여 용량 계획을 세우는 데 도움을 줌.</p>
<br>

</li>
</ul>
</li>
</ol>
<h3 id="span-stylecolor-slateblue1-3-부하를-사용하지-않는-성능-테스트span"><span style="color: SlateBlue">1-3. 부하를 사용하지 않는 성능 테스트</span></h3>
<p><strong>모든 성능 테스트가 동시 접속 부하를 필요로 하는 것은 아니다.</strong></p>
<ul>
<li><p><span style="color: indianred"><strong>프로파일링 (Profiling):</strong></span> 단일 사용자/스레드로 특정 함수나 로직의 실행 시간을 분석한다.</p>
</li>
<li><p><span style="color: indianred"><strong>브라우저 성능 테스트:</strong></span> 백엔드 부하보다는 프론트엔드 최적화(렌더링 속도, 에셋 로딩 시간 등)에 집중한다. 이 경우 백엔드에 부하를 주입할 필요가 없다.</p>
</li>
<li><p><span style="color: indianred"><strong>신세틱 모니터링 (Synthetic Monitoring):</strong></span> 운영 중인 시스템의 가용성을 확인하기 위해 주기적으로 가상의 트랜잭션을 발생시켜 모니터링한다.</p>
</li>
</ul>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine2-성능-테스트-도구-생태계span"><span style='background-color: MediumAquamarine'>2. 성능 테스트 도구 생태계</span></h2>
<hr>

<p><strong>성능 테스트 도구는 매우 다양하며, 사용 방식과 목적에 따라 크게 4가지 카테고리로 분류할 수 있다.</strong></p>
<ol>
<li><p><span style="color: SlateBlue"><strong>벤치마킹 도구</strong></span></p>
<ul>
<li><p><span style="color: indianred"><strong>대표 도구:</strong></span> <code>ab</code> (Apache Bench), <code>wrk</code></p>
</li>
<li><p><span style="color: indianred"><strong>특징:</strong></span> 단일 엔드포인트나 URL 리스트를 대상으로 단순한 부하를 발생시킨다.</p>
</li>
<li><p><span style="color: indianred"><strong>장단점:</strong></span> 사용이 매우 간편하고 결과가 터미널에 바로 출력되지만, 복잡한 시나리오 구성은 어렵다.</p>
</li>
</ul>
</li>
<li><p><span style="color: SlateBlue"><strong>프로토콜 특화 도구</strong></span></p>
<ul>
<li><span style="color: indianred"><strong>특징:</strong></span> <code>gRPC</code>, <code>MQTT</code>, <code>Kafka</code> 등 특정 프로토콜 테스트에 최적화된 도구들이 존재한다.</li>
</ul>
</li>
<li><p><span style="color: SlateBlue"><strong>GUI 기반 도구</strong></span></p>
<ul>
<li><p><span style="color: indianred"><strong>대표 도구:</strong></span> JMeter</p>
</li>
<li><p><span style="color: indianred"><strong>특징:</strong></span> 가장 오래되고 널리 쓰이는 도구 중 하나다. GUI를 통해 테스트 단계를 클릭으로 구성할 수 있다.</p>
</li>
<li><p><span style="color: indianred"><strong>장단점:</strong></span> 코딩 없이 접근 가능하고 커뮤니티가 크지만, 스크립트 관리가 어렵고 리소스를 많이 차지할 수 있다.</p>
</li>
</ul>
</li>
<li><p><span style="color: SlateBlue"><strong>코드 기반(Scripting) 도구</strong></span></p>
<ul>
<li><p><span style="color: indianred"><strong>대표 도구:</strong></span> k6 (JavaScript/Go), Locust (Python), Gatling (Scala/Java)</p>
</li>
<li><p><span style="color: indianred"><strong>특징:</strong></span> 개발자 친화적이며, 코드로 테스트 시나리오를 작성하므로 버전 관리(Git)가 용이하고 유연성이 매우 높다.</p>
</li>
<li><p><span style="color: indianred"><strong>참고:</strong></span> Artillery와 같이 YAML 설정을 사용하는 도구도 있지만, 스크립팅 언어만큼의 완벽한 유연성을 제공하지는 못할 수 있다.</p>
</li>
</ul>
</li>
</ol>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine3-k6span"><span style='background-color: MediumAquamarine'>3. K6</span></h2>
<hr>

<p><strong>k6는 개발자 중심의 오픈 소스 부하 테스트 도구다.</strong></p>
<p>k6는 단순히 부하를 일으키는 도구를 넘어, 개발자 경험(DX, Developer Experience)을 최우선으로 설계된 오픈 소스 성능 테스트 프레임워크이며, Grafana Labs에 인수된 이후, 관측성(Observability) 생태계와 결합하며 더욱 강력해졌다.</p>
<p><img src="https://velog.velcdn.com/images/taekyu_/post/e142e33f-1466-41d3-8287-1d22fd46d2f6/image.png" alt=""></p>
<p><span style="color: indianred"><strong>k6의 가장 큰 기술적 특징은 &quot;작성은 쉬운 JavaScript로, 실행은 강력한 Go로&quot; 수행한다는 점이다.</strong></span></p>
<ul>
<li><p><span style="color: SlateBlue"><strong>스크립팅의 용이성:</strong></span> </p>
<ul>
<li><p>개발자에게 익숙한 ES6 자바스크립트 문법을 사용하여 테스트 시나리오를 작성한다. </p>
</li>
<li><p>덕분에 별도의 도구 사용법을 익힐 필요 없이, 복잡한 비즈니스 로직을 코드로 유연하게 구현할 수 있다.</p>
</li>
</ul>
</li>
<li><p><span style="color: SlateBlue"><strong>고성능 실행 엔진:</strong></span> </p>
<ul>
<li><p>*<em>작성된 스크립트는 내부적으로 내장된 Go 기반의 JS 인터프리터(goja)를 통해 실행된다. *</em></p>
</li>
<li><p>이는 Node.js나 브라우저 엔진 위에서 구동되는 것이 아니기 때문에 오버헤드가 극도로 낮다.</p>
</li>
</ul>
</li>
<li><p><span style="color: SlateBlue"><strong>스레드 대신 가상 사용자(VU):</strong></span></p>
<ul>
<li><p>기존의 JMeter가 OS Thread를 사용하여 메모리 소비가 컸던 반면, k6는 Go의 Goroutine을 활용하여 가상 사용자(Virtual Users)를 구현한다. </p>
</li>
<li><p>덕분에 단일 인스턴스에서 수만 명의 동시 접속자를 시뮬레이션하더라도 메모리 사용량이 현저히 적다.</p>
</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/taekyu_/post/ef4e6712-1d64-4de7-aede-3eddd3eb54e7/image.png" alt=""></p>
<p><strong>첨부된 이미지가 보여주듯, k6는 소프트웨어 개발 수명 주기(SDLC) 전반에 걸쳐 성능을 관리할 수 있도록 설계되었다.</strong></p>
<ul>
<li><p><span style="color: SlateBlue"><strong>Pre-production (Proactive):</strong></span> 배포 전 단계에서 가상 사용자 트래픽(Virtual User Traffic)을 통해 잠재적인 병목 구간을 사전에 발견하고 해결한다.</p>
</li>
<li><p><span style="color: SlateBlue"><strong>Production (Reactive):</strong></span> 실제 운영 환경의 모니터링 데이터(Real User Traffic)와 테스트 결과를 비교 분석한다.</p>
</li>
<li><p><span style="color: SlateBlue"><strong>Grafana와의 결합:</strong></span> </p>
<ul>
<li>*<em>k6는 테스트 결과를 단순히 콘솔에 찍는 것에 그치지 않고, Grafana 플랫폼으로 전송하여 메트릭(Metrics), 로그(Logs), 트레이스(Traces)와 함께 통합 분석할 수 있게 해준다. *</em></li>
</ul>
</li>
</ul>
<br>


<p><span style="color: indianred"><strong>또한, k6의 가장 강력한 장점은 하나의 테스트 스크립트를 수정 없이 다양한 환경에서 실행할 수 있다는 점이다.</strong></span></p>
<ul>
<li><p><span style="color: SlateBlue"><strong>Local (로컬):</strong></span> 개발자 개인 PC나 단일 서버에서 실행한다. k6 run 명령어를 사용한다.</p>
</li>
<li><p><span style="color: SlateBlue"><strong>Distributed (분산):</strong></span> Kubernetes 클러스터 등을 활용하여 여러 파드(Pod)에 부하를 분산시켜 대규모 테스트를 수행한다.</p>
</li>
<li><p><span style="color: SlateBlue"><strong>Cloud (클라우드):</strong></span> k6의 상용 클라우드 서비스(Grafana Cloud k6)를 이용하여 인프라 관리 없이 대규모 테스트를 수행하고 결과를 저장/분석한다.</p>
</li>
</ul>
<p><strong>이는 로컬 개발부터 대규모 프로덕션 테스트까지 매끄러운 확장을 가능하게 한다.</strong>
<br></p>
<p><span style="color: indianred"><strong>추가적으로, k6를 사용할 때 반드시 이해해야 할 점은 k6는 브라우저가 아니라는 것이다.</strong></span></p>
<p>k6는 프로토콜 레벨(HTTP 등)에서 요청을 보내고 응답을 측정하는 도구다. </p>
<p><strong>웹 페이지의 HTML을 가져오지만, 브라우저처럼 화면을 렌더링하거나 페이지 내의 JavaScript를 실행하지는 않는다.</strong></p>
<p><span style="color: indianred">*<em>따라서 정확한 페이지 로딩 속도(렌더링 포함)를 측정하고 싶다면, 최근 k6에 실험적으로 추가된 k6 Browser 모듈(Chrome DevTools Protocol 기반)을 활용하거나 다른 도구를 병행해야 한다. *</em></span></p>
<blockquote>
<p><strong>하지만 백엔드 API의 성능을 극한으로 테스트하는 목적이라면 k6의 기본 모드가 가장 효율적이고 강력한 선택지가 된다.</strong></p>
</blockquote>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine4-k6-실전-데모span"><span style='background-color: MediumAquamarine'>4. K6 실전 데모</span></h2>
<hr>


<p>해당 프레젠테이션의 데모는 피자 주문 및 추천 서비스인 &#39;Quick Pizza&#39; 애플리케이션을 대상으로 진행된다. </p>
<p>Docker Compose를 사용하여 애플리케이션, Grafana, Prometheus를 로컬 환경에 구성했다.</p>
<h3 id="span-stylecolor-slateblue4-1-기본-테스트-스크립트-구조-span"><span style="color: SlateBlue">4-1. 기본 테스트 스크립트 구조 </span></h3>
<p><strong>k6 스크립트는 자바스크립트(ES6) 기반이며, 크게 Imports, Options, Default Function 세 부분으로 구성된다.</strong></p>
<ul>
<li><p><span style="color: indianred"><strong>Imports:</strong></span> <code>k6/http</code>와 같은 내장 모듈이나 외부 라이브러리를 불러온다.</p>
</li>
<li><p><span style="color: indianred"><strong>Options:</strong></span> 가상 사용자 수(VUs), 지속 시간(Duration) 등 테스트 실행 설정을 정의한다.</p>
</li>
<li><p><span style="color: indianred"><strong>Default Function:</strong></span> 가상 사용자가 반복해서 수행할 실제 비즈니스 로직을 작성한다.</p>
</li>
</ul>
<pre><code class="language-javascript">import http from &#39;k6/http&#39;;
import { sleep, check } from &#39;k6&#39;;

// 1. 설정 (Options): 부하 수준 정의
export const options = {
  vus: 5,           // 가상 사용자 5명
  duration: &#39;10s&#39;,  // 10초 동안 실행
};

// 2. 메인 로직 (Default Function): VU가 반복 실행할 시나리오
export default function () {
  const res = http.post(&#39;http://localhost:3333/pizza&#39;); // HTTP 요청 (POST)

  // 응답 확인 및 로깅 (선택 사항)
  console.log(`Response status: ${res.status}`);

  sleep(1); // 1초 대기 (Think Time)
}</code></pre>
<br>

<h3 id="span-stylecolor-slateblue4-2-부하-모델링-심화-span"><span style="color: SlateBlue">4-2. 부하 모델링 심화 </span></h3>
<p><strong>k6는 매우 정교한 부하 제어 기능을 제공한다. 상황에 따라 다양한 옵션을 적용할 수 있다.</strong></p>
<ul>
<li><p><span style="color: indianred"><strong>A. Iterations 기반 실행</strong></span></p>
<ul>
<li><p>기본적으로 시간(Duration) 기반으로 실행하지만, iterations 옵션을 사용하여 총 실행 횟수를 지정할 수도 있다. CLI에서 플래그로 덮어쓰는 것도 가능하다.</p>
<pre><code class="language-bash"># 5명의 VU가 협력하여 총 20번의 시나리오를 수행하고 종료
k6 run --iterations 20 --vus 5 script.js</code></pre>
<br>


</li>
</ul>
</li>
</ul>
<ul>
<li><p><span style="color: indianred"><strong>B. 스테이지(Stages)를 이용한 램핑(Ramping)</strong></span></p>
<ul>
<li><p>실제 트래픽처럼 부하를 서서히 올리고(Ramp-up) 내리는(Ramp-down) 패턴을 만든다. </p>
</li>
<li><p>options 객체 내 stages 배열을 사용한다.</p>
<pre><code class="language-javascript">export const options = {
 stages: [
   { duration: &#39;5s&#39;, target: 5 },  // 0명에서 5명으로 5초 동안 증가 (Ramp-up)
   { duration: &#39;10s&#39;, target: 5 }, // 5명 유지 (Steady State - 안정성 확인)
   { duration: &#39;5s&#39;, target: 0 },  // 5명에서 0명으로 감소 (Ramp-down)
 ],
};</code></pre>
<br>


</li>
</ul>
</li>
</ul>
<ul>
<li><p><span style="color: indianred"><strong>C. 초당 요청 수(RPS) 제어: Constant Arrival Rate</strong></span></p>
<ul>
<li><p>일반적인 방식으로는 함수 실행 시간(응답 속도)에 따라 반복 횟수가 달라지므로 정확한 RPS(Requests Per Second)를 맞추기 어렵다. </p>
</li>
<li><p><strong>이를 위해 <code>scenarios</code>의 <code>constant-arrival-rate 실행기</code>를 사용한다.</strong></p>
<pre><code class="language-javascript">export const options = {
scenarios: {
  constant_request_rate: {
    executor: &#39;constant-arrival-rate&#39;,
    rate: 30,             // 초당 30회 반복 목표
    timeUnit: &#39;1s&#39;,       // 시간 단위
    duration: &#39;30s&#39;,      // 30초 동안 실행
    preAllocatedVUs: 50,  // 미리 할당할 VU 수
    maxVUs: 100,          // 최대 허용 VU 수
  },
},
};</code></pre>
</li>
<li><p><span style="color: indianred"><strong>주의:</strong></span> <strong>k6는 엄밀히 말해 초당 반복(Iteration) 수를 제어한다.</strong> 
시나리오 내에 요청이 1개라면 반복 수 = RPS가 되지만, 요청이 여러 개라면 계산이 필요하다.</p>
<br>

</li>
</ul>
</li>
</ul>
<h3 id="span-stylecolor-slateblue4-3-메트릭의-이해-span"><span style="color: SlateBlue">4-3. 메트릭의 이해 </span></h3>
<p><strong>k6는 테스트 중 발생하는 데이터를 4가지 유형의 메트릭으로 수집한다.</strong></p>
<ol>
<li><p><span style="color: indianred"><strong>Counter:</strong></span> 누적 합계 (예: <code>http_reqs</code>)</p>
</li>
<li><p><span style="color: indianred"><strong>Gauge:</strong></span> 특정 시점의 값 (예: <code>vus</code>)</p>
</li>
<li><p><span style="color: indianred"><strong>Rate:</strong></span> 성공/실패 비율 (예: <code>checks</code>, <code>http_req_failed</code>)</p>
</li>
<li><p><span style="color: indianred"><strong>Trend:</strong></span> 통계적 분포 (예: <code>http_req_duration</code> - p95, p99 등)</p>
</li>
</ol>
<p><span style="color: indianred"><strong>커스텀 메트릭 (Custom Metrics) 이란?</strong></span></p>
<p><strong>추가적으로, 기본 메트릭 외에 비즈니스 로직에 맞는 데이터를 수집할 수 있다.</strong></p>
<pre><code class="language-javascript">import http from &#39;k6/http&#39;;
import { Trend, Counter } from &#39;k6/metrics&#39;;

// 커스텀 메트릭 정의
const ingredientsTrend = new Trend(&#39;quickpizza_ingredients&#39;);
const pizzaCounter = new Counter(&#39;quickpizza_number_of_pizzas&#39;);

export default function () {
  const res = http.get(&#39;http://localhost:3333/pizza&#39;);

  // 응답받은 JSON 데이터 파싱
  const pizza = res.json();

  // 메트릭에 데이터 추가
  ingredientsTrend.add(pizza.ingredients.length); // 재료 개수 통계
  pizzaCounter.add(1);                            // 피자 개수 카운트
}</code></pre>
<br>

<h3 id="span-stylecolor-slateblue4-4-결과-출력과-시각화-span"><span style="color: SlateBlue">4-4. 결과 출력과 시각화 </span></h3>
<ol>
<li><p><span style="color: indianred"><strong>터미널 요약 및 파일 내보내기</strong></span></p>
<ul>
<li><p>테스트 종료 후 터미널에 출력되는 요약 리포트는 집계된 데이터다.</p>
</li>
<li><p>상세 분석을 위해 <code>handleSummary()</code> 함수를 이용해 JSON 파일로 내보낼 수 있다.</p>
</li>
</ul>
</li>
<li><p><span style="color: indianred"><strong>실시간 데이터 스트리밍</strong></span></p>
<ul>
<li><p>가장 강력한 기능은 <code>--out</code> 플래그를 사용하여 Raw Data를 시계열 데이터베이스로 실시간 전송하는 것이다.</p>
<pre><code class="language-bash"># Prometheus로 데이터 실시간 전송 (Remote Write 기능 사용)
 k6 run --out experimental-prometheus-rw script.js</code></pre>
</li>
<li><p>*<em>위 명령어를 실행하면 k6가 데이터를 Prometheus로 전송하고, 이를 Grafana 대시보드에서 실시간 그래프로 확인할 수 있다. *</em></p>
</li>
<li><p>스파이크 발생 지점이나 지연 시간 변화 추이를 시각적으로 파악하는 데 필수적이다.</p>
<br>

</li>
</ul>
</li>
</ol>
<h3 id="span-stylecolor-slateblue4-5-검증-checks와-thresholds-span"><span style="color: SlateBlue">4-5. 검증: Checks와 Thresholds </span></h3>
<p><strong>k6는 테스트 성공 여부를 판단하고 검증하기 위해 두 가지 방법을 제공한다.</strong></p>
<p><span style="color: indianred">*<em>1. Checks *</em></span>
단순 확인용(Boolean) 검증으로, 실패해도 테스트가 중단되거나 실패로 기록되지 않는다.</p>
<pre><code class="language-javascript">import { check } from &#39;k6&#39;;

export default function (res) {
  // 응답 코드가 200인지 확인 (실패해도 테스트는 계속 진행됨)
  check(res, { &#39;is status 200&#39;: (r) =&gt; r.status === 200 });
}</code></pre>
<p><span style="color: indianred">*<em>2. Thresholds (임계값) *</em></span>
테스트의 성공/실패 기준을 정의하며, 이를 위반하면 k6는 실패(Non-zero) 종료 코드를 반환하므로 CI/CD 파이프라인에서 매우 중요하다.</p>
<pre><code class="language-javascript">export const options = {
  thresholds: {
    // 에러율(http_req_failed)이 1% 미만이어야 함
    &#39;http_req_failed&#39;: [&#39;rate&lt;0.01&#39;], 

    // 응답 시간(http_req_duration)의 95%가 500ms 이내여야 함
    &#39;http_req_duration&#39;: [&#39;p95&lt;500&#39;],

    // 커스텀 메트릭 평균이 2 미만이어야 함
    &#39;quickpizza_ingredients&#39;: [&#39;avg&lt;2&#39;], 
  },
};</code></pre>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine5-결론span"><span style='background-color: MediumAquamarine'>5. 결론</span></h2>
<hr>

<p>성능 테스트는 시스템의 신뢰성을 보장하기 위한 선택이 아닌 필수 과정이다. </p>
<p><strong>k6는 강력한 스크립팅 기능과 확장성, 그리고 개발자 친화적인 경험(DX)을 제공하여 이를 효율적으로 지원한다.</strong></p>
<p><span style="color: indianred"></span>
<span style="color: SlateBlue"></span></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[90DaysOfDevOps (Day 86)]]></title>
            <link>https://velog.io/@taekyu_/90DaysOfDevOps-Day-86-78f3qstq</link>
            <guid>https://velog.io/@taekyu_/90DaysOfDevOps-Day-86-78f3qstq</guid>
            <pubDate>Sat, 07 Feb 2026 03:15:31 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>해당 스터디는 90DaysOfDevOps
<a href="https://github.com/MichaelCade/90DaysOfDevOps">https://github.com/MichaelCade/90DaysOfDevOps</a>
를 기반으로 진행한 내용입니다.</strong></p>
</blockquote>
<p>** Day 86 - Tools To Make Your Terminal DevOps and Kubernetes Friendly **</p>
<p><strong>프레젠테이션에서는 엔지니어에게 터미널은 하루의 대부분을 보내는 집과 같은 곳이기때문에,  이 공간을 더 효율적이고 예쁘게 만드는 것은 생산성과 직결된다고 한다.</strong></p>
<p><span style="color: indianred"><strong>따라서, Zsh, Terraform, Git, 그리고 Kubernetes 환경에서 사용할 수 있는 유용한 오픈 소스 및 상용 도구들을 제시한다.</strong></span></p>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine1-zsh-z-shell-관련-도구span"><span style='background-color: MediumAquamarine'>1. Zsh (Z Shell) 관련 도구</span></h2>
<hr>

<p><strong>Zsh는 유닉스 기반의 쉘로, Bash와 유사하지만 훨씬 강력한 기능과 생산성 향상 도구를 제공하여 최근 개발자들 사이에서 인기가 높다.</strong></p>
<p><img src="https://velog.velcdn.com/images/taekyu_/post/2a454b39-abba-4d3d-abab-7c91d1d0e0dc/image.png" alt=""></p>
<ul>
<li><p><span style="color: SlateBlue"><strong>Oh My Zsh</strong></span></p>
<ul>
<li><p><strong>Zsh 구성을 관리하기 위한 커뮤니티 기반 오픈 소스 프레임워크</strong></p>
</li>
<li><p><span style="color: indianred"><strong>특징:</strong></span> 수많은 테마와 플러그인을 제공하여 사용자 경험을 극대화</p>
</li>
<li><p><span style="color: indianred"><strong>설치:</strong></span> Zsh, curl, git이 사전에 설치되어 있어야 하며 <code>curl</code>, <code>wget</code>, <code>fetch</code> 등을 통해 설치 가능</p>
</li>
<li><p><span style="color: indianred"><strong>사용법:</strong></span> 홈 디렉토리의 <code>.zshrc</code> 파일을 수정하여 플러그인(예: git)을 추가하거나 테마를 변경할 수 있다. 다양한 테마의 스크린샷과 색상 정보를 확인하고 선택하는 것을 추천한다.</p>
</li>
</ul>
</li>
<li><p><span style="color: SlateBlue"><strong>zsh-syntax-highlighting</strong></span></p>
<ul>
<li><p><strong>명령어를 입력하는 동안 실시간으로 구문을 강조해 주는 플러그인</strong></p>
</li>
<li><p><span style="color: indianred"><strong>기능:</strong></span> 명령어, 옵션, 인수, 변수 등을 시각적으로 구분해 준다.</p>
</li>
<li><p><span style="color: indianred"><strong>장점:</strong></span> 오타나 문법 오류를 입력 단계에서 즉시 파악할 수 있어 명령어 구조를 이해하는 데 도움을 준다.</p>
</li>
</ul>
</li>
<li><p><span style="color: SlateBlue"><strong>zsh-autosuggestions</strong></span></p>
<ul>
<li><p><strong>사용자의 명령어 히스토리를 기반으로 지능적인 자동 완성을 제안하는 플러그인</strong></p>
</li>
<li><p><span style="color: indianred"><strong>기능:</strong></span> 이전에 사용했던 명령어를 기억하여, 앞부분만 입력해도 회색 텍스트로 전체 명령어를 제안한다.</p>
</li>
<li><p><span style="color: indianred"><strong>장점:</strong></span>  긴 명령어를 반복적으로 입력해야 할 때 Tab 키만 누르면 완성되므로 작업 속도가 빨라진다.</p>
</li>
</ul>
</li>
</ul>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine2-terraform-관련-도구span"><span style='background-color: MediumAquamarine'>2. Terraform 관련 도구</span></h2>
<hr>

<p><strong>Terraform은 HashiCorp의 IaC(Infrastructure as Code) 도구이며, Terragrunt는 Terraform 모듈을 DRY(Don&#39;t Repeat Yourself)하게 관리해 주는 래퍼 도구다.</strong></p>
<ul>
<li><p><span style="color: SlateBlue"><strong>tfswitch &amp; tgswitch</strong></span></p>
<ul>
<li><p><strong>다양한 프로젝트나 환경에 따라 서로 다른 버전의 Terraform이나 Terragrunt를 사용해야 할 때 유용</strong></p>
</li>
<li><p><span style="color: indianred"><strong>기능:</strong></span> 시스템에 설치된 여러 버전 중 원하는 버전을 손쉽게 전환할 수 있게 해준다.</p>
<br>


</li>
</ul>
</li>
</ul>
<ul>
<li><p><span style="color: SlateBlue"><strong>Infracost</strong></span></p>
<ul>
<li><p><strong>IaC 코드의 비용을 추정하고 추적하는 도구</strong></p>
</li>
<li><img src="https://velog.velcdn.com/images/taekyu_/post/93cb17f7-360c-4d15-81b0-d523454d0e84/image.png" width="60%" height="n%">
</li>
<li><p><span style="color: indianred"><strong>기능:</strong></span> 구성 파일을 분석하여 실시간 클라우드 비용 견적을 제공한다.</p>
</li>
<li><p><span style="color: indianred"><strong>활용:</strong></span> VS Code 확장 프로그램으로도 제공되며, 코드 작성 단계에서 예산을 고려한 의사결정을 돕는다.</p>
<br>
</li>
</ul>
</li>
<li><p><span style="color: SlateBlue"><strong>Trivy (구 tfsec 통합)</strong></span></p>
<ul>
<li><strong>보안 스캔 도구로, 기존의 tfsec이 Trivy로 통합</strong></li>
</ul>
</li>
</ul>
<ul>
<li><img src="https://velog.velcdn.com/images/taekyu_/post/6b6903c4-91cd-4d98-816c-f48d95ed4302/image.png" width="50%" height="n%">
</li>
<li><p><span style="color: indianred"><strong>변화:</strong></span> tfsec 제작사는 도구 통합으로 인해 Trivy로의 마이그레이션을 권장한다.</p>
</li>
<li><p><span style="color: indianred"><strong>기능:</strong></span> OS 패키지, 소프트웨어 의존성, CVE(알려진 취약점), IaC(기존 tfsec 기능), 민감 정보(Secret), 라이선스 등을 포괄적으로 스캔한다.</p>
</li>
<li><p><span style="color: indianred"><strong>장점:</strong></span> 코드의 잠재적 취약점과 모범 사례 위반을 식별하고 개선 방안을 제안한다.</p>
<br>

</li>
</ul>
<ul>
<li><p><span style="color: SlateBlue"><strong>terraform-docs</strong></span></p>
<ul>
<li><p><strong>Terraform 코드를 기반으로 문서 생성을 자동화해 주는 CLI 유틸리티</strong></p>
</li>
<li><p><span style="color: indianred"><strong>기능:</strong></span> 구성 파일에서 정보(<code>Requirements, Providers, Inputs, Outputs</code>)를 추출하여 마크다운 테이블 등 다양한 형식의 깔끔한 문서를 만들어준다.</p>
</li>
</ul>
</li>
</ul>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine3-git-관련-도구span"><span style='background-color: MediumAquamarine'>3. git 관련 도구</span></h2>
<hr>

<p><strong>버전 관리를 위한 Git 사용 시 생산성을 높여주는 도구들이다.</strong></p>
<ul>
<li><p><span style="color: SlateBlue"><strong>Git Aliases (별칭)</strong></span></p>
<ul>
<li><p><strong>자주 사용하는 긴 명령어를 짧은 단축어로 정의하여 사용</strong></p>
</li>
<li><p><span style="color: indianred"><strong>예시:</strong></span> git status 대신 gs를 입력하도록 설정하여 타이핑 수고를 덜고 속도를 높인다.</p>
</li>
</ul>
</li>
<li><p><span style="color: SlateBlue"><strong>GitLens</strong></span></p>
<ul>
<li><p><strong>VS Code용 확장 프로그램으로 코드의 히스토리를 추적하는 데 최적화</strong></p>
</li>
<li><p><span style="color: indianred"><strong>기능:</strong></span> 코드 에디터 내에서 각 라인을 누가, 언제 수정했는지(Blame 기능) 바로 보여준다.</p>
</li>
<li><p><span style="color: indianred"><strong>상세:</strong></span> 커밋 메시지와 작성자 정보를 시각적으로 표시하며, 마우스 오버 시 해당 히스토리 링크로 연결되어 변경 이력을 쉽게 파악할 수 있다.</p>
</li>
</ul>
</li>
<li><p><span style="color: SlateBlue"><strong>Git Graph</strong></span></p>
<ul>
<li><p><strong>VS Code용 확장 프로그램으로, Git 히스토리를 그래프 형태로 시각화</strong></p>
</li>
<li><p><span style="color: indianred"><strong>기능:</strong></span> 복잡한 브랜치 구조와 커밋 흐름을 시각적으로 표현해 준다.</p>
</li>
<li><p><span style="color: indianred"><strong>장점:</strong></span> CLI만으로는 히스토리 파악이 어려운 초심자에게 현재 브랜치 위치와 흐름을 이해하는 데 큰 도움을 준다.</p>
</li>
</ul>
</li>
</ul>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine4-kubernetes-관련-도구span"><span style='background-color: MediumAquamarine'>4. Kubernetes 관련 도구</span></h2>
<hr>

<p><strong>복잡한 쿠버네티스 시스템을 효율적으로 관리하기 위해 개발된 다양한 도구들이다.</strong></p>
<ul>
<li><p><span style="color: SlateBlue"><strong>Kubernetes Aliases</strong></span></p>
<ul>
<li><p><strong>kubectl 명령어를 매번 입력하는 번거로움을 줄이기 위해 사용</strong></p>
</li>
<li><pre><code class="language-bash">alias k=&#39;kubectl&#39;
 alias kpod=&#39;kubectl get pods -A&#39;
 alias knode=&#39;kubectl get nodes&#39;
 alias kdesp=&#39;kubectl describe pod&#39;
 alias kdp=&#39;kubectl delete pod&#39;
 alias=&#39;kubectl get deployments&#39;</code></pre>
</li>
<li><p><span style="color: indianred"><strong>활용:</strong></span> kubectl을 k와 같은 한 글자 별칭으로 설정하여 명령어 입력 속도를 비약적으로 높인다.</p>
<br>
</li>
</ul>
</li>
<li><p><span style="color: SlateBlue"><strong>kube-ps1</strong></span></p>
<ul>
<li><p><strong>현재 사용 중인 클러스터 컨텍스트와 네임스페이스를 프롬프트에 시각적으로 표시</strong></p>
</li>
<li><p><span style="color: indianred"><strong>필요성:</strong></span> 여러 클러스터를 관리할 때, 실수로 엉뚱한 클러스터(예: 프로덕션)에 명령을 내리는 치명적인 실수를 방지해 준다.</p>
</li>
<li><p><span style="color: indianred"><strong>기능:</strong></span> 터미널 프롬프트에 현재 컨텍스트와   - 네임스페이스 정보를 색상과 함께 보여주어 직관적인 확인이 가능하다.</p>
<br>
</li>
</ul>
</li>
<li><p><span style="color: SlateBlue"><strong>kubecolor</strong></span></p>
<ul>
<li><p><strong>kubectl 명령어의 출력 결과에 색상을 입혀 가독성을 높인다.</strong></p>
</li>
<li><p><span style="color: indianred"><strong>기능:</strong></span> 밋밋한 텍스트 출력 대신 중요 정보를 색상으로 구분하여, 원하는 정보를 빠르게 식별할 수 있게 돕는다.</p>
<br>
</li>
</ul>
</li>
<li><p><span style="color: SlateBlue"><strong>kubectx &amp; kubens</strong></span></p>
<ul>
<li><p><strong>컨텍스트와 네임스페이스 전환을 획기적으로 간편하게 만들어주는 도구</strong></p>
</li>
<li><p><span style="color: indianred"><strong>kubectx:</strong></span> 복잡한 명령어 없이 kubectx &lt;컨텍스트명&gt; 입력만으로 클러스터 간 전환이 가능하다.</p>
</li>
<li><p><span style="color: indianred"><strong>kubens:</strong></span> kubens &lt;네임스페이스명&gt; 입력만으로 네임스페이스를 즉시 변경할 수 있다.</p>
<br>
</li>
</ul>
</li>
<li><p><span style="color: SlateBlue"><strong>K9s</strong></span></p>
<ul>
<li><p>터미널 기반의 UI(TUI)를 제공하는 쿠버네티스 관리 대시보드다.</p>
</li>
<li><p><span style="color: indianred"><strong>기능:</strong></span> CLI 환경에서도 시각적인 인터페이스를 통해 클러스터 상태를 모니터링하고 리소스를 관리할 수 있다.</p>
<br>
</li>
</ul>
</li>
<li><p><span style="color: SlateBlue"><strong>Lens</strong></span></p>
<ul>
<li><p><strong>쿠버네티스 관리를 위한 데스크톱 GUI 애플리케이션</strong></p>
</li>
<li><p><span style="color: indianred"><strong>기능:</strong></span> 노드, 파드 등 모든 리소스를 그래픽 인터페이스로 보여주며, 마우스 클릭만으로 상세 정보를 확인하고 관리할 수 있는 강력한 도구다.</p>
<br>
</li>
</ul>
</li>
<li><p><span style="color: SlateBlue"><strong>Popeye</strong></span></p>
<ul>
<li><p><strong>쿠버네티스 클러스터 분석 및 살균(Sanitizer) 도구</strong></p>
</li>
<li><p><span style="color: indianred"><strong>기능:</strong></span> 클러스터 전체를 스캔하여 구성 오류, 리소스 비효율성, 보안 위협, 모범 사례 위반 사항 등을 찾아내고 보고한다.</p>
<br>
</li>
</ul>
</li>
<li><p><span style="color: SlateBlue"><strong>kube-capacity</strong></span></p>
<ul>
<li><p><strong>클러스터 리소스 사용량과 용량을 분석하는 도구</strong></p>
</li>
<li><p><span style="color: indianred"><strong>기능:</strong></span> CPU와 메모리의 요청(Requests) 및 제한(Limits) 설정값과 실제 사용량을 비교하여 보여주며, 노드 및 파드 단위의 용량 정보를 제공한다.</p>
<br>
</li>
</ul>
</li>
<li><p><span style="color: SlateBlue"><strong>kube-shell</strong></span></p>
<ul>
<li><p><strong>쿠버네티스 CLI를 위한 통합 쉘</strong></p>
</li>
<li><p><span style="color: indianred"><strong>기능:</strong></span> 명령어 자동 완성 및 제안 기능을 제공하여, 사용 가능한 명령어를 쉽게 찾고 실행할 수 있도록 돕는다.</p>
</li>
</ul>
</li>
</ul>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine5-결론span"><span style='background-color: MediumAquamarine'>5. 결론</span></h2>
<hr>

<p>*<em>이미 시중에는 데브옵스 여정을 돕고 생산성을 높여줄 수 있는 수많은 도구가 존재한다. *</em></p>
<p>이러한 도구들을 적극적으로 활용하여 불필요한 반복 작업을 줄이고, 더 중요하고 가치 있는 일에 집중하며, 결과적으로 엔지니어로서의 삶을 더 편안하게 만드는 것이 중요하다.</p>
<p><span style="color: indianred"></span>
<span style="color: SlateBlue"></span></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[90DaysOfDevOps (Day 85)]]></title>
            <link>https://velog.io/@taekyu_/90DaysOfDevOps-Day-85-4srzfbxp</link>
            <guid>https://velog.io/@taekyu_/90DaysOfDevOps-Day-85-4srzfbxp</guid>
            <pubDate>Sun, 01 Feb 2026 05:59:36 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>해당 스터디는 90DaysOfDevOps
<a href="https://github.com/MichaelCade/90DaysOfDevOps">https://github.com/MichaelCade/90DaysOfDevOps</a>
를 기반으로 진행한 내용입니다.</strong></p>
</blockquote>
<p>** Day 85 - Reuse, Don&#39;t Repeat - Creating an Infrastructure as Code Module Library **</p>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine1-왜-인프라-코드를-모듈화해야-하는가-span"><span style='background-color: MediumAquamarine'>1. 왜 인프라 코드를 모듈화해야 하는가 </span></h2>
<hr>

<p>IaC를 작성하다 보면 VM, 스토리지 계정 등 동일한 리소스를 반복적으로 정의하게 된다.</p>
<p><strong>매번 코드를 복사해서 붙여넣는 방식은 다음과 같은 문제를 야기한다.</strong></p>
<ul>
<li><p><span style="color: indianred"><strong>시간 낭비:</strong></span> 동일한 구성을 매번 새로 작성해야 한다.</p>
</li>
<li><p><span style="color: indianred"><strong>일관성 결여:</strong></span> 시간이 지남에 따라 초기에 작성한 코드와 나중에 작성한 코드가 달라져 관리 포인트가 늘어난다.</p>
</li>
</ul>
<hr>
<h3 id="span-stylecolor-slateblue1-1-모듈화의-4가지-핵심-이점span"><span style="color: SlateBlue"><strong>1-1. 모듈화의 4가지 핵심 이점</strong></span></h3>
<ul>
<li><p><span style="color: indianred"><strong>재사용성:</strong></span> 한 번 잘 만들어진 모듈을 여러 프로젝트에서 Parameter만 바꿔가며 반복 사용할 수 있다.</p>
</li>
<li><p><span style="color: indianred"><strong>복잡성 추상화:</strong></span> </p>
<ul>
<li><p>클라우드 리소스의 복잡한 설정(<strong><code>네트워크 피어링, 암호화 세팅 등</code></strong>)을 모듈 내부로 숨기고, 사용자에게는 이름과 위치 같은 단순한 인터페이스만 제공할 수 있다.</p>
</li>
<li><p>이를 통해 클라우드 전문 지식이 없는 팀원도 손쉽게 인프라를 배포할 수 있다.</p>
</li>
</ul>
</li>
<li><p><span style="color: indianred"><strong>표준 준수:</strong></span> 회사의 보안 정책(예: HTTPS 필수, 퍼블릭 액세스 차단)을 모듈 내부에 하드코딩함으로써, 사용자가 모듈을 쓰기만 해도 자동으로 규정을 준수하도록 강제할 수 있다.</p>
</li>
<li><p><span style="color: indianred"><strong>지식 공유:</strong></span> 인프라 아키텍트의 노하우와 모범 사례를 모듈에 담아 조직 전체에 배포할 수 있다.</p>
</li>
</ul>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine2-도구별-모듈-구현-방법span"><span style='background-color: MediumAquamarine'>2. 도구별 모듈 구현 방법</span></h2>
<hr>

<p><strong>대부분의 IaC 도구는 이미 모듈화 기능을 제공한다. 세션에서는 Bicep, Terraform, Pulumi를 예시로 들었다.</strong></p>
<h3 id="span-stylecolor-slateblue2-1-azure-bicepspan"><span style="color: SlateBlue">2-1. Azure Bicep</span></h3>
<ul>
<li><p><span style="color: indianred"><strong>구현:</strong></span> 일반적인 <code>.bicep</code> 파일이 곧 모듈이 된다.</p>
</li>
<li><p><span style="color: indianred"><strong>인터페이스 설계:</strong></span> <code>param</code>으로 입력을 받고 output으로 결과를 내보낸다.</p>
</li>
<li><p><span style="color: indianred"><strong>내부 로직:</strong></span> 리소스 생성 시 <code>uniqueString()</code> 함수 등을 사용해 네이밍 규칙을 자동화하거나, 특정 속성(예: <code>supportsHttpsTrafficOnly: true</code>)을 강제하여 사용자에게 보이지 않게 처리한다.</p>
<br>


</li>
</ul>
<ul>
<li><span style="color: indianred"><strong>소비:</strong></span> <code>module</code> 키워드를 사용하여 해당 bicep 파일을 참조하고 파라미터를 전달한다.</li>
</ul>
<h3 id="span-stylecolor-slateblue2-2-terraformspan"><span style="color: SlateBlue">2-2. Terraform</span></h3>
<ul>
<li><p><span style="color: indianred"><strong>구조:</strong></span> 일반적으로 <code>main.tf</code> (리소스 로직), <code>variables.tf</code> (입력 변수), <code>outputs.tf</code> (출력값)로 파일을 분리하여 관리한다.</p>
</li>
<li><p><span style="color: indianred"><strong>소비:</strong></span> <code>module</code> 블록을 선언하고 source 속성에 모듈이 위치한 경로(로컬 또는 원격)를 지정하여 사용한다.</p>
<br>

</li>
</ul>
<h3 id="span-stylecolor-slateblue2-3-pulumispan"><span style="color: SlateBlue">2-3. Pulumi</span></h3>
<ul>
<li><p><span style="color: indianred"><strong>특징:</strong></span> Python, C# 등 범용 프로그래밍 언어를 사용하므로, 해당 언어의 클래스나 함수 구조를 그대로 활용한다.</p>
</li>
<li><p><span style="color: indianred"><strong>구현:</strong></span> <code>&#39;Component Resource&#39;</code> 클래스를 정의하여 리소스 생성 로직을 캡슐화한다.</p>
</li>
<li><p><span style="color: indianred"><strong>소비:</strong></span> 메인 코드에서 해당 클래스의 인스턴스를 생성하는 방식으로 모듈을 호출한다.</p>
</li>
</ul>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine3-좋은-모듈을-만드는-7원칙span"><span style='background-color: MediumAquamarine'>3. 좋은 모듈을 만드는 7원칙</span></h2>
<hr>

<p><strong>단순히 코드를 분리한다고 좋은 모듈이 되는 것은 아니다. 지속 가능한 라이브러리를 구축하기 위해 다음 원칙을 따라야 한다.</strong></p>
<p><img src="https://velog.velcdn.com/images/taekyu_/post/4d3b8986-002f-4b04-92db-e0ea45a54ec6/image.png" alt=""></p>
<ol>
<li><p><span style="color: SlateBlue"><strong>단일 책임 원칙:</strong></span></p>
<ul>
<li><p><strong>슈퍼 모듈을 피해야 한다.</strong> 스토리지 모듈은 스토리지 생성에만 집중해야 한다.</p>
</li>
<li><p><span style="color: indianred"><strong>예외:</strong></span> Kubernetes 클러스터처럼 그 자체로 복합적인 리소스는 묶어서 배포하되, 네트워크처럼 외부 의존성이 있는 리소스는 분리하여 주입받는 것이 좋다.</p>
</li>
</ul>
</li>
<li><p><span style="color: SlateBlue"><strong>단순한 인터페이스:</strong></span></p>
<ul>
<li><p>모든 속성을 파라미터로 노출하지 말아야 한다. <strong>모듈의 목적은 복잡성 은폐이다.</strong></p>
</li>
<li><p>사용자가 고민할 필요가 없는 설정(예: 보안 표준)은 내부적으로 고정하고, 꼭 필요한 값만 입력받는다.</p>
</li>
</ul>
</li>
<li><p><span style="color: SlateBlue"><strong>가치 창출:</strong></span></p>
<ul>
<li><p>단순히 리소스 그룹처럼 옵션이 거의 없는 리소스를 감싸는 Wrapper 모듈은 의미가 없다. </p>
</li>
<li><p><strong>복잡성을 줄이거나 표준을 강제하는 가치가 있어야 한다.</strong></p>
</li>
</ul>
</li>
<li><p><span style="color: SlateBlue"><strong>버전 관리:</strong></span></p>
<ul>
<li><p>모듈 코드를 수정했을 때 기존 사용자의 배포가 깨지면 안 된다. </p>
</li>
<li><p>시맨틱 버저닝을 도입하여 사용자가 업그레이드 시점을 선택할 수 있게 해야 한다.</p>
</li>
</ul>
</li>
<li><p><span style="color: SlateBlue"><strong>접근성:</strong></span></p>
<ul>
<li>사용자가 모듈을 쉽게 가져다 쓸 수 있어야 한다. </li>
</ul>
</li>
<li><p><span style="color: SlateBlue"><strong>유지보수:</strong></span></p>
<ul>
<li><p>클라우드 공급자의 신규 기능이나 변경 사항을 제때 반영해야 한다. </p>
</li>
<li><p>방치된 모듈은 결국 버려진다.</p>
</li>
</ul>
</li>
<li><p><span style="color: SlateBlue"><strong>문서화 및 홍보:</strong></span></p>
<ul>
<li><p>사용법이 문서화되지 않은 모듈은 아무도 쓰지 않는다. </p>
</li>
<li><p>또한, 모듈의 존재와 사용 이점을 조직 내에 적극적으로 알려야 한다.</p>
</li>
</ul>
</li>
</ol>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine4-모듈-배포-및-관리-전략span"><span style='background-color: MediumAquamarine'>4. 모듈 배포 및 관리 전략</span></h2>
<hr>

<p>*<em>로컬 경로(<code>./modules/storage</code>)를 참조하는 방식은 협업 환경에서 확장성이 없다. *</em></p>
<p><span style="color: indianred"><strong>각 도구별로 제공하는 중앙 저장소 기능을 활용하여 단일 진실 공급원을 구축해야 한다.</strong></span></p>
<ul>
<li><p><span style="color: SlateBlue"><strong>Azure (Bicep/ARM):</strong></span></p>
<ul>
<li><p><span style="color: indianred"><strong>Template Specs:</strong></span> Azure 리소스로 템플릿을 저장하고 버전 관리할 수 있다.</p>
</li>
<li><p><span style="color: indianred"><strong>Bicep Registry:</strong></span> Azure Container Registry(ACR)를 OCI 호환 저장소로 활용하여 모듈을 배포할 수 있다.</p>
</li>
</ul>
</li>
<li><p><span style="color: SlateBlue"><strong>Terraform:</strong></span></p>
<ul>
<li>Git 리포지토리를 직접 참조하거나, Terraform Registry (Terraform Cloud/Enterprise)를 사용한다.</li>
</ul>
</li>
<li><p><span style="color: SlateBlue"><strong>Pulumi:</strong></span></p>
<ul>
<li>프로그래밍 언어 기반이므로 npm (Node.js), PyPI (Python), NuGet (.NET) 등 언어별 패키지 매니저를 통해 배포하고 설치한다. </li>
</ul>
</li>
<li><p><span style="color: SlateBlue"><strong>AWS CloudFormation:</strong></span></p>
<ul>
<li>CloudFormation Registry를 통해 모듈을 등록하고 관리한다.</li>
</ul>
</li>
</ul>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine5-정리span"><span style='background-color: MediumAquamarine'>5. 정리</span></h2>
<hr>

<p>*<em>인프라 코드 모듈화는 단순한 코드 정리를 넘어, 조직의 인프라 거버넌스를 기술적으로 구현하는 핵심 전략이다. *</em></p>
<p><span style="color: indianred"></span>
<span style="color: SlateBlue"></span></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[90DaysOfDevOps (Day 84)]]></title>
            <link>https://velog.io/@taekyu_/90DaysOfDevOps-Day-84-ttzx8w7x</link>
            <guid>https://velog.io/@taekyu_/90DaysOfDevOps-Day-84-ttzx8w7x</guid>
            <pubDate>Sun, 01 Feb 2026 03:44:16 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>해당 스터디는 90DaysOfDevOps
<a href="https://github.com/MichaelCade/90DaysOfDevOps">https://github.com/MichaelCade/90DaysOfDevOps</a>
를 기반으로 진행한 내용입니다.</strong></p>
</blockquote>
<p>** Day 84 - Hacking Kubernetes For Beginners **</p>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine1-쿠버네티스-해킹span"><span style='background-color: MediumAquamarine'>1. 쿠버네티스 해킹</span></h2>
<hr>

<p>*<em>해킹이란 마법이 아니라, 설계자가 의도하지 않았던 무언가를 활용하는 창의적 접근이다. *</em></p>
<p>해당 프레젠테이션은 쿠버네티스 클러스터를 공격하는 5가지 주요 시나리오를 통해 공격자의 관점과 방어자의 대응 전략을 살펴본다.</p>
<p><img src="https://velog.velcdn.com/images/taekyu_/post/c06ee6c8-923d-4c75-a3f8-49ff1d003e8b/image.png" alt=""></p>
<ul>
<li><p><span style="color: indianred"><strong>시나리오1:</strong></span> <strong>Kubelet 해킹</strong></p>
</li>
<li><p><span style="color: indianred"><strong>시나리오2:</strong></span> <strong>서비스 및 웹 애플리케이션 해킹</strong></p>
</li>
<li><p><span style="color: indianred"><strong>시나리오3:</strong></span> 컨테이너 탈출</p>
</li>
<li><p><span style="color: indianred"><strong>시나리오4:</strong></span> 이미지 레지스트리 오염</p>
</li>
<li><p><span style="color: indianred"><strong>시나리오5:</strong></span> 악의적인 관리자와 특권 파드</p>
</li>
</ul>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine2-시나리오1-kubelet-해킹span"><span style='background-color: MediumAquamarine'>2. 시나리오1: Kubelet 해킹</span></h2>
<hr>

<h3 id="span-stylecolor-slateblue2-1-정상적인-흐름span"><span style="color: SlateBlue">2-1. 정상적인 흐름</span></h3>
<p>관리자가 kubectl을 사용하여 파드에 접속하려 할 때, 요청은 
<span style="color: indianred"><strong><code>API Server -&gt; etcd(검증) -&gt; Kubelet(노드)</code></strong></span> 순으로 전달된다. </p>
<p><strong>Kubelet은 각 워커 노드의 문지기 역할을 하며 파드와 컨테이너를 관리한다.</strong>
<br></p>
<h3 id="span-stylecolor-slateblue2-2-red-team-공격-방법span"><span style="color: SlateBlue">2-2. Red Team: 공격 방법</span></h3>
<p>공격자는 API 서버를 거치지 않고, 워커 노드의 <strong>Kubelet 포트(기본 10250)</strong>로 직접 통신을 시도한다.</p>
<ul>
<li><p><span style="color: indianred"><strong>공격 기법:</strong></span> <strong><code>curl -k https://&lt;node-ip&gt;:10250/pods</code></strong> 등과 같이 비인증 요청을 보낸다.</p>
</li>
<li><p><span style="color: indianred"><strong>결과:</strong></span> Kubelet이 적절히 보호되지 않은 경우, 공격자는 인증 없이 파드 목록을 조회하거나 exec API를 통해 컨테이너 내부로 진입하여 명령어를 실행할 수 있다.</p>
<br>

</li>
</ul>
<h3 id="span-stylecolor-slateblue2-3-blue-team-방어-및-조치-전략span"><span style="color: SlateBlue">2-3. Blue Team: 방어 및 조치 전략</span></h3>
<p>강연에서는 보안 담당자가 파라미터를 설정했다고 언급했다. </p>
<p><strong>이를 구체적으로 구현하기 위해서는 Kubelet 설정 파일(<code>/var/lib/kubelet/config.yaml</code>)을 다음과 같이 수정해야 한다.</strong></p>
<ol>
<li><p><span style="color: indianred"><strong>익명 인증 비활성화:</strong></span> 누구나 접근하지 못하도록 설정</p>
<ul>
<li><code>authentication.anonymous.enabled: false</code></li>
</ul>
</li>
<li><p><span style="color: indianred"><strong>Kubelet 권한 부여 모드 설정:</strong></span> 모든 요청이 API 서버를 통해 권한 검사를 받도록 설정</p>
<ul>
<li><code>authorization.mode: Webhook</code></li>
</ul>
</li>
<li><p><span style="color: indianred"><strong>인증서 기반 접근 제어:</strong></span> 클라이언트 인증서(x509)가 있는 요청만 수락하도록 설정한다.</p>
</li>
<li><p><span style="color: indianred"><strong>네트워크 정책 (NetworkPolicy):</strong></span> 워커 노드의 10250 포트에 대한 접근을 API 서버 등 신뢰할 수 있는 소스로만 제한하는 방화벽 규칙을 적용한다.</p>
</li>
</ol>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine3-시나리오2-서비스-및-웹-애플리케이션-해킹span"><span style='background-color: MediumAquamarine'>3. 시나리오2: 서비스 및 웹 애플리케이션 해킹</span></h2>
<hr>

<h3 id="span-stylecolor-slateblue3-1-정상적인-흐름span"><span style="color: SlateBlue">3-1. 정상적인 흐름</span></h3>
<p>사용자가 웹사이트에 접속하면 
<span style="color: indianred"><strong><code>Ingress -&gt; Service -&gt; Pod(Frontend)</code></strong></span> 순으로 트래픽이 전달된다. </p>
<p>프론트엔드에서 User ID를 입력하면 백엔드 DB와 통신하여 인증을 수행한다.
<br></p>
<h3 id="span-stylecolor-slateblue3-2-red-team-공격-방법span"><span style="color: SlateBlue">3-2. Red Team: 공격 방법</span></h3>
<p>공격자는 웹 애플리케이션의 입력 필드(User ID 창)에 정상적인 ID 대신 악의적인 명령어를 주입한다.</p>
<ul>
<li><p><span style="color: indianred"><strong>공격 기법:</strong></span> SQL Injection 또는 Command Injection </p>
<ul>
<li><code>; cat /etc/passwd</code> 또는 MySQL 제어 명령</li>
</ul>
</li>
<li><p><span style="color: indianred"><strong>결과:</strong></span> 애플리케이션의 취약점을 통해 백엔드 데이터베이스를 탈취하거나, Reverse Shell을 실행하여 컨테이너 내부 쉘 권한을 획득한다.</p>
<br>

</li>
</ul>
<h3 id="span-stylecolor-slateblue3-3-blue-team-방어-및-조치-전략span"><span style="color: SlateBlue">3-3. Blue Team: 방어 및 조치 전략</span></h3>
<p>강연에서는 코드를 수정하고 출력 제어를 추가했다고 했다. </p>
<p><strong>이는 애플리케이션 레벨과 인프라 레벨의 보안이 모두 필요하다.</strong></p>
<ol>
<li><p><span style="color: indianred"><strong>입력 값 검증:</strong></span> 애플리케이션 코드 단계에서 사용자 입력값에 대한 엄격한 타입 체크와 특수문자 필터링을 수행한다. (Secure Coding)</p>
</li>
<li><p><span style="color: indianred"><strong>WAF (Web Application Firewall):</strong></span> Ingress 단계에서 ModSecurity나 AWS WAF 등을 연동하여 SQL Injection 등의 잘 알려진 공격 패턴을 차단한다.</p>
</li>
<li><p><span style="color: indianred"><strong>최소 권한 원칙:</strong></span> 만약 뚫리더라도 피해를 최소화하기 위해 컨테이너를 Non-Root 사용자로 실행하고, 파일 시스템을 Read-Only로 설정한다.</p>
</li>
</ol>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine4-시나리오3-컨테이너-탈출span"><span style='background-color: MediumAquamarine'>4. 시나리오3: 컨테이너 탈출</span></h2>
<hr>

<h3 id="span-stylecolor-slateblue4-1-정상적인-흐름span"><span style="color: SlateBlue">4-1. 정상적인 흐름</span></h3>
<p><strong>공격자가 시나리오 2를 통해 컨테이너 내부 쉘을 획득했다고 가정한다.</strong>
<br></p>
<h3 id="span-stylecolor-slateblue4-2-red-team-공격-방법span"><span style="color: SlateBlue">4-2. Red Team: 공격 방법</span></h3>
<p><strong>공격자는 먼저 정찰을 수행한다. <code>id</code> 명령어로 루트 권한인지 확인하고, <code>ls /</code> 등을 통해 도커 환경임을 파악한다.</strong></p>
<ul>
<li><p><span style="color: indianred"><strong>공격 기법:</strong></span> 컨테이너가 과도한 권한을 가지고 있거나 커널 취약점이 있는 경우, 특수 제작된 스크립트를 실행하여 컨테이너 격리를 뚫고 호스트 또는 워커 노드의 쉘을 획득한다.</p>
</li>
<li><p><span style="color: indianred"><strong>결과:</strong></span> 워커 노드의 루트 권한을 장악하여 해당 노드의 모든 컨테이너를 제어한다.</p>
<br>

</li>
</ul>
<h3 id="span-stylecolor-slateblue4-3-blue-team-방어-및-조치-전략span"><span style="color: SlateBlue">4-3. Blue Team: 방어 및 조치 전략</span></h3>
<p>강연에서는 컨테이너 보안 파라미터를 설정했다고 했다. <strong>이는 SecurityContext와 런타임 보안을 의미한다.</strong></p>
<ol>
<li><p><span style="color: indianred"><strong>SecurityContext 강화:</strong></span> 파드 배포 시 다음 설정을 강제한다.</p>
<ul>
<li><p><code>allowPrivilegeEscalation: false</code> (권한 상승 방지)</p>
</li>
<li><p><code>privileged: false</code> (특권 모드 금지)</p>
</li>
<li><p><code>runAsNonRoot: true</code> (루트 사용자 실행 금지)</p>
</li>
</ul>
</li>
<li><p><span style="color: indianred"><strong>Linux Capabilities 제한:</strong></span> 컨테이너에 불필요한 리눅스 커널 권한을 제거한다. (<code>drop: [&quot;ALL&quot;]</code>)</p>
</li>
<li><p><span style="color: indianred"><strong>Seccomp 및 AppArmor 프로파일 적용:</strong></span> 컨테이너가 호스트 커널에 요청할 수 있는 System Call을 제한하여 탈출 가능성을 차단한다.</p>
</li>
<li><p><span style="color: indianred"><strong>Sandbox 컨테이너:</strong></span> gVisor나 Kata Containers와 같은 샌드박스 기술을 사용하여 커널 레벨의 격리를 강화한다.</p>
</li>
</ol>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine5-시나리오4-이미지-레지스트리-오염span"><span style='background-color: MediumAquamarine'>5. 시나리오4: 이미지 레지스트리 오염</span></h2>
<hr>

<h3 id="span-stylecolor-slateblue5-1-정상적인-흐름span"><span style="color: SlateBlue">5-1. 정상적인 흐름</span></h3>
<p><strong>Kubelet은 파드를 생성할 때 지정된 레지스트리에서 이미지를 Pull하여 컨테이너를 실행한다.</strong>
<br></p>
<h3 id="span-stylecolor-slateblue5-2-red-team-공격-방법span"><span style="color: SlateBlue">5-2. Red Team: 공격 방법</span></h3>
<p><strong>공격자가 레지스트리 접근 권한을 탈취하여 정상 이미지를 악성 코드가 심어진 이미지로 교체한다.</strong></p>
<ul>
<li><p><span style="color: indianred"><strong>공격 기법:</strong></span> 이미지 내부에 백도어나 리버스 쉘 코드를 삽입하여 기존 태그로 덮어쓰기한다.</p>
</li>
<li><p><span style="color: indianred"><strong>결과:</strong></span> 관리자가 정상적인 절차로 파드를 생성하더라도, 실행되는 컨테이너는 이미 오염된 상태이므로 공격자가 즉시 접속할 수 있다.</p>
<br>

</li>
</ul>
<h3 id="span-stylecolor-slateblue5-3-blue-team-방어-및-조치-전략span"><span style="color: SlateBlue">5-3. Blue Team: 방어 및 조치 전략</span></h3>
<p>강연에서는 이미지 스캔 및 해시 키 비교를 언급했다. 이를 자동화된 파이프라인으로 구축해야 한다.</p>
<ul>
<li><p><span style="color: indianred"><strong>이미지 서명 및 검증:</strong></span> </p>
<ul>
<li><p>Cosign이나 Notary를 사용하여 이미지를 서명한다. </p>
</li>
<li><p>배포 시 Admission Controller(Kyverno, OPA Gatekeeper)가 서명이 유효한 이미지만 실행되도록 강제한다.</p>
</li>
</ul>
</li>
<li><p><span style="color: indianred"><strong>이미지 취약점 스캔:</strong></span> CI/CD 파이프라인 및 레지스트리(Harbor 등) 단계에서 Trivy, Clair 등을 사용해 이미지를 주기적으로 스캔한다.</p>
</li>
<li><p><span style="color: indianred"><strong>불변 태그 사용:</strong></span> 레지스트리 설정에서 한 번 푸시된 태그(예: v1.0)를 덮어쓰지 못하도록 설정하거나, 이미지 태그 대신 고유한 해시값(sha256:abcd...)을 사용하여 배포한다.</p>
</li>
</ul>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine6-시나리오5-악의적인-관리자와-특권-파드span"><span style='background-color: MediumAquamarine'>6. 시나리오5: 악의적인 관리자와 특권 파드</span></h2>
<hr>

<h3 id="span-stylecolor-slateblue6-1-정상적인-흐름span"><span style="color: SlateBlue">6-1. 정상적인 흐름</span></h3>
<p><strong>쿠버네티스 관리자 권한을 가진 사용자(혹은 탈취된 계정)가 파드를 생성한다.</strong>
<br></p>
<h3 id="span-stylecolor-slateblue6-2-red-team-공격-방법span"><span style="color: SlateBlue">6-2. Red Team: 공격 방법</span></h3>
<p>관리자 계정이라도 호스트 서버(마스터/워커 노드)의 루트 파일 시스템에는 직접 접근할 수 없다. 하지만 관리자는 파드를 생성할 권한이 있다.</p>
<ul>
<li><span style="color: indianred"><strong>공격 기법:</strong></span> 호스트의 루트 디렉토리(<code>/</code>)를 파드 내부의 볼륨으로 마운트(hostPath 방식 이용)하는 특권 파드를 생성한다.</li>
<li><pre><code class="language-yaml">volumes:
- name: host-root
  hostPath:
    path: /</code></pre>
</li>
<li><span style="color: indianred"><strong>결과:</strong></span> 파드 내부로 진입(<code>exec</code>)하면 마운트된 볼륨을 통해 호스트의 모든 파일(Shadow 파일, SSH 키 등)을 읽고 수정할 수 있어 호스트를 완전히 장악한다.</li>
</ul>
<br>

<h3 id="span-stylecolor-slateblue6-2-red-team-공격-방법span-1"><span style="color: SlateBlue">6-2. Red Team: 공격 방법</span></h3>
<p>강연에서는 관리자 권한 제한 및 모니터링을 언급했다. </p>
<p><strong>이는 50:50의 싸움이라고 했지만, 기술적으로 통제 가능하다.</strong></p>
<ul>
<li><p><span style="color: indianred"><strong>Pod Security Admission (PSA) 적용:</strong></span> 네임스페이스 수준에서 <code>Restricted</code> 또는 <code>Baseline</code> 정책을 적용하여 <strong>hostPath 사용이나 privileged 모드를 원천 차단한다.</strong></p>
</li>
<li><p><span style="color: indianred"><strong>정책 엔진 도입 (Policy-as-Code):</strong></span> OPA Gatekeeper나 Kyverno를 사용하여 &quot;특정 그룹을 제외하고는 hostPath 마운트 금지&quot;와 같은 세밀한 정책을 강제한다.</p>
</li>
<li><p><span style="color: indianred"><strong>RBAC 최소화:</strong></span> </p>
<ul>
<li><p>파드 생성 권한은 생각보다 강력한 권한이다. </p>
</li>
<li><p>개발자에게는 파드 생성 권한 대신, 제한된 범위의 권한만 부여하거나 ArgoCD와 같은 GitOps 도구를 통해서만 배포하도록 통제한다.</p>
</li>
</ul>
</li>
<li><p><span style="color: indianred"><strong>Audit Logging:</strong></span> 쿠버네티스 Audit Log를 활성화하고, SIEM 시스템(Falcosecurity 등)과 연동하여 특권 파드 생성이 시도될 때 즉시 알람을 발생시킨다.</p>
</li>
</ul>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine7-정리-span"><span style='background-color: MediumAquamarine'>7. 정리 </span></h2>
<hr>

<p>*<em>방어자의 업무는 매우 어렵다. *</em></p>
<p><strong>공격자는 수많은 취약점 중 단 하나만 성공시키면 클러스터를 장악할 수 있지만, 방어자는 모든 구멍을 막아야 하기 때문이다.</strong></p>
<p>오늘 살펴본 5가지 시나리오는 빙산의 일각이며, 쿠버네티스 보안을 강화하기 위해서는 &#39;Hacking Kubernetes&#39;, &#39;Container Security&#39;와 같은 전문 서적을 참고하고, 지속적으로 클러스터의 보안 태세를 점검하는 것이 필수적이다.</p>
<p><span style="color: indianred"></span>
<span style="color: SlateBlue"></span></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[90DaysOfDevOps (Day 83)]]></title>
            <link>https://velog.io/@taekyu_/90DaysOfDevOps-Day-83-8dcir6g6</link>
            <guid>https://velog.io/@taekyu_/90DaysOfDevOps-Day-83-8dcir6g6</guid>
            <pubDate>Sun, 01 Feb 2026 02:46:50 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>해당 스터디는 90DaysOfDevOps
<a href="https://github.com/MichaelCade/90DaysOfDevOps">https://github.com/MichaelCade/90DaysOfDevOps</a>
를 기반으로 진행한 내용입니다.</strong></p>
</blockquote>
<p>** Day 83 - Saving Cloud Costs Using Existing Prometheus Metrics **</p>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine1-krr-kubernetes-resource-recommender-란span"><span style='background-color: MediumAquamarine'>1. KRR (Kubernetes Resource Recommender) 란?</span></h2>
<hr>

<p>최근 1~2년 사이 많은 기업들이 클라우드 비용 절감을 위해 온프레미스로 회귀하거나 하이브리드 클라우드 전략을 채택하고 있다. </p>
<p><span style="color: indianred"><strong>하지만 온프레미스는 확장성과 유지보수 측면에서 명확한 한계가 있다. 클라우드의 이점을 유지하면서 비용을 줄이는 방법은 없을까?</strong></span></p>
<p>연구에 따르면, 쿠버네티스 클러스터 비용의 69% 이상이 리소스 과다 할당으로 인해 낭비되고 있다고 한다. </p>
<p><span style="color: indianred"><strong>즉, 애플리케이션에 적절한 리소스(Request/Limit)만 할당해도 막대한 비용을 절감할 수 있다는 것이다.</strong></span></p>
<p>따라서 프레젠테이션에서는 기존 Prometheus 데이터를 활용하여 데이터 기반의 리소스 최적화 권장 사항을 제공하는 오픈 소스 도구, <strong>KRR(Kubernetes Resource Recommender)</strong>에 대해 제시한다.</p>
<hr>
<p><strong>KRR (Kubernetes Resource Recommender)이란, Robusta에서 개발한 KRR은 쿠버네티스 리소스(CPU/Memory)의 Request과 Limit 값을 최적화할 수 있도록 도와주는 오픈 소스 CLI 도구이다.</strong></p>
<p><img src="https://velog.velcdn.com/images/taekyu_/post/95f63414-1184-45d0-8af9-a60d36eb28cf/image.png" alt=""></p>
<ul>
<li><p><span style="color: indianred"><strong>에이전트리스 (No Agent):</strong></span> 클러스터에 별도의 에이전트를 지속적으로 실행할 필요가 없다.</p>
</li>
<li><p><span style="color: indianred"><strong>Prometheus 통합:</strong></span> 이미 수집된 Prometheus의 히스토리 데이터를 활용한다. 
즉, 추측이 아닌 실제 내 애플리케이션의 데이터를 기반으로 분석한다.</p>
</li>
<li><p><span style="color: indianred"><strong>즉시 실행:</strong></span> 한 번의 CLI 실행으로 즉각적인 결과를 얻을 수 있다.</p>
</li>
<li><p><span style="color: indianred"><strong>확장성:</strong></span> Python 기반으로 작성되어 있어 알고리즘을 사용자가 직접 커스터마이징할 수 있다.</p>
</li>
</ul>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine2-설치-및-실행-방법span"><span style='background-color: MediumAquamarine'>2. 설치 및 실행 방법</span></h2>
<hr>

<h3 id="span-stylecolor-slateblue2-1-사전-요구-사항span"><span style="color: SlateBlue">2-1. 사전 요구 사항</span></h3>
<ul>
<li><p><span style="color: indianred"><strong>Prometheus:</strong></span> 클러스터 메트릭을 수집 중이어야 한다.</p>
</li>
<li><p><span style="color: indianred"><strong>kube-state-metrics:</strong></span> KRR이 분석에 필요한 메트릭을 제공한다.</p>
<ul>
<li><strong>참고:</strong> <code>kube-prometheus-stack</code>을 사용 중이라면 이미 포함되어 있어 별도 설정이 필요 없다.<br>


</li>
</ul>
</li>
</ul>
<h3 id="span-stylecolor-slateblue2-2-실행-및-결과-분석span"><span style="color: SlateBlue">2-2. 실행 및 결과 분석</span></h3>
<p>KRR을 실행하면 지난 14일간의 데이터를 분석하여 다음과 같은 권장 사항 테이블을 출력한다.
<img src="https://velog.velcdn.com/images/taekyu_/post/0b1d577b-e28b-4d57-9af2-df88d1a87497/image.png" alt=""></p>
<ul>
<li><p><span style="color: indianred"><strong>Pods/Old Pods:</strong></span> 현재 실행 중인 파드와 과거 파드 수.</p>
</li>
<li><p><span style="color: indianred"><strong>Container:</strong></span> 파드 내 각 컨테이너별로 분석 결과를 제공한다.</p>
</li>
<li><p><span style="color: indianred"><strong>Recommended Request/Limit:</strong></span> 데이터 기반으로 산출된 최적의 CPU/Memory 값.</p>
</li>
<li><p><span style="color: indianred"><strong>Diff:</strong></span> 현재 설정값과 권장 값의 차이를 보여준다. (예: +251m은 추가 필요, -925Mi는 절감 가능)</p>
</li>
</ul>
<p><strong>이 결과를 통해 사용자는 어떤 서비스가 과다 할당되었는지, 혹은 리소스 부족 위험이 있는지 한눈에 파악할 수 있다.</strong></p>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine3-krr에서는-왜-cpu-limit을-설정하지-말아야-하는가span"><span style='background-color: MediumAquamarine'>3. KRR에서는 왜 CPU Limit을 설정하지 말아야 하는가</span></h2>
<hr>

<p><strong>KRR을 실행해보면 대부분의 권장 사항이 CPU Limit을 설정 해제(Unset)하라는 내용임을 알게 된다.</strong> </p>
<p>이는 많은 엔지니어들이 의아해하는 부분이지만, 첨부된 이미지를 통해 그 이유를 명확히 이해할 수 있다.
<img src="https://velog.velcdn.com/images/taekyu_/post/06178703-3630-4b25-aa97-80c51d318761/image.png" width="50%" height="n%"></p>
<p><strong>해당 이미지는 CPU Request와 Limit 설정 조합에 따른 파드의 동작 방식을 4분면으로 나누어 설명한다.</strong></p>
<ol>
<li><p><span style="color: SlateBlue"><strong>CPU Requests + CPU Limits (주황색 - 좌상단)</strong></span></p>
<ul>
<li><p><span style="color: indianred"><strong>상태:</strong></span> Request와 Limit을 모두 설정한 경우.</p>
</li>
<li><p><span style="color: indianred"><strong>동작:</strong></span> Request만큼의 CPU는 보장받는다. 하지만 Limit을 초과하는 CPU 사용은 불가능하다.</p>
</li>
<li><p><span style="color: indianred"><strong>문제점:</strong></span> 노드에 유휴 CPU 자원이 남더라도, 파드는 Limit에 막혀 이를 사용할 수 없다. 이는 CPU Throttling을 유발하여 애플리케이션의 성능 저하(Latency 증가)로 이어진다.</p>
<br>
</li>
</ul>
</li>
<li><p><span style="color: SlateBlue"><strong>CPU Requests + No CPU Limits (초록색 - 우상단, 권장)</strong></span></p>
<ul>
<li><p><span style="color: indianred"><strong>상태:</strong></span> Request는 설정하고, Limit은 설정하지 않은 경우.</p>
</li>
<li><p><span style="color: indianred"><strong>동작:</strong></span> Request만큼의 CPU 자원은 확실히 보장받는다.</p>
</li>
<li><p><span style="color: indianred"><strong>장점:</strong></span> 노드에 남는 CPU 자원이 있다면, 파드는 필요한 만큼 이를 가져다 쓸 수 있다.</p>
</li>
<li><p><span style="color: indianred"><strong>결론:</strong></span> *<em>CPU는 압축 가능한 자원이다. *</em>
즉, 시스템이 바쁠 때는 조금 느려질 뿐 죽지는 않는다. 따라서 Limit을 없애 유휴 자원을 최대한 활용하여 성능을 높이는 것이 효율적이다. KRR이 이 방식을 권장하는 이유다.</p>
<br>
</li>
</ul>
</li>
<li><p><span style="color: SlateBlue"><strong>No CPU Requests (하단 영역)</strong></span></p>
<ul>
<li><p><span style="color: indianred"><strong>No Requests + Limits (주황색):</strong></span> 쿠버네티스가 자동으로 Request를 Limit과 동일하게 설정한다. Limit만큼 보장되지만 그 이상은 사용 불가하다.</p>
</li>
<li><p><span style="color: indianred"><strong>No Requests + No Limits (빨간색 - 우하단):</strong></span> 아무런 자원 보장도 받지 못한다. 노드 자원이 부족하면 가장 먼저 성능이 저하되거나 죽을 수 있다. 절대 권장하지 않는다.</p>
<br>

</li>
</ul>
</li>
</ol>
<blockquote>
<ul>
<li><span style="color: SlateBlue"><strong>주의:</strong></span> <strong>위 내용은 CPU에만 해당된다. Memory는 압축 불가능한 자원이므로, Limit을 초과하면 OOM Kill이 발생한다. 따라서 메모리는 Limit 설정이 필수적이다.</strong></li>
</ul>
</blockquote>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine4-krr-vs-vpa-vertical-pod-autoscalerspan"><span style='background-color: MediumAquamarine'>4. KRR vs VPA (Vertical Pod Autoscaler)</span></h2>
<hr>

<p><strong>KRR과 유사한 기능을 하는 쿠버네티스 네이티브 도구인 VPA와의 차이점은 다음과 같다.</strong></p>
<table>
<thead>
<tr>
<th>특징</th>
<th align="left">KRR (Robusta)</th>
<th align="left">VPA (Kubernetes Native)</th>
</tr>
</thead>
<tbody><tr>
<td>실행 방식</td>
<td align="left">CLI 도구 (필요할 때 실행)</td>
<td align="left">클러스터 내 상시 실행 데몬</td>
</tr>
<tr>
<td>결과 도출</td>
<td align="left">즉시 (기존 Prometheus 데이터 활용)</td>
<td align="left">일정 기간 데이터 수집 필요 (Warm-up)</td>
</tr>
<tr>
<td>적용 방식</td>
<td align="left">권장 사항 리포트 제공 (사람이 판단)</td>
<td align="left">파드 설정을 직접/자동으로 수정</td>
</tr>
<tr>
<td>커스텀</td>
<td align="left">알고리즘 확장 및 수정 용이</td>
<td align="left">설정이 제한적</td>
</tr>
</tbody></table>
<p>KRR은 즉각적인 가시성을 제공하고, 변경 사항을 적용하기 전 엔지니어가 검토할 수 있다는 점에서 운영 안정성이 높다.</p>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine5-추가-기능-및-활용span"><span style='background-color: MediumAquamarine'>5. 추가 기능 및 활용</span></h2>
<hr>

<p>KRR은 단순 CLI를 넘어 다양한 방식으로 활용 가능하다.</p>
<ul>
<li><p><span style="color: indianred"><strong>Slack 통합:</strong></span> Robusta와 연동하여 주기적으로 리소스 최적화 리포트를 슬랙으로 받아볼 수 있다.</p>
</li>
<li><p><span style="color: indianred"><strong>상세 분석:</strong></span> Robusta UI를 통해 특정 권장 사항이 도출된 근거(히스토리 그래프 등)를 시각적으로 확인할 수 있다.</p>
</li>
<li><p><span style="color: indianred"><strong>K9s 플러그인:</strong></span> 터미널 UI 도구인 K9s에 플러그인으로 설치하여, 실시간으로 파드 리스트 옆에서 권장 값을 확인할 수 있다.</p>
</li>
</ul>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine6-정리span"><span style='background-color: MediumAquamarine'>6. 정리</span></h2>
<hr>

<ol>
<li><p><span style="color: indianred"><strong>리소스의 Request/Limit 최적화만으로도 상당한 비용을 아낄 수 있다.</strong></span></p>
</li>
<li><p><strong>새로운 데이터 수집 도구 필요 없이, 기존 Prometheus 데이터를 활용하면 된다.</strong></p>
</li>
<li><p><strong>KRR을 사용하면 별도의 에이전트 없이 즉각적인 최적화 권고안을 얻을 수 있다.</strong></p>
</li>
<li><p><strong>CPU Limit을 제거하고 Request를 적절히 설정하는 것이 리소스 효율성과 성능 면에서 가장 유리하다.</strong></p>
</li>
</ol>
<p><span style="color: indianred"></span>
<span style="color: SlateBlue"></span></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[90DaysOfDevOps (Day 82)]]></title>
            <link>https://velog.io/@taekyu_/90DaysOfDevOps-Day-82-mgpnhtb6</link>
            <guid>https://velog.io/@taekyu_/90DaysOfDevOps-Day-82-mgpnhtb6</guid>
            <pubDate>Sat, 31 Jan 2026 17:21:43 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>해당 스터디는 90DaysOfDevOps
<a href="https://github.com/MichaelCade/90DaysOfDevOps">https://github.com/MichaelCade/90DaysOfDevOps</a>
를 기반으로 진행한 내용입니다.</strong></p>
</blockquote>
<p>** Day 82 - Dev Containers in VS Code **</p>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine1-dev-containers의-본질과-도입-필요성-span"><span style='background-color: MediumAquamarine'>1. Dev Containers의 본질과 도입 필요성 </span></h2>
<hr>

<h3 id="span-stylecolor-slateblue1-1-단순한-docker가-아닌-환경-사양-specspan"><span style="color: SlateBlue">1-1. 단순한 Docker가 아닌 환경 사양 (Spec)</span></h3>
<p><strong>많은 개발자가 Dev Containers를 단순히 Docker 이미지를 띄우는 것으로 오해하곤 한다.</strong></p>
<p><span style="color: indianred"><strong>하지만 Dev Containers는 커뮤니티가 채택한 오픈 소스 사양(Specification)이다.</strong></span></p>
<ul>
<li><p><span style="color: indianred"><strong>정의:</strong></span> 단순한 OS 뿐만 아니라 개발에 필요한 도구, 런타임, Extensions, 설정, 파일 시스템 마운트 등을 코드인 devcontainer.json로 정의한 집합체다.
<img src="https://velog.velcdn.com/images/taekyu_/post/990fda0b-8eed-4b6e-8966-1b6a21b106c0/image.png" alt=""></p>
</li>
<li><p><span style="color: indianred"><strong>아키텍처:</strong></span> </p>
<ul>
<li><p>로컬 VS Code가 컨테이너 내부의 VS Code Server와 연결되는 구조다. </p>
</li>
<li><p>소스 코드는 마운트되어 격리된 환경에서 실행되지만, 개발자는 로컬 테마와 단축키를 그대로 사용하는 Inner Loop(내부 개발 루프)를 경험할 수 있다.</p>
</li>
</ul>
</li>
</ul>
<br>


<h3 id="span-stylecolor-slateblue1-2-왜-도입해야-하는가-span"><span style="color: SlateBlue">1-2. 왜 도입해야 하는가? </span></h3>
<p>개발 현장에서 겪는 세 가지 주요 고통을 해결한다.</p>
<ul>
<li><p><span style="color: indianred"><strong>고통스러운 온보딩:</strong></span> </p>
<ul>
<li>신규 프로젝트 투입 시 하드웨어 세팅, 라이선스 설정, 특정 경로 파일 복사 등 수작업으로 인해 며칠씩 소요되는 시간을 단축한다.</li>
</ul>
</li>
<li><p><span style="color: indianred"><strong>버전 호환성 문제:</strong></span> </p>
<ul>
<li><p>&quot;팀원 A는 Node 14를 쓰고 나는 Node 12를 써서 코드가 깨지는&quot; 상황을 방지한다. </p>
</li>
<li><p>컨설팅 업무 시 고객사마다 다른 언어 버전(.NET, Java 등)을 요구해도, 로컬 머신의 오염 없이 프로젝트별로 격리된 환경을 사용할 수 있다.</p>
</li>
</ul>
</li>
<li><p><span style="color: indianred"><strong>문서화와 실행의 괴리:</strong></span> </p>
<ul>
<li><p>README에 &quot;시작 전 1, 2, 3단계를 수행하시오&quot;라고 적어도 누락하기 쉽다. </p>
</li>
<li><p>Dev Containers는 <code>post-create-command</code> 등을 통해 초기화 작업을 자동화한다.</p>
</li>
</ul>
</li>
</ul>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine2-환경-구축-실습-span"><span style='background-color: MediumAquamarine'>2. 환경 구축 실습 </span></h2>
<hr>


<p><strong>Dev Containers의 가장 큰 장점은 로컬(Docker Desktop)과 클라우드(GitHub Codespaces)를 동일한 설정으로 오갈 수 있다는 유연성이다.</strong></p>
<h3 id="span-stylecolor-slateblue2-1-로컬-vs-code에서의-구축-span"><span style="color: SlateBlue">2-1. 로컬 VS Code에서의 구축 </span></h3>
<p><strong>프레젠테이션에서는 VS Code와 Docker Desktop(WSL 2)을 사용한 데모를 통해 다음 과정을 시연했다.</strong></p>
<ul>
<li><p><span style="color: indianred"><strong>필수 도구:</strong></span> &#39;Dev Containers&#39; 확장 프로그램과 &#39;GitHub Codespaces&#39; 확장이 필요하다.</p>
</li>
<li><p><span style="color: indianred"><strong>설정 생성:</strong></span> 명령 팔레트(F1)에서 <code>Add Dev Container Configuration Files</code>를 실행하여 템플릿을 선택한다. Alpine, Debian 같은 OS 베이스나 Go, Rust 같은 언어 베이스 템플릿을 제공한다.</p>
</li>
<li><p><span style="color: indianred"><strong>기능 활용:</strong></span> 복잡한 Dockerfile 스크립트 작성 없이, &#39;Features&#39; 메뉴에서 Azure CLI, Terraform, Kubectl 등을 체크박스 선택만으로 컨테이너에 주입할 수 있다.</p>
</li>
<li><p><span style="color: indianred"><strong>결과물:</strong></span> </p>
<ul>
<li><p><code>.devcontainer</code> 폴더 내에 <code>devcontainer.json(메인 설정)</code>, <code>Dockerfile(빌드 정의)</code>, <code>dependabot.yml(버전 관리)</code> 등이 생성된다.</p>
</li>
<li><p>이를 실행(&quot;Reopen in Container&quot;)하면 로컬에는 없는 도구들이 설치된 완벽한 개발 환경이 뚝딱 만들어진다.</p>
<br>


</li>
</ul>
</li>
</ul>
<h3 id="span-stylecolor-slateblue2-2-github-codespaces를-활용한-클라우드-개발span"><span style="color: SlateBlue">2-2. GitHub Codespaces를 활용한 클라우드 개발</span></h3>
<p>고사양 PC가 없거나 Docker 라이선스 문제가 있다면 GitHub Codespaces가 대안이 된다.</p>
<ul>
<li><p><span style="color: indianred"><strong>브라우저 모드:</strong></span> GitHub 리포지토리에서 버튼 하나로 실행되며, 별도 설치 없이 브라우저 내에서 VS Code 환경을 제공한다. .NET 8, GitHub CLI 등 설정된 도구가 즉시 가용 상태가 된다.</p>
</li>
<li><p><span style="color: indianred"><strong>하이브리드 모드 (Open in VS Code):</strong></span> 브라우저가 어색하다면, 로컬 VS Code를 인터페이스로 사용하고 컴퓨팅 자원만 클라우드(Codespace)를 사용할 수 있다. 로컬의 편안함과 클라우드의 성능을 동시에 누리는 방식이다.</p>
</li>
<li><p><span style="color: indianred"><strong>관리 기능:</strong></span> Dotfiles(개인 설정) 자동 적용, Secrets(API 키) 관리, 데이터 주권을 위한 리전 선택 등을 지원한다.</p>
</li>
<li><p><span style="color: indianred"><strong><code>containers.dev</code> 문서: **</span> **<a href="https://containers.dev/features">https://containers.dev/features</a></strong></p>
</li>
</ul>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine3-디버깅-및-네트워크-통합-span"><span style='background-color: MediumAquamarine'>3. 디버깅 및 네트워크 통합 </span></h2>
<hr>

<p><strong>Dev Containers는 환경만 격리할 뿐, 개발자가 누려야 할 생산성 도구(디버거, 네트워크 등)는 로컬 환경처럼 투명하게 연결해 준다.</strong></p>
<h3 id="span-stylecolor-slateblue3-1-로컬과-동일한-원격-디버깅span"><span style="color: SlateBlue">3-1. 로컬과 동일한 원격 디버깅</span></h3>
<p>프레젠테이션에서는 Rust와 Go 언어 샘플을 통해 디버깅 과정을 시연했다.</p>
<ul>
<li><p>코드는 컨테이너(또는 클라우드) 내부에서 돌지만, 개발자는 로컬 VS Code에서 중단점(Breakpoint)을 설정한다.</p>
</li>
<li><p>F5를 눌러 실행하면 정확히 중단점에서 멈추고, 변수 값 조사(Inspect)나 스택 추적을 로컬 개발과 똑같이 수행할 수 있다.</p>
<br>

</li>
</ul>
<h3 id="span-stylecolor-slateblue3-2-포트-포워딩span"><span style="color: SlateBlue">3-2. 포트 포워딩</span></h3>
<p>웹 애플리케이션 개발 시 네트워크 연결도 자동으로 처리된다.</p>
<ul>
<li><p><span style="color: indianred"><strong>시나리오:</strong></span> 클라우드 Codespace 내부에서 웹 서버를 9000번 포트로 실행했다.</p>
</li>
<li><p><span style="color: indianred"><strong>자동 연결:</strong></span> VS Code는 이를 감지하고 내 로컬 머신의 <code>localhost:9000</code>으로 자동 포트 포워딩을 수행한다.</p>
</li>
<li><p><span style="color: indianred"><strong>결과:</strong></span>  개발자는 자신의 로컬 브라우저(Chrome, Edge 등)를 열어 localhost:9000에 접속하면, 클라우드에 있는 서버와 연결된다. 심지어 이 요청에 대해서도 실시간 디버깅이 작동한다.</p>
</li>
</ul>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine4-정리-span"><span style='background-color: MediumAquamarine'>4. 정리 </span></h2>
<hr>

<p>*<em>Dev Containers는 단순한 도구가 아니라 개발 환경의 표준화를 위한 솔루션이다. *</em></p>
<p><span style="color: indianred">*<em>프로젝트에 <code>.devcontainer</code> 폴더를 추가하는 것만으로, 팀 전체가 버전 의존성에서 벗어나고 온보딩 시간을 획기적으로 단축할 수 있다. *</em></span></p>
<p><span style="color: indianred"></span>
<span style="color: SlateBlue"></span></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[90DaysOfDevOps (Day 81)]]></title>
            <link>https://velog.io/@taekyu_/90DaysOfDevOps-Day-81-yqv0c0pr</link>
            <guid>https://velog.io/@taekyu_/90DaysOfDevOps-Day-81-yqv0c0pr</guid>
            <pubDate>Fri, 30 Jan 2026 17:43:18 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>해당 스터디는 90DaysOfDevOps
<a href="https://github.com/MichaelCade/90DaysOfDevOps">https://github.com/MichaelCade/90DaysOfDevOps</a>
를 기반으로 진행한 내용입니다.</strong></p>
</blockquote>
<p>** Day 81 - Leveraging Kubernetes to build a better Cloud Native Development Experience **</p>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine1-okteto에-대한-발전-배경-문제-제기span"><span style='background-color: MediumAquamarine'>1. Okteto에 대한 발전 배경 (문제 제기)</span></h2>
<hr>

<h3 id="span-stylecolor-slateblue1-1-배포에서-개발로의-관점-이동span"><span style="color: SlateBlue">1-1. 배포에서 개발로의 관점 이동</span></h3>
<p><strong>2014년 쿠버네티스 초기 시절, 업계의 주된 관심사는 워크로드를 어떻게 쿠버네티스에 배포할 것인가였다.</strong></p>
<p>당시에는 배포 자체가 난제였기에 &#39;Kubernetes the Hard Way&#39;와 같은 학습 자료나 Rancher Desktop 같은 솔루션들이 등장하며 배포의 복잡성을 해결하려 했다.</p>
<p>하지만 시간이 흐르며 상황은 변했다. </p>
<p><span style="color: indianred"><strong>개발자의 본질은 인프라 배포가 아닌 코드 작성에 있다.</strong></span> </p>
<p><strong>개발자는 불필요한 배포에 시간을 쏟기보다 비즈니스 로직 구현에 집중하기를 원한다.</strong>
<br></p>
<h3 id="span-stylecolor-slateblue1-2-전통적인-개발-루프의-고통-span"><span style="color: SlateBlue">1-2. 전통적인 개발 루프의 고통 </span></h3>
<p>일반적인 클라우드 네이티브 애플리케이션 개발 프로세스이다.</p>
<ol>
<li><p><span style="color: indianred"><strong>SRS(요구사항) 분석 및 코딩:</strong></span> 개발팀이 기능을 구현한다.</p>
</li>
<li><p><span style="color: indianred"><strong>컨테이너화:</strong></span> Dockerfile을 작성하고 이미지를 빌드한다.</p>
</li>
<li><p><span style="color: indianred"><strong>레지스트리 Push:</strong></span> 빌드된 이미지를 컨테이너 레지스트리에 업로드한다.</p>
</li>
<li><p><span style="color: indianred"><strong>Pull 및 배포:</strong></span> 쿠버네티스 클러스터에서 이미지를 받아(Pull) 배포한다.</p>
<br>

</li>
</ol>
<p>문제는 기능 추가나 버그 수정이 필요할 때마다 이 과정을 무한 반복해야 한다는 점이다.</p>
<ul>
<li><p><span style="color: indianred"><strong>시나리오:</strong></span> 로컬에서 코드를 수정했다. 이 변경 사항이 운영 환경과 유사한 쿠버네티스 환경에서 어떻게 동작할지 확인해야 한다.</p>
</li>
<li><p><span style="color: indianred"><strong>비효율:</strong></span> 코드를 한 줄 고칠 때마다 <strong><code>이미지 Build -&gt; Push -&gt; Pull -&gt; 배포</code></strong> 과정을 거쳐야 한다.</p>
</li>
<li><p><span style="color: indianred"><strong>리스크:</strong></span> 로컬에서는 잘 작동하던 코드가 실제 클러스터에 배포되었을 때 의존성 문제 등으로 터질 수 있다. &quot;내 컴퓨터에서는 되는데?&quot;라는 상황이 발생한다.</p>
<br>

</li>
</ul>
<h3 id="span-stylecolor-slateblue1-3-제1원칙에-입각한-해결책span"><span style="color: SlateBlue">1-3. 제1원칙에 입각한 해결책</span></h3>
<p><strong>이 문제의 핵심은 피드백 루프가 너무 길고, 운영 환경과의 괴리가 크다는 것이다.</strong></p>
<p>이를 해결하기 위한 이상적인 솔루션은 다음과 같다.</p>
<p><span style="color: indianred"><strong>&quot;이미지를 매번 빌드하지 않고, 로컬 머신의 변경 사항이 쿠버네티스 클러스터 내부의 애플리케이션과 즉시 동기화되어 Preview 환경에서 바로 확인할 수 있다면 어떨까?&quot;</strong></span></p>
<p>이것이 바로 Okteto가 해결하고자 하는 지점이다.</p>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine2-솔루션-okteto-the-solutionspan"><span style='background-color: MediumAquamarine'>2. 솔루션: Okteto (The Solution)</span></h2>
<hr>

<p>*<em>Okteto의 핵심 기능은 로컬 개발 환경과 클러스터 환경의 실시간 동기화다. *</em></p>
<p><span style="color: indianred"><strong>이를 통해 개발자는 매번 이미지를 빌드/배포하는 과정 없이, 로컬에서 코드를 저장하는 즉시 클러스터에 반영된 결과를 확인할 수 있다.</strong></span></p>
<p><img src="https://velog.velcdn.com/images/taekyu_/post/4866145d-7af1-428f-8a19-4780e439edd7/image.png" alt=""></p>
<ul>
<li><p><span style="color: indianred"><strong>Okteto CLI &amp; Cloud:</strong></span> CLI와 관리형 클라우드 서비스를 모두 제공한다.</p>
</li>
<li><p><span style="color: indianred"><strong>Preview 환경:</strong></span> PR 단계에서 변경 사항이 적용된 라이브 프리뷰를 자동으로 생성할 수 있다.</p>
</li>
<li><p><span style="color: indianred"><strong>동기화 메커니즘:</strong></span> 로컬 파일 시스템의 변경 사항을 컨테이너 내부로 즉시 전송한다.</p>
<br>

</li>
</ul>
<h3 id="span-stylecolor-slateblue-21-핵심-아키텍처-및-작동-원리span"><span style="color: SlateBlue"> 2.1. 핵심 아키텍처 및 작동 원리</span></h3>
<p>Okteto은 단순한 파일 복사가 아니다. </p>
<p><strong>기존에 배포된 애플리케이션의 컨테이너를 개발용 컨테이너로 교체하는 기술이 핵심이다.</strong></p>
<ul>
<li><p><span style="color: indianred"><strong>배포 교체:</strong></span></p>
<ul>
<li><p><code>okteto up</code>을 실행하면, Okteto는 기존의 운영용 Pod를 내리고 개발 도구(컴파일러, 디버거 등)가 포함된 Dev Container로 교체하여 재실행한다. </p>
</li>
<li><p>개발이 끝나면 원래 설정으로 자동 복구된다.</p>
</li>
</ul>
</li>
<li><p><span style="color: indianred"><strong>양방향 파일 동기화:</strong></span></p>
<ul>
<li><p>로컬 PC의 소스 코드 디렉토리와 클러스터 내 컨테이너를 네트워크로 연결한다. </p>
</li>
<li><p><span style="color: indianred"><strong>로컬에서 파일을 저장하는 순간, 변경된 파일만 즉시 컨테이너로 전송</strong></span>되며, 컨테이너 내부의 Hot Reload 도구가 이를 감지해 프로세스를 재시작한다. (소요 시간: 0.5초~2초)</p>
</li>
</ul>
</li>
<li><p><span style="color: indianred"><strong>자동 포트 포워딩:</strong></span></p>
<ul>
<li>별도의 Ingress 설정이나 kubectl port-forward 없이도, 로컬호스트(localhost:8080)로 요청을 보내면 터널링을 통해 클러스터 내부의 컨테이너로 안전하게 전달된다.<br>

</li>
</ul>
</li>
</ul>
<h3 id="span-stylecolor-slateblue-2-2-주요-기능-상세span"><span style="color: SlateBlue"> 2-2. 주요 기능 상세</span></h3>
<ul>
<li><p><span style="color: indianred"><strong>Okteto Manifest (okteto.yml)</strong></span></p>
<ul>
<li><p>Okteto가 어떻게 동작해야 하는지 정의하는 설정 파일이다. </p>
</li>
<li><p>Dockerfile만 있어도 자동 추론하지만, 정교한 제어를 위해 작성한다.</p>
</li>
<li><pre><code class="language-yaml"># okteto.yml 예시
name: my-api
image: okteto/node:18  # 개발에 사용할 도구가 포함된 이미지
command: [&quot;bash&quot;]      # 컨테이너 시작 시 실행할 셸
sync:
  - .:/usr/src/app     # 로컬의 현재 폴더(.)를 컨테이너의 /usr/src/app과 동기화
forward:
  - 8080:8080          # 포트 포워딩 설정
  - 9229:9229          # 디버거 포트 연결 (원격 디버깅 가능)</code></pre>
</li>
<li><p>이 설정을 통해 어떤 이미지로 교체할지, 어떤 폴더를 동기화할지를 지정한다.</p>
<br>
</li>
</ul>
</li>
<li><p><span style="color: indianred"><strong>Preview 환경</strong></span></p>
<ul>
<li><p>GitHub/GitLab과 연동하여 PR가 생성될 때마다 격리된 네임스페이스에 해당 코드를 배포한다.</p>
</li>
<li><p><strong>Before:</strong> &quot;내 로컬에서 돌려봤는데 잘 돼. 머지해줘.&quot; (검증 불가)</p>
</li>
<li><p><strong>After:</strong> &quot;PR 34번 프리뷰 링크 생성됐어. 거기서 직접 눌러봐.&quot; (확실한 검증)</p>
</li>
<li><p>DB, Redis 등 연관된 모든 서비스를 포함한 전체 스택을 띄워주므로 통합 테스트에 유리하다.</p>
</li>
<li><p>PR이 닫히거나 머지되면 해당 환경(네임스페이스)은 자동으로 삭제되어 리소스 낭비를 막는다.</p>
<br>
</li>
</ul>
</li>
<li><p><span style="color: indianred"><strong>원격 디버거 연결</strong></span></p>
<ul>
<li><p>파일 동기화뿐만 아니라 디버깅 포트도 연결된다. </p>
</li>
<li><p>로컬 IDE에서 중단점을 찍으면, 쿠버네티스 클러스터에서 실행 중인 코드가 멈추고 변수 상태를 검사할 수 있다.</p>
</li>
</ul>
</li>
</ul>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine3-데모-시연-및-동작-원리span"><span style='background-color: MediumAquamarine'>3. 데모 시연 및 동작 원리</span></h2>
<hr>

<p><strong>발표에서는 &#39;Movies with Compose&#39;라는 React, Node.js, MongoDB, Nginx로 구성된 마이크로서비스 애플리케이션을 통해 Okteto의 장점을 어필했다.</strong></p>
<h3 id="span-stylecolor-slateblue3-1-초기-설정-및-배포-okteto-upspan"><span style="color: SlateBlue">3-1. 초기 설정 및 배포 (okteto up)</span></h3>
<ul>
<li><p><span style="color: indianred"><strong>애플리케이션 준비:</strong></span> docker-compose.yml 또는 Dockerfile이 있는 프로젝트를 준비한다. 쿠버네티스 매니페스트가 없어도 된다.</p>
</li>
<li><p><span style="color: indianred"><strong>명령어 실행:</strong></span> <code>okteto context</code>로 클러스터를 지정하고 <code>okteto up</code>을 실행한다.</p>
</li>
<li><p><span style="color: indianred"><strong>자동화된 프로세스:</strong></span></p>
<ul>
<li><p>Okteto는 소스 코드를 분석하여 필요한 이미지를 빌드하고 Okteto Cloud 혹은 지정된 클러스터에 배포한다.</p>
</li>
<li><p>각 서비스(API, Frontend, DB 등)와 외부 접속을 위한 엔드포인트(URL)를 자동으로 생성한다.</p>
<br>

</li>
</ul>
</li>
</ul>
<h3 id="span-stylecolor-slateblue3-2-개발-컨테이너-활성화span"><span style="color: SlateBlue">3-2. 개발 컨테이너 활성화</span></h3>
<p>배포가 완료되면 Okteto는 사용자에게 <strong>&quot;어떤 서비스를 개발 모드로 전환할 것인가?&quot;</strong>를 묻는다.</p>
<ul>
<li><p>사용자가 특정 서비스(예: API)를 선택하면, Okteto는 해당 서비스의 기존 운영 컨테이너를 개발 컨테이너로 교체한다.</p>
</li>
<li><p>해당 개발 컨테이너는 로컬 머신의 소스 코드 디렉토리와 볼륨 동기화가 설정되어 있다.</p>
</li>
<li><p>터미널은 해당 컨테이너의 Shell로 바로 연결된다.</p>
<br>


</li>
</ul>
<h3 id="span-stylecolor-slateblue3-3-실시간-코드-수정-및-반영span"><span style="color: SlateBlue">3-3. 실시간 코드 수정 및 반영</span></h3>
<p>시연에서는 영화 목록이 중복해서 출력되는 버그를 수정하는 과정을 보여주었다.</p>
<ol>
<li><p><span style="color: indianred"><strong>버그 확인:</strong></span> 생성된 프리뷰 URL로 접속하여 버그를 확인한다.</p>
</li>
<li><p><span style="color: indianred"><strong>코드 수정:</strong></span> 로컬 IDE에서 server.js 파일을 열어 db.collection(&#39;movies&#39;)를 올바른 컬렉션으로 수정하고 저장한다.</p>
</li>
<li><p><span style="color: indianred"><strong>즉시 반영:</strong></span> 별도의 docker build나 kubectl apply 명령어가 필요 없다. 저장하는 순간 Okteto가 변경된 파일을 컨테이너로 동기화한다.</p>
</li>
<li><p><span style="color: indianred"><strong>결과 확인:</strong></span> 브라우저를 새로고침하면 수정된 코드가 즉시 반영되어 버그가 해결된 것을 볼 수 있다.</p>
<br>


</li>
</ol>
<h3 id="span-stylecolor-slateblue3-4-dockerfile만으로-충분하다span"><span style="color: SlateBlue">3-4. Dockerfile만으로 충분하다</span></h3>
<p>가장 강력한 장점 중 하나는 복잡한 쿠버네티스 지식이 없어도 된다는 점이다.</p>
<p><img src="https://velog.velcdn.com/images/taekyu_/post/efe54292-91ac-49b0-af1a-95e8440ddfa5/image.png" alt=""></p>
<p><strong>Dockerfile이나 docker-compose.yml만 있다면, Okteto가 이를 기반으로 쿠버네티스 배포를 자동으로 처리하고 프리뷰 환경을 구축해준다.</strong></p>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine4-결론span"><span style='background-color: MediumAquamarine'>4. 결론</span></h2>
<hr>

<p>*<em>과거에는 배포 자체가 목적이었다면, 지금은 얼마나 빠르고 효율적으로 개발하고 검증할 수 있는가가 핵심이다. *</em></p>
<p>따라서 프레젠테이션은 Okteto가 다음과 같은 가치를 제공한다고 주장한다. </p>
<ul>
<li><p><span style="color: indianred"><strong>Inner Loop 단축:</strong></span> 코드 수정부터 확인까지의 시간을 획기적으로 줄여준다.</p>
</li>
<li><p><span style="color: indianred"><strong>Prod-like Dev:</strong></span>  로컬(Docker Desktop 등)이 아닌 실제 쿠버네티스 클러스터 위에서 개발하므로, 배포 시 발생할 수 있는 환경 차이 문제를 사전에 방지한다.</p>
</li>
<li><p><span style="color: indianred"><strong>오픈 소스:</strong></span>  Okteto CLI는 오픈 소스이며 활발한 커뮤니티 지원을 받고 있다.</p>
</li>
</ul>
<p><span style="color: indianred"></span>
<span style="color: SlateBlue"></span></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[90DaysOfDevOps (Day 80)]]></title>
            <link>https://velog.io/@taekyu_/90DaysOfDevOps-Day-80-eo2jwsv9</link>
            <guid>https://velog.io/@taekyu_/90DaysOfDevOps-Day-80-eo2jwsv9</guid>
            <pubDate>Sat, 24 Jan 2026 11:31:41 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>해당 스터디는 90DaysOfDevOps
<a href="https://github.com/MichaelCade/90DaysOfDevOps">https://github.com/MichaelCade/90DaysOfDevOps</a>
를 기반으로 진행한 내용입니다.</strong></p>
</blockquote>
<p>** Day 80 - Unlocking K8s Troubleshooting Best Practices with Botkube **</p>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine1-쿠버네티스-트러블슈팅span"><span style='background-color: MediumAquamarine'>1. 쿠버네티스 트러블슈팅</span></h2>
<hr>

<p>*<em>쿠버네티스 생태계는 거대하고 복잡하다. *</em></p>
<p>네트워킹, 컨테이너, 배포 전략 등 알아야 할 지식의 양이 방대하여 진입 장벽이 높을 뿐만 아니라, 수많은 CNCF 도구들과 함께 사용되기 때문에 이들을 통합 관리하는 것은 엔지니어에게 큰 부담이다.</p>
<p><span style="color: indianred"><strong>특히 팀이 지리적으로 분산된 하이브리드 근무 환경에서 장애가 발생했을 때, Slack이나 Teams 같은 메신저로 파편화된 정보를 공유하며 문제를 해결하는 것은 Context Switching 비용을 발생시키고 대응 속도를 늦춘다.</strong></span></p>
<hr>
<p><strong>쿠버네티스 트러블슈팅은 공식적으로 클러스터 내의 문제를 식별하고 해결하는 과정이다.</strong></p>
<p>하지만 이를 좀 더 실천적인 관점에서 정의하자면 <span style="color: indianred"><strong>&quot;모니터링 + 행동 또는 반응의 결합&quot;</strong></span>이라 할 수 있다.</p>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine2-기존-트러블슈팅의-문제점span"><span style='background-color: MediumAquamarine'>2. 기존 트러블슈팅의 문제점</span></h2>
<hr>

<p>가장 흔한 에러 중 하나인 OOMKilled(Out Of Memory) 상황을 가정해보자. 전통적인 해결 방식은 다음 5단계를 거친다.</p>
<ol>
<li><p><span style="color: SlateBlue"><strong>종료된 Pod 식별</strong></span></p>
</li>
<li><p><span style="color: SlateBlue"><strong>메모리 사용량 확인</strong></span></p>
</li>
<li><p><span style="color: SlateBlue"><strong>로그 내 에러 확인</strong></span></p>
</li>
<li><p><span style="color: SlateBlue"><strong>이미지 업데이트</strong></span></p>
</li>
<li><p><span style="color: SlateBlue"><strong>메모리 Limit 증설</strong></span></p>
</li>
</ol>
<p>이 5단계는 간단해 보이지만, 20개 이상의 클러스터를 운영하거나 업무 시간 외에 발생할 경우, 해결까지 몇 분에서 며칠이 소요될 수 있다.
<br></p>
<p>또한, 일반적인 조직에서는 <span style="color: indianred"><strong><code>[개발자 장애 발견 → 티켓 생성 → DevOps 팀 알림 → DevOps 환경 진단 → SRE 팀 호출 → 트러블슈팅 → 상태 업데이트 → 팀 전파]</code></strong></span>라는 복잡한 과정을 거친다. 이 과정에서 너무 많은 사람과 단계가 개입된다.</p>
<ul>
<li><p><span style="color: indianred"><strong>DevOps 팀:</strong></span> 인력 부족과 업무 과다에 시달리며, 개발과 운영 사이에서 끊임없는 문맥 전환으로 인한 피로감을 느낀다.</p>
</li>
<li><p><span style="color: indianred"><strong>개발자:</strong></span> 앱의 상태만 확인하고 싶은데, 쿠버네티스 심층 지식까지 강요받는 부담을 느낀다.</p>
</li>
<li><p><span style="color: indianred"><strong>운영 팀:</strong></span> 보안 리스크와 비용 효율성 때문에 시스템 신뢰성 유지가 최우선 과제라 압박감이 크다.</p>
</li>
</ul>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine3-쿠버네티스-트러블슈팅-모범-사례-5가지span"><span style='background-color: MediumAquamarine'>3. 쿠버네티스 트러블슈팅 모범 사례 5가지</span></h2>
<hr>

<p>이러한 문제를 해결하기 위한 5가지 핵심 전략은 다음과 같다.</p>
<ul>
<li><p><span style="color: indianred"><strong>모니터링과 관측성(Observability) 중앙화:</strong></span> 흩어진 정보를 한곳으로 모은다.</p>
</li>
<li><p><span style="color: indianred"><strong>인시던트 대응과 협업 강화:</strong></span> 팀 간 소통을 효율화한다.</p>
</li>
<li><p><span style="color: indianred"><strong>피드백 루프 구축:</strong></span> 과거 데이터를 통해 학습하고 개선한다.</p>
</li>
<li><p><span style="color: indianred"><strong>Command Execution 간소화:</strong></span> 복잡한 터미널 작업을 줄인다.</p>
</li>
<li><p><span style="color: indianred"><strong>관측성 및 전달 프로세스 자동화:</strong></span> 수동 작업을 최소화한다.</p>
</li>
</ul>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine4-솔루션-botkube를-활용한-chatops-구현span"><span style='background-color: MediumAquamarine'>4. 솔루션: Botkube를 활용한 ChatOps 구현</span></h2>
<hr>

<p><strong>Botkube는 협업 중심의 쿠버네티스 트러블슈팅 도구로, 메신저(Slack, Teams, Discord, Mattermost)와 쿠버네티스 클러스터 사이를 연결하는 양방향 에이전트다.</strong> </p>
<p><span style="color: indianred"><strong>단순 알림 수신을 넘어, 메신저에서 직접 클러스터를 제어하는 것이 특징이다.</strong></span>
<img src="https://velog.velcdn.com/images/taekyu_/post/6c0e6031-e85f-4fb6-a181-d1607187b519/image.png" width="70%" height="n%"></p>
<h3 id="span-stylecolor-slateblue4-1-관측성-강화-observabilityspan"><span style="color: SlateBlue">4-1. 관측성 강화 (Observability)</span></h3>
<p><strong>모니터링은 단순한 데이터 수신이 아니라 문맥(Context)을 파악하는 것이다.</strong></p>
<ul>
<li><p><span style="color: indianred"><strong>Watchdog 역할:</strong></span> 클러스터 변경 사항을 실시간으로 감지하여 알린다.</p>
</li>
<li><p><span style="color: indianred"><strong>채널별 격리:</strong></span> 개발팀은 개발 관련 알림만, 플랫폼 팀은 인프라 알림만 받도록 채널을 분리하여 피로도를 줄인다.</p>
</li>
<li><p><span style="color: indianred"><strong>직접 해결:</strong></span> 알림을 받고 터미널을 켜는 것이 아니라, 슬랙에서 버튼을 누르거나 명령어를 입력해 즉시 문제를 해결한다. 이는 문맥 전환 비용을 획기적으로 줄여준다.</p>
<img src="https://velog.velcdn.com/images/taekyu_/post/a42a5817-1e5d-4652-acd1-7205952ccb93/image.png" width="70%" height="n%">

</li>
</ul>
<br>


<h3 id="span-stylecolor-slateblue4-2-협업과-피드백-루프-collaboration--feedback-loopspan"><span style="color: SlateBlue">4-2. 협업과 피드백 루프 (Collaboration &amp; Feedback Loop)</span></h3>
<ul>
<li><p><span style="color: indianred"><strong>정보 공유:</strong></span> 공유된 채널에서 트러블슈팅이 이루어지므로, 별도의 보고 절차 없이 팀 전체가 상황을 파악할 수 있다.</p>
</li>
<li><p><span style="color: indianred"><strong>Audit Logs:</strong></span> 누가 어떤 명령어를 실행했는지 기록된다. 예를 들어 주니어 엔지니어가 반복적인 실수를 한다면, 로그를 통해 근본 원인을 파악하고 교육하여 팀 역량을 높일 수 있다.</p>
</li>
<li><p><span style="color: indianred"><strong>반응형 → 주도형:</strong></span> 과거 인시던트 이력을 바탕으로 장애의 전조 증상을 파악하고 사전 대응이 가능해진다.</p>
</li>
</ul>
<h3 id="span-stylecolor-slateblue4-3-개발자-셀프-서비스와-명령어-실행-간소화span"><span style="color: SlateBlue">4-3. 개발자 셀프 서비스와 명령어 실행 간소화</span></h3>
<ul>
<li><p><span style="color: indianred"><strong>비전문가를 위한 접근성:</strong></span> 쿠버네티스 전문가가 아니더라도, Botkube가 제공하는 드래그 앤 드롭 UI나 간소화된 명령어를 통해 필요한 조치를 취할 수 있다.</p>
</li>
<li><p><span style="color: indianred"><strong>명령어 실행 속도 향상:</strong></span> 복잡한 kubectl 명령어를 일일이 기억할 필요 없이, 채팅창에서 빠르게 실행 가능하다.</p>
</li>
</ul>
<h3 id="span-stylecolor-slateblue4-4-자동화-automationspan"><span style="color: SlateBlue">4-4. 자동화 (Automation)</span></h3>
<p><strong>가장 강력한 기능은 트러블슈팅 단계의 자동화다. 앞서 언급한 OOMKilled 5단계를 Botkube는 2단계로 단축시킨다.</strong></p>
<ul>
<li><p><span style="color: indianred"><strong>기존 5단계:</strong></span> 식별 → 확인 → 로그 확인 → 업데이트 → 메모리 증설</p>
</li>
<li><p><span style="color: indianred"><strong>Botkube 자동화:</strong></span></p>
<ul>
<li><p>Botkube가 자동으로 파드를 식별하고, 로그와 메모리 사용량을 분석하여 에러 원인을 채팅창에 띄워준다 (1~3단계 자동 수행).</p>
</li>
<li><p><strong>엔지니어는 제공된 정보를 바탕으로 이미지 업데이트 및 메모리 증설(4~5단계)만 수행하면 된다.</strong></p>
</li>
</ul>
</li>
</ul>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine5-도입-효과-및-실제-사례span"><span style='background-color: MediumAquamarine'>5. 도입 효과 및 실제 사례</span></h2>
<hr>


<h3 id="span-stylecolor-slateblue5-1-정량적-기대-효과span"><span style="color: SlateBlue">5-1. 정량적 기대 효과</span></h3>
<ul>
<li><p><span style="color: indianred"><strong>MTTR(평균 복구 시간) 단축:</strong></span> 커뮤니케이션 지연을 제거하여 빠른 복구 가능.</p>
</li>
<li><p><span style="color: indianred"><strong>운영 효율성 증대:</strong></span> 단일 플랫폼에서 정보 확인 및 조치 가능.</p>
</li>
<li><p><span style="color: indianred"><strong>팀 번아웃 감소:</strong></span> 불필요한 문맥 전환과 전문가에 대한 과도한 의존 제거.</p>
</li>
<li><p><span style="color: indianred"><strong>장애 감소:</strong></span> 사전 대응을 통해 작은 이슈가 대형 장애로 번지는 것을 방지.</p>
<br>

</li>
</ul>
<h3 id="span-stylecolor-slateblue5-2-고객-사례-해운-회사span"><span style="color: SlateBlue">5-2. 고객 사례 (해운 회사)</span></h3>
<p>운영(Prod)과 스테이징(Staging) 클러스터를 운영하는 한 고객사는 Botkube의 RBAC(역할 기반 접근 제어) 기능을 활용했다. </p>
<p><strong>슬랙 채널별로 권한을 매핑하여, 특정 채널에서는 조회만 가능하고 비공개 채널에서는 명령 실행이 가능하도록 설정함으로써 보안과 신뢰성을 동시에 확보했다.</strong></p>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine6-정리span"><span style='background-color: MediumAquamarine'>6. 정리</span></h2>
<hr>

<p><strong>쿠버네티스 트러블슈팅에서 가장 중요한 것은 반응적(Reactive) 태도를 버리고 전략적(Strategic)으로 접근하는 것이다.</strong></p>
<p><span style="color: indianred"><strong>팀의 리소스와 히스토리를 바탕으로 트러블슈팅 과정을 중앙화하고 자동화해야 한다.</strong></span></p>
<p>Botkube와 같은 도구는 복잡한 하이브리드 환경에서 팀의 협업 능력을 극대화하고, 더욱 회복탄력성있는 조직을 만드는 데 핵심적인 역할을 한다.</p>
<p><span style="color: indianred"></span>
<span style="color: SlateBlue"></span></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[90DaysOfDevOps (Day 79)]]></title>
            <link>https://velog.io/@taekyu_/90DaysOfDevOps-Day-79-gvg8o01o</link>
            <guid>https://velog.io/@taekyu_/90DaysOfDevOps-Day-79-gvg8o01o</guid>
            <pubDate>Sat, 24 Jan 2026 09:18:40 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>해당 스터디는 90DaysOfDevOps
<a href="https://github.com/MichaelCade/90DaysOfDevOps">https://github.com/MichaelCade/90DaysOfDevOps</a>
를 기반으로 진행한 내용입니다.</strong></p>
</blockquote>
<p>** Day 79 - DevEdOps **</p>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine1-devedops란span"><span style='background-color: MediumAquamarine'>1. DevEdOps란?</span></h2>
<hr>

<p><strong>DevEdOps는 Development(개발), Education(교육), Operations(운영)의 합성어로, 데브옵스의 세 가지 방법을 교육 및 학습 과정에 적용하여, 자동화되고 피드백이 빠른 학습 환경을 구축하는 것으로 정의할 수 있다.</strong></p>
<p><span style="color: indianred"><strong>기존의 정적인 학습에서 벗어나, 학습자가 실제 환경과 유사한 샌드박스에서 반복적인 의도적 연습을 수행하고, 시스템으로부터 즉각적인 피드백을 받으며 기술을 체화하는 방법론이다.</strong></span></p>
<hr>
<p><strong>기술을 배운다는 것은 무엇인가?</strong></p>
<p>Adam Leskis는 인지 심리학을 바탕으로 학습의 본질이 <span style="color: indianred"><strong>반복적인 의도적 연습</strong></span>에 있다고 설명한다.</p>
<p>숙련된 체스 선수가 말의 위치를 개별적으로 외우는 것이 아니라 특정 패턴을 하나의 덩어리(Chunk)로 인식하여 장기 기억에 저장하듯, <strong>개발자 또한 복잡한 시스템을 다루기 위해서는 지식의 청킹(Chunking) 과정이 필수적이다.</strong></p>
<p>DevEdOps는 학습자가 안전한 환경에서 반복 연습을 통해 이러한 패턴을 뇌에 각인시킬 수 있도록 돕는 환경을 제공한다.</p>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine2-devops의-세가지-방법을-통한-학습-설계span"><span style='background-color: MediumAquamarine'>2. DevOps의 세가지 방법을 통한 학습 설계</span></h2>
<hr>

<p><strong>DevEdOps는 데브옵스의 The Three Ways를 학습 프로세스에 그대로 이식한다.</strong></p>
<ul>
<li><p><span style="color: indianred"><strong>첫 번째 방법 (흐름):</strong></span> 학습 목표를 아주 작은 단위(Batch Size)로 쪼개고, 학습자가 실습 환경에 접속하자마자 즉시 연습을 시작할 수 있도록 Lead Time을 0으로 단축한다.</p>
</li>
<li><p><span style="color: indianred"><strong>두 번째 방법 (피드백):</strong></span> 학습자의 행동(코드 작성, 설정 변경 등)에 대해 시스템이 자동화된 검증을 수행하여, 맞았는지 틀렸는지 즉각적인 피드백을 제공한다.</p>
</li>
<li><p><span style="color: indianred"><strong>세 번째 방법 (지속적 학습):</strong></span> 최고의 학습은 가르치는 것이다. 학습자가 직접 가르치기 위한 도구를 개발하고 공유함으로써 로컬의 지식을 전역으로 확장한다.</p>
</li>
</ul>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine3-devedops의-미래-자동-생성되는-샌드박스-환경span"><span style='background-color: MediumAquamarine'>3. DevEdOps의 미래: 자동 생성되는 샌드박스 환경</span></h2>
<hr>

<p><strong>실전에서의 DevEdOps는 &#39;MongoDB 복제 세트 업그레이드&#39;와 같이 운영 환경에서 수행하기 위험한 작업을, 로컬의 격리된 Sandbox 환경에서 안전하게 연습하는 형태로 구현된다.</strong></p>
<p><span style="color: indianred"><strong>실패해도 리스크가 없기 때문에 엔지니어는 두려움 없이 실험할 수 있다.</strong></span></p>
<p>더 나아가 DevEdOps의 미래는 자동 생성 학습 환경에 있다. </p>
<blockquote>
<p><strong>엔지니어가 배우고 싶은 기술 스택을 입력하면 LLM 에이전트가 맞춤형 실습 환경을 즉시 배포하고, 카오스 엔지니어링을 통해 무작위 장애 시나리오를 주입하여 복구 훈련을 돕는 형태로 진화할 것이다.</strong></p>
</blockquote>
<p><span style="color: indianred"></span>
<span style="color: SlateBlue"></span></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[90DaysOfDevOps (Day 78)]]></title>
            <link>https://velog.io/@taekyu_/90DaysOfDevOps-Day-78</link>
            <guid>https://velog.io/@taekyu_/90DaysOfDevOps-Day-78</guid>
            <pubDate>Sat, 24 Jan 2026 06:40:55 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>해당 스터디는 90DaysOfDevOps
<a href="https://github.com/MichaelCade/90DaysOfDevOps">https://github.com/MichaelCade/90DaysOfDevOps</a>
를 기반으로 진행한 내용입니다.</strong></p>
</blockquote>
<p>** Day 78 - Scaling Terraform Deployments with GitHub Actions: Essential Configurations **</p>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine1-안정적인-협업을-위한-저장소-설정-및-보안-전략span"><span style='background-color: MediumAquamarine'>1. 안정적인 협업을 위한 저장소 설정 및 보안 전략</span></h2>
<hr>

<p>*<em>성공적인 인프라 관리(IaC)의 시작은 코드를 담는 그릇인 Repository의 정책을 올바르게 수립하는 것에서부터 출발한다. *</em></p>
<p>이는 단순한 권장 사항이 아니라, 사고를 예방하고 협업의 품질을 높이는 강제적 제어 장치이다.</p>
<h3 id="span-stylecolor-slateblue1-1-브랜치-정책과-보호span"><span style="color: SlateBlue">1-1. 브랜치 정책과 보호</span></h3>
<p><strong>운영 환경의 안정성을 위해 main 브랜치에 대한 직접적인 커밋을 차단하는 브랜치 보호 규칙 설정은 필수적이다.</strong></p>
<ul>
<li><p><span style="color: indianred"><strong>사고 방지:</strong></span> 개발자가 실수로 운영 코드를 변경하거나 삭제하는 것을 시스템 레벨에서 차단한다.</p>
</li>
<li><p><span style="color: indianred"><strong>제어된 Workflows:</strong></span> 모든 변경 사항은 검증된 절차를 거쳐야만 병합될 수 있도록 강제하여, 인프라의 무결성을 유지한다.</p>
<br>

</li>
</ul>
<h3 id="span-stylecolor-slateblue1-2-풀-리퀘스트pull-request-중심의-개발-수명주기span"><span style="color: SlateBlue">1-2. 풀 리퀘스트(Pull Request) 중심의 개발 수명주기</span></h3>
<p><strong>코드 병합 전에 반드시 Pull Request를 생성하도록 요구하는 것은 DevOps 문화의 핵심이다.</strong></p>
<ul>
<li><p><span style="color: indianred"><strong>코드 리뷰의 제도화:</strong></span> PR은 팀원들이 변경 사항을 검토하고 피드백을 주고받는 공식적인 공간이 된다.</p>
</li>
<li><p><span style="color: indianred"><strong>자동화된 검증:</strong></span> </p>
<ul>
<li><p>PR이 생성되는 즉시 CI 파이프라인을 트리거하여, 코드가 병합되기 전에 문법 오류나 잠재적 문제를 사전에 탐지할 수 있다. </p>
</li>
<li><p>이는 배포 후 발생할 수 있는 회귀 오류를 최소화하는 가장 확실한 방법이다.</p>
<br>

</li>
</ul>
</li>
</ul>
<h3 id="span-stylecolor-slateblue1-3-환경별-secrets-및-변수-관리span"><span style="color: SlateBlue">1-3. 환경별 Secrets 및 변수 관리</span></h3>
<p><strong>보안과 유연성을 동시에 확보하기 위해 GitHub의 <code>Environment Secrets</code> 기능을 적극 활용해야 한다.</strong></p>
<ul>
<li><p><span style="color: indianred"><strong>민감 정보 격리:</strong></span> </p>
<ul>
<li><p>Client ID, Secret, Subscription ID와 같은 민감한 정보는 절대 평문으로 코드에 포함되어서는 안 된다. </p>
</li>
<li><p><strong>이를 GitHub Secrets에 암호화하여 저장하고 파이프라인 실행 시에만 주입받아 사용한다.</strong></p>
</li>
</ul>
</li>
<li><p><span style="color: indianred"><strong>환경별 분리 전략:</strong></span> </p>
<ul>
<li><p>개발과 운영 환경은 서로 다른 자격 증명(<code>Credential</code>)을 사용해야 한다. </p>
</li>
<li><p>GitHub Environments 기능을 통해 동일한 변수명을 사용하더라도, 배포되는 환경 컨텍스트에 따라 자동으로 다른 비밀 키 값을 매핑함으로써 보안 사고를 예방한다.</p>
</li>
</ul>
</li>
</ul>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine2-테라폼-저장소-구조화span"><span style='background-color: MediumAquamarine'>2. 테라폼 저장소 구조화</span></h2>
<hr>

<p><strong>테라폼 코드베이스의 유지보수성과 확장성은 폴더 구조를 어떻게 설계하느냐에 달려 있다.</strong></p>
<p>단일 파일 관리가 아닌, 기능과 역할에 따른 명확한 디렉터리 분리 전략이 필요하다.</p>
<h3 id="span-stylecolor-slateblue2-1-논리적-폴더-구조-설계span"><span style="color: SlateBlue">2-1. 논리적 폴더 구조 설계</span></h3>
<p>프레젠테이션에서는 다음과 같은 계층적 구조를 제안한다.</p>
<ul>
<li><p><span style="color: indianred"><strong><code>.github/workflows</code></strong></span>: CI/CD 파이프라인 정의 파일(.yml)과 재사용 가능한 Template workflows가 위치</p>
</li>
<li><p><span style="color: indianred"><strong><code>environments</code></strong></span>: 환경별 설정 값을 관리한다.</p>
<ul>
<li>development, production 폴더로 나누고 각 폴더 내에 terraform.tfvars 파일을 두어, 환경마다 달라지는 값(예: IP 대역, 리소스 이름 접두사 등)을 정의</li>
</ul>
</li>
<li><p><span style="color: indianred"><strong><code>modules</code></strong></span>: 재사용 가능한 테라폼 모듈을 관리한다. </p>
<ul>
<li>조직의 규모에 따라 별도 리포지토리로 분리할 수도 있지만, 관리의 편의성을 위해 단일 리포지토리 내에 구성할 수도 있다. (예: Private DNS Zone 모듈 등)</li>
</ul>
</li>
<li><p><span style="color: indianred"><strong><code>platform</code></strong></span>: 인프라를 구성하는 실제 리소스들을 기능 단위로 쪼개어 관리하는 디렉토리</p>
</li>
</ul>
<pre><code class="language-bash">.
├── .github
│   └── workflows
│       ├── deploy.yml          # 메인 워크플로 (트리거 정의)
│       └── templates           # 재사용 가능한 템플릿 (CI, Apply 로직 등)
├── environments
│   ├── dev
│   │   └── terraform.tfvars    # 개발 환경 전용 변수 (IP 대역, 이름 등)
│   └── prod
│       └── terraform.tfvars    # 운영 환경 전용 변수
├── modules                     # 재사용 가능한 테라폼 모듈 (예: DNS, VM)
└── platform                    # 실제 인프라 구성 요소 (기능 단위 분리)
    ├── core                    # 리소스 그룹 등 기초 자원
    ├── network                 # VNet, Subnet 등
    └── logging                 # 모니터링 및 로깅 자원</code></pre>
<br>


<h3 id="span-stylecolor-slateblue2-2-플랫폼-구성-요소-분리-전략span"><span style="color: SlateBlue">2-2. 플랫폼 구성 요소 분리 전략</span></h3>
<p><strong>거대한 하나의 main.tf 파일은 관리가 불가능하다. 인프라를 플랫폼 구성 요소 단위로 나누어야 한다.</strong></p>
<ul>
<li><p><span style="color: indianred"><strong>구성 요소 예시:</strong></span></p>
<ul>
<li><p><code>Core</code>: 리소스 그룹 등 가장 기초가 되는 자원</p>
</li>
<li><p><code>Network</code>: VNet, Subnet 등 네트워킹 자원</p>
</li>
<li><p><code>Logging</code>: Log Analytics Workspace 등 모니터링 자원</p>
</li>
</ul>
</li>
<li><p><span style="color: indianred"><strong>의존성 관리:</strong></span></p>
<ul>
<li><p>이러한 분리는 인프라 배포의 순서를 명확히 한다. </p>
</li>
<li><p>예를 들어, VM을 배포하기 전에 Network가 존재해야 하고, 네트워크 전에 리소스 그룹인 Core가 있어야 한다. 폴더를 분리함으로써 &#39;왼쪽에서 오른쪽으로&#39; 흐르는 배포 파이프라인을 설계할 수 있다.</p>
</li>
</ul>
</li>
</ul>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine3-github-actions-구성span"><span style='background-color: MediumAquamarine'>3. GitHub Actions 구성</span></h2>
<hr>

<p><strong>단순한 스크립트 실행을 넘어, GitHub Actions의 고급 기능을 활용하여 중복을 줄이고 배포 속도를 높이는 전략이다.</strong></p>
<h3 id="span-stylecolor-slateblue3-1-템플릿templating을-통한-표준화span"><span style="color: SlateBlue">3-1. 템플릿(Templating)을 통한 표준화</span></h3>
<p><strong>DRY(Don&#39;t Repeat Yourself) 원칙을 CI/CD 파이프라인에도 적용해야 한다.</strong></p>
<ul>
<li><p><span style="color: indianred"><strong>재사용 가능한 워크플로:</strong></span> <code>terraform init</code>, <code>plan</code>, <code>apply</code>와 같은 공통적인 작업 절차를 별도의 YAML 템플릿 파일로 정의한다.</p>
</li>
<li><p><span style="color: indianred"><strong>효과:</strong></span> </p>
<ul>
<li><p>메인 워크플로에서는 이 템플릿을 호출하고 필요한 Input만 전달하면 된다. </p>
</li>
<li><p>이를 통해 모든 프로젝트와 환경에서 일관된 배포 프로세스를 보장하고, 로직 수정 시 템플릿 파일 하나만 수정하면 되므로 유지보수가 용이해진다.</p>
</li>
</ul>
<pre><code class="language-yaml">#.github/workflows/templates/terraform-template.yml
name: &quot;Reusable Terraform Workflow&quot;

on:
  workflow_call: # 다른 워크플로에서 호출 가능하도록 설정
    inputs:
      environment: { required: true, type: string }
      path: { required: true, type: string }
    secrets:
      AZURE_CREDENTIALS: { required: true }

jobs:
  terraform:
    runs-on: ubuntu-latest
    steps:
      - uses: hashicorp/setup-terraform@v2

      # 1. 초기화 (공통 로직)
      - run: terraform init
        working-directory: ${{ inputs.path }}

      # 2. 적용 (환경 변수 동적 할당)
      - run: terraform apply -auto-approve -var-file=../../environments/${{ inputs.environment }}.tfvars
        working-directory: ${{ inputs.path }}</code></pre>
<br>

</li>
</ul>
<h3 id="span-stylecolor-slateblue3-2-매트릭스-전략을-이용한-병렬-배포span"><span style="color: SlateBlue">3-2. 매트릭스 전략을 이용한 병렬 배포</span></h3>
<p><strong>대규모 배포 시 Matrix Strategy를 도입한다.</strong></p>
<ul>
<li><p><span style="color: indianred"><strong>문제 상황:</strong></span> 개발/운영 2개의 환경에 Core, Network, Logging 3개의 구성 요소를 배포하려면 총 6번의 배포 스크립트를 작성해야 하는 비효율이 발생한다.</p>
</li>
<li><p><span style="color: indianred"><strong>해결책:</strong></span> GitHub Actions의 Matrix 기능을 사용하여 환경 목록(<code>[dev, prod]</code>)과 구성 요소 목록(<code>[core, network, logging]</code>)을 정의한다.</p>
</li>
<li><p><span style="color: indianred"><strong>동작 방식:</strong></span> </p>
<ul>
<li><p>파이프라인이 실행되면 정의된 조합(2 x 3 = 6개)에 따라 6개의 Job이 자동으로 생성되어 병렬로 실행된다. </p>
</li>
<li><p>코드는 단 한 번만 작성하지만, 실행은 N번 이루어지므로 작성량이 획기적으로 줄어들고 배포 속도는 빨라진다.</p>
</li>
</ul>
</li>
<li><p><span style="color: indianred"><strong>구현 코드 예시:</strong></span></p>
<ul>
<li>아래와 같이 strategy 블록 내에 matrix를 정의하면, 단 한 번의 코드 작성으로 다중 환경 및 다중 컴포넌트 배포를 처리할 수 있다.<pre><code class="language-yaml">jobs:
terraform-deploy:
  strategy:
    matrix:
      environment: [dev, prod]           # 배포할 환경 목록
      component: [core, network, logging] # 배포할 플랫폼 구성 요소 목록
  steps:
    - name: Deploy ${{ matrix.component }} to ${{ matrix.environment }}
      # working-directory를 동적으로 변경하여 해당 컴포넌트 폴더에서 실행
      working-directory: platform/${{ matrix.component }}
      # 해당 환경에 맞는 tfvars 파일을 동적으로 주입
      run: |
        terraform init
        terraform apply -var-file=&quot;../../environments/${{ matrix.environment }}/terraform.tfvars&quot;</code></pre>
</li>
</ul>
</li>
</ul>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine4-실전-데모-워크플로-분석-및-운영-최적화-span"><span style='background-color: MediumAquamarine'>4. 실전 데모 워크플로 분석 및 운영 최적화 </span></h2>
<hr>

<p><strong>실제 데모 시연을 통해 확인된 파이프라인의 구체적인 동작 방식과, 지속적인 개선을 위한 운영 도구 활용법이다.</strong></p>
<h3 id="span-stylecolor-slateblue4-1-ci-파이프라인-pr-단계span"><span style="color: SlateBlue">4-1. CI 파이프라인 (PR 단계)</span></h3>
<p><strong>PR이 생성되면 Terraform CI 작업이 실행된다.</strong></p>
<ul>
<li><p><span style="color: indianred"><strong>자동 포맷팅:</strong></span> </p>
<ul>
<li><p>개발자가 <code>terraform fmt</code>를 수행하지 않고 코드를 올렸을 경우(들여쓰기 오류 등), CI 파이프라인이 이를 감지하여 자동으로 포맷팅을 수정하고 해당 브랜치에 커밋한다.</p>
</li>
<li><p>사소한 스타일 문제로 리뷰 시간을 낭비하는 것을 방지한다.</p>
</li>
</ul>
</li>
<li><p><span style="color: indianred"><strong>피드백 루프 자동화:</strong></span> </p>
<ul>
<li><p><code>terraform plan</code>의 실행 결과가 GitHub Actions 로그에만 남는 것이 아니라, PR의 코멘트로 자동 등록된다. </p>
</li>
<li><p>리뷰어는 파이프라인 로그를 찾아 헤맬 필요 없이, PR 페이지에서 즉시 변경 예정 사항인Plan을 확인하고 승인 여부를 결정할 수 있다.</p>
</li>
</ul>
<pre><code class="language-yaml">name: &quot;Terraform CI&quot;

on:
  pull_request:
    branches: [ main ]

permissions:
  contents: write      # 포맷팅 수정 후 커밋을 위해 쓰기 권한 필요
  pull-requests: write # PR 코멘트 작성을 위해 쓰기 권한 필요

jobs:
  terraform-ci:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Code
        uses: actions/checkout@v3
        with:
          ref: ${{ github.head_ref }}

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v2

      # 1. 자동 포맷팅 및 커밋 (Auto-Formatting)
      - name: Terraform Format
        id: fmt
        run: terraform fmt -recursive

      - name: Commit Formatting Changes
        if: steps.fmt.outputs.exitcode != 0 # 변경사항이 있을 경우에만 실행
        uses: stefanzweifel/git-auto-commit-action@v4
        with:
          commit_message: &quot;style: Apply terraform fmt changes&quot;

      # 2. Terraform Init &amp; Validate
      - name: Terraform Init
        run: terraform init

      - name: Terraform Validate
        run: terraform validate

      # 3. Plan 실행 및 결과 저장
      - name: Terraform Plan
        id: plan
        run: terraform plan -no-color
        continue-on-error: true # Plan 실패 시에도 코멘트는 남기도록 설정

      # 4. PR에 코멘트로 Plan 결과 등록 (Feedback Loop)
      - name: Update PR with Plan
        uses: actions/github-script@v6
        if: github.event_name == &#39;pull_request&#39;
        env:
          PLAN: &quot;terraform\n${{ steps.plan.outputs.stdout }}&quot;
        with:
          script: |
            const output = `#### Terraform Format and Style 🖌\`${{ steps.fmt.outcome }}\`
            #### Terraform Initialization ⚙️\`${{ steps.init.outcome }}\`
            #### Terraform Plan 📖\`${{ steps.plan.outcome }}\`

            &lt;details&gt;&lt;summary&gt;Show Plan&lt;/summary&gt;

            \`\`\`\n
           ${process.env.PLAN}
            \`\`\`

            &lt;/details&gt;`;

            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: output
            })</code></pre>
<br>

</li>
</ul>
<h3 id="span-stylecolor-slateblue4-2-cd-파이프라인-merge-및-배포-단계span"><span style="color: SlateBlue">4-2. CD 파이프라인 (Merge 및 배포 단계)</span></h3>
<p><strong>PR이 승인되어 main 브랜치에 병합되면 실제 배포 파이프라인이 트리거 된다.</strong></p>
<ul>
<li><p><span style="color: indianred"><strong>조건부 실행:</strong></span> if 문을 사용하여 main 브랜치일 경우에만 <code>terraform apply</code>가 실행되도록 제어한다.</p>
</li>
<li><p><span style="color: indianred"><strong>매트릭스 기반 배포:</strong></span> 앞서 설계한 매트릭스 전략에 따라 개발 및 운영 환경의 각 구성 요소들이 의존성에 맞춰 배포된다. 데모에서는 Azure Tag가 업데이트되는 과정을 통해 실시간 반영을 확인했다.</p>
<pre><code class="language-yaml">name: &quot;Terraform CD (Deploy)&quot;

on:
  push:
    branches: [ main ]

jobs:
  deploy:
    name: Deploy ${{ matrix.component }} to ${{ matrix.environment }}
    runs-on: ubuntu-latest

    # Matrix 전략 정의: 환경(2개) * 컴포넌트(3개) = 총 6개의 병렬 Job 실행
    strategy:
      matrix:
        environment: [dev, prod]
        component: [core, network, logging]
      fail-fast: false # 하나의 Job이 실패해도 나머지는 계속 진행

    # 환경별 Secret 분리를 위해 GitHub Environment 기능 사용
    environment: ${{ matrix.environment }}

    steps:
      - name: Checkout Code
        uses: actions/checkout@v3

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v2

      # Azure 로그인 (OIDC 사용 시)
      - name: Azure Login
        uses: azure/login@v1
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

      # Matrix 변수를 활용한 동적 경로 설정 및 배포
      - name: Terraform Init &amp; Apply
        # 실제 테라폼 코드가 있는 폴더로 이동 (예: platform/core)
        working-directory: platform/${{ matrix.component }}
        run: |
          terraform init

          # 환경별 tfvars 파일을 참조하여 Apply 실행
          # (예: environments/dev/terraform.tfvars)
          terraform apply -auto-approve \
            -var-file=&quot;../../environments/${{ matrix.environment }}/terraform.tfvars&quot;</code></pre>
</li>
</ul>
<br>

<h3 id="span-stylecolor-slateblue4-3-지속적인-관리-도구-renovate--dependabotspan"><span style="color: SlateBlue">4-3. 지속적인 관리 도구 (Renovate &amp; Dependabot)</span></h3>
<p><strong>인프라 코드는 한 번 작성하고 끝나는 것이 아니다. 지속적인 최신 상태 유지가 필요하다.</strong></p>
<ul>
<li><p><span style="color: indianred"><strong>Renovate/Dependabot 도입:</strong></span> Terraform 모듈 버전, Provider 버전, GitHub Actions 템플릿 버전 등을 모니터링하는 도구를 설정한다.</p>
</li>
<li><p><span style="color: indianred"><strong>종속성 대시보드:</strong></span> 업데이트가 필요한 항목을 대시보드 형태로 시각화해 주며, 새 버전이 나올 경우 자동으로 PR을 생성해 준다. 이를 통해 엔지니어는 수동으로 버전을 확인하는 수고를 덜고, 보안 취약점이 패치된 최신 버전을 손쉽게 유지할 수 있다.</p>
</li>
</ul>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine5-정리-span"><span style='background-color: MediumAquamarine'>5. 정리 </span></h2>
<hr>

<p><span style="color: indianred"><strong>대규모 테라폼 운영을 위해서는 환경과 플랫폼 구성 요소를 분리한 모듈식 저장소 구조와 Matrix Strategy를 결합하여 코드 중복 없이 다중 환경 병렬 배포를 구현해야 한다.</strong></span></p>
<p>이 과정에서 브랜치 보호 규칙과 PR 기반의 자동화된 CI 검증을 통해 인프라의 무결성을 확보하고, GitHub Environment Secrets를 활용하여 보안성을 강화한다. </p>
<p>최종적으로 Renovate와 같은 종속성 관리 도구를 통합함으로써, 확장 가능하고 유지보수가 용이한 IaC 아키텍처를 완성할 수 있다.</p>
<p><span style="color: indianred"></span>
<span style="color: SlateBlue"></span></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[90DaysOfDevOps (Day 77)]]></title>
            <link>https://velog.io/@taekyu_/90DaysOfDevOps-Day-77-gy84bjg5</link>
            <guid>https://velog.io/@taekyu_/90DaysOfDevOps-Day-77-gy84bjg5</guid>
            <pubDate>Sat, 24 Jan 2026 03:38:05 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>해당 스터디는 90DaysOfDevOps
<a href="https://github.com/MichaelCade/90DaysOfDevOps">https://github.com/MichaelCade/90DaysOfDevOps</a>
를 기반으로 진행한 내용입니다.</strong></p>
</blockquote>
<p>** Day 77 - Connect to Microsoft APIs in Azure DevOps Pipelines using Workload Identity Federation **</p>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine1-workload-identity-federation이란span"><span style='background-color: MediumAquamarine'>1. Workload Identity Federation이란?</span></h2>
<hr>

<p>DevOps 환경을 구축하다 보면 GitHub Actions나 Azure DevOps와 같은 외부 서비스에서 Azure 리소스에 접근해야 할 일이 빈번하다. </p>
<p><strong>기존에는 이를 위해 클라이언트 Secret을 생성하고 관리해야 했지만, 이는 보안상 취약점이 될 수 있고 만료 주기마다 갱신해야 하는 번거로움이 있었다.</strong></p>
<p><img src="https://velog.velcdn.com/images/taekyu_/post/488f8888-ba9e-4227-af50-e7fd672787af/image.png" alt=""></p>
<p><span style="color: indianred"><strong>Workload Identity Federation은 GitHub, Azure DevOps 등과 같은 외부 워크로드의 ID를 Microsoft Entra ID의 ID와 연동하여, 별도의 암호나 액세스 키를 관리할 필요 없이 Azure 리소스에 접근할 수 있게 해주는 기능이다.</strong></span></p>
<blockquote>
<p><strong><span style="color: SlateBlue">Workload Identity Federation:</span> 앱 등록이나 사용자 할당 관리 ID를 생성한 후, OpenID Connect를 통해 신뢰 관계를 설정함으로써 외부 파이프라인이 Azure 리소스에 액세스하도록 허용하는 방식</strong></p>
</blockquote>
<p>*<em>Workload Identity Federation은 OIDC(OpenID Connect) 프로토콜을 사용한다. *</em></p>
<p><span style="color: indianred">*<em>Azure AD는 외부 자격 증명 공급자(예: GitHub, Azure DevOps)가 발급한 토큰을 신뢰하도록 설정된다. *</em></span></p>
<p>즉, &quot;비밀번호를 가져와&quot;가 아니라, &quot;내가 신뢰하는 시스템(Azure DevOps)이 증명한 사용자니까 통과시켜&quot;라는 방식이다. 이로 인해 시크릿 관리가 필요 없어지는 Secret-less 인증이 가능해진다.</p>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine2-oidc란span"><span style='background-color: MediumAquamarine'>2. OIDC란?</span></h2>
<hr>

<p>Workload Identity Federation은 OIDC(OpenID Connect) 프로토콜을 사용한다.</p>
<p><strong>그렇다면 OIDC는 무엇이며, 해당 시나리오에서 어떻게 작동하는 것일까?</strong></p>
<h3 id="span-stylecolor-slateblue2-1-oidc-openid-connect의-개념span"><span style="color: SlateBlue">2-1. OIDC (OpenID Connect)의 개념</span></h3>
<p><img src="https://velog.velcdn.com/images/taekyu_/post/ce1154f2-d26f-4d88-9628-1254b3e09840/image.png" alt=""></p>
<ul>
<li><span style="color: indianred"><strong>정의:</strong></span> OAuth 2.0 프로토콜 위에 구축된 인증(Authentication) 레이어이다.</li>
</ul>
<ul>
<li><p><span style="color: indianred"><strong>차이점:</strong></span></p>
<ul>
<li><p><code>OAuth 2.0</code>: &quot;이 사용자가 내 사진첩에 접근해도 될까?&quot; (권한 부여: Authorization)에 초점</p>
</li>
<li><p><code>OIDC</code>: &quot;이 사용자가 누구인가?&quot; (신원 확인: Authentication)에 초점</p>
</li>
</ul>
</li>
</ul>
<p>OAuth 2.0이 호텔의 객실 카드키를 발급해 주는 절차라면, OIDC는 그 카드키를 발급받기 위해 프론트 데스크에서 신분증을 확인하는 과정과 유사하다.
<br></p>
<h3 id="span-stylecolor-slateblue2-2-wif에서-oidc의-작동-원리-토큰-교환-과정span"><span style="color: SlateBlue">2-2. WIF에서 OIDC의 작동 원리 (토큰 교환 과정)</span></h3>
<p><strong>Workload Identity Federation 환경에서 Azure Entra ID와 외부 공급자인 Azure DevOps/GitHub는 다음과 같은 방식으로 신뢰를 구축한다.</strong></p>
<p><img src="https://velog.velcdn.com/images/taekyu_/post/363ba26c-e635-4336-9b97-ac33a46b752e/image.png" alt=""></p>
<ol>
<li><p><span style="color: indianred"><strong>외부 토큰 생성 (External Token):</strong></span></p>
<ul>
<li><p>Azure DevOps 파이프라인이 실행되면, 외부 자격 증명 공급자(Azure DevOps)는 현재 작업에 대한 정보를 담은 JWT(JSON Web Token)를 생성한다.</p>
</li>
<li><p>이 토큰에는 발급자(Issuer), 대상(Subject), 유효기간 등의 정보가 서명(Signed)되어 있다.</p>
</li>
</ul>
</li>
<li><p><span style="color: indianred"><strong>토큰 교환 요청:</strong></span></p>
<ul>
<li>파이프라인은 이 외부 토큰을 가지고 Azure Entra ID에 접근하여 &quot;Azure 리소스에 접근할 수 있는 액세스 토큰으로 교환해 달라&quot;고 요청한다.</li>
</ul>
</li>
<li><p><span style="color: indianred"><strong>검증:</strong></span></p>
<ul>
<li><p>Azure AD는 외부 공급자의 공개 키를 사용하여 JWT의 서명을 확인한다. (OIDC Discovery)</p>
</li>
<li><p>등록된 &#39;페더레이션 자격 증명&#39; 설정과 토큰의 정보(Issuer, Subject)가 일치하는지 대조한다.</p>
</li>
</ul>
</li>
<li><p><span style="color: indianred"><strong>액세스 토큰 발급:</strong></span></p>
<ul>
<li>검증이 완료되면 Azure AD는 Azure 리소스 제어에 사용할 수 있는 자체 액세스 토큰을 발급한다.<br>

</li>
</ul>
</li>
</ol>
<h3 id="span-stylecolor-slateblue3-왜-oidc가-더-안전한가span"><span style="color: SlateBlue">3. 왜 OIDC가 더 안전한가?</span></h3>
<ul>
<li><p><span style="color: indianred"><strong>Secret-less:</strong></span> 클라이언트 시크릿(비밀번호)을 어딘가에 저장하거나 전송할 필요가 없다. 오직 신뢰 관계(Trust)만 존재한다.</p>
</li>
<li><p><span style="color: indianred"><strong>단기 유효성:</strong></span> 교환에 사용되는 토큰은 유효 기간이 매우 짧아, 탈취되더라도 위험이 적다.</p>
</li>
<li><p><span style="color: indianred"><strong>표준화:</strong></span> GitHub, Azure DevOps, Kubernetes 등 다양한 플랫폼이 OIDC 표준을 따르므로 확장성이 뛰어나다.</p>
</li>
</ul>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine3-federation-설정-방법span"><span style='background-color: MediumAquamarine'>3. Federation 설정 방법</span></h2>
<hr>

<p><strong>Workload Identity Federation을 설정하는 데는 크게 두 가지 진입점이 있다.</strong></p>
<ul>
<li><p><span style="color: SlateBlue"><strong>옵션 A: 앱 등록 (App Registration) 사용</strong></span></p>
<ol>
<li><p>Microsoft Entra ID 테넌트에서 <code>새 앱 등록</code>을 생성한다. </p>
</li>
<li><p>등록 후 <code>[인증서 및 암호]</code> 메뉴로 이동</p>
</li>
<li><p>클라이언트 암호를 만드는 대신 <code>[페더레이션 자격 증명(Federated Credentials)]</code> 탭을 선택</p>
</li>
<li><p><code>[자격 증명 추가]</code>를 클릭하고 시나리오를 선택</p>
<ul>
<li><p>GitHub Actions나 Kubernetes 등을 선택할 수 있다.</p>
</li>
<li><p><span style="color: indianred"><strong>Azure DevOps를 사용하는 경우 <code>기타 발급자(Other issuer)</code>를 선택하고 값을 수동으로 입력</strong></span></p>
<br>


</li>
</ul>
</li>
</ol>
</li>
</ul>
<ul>
<li><p><span style="color: SlateBlue"><strong>옵션 B: 사용자 할당 관리 ID (User-assigned Managed Identity) 사용</strong></span></p>
<ol>
<li><p>Azure Portal에서 <code>관리 ID(Managed Identities)</code>를 검색하여 생성 또는 선택</p>
</li>
<li><p>설정 메뉴 중 <code>[페더레이션 자격 증명]</code> 탭으로 이동</p>
</li>
<li><p>자격 증명을 추가하며 GitHub Actions, Kubernetes 등의 시나리오를 지원</p>
<br>


</li>
</ol>
</li>
</ul>
<ul>
<li><p><span style="color: SlateBlue">💡<strong>관리 ID (Managed Identity)란?</strong></span></p>
<ul>
<li><p><strong>Azure 리소스(VM, App Service 등)가 다른 Azure 서비스(SQL DB, Storage 등)에 접근할 때, 코드 내에 자격 증명을 저장하지 않도록 돕는 기능</strong></p>
</li>
<li><p>Azure가 내부적으로 ID 관리를 전담하므로 개발자가 비밀번호를 알 필요도, 관리할 필요도 없음.
<img src="https://velog.velcdn.com/images/taekyu_/post/ba6d0aa7-7156-4d34-86aa-1f22855bd626/image.png" alt=""></p>
</li>
<li><p><span style="color: indianred"><strong>시스템 할당:</strong></span> 리소스 생명주기와 함께 생성 및 삭제됨.</p>
</li>
<li><p><span style="color: indianred"><strong>사용자 할당:</strong></span> 독립적인 리소스로 생성되어 여러 서비스에 할당 가능. </p>
</li>
</ul>
</li>
</ul>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine4-azure-devops에서-서비스-연결-생성span"><span style='background-color: MediumAquamarine'>4. Azure DevOps에서 서비스 연결 생성</span></h2>
<hr>

<p><strong>해당 프레젠테이션 데모에서는 Azure DevOps 프로젝트로 이동하여 실제 연결을 설정하는 과정을 설명하였다.</strong></p>
<ol>
<li><p>Azure DevOps 프로젝트의 <strong>[Project Settings]</strong> -&gt; <strong>[Service connections]</strong>로 이동한다.</p>
</li>
<li><p><strong>[New service connection]</strong>을 클릭하고 <code>Azure Resource Manager</code> 타입을 선택한다.</p>
<br>


</li>
</ol>
<ul>
<li><p><span style="color: SlateBlue">*<em>Azure Resource Manager (ARM) 란? *</em></span></p>
<ul>
<li><p>Azure의 배포 및 관리 서비스</p>
</li>
<li><p>Azure의 모든 리소스(VM, DB, Network 등)를 생성, 업데이트, 삭제하는 관리 계층을 제공</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/taekyu_/post/9ad03846-9a65-4091-8f2f-240a68933b0b/image.png" alt=""></p>
<ul>
<li><span style="color: indianred"><strong>Azure DevOps가 Azure 리소스를 제어하려면 ARM API와 통신해야 하므로, 서비스 연결 타입이 <code>Azure Resource Manager</code>가 되어야 함.</strong></span></li>
</ul>
</li>
</ul>
<br>

<ul>
<li><p><span style="color: SlateBlue">*<em>연결 설정 방식: 자동 vs 수동 *</em></span></p>
<ul>
<li><p><span style="color: indianred"><strong>Automatic (권장):</strong></span> </p>
<ul>
<li><p>테넌트 내 앱 등록 권한이 있다면 권장되는 방식</p>
</li>
<li><p>구독을 선택하고 저장하면 자동으로 앱 등록과 페더레이션 설정이 완료</p>
</li>
</ul>
</li>
<li><p><span style="color: indianred"><strong>Manual:</strong></span> </p>
<ul>
<li><p>앱 등록 권한이 없는 경우 사용</p>
</li>
<li><p>시스템이 생성해주는 발급자(Issuer)와 주체 식별자(Subject Identifier) 정보를 복사하여 Entra ID 관리자에게 전달하고 설정을 요청해야 함.</p>
</li>
</ul>
<br>
</li>
</ul>
</li>
<li><p><span style="color: SlateBlue"><strong>페더레이션 자격 증명의 구성 요소</strong></span></p>
<ul>
<li><p>설정이 완료된 후 Entra ID에서 해당 앱 등록을 확인해보면 시크릿은 없고 페더레이션 자격 증명만 존재한다.
<img src="https://velog.velcdn.com/images/taekyu_/post/2ae2f8ea-97da-4ec7-9f0a-882db274670b/image.png" alt=""></p>
</li>
<li><p><span style="color: indianred"><strong>Issuer (발급자):</strong></span> Azure DevOps 환경의 URL</p>
</li>
<li><p><span style="color: indianred"><strong>Subject Identifier (주체 식별자):</strong></span> Organization, 프로젝트, 그리고 서비스 연결 이름을 식별하는 문자열</p>
</li>
<li><p><span style="color: indianred"><strong>Audience (대상):</strong></span>   <code>api://AzureADTokenExchange</code> (항상 해당 값으로 고정)</p>
</li>
</ul>
</li>
</ul>
<p>해당 설정 덕분에 Azure DevOps의 특정 서비스 연결이 요청을 보낼 때, Entra ID는 이를 신뢰할 수 있는 요청으로 식별하게 된다.</p>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine5-azure-pipeline에서-microsoft-graph-연동span"><span style='background-color: MediumAquamarine'>5. Azure Pipeline에서 Microsoft Graph 연동</span></h2>
<hr>

<p><strong>설정된 Workload ID Federation을 활용하여, 파이프라인 상에서 Microsoft Graph에 인증하고 데이터를 가져오는 시나리오이다.</strong>
<br></p>
<ul>
<li><p><span style="color: SlateBlue">*<em>데모의 핵심 의도 *</em></span></p>
<ul>
<li><p>일반적으로 Azure AD 사용자 조회나 관리 작업은 관리자가 직접 수행하거나, 스크립트에 관리자 ID/PW를 저장하여 수행한다.</p>
</li>
<li><p><span style="color: indianred"><strong>해당 데모는 파이프라인이 Workload ID Federation을 통해 스스로 신원을 증명하고, 관리자 개입 없이 보안 토큰을 발급받아 관리 도구인 Graph API를 호출할 수 있음을 검증하는 개념 증명 과정이다.</strong></span></p>
<br>
</li>
</ul>
</li>
<li><p><span style="color: SlateBlue"><strong>Microsoft Graph란?</strong></span> </p>
<ul>
<li><p>Microsoft 365(Office, Windows, Entra ID 등)의 수많은 데이터와 인텔리전스에 접근할 수 있는 통합 API 엔드포인트 
<img src="https://velog.velcdn.com/images/taekyu_/post/ed1a2e51-63a1-4eb9-a375-b1a7ae473748/image.png" alt=""></p>
</li>
<li><p>이를 통해 사용자 정보 조회, 메일 발송, 일정 관리 등을 프로그래밍 방식으로 제어할 수 있다. </p>
</li>
<li><p><span style="color: indianred"><strong>해당 데모에서는 Connect-MgGraph를 통해 Entra ID의 사용자 정보를 조회하는 데 사용</strong></span></p>
<br>

</li>
</ul>
</li>
</ul>
<h3 id="span-stylecolor-slateblue4-1-파이프라인-구성-1단계--액세스-토큰-획득-azure-cli-span"><span style="color: SlateBlue">*<em>4-1. 파이프라인 구성 1단계 : 액세스 토큰 획득 (Azure CLI) *</em></span></h3>
<p>*<em>먼저 <code>AzureCLI@2</code> 태스크를 사용하여 토큰을 가져온다. *</em></p>
<p>해당 단계는 파이프라인이 &quot;나 신뢰할 수 있는 파이프라인이야&quot;라고 Azure AD에 신호를 보내고, OIDC 검증을 거쳐 Access Token를 건네받는 과정이다.</p>
<p>이때 서비스 연결은 Workload Identity Federation이 적용된 연결을 사용한다.</p>
<pre><code class="language-bash"># Azure CLI로 액세스 토큰 요청
az account get-access-token --resource-type ms-graph</code></pre>
<ul>
<li><p><code>--resource-type</code>을 ms-graph로 지정하여 Graph API용 토큰을 받는다. 
(ARM API나 Key Vault용으로도 변경 가능)</p>
</li>
<li><p><span style="color: indianred"><strong>핵심 원리:</strong></span> 파이프라인 에이전트가 자신의 OIDC 토큰을 Azure AD에 제시하면, Azure AD는 미리 등록된 &#39;페더레이션 자격 증명&#39;과 대조하여 유효성을 확인한 후 Microsoft Graph 접근용 액세스 토큰을 발급해준다.</p>
</li>
<li><p>받아온 토큰을 파이프라인 내에서 사용할 변수인 <code>secretToken</code>로 설정하고, 이를 Secret 타입으로 지정하여 로그에 노출되지 않도록 한다.</p>
<br>

</li>
</ul>
<h3 id="span-stylecolor-slateblue4-2-파이프라인-구성-2단계--보안-문자열-변환-및-graph-api-호출span"><span style="color: SlateBlue"><strong>4-2. 파이프라인 구성 2단계 : 보안 문자열 변환 및 Graph API 호출</strong></span></h3>
<p>앞서 발급받은 토큰을 사용하여 실제로 Microsoft Graph에 로그인하고 관리 작업을 수행한다.</p>
<pre><code class="language-bash"># 1. 일반 변수를 PowerShell의 SecureString으로 변환
$secureToken = ConvertTo-SecureString -String $env:secretToken -AsPlainText -Force

# 2. Microsoft Graph 연결
Connect-MgGraph -AccessToken $secureToken

# 3. 사용자 정보 조회
Get-MgUser -UserId &quot;testuser@domain.com&quot;</code></pre>
<ul>
<li><p><span style="color: indianred"><strong>의의:</strong></span> <code>Connect-MgGraph</code> 명령어 실행 시 별도의 로그인 창이나 패스워드 입력 없이, 1단계에서 획득한 토큰만으로 인증(-AccessToken)을 통과하는 것이 핵심이다.</p>
</li>
<li><p>파이프라인은 비밀 변수를 평문으로 접근할 수 있지만, <strong><code>Connect-MgGraph 명령어</code></strong>는 SecureString을 요구하므로 변환 과정이 필요하다.</p>
</li>
</ul>
<br>

<h3 id="span-stylecolor-slateblue4-3-주의사항-권한-부여-api-permissionsspan"><span style="color: SlateBlue"><strong>4-3. 주의사항: 권한 부여 (API Permissions)</strong></span></h3>
<p>인증(Authentication)에 성공했더라도, 특정 작업을 수행하려면 권한(Authorization)이 필요하다.</p>
<p><span style="color: indianred"><strong>토큰을 통해 &quot;내가 누구인지&quot;는 증명했지만, &quot;무엇을 할 수 있는지&quot;는 별도로 정의해야 한다.</strong></span></p>
<ol>
<li><p>Entra ID 포털의 해당 앱 등록 메뉴로 이동한다.</p>
</li>
<li><p>[API 권한] 탭에서 Microsoft Graph -&gt; <code>User.Read.All</code> 등의 필요한 권한을 추가하고 관리자 동의를 얻어야 한다.</p>
</li>
<li><p>이 과정이 누락되면 Get-MgUser 실행 시 권한 부족 오류가 발생한다.</p>
</li>
</ol>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine6-정리span"><span style='background-color: MediumAquamarine'>6. 정리</span></h2>
<hr>

<p><span style="color: indianred">*<em>Workload Identity Federation은 기존의 클라이언트 시크릿 방식이 가진 보안 취약점과 관리 비용을 OIDC 기반의 신뢰 관계로 대체하는 인증 기술이다. *</em></span></p>
<p>*<em>이는 외부 ID 공급자와 Azure AD 간의 토큰 교환을 통해 자격 증명이 필요 없는(Secret-less) 아키텍처를 구현함으로써, 보안 리스크를 원천적으로 차단한다. *</em></p>
<p>결과적으로 엔지니어는 시크릿 만료나 유출 걱정 없이 Microsoft Graph 및 Azure 리소스에 대한 일관된 보안 거버넌스를 유지하며, 더욱 견고한 자동화 파이프라인을 구축할 수 있다.</p>
<p><span style="color: indianred"></span>
<span style="color: SlateBlue"></span></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[90DaysOfDevOps (Day 76)]]></title>
            <link>https://velog.io/@taekyu_/90DaysOfDevOps-Day-76-v77hoer5</link>
            <guid>https://velog.io/@taekyu_/90DaysOfDevOps-Day-76-v77hoer5</guid>
            <pubDate>Thu, 22 Jan 2026 04:47:58 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>해당 스터디는 90DaysOfDevOps
<a href="https://github.com/MichaelCade/90DaysOfDevOps">https://github.com/MichaelCade/90DaysOfDevOps</a>
를 기반으로 진행한 내용입니다.</strong></p>
</blockquote>
<p>** Day 76 - All you need to know about AWS CDK **</p>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine1-aws-cdk란-span"><span style='background-color: MediumAquamarine'>1. AWS CDK란? </span></h2>
<hr>

<p>AWS에서 기존의 관리 콘솔을 통한 수동 작업은 오류가 발생할 확률이 높다. 이러한 문제를 해결하기 위해 등장한 것이 바로 AWS CDK이다.</p>
<p><span style="color: indianred"><strong>CDK(Cloud Development Kit)는 코드를 사용하여 클라우드 인프라를 정의할 수 있게 해주는 프레임워크이자 툴킷이다.</strong></span></p>
<p><strong>즉, 일반적인 소프트웨어를 개발하듯이 인프라를 코딩(Infrastructure as Code)하는 것이다.</strong></p>
<p><img src="https://velog.velcdn.com/images/taekyu_/post/f4dd8f54-2dfa-4aad-a4f8-a4fa4983add8/image.png" alt=""></p>
<p>*<em>사용자가 배포하고자 하는 인프라를 코드로 정의하면, CDK는 이를 AWS 리소스를 설정해 주는 서비스인 CloudFormation 템플릿으로 변환하여 배포한다. *</em></p>
<p>CloudFormation은 리소스의 프로비저닝, 구성, 의존성 관리를 자동으로 처리해 준다.</p>
<hr>
<p><span style="color: indianred"><strong>CDK를 이해할 때 가장 중요한 점은 작성한 코드가 AWS에 직접 배포되는 것이 아니라는 점이다.</strong></span></p>
<ol>
<li><p><span style="color: SlateBlue"><strong>코드 작성:</strong></span> 사용자가 CDK를 이용해 인프라를 코딩</p>
</li>
<li><p><span style="color: SlateBlue"><strong>변환:</strong></span> 작성된 코드는 JSON 또는 YAML 형태의 CloudFormation 템플릿으로 변환됨.</p>
</li>
<li><p><span style="color: SlateBlue"><strong>배포:</strong></span> 변환된 CloudFormation 템플릿이 AWS에 업로드되고, 이를 통해 리소스가 생성된다.</p>
</li>
</ol>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine2-aws-cdk를-사용해야하는-이유span"><span style='background-color: MediumAquamarine'>2. AWS CDK를 사용해야하는 이유</span></h2>
<hr>

<ul>
<li><p><span style="color: SlateBlue"><strong>관리 콘솔(Console) 대비 장점</strong></span></p>
<ul>
<li><p>S3 버킷을 생성하고 객체를 업로드하는 단순한 작업은 콘솔에서도 가능하지만, S3와 DynamoDB를 연동하는 등 복잡한 인프라를 구축할 때는 이야기가 다르다.</p>
</li>
<li><p>콘솔에서의 수동 클릭은 실수를 유발하기 쉽다.</p>
</li>
<li><p>복잡한 아키텍처를 단 몇 줄의 코드로 구현하여 빠르게 배포할 수 있다.</p>
<br>
</li>
</ul>
</li>
<li><p><span style="color: SlateBlue"><strong>CloudFormation 직접 사용 대비 장점</strong></span></p>
<ul>
<li><p>&quot;어차피 CloudFormation으로 변환되는데, 처음부터 CloudFormation(JSON/YAML)으로 작성하면 되지 않나?&quot;라는 의문이 들 수 있다. 하지만 CDK는 다음과 같은 강력한 이점을 가진다.</p>
</li>
<li><p><span style="color: indianred"><strong>프로그래밍 언어 선택권:</strong></span> </p>
<ul>
<li><p>CloudFormation은 오직 JSON이나 YAML만 사용해야 하므로 복잡한 로직을 구현하기 어렵다. </p>
</li>
<li><p>반면, CDK는 Python, Java, TypeScript, JavaScript, .NET(C#), Go 등 친숙한 프로그래밍 언어를 사용하여 인프라를 구축할 수 있다.</p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine3-aws-cdk-아키텍처span"><span style='background-color: MediumAquamarine'>3. AWS CDK 아키텍처</span></h2>
<hr>

<p><img src="https://velog.velcdn.com/images/taekyu_/post/a9f4fe24-b7b1-4d24-a46a-680054bb5743/image.png" alt=""></p>
<ol>
<li><p><span style="color: SlateBlue"><strong>App (앱):</strong></span></p>
<ul>
<li><p>가장 상위 개념으로, Container 역할을 한다.</p>
</li>
<li><p>내부에 여러 개의 Stack을 담는다.</p>
</li>
</ul>
</li>
<li><p><span style="color: SlateBlue"><strong>Stack (스택):</strong></span></p>
<ul>
<li><p>기본적으로 Template과 같은 개념이다.</p>
</li>
<li><p><strong>리소스 간의 관계를 설정하는 단위</strong></p>
</li>
<li><p><span style="color: indianred"><strong>Stack이 나중에 CloudFormation 템플릿으로 변환된다.</strong></span></p>
</li>
</ul>
</li>
<li><p><span style="color: SlateBlue"><strong>Construct (컨스트럭트):</strong></span></p>
<ul>
<li><p>Stack 내부에서 정의되며, 실제 AWS 리소스들을 보유하고 그룹화하는 구성 요소</p>
</li>
<li><p>예를 들어, S3 버킷과 같은 구체적인 리소스 정의가 이곳에서 이루어진다.</p>
</li>
</ul>
</li>
</ol>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine4-정리span"><span style='background-color: MediumAquamarine'>4. 정리</span></h2>
<hr>

<p>AWS CDK는 복잡한 클라우드 인프라를 익숙한 프로그래밍 언어로 효율적으로 관리하고 배포할 수 있게 해주는 강력한 도구이다. </p>
<p><strong>Terraform과 같은 대안도 존재하지만, AWS 환경에서 프로그래밍 언어의 이점을 살려 인프라를 구축하고 싶다면 CDK는 최적의 선택이 될 것이다.</strong></p>
<p><span style="color: indianred"></span>
<span style="color: SlateBlue"></span></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[90DaysOfDevOps (Day 75)]]></title>
            <link>https://velog.io/@taekyu_/90DaysOfDevOps-Day-75-wtqg8hzq</link>
            <guid>https://velog.io/@taekyu_/90DaysOfDevOps-Day-75-wtqg8hzq</guid>
            <pubDate>Mon, 19 Jan 2026 15:48:31 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>해당 스터디는 90DaysOfDevOps
<a href="https://github.com/MichaelCade/90DaysOfDevOps">https://github.com/MichaelCade/90DaysOfDevOps</a>
를 기반으로 진행한 내용입니다.</strong></p>
</blockquote>
<p>** Day 75 - Distracted Development **</p>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine1-산만한-개발의-실체span"><span style='background-color: MediumAquamarine'>1. 산만한 개발의 실체</span></h2>
<hr>

<p>우리는 흔히 바쁜 것을 생산적인 것으로 착각한다. </p>
<p><span style="color: indianred"><strong>하지만 산만한 개발(Distracted Development)은 멀티태스킹이나 분산된 주의력으로 인해 집중력과 생산성을 잃는 현상을 말한다.</strong></span></p>
<p>이는 단순히 일을 덜 하는 문제가 아니라, 결과물의 품질과 영향력이 떨어지는 심각한 문제다.</p>
<p>*<em>캘리포니아 대학교 어바인의 연구에 따르면, 현대 직장인은 8시간 근무 중 평균 40~45회의 방해를 받는다고 한다. *</em></p>
<p>이러한 잦은 방해는 업무 흐름을 끊고, 깊이 있는 사고를 불가능하게 만든다.</p>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine2-멀티태스킹이라는-환상span"><span style='background-color: MediumAquamarine'>2. 멀티태스킹이라는 환상</span></h2>
<hr>

<p><strong>우리는 동시에 여러 일을 처리하는 능력을 찬양하지만, 뇌과학적으로 볼 때 멀티태스킹은 존재하지 않는다.</strong></p>
<h3 id="span-stylecolor-slateblue2-1-뇌는-전환할-뿐이다span"><span style="color: SlateBlue">2-1. 뇌는 전환할 뿐이다</span></h3>
<p>*<em>인간의 뇌는 한 번에 하나의 작업만 처리할 수 있다. *</em></p>
<p>우리가 멀티태스킹이라고 믿는 것은 사실 A 작업에서 B 작업으로 아주 빠르게 초점을 옮기는 행위일 뿐이다.
<br></p>
<h3 id="span-stylecolor-slateblue2-2-숨겨진-대가-전환-비용span"><span style="color: SlateBlue">2-2. 숨겨진 대가: 전환 비용</span></h3>
<p><strong>작업을 바꿀 때마다 우리 뇌는 새로운 문맥에 적응하기 위해 에너지를 쓴다. 이를 전환 비용이라고 한다.</strong></p>
<p>미국 심리학회에 따르면, 이 잦은 전환으로 인해 생산적인 시간의 최대 40%가 낭비된다.</p>
<p>생산성 저하 없이 진정으로 멀티태스킹이 가능한 슈퍼 태스커는 전 세계 인구의 단 2%에 불과하다.</p>
<p><span style="color: indianred"><strong>나머지 98%인 우리는 한 번에 하나만 하는 것이 훨씬 효율적이다.</strong></span></p>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine3-방해받은-시간의-경제학span"><span style='background-color: MediumAquamarine'>3. 방해받은 시간의 경제학</span></h2>
<hr>

<p>잠깐 메신저를 확인하고 다시 업무로 돌아오는 데 얼마나 걸릴까? 데이터는 충격적인 결과를 보여준다.</p>
<ul>
<li><p><span style="color: indianred"><strong>23분의 법칙:</strong></span> UC Irvine 연구에 따르면, 직장인은 평균 11분마다 방해를 받으며, 한 번 방해를 받은 후 원래의 깊은 집중 상태로 돌아가는 데는 최대 23분이 걸린다. 이를 계산하면 하루에 온전히 집중하는 시간은 약 3시간 18분에 불과하다.</p>
</li>
<li><p><span style="color: indianred"><strong>회의에 의한 죽음 (Death by Meeting):</strong></span> 아틀라시안의 연구에 따르면, 직장인들은 하루 약 1시간 30분을 가치 없는 회의에 소비한다. 이는 기업 입장에서 연간 수십억 원의 매몰 비용을 의미한다.</p>
</li>
</ul>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine4-몰입을-되찾는-3가지-전략-span"><span style='background-color: MediumAquamarine'>4. 몰입을 되찾는 3가지 전략 </span></h2>
<hr>

<p><strong>산만함의 늪에서 빠져나와 Deep Work를 실현하기 위한 검증된 방법들이다.</strong></p>
<h3 id="span-stylecolor-slateblue4-1-타임-블로킹과-우선순위span"><span style="color: SlateBlue">4-1. 타임 블로킹과 우선순위</span></h3>
<p>하루 종일 반응형으로 일해서는 안 된다. </p>
<p><span style="color: indianred"><strong>타임 블로킹을 통해 특정 작업에 전용 시간을 할당해야 한다.</strong></span></p>
<p><strong>또한, 아이젠하워 매트릭스를 활용해 긴급한 일과 중요한 일을 구분하고, 무엇에 집중할지 전략적으로 선택해야 한다.</strong>
<br></p>
<h3 id="span-stylecolor-slateblue4-2-휴식의-기술-포모도로-pomodorospan"><span style="color: SlateBlue">4-2. 휴식의 기술: 포모도로 (Pomodoro)</span></h3>
<p>*<em>쉬지 않고 일하는 것이 능사가 아니다. *</em></p>
<p><span style="color: indianred"><strong>25분간 집중하고 5분간 휴식하는 포모도로 기법을 활용한다.</strong> </span></p>
<p>일리노이 대학교의 연구에 따르면, 작업 중의 짧고 계획된 휴식은 뇌의 인지 배터리를 재충전시켜 장기적인 집중력을 극적으로 향상시킨다.
<br></p>
<h3 id="span-stylecolor-slateblue4-3-디지털-차단-digital-detoxspan"><span style="color: SlateBlue">4-3. 디지털 차단 (Digital Detox)</span></h3>
<p>이메일과 메신저는 정해진 시간에만 확인한다. 불필요한 알림을 끄고, 기술적으로 방해 요소를 차단하는 앱을 활용하는 것도 훌륭한 전략이다.</p>
<hr>

<h2 id="span-stylebackground-color-mediumaquamarine5-결론-싱글-태스킹의-힘span"><span style='background-color: MediumAquamarine'>5. 결론: 싱글 태스킹의 힘</span></h2>
<hr>

<p><strong>마이크로소프트는 회의 없는 날을 도입해 생산성을 높였고, 구글은 20% 타임을 통해 직원들이 한 가지 프로젝트에 몰입할 시간을 보장했다.</strong></p>
<p>하버드 비즈니스 리뷰를 비롯한 수많은 연구가 가리키는 결론은 명확하다. </p>
<p><span style="color: indianred">*<em>우리는 한 번에 하나에 온전히 집중할 때 가장 똑똑하고, 창의적이며, 평온하다. *</em></span></p>
<p>멀티태스킹의 환상을 버리고 싱글 태스킹을 선택할 때, 우리의 하루는 더 이상 산만하지 않을 것이다.</p>
<p><span style="color: indianred"></span>
<span style="color: SlateBlue"></span></p>
]]></description>
        </item>
    </channel>
</rss>