<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>언젠가는 풍성해질 개발블로그</title>
    <link>https://developer-it.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Sun, 27 Sep 2026 16:01:28 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Hun's blog</managingEditor>
    <image>
      <title>언젠가는 풍성해질 개발블로그</title>
      <url>https://tistory1.daumcdn.net/tistory/5422151/attach/5e4bf84b8f51481baf8dfbf91d1fcb02</url>
      <link>https://developer-it.tistory.com</link>
    </image>
    <item>
      <title>[홈 서버 구축] Linux Node Exporter와 Prometheus&amp;middot;Grafana 설치</title>
      <link>https://developer-it.tistory.com/27</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;들어가며&lt;/h2&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;최근 서버로 활용하기 좋은 PC를 한 대 얻게 되어 직접 홈 서버를 구축해 보려고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;주된 목적은 친구들과 함께 즐길 마인크래프트 서버를 운영하는 것이다. 하지만 직업이 직업인 만큼 단순히 게임 서버만 실행하는 데 그치지 않고, Ubuntu 기반 운영체제에 모니터링 스택까지 구성해 제대로 운영해 볼 생각이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;나아가 기회가 된다면 Ansible을 이용해 서버 설정을 코드로 관리하고, 필요할 때 언제든 동일한 환경을 재현할 수 있도록 구성 자동화까지 진행해 보는 것이 이번 시리즈의 목표다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이번 포스트에서는 Ubuntu가 설치된 PC에 Node Exporter를 구성하고, 시스템 메트릭을 Prometheus로 수집한 뒤 Grafana 대시보드로 시각화하는 과정까지 다룬다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Node Exporter를 비롯한 각 구성 요소는 컨테이너가 아닌 Linux systemd 서비스 방식으로 설치할 예정이다. 특별한 기술적 이유가 있는 것은 아니다. 회사에서 Docker Compose와 Kubernetes를 주로 사용하고 있어서, 이번에는 호스트에서 서비스를 직접 구성하고 관리해 보고 싶었다. 결국 가장 큰 이유는 그냥 내 마음이다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사용 장비&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;399&quot; data-origin-height=&quot;185&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dHCd6R/dJMcabZ69zm/yNl43y9nFRqOBukpjoAmyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dHCd6R/dJMcabZ69zm/yNl43y9nFRqOBukpjoAmyK/img.png&quot; data-alt=&quot;HP ProDesk 400 G7 소형 폼 팩터&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dHCd6R/dJMcabZ69zm/yNl43y9nFRqOBukpjoAmyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdHCd6R%2FdJMcabZ69zm%2FyNl43y9nFRqOBukpjoAmyK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;395&quot; height=&quot;185&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;399&quot; data-origin-height=&quot;185&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;HP ProDesk 400 G7 소형 폼 팩터&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; 모델&lt;/b&gt;: HP ProDesk 400 G7 SFF&lt;br /&gt;&lt;b&gt;CPU&lt;/b&gt;:&amp;nbsp;Intel Core i5-10400 &amp;mdash; 6코어 12스레드, 2.9GHz&amp;middot;최대 4.3GHz&lt;br /&gt;&lt;b&gt;메모리&lt;/b&gt;:&amp;nbsp;DDR4 24GB &amp;mdash; 16GB + 8GB&lt;br /&gt;&lt;b&gt;저장장치&lt;/b&gt;:&amp;nbsp;500GB&lt;br /&gt;&lt;b&gt;내장 그래픽&lt;/b&gt;:&amp;nbsp;Intel UHD Graphics 630&lt;br /&gt;&lt;b&gt;운영체제&lt;/b&gt;: Ubuntu&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Node Exporter 설치&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 홈 서버의 CPU, 메모리, 디스크, 네트워크 등의 상태를 수집하기 위해 Node Exporter를 설치한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Node Exporter는 Linux 시스템의 하드웨어 및 운영체제 정보를 Prometheus 형식의 메트릭으로 제공하는 Exporter다. 기본적으로 9100 포트를 사용하며, Prometheus는 이 엔드포인트에 주기적으로 접근해 메트릭을 수집한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;전용 사용자 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Node Exporter를 별도의 사용자 권한으로 실행하기 위해 로그인할 수 없는 시스템 사용자를 생성한다. 이는 서비스를 불필요한 root 권한으로 실행하지 않기 위함이다.&lt;/p&gt;
&lt;pre id=&quot;code_1789477956329&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo useradd \
  --system \                 # 일반 사용자가 아닌 시스템 계정
  --user-group \             # 같은 이름의 전용 그룹 생성
  --no-create-home \         # 홈 디렉터리를 만들지 않음
  --shell /usr/sbin/nologin \ # 직접 로그인하지 못하도록 제한
  node_exporter&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성된 사용자는 다음과 같이 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;777&quot; data-origin-height=&quot;42&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VDaDW/dJMcaaz4574/AZKbmKA7L95Y2GhyqISzOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VDaDW/dJMcaaz4574/AZKbmKA7L95Y2GhyqISzOk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VDaDW/dJMcaaz4574/AZKbmKA7L95Y2GhyqISzOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVDaDW%2FdJMcaaz4574%2FAZKbmKA7L95Y2GhyqISzOk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;777&quot; height=&quot;42&quot; data-origin-width=&quot;777&quot; data-origin-height=&quot;42&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Node Exporter 다운로드 및 설치&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 1.12.1 버전을 설치할 예정이고, 서버의 CPU가 Intel x86-64 아키텍처이므로 linux-amd64 파일을 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1789478568620&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 임시 디렉토리로 이동
cd /tmp

# Node Exporter 다운로드
wget &quot;https://github.com/prometheus/node_exporter/releases/download/v1.12.1/node_exporter-1.12.1.linux-amd64.tar.gz&quot;

# 압축 해제
tar -xzf &quot;node_exporter-1.12.1.linux-amd64.tar.gz&quot;

# 실행 파일 설치
sudo install \
  -m 0755 \
  &quot;node_exporter-1.12.1.linux-amd64/node_exporter&quot; \
  /opt/node_exporter/node_exporter&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치 경로(/opt/node_exporter)로 가서 설치되었는지 다음과 같이 확인한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;778&quot; data-origin-height=&quot;170&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/v4b2q/dJMcahsudEV/MK7QTve7jKghBP9k1iWSw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/v4b2q/dJMcahsudEV/MK7QTve7jKghBP9k1iWSw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/v4b2q/dJMcahsudEV/MK7QTve7jKghBP9k1iWSw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fv4b2q%2FdJMcahsudEV%2FMK7QTve7jKghBP9k1iWSw1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;778&quot; height=&quot;170&quot; data-origin-width=&quot;778&quot; data-origin-height=&quot;170&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Systemd 서비스 등록&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Node Exporter가 서버 부팅 시 자동으로 실행되도록 systemd 서비스로 등록한다.&lt;/p&gt;
&lt;pre id=&quot;code_1789479659930&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo nano /etc/systemd/system/node_exporter.service&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스 파일에 다음 내용을 작성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1789479707833&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[Unit]
Description=Prometheus Node Exporter
Wants=network-online.target
After=network-online.target

[Service]
Type=simple
ExecStart=/opt/node_exporter/node_exporter
Restart=on-failure
User=node_exporter
Group=node_exporter

[Install]
WantedBy=multi-user.target&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일을 저장한 뒤 systemd 설정을 다시 불러오고 Node Exporter를 실행한다.&lt;/p&gt;
&lt;pre id=&quot;code_1789479748588&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo systemctl daemon-reload
sudo systemctl enable --now node_exporter&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;서비스 상태 확인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 명령으로 서비스가 정상적으로 실행되고 있는지 확인한다.&lt;/p&gt;
&lt;pre id=&quot;code_1789513915464&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo systemctl status node_exporter&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력에 active (running)이 표시되면 서비스가 정상적으로 실행된 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Node Exporter는 기본적으로 9100 포트의 /metrics 경로를 통해 메트릭을 제공한다. 다음 명령으로 실제 응답을 확인한다.&lt;/p&gt;
&lt;pre id=&quot;code_1789514000413&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;curl http://localhost:9100/metrics&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;796&quot; data-origin-height=&quot;244&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lPlV1/dJMcacLva11/glBkjbhSVrBVJWnzkcTLnK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lPlV1/dJMcacLva11/glBkjbhSVrBVJWnzkcTLnK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lPlV1/dJMcacLva11/glBkjbhSVrBVJWnzkcTLnK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlPlV1%2FdJMcacLva11%2FglBkjbhSVrBVJWnzkcTLnK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;796&quot; height=&quot;244&quot; data-origin-width=&quot;796&quot; data-origin-height=&quot;244&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Prometheus 설치 및 연동&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Node Exporter가 정상적으로 시스템 메트릭을 제공하는 것까지 확인했다. 이제 해당 메트릭을 주기적으로 수집하고 저장할 Prometheus를 설치한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Prometheus 역시 Node Exporter와 마찬가지로 전용 사용자와 systemd 서비스를 이용해 실행한다. Prometheus 버전은 현재 LTS인 3.13.3을 사용하고, 관련 파일은 모두 /opt/prometheus 아래에 구성한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;전용 사용자 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Prometheus가 root 권한으로 실행되지 않도록 전용 시스템 사용자를 생성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1789514901413&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo useradd \
  --system \
  --user-group \
  --no-create-home \
  --shell /usr/sbin/nologin \
  prometheus&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성된 계정을 확인한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;797&quot; data-origin-height=&quot;43&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wtuIr/dJMcafg4hIo/qqY2HDjKkR0NkGEkWQ9uHk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wtuIr/dJMcafg4hIo/qqY2HDjKkR0NkGEkWQ9uHk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wtuIr/dJMcafg4hIo/qqY2HDjKkR0NkGEkWQ9uHk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwtuIr%2FdJMcafg4hIo%2FqqY2HDjKkR0NkGEkWQ9uHk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;797&quot; height=&quot;43&quot; data-origin-width=&quot;797&quot; data-origin-height=&quot;43&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Prometheus 3.13.3 다운로드&lt;/h3&gt;
&lt;pre id=&quot;code_1789514976053&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 임시 디렉터리로 이동
cd /tmp

# Prometheus 3.13.3 다운로드
wget &quot;https://github.com/prometheus/prometheus/releases/download/v3.13.3/prometheus-3.13.3.linux-amd64.tar.gz&quot;

# 압축 해제
tar -xzf &quot;prometheus-3.13.3.linux-amd64.tar.gz&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실행 파일 설치&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Prometheus를 설치할 디렉토리를 생성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1789515076837&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo mkdir -p /opt/prometheus
sudo mkdir -p /opt/prometheus/data&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;압축을 해제한 디렉터리에서 실행 파일을 설치한다.&lt;/p&gt;
&lt;pre id=&quot;code_1789515093815&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo install \
  -m 0755 \
  &quot;prometheus-3.13.3.linux-amd64/prometheus&quot; \
  /opt/prometheus/prometheus

sudo install \
  -m 0755 \
  &quot;prometheus-3.13.3.linux-amd64/promtool&quot; \
  /opt/prometheus/promtool&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;기본 설정 파일도 &lt;/span&gt;&lt;span&gt;/opt/prometheus&lt;/span&gt;&lt;span&gt;로 복사한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1789515123853&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo install \
  -m 0755 \
  &quot;prometheus-3.13.3.linux-amd64/prometheus.yml&quot; \
  /opt/prometheus/prometheus.yml&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 뜨면 정상적으로 설치된 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;174&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/q5ykk/dJMcaheWgti/vyMzcmH3Iq3PYKKyZSoQS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/q5ykk/dJMcaheWgti/vyMzcmH3Iq3PYKKyZSoQS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/q5ykk/dJMcaheWgti/vyMzcmH3Iq3PYKKyZSoQS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fq5ykk%2FdJMcaheWgti%2FvyMzcmH3Iq3PYKKyZSoQS0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;174&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;174&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Node Exporter 수집 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Prometheus가 Node Exporter의 메트릭을 수집하도록 스크랩 설정을 진행한다.&lt;/p&gt;
&lt;pre id=&quot;code_1789515234513&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo nano /opt/prometheus/prometheus.yml&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Prometheus 자체 메트릭과 Node Exporter 메트릭을 수집하도록 다음과 같이 작성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1789515277799&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;global:
  scrape_interval: 15s
  evaluation_interval: 15s

scrape_configs:
  - job_name: prometheus
    static_configs:
      - targets:
          - localhost:9090

  - job_name: node_exporter
    static_configs:
      - targets:
          - localhost:9100&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Prometheus와 Node Exporter가 같은 서버에서 실행되고 있으므로 수집 대상에는 localhost를 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 promtool 을 통해 설정 파일의 문법 오류를 검증할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1789515351666&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo /opt/prometheus/promtool \
  check config /opt/prometheus/prometheus.yml&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 SUCCESS가 표시되면 설정 파일에 문제가 없는 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1789515398693&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SUCCESS: /opt/prometheus/prometheus.yml is valid prometheus config file syntax&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;systemd 서비스 등록&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Prometheus를 systemd 서비스에 등록해서 자동으로 관리되도록 하자.&lt;/p&gt;
&lt;pre id=&quot;code_1789515481274&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo nano /etc/systemd/system/prometheus.service&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 내용을 작성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1789515511701&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[Unit]
Description=Prometheus Monitoring
Wants=network-online.target
After=network-online.target

[Service]
Type=simple
ExecStart=/opt/prometheus/prometheus \
  --config.file=/opt/prometheus/prometheus.yml \
  --storage.tsdb.path=/opt/prometheus/data
Restart=on-failure
User=prometheus
Group=prometheus

[Install]
WantedBy=multi-user.target&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정을 적용하고 서비스를 실행한다.&lt;/p&gt;
&lt;pre id=&quot;code_1789515535313&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo systemctl daemon-reload
sudo systemctl enable --now prometheus&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실행 상태 확인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 상태를 확인할 예정인데, systemctl status 로 확인하는 것도 좋지만 바로 Prometheus 웹 UI로 들어가서 확인하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저에서 다음 주소로 접속한다.&lt;/p&gt;
&lt;pre id=&quot;code_1789515963518&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;http://localhost:9090/targets&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;prometheus와 node_exporter가 모두 UP으로 표시되면 정상적으로 메트릭을 수집하고 있는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1275&quot; data-origin-height=&quot;558&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/s0tnx/dJMcajw2l16/U1CBs3oXYOJrXB5lUOjKck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/s0tnx/dJMcajw2l16/U1CBs3oXYOJrXB5lUOjKck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/s0tnx/dJMcajw2l16/U1CBs3oXYOJrXB5lUOjKck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fs0tnx%2FdJMcajw2l16%2FU1CBs3oXYOJrXB5lUOjKck%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;350&quot; data-origin-width=&quot;1275&quot; data-origin-height=&quot;558&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 쿼리화면에서 다음 PromQL을 실행해서 정상적으로 쿼리를 보내는지도 확인한다.&lt;/p&gt;
&lt;pre id=&quot;code_1789516106153&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;up{job=&quot;node_exporter&quot;}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과가 1이면 Prometheus와 Node Exporter의 연동이 완료된 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Grafana 설치 및 Prometheus 연동&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 Prometheus에 저장된 메트릭을 시각화하기 위해 Grafana를 설치한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Grafana는 이전 두 서비스와 다르게 공식 APT 저장소를 이용해서 설치해야 한다. Grafana 패키지는 앞선 두 서비스와 달리 전체 사용자와 systemd 서비스까지 함께 구성해준다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Grafana 저장소 추가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 저장소를 추가하는 데 필요한 패키지를 설치한다.&lt;/p&gt;
&lt;pre id=&quot;code_1789516554171&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo apt-get install -y \
  apt-transport-https \
  wget \
  gnupg&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Grafana 저장소의 GPG 키를 내려받는다.&lt;/p&gt;
&lt;pre id=&quot;code_1789600729983&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;http://localhost:3000/login&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1789600519695&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo mkdir -p /etc/apt/keyrings

sudo wget \
  -O /etc/apt/keyrings/grafana.asc \
  https://apt.grafana.com/gpg-full.key

sudo chmod 644 /etc/apt/keyrings/grafana.asc&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Grafana 공식 stable 저장소를 추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1789600535669&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;echo &quot;deb [signed-by=/etc/apt/keyrings/grafana.asc] https://apt.grafana.com stable main&quot; \
  | sudo tee /etc/apt/sources.list.d/grafana.list&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Grafana 설치 및 실행 확인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;패키지 목록을 갱신하고 Grafana OSS를 설치한다.&lt;/p&gt;
&lt;pre id=&quot;code_1789600617581&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo apt-get update
sudo apt-get install -y grafana

sudo systemctl daemon-reload
sudo systemctl enbale --now grafana-server&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 다음 주소로 들어가서 설치를 확인할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1789600734830&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;http://localhost:3000/login&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1917&quot; data-origin-height=&quot;1043&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIn5Ya/dJMcahMDdTY/8Sd7Gwz5MqF0El6yw9PHvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIn5Ya/dJMcahMDdTY/8Sd7Gwz5MqF0El6yw9PHvK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIn5Ya/dJMcahMDdTY/8Sd7Gwz5MqF0El6yw9PHvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIn5Ya%2FdJMcahMDdTY%2F8Sd7Gwz5MqF0El6yw9PHvK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;435&quot; data-origin-width=&quot;1917&quot; data-origin-height=&quot;1043&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기 비밀번호는 아래와 같고 UI 에서 변경이 가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1789600795019&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Id: admin
Password: admin&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Prometheus 데이터 소스 추가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Node Exporter 대시보드를 추가 하기에 앞서, Grafana 가 Prometheus를 데이터 소스로 쓰기위한 설치부터 진행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사이드바에서 Connections -&amp;gt; Data sources 를 클릭한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;632&quot; data-origin-height=&quot;655&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRPNtA/dJMcajcCPEa/X26Pu9kI1RU9NtWmgN1xf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRPNtA/dJMcajcCPEa/X26Pu9kI1RU9NtWmgN1xf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRPNtA/dJMcajcCPEa/X26Pu9kI1RU9NtWmgN1xf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRPNtA%2FdJMcajcCPEa%2FX26Pu9kI1RU9NtWmgN1xf1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;454&quot; height=&quot;471&quot; data-origin-width=&quot;632&quot; data-origin-height=&quot;655&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Add new data source 버튼을 누른다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1581&quot; data-origin-height=&quot;514&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/P2Wal/dJMcabZ8jjs/zQnTCMxDbAZ7QdDqv68Rh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/P2Wal/dJMcabZ8jjs/zQnTCMxDbAZ7QdDqv68Rh1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/P2Wal/dJMcabZ8jjs/zQnTCMxDbAZ7QdDqv68Rh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FP2Wal%2FdJMcabZ8jjs%2FzQnTCMxDbAZ7QdDqv68Rh1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;260&quot; data-origin-width=&quot;1581&quot; data-origin-height=&quot;514&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Prometheus 를 검색 후 선택하고, Connection 항목에 http://localhost:9100 을 기입한뒤 save&amp;amp;test 버튼을 누른다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1010&quot; data-origin-height=&quot;632&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdcOSC/dJMcac5FUa1/JQpmRKiQg6lQKD9omJjk4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdcOSC/dJMcac5FUa1/JQpmRKiQg6lQKD9omJjk4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdcOSC/dJMcac5FUa1/JQpmRKiQg6lQKD9omJjk4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdcOSC%2FdJMcac5FUa1%2FJQpmRKiQg6lQKD9omJjk4k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;501&quot; data-origin-width=&quot;1010&quot; data-origin-height=&quot;632&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Data source 설정이 완료되면 Explore 에 들어가서 Prometheus 와 메트릭이 불러와지는 모습을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1919&quot; data-origin-height=&quot;1046&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cvblh5/dJMcafg5eMH/llnW8PMB3LSKserOcShMr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cvblh5/dJMcafg5eMH/llnW8PMB3LSKserOcShMr0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cvblh5/dJMcafg5eMH/llnW8PMB3LSKserOcShMr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcvblh5%2FdJMcafg5eMH%2FllnW8PMB3LSKserOcShMr0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;436&quot; data-origin-width=&quot;1919&quot; data-origin-height=&quot;1046&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Linux Node Exporter 대시보드 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Linux Node Exporter 의 대시보드를 만든다. 이미 남이 잘 만들어 놓은 대시보드를 그대로 가져 와서 쓰겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 사이드바에서 Dashboard 를 선택한뒤, New -&amp;gt; Import dashboard 버튼을 누른다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1919&quot; data-origin-height=&quot;1046&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xPlKn/dJMcaidUpn4/3kTKAZ0BRIskT9fcuF6FxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xPlKn/dJMcaidUpn4/3kTKAZ0BRIskT9fcuF6FxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xPlKn/dJMcaidUpn4/3kTKAZ0BRIskT9fcuF6FxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxPlKn%2FdJMcaidUpn4%2F3kTKAZ0BRIskT9fcuF6FxK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;436&quot; data-origin-width=&quot;1919&quot; data-origin-height=&quot;1046&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Import 에는&amp;nbsp; JSON을 사용하는 방식, 이미 Grafana 커뮤니티에 공개된 대시보드 id 이렇게 두 가지가 있다. 우리는 대시보드 ID 를 사용해서 Import 하겠다. 1860을 기입하고 Load를 눌러준다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1114&quot; data-origin-height=&quot;816&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbn4Ws/dJMcagfUSVG/LOOlDKMMQ8RuBwJVEIaKvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbn4Ws/dJMcagfUSVG/LOOlDKMMQ8RuBwJVEIaKvk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbn4Ws/dJMcagfUSVG/LOOlDKMMQ8RuBwJVEIaKvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbbn4Ws%2FdJMcagfUSVG%2FLOOlDKMMQ8RuBwJVEIaKvk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;586&quot; data-origin-width=&quot;1114&quot; data-origin-height=&quot;816&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 Import 버튼을 누른다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;970&quot; data-origin-height=&quot;639&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMPhwY/dJMcacdwub0/NLDdC4Idvm88BARDydoKRK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMPhwY/dJMcacdwub0/NLDdC4Idvm88BARDydoKRK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMPhwY/dJMcacdwub0/NLDdC4Idvm88BARDydoKRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMPhwY%2FdJMcacdwub0%2FNLDdC4Idvm88BARDydoKRK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;527&quot; data-origin-width=&quot;970&quot; data-origin-height=&quot;639&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 Linux Node Exporter 가 설치되고 Prometheus가 수집하기 시작한 이후의 데이터들을 대시보드에서 확인 가능하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1531&quot; data-origin-height=&quot;867&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGQzAg/dJMcaiky6VA/0N7P1U2195rZoJhbB3WIJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGQzAg/dJMcaiky6VA/0N7P1U2195rZoJhbB3WIJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGQzAg/dJMcaiky6VA/0N7P1U2195rZoJhbB3WIJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGQzAg%2FdJMcaiky6VA%2F0N7P1U2195rZoJhbB3WIJ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;453&quot; data-origin-width=&quot;1531&quot; data-origin-height=&quot;867&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 요소들의 정보는 다음에 기회가 있을 때 정리해보고자 한다. 그리고 추후에 이 3요소를 한번에 설치 가능한 Ansible 플레이북을 만들까 싶다. 분명히 도움이 될 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>인프라/홈서버</category>
      <category>리눅스</category>
      <category>우분투</category>
      <category>인프라</category>
      <category>홈 서버</category>
      <author>Hun's blog</author>
      <guid isPermaLink="true">https://developer-it.tistory.com/27</guid>
      <comments>https://developer-it.tistory.com/27#entry27comment</comments>
      <pubDate>Thu, 17 Sep 2026 08:50:12 +0900</pubDate>
    </item>
    <item>
      <title>[쿠버네티스] Nginx Gateway Fabric 정리 및 설치</title>
      <link>https://developer-it.tistory.com/26</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Nginx Gateway Fabric 개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;NGINX Gateway Fabric(NGF)&lt;/b&gt;는 Gateway API를 구현한 구현체다. Kubernetes 커뮤니티가 유지하던 ingress-nginx의 지원 종료 이후, Gateway API로 전환하기 위한 대안으로 주목받고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NGF는 &lt;b&gt;컨트롤 플레인&lt;/b&gt;과 &lt;b&gt;데이터 플레인&lt;/b&gt;으로 역할을 확실히 나누고 있다. 컨트롤 플레인은 Gateway, HTTPRoute 등 &lt;b&gt;Kubernetes 리소스를 지속적으로 감시&lt;/b&gt;하며,&amp;nbsp; 데이터 플레인에 필요한 설정과 컴포넌트를 동적으로 구성한다. 데이터 플레인은 &lt;b&gt;실제 트래픽 흐르고 처리하는 영역&lt;/b&gt;으로 Nginx 인스턴스 가 포함된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NGF는 기존 Ingress처럼 LoadBalancer나 NodePort 뒤에서 &lt;b&gt;트래픽을 라우팅&lt;/b&gt;하지만, 라우팅 규칙과 컨트롤러 설정을 한 곳에 모아두는 &lt;b&gt;모놀리식 한 구조가 아니라는 점&lt;/b&gt;에서 차이가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 NGF의 기본 개념을 간단히 살펴본 뒤, Host 라우팅 방식으로 설정하고 MetalLB와 연결하는 과정, 마지막으로 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;/etc/hosts&lt;/span&gt;&amp;nbsp;설정까지 실습해 볼 예정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Nginx Gateway Fabric 아키텍처&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 언급했듯이 NGF는 &lt;b&gt;컨트롤 플레인(Control Plane)&lt;/b&gt;과 &lt;b&gt;데이터 플레인(Data Plane)&lt;/b&gt;으로 나눌 수 있다. 인프라 담당자나 개발자가 Gateway, HTTPRoute와 같은 리소스를 적용하면, 컨트롤 플레인이 이를 지속적으로 감시하고 데이터 플레인 구성을 동적으로 반영한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트들의 요청은 데이터 플레인의 NGINX 인스턴스를 거쳐 라우팅 된다. 이 과정에서 &lt;b&gt;Gateway는 NGINX 인스턴스를 구성&lt;/b&gt;하는 역할을, &lt;b&gt;HTTPRoute는 실제 라우팅 규칙을 정의&lt;/b&gt;하는 역할을 담당한다고 보면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;nginx gateway fabric.svg&quot; data-origin-width=&quot;243&quot; data-origin-height=&quot;150&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwdn8r/dJMcadJZ3Wf/RQKyWKOdx0E1uYE5pSkAkK/tfile.svg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwdn8r/dJMcadJZ3Wf/RQKyWKOdx0E1uYE5pSkAkK/tfile.svg&quot; data-alt=&quot;출처: https://www.f5.com/company/blog/nginx/announcing-nginx-gateway-fabric-release-1-2-0&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwdn8r/dJMcadJZ3Wf/RQKyWKOdx0E1uYE5pSkAkK/tfile.svg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbwdn8r%2FdJMcadJZ3Wf%2FRQKyWKOdx0E1uYE5pSkAkK%2Ftfile.svg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;432&quot; data-filename=&quot;nginx gateway fabric.svg&quot; data-origin-width=&quot;243&quot; data-origin-height=&quot;150&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: https://www.f5.com/company/blog/nginx/announcing-nginx-gateway-fabric-release-1-2-0&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Gateway API vs Ingress&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Gateway API&lt;/b&gt;의 구현체인 &lt;b&gt;NGF&lt;/b&gt; 와 &lt;b&gt;Ingress&lt;/b&gt;의 구현체인 &lt;b&gt;ingress-nginx는&lt;/b&gt; &lt;b&gt;리소스를 작성하는 방식에서 가장 큰 차이를 보인다&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ingress 는 하나의 리소스 안에 라우팅 대상과 규칙을 함께 정의하는 방식이라, Controller가 이를 해석해 트래픽을 처리한다. 즉, 외부 요청이 어떤 서비스로 전달되어야 하는지에 대한 설정을 Ingress 리소스에 직접 작성해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 Ingress-nginx 는 아래와 같이 작성할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1788330377484&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: simple-ingress
spec:
  ingressClassName: nginx
  rules:
  - host: app.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: app-service
            port:
              number: 80&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식은 &lt;b&gt;중앙집중식으로 관리&lt;/b&gt;할 수 있다는 장점이 있다. 반면 &lt;b&gt;협업 측면&lt;/b&gt;에서는 아쉬운 부분도 있다. 개발자 측에서 라우팅 규칙이 자주 바뀌는 경우, 인프라 엔지니어가 매번 설정을 수정해야 하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 모놀리식한 구조에서는 &lt;b&gt;작은 휴먼 에러도 큰 장애&lt;/b&gt;로 이어질 수 있다. 특히 클러스터의 진입점을 다루는 설정인 만큼, 더욱 민감하게 관리할 필요가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 이유로 Gateway API 방식에서는 기능을 &lt;b&gt;여러 리소스&lt;/b&gt;로 나누었고, NGF 역시 다음과 같은 리소스 구조를 따른다.&lt;/p&gt;
&lt;pre id=&quot;code_1788330881970&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: sample-gateway
spec:
  gatewayClassName: nginx
  listeners:
  - name: http
    protocol: HTTP
    port: 80
    hostname: app.example.com
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: sample-httproute
spec:
  parentRefs:
  - name: sample-gateway
  hostnames:
  - app.example.com
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /
    backendRefs:
    - name: app-service
      port: 80&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 리소스의 역할을 나눔으로써, 인프라 엔지니어와 개발자 간의 &lt;b&gt;책임과 역할&lt;/b&gt;을 확실히 할 수 있으며, 모놀리식 방식에 따른 리스크도 줄일 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이해하기 쉽도록 다음 그림을 참고하기를 바란다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;5-things-NGINX-Kubernetes-Gateway_RBAC.png&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;1000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/G5U2X/dJMcabFrdsY/rKqJCk91kUOExX4ookxEwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/G5U2X/dJMcabFrdsY/rKqJCk91kUOExX4ookxEwk/img.png&quot; data-alt=&quot;출처: https://blog.nginx.org/blog/5-things-to-know-about-nginx-gateway-fabric&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/G5U2X/dJMcabFrdsY/rKqJCk91kUOExX4ookxEwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FG5U2X%2FdJMcabFrdsY%2FrKqJCk91kUOExX4ookxEwk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;342&quot; data-filename=&quot;5-things-NGINX-Kubernetes-Gateway_RBAC.png&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;1000&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: https://blog.nginx.org/blog/5-things-to-know-about-nginx-gateway-fabric&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Nginx Gateway Fabric 실습&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 실습은 Helm 으로 진행할 예정이며, 진입점을 위한 LoadBalancer 혹은 NodePort 가 필요하다. 필자는 MetalLB로 진행할 예정이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사전 준비&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gateway API 리소스는 쿠버네티스 표준으로 정의되어 있지 않으므로 따로 설치를 진행해야한다.&lt;/p&gt;
&lt;pre id=&quot;code_1788332817360&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl kustomize &quot;https://github.com/nginx/nginx-gateway-fabric/config/crd/gateway-api/standard?ref=v2.6.7&quot; | kubectl --kubeconfig .kube/config apply -f -&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;설치&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;helm chart를 설치하기에 앞서 Gateway 리소스 부터 정의하겠다. Gateway 를 정의하는 방법은 매니패스트를 만들고 kubectl apply 를 하든가, helm value에 gateways 배열에 기입하는 방법이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서는 후자로 진행한다.&lt;/p&gt;
&lt;pre id=&quot;code_1788333164891&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 데이터 플레인 Service 노출 방식.
# 기본값이 type: LoadBalancer 이므로 MetalLB 가 IP 를 할당한다.
# nginx:
#   service:
#     type: NodePort
#     nodePorts:
#       - port: 30080
#         listenerPort: 80

gateways:
  - name: nginx-gateway
    namespace: kube-system
    spec:
      gatewayClassName: nginx
      listeners:
        - name: http
          port: 80
          protocol: HTTP
          allowedRoutes:
            namespaces:
              from: All&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 helm chart 를 배포해 보자. OCI 레지스트리로 배포되므로 레포를 추가할 필요는 없다.&lt;/p&gt;
&lt;pre id=&quot;code_1788333231268&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;helm upgrade --install ngf oci://ghcr.io/nginx/charts/nginx-gateway-fabric `
  --version 2.6.7 `
  --namespace nginx-gateway `
  --create-namespace `
  --values helm/nginx-gateway-fabric/values.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정상적으로 설치가 되었다면 kube-system 네임스페이스와 nginx-gateway 네임스페이스에 각각 다음과 같은 파드와 서비스가 생겼을 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1628&quot; data-origin-height=&quot;341&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dMMT31/dJMcaaGGUBn/KPw9Dhy4sAL49FITki5Tc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dMMT31/dJMcaaGGUBn/KPw9Dhy4sAL49FITki5Tc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dMMT31/dJMcaaGGUBn/KPw9Dhy4sAL49FITki5Tc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdMMT31%2FdJMcaaGGUBn%2FKPw9Dhy4sAL49FITki5Tc0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;168&quot; data-origin-width=&quot;1628&quot; data-origin-height=&quot;341&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1618&quot; data-origin-height=&quot;363&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c8dCJi/dJMcac5sKbc/LZnQtkyISCuPGkkmRKzNIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c8dCJi/dJMcac5sKbc/LZnQtkyISCuPGkkmRKzNIK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c8dCJi/dJMcac5sKbc/LZnQtkyISCuPGkkmRKzNIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc8dCJi%2FdJMcac5sKbc%2FLZnQtkyISCuPGkkmRKzNIK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;179&quot; data-origin-width=&quot;1618&quot; data-origin-height=&quot;363&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스를 잘 보면&lt;b&gt; nginx-gateway-nginx&lt;/b&gt;에 외부 IP가 부여된 모습을 볼 수 있다. 즉 이 부분이 데이터 플레인이 되고 &lt;b&gt;ngf-nginx-gateway-fabric&lt;/b&gt;이 컨트롤 플레인이 되는 것으로 이해하면 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;라우팅 규칙 정의&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 까지는 외부 로드밸런서를 통해 외부에 진입점을 여는 Gateway를 만들었다면 지금부터는 &lt;b&gt;내부 서비스로의 라우팅&lt;/b&gt;을 위한 라우팅 규칙을 정의할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자의 클러스터에는 이미 Grafana 대시보드가 설치되어 있으므로 Grafana에 라우팅 규칙을 만들어서 Host 기반 라우팅을 설정할 예정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 라우팅 규칙은 애플리케이션의 영역에 가까움으로 Grafana IaC와 함께 보관하면 좋다.&lt;/p&gt;
&lt;pre id=&quot;code_1788333926479&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: grafana-route
  namespace: monitoring
spec:
  parentRefs:
  - name: nginx-gateway
    namespace: kube-system
    sectionName: http
  hostnames:
  - &quot;grafana.virtualbox.io&quot;
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /
    backendRefs:
    - name: prometheus-grafana
      port: 80&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정을 마쳤다면, HTTPRoute 리소스로 다음과 같이 설정되었을 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1636&quot; data-origin-height=&quot;223&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZ5fFK/dJMcaiklerc/QLJxiimxcfj8sPazgzfo5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZ5fFK/dJMcaiklerc/QLJxiimxcfj8sPazgzfo5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZ5fFK/dJMcaiklerc/QLJxiimxcfj8sPazgzfo5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZ5fFK%2FdJMcaiklerc%2FQLJxiimxcfj8sPazgzfo5K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;109&quot; data-origin-width=&quot;1636&quot; data-origin-height=&quot;223&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;접속 및 확인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Host 기반 라우팅으로 설정했으므로, DNS 서버 혹은 로컬 DNS 매핑이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자의 실습환경은 윈도우 11 이므로 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;C:\Windows\System32\drivers\etc&lt;/span&gt;에 '192.168.56.20 grafana.virtualbox.io'를 추가했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1914&quot; data-origin-height=&quot;1030&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBjHys/dJMcaamb9sk/UruVKdDpCgKUWvxgsU5QU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBjHys/dJMcaamb9sk/UruVKdDpCgKUWvxgsU5QU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBjHys/dJMcaamb9sk/UruVKdDpCgKUWvxgsU5QU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBjHys%2FdJMcaamb9sk%2FUruVKdDpCgKUWvxgsU5QU0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;377&quot; data-origin-width=&quot;1914&quot; data-origin-height=&quot;1030&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 접속해 보니 정상적으로 Grafana에 접근이 되었다!&lt;/p&gt;</description>
      <category>인프라/Kubernetes</category>
      <category>DevOps</category>
      <category>Infra</category>
      <category>Kubernetes</category>
      <category>NGINX Gateway Fabric</category>
      <author>Hun's blog</author>
      <guid isPermaLink="true">https://developer-it.tistory.com/26</guid>
      <comments>https://developer-it.tistory.com/26#entry26comment</comments>
      <pubDate>Wed, 2 Sep 2026 21:00:49 +0900</pubDate>
    </item>
    <item>
      <title>[쿠버네티스] MetalLB 정리 및 설치 방법</title>
      <link>https://developer-it.tistory.com/25</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;MetalLB 개요&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;MetalLB&lt;/b&gt;는 베어메탈 환경(&lt;u&gt;클라우드 환경은 거의 지원하지 않음&lt;/u&gt;)에서 클러스터 내부에 진입점을 마련하기 위한 &lt;b&gt;LoadBalancer&lt;/b&gt;이다. 주로 &lt;b&gt;Ingress Controller&lt;/b&gt; 혹은 &lt;b&gt;API Gateway&lt;/b&gt; 와 같이 배치되며, &lt;b&gt;LoadBalancer&lt;/b&gt;가 외부 트래픽을 진입시키면, &lt;b&gt;Ingress Controller&lt;/b&gt;가 분배하는 개념이다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bd5NpI/dJMcacEt1vo/ksmkNGr7klkQsmkqHrpn1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bd5NpI/dJMcacEt1vo/ksmkNGr7klkQsmkqHrpn1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bd5NpI/dJMcacEt1vo/ksmkNGr7klkQsmkqHrpn1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbd5NpI%2FdJMcacEt1vo%2FksmkNGr7klkQsmkqHrpn1k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;394&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;MetalLB&lt;/b&gt;는 두 가지 모드(L2, BGP)를 지원하는데, 두 모드의 핵심 차이는 &lt;b&gt;VIP(Virtual IP)를 외부 네트워크에 알리는 방식&lt;/b&gt;에 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;L2 모드&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;L2 모드&lt;/b&gt;는 서비스마다 speaker 하나가 리더로 뽑혀서 ARP로 응답, 그 노드로만 트래픽을 집중하는 방식이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mx9iy/dJMcacEt1v6/Dr2s7m7GQsHumxGWOiYgU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mx9iy/dJMcacEt1v6/Dr2s7m7GQsHumxGWOiYgU1/img.png&quot; data-alt=&quot;L2 모드&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mx9iy/dJMcacEt1v6/Dr2s7m7GQsHumxGWOiYgU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmx9iy%2FdJMcacEt1v6%2FDr2s7m7GQsHumxGWOiYgU1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;394&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;L2 모드&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;L2 모드 장점&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;설정이 간단 (라우터 BGP 지원 불필요)&lt;/li&gt;
&lt;li&gt;어떤 네트워크 환경에서도 동작&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;L2 모드 단점&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로드 밸런싱 안 됨&lt;/li&gt;
&lt;li&gt;리더 노드 대역폭이 병목&lt;/li&gt;
&lt;li&gt;failover 느림 (ARP 캐시 갱신 대기)&lt;/li&gt;
&lt;li&gt;클라이언트와 같은 L2 네트워크에 있어야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;BGP 모드&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; BGP 모드&lt;/b&gt;는 서비스를 서빙하는 모든 노드의 speaker가 동시에 라우터와 BGP 세션을 맺고 동일 VIP를 광고하면, 라우터가 ECMP로 트래픽을 여러 노드에 분산하는 방식이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WQ072/dJMcafVtcYq/LBQ478j9AtRrFZKBdzdZK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WQ072/dJMcafVtcYq/LBQ478j9AtRrFZKBdzdZK0/img.png&quot; data-alt=&quot;BGP 모드&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WQ072/dJMcafVtcYq/LBQ478j9AtRrFZKBdzdZK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWQ072%2FdJMcafVtcYq%2FLBQ478j9AtRrFZKBdzdZK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;394&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;BGP 모드&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;BGP 모드 장점&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로드밸런싱을 지원 (여러 노드로 트래픽 분산)&lt;/li&gt;
&lt;li&gt;failover 빠름&lt;/li&gt;
&lt;li&gt;L2 제약 없음 (다른 서브넷/라우팅 환경에서도 동작)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;BGP 모드 단점&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;라우터가 BGP/ECMP를 지원해야 함&lt;/li&gt;
&lt;li&gt;설정 복잡&lt;/li&gt;
&lt;li&gt;네트워크 팀/장비 협조 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MetalLB 설치 실습&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실습에서는 L2 방식만 설치할 예정이며, Nginx 를 띄워 접근이 되는지 까지 테스트 해볼 예정이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;설치 방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kubernetes v1.14.2 이후에 kube-proxy 를 IPVS 모드로 사용중이라면 strict ARP mode를 켜준다.&lt;/p&gt;
&lt;pre id=&quot;code_1788153167531&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl edit configmap -n kube-system kube-proxy

# 해당 부분을 추가 혹은 설정 해준다.
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: &quot;ipvs&quot;
ipvs:
  strictARP: true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;metalLB 시스템을 위한 기본 컴포넌트들을 세팅 해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1788153199173&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.16.1/config/manifests/metallb-frr-k8s.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이외에 추가적으로 VIP(Virtual IP Address)로 사용 될 IP 풀과 광고 방식을 설정하는 리소스를 만든다.&lt;/p&gt;
&lt;pre id=&quot;code_1788153244369&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
&amp;nbsp; name: sandbox
&amp;nbsp; namespace: metallb-system
spec:
&amp;nbsp; addresses:
&amp;nbsp; &amp;nbsp; - 192.168.56.20-192.168.56.30
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
&amp;nbsp; name: local
&amp;nbsp; namespace: metallb-system
spec:
&amp;nbsp; ipAddressPools:
&amp;nbsp; - sandbox&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;설치 결과&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정상 설치가 완료되면 다음과 같이 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1131&quot; data-origin-height=&quot;464&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/daBP9G/dJMcabSYHHu/Uz8SqFTmnMqII4EpdzLw8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/daBP9G/dJMcabSYHHu/Uz8SqFTmnMqII4EpdzLw8k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/daBP9G/dJMcabSYHHu/Uz8SqFTmnMqII4EpdzLw8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdaBP9G%2FdJMcabSYHHu%2FUz8SqFTmnMqII4EpdzLw8k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;328&quot; data-origin-width=&quot;1131&quot; data-origin-height=&quot;464&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 파트를 정리하면 다음과 같다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 202px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 21px;&quot;&gt;구성 요소&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 21px;&quot;&gt;개수&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 21px;&quot;&gt;리소스 타입&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 21px;&quot;&gt;주 역할&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 63px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 63px;&quot;&gt;controller&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 63px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 63px;&quot;&gt;Deployment&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 63px;&quot;&gt;&lt;span&gt;LoadBalancer Service에 외부 IP를 할당하고 MetalLB CR을 조정&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 42px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 42px;&quot;&gt;speaker&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 42px;&quot;&gt;노드 당 1&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 42px;&quot;&gt;DaemonSet&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 42px;&quot;&gt;&lt;span&gt;각 노드에서 광고 대상과 상태를 관리하고 FRR 설정을 연동&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 42px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 42px;&quot;&gt;frr-k8s-daemon&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 42px;&quot;&gt;노드 당 1&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 42px;&quot;&gt;DaemonSet&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 42px;&quot;&gt;&lt;span&gt;실제 FRR/BGP 라우팅 데몬 실행&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;frr-k8s-statuscleaner&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;Deployment&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;&lt;span&gt;FRR 관련 상태 리소스 정리&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;webhookservices&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;ClusterIpService&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;&lt;span&gt;MetalLB/FRR 설정 CR 생성&amp;middot;수정 시 검증&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;로드 밸런서 서비스 연결&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 nginx 하나 띄우고 LoadBalancer 타입으로 서비스를 만들어서 외부와 연결해보자&lt;/p&gt;
&lt;pre id=&quot;code_1788153596763&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: apps/v1
kind: Deployment
metadata:
&amp;nbsp; name: nginx-loadbalancer
spec:
&amp;nbsp; replicas: 1
&amp;nbsp; selector:
&amp;nbsp; &amp;nbsp; matchLabels:
&amp;nbsp; &amp;nbsp; &amp;nbsp; app: nginx-loadbalancer
&amp;nbsp; template:
&amp;nbsp; &amp;nbsp; metadata:
&amp;nbsp; &amp;nbsp; &amp;nbsp; labels:
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; app: nginx-loadbalancer
&amp;nbsp; &amp;nbsp; spec:
&amp;nbsp; &amp;nbsp; &amp;nbsp; containers:
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; - name: nginx
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; image: nginx:alpine
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; ports:
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
&amp;nbsp; name: nginx-loadbalancer
spec:
&amp;nbsp; type: LoadBalancer
&amp;nbsp; selector:
&amp;nbsp; &amp;nbsp; app: nginx-loadbalancer
&amp;nbsp; ports:
&amp;nbsp; &amp;nbsp; - port: 80
&amp;nbsp; &amp;nbsp; &amp;nbsp; targetPort: 80
&amp;nbsp; &amp;nbsp; &amp;nbsp; protocol: TCP&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적용 후에, nginx-loadbalancer 서비스를 보면 EXTERNAL-IP가 잡혀있는 모습을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;868&quot; data-origin-height=&quot;68&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wgbjY/dJMcahS7e5o/jdSgXhFoGjzlm1U2tgc1uK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wgbjY/dJMcahS7e5o/jdSgXhFoGjzlm1U2tgc1uK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wgbjY/dJMcahS7e5o/jdSgXhFoGjzlm1U2tgc1uK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwgbjY%2FdJMcahS7e5o%2FjdSgXhFoGjzlm1U2tgc1uK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;63&quot; data-origin-width=&quot;868&quot; data-origin-height=&quot;68&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;curl 을 통해서 nginx 에 접근이 가능한지 확인을 하고 이번 실습을 마치고자 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;865&quot; data-origin-height=&quot;480&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mZ0p2/dJMcaiECSqn/2Lwo9oeONmygJtgNzRcJ7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mZ0p2/dJMcaiECSqn/2Lwo9oeONmygJtgNzRcJ7K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mZ0p2/dJMcaiECSqn/2Lwo9oeONmygJtgNzRcJ7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmZ0p2%2FdJMcaiECSqn%2F2Lwo9oeONmygJtgNzRcJ7K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;444&quot; data-origin-width=&quot;865&quot; data-origin-height=&quot;480&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>인프라/Kubernetes</category>
      <category>DevOps</category>
      <category>인프라</category>
      <category>쿠버네티스</category>
      <author>Hun's blog</author>
      <guid isPermaLink="true">https://developer-it.tistory.com/25</guid>
      <comments>https://developer-it.tistory.com/25#entry25comment</comments>
      <pubDate>Mon, 31 Aug 2026 21:00:01 +0900</pubDate>
    </item>
    <item>
      <title>[쿠버네티스] Grafana Loki 정리</title>
      <link>https://developer-it.tistory.com/24</link>
      <description>&lt;div style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;사내 클러스터에서 로그를 중앙집중식으로 수집하기 위한 모니터링 플랫폼이 필요했다. 여러 대안 중 Loki가 괜찮은 선택지로 보여, 본격적인 도입에 앞서 사용자 관점에서 최소한으로 알아야 할 내용을 정리해보고자 한다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 글은 필자가 나중에 다시 읽었을 때 빠르게 기억을 되살릴 수 있도록 작성한 개인 맞춤형 정리 글이며, 개념 설명이나 내용에 빈틈이 있을 수 있다.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Grafana_loki_logo.png&quot; data-origin-width=&quot;538&quot; data-origin-height=&quot;318&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xOgQz/dJMcaftQRos/p8dXnoKX3RAiLQfU81iLQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xOgQz/dJMcaftQRos/p8dXnoKX3RAiLQfU81iLQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xOgQz/dJMcaftQRos/p8dXnoKX3RAiLQfU81iLQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxOgQz%2FdJMcaftQRos%2Fp8dXnoKX3RAiLQfU81iLQk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;538&quot; height=&quot;318&quot; data-filename=&quot;Grafana_loki_logo.png&quot; data-origin-width=&quot;538&quot; data-origin-height=&quot;318&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;div&gt;&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Loki 개요&lt;/b&gt;&lt;/h2&gt;
&lt;div style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Loki는 수평 확장 가능한, 고가용성, 멀티 테넌트 &lt;b&gt;로그 집계 시스템&lt;/b&gt;이다.&lt;/li&gt;
&lt;li&gt;보통 Promtail이나 Grafana Alloy와 같은 수집기를 통해 &lt;b&gt;로그를 Loki에 저장&lt;/b&gt;하고, &lt;b&gt;Grafana에서 쿼리하여 시각화&lt;/b&gt;한다.&lt;/li&gt;
&lt;li&gt;Loki는 로그 내용을 인덱싱하는 대신 라벨만 인덱싱함으로써, 효율적이고 운영이 쉬운 로그 플랫폼을 제공한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;무엇을 할 수 있는가?&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 에이전트(Promtail, Grafana Alloy)를 통해 &lt;b&gt;여러 서버, 클러스터&lt;/b&gt;에서 나오는 로그를 &lt;b&gt;하나의 Loki로&lt;/b&gt; 모을 수 있다.&lt;/li&gt;
&lt;li&gt;app, env, cluster, namespace&amp;nbsp;와 같은&lt;b&gt; 라벨을 기준으로 특정 서비스, 환경, 클러스터의 로그만 빠르게 필터링&lt;/b&gt;할 수 있다.&lt;/li&gt;
&lt;li&gt;Loki를 Grafana 데이터 소스로 붙이고 시간 단위로 쿼리해 로그를 조회할 수 있다.특정 조건(에러 등)을 만족하면 Aletermanager/Grafana Aletering 으로 알림을 받을 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;언제 도입하면 좋은가?&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;K8s 클러스터에서 로그가 노드별로 흩어져 있어, 장애 발생 시 각 서버에 접속해 로그를 찾아야 하는 환경일 때&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;K8s Pod가 재시작&amp;middot;스케일링을 자주하여, 특정 Pod의 로그를 한 번에 모아 보기가 어려울 때&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;K8s Pod 복제수를 2 이상으로 설정하여 로그가 여러 노드에 분산되어 있는 환경일 때&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;기존 ELK 사용 시 저장 비용이나 운영 복잡도 측면에서 부담을 느낄 때&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;아키텍처와 컨셉&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;loki_architecture_components.svg&quot; data-origin-width=&quot;200&quot; data-origin-height=&quot;150&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mI80D/dJMcadizp13/vngAPPcgWxJ1tfv8CzIRB0/tfile.svg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mI80D/dJMcadizp13/vngAPPcgWxJ1tfv8CzIRB0/tfile.svg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mI80D/dJMcadizp13/vngAPPcgWxJ1tfv8CzIRB0/tfile.svg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmI80D%2FdJMcadizp13%2FvngAPPcgWxJ1tfv8CzIRB0%2Ftfile.svg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;200&quot; height=&quot;150&quot; data-filename=&quot;loki_architecture_components.svg&quot; data-origin-width=&quot;200&quot; data-origin-height=&quot;150&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Distributer&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;클라이언트(에이전트)로부터 들어오는 모든 로그 push 요청을 담당하는 &lt;b&gt;쓰기 경로(write path)의 첫 엔드포인트&lt;/b&gt;이다&lt;b&gt;.&amp;nbsp;&lt;/b&gt;요청 받은 스트림을 검증하고 전처리한 뒤, 일관 해싱 (consistent hashing)으로 대상 Ingester를 선택하여 복제 계수(replication factor)만큼 병렬 전송한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Distributor의 처리 흐름은 &lt;b&gt;수신 -&amp;gt; 검증 -&amp;gt; 전처리 -&amp;gt; 처리율 제한 -&amp;gt; 대상 Ingester 선택 및 전달 -&amp;gt; 병렬 전달 -&amp;gt; 정족수 응답 확인&lt;/b&gt; 이 있다. 자세한 사항은 공식 문서를 참고하기를 바란다. &lt;a href=&quot;https://grafana.com/docs/loki/latest/get-started/components/#distributor&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식 문서&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Ingester&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span&gt;Ingester 서비스는 쓰기 경로(write path)에서는 로그 데이터를 메모리에 청크 단위로 적재하고, 압축하여 S3, GC3 등 장기 스토리지로 전송하는 역할을 한다. 동시에 읽기 경로(read path)에서는 최근에 적재된, 아직 플러시되지 않은 인메모리 로그데이터를 쿼리에 응답하기 위해 사용된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Query frontend&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span&gt;Query frontend는 Loki의 읽기 경로(read path)를 가속하기 위해 사용하는 선택적(optional) 컴포넌트로, Querier 앞단에서 쿼리 요청을 받아 분산 처리하기 쉽게 만들어준다. 내부적으로는 쿼리를 큐에 적재하고, 필요한 경우 여러 구간으로 분할한 뒤, Querier가 이를 병렬로 처리할 수 있도록 중개한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Querier&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span&gt;Querier 서비스는 Loki에서 LogQL(Log Query Language) 쿼리를 실제로 실행하는 컴포넌트로, 읽기 경로(read path)의 핵심에 해당한다. Querier는 구성 방식에 따라 클라이언트의 HTTP 요청을 직접 받아 처리하거나, Query frontend/Query scheduler로부터 전달된 서브쿼리를 워커(worker)처럼 실행한 뒤 결과를 반환한다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;실행 모드&lt;/b&gt;&lt;/h2&gt;
&lt;div style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;
&lt;div&gt;&lt;span&gt;Loki는 규모에 따라 배포 구조를 다르게 가져갈 수 있다. 처음 도입할 때는 Monolithic으로 시작하고, 로그량이나 사용자가 늘어나면 SSD로 옮기는 흐름이 일반적이다.&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Monolithic (SingleBinary)&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Distributor, Ingester, Querier 등 Loki 컴포넌트를 하나의 프로세스/Pod로 실행하는 방식이다.&lt;/li&gt;
&lt;li&gt;설정과 운영이 가장 단순하다. 개발 환경이나 로그량이 많지 않은 사내 클러스터에 적합하다.&lt;/li&gt;
&lt;li&gt;Pod 하나에 장애가 나면 Loki 전체가 영향을 받으므로 고가용성 구성에는 맞지 않는다. 이 문서의 Helm 설치 예시는 이 방식을 기준으로 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt; &lt;span&gt;SSD (Simple Scalable Deployment)&lt;/span&gt; &lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컴포넌트를 `read`, `write`, `backend` 세 영역으로 나눠 배포하는 방식이다.&lt;/li&gt;
&lt;li&gt;쓰기량이 많으면 `write`를, 조회 요청이 많으면 `read`를 각각 늘릴 수 있어 Monolithic보다 확장하기 쉽다.&lt;/li&gt;
&lt;li&gt;데이터를 오브젝트 스토리지에 저장하는 구성을 전제로 하며, 운영 환경에서 가장 먼저 고려할 만한 배포 방식이다.&lt;/li&gt;
&lt;li&gt;다만 저장소 설정과 운영할 Pod 수가 늘어나므로, 작은 환경에서는 Monolithic이 더 편할 수 있다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>인프라/Kubernetes</category>
      <category>DevOps</category>
      <category>k8s</category>
      <category>인프라</category>
      <category>쿠버네티스</category>
      <author>Hun's blog</author>
      <guid isPermaLink="true">https://developer-it.tistory.com/24</guid>
      <comments>https://developer-it.tistory.com/24#entry24comment</comments>
      <pubDate>Tue, 21 Jul 2026 18:55:58 +0900</pubDate>
    </item>
    <item>
      <title>[쿠버네티스] Longhorn 정리</title>
      <link>https://developer-it.tistory.com/23</link>
      <description>&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Longhorn 개요&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;LongHorn은 가볍고, 신뢰성 있고, 쉽게 사용할 수 있는 쿠버네티스 기반 분석 블럭 스토리지이다.&lt;/li&gt;
&lt;li&gt;Rancher Labs 라는 회사에서 만들어 졌으나 현재는 CNCF에 기증되어 incubating 프로젝트이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;longhorn-stacked-color.png&quot; data-origin-width=&quot;1556&quot; data-origin-height=&quot;767&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DF8XK/dJMcadJiRSo/hdBIHEHMiet1yrsvEryiC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DF8XK/dJMcadJiRSo/hdBIHEHMiet1yrsvEryiC0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DF8XK/dJMcadJiRSo/hdBIHEHMiet1yrsvEryiC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDF8XK%2FdJMcadJiRSo%2FhdBIHEHMiet1yrsvEryiC0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;528&quot; height=&quot;260&quot; data-filename=&quot;longhorn-stacked-color.png&quot; data-origin-width=&quot;1556&quot; data-origin-height=&quot;767&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;무엇을 할 수 있는가?&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 노드의 로컬 디스크를 하나의 풀로 관리하며, 데이터를 여러 노드에 복제할 수 있다.&lt;/li&gt;
&lt;li&gt;노드에 데이터가 replica 되어 노드로 인한 단일 실패 지점을 해소 (고가용성)&lt;/li&gt;
&lt;li&gt;NFS 혹은 S3를 통해 데이터를 백업할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;언제 도입하면 좋은가?&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스토리지 관련 리소스가 없을 때 (클라우드, SAN 포함)&lt;/li&gt;
&lt;li&gt;온프레미스 환경에서 중소규모로 스토리지 환경이 필요할 때&lt;/li&gt;
&lt;li&gt;동일한 데이터의 다중 접근이 드문 상황일 때 (다중 접근을 위해 블록 스토리지 위에 NFS 레이어를 얹기 때문에 성능 저하 발생)&lt;/li&gt;
&lt;li&gt;간편하고 신속한 백업 및 재해 복구가 필요할때 (간편하게 nfs 혹은 s3 저장소에 볼륨 백업 가능)&lt;/li&gt;
&lt;li&gt;쿠버네티스 네이티브한 스토리지 관리를 원할 때 (쿠버네티스의 동적 프로비저닝을 통해 볼륨 생성 및 사용 가능)&lt;/li&gt;
&lt;li&gt;Local PVC 를 사용하자니 노드 장애로 인한 데이터 유실 및 가용성 저하가 우려될 때&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Local PVC vs Longhorn 장단점 비교&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Longhorn의 장점&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;노드 장애 시 데이터 자동 복구: &lt;/b&gt;Local PVC는 해당 노드가 죽으면 파드가 다른 노드로 이동해도 데이터에 접근할 수 없음&lt;/li&gt;
&lt;li&gt;&lt;b&gt;자유로운 파드 스케줄링&lt;/b&gt;: Local PVC는 특정 파드에 종속되지만, Longhorn은 어느 노드에서나 자유롭게 띄우고 이동가능&lt;/li&gt;
&lt;li&gt;&lt;b&gt;편리한 운영 기능 기본 제공: &lt;/b&gt;볼륨 크기 자동 확장, 스냅샷 생성, 외부 저장소 백업 등 기능 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Longhorn의 단점&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;I/O 성능저하&lt;/b&gt;: Longhorn 은 데이터 쓰기 동작 시 모든 레플리카에게 동기적으로 적용함으로 I/O 성능이 Local PVC 보다 저하됨&lt;/li&gt;
&lt;li&gt;&lt;b&gt;추가 리소스 소모&lt;/b&gt;: 분산 스토리지 유지를 위한 CPU, 메모리를 일정 부분 소모&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;아키텍처와 컨셉&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Longhorn은 크게 data plane과 control plane으로 나눌 수 있는데, Longhorn Engine이 data plane에 해당하고 Longhorn Manager가 control plane에 해당한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1232&quot; data-origin-height=&quot;691&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHmZdd/dJMcadimJmD/wgpy1dIlSvnkDWDQjccsf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHmZdd/dJMcadimJmD/wgpy1dIlSvnkDWDQjccsf0/img.png&quot; data-alt=&quot;Longhorn Data read/write Flow&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHmZdd/dJMcadimJmD/wgpy1dIlSvnkDWDQjccsf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHmZdd%2FdJMcadimJmD%2Fwgpy1dIlSvnkDWDQjccsf0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1232&quot; height=&quot;691&quot; data-origin-width=&quot;1232&quot; data-origin-height=&quot;691&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Longhorn Data read/write Flow&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Longhorn CSI Plugin&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;쿠버네티스의 동적 프로비저닝 기능을 Longhorn과 연동 시켜주는 플러그인이다.&lt;/li&gt;
&lt;li&gt;사용자가&amp;nbsp; &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;i&gt;storageClassName: longhorn&lt;/i&gt;&lt;/span&gt; 으로 PVC를 만들면 longhorn 과 연동시킨다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Longhorn Manager&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 노드에 하나씩 상주하며 Longhorn 클러스터 전체를 제어하는 데몬셋이다.&lt;/li&gt;
&lt;li&gt;CSI Plugin 혹은 UI에 의한 요청에 맞춰 Longhorn ㅂ로륨 리소스 생성을 지시한다.&lt;/li&gt;
&lt;li&gt;볼륨 생성이 확정되면 Engine(컨트롤러)과 Replica(복제본)을 띄우도록 지시한다.&lt;/li&gt;
&lt;li&gt;사용자가 접속하는 Longhorn UI 나 쿠버네티스의 CSI 플러그인이 보내는 명령을 가장 앞단에서 받아 처리한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Longhorn Engine&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;볼륨 1개 만을 전담 관리하는 스토리지 컨트롤러이다.&lt;/li&gt;
&lt;li&gt;애플리케이션 파드에 있는 노드에 가상의 블록 디바이스를 만들어 노출하여 kubelet 이 애플리케이션 파드와 바인드 마운트 해준다.&lt;/li&gt;
&lt;li&gt;애플리케이션에 의해 볼륨에 쓰기(Write) 동작이 발생하면 Replica들에게 동기적으로 복제한다.&lt;/li&gt;
&lt;li&gt;특정 노드의 Replica에 장애가 발생해도 다른 노드의 정상 Replica를 통해 애플리케이션에 중단 없이 고가용성을 제공한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Longhorn Replica&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;볼륨의 데이터를 노드의 물리적 디스크에 저장한다.&lt;/li&gt;
&lt;li&gt;Longhorn Engine 에게 물리적인 데이터를 제공한다.&lt;/li&gt;
&lt;li&gt;데이터 고가용성을 위해 설정된 복제본 개수(예: 3개)만큼 여러 노드에 분산되어 생성된다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>인프라/Kubernetes</category>
      <category>k8s</category>
      <category>longhorn</category>
      <category>인프라</category>
      <category>쿠버네티스</category>
      <author>Hun's blog</author>
      <guid isPermaLink="true">https://developer-it.tistory.com/23</guid>
      <comments>https://developer-it.tistory.com/23#entry23comment</comments>
      <pubDate>Sun, 5 Jul 2026 15:33:28 +0900</pubDate>
    </item>
    <item>
      <title>[트러블 슈팅] Docker 가 의문사 했다?</title>
      <link>https://developer-it.tistory.com/22</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;사내 서비스를 운영 중인데 Docker 컨테이너가 죽어버렸다. 원인을 파악 해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;946&quot; data-origin-height=&quot;61&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dqwSax/dJMb99NwLOZ/MaMcQcaFqxNQfroMDPyNTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dqwSax/dJMb99NwLOZ/MaMcQcaFqxNQfroMDPyNTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dqwSax/dJMb99NwLOZ/MaMcQcaFqxNQfroMDPyNTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdqwSax%2FdJMb99NwLOZ%2FMaMcQcaFqxNQfroMDPyNTk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;946&quot; height=&quot;61&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;946&quot; data-origin-height=&quot;61&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;docker cli&lt;/b&gt;는 정상으로 보이나 &lt;b&gt;docker.sock&lt;/b&gt;에 연결이 안 되는 걸로 봐서 &lt;b&gt;docker daemon&lt;/b&gt;에 문제가 생긴것으로 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;942&quot; data-origin-height=&quot;113&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bw1gpX/dJMcadvANdL/KJuKgEuzvkhY3Mks6yKE2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bw1gpX/dJMcadvANdL/KJuKgEuzvkhY3Mks6yKE2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bw1gpX/dJMcadvANdL/KJuKgEuzvkhY3Mks6yKE2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbw1gpX%2FdJMcadvANdL%2FKJuKgEuzvkhY3Mks6yKE2K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;942&quot; height=&quot;113&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;942&quot; data-origin-height=&quot;113&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확실히 이상하다 &lt;b&gt;Docker CLI&lt;/b&gt;는 있고 버전 확인도 되는데 서비스 인식이 불가능한 상황이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;dpkg&lt;/b&gt; 로 확실히 docker 와 관련된 패키지들을 확인해 봐야겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;938&quot; data-origin-height=&quot;397&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vmRfc/dJMcaasdPWW/k3txpGZeFOum801zqtCkzK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vmRfc/dJMcaasdPWW/k3txpGZeFOum801zqtCkzK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vmRfc/dJMcaasdPWW/k3txpGZeFOum801zqtCkzK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvmRfc%2FdJMcaasdPWW%2Fk3txpGZeFOum801zqtCkzK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;938&quot; height=&quot;397&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;938&quot; data-origin-height=&quot;397&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;눈에 띄는 건 &lt;b&gt;containerd.io&lt;/b&gt;와 &lt;b&gt;docker-ce&lt;/b&gt;가 &lt;b&gt;rc&lt;/b&gt; 상태라는 점이다. rc는 패키지는 제거됐지만 설정 파일은 남아있다는 뜻으로, 어떤 이유에서인지 이 두 패키지가 삭제된 것으로 확인된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 containerd는 컨테이너 런타임인데, Docker는 이를 직접 패키징해서 containerd.io라는 이름으로 따로 배포하고 있다. Ubuntu 공식 저장소의 containerd와 소프트웨어 자체는 동일하지만 패키징 주체가 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;944&quot; data-origin-height=&quot;381&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCZBXK/dJMcabdu6GE/y34xKGU1drTMhVxUsoOgvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCZBXK/dJMcabdu6GE/y34xKGU1drTMhVxUsoOgvK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCZBXK/dJMcabdu6GE/y34xKGU1drTMhVxUsoOgvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCZBXK%2FdJMcabdu6GE%2Fy34xKGU1drTMhVxUsoOgvK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;944&quot; height=&quot;381&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;944&quot; data-origin-height=&quot;381&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결 방법은 너무 간단하다 docker-ce 를 다시 설치해주면 되고 정상 동작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;93&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ckJjvF/dJMb9909EhG/F9S5NbmT1cKkwDTwkPxK10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ckJjvF/dJMb9909EhG/F9S5NbmT1cKkwDTwkPxK10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ckJjvF/dJMb9909EhG/F9S5NbmT1cKkwDTwkPxK10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FckJjvF%2FdJMb9909EhG%2FF9S5NbmT1cKkwDTwkPxK10%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;940&quot; height=&quot;93&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;93&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 &lt;b&gt;containerd.io&lt;/b&gt; 와 &lt;b&gt;docker-ce&lt;/b&gt; 가 삭제된 이유를 알지못하면 반복될 수 있으니 원인을 제대로 파악해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 apt 패키지 매니저의 히스토리 로그를 열어서 docker-ce와 관련된 변경이 있는지 확인하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;931&quot; data-origin-height=&quot;264&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cs8lQe/dJMb9909EtA/jHF24Li87CyN1QN0QTz3K0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cs8lQe/dJMb9909EtA/jHF24Li87CyN1QN0QTz3K0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cs8lQe/dJMb9909EtA/jHF24Li87CyN1QN0QTz3K0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcs8lQe%2FdJMb9909EtA%2FjHF24Li87CyN1QN0QTz3K0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;931&quot; height=&quot;264&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;931&quot; data-origin-height=&quot;264&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그를 보면 &lt;b&gt;containerd&amp;nbsp;&lt;/b&gt;가 설치되면서 &lt;b&gt;containerd.io&lt;/b&gt; 와 &lt;b&gt;docker-ce&lt;/b&gt; 가 함께 제거됐다. 최근 해당 서버를 &lt;b&gt;K8s&lt;/b&gt;에 편입하는 작업을 준비하면서 컨테이너 런타임으로&lt;b&gt; containerd&lt;/b&gt; 를 설치했고, 이 과정에서 &lt;b&gt;apt&lt;/b&gt;가 충돌 패키지인 &lt;b&gt;containerd.io&lt;/b&gt; 를 자동으로 제거했으며 이제 의존하던 &lt;b&gt;docker-ce&lt;/b&gt; 까지 연쇄적으로 삭제된 것으로 추측된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3줄 요약&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt; docker ps&lt;/b&gt; 실패 원인을 추적한 결과,&lt;b&gt; docker-ce&lt;/b&gt;와 &lt;b&gt;containerd.io&lt;/b&gt; 패키지가 제거된 상태(&lt;b&gt;rc&lt;/b&gt;)였다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt; apt history&lt;/b&gt; 로그에서 &lt;b&gt;containerd&lt;/b&gt; 설치 시 충돌로 인해 apt가 &lt;b&gt;containerd.io&lt;/b&gt;와 &lt;b&gt;docker-ce&lt;/b&gt;를 자동 제거한 것이 확인됐다.&lt;/li&gt;
&lt;li&gt;해당 서버의 Kubernetes 편입 작업 중 컨테이너 런타임 교체가 원인으로 추측된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>인프라/Docker</category>
      <category>Docker</category>
      <category>linux</category>
      <category>도커</category>
      <category>리눅스</category>
      <author>Hun's blog</author>
      <guid isPermaLink="true">https://developer-it.tistory.com/22</guid>
      <comments>https://developer-it.tistory.com/22#entry22comment</comments>
      <pubDate>Sat, 20 Jun 2026 16:43:40 +0900</pubDate>
    </item>
    <item>
      <title>[파이썬] GIL: 무엇이고, 왜 필요하고, 왜 못 없애나</title>
      <link>https://developer-it.tistory.com/21</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;GIL 이미지.png&quot; data-origin-width=&quot;2510&quot; data-origin-height=&quot;1287&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xNvbM/dJMcafmwUKC/5cVvpJ3FAKRpX6okSKxKhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xNvbM/dJMcafmwUKC/5cVvpJ3FAKRpX6okSKxKhk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xNvbM/dJMcafmwUKC/5cVvpJ3FAKRpX6okSKxKhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxNvbM%2FdJMcafmwUKC%2F5cVvpJ3FAKRpX6okSKxKhk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2510&quot; height=&quot;1287&quot; data-filename=&quot;GIL 이미지.png&quot; data-origin-width=&quot;2510&quot; data-origin-height=&quot;1287&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬을 처음 배울 때&amp;nbsp; 파이썬은 멀티 스레드를 사용해도 병렬처리가 되지 않는다고 배웠다. 이것을 &lt;b&gt;GIL이라고&lt;/b&gt; 배웠고, 단순히 인터프리터가 멀티스레드 환경에서 한 개의 스레드만 사용하도록 강제했고 우회할 대안으로는 &lt;b&gt;multiprocessing&lt;/b&gt; 라이브러리를 사용해서 &lt;b&gt;멀티 프로세스&lt;/b&gt;로 접근하라고 외웠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서 파이썬을 다루면서 실제 내부 구조를 깊게 파악하고 싶다는 호기심이 생겼다. 특히 최근에 화두가 되고 있는 파이썬의 GIL 제거 시도부터 공부하면서 파이썬 내부를 분석해보고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스트에서는 GIL의 &lt;b&gt;정의와 역사, 그리고 초기 제거 시도&lt;/b&gt;에 대해서 다루고자 한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;GIL(Global Interpreter Lock) 이란?&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;인터프리터 프로세스가 멀티 스레드 환경에서 하나의 스레드에게만 Lock을 부여하고 나머지는 대기시키는 방식&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;GIL이 필요한 이유&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파이썬 인터프리터인 CPython은 &lt;b&gt;참조 횟수(Reference Count)&lt;/b&gt;를 통해 메모리를 정리&lt;/li&gt;
&lt;li&gt;특정 객체(문자열, 튜플, 리스트 등)가 변수에 할당되거나, 다른 함수 혹은 메소드의 인자로 전달되면 참조 + 1, 해제되면 -1 되는 &lt;b&gt;참조 횟수 증감 연산&lt;/b&gt;을 하고 있음&lt;/li&gt;
&lt;li&gt;참조 횟수가 0이 될 경우, &lt;b&gt;객체를 메모리에서 제거&lt;/b&gt;(free)&lt;/li&gt;
&lt;li&gt;하지만 참조 횟수는 멀티 스레드 환경에서 동시 접근에 의해 &lt;b&gt;경쟁 상태(Race Condition)&lt;/b&gt;에 빠질 수 있으며 각 스레드 참조 횟수의 &lt;b&gt;정합성을 훼손&lt;/b&gt;할 수 있음&lt;/li&gt;
&lt;li&gt;그래서 멀티 스레드 상황에서도 &lt;b&gt;인터프리터당 하나의 스레드&lt;/b&gt;가 동작하도록 Lock을 부여하여 동시 접근을 방지함&lt;/li&gt;
&lt;li&gt;이 외에도, 멀티스레드 환경에서 동기 접근에 안전한 &lt;b&gt;Thread-Safe&lt;/b&gt; 한 조건을 요구하는 C Extension을 만족시키기 위함&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;GIL을 도입할 수 밖에 없는 현실적인 이유&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파이썬이 첫 개발 되던 시절에는 &lt;b&gt;단일 코어&lt;/b&gt; 중심의 하드웨어었고, 운영 체제 차원에서도 스레드라는 개념을 지원하지 않거나, 지원하더라도 구현 방식이 달랐음&lt;/li&gt;
&lt;li&gt;파이썬의 주요 기능들은 &lt;b&gt;C 언어로 구현되거나 C 라이브러리&lt;/b&gt;를 가져와서 사용&lt;/li&gt;
&lt;li&gt;C Extension은 Thread-Safe 한 메모리 관리를 요구했고 GIL이 &lt;b&gt;가장 적합&lt;/b&gt;하여 적용&lt;/li&gt;
&lt;li&gt;시간이 지나 멀티 스레드 기술이 성숙해지고 하드웨어 사양이 충분해졌을 때, GIL로 인한 문제가 수면 위로 드러남&lt;/li&gt;
&lt;li&gt;하지만 오랜 기간 동안 쌓인 레거시들의 하위 호환성을 고려했을 때 GIL을 제거하고 다른 방안을 도입하기 어려운 상황이 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;GIL이 파이썬에게 의미있는 도입일 수도 있는 이유&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Python이 개발 되던 당시 주류 언어는 C언어&lt;/li&gt;
&lt;li&gt;Python이 GIL을 통해 C Extension을 사용할 수 있게 되고, 기존 C를 사용하던 개발자들이 Python으로 &lt;b&gt;쉽게 넘어올 수&lt;/b&gt; 있도록 만듬&lt;/li&gt;
&lt;li&gt;결과적으로 Python 커뮤니티는 성장할 수 있었고, 현재 AI, 데이터 분야에 대표적인 언어로 정착&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;GIL을 제거하려는 노력&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GIL을 제거하려는 노력은 꾸준히 있어왔다. 크게는 1996년 Greg Stein의 free-threading 패치부터 PEP 703, PEP 779, PEP 803 같이 현재에도 시도를 하고 있지만 완전한 제거는 이루지 못했고, 선택적인 비활성화 수준의 기능을 제공하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 &lt;b&gt;Greg Stein의 free-threading(1996)와&lt;/b&gt; &lt;b&gt;Larry Hasting의 Gilectomy(2016)을&lt;/b&gt; 먼저 다루고, 이후 포스팅에서 실제로 파이썬 운영위원회(Steering Council)에서 승인받은 PEP703와 이후 최신 현황에 대해서 다룰 예정이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Greg Stein의 free-threading&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1996년 Greg Stein 은 Python 1.4 버전을 기반으로 GIL을 제거하고 멀티스레딩을 구현하기 위한 최초의 'free-threading' 패치를 시도했다. 그는 인터프리터 전체를 묶고 있던 거대한 Lock을 없애는 대신 다음과 같은 구조적 변화를 제안했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;핵심 아키텍처&lt;/b&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;스레드 상태 격리&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인터프리터의 전역 상태를 스레드의 독립된 상태로 편입&lt;/li&gt;
&lt;li&gt;스레드 상태를 연결 리스트로 연결&lt;/li&gt;
&lt;li&gt;스레드 상태를 조회 시, 리스트를 순회하며 스레드 ID를 탐색하고 리스트 맨 앞으로 이동시킨 후에 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;참조 횟수 연산의 세분화된 자금 (Fine-Grained Locking)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;참조 횟수 증감연산을 할 때 Lock을 부여&lt;/li&gt;
&lt;li&gt;멀티 스레드 환경에서 동시 작업이 가능하지만 동시에 증감연산은 금지&lt;/li&gt;
&lt;li&gt;A 스레드가 I 객체의 참조 횟수를 증감연산 할 때 B 스레드가 J 객체의 참조 횟수를 증감 불가&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;가변(mutable) 객체들의 잠금&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;참조 횟수 연산에서는 전역 자물쇠를 사용하지만, 리스트나 딕셔너리 같은 가변 객체들은 개별적으로 Lock 부여&lt;/li&gt;
&lt;li&gt;중첩 리스트에서는 Lock 해제를 재귀적으로 수행&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;결과는 처참했다. 단일 스레드 작업은 &lt;b&gt;1.9초에서 12.7초&lt;/b&gt;로, 멀티스레드 작업은&lt;b&gt; 2.5초에서 18.5초&lt;/b&gt;로 늘어나며 기존 대비 약 6~7배의 성능 저하(속도 기준 85% 감소)를 기록했다 &lt;/span&gt;&lt;/span&gt;&lt;a style=&quot;color: #0070d1; text-align: start;&quot; href=&quot;https://dabeaz.blogspot.com/2011/08/inside-look-at-gil-removal-patch-of.html&quot;&gt;[1]&lt;/a&gt;. 성능이 급감한 결정적인 이유는 참조 횟수를 증감할 때마다 스레드별로 뮤텍스(Mutex) Lock을 매번 수행하면서 발생한 오버헤드 때문이었다. 비록 패치는 실패로 끝났지만, 이 시도를 계기로 파이썬 커뮤니티에는&lt;b&gt; 'GIL을 제거하더라도 싱글 스레드의 성능을 희생시켜서는 안 된다'&lt;/b&gt;라는 철칙이 깊게 새겨지게 된다.&quot;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Larry Hasting의 Gilectomy&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2016년 Larry Hasting은 Pycon에서 Gilectomy를 소개했다(Gilectomy는 GIL + ectomy (절제술)을 합친 합성어). 기존의 Greg Stein이 참조 횟수 연산을 할 때 모든 스레드가 접근 가능하되, 연산 시에 Lock을 부여하는 것과 달리, 한 스레드에 모든 참조 증감 연산의 책임을 위임하는 아이디어를 제안했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;핵심 아키텍처&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Buffered reference counting&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하나의 스레드에 참조 횟수 증감 연산의 책임을 위임&lt;/li&gt;
&lt;li&gt;다른 스레드들은 자신의 상태에 증감 연산 로그를 추가&lt;/li&gt;
&lt;li&gt;증감 연산 담당 스레드가 모든 스레드의 로그를 취합하여 참조 횟수 연산을 수행&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;특이사항&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;처음에는 한 스레드에 증감 연산과 로그 상태 보관을 담당시켜, 다른 스레드들이 직접 로그를 작성시키려 함&lt;/li&gt;
&lt;li&gt;이 과정에서 스레드끼리 쓰기 경쟁이 발생하여, 각 스레드에 로그를 분산 시킴&lt;/li&gt;
&lt;li&gt;분산된 로그는 타임스탬프가 없어서 취합 시에 순서 문제가 발생&lt;/li&gt;
&lt;li&gt;특히, 감소 -&amp;gt; 증가 환경에서는 문제가 심각 &lt;b&gt;(0 -&amp;gt; -1 -&amp;gt; 0)&lt;/b&gt; &amp;lt;- 이미 객체가 삭제됨&lt;/li&gt;
&lt;li&gt;그래서 모든 로그들 중 증가 연산을 먼저 수행하도록 수정&lt;/li&gt;
&lt;li&gt;이 방법은 참조 횟수의 실시간성을 보장하지 못한다는 단점이 존재&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성능은 기존 CPython에 근접했다고 주장했으나 이를 뒷받침할 벤치마크 근거는 없었다. 게다가 C API와의 호환성 문제로 귀도 반 로섬과 의견 대립을 겪으며 결국 채택되지 못했다. 하지만 이 제안은 CPython 내부의 병목 지점을 구체화하고, C API 구조 개편의 필요성을 대두시키는 계기가 되었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;다음 글 예고&lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;2,0&quot; data-ke-size=&quot;size16&quot;&gt;Greg Stein과 Larry Hasting의 시도는 비록 반려되었지만, 파이썬 내부의 병목 지점을 확인하고 C API 개편의 필요성을 공론화하는 중요한 밑거름이 되었다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;2,0&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;2,1&quot; data-ke-size=&quot;size16&quot;&gt;다음 글에서는 이들의 한계를 극복하고 마침내 파이썬 표준에 채택된 &lt;b data-index-in-node=&quot;37&quot; data-path-to-node=&quot;2,1&quot;&gt;PEP 703&lt;/b&gt;과 최신 No-GIL 동향을 다룬다. 과연 어떤 기술적 돌파구를 통해 싱글 스레드의 성능 저하 없이 GIL을 선택적으로 비활성화할 수 있었는지, 그 핵심 아키텍처를 자세히 알아볼 예정이.&lt;/p&gt;</description>
      <category>파이썬/GIL 제거 동향</category>
      <category>gil</category>
      <category>It</category>
      <category>PYTHON</category>
      <category>개발</category>
      <category>개발자</category>
      <category>파이썬</category>
      <author>Hun's blog</author>
      <guid isPermaLink="true">https://developer-it.tistory.com/21</guid>
      <comments>https://developer-it.tistory.com/21#entry21comment</comments>
      <pubDate>Mon, 1 Jun 2026 10:21:26 +0900</pubDate>
    </item>
    <item>
      <title>[회고] 사용자가 아닌 나를 위한 설계였다</title>
      <link>https://developer-it.tistory.com/20</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스트는 웹 서비스를 유지보수하다 든 생각을 주저리주저리 적어볼 생각이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 서비스를 개발하고 운영하다 보면, 기획 단계에서 '많이 쓰이겠지' 싶었던 기능이 막상 출시 후엔 거의 사용되지 않는 경우가 있다. 그렇다고 없애버리기엔 가뭄에 콩 나듯 찾는 사용자가 있어, 애매하게 살아남은 기능들이 생기기 마련이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스에는 두 가지 동작 방식이 있는데, 데이터베이스에 연동해 결과를 저장하고 관리하는 &lt;b&gt;DB 모드&lt;/b&gt;와, 별도 저장 없이 사용 후 휘발되는 &lt;b&gt;Standalone 모드&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제의 기능은 Standalone 모드에만 존재했는데, &lt;b&gt;이 기능을 쓰려고 Standalone을 켜는 수준&lt;/b&gt;이었다. 이 기능 하나를 위해 4~5개의 페이지를 따로 만들고, API 쿼리 파라미터 분기 처리도 하면서 기술 부채도 심했다. 결국 Standalone 모드를 deprecated하고, 해당 기능을 DB 모드에 편입하기로 팀내 결정되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 다음이었는데 기존 DB는 애초에 이 기능을 크게 고려하지 않고 설계된 터라, 사용량도 많지 않은 기능 하나를 위해 &lt;b&gt;DB를 준-재설계 수준으로 손봐야 할지, 아니면 로직으로 어떻게든 온몸 비틀기를 해야 할지&lt;/b&gt; 고민을 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 방식의 트레이드오프는 명확했다. DB 테이블을 수정하면 기존 DB 모드에서 제공하는 서드파티 기능들을 추가로 활용할 수 있지만, &lt;b&gt;변경 범위가 코드베이스의 절반에 가까울 만큼 방대했다.&lt;/b&gt; 반면 로직으로 온몸 비틀기를 택하면 코드량은 줄일 수 있지만, &lt;b&gt;서드파티 연동이 불가능해져 미래 요구사항에 유연하게 대응하기 어려워진다.&lt;/b&gt; 머릿속으로는 트레이드오프를 정리해두고 있었지만, 막상 결론을 내리려니 쉽지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그때 팀장님이 한 가지 조언을 해주셨다. &lt;b&gt;실제 사용자에게 직접 물어보라&lt;/b&gt;는 것이었다. 이에 따라 현 사용자들을 대상으로 간단한 설문을 진행했다. 기능을 앞으로도 사용할 의향이 있는지, DB 모드로 편입될 경우 사용 빈도가 늘어날 것 같은지, 서드파티 기능에 대한 수요가 있는지를 물었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과는 서드파티 기능에 대한 니즈는 아예 없었고, &lt;b&gt;사용자들이 가장 기대하는 건 이 기능이 DB 모드로의 편입, 그 자체였다.&lt;/b&gt; 설문 전까지만 해도 '확장성'과 '사용성'을 명분 삼아 DB 테이블을 수정하는 쪽으로 마음이 거의 기울어 있었는데, 그 선택이 &lt;b&gt;아무도 쓰지 않는 거대하고 복잡한 서비스로 이어질 뻔했다&lt;/b&gt;는 걸 깨달았을 때 적잖이 충격이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;돌이켜보면, 그 결정의 이면에는 주니어로서 DB 스키마 수정 같은 굵직한 작업을 경험해보고 싶다는 욕심이 있었던 것 같다. '확장성'과 '사용성'이라는 그럴듯한 명분을 내세웠지만, &lt;b&gt;그 밑에는 그냥 해보고 싶다는 개인적인 동기가 깔려 있었다.&lt;/b&gt; 사용자가 원하는 것과 무관한 이유로 기술적 방향을 정하려 했던 셈이다. &lt;b&gt;합리적인 의사결정과는 거리가 멀었다.&lt;/b&gt; 뭐, 그런 기회는 나중에 더 적절한 때가 오겠지.&lt;/p&gt;</description>
      <category>회고</category>
      <author>Hun's blog</author>
      <guid isPermaLink="true">https://developer-it.tistory.com/20</guid>
      <comments>https://developer-it.tistory.com/20#entry20comment</comments>
      <pubDate>Tue, 26 May 2026 17:41:25 +0900</pubDate>
    </item>
    <item>
      <title>[자바스크립트] Math 객체</title>
      <link>https://developer-it.tistory.com/19</link>
      <description>&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;실무에서 React를 다루며 관련 문법은 익숙해졌으나, 자바스크립트 기본기는 등한시했음을 느꼈다.&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;AI 생성 코드를 리뷰할 때도 React나 JSX보다 자바스크립트 로직에서 병목이 발생했다.&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이를 보완하고자 프로그래머스 기초 문제를 풀며 자바스크립트 구현 스킬을 향상하려 한다. 앞으로 문제 풀이와 타인의 코드를 참고해 유용한 함수와 메소드를 기록할 예정이며, 이번 편에서는&lt;span&gt; &lt;/span&gt;&lt;b&gt;Math 객체&lt;/b&gt;를 정리한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 style=&quot;text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 올림&amp;middot;내림&amp;middot;반올림 (소수점 처리)&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Math.ceil(x)&lt;/b&gt;: 소수점 아래를 올림&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Math.floor(x)&lt;/b&gt;: 소수점 아래를 내림 &lt;b&gt;(음수를 다룰 때, Math.floor(-12.4) =&amp;gt; -13)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Math.trunc(x)&lt;/b&gt;: 소수점 아래 버림 &lt;b&gt;(음수를 다룰 때, Math.trunc(-12.4) =&amp;gt; -12)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Math.round(x)&lt;/b&gt;: 소수점 아래를 반올림 (단, y째 소수점에서 반올림은 지원하지 않음)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1779695535571&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;console.log(Math.ceil(10.15))
// 결과: 11
console.log(Math.floor(10.15))
// 결과: 10
console.log(Math.trunc(10.15))
// 결과: 10
console.log(Math.round(10.15))
// 결과: 10

// 두번째 자리 소수점에서 반올림은 아래와 같이 구현해야함
console.log(Math.round(10.15 * 10) / 10)
// 결과: 10.2

// 소수점 세 번째 자리에서 반올림하여 두 번째 자리까지 남기기
console.log(Math.round(10.156 * 100) / 100)
// 결과: 10.16&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. 최댓값&amp;middot;최솟값 구하기&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Math.max(...array)&lt;/b&gt;: 전달받은 인수 중 가장 큰 값 변환&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Math.min(...array)&lt;/b&gt;: 전달받은 인수 중 가장 작은 값 변환&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1779695900781&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let array = [1, 2, 3, 4, 5];

console.log(Math.max(...array));
// 결과: 5

console.log(Math.min(...array));
// 결과: 1&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. 절대값 및 제곱&amp;middot;루트&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Math.abs(x): 절대값 반환&lt;/li&gt;
&lt;li&gt;Math.pow(base, exponent): 거듭제곱 계산&lt;/li&gt;
&lt;li&gt;Math.sqrt(x): 제곱근(루트) 계산&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1779696135674&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;console.log(Math.abs(-10));
// 결과: 10

console.log(Math.pow(2, 3));
// 결과: 8
console.log(2 ** 3);
// 결과: 8

console.log(Math.sqrt(16));
// 결과: 4&lt;/code&gt;&lt;/pre&gt;</description>
      <category>자바스크립트/표준 내장 객체</category>
      <category>javascript</category>
      <category>개발</category>
      <category>알고리즘</category>
      <category>자바스크립트</category>
      <category>코딩테스트</category>
      <author>Hun's blog</author>
      <guid isPermaLink="true">https://developer-it.tistory.com/19</guid>
      <comments>https://developer-it.tistory.com/19#entry19comment</comments>
      <pubDate>Mon, 25 May 2026 17:08:38 +0900</pubDate>
    </item>
    <item>
      <title>[자바스크립트] Array 객체 정리</title>
      <link>https://developer-it.tistory.com/18</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서 React를 다루며 관련 문법은 익숙해졌으나, 자바스크립트 기본기는 등한시했음을 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 생성 코드를 리뷰할 때도 React나 JSX보다 자바스크립트 로직에서 병목이 발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 보완하고자 프로그래머스 기초 문제를 풀며 자바스크립트 구현 스킬을 향상하려 한다. 앞으로 문제 풀이와 타인의 코드를 참고해 유용한 함수와 메소드를 기록할 예정이며, 이번 편에서는 &lt;b&gt;Array 객체&lt;/b&gt;를 정리한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 기본 조작 및 탐색&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;push() / pop()&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열 맨 끝에 요소를 추가하거나 제거하는 가장 기초적인 기능&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;let arr = [];

arr.push(1);
console.log(arr); // [ 1 ]

arr.pop();
console.log(arr); // []&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;includes() / indexOf() / lastIndexOf()&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 값이 배열에 있는지 확인하고 &lt;b&gt;위치를 찾는&lt;/b&gt; 기본 탐색 기능&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;let arr = [0, 1, 2, 1];

console.log(arr.includes(1)); // true (1 값 포함 여부)
console.log(arr.indexOf(1)); // 1 (앞에서 부터 찾음)
console.log(arr.indexOf(1, 2) // 3 (2번 인덱스 부터 찾음)
console.log(arr.lastIndexOf(1)); // 3 (뒤에서 부터 찾음)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. 순회 및 필터링&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;forEach()&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순 반복 조작용으로 &lt;b&gt;for 루프를 대체&lt;/b&gt;하는 기본 순회 메소드&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;let arr = [[1, 2], [3, 4], [5, 6]];

// value: 배열에 담긴 요소 (옵션) -
// index: 순회중인 인덱스 번호 (옵션)
// array: 원본 배열 (옵션)
// 아무 인자를 포함하지 않으면 arr.length 만큼 순회
arr.forEach((value, index, array) =&amp;gt; console.log(value, index, array));
// [ 1, 2 ] 0 [ [ 1, 2 ], [ 3, 4 ], [ 5, 6 ] ]
// [ 3, 4 ] 1 [ [ 1, 2 ], [ 3, 4 ], [ 5, 6 ] ]
// [ 5, 6 ] 2 [ [ 1, 2 ], [ 3, 4 ], [ 5, 6 ] ]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;map()&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요소를 가공해 &lt;b&gt;새로운 배열&lt;/b&gt;을 만드는 메소드&lt;/p&gt;
&lt;pre id=&quot;code_1779606244950&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let arr = [1, 2, 3, 4, 5];

// value: 배열에 담긴 요소 (옵션) -
// index: 순회중인 인덱스 번호 (옵션)
// array: 원본 배열 (옵션)
let new_arr = arr.map((value, index, array) =&amp;gt; value * index);
console.log(new_arr);
// [0, 2, 6, 12, 20]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;filter()&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건을 만족하는 요소만 걸러내어 새로운 배열을 만드는 메소드&lt;/p&gt;
&lt;pre id=&quot;code_1779610481740&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const numbers = [1, 2, 3, 4, 5];

// 짝수만 필터링
const evens = numbers.filter((value) =&amp;gt; value % 2 === 0);
console.log(evens);
// 출력: [2, 4]


const words = [&quot;사과&quot;, &quot;바나나&quot;, &quot;수박&quot;, &quot;파인애플&quot;];

// 글자 수가 3글자 이상인 단어만 필터링
const longWords = words.filter((word) =&amp;gt; word.length &amp;gt;= 3);
console.log(longWords);
// 출력: [&quot;바나나&quot;, &quot;파인애플&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. 배열 잘라내기 및 수정&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;slice()&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원본 배열은 건드리지 않고, 필요한 부분만 복사해서 가져오는 메소드&lt;/p&gt;
&lt;pre id=&quot;code_1779606800378&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const months = [&quot;1월&quot;, &quot;2월&quot;, &quot;3월&quot;, &quot;4월&quot;, &quot;5월&quot;];

console.log(months.slice(2));
// [&quot;3월&quot;, &quot;4월&quot;, &quot;5월&quot;]

console.log(months.slice(2, 4));
// [&quot;3월&quot;, &quot;4월&quot;]

console.log(months.slice(1, 5));
// [&quot;2월&quot;, &quot;3월&quot;, &quot;4월&quot;, &quot;5월&quot;]

console.log(months.slice(-2));
// [&quot;4월&quot;, &quot;5월&quot;]

console.log(months.slice(2, -1));
// [&quot;3월&quot;, &quot;4월&quot;]

console.log(months.slice());
// [&quot;1월&quot;, &quot;2월&quot;, &quot;3월&quot;, &quot;4월&quot;, &quot;5월&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;splice()&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원본 배열을 직접 수정(요소 삭제 및 삽입)함.&lt;/p&gt;
&lt;pre id=&quot;code_1779607119503&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const months = [&quot;1월&quot;, &quot;3월&quot;, &quot;4월&quot;, &quot;5월&quot;];

// 인덱스 1번 자리에 아무것도 지우지 않고(0) &quot;2월&quot;을 추가
months.splice(1, 0, &quot;2월&quot;);
console.log(months);
// 출력: [&quot;1월&quot;, &quot;2월&quot;, &quot;3월&quot;, &quot;4월&quot;, &quot;5월&quot;]

// 인덱스 4번 자리(현재 &quot;5월&quot;)의 요소 1개를 지우고(1) 그 자리에 &quot;5월&quot;을 다시 넣음 (결과적으로 교체)
// 만약 &quot;5월&quot;을 지우고 다른 걸 넣고 싶다면 세 번째 인자를 바꾸면 돼.
months.splice(4, 1, &quot;5월&quot;);
console.log(months);
// 출력: [&quot;1월&quot;, &quot;2월&quot;, &quot;3월&quot;, &quot;4월&quot;, &quot;5월&quot;]

// 인덱스 2번 자리부터 모든 요소를 지움
months.splice(2);
console.log(months);
// 출력 : [&quot;1월&quot;, &quot;2월&quot;]

// 인덱스 0번 자리의 요소 1개를 지움
months.splice(0, 1);
console.log(months);
// 출력 : [&quot;2월&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4. 정렬 및 반전&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;sort()&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열 요소를 정렬하는 메소드&lt;/p&gt;
&lt;pre id=&quot;code_1779609603523&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 문자열 배열을 정렬할 때
const fruits = [&quot;바나나&quot;, &quot;딸기&quot;, &quot;사과&quot;, &quot;아보카도&quot;];
fruits.sort();

console.log(fruits);
// 출력: [&quot;딸기&quot;, &quot;바나나&quot;, &quot;사과&quot;, &quot;아보카도&quot;]

// 숫자르 정렬할 때
const numbers = [4, 2, 5, 1, 3, 10];
numbers.sort();

console.log(numbers);
// 출력: [1, 10, 2, 3, 4, 5]

// 숫자를 올바르게 정렬하려면 비교 함수를 제공해야 함
numbers.sort((a, b) =&amp;gt; a - b);
console.log(numbers);
// 출력: [1, 2, 3, 4, 5, 10]

numbers.sort((a, b) =&amp;gt; b - a);
console.log(numbers);
// 출력: [10, 5, 4, 3, 2, 1]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;reverse()&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열 순서를 뒤집는 메소드&lt;/p&gt;
&lt;pre id=&quot;code_1779609688618&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const directions = [&quot;동&quot;, &quot;서&quot;, &quot;남&quot;, &quot;북&quot;];
directions.reverse();

console.log(directions); 
// 출력: [&quot;북&quot;, &quot;남&quot;, &quot;서&quot;, &quot;동&quot;]

// reverse 는 원본 배열도 뒤집어 버림
const original = [&quot;a&quot;, &quot;b&quot;, &quot;c&quot;];
const reversed = original.reverse();

console.log(original);
// 출력: [&quot;c&quot;, &quot;b&quot;, &quot;a&quot;]

console.log(reversed);
// 출력: [&quot;c&quot;, &quot;b&quot;, &quot;a&quot;]

// 원본을 유지하고 싶을 경우에는 다음과 같은 방식을 활용할 것
const reversed = [...original].reverse();&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;5. 가공 및 최종 연산&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;join()&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;배열을 하나의 문자열로 결합하는 메소드&lt;/p&gt;
&lt;pre id=&quot;code_1779610013046&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const elements = [&quot;Fire&quot;, &quot;Air&quot;, &quot;Water&quot;];

// 1. 인자를 생략하면 기본값으로 쉼표(,)로 연결됨
const result1 = elements.join();
console.log(result1); 
// 출력: &quot;Fire,Air,Water&quot;

// 2. 빈 문자열('')을 넣으면 공백 없이 싹 다 붙음
const result2 = elements.join('');
console.log(result2); 
// 출력: &quot;FireAirWater&quot;

// 3. 원하는 구분자(공백, 대시 등)를 자유롭게 넣을 수 있음
const result3 = elements.join(' - ');
console.log(result3); 
// 출력: &quot;Fire - Air - Water&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;reduce()&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열을 하나의 값으로 축소(누적 연산) 하는 메소드&lt;/p&gt;
&lt;pre id=&quot;code_1779610099538&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const numbers = [1, 2, 3, 4];

// 초기값 0에서 시작해서 배열의 숫자를 하나씩 더해나감
// previousValue(누적값): 이전 콜백 함수가 반환한 누적값. acc 로 줄여 사용
// currentValue (현재값): 현재 배열에서 순회하며 처리중인 요소의 값. cur로 줄여 사용
// currentIndex: 현재 인덱스
// array: 원본배열
const total = numbers.reduce((previousValue, currentValue, currentIndex, array) =&amp;gt; {
  return previousValue + currentValue;
}, 0);

console.log(total);
// 결과: 10&lt;/code&gt;&lt;/pre&gt;</description>
      <category>자바스크립트/표준 내장 객체</category>
      <author>Hun's blog</author>
      <guid isPermaLink="true">https://developer-it.tistory.com/18</guid>
      <comments>https://developer-it.tistory.com/18#entry18comment</comments>
      <pubDate>Sun, 24 May 2026 17:16:20 +0900</pubDate>
    </item>
  </channel>
</rss>