<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>hahm's period of growth</title>
    <link>https://dev-hahm.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Sat, 20 Jun 2026 07:24:24 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>dev-hahm</managingEditor>
    <image>
      <title>hahm's period of growth</title>
      <url>https://tistory1.daumcdn.net/tistory/5416043/attach/679773a4662b4a278fb3b6068f8927c4</url>
      <link>https://dev-hahm.tistory.com</link>
    </image>
    <item>
      <title>PostgreSQL의 AT TIME ZONE 완벽히 이해하기</title>
      <link>https://dev-hahm.tistory.com/33</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;PostgreSQL에서 타임존을 다루다 보면 예상과 다른 결과가 나와 당황한 경험이 있으실 겁니다. 특히 &lt;code&gt;AT TIME ZONE&lt;/code&gt; 구문은 입력 타입에 따라 완전히 다르게 동작하는데, 이를 제대로 이해하지 못하면 심각한 버그로 이어질 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 실제 쿼리 결과를 통해 PostgreSQL 타임존 처리의 모든 것을 파헤쳐보겠습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기본 환경 설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 테스트 환경을 확인해봅시다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;SHOW timezone;
-- 결과: UTC

SELECT current_date, now();
-- 2025-10-17 | 2025-10-17 14:34:00+00&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세션 타임존은 UTC로 설정되어 있고, 현재 시각은 &lt;code&gt;2025-10-17 14:34 UTC&lt;/code&gt;입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;AT TIME ZONE의 두 얼굴&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;AT TIME ZONE&lt;/code&gt;은 입력 타입에 따라 정반대로 동작합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;규칙 1: timestamp (타임존 없음) &amp;rarr; timestamptz&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;SELECT ('2025-10-17 08:59:59'::timestamp) AT TIME ZONE 'Asia/Seoul';
-- 결과: 2025-10-16 23:59:59+00:00&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;code&gt;2025-10-17 08:59:59&lt;/code&gt;를 &lt;b&gt;한국 시간에 있는 시각&lt;/b&gt;으로 해석&lt;/li&gt;
&lt;li&gt;이를 UTC로 변환 (한국은 UTC+9이므로 -9시간)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;2025-10-16 23:59:59 UTC&lt;/code&gt;를 timestamptz로 반환&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;규칙 2: timestamptz &amp;rarr; timestamp (타임존 없음)&lt;/h3&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;SELECT ('2025-10-17 08:59:59'::timestamptz) AT TIME ZONE 'Asia/Seoul';
-- 결과: 2025-10-17 17:59:59&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;code&gt;2025-10-17 08:59:59 UTC&lt;/code&gt; (세션 타임존이 UTC이므로)&lt;/li&gt;
&lt;li&gt;이를 &lt;b&gt;한국 시간으로 표시&lt;/b&gt; (UTC+9이므로 +9시간)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;2025-10-17 17:59:59&lt;/code&gt;를 timestamp(타임존 제거)로 반환&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;헷갈리는 문자열 처리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열에 직접 &lt;code&gt;AT TIME ZONE&lt;/code&gt;을 사용하면 어떻게 될까요?&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;SELECT 
    ('2025-10-17 08:59:59') AT TIME ZONE 'Asia/Seoul' AS case1,
    '2025-10-17 08:59:59'::timestamptz AS case2,
    ('2025-10-17 08:59:59'::timestamptz) AT TIME ZONE 'Asia/Seoul' AS case3;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결과:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;       case1            |        case2            |       case3
------------------------+-------------------------+------------------------
2025-10-17 17:59:59     | 2025-10-17 08:59:59+00  | 2025-10-17 17:59:59&lt;/code&gt;&lt;/pre&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;b&gt;case1&lt;/b&gt;: 문자열이 먼저 세션 타임존(UTC)으로 timestamptz 변환 &amp;rarr; 한국 시간으로 표시&lt;/li&gt;
&lt;li&gt;&lt;b&gt;case2&lt;/b&gt;: 문자열을 세션 타임존(UTC)으로 timestamptz 변환&lt;/li&gt;
&lt;li&gt;&lt;b&gt;case3&lt;/b&gt;: timestamptz를 한국 시간으로 표시&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;case1과 case3은 결과가 같지만, 중간 과정이 다릅니다!&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;current_date의 함정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 헷갈리는 케이스입니다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;SELECT 
    current_date AS original,
    current_date AT TIME ZONE 'Asia/Seoul' AS seoul_time1,
    current_date::timestamp AT TIME ZONE 'Asia/Seoul' AS seoul_time2;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결과:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;  original  |      seoul_time1       |      seoul_time2
------------+------------------------+---------------------------
2025-10-17  | 2025-10-17 09:00:00    | 2025-10-16 15:00:00+00:00&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;왜 이런 결과가 나올까?&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;seoul_time1: &lt;code&gt;current_date AT TIME ZONE 'Asia/Seoul'&lt;/code&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;code&gt;current_date&lt;/code&gt; = &lt;code&gt;2025-10-17&lt;/code&gt; (date 타입)&lt;/li&gt;
&lt;li&gt;암묵적으로 timestamp 변환: &lt;code&gt;2025-10-17 00:00:00&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;이게 &lt;b&gt;세션 타임존(UTC)으로 해석됨&lt;/b&gt;: &lt;code&gt;2025-10-17 00:00:00 UTC&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;한국 시간으로 변환: &lt;code&gt;2025-10-17 09:00:00 KST&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;타임존 제거한 timestamp 반환&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;seoul_time2: &lt;code&gt;current_date::timestamp AT TIME ZONE 'Asia/Seoul'&lt;/code&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;code&gt;current_date&lt;/code&gt; = &lt;code&gt;2025-10-17&lt;/code&gt; (date 타입)&lt;/li&gt;
&lt;li&gt;명시적으로 timestamp 변환: &lt;code&gt;2025-10-17 00:00:00&lt;/code&gt; (타임존 없음)&lt;/li&gt;
&lt;li&gt;이를 &lt;b&gt;한국 시간에 있는 시각으로 해석&lt;/b&gt;: &lt;code&gt;2025-10-17 00:00:00 KST&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;UTC로 변환: &lt;code&gt;2025-10-16 15:00:00 UTC&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;timestamptz 반환&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실전 문제: &quot;오늘&quot; 데이터 조회하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 쿼리는 어떤 결과를 반환할까요?&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;WITH data AS (
    SELECT *
    FROM (VALUES
        ('2025-10-17T07:00:00Z'::timestamptz),
        ('2025-10-17T08:00:00Z'::timestamptz),
        ('2025-10-17T09:00:00Z'::timestamptz),
        ('2025-10-17T10:00:00Z'::timestamptz),
        ('2025-10-17T11:00:00Z'::timestamptz)
    ) AS t(dt)
)
SELECT *
FROM data
WHERE dt &amp;gt;= (current_date AT TIME ZONE 'Asia/Seoul');&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결과: 3개 행 반환 (09:00, 10:00, 11:00)&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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;code&gt;current_date AT TIME ZONE 'Asia/Seoul'&lt;/code&gt; = &lt;code&gt;2025-10-17 09:00:00&lt;/code&gt; (timestamp)&lt;/li&gt;
&lt;li&gt;비교 시 세션 타임존(UTC)으로 해석: &lt;code&gt;2025-10-17 09:00:00 UTC&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;따라서 09:00 UTC 이후의 데이터만 조회됨&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 의도한 결과가 아닐 수 있습니다! 한국 시간 기준 &quot;오늘&quot;(00:00 KST)의 데이터를 조회하려면:&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;-- 올바른 방법 1
WHERE dt &amp;gt;= (current_date::timestamp AT TIME ZONE 'Asia/Seoul')

-- 올바른 방법 2
WHERE dt &amp;gt;= date_trunc('day', now() AT TIME ZONE 'Asia/Seoul') AT TIME ZONE 'Asia/Seoul'&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;다국적 서비스에서 &quot;오늘&quot; 집계하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간대가 다른 사용자들에게 각자의 &quot;오늘&quot; 데이터를 보여줘야 한다면?&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;방법 1: 사용자별 타임존으로 필터링&lt;/h3&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;-- 서울 사용자
SELECT COUNT(*)
FROM events
WHERE created_at &amp;gt;= (current_date::timestamp AT TIME ZONE 'Asia/Seoul')
  AND created_at &amp;lt; (current_date::timestamp AT TIME ZONE 'Asia/Seoul') + INTERVAL '1 day';

-- 뉴욕 사용자
SELECT COUNT(*)
FROM events
WHERE created_at &amp;gt;= (current_date::timestamp AT TIME ZONE 'America/New_York')
  AND created_at &amp;lt; (current_date::timestamp AT TIME ZONE 'America/New_York') + INTERVAL '1 day';&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;방법 2: 더 명확한 date_trunc 사용&lt;/h3&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;WITH user_timezone AS (
    SELECT 
        date_trunc('day', now() AT TIME ZONE 'Asia/Seoul') AS day_start_local
)
SELECT COUNT(*)
FROM events, user_timezone
WHERE created_at &amp;gt;= (day_start_local AT TIME ZONE 'Asia/Seoul')
  AND created_at &amp;lt; (day_start_local AT TIME ZONE 'Asia/Seoul') + INTERVAL '1 day';&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;방법 3: 클라이언트에서 타임존 전달 (권장)&lt;/h3&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;-- API에서 user_timezone 파라미터 받기
SELECT 
    DATE(created_at AT TIME ZONE :user_timezone) AS local_date,
    COUNT(*) AS count
FROM events
WHERE created_at &amp;gt;= date_trunc('day', now() AT TIME ZONE :user_timezone) 
                    AT TIME ZONE :user_timezone
  AND created_at &amp;lt; date_trunc('day', now() AT TIME ZONE :user_timezone) 
                   AT TIME ZONE :user_timezone + INTERVAL '1 day'
GROUP BY local_date;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;핵심 정리&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;AT TIME ZONE 동작 규칙&lt;/h3&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;입력 타입&lt;/th&gt;
&lt;th&gt;AT TIME ZONE 동작&lt;/th&gt;
&lt;th&gt;출력 타입&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;timestamp&lt;/td&gt;
&lt;td&gt;해당 타임존의 시각으로 &lt;b&gt;해석&lt;/b&gt; &amp;rarr; UTC 변환&lt;/td&gt;
&lt;td&gt;timestamptz&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;timestamptz&lt;/td&gt;
&lt;td&gt;해당 타임존으로 &lt;b&gt;표시&lt;/b&gt; (타임존 제거)&lt;/td&gt;
&lt;td&gt;timestamp&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;date/문자열&lt;/td&gt;
&lt;td&gt;먼저 세션 타임존으로 변환 &amp;rarr; 해당 타임존으로 표시&lt;/td&gt;
&lt;td&gt;timestamp&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실수하기 쉬운 패턴&lt;/h3&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;-- ❌ 잘못된 방법 (UTC 09:00 기준)
WHERE dt &amp;gt;= current_date AT TIME ZONE 'Asia/Seoul'

-- ✅ 올바른 방법 (한국 00:00 기준)
WHERE dt &amp;gt;= current_date::timestamp AT TIME ZONE 'Asia/Seoul'

-- ✅ 더 명확한 방법
WHERE dt &amp;gt;= date_trunc('day', now() AT TIME ZONE 'Asia/Seoul') 
            AT TIME ZONE 'Asia/Seoul'&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기억해야 할 것&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;문자열/date는 항상 세션 타임존으로 먼저 해석됩니다&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;::timestamp&lt;/code&gt; 명시적 캐스팅으로 타임존 없는 값을 만드세요&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;timestamp와 timestamptz의 변환 방향을 명확히 이해하세요&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;다국적 서비스에서는 사용자의 타임존을 명시적으로 관리하세요&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PostgreSQL의 타임존 처리는 처음에는 복잡해 보이지만, 규칙을 이해하면 매우 논리적입니다. 특히 글로벌 서비스를 개발할 때는 이러한 동작을 정확히 이해하고 있어야 사용자에게 올바른 시간 정보를 제공할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스에 데이터를 저장할 때는 항상 UTC로 저장하고, 사용자에게 보여줄 때만 해당 타임존으로 변환하는 것이 베스트 프랙티스입니다!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&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;a href=&quot;https://www.postgresql.org/docs/current/datatype-datetime.html&quot;&gt;PostgreSQL 공식 문서 - Date/Time Types&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.postgresql.org/docs/current/functions-datetime.html&quot;&gt;PostgreSQL 공식 문서 - Date/Time Functions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>DB</category>
      <category>PostgreSQL</category>
      <author>dev-hahm</author>
      <guid isPermaLink="true">https://dev-hahm.tistory.com/33</guid>
      <comments>https://dev-hahm.tistory.com/33#entry33comment</comments>
      <pubDate>Fri, 17 Oct 2025 16:48:37 +0900</pubDate>
    </item>
    <item>
      <title>gRPC 입문: 서버 간 통신을 간단하게 만드는 Google RPC</title>
      <link>https://dev-hahm.tistory.com/32</link>
      <description>&lt;h1&gt;gRPC 입문: 서버 간 통신을 간단하게 만드는 Google RPC&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현대 웹 서비스와 마이크로서비스 환경에서 서버 간 통신은 필수적입니다.&lt;br /&gt;그런데 REST API만으로는 &lt;b&gt;대규모, 고성능, 실시간 통신&lt;/b&gt; 환경에서 부족함을 느낄 수 있습니다.&lt;br /&gt;바로 이런 상황에서 등장한 것이 &lt;b&gt;gRPC(Google Remote Procedure Call)&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 &lt;b&gt;gRPC란 무엇인지, 구조와 특징, 간단한 예제&lt;/b&gt;까지 살펴보겠습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. gRPC란 무엇인가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gRPC는 구글이 개발한 &lt;b&gt;원격 프로시저 호출(Remote Procedure Call) 프레임워크&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 말하면,&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크를 통해 다른 서버의 함수를 마치 내 컴퓨터에서 호출하는 것처럼 사용할 수 있게 해주는 기술&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 클라이언트 코드에서 이렇게 작성할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;result = stub.Add(5, 7)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 실제로는 &lt;code&gt;localhost:50051&lt;/code&gt; 서버에 있는 &lt;code&gt;Calculator.Add&lt;/code&gt; 함수를 호출하고, 결과가 돌아옵니다.&lt;br /&gt;개발자는 이를 &lt;b&gt;로컬 함수 호출처럼 간단하게&lt;/b&gt; 사용할 수 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. gRPC가 필요한 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REST API와 비교했을 때 gRPC의 장점은 다음과 같습니다.&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;고성능&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Protocol Buffers를 사용한 &lt;b&gt;바이너리 직렬화&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;HTTP/2 기반 &lt;b&gt;멀티플렉싱&lt;/b&gt; 지원 &amp;rarr; 요청/응답 빠르고 효율적&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&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;code&gt;.proto&lt;/code&gt; 파일로 메시지 구조와 서비스 정의 &amp;rarr; 컴파일 시점 타입 체크 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&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;클라이언트/서버 스텁 자동 생성 &amp;rarr; 개발자가 HTTP 요청, 파싱 등을 직접 구현할 필요 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&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;서버, 클라이언트, 양방향 스트리밍 모두 가능 &amp;rarr; 실시간 데이터 처리에 적합&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. gRPC 기본 구성 요소&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;구성 요소&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.proto&lt;/code&gt; 파일&lt;/td&gt;
&lt;td&gt;메시지와 서비스 정의 설계도&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;메시지(message)&lt;/td&gt;
&lt;td&gt;요청/응답 데이터 구조 정의&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;서비스(service)&lt;/td&gt;
&lt;td&gt;서버가 제공할 원격 호출 함수 모음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;서버(Server)&lt;/td&gt;
&lt;td&gt;실제 함수 구현 및 요청 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;클라이언트(Client)&lt;/td&gt;
&lt;td&gt;서버 메서드를 호출&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;스텁(Stub)&lt;/td&gt;
&lt;td&gt;클라이언트에서 사용하는 &amp;ldquo;가짜 함수&amp;rdquo; &amp;rarr; 네트워크 호출 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 협업 시 gRPC 사용 방법&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;서버 개발자가 &lt;code&gt;.proto&lt;/code&gt; 파일 작성 &amp;rarr; 서비스, 메서드, 요청/응답 구조 정의&lt;/li&gt;
&lt;li&gt;클라이언트 팀은 &lt;code&gt;.proto&lt;/code&gt; 파일로 &lt;b&gt;Stub 생성&lt;/b&gt; &amp;rarr; 서버 코드 몰라도 함수 호출 가능&lt;/li&gt;
&lt;li&gt;Stub을 이용해 &lt;b&gt;로컬 함수처럼 서버 메서드 호출&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 협업 시 클라이언트와 서버 간 &lt;b&gt;API 문서 대신 &lt;code&gt;.proto&lt;/code&gt; 공유&lt;/b&gt;로 모든 통신이 가능합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 간단한 예제: 계산기 서비스&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;code&gt;.proto&lt;/code&gt; 파일&lt;/h3&gt;
&lt;pre class=&quot;protobuf&quot;&gt;&lt;code&gt;syntax = &quot;proto3&quot;;

package calculator;

message AddRequest {
  int32 a = 1;
  int32 b = 2;
}

message AddResponse {
  int32 result = 1;
}

service Calculator {
  rpc Add (AddRequest) returns (AddResponse);
  rpc Multiply (AddRequest) returns (AddResponse);
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;서버 구현 (Python)&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;from concurrent import futures
import grpc
import calculator_pb2
import calculator_pb2_grpc

class CalculatorServicer(calculator_pb2_grpc.CalculatorServicer):
    def Add(self, request, context):
        return calculator_pb2.AddResponse(result=request.a + request.b)

    def Multiply(self, request, context):
        return calculator_pb2.MultiplyResponse(result=request.a * request.b)

server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
calculator_pb2_grpc.add_CalculatorServicer_to_server(CalculatorServicer(), server)
server.add_insecure_port('[::]:50051')
server.start()
server.wait_for_termination()&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;클라이언트 호출&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;channel = grpc.insecure_channel('localhost:50051')
stub = calculator_pb2_grpc.CalculatorStub(channel)

response = stub.Add(calculator_pb2.AddRequest(a=5, b=7))
print(response.result)  # 12&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. gRPC 사용이 적합한 환경&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;서버 간 통신&lt;/b&gt;: 마이크로서비스, 클라우드 내부 통신&lt;/li&gt;
&lt;li&gt;&lt;b&gt;실시간 스트리밍&lt;/b&gt;: 채팅, 게임, IoT 데이터&lt;/li&gt;
&lt;li&gt;&lt;b&gt;멀티언어 환경&lt;/b&gt;: 서버와 클라이언트가 다른 언어로 구현된 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 브라우저에서 단순한 API 호출은 REST API가 더 편리합니다.&lt;br /&gt;브라우저에서 gRPC를 쓰려면 gRPC-Web을 사용해야 합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 정리&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;gRPC는 &lt;b&gt;서버 간 고성능 원격 호출&lt;/b&gt;을 간단하게 만들어주는 기술입니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.proto&lt;/code&gt; 파일로 설계 &amp;rarr; Stub 생성 &amp;rarr; 클라이언트/서버 구현 &amp;rarr; 원격 함수 호출&lt;/li&gt;
&lt;li&gt;REST API보다 &lt;b&gt;성능, 타입 안전, 스트리밍 지원&lt;/b&gt;에 강점이 있습니다.&lt;/li&gt;
&lt;li&gt;협업 환경에서 &lt;b&gt;API 문서 대신 &lt;code&gt;.proto&lt;/code&gt; 공유&lt;/b&gt;로 통신이 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글을 통해 gRPC의 &lt;b&gt;기본 개념, 구조, 장점, 협업 방법&lt;/b&gt;까지 이해할 수 있습니다.&lt;/p&gt;</description>
      <category>gRPC</category>
      <category>GRPC</category>
      <author>dev-hahm</author>
      <guid isPermaLink="true">https://dev-hahm.tistory.com/32</guid>
      <comments>https://dev-hahm.tistory.com/32#entry32comment</comments>
      <pubDate>Thu, 25 Sep 2025 11:18:17 +0900</pubDate>
    </item>
    <item>
      <title>사무실 VLAN 설정을 통한 팀별 망분리</title>
      <link>https://dev-hahm.tistory.com/31</link>
      <description>&lt;h1 data-end=&quot;156&quot; data-start=&quot;111&quot;&gt;사무실 네트워크에서 VLAN 구성: 공유기와 L2 스위치를 이용한 실무 가이드&lt;/h1&gt;
&lt;p data-end=&quot;287&quot; data-start=&quot;158&quot; data-ke-size=&quot;size16&quot;&gt;사무실 네트워크를 효율적으로 구성하려면 **VLAN(Virtual LAN)**을 활용해 부서별, 팀별로 네트워크를 분리하는 것이 중요합니다. 이번 글에서는 &lt;b&gt;공유기와 L2 스위치를 활용한 VLAN 구성 방법&lt;/b&gt;을 정리했습니다.&lt;/p&gt;
&lt;p data-end=&quot;287&quot; data-start=&quot;158&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;287&quot; data-start=&quot;158&quot; data-ke-size=&quot;size16&quot;&gt;(공유기가 VLAN을 지원하며, 스위치는 L2스위치 입니다.)&lt;/p&gt;
&lt;hr data-end=&quot;292&quot; data-start=&quot;289&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;307&quot; data-start=&quot;294&quot; data-ke-size=&quot;size26&quot;&gt;1. VLAN이란?&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;475&quot; data-start=&quot;309&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;384&quot; data-start=&quot;309&quot;&gt;&lt;b&gt;VLAN(Virtual LAN)&lt;/b&gt;: 물리적인 동일 네트워크를 논리적으로 나누어 &lt;b&gt;서로 다른 네트워크처럼 운영&lt;/b&gt;하는 기술&lt;/li&gt;
&lt;li data-end=&quot;475&quot; data-start=&quot;385&quot;&gt;장점:
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;475&quot; data-start=&quot;393&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;412&quot; data-start=&quot;393&quot;&gt;팀/부서별 네트워크 분리 가능&lt;/li&gt;
&lt;li data-end=&quot;444&quot; data-start=&quot;415&quot;&gt;브로드캐스트 도메인 분리 &amp;rarr; 네트워크 효율 향상&lt;/li&gt;
&lt;li data-end=&quot;475&quot; data-start=&quot;447&quot;&gt;보안 강화 &amp;rarr; 특정 VLAN만 접근 허용 가능&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;480&quot; data-start=&quot;477&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;502&quot; data-start=&quot;482&quot; data-ke-size=&quot;size26&quot;&gt;2. 네트워크 장비 계층과 역할&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;장비OSI 계층주요 기능
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;736&quot; data-start=&quot;504&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody data-end=&quot;736&quot; data-start=&quot;560&quot;&gt;
&lt;tr data-end=&quot;590&quot; data-start=&quot;560&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;565&quot; data-start=&quot;560&quot;&gt;허브&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;570&quot; data-start=&quot;565&quot;&gt;L1&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;590&quot; data-start=&quot;570&quot;&gt;단순 신호 전달, 브로드캐스트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;633&quot; data-start=&quot;591&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;597&quot; data-start=&quot;591&quot;&gt;스위치&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;602&quot; data-start=&quot;597&quot;&gt;L2&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;633&quot; data-start=&quot;602&quot;&gt;MAC 주소 기반 패킷 전달, VLAN 지원 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;681&quot; data-start=&quot;634&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;643&quot; data-start=&quot;634&quot;&gt;L3 스위치&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;648&quot; data-start=&quot;643&quot;&gt;L3&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;681&quot; data-start=&quot;648&quot;&gt;IP 기반 라우팅, VLAN 간 통신, 서브넷 라우팅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;736&quot; data-start=&quot;682&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;688&quot; data-start=&quot;682&quot;&gt;공유기&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;698&quot; data-start=&quot;688&quot;&gt;L3 + L4&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;736&quot; data-start=&quot;698&quot;&gt;서로 다른 네트워크 간 라우팅, NAT, DHCP, 포트 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;854&quot; data-start=&quot;738&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;807&quot; data-start=&quot;738&quot;&gt;일반 공유기는 &lt;b&gt;LAN과 WAN을 연결하는 L3 장치&lt;/b&gt; 역할을 수행하며, 일부 모델은 VLAN 설정을 지원합니다.&lt;/li&gt;
&lt;li data-end=&quot;854&quot; data-start=&quot;808&quot;&gt;L2 스위치는 VLAN을 구분하고 포트를 팀별로 분리하는 역할을 수행합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;859&quot; data-start=&quot;856&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;881&quot; data-start=&quot;861&quot; data-ke-size=&quot;size26&quot;&gt;3. 사무실 VLAN 구성 예시&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;network.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxti37/btsQpysRryb/SS0JAay3yKufIAnMHVTri0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxti37/btsQpysRryb/SS0JAay3yKufIAnMHVTri0/img.png&quot; data-alt=&quot;[목표 구성도]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxti37/btsQpysRryb/SS0JAay3yKufIAnMHVTri0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbxti37%2FbtsQpysRryb%2FSS0JAay3yKufIAnMHVTri0%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;352&quot; height=&quot;352&quot; data-filename=&quot;network.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[목표 구성도]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-end=&quot;889&quot; data-start=&quot;883&quot; data-ke-size=&quot;size23&quot;&gt;목표&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;958&quot; data-start=&quot;890&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;903&quot; data-start=&quot;890&quot;&gt;VLAN10: 팀 A&lt;/li&gt;
&lt;li data-end=&quot;917&quot; data-start=&quot;904&quot;&gt;VLAN20: 팀 B&lt;/li&gt;
&lt;li data-end=&quot;941&quot; data-start=&quot;918&quot;&gt;L2 스위치를 이용해 VLAN 간 분리&lt;/li&gt;
&lt;li data-end=&quot;958&quot; data-start=&quot;942&quot;&gt;공유기를 통해 인터넷 연결&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;969&quot; data-start=&quot;960&quot; data-ke-size=&quot;size23&quot;&gt;구성 흐름&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;1320&quot; data-start=&quot;970&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;1055&quot; data-start=&quot;970&quot;&gt;&lt;b&gt;공유기 VLAN 설정&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1055&quot; data-start=&quot;992&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1034&quot; data-start=&quot;992&quot;&gt;공유기 LAN 포트 중 일부를 VLAN10, VLAN20으로 나누어 설정&lt;/li&gt;
&lt;li data-end=&quot;1055&quot; data-start=&quot;1038&quot;&gt;VLAN별 서브넷 지정 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1243&quot; data-start=&quot;1056&quot;&gt;&lt;b&gt;L2 스위치 VLAN 설정&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1243&quot; data-start=&quot;1081&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1156&quot; data-start=&quot;1081&quot;&gt;트렁크 포트 설정: 공유기와 스위치를 연결할 포트를 &lt;b&gt;트렁크 모드&lt;/b&gt;로 설정
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1156&quot; data-start=&quot;1132&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1156&quot; data-start=&quot;1132&quot;&gt;트렁크 포트 &amp;rarr; 여러 VLAN 패킷 전달&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1243&quot; data-start=&quot;1160&quot;&gt;액세스 포트 설정: 팀별 PC 연결 포트를 각 VLAN에 할당
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1243&quot; data-start=&quot;1202&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1219&quot; data-start=&quot;1202&quot;&gt;포트 1~8 &amp;rarr; VLAN10&lt;/li&gt;
&lt;li data-end=&quot;1243&quot; data-start=&quot;1225&quot;&gt;포트 9~16 &amp;rarr; VLAN20&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1320&quot; data-start=&quot;1244&quot;&gt;&lt;b&gt;PC 연결&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1320&quot; data-start=&quot;1260&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1281&quot; data-start=&quot;1260&quot;&gt;각 팀별 PC를 스위치 포트에 연결&lt;/li&gt;
&lt;li data-end=&quot;1320&quot; data-start=&quot;1285&quot;&gt;DHCP 또는 고정 IP를 VLAN 서브넷 범위에 맞게 할당&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-end=&quot;1325&quot; data-start=&quot;1322&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;1347&quot; data-start=&quot;1327&quot; data-ke-size=&quot;size26&quot;&gt;4. 트렁크 포트와 액세스 포트&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;포트 타입설명
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;1482&quot; data-start=&quot;1349&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody data-end=&quot;1482&quot; data-start=&quot;1385&quot;&gt;
&lt;tr data-end=&quot;1441&quot; data-start=&quot;1385&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1394&quot; data-start=&quot;1385&quot;&gt;트렁크 포트&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1441&quot; data-start=&quot;1394&quot;&gt;한 포트에서 &lt;b&gt;여러 VLAN 패킷&lt;/b&gt; 전달, 공유기와 스위치 간 연결에 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1482&quot; data-start=&quot;1442&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1451&quot; data-start=&quot;1442&quot;&gt;액세스 포트&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1482&quot; data-start=&quot;1451&quot;&gt;특정 VLAN에만 속하며, 팀별 PC 연결에 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1602&quot; data-start=&quot;1484&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1552&quot; data-start=&quot;1484&quot;&gt;트렁크 포트를 공유기 &amp;rarr; L2 스위치에 연결하면, &lt;b&gt;VLAN10과 VLAN20이 한 물리적 케이블로 전달 가능&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1602&quot; data-start=&quot;1553&quot;&gt;스위치 액세스 포트로 팀별 PC를 연결하면, VLAN에 따라 네트워크가 분리됩니다&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;1607&quot; data-start=&quot;1604&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;1624&quot; data-start=&quot;1609&quot; data-ke-size=&quot;size26&quot;&gt;5. VLAN 간 통신&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1761&quot; data-start=&quot;1626&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1667&quot; data-start=&quot;1626&quot;&gt;VLAN10과 VLAN20은 &lt;b&gt;L2 스위치만으로는 서로 통신 불가&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1761&quot; data-start=&quot;1668&quot;&gt;서로 통신이 필요하면 &lt;b&gt;L3 장비&lt;/b&gt;에서 라우팅 설정 필요
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1761&quot; data-start=&quot;1706&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1730&quot; data-start=&quot;1706&quot;&gt;L3 스위치나 공유기의 정적 라우트 설정&lt;/li&gt;
&lt;li data-end=&quot;1761&quot; data-start=&quot;1733&quot;&gt;VLAN10 &amp;harr; VLAN20 간 패킷 전달 허용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;1766&quot; data-start=&quot;1763&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;1782&quot; data-start=&quot;1768&quot; data-ke-size=&quot;size26&quot;&gt;6. 장점과 유의사항&lt;/h2&gt;
&lt;h3 data-end=&quot;1790&quot; data-start=&quot;1784&quot; data-ke-size=&quot;size23&quot;&gt;장점&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;1875&quot; data-start=&quot;1791&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;1812&quot; data-start=&quot;1791&quot;&gt;팀별 네트워크 분리 &amp;rarr; 보안 강화&lt;/li&gt;
&lt;li data-end=&quot;1842&quot; data-start=&quot;1813&quot;&gt;브로드캐스트 트래픽 감소 &amp;rarr; 네트워크 효율 향상&lt;/li&gt;
&lt;li data-end=&quot;1875&quot; data-start=&quot;1843&quot;&gt;관리 용이 &amp;rarr; DHCP 풀, IP 대역 별 관리 가능&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-end=&quot;1885&quot; data-start=&quot;1877&quot; data-ke-size=&quot;size23&quot;&gt;유의사항&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2013&quot; data-start=&quot;1886&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1924&quot; data-start=&quot;1886&quot;&gt;VLAN 구성 시 &lt;b&gt;IP 서브넷을 팀별로 구분&lt;/b&gt;해야 충돌 방지&lt;/li&gt;
&lt;li data-end=&quot;1964&quot; data-start=&quot;1925&quot;&gt;트렁크 포트 설정과 VLAN 태깅(tagging)을 정확히 해야 함&lt;/li&gt;
&lt;li data-end=&quot;2013&quot; data-start=&quot;1965&quot;&gt;L2 스위치만으로 VLAN 간 통신은 불가 &amp;rarr; 필요 시 L3 장비 라우팅 설정 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;2018&quot; data-start=&quot;2015&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;2028&quot; data-start=&quot;2020&quot; data-ke-size=&quot;size26&quot;&gt;7. 요약&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2198&quot; data-start=&quot;2030&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2075&quot; data-start=&quot;2030&quot;&gt;사무실에서 &lt;b&gt;팀별 VLAN&lt;/b&gt; 구성 시 공유기와 L2 스위치를 활용 가능&lt;/li&gt;
&lt;li data-end=&quot;2125&quot; data-start=&quot;2076&quot;&gt;공유기 VLAN 설정 &amp;rarr; L2 스위치 트렁크 포트 연결 &amp;rarr; 액세스 포트 팀별 할당&lt;/li&gt;
&lt;li data-end=&quot;2157&quot; data-start=&quot;2126&quot;&gt;VLAN 간 통신 필요 시 L3 라우팅 설정 필요&lt;/li&gt;
&lt;li data-end=&quot;2198&quot; data-start=&quot;2158&quot;&gt;IP 서브넷 관리, 트렁크/액세스 포트 설정, DHCP 분리 등 필수&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Network</category>
      <author>dev-hahm</author>
      <guid isPermaLink="true">https://dev-hahm.tistory.com/31</guid>
      <comments>https://dev-hahm.tistory.com/31#entry31comment</comments>
      <pubDate>Tue, 9 Sep 2025 16:49:31 +0900</pubDate>
    </item>
    <item>
      <title>Git flow, 깃 브러치 관리 전략</title>
      <link>https://dev-hahm.tistory.com/30</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;1인 개발자면 크게 와닿지 않겠지만 누군가와 협업을 하다보면 형상관리를 사용할 수 밖에 없습니다. 이 형상관리에도 개념적인 전략이 있습니다. 이 전략을 따라했을때 팀원들간에 협업에 브레이크가 걸리지 않고 시너지가 날 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 전략 몇개 중 저는 Git Flow에 대해 얘기 해볼까 합니다.&lt;/p&gt;
&lt;h1&gt;1. Git Flow 개요&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Git Flow는 &lt;b&gt;Vincent Driessen&lt;/b&gt;이 제안한 Git 브랜치 모델입니다.&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;주요 브랜치 종류:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브랜치 용도&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;main (또는 master)&lt;/td&gt;
&lt;td&gt;항상 프로덕션 배포 가능한 상태 유지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;develop&lt;/td&gt;
&lt;td&gt;개발 중인 기능을 통합하는 브랜치, 다음 배포 대상&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;feature/*&lt;/td&gt;
&lt;td&gt;새로운 기능 개발&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;release/*&lt;/td&gt;
&lt;td&gt;배포 준비, 버그 수정, 버전 번호 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;hotfix/*&lt;/td&gt;
&lt;td&gt;긴급 프로덕션 버그 수정&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;2. 머지 전략 (Merge Strategy)&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Git Flow에서는 브랜치 간 병합 시 전략이 약속되어 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(1) Feature &amp;rarr; Develop&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;: 새 기능(feature)을 개발 브랜치(develop)에 통합&lt;/li&gt;
&lt;li&gt;&lt;b&gt;브랜치 구조&lt;/b&gt;: feature/XXX &amp;rarr; develop&lt;/li&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;b&gt;-no-ff (non-fast-forward)&lt;/b&gt; 머지 사용&lt;/li&gt;
&lt;li&gt;이유: Feature 브랜치가 독립적이었음을 히스토리에 명확히 남기기 위해&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;stata&quot;&gt;&lt;code&gt;git checkout develop
git merge --no-ff feature/test

&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과: develop 브랜치에 feature 브랜치의 히스토리가 단일 merge commit으로 남음&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(2) Develop &amp;rarr; Release&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;브랜치 구조&lt;/b&gt;: develop &amp;rarr; release/X.Y.Z&lt;/li&gt;
&lt;li&gt;&lt;b&gt;머지 방식&lt;/b&gt;: 일반적으로 &lt;b&gt;non-fast-forward&lt;/b&gt;, 충돌 해결 후 배포 전 테스트&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;git checkout release/1.2.0
git merge --no-ff develop

&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;release 브랜치에서 최종 버그 픽스 가능, 버전 번호 수정 가능&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(3) Release &amp;rarr; Main / Release &amp;rarr; Develop&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Main: 실제 프로덕션 배포&lt;/li&gt;
&lt;li&gt;Develop: release 브랜치에서 수정된 사항 반영&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&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;release &amp;rarr; main: &lt;b&gt;non-fast-forward&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;release &amp;rarr; develop: &lt;b&gt;fast-forward&lt;/b&gt; 또는 &lt;b&gt;non-fast-forward&lt;/b&gt; (충돌 방지 위해 merge commit 권장)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;lsl&quot;&gt;&lt;code&gt;git checkout main
git merge --no-ff release/1.2.0
git tag -a 1.2.0 -m &quot;Release 1.2.0&quot;

git checkout develop
git merge --no-ff release/1.2.0

&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(4) Hotfix &amp;rarr; Main / Hotfix &amp;rarr; Develop&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;브랜치 구조&lt;/b&gt;: hotfix/X.Y.Z &amp;rarr; main, develop&lt;/li&gt;
&lt;li&gt;&lt;b&gt;머지 방식&lt;/b&gt;: 항상 &lt;b&gt;non-fast-forward&lt;/b&gt;, 태그로 버전 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;lsl&quot;&gt;&lt;code&gt;git checkout main
git merge --no-ff hotfix/1.2.1
git tag -a 1.2.1 -m &quot;Hotfix 1.2.1&quot;

git checkout develop
git merge --no-ff hotfix/1.2.1

&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;3. 요약&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Fast-forward(Fast-forward merge)&lt;/b&gt;: 단순히 브랜치가 직선으로 이어질 때 사용 가능, 히스토리 깨끗&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Non-fast-forward (-no-ff)&lt;/b&gt;: 브랜치 합쳤다는 기록을 남김, Git Flow에서는 주로 이 방식 사용&lt;/li&gt;
&lt;li&gt;Git Flow 머지 전략 핵심:
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;기능 개발은 feature &amp;rarr; develop&lt;/li&gt;
&lt;li&gt;배포 준비는 develop &amp;rarr; release&lt;/li&gt;
&lt;li&gt;배포 완료 후 main, develop에 반영&lt;/li&gt;
&lt;li&gt;긴급 수정은 hotfix &amp;rarr; main &amp;rarr; develop&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혼자 프로젝트 하나 만들어서 장난감처럼 좀 가지고 놀다보면 대략적인 흐름은 금방 이해할 수 있습니다.&lt;/p&gt;</description>
      <category>Git</category>
      <category>GIT</category>
      <author>dev-hahm</author>
      <guid isPermaLink="true">https://dev-hahm.tistory.com/30</guid>
      <comments>https://dev-hahm.tistory.com/30#entry30comment</comments>
      <pubDate>Mon, 8 Sep 2025 16:32:24 +0900</pubDate>
    </item>
    <item>
      <title>Jenkins 구축기</title>
      <link>https://dev-hahm.tistory.com/29</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;요즘 사무실에서 이것저것.. 잡다하게 넓은 영역에 대해서 커버하고 있습니다. 그러다 보니 여러프로젝트를 배포할일이 많습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에는 그렇게 바쁘지 않았어서 호스트에 접근해서 pull 받고 하나하나 배포했는데 이제는... 여력이 없습니다.&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;우선 jenkins를&amp;nbsp; docker-compose.yaml로 작성했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1755086968282&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;version: &quot;3.9&quot;

services:
  jenkins:
    image: jenkins/jenkins:lts
    container_name: jenkins
    restart: unless-stopped
    privileged: true
    user: root
    ports:
      - &quot;8080:8080&quot;   # 웹 UI
      - &quot;50000:50000&quot; # 에이전트 통신 포트
    volumes:
      - jenkins_home:/var/jenkins_home
      - /var/run/docker.sock:/var/run/docker.sock
      - /usr/bin/docker:/usr/bin/docker
    networks:
      - jenkins_net

volumes:
  jenkins_home:

networks:
  jenkins_net:
    driver: bridge&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저의 경우&amp;nbsp; yaml의 위치는 /opt/cicd/docker-compose.jenkins.yaml 로 만들었습니다. (조만간 Nexus도 같은 경로에 컨테이너로 만들 예정이랍니다.)&lt;/p&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; data-start=&quot;1124&quot; data-end=&quot;1253&quot;&gt;
&lt;li data-start=&quot;1124&quot; data-end=&quot;1162&quot;&gt;jenkins_home 볼륨: Jenkins 데이터 영구 저장&lt;/li&gt;
&lt;li data-start=&quot;1163&quot; data-end=&quot;1220&quot;&gt;/var/run/docker.sock 마운트: Jenkins에서 Docker 컨테이너 제어 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1755087128317&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker compose -f docker-compose.jenkins.yaml up -d&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 컨테이너를 구동해줍니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;http://{ip}:8080. 로 접속하셔서 초기 셋팅 하시고 로그인 하라고 나올겁니다.&amp;nbsp; 그렇지만 우리는 초기 비밀번호를 모르죠??&lt;/p&gt;
&lt;pre id=&quot;code_1755087199901&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker exec -it jenkins cat /var/jenkins_home/secrets/initialAdminPassword&lt;/code&gt;&lt;/pre&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;이렇게 까지 하면&amp;nbsp; jenkins 자체 설치는 끝이 납니다. 쉽죠??? 다음번엔 파이프라인 구축하는 글로 찾아오겠습니다.&lt;/p&gt;</description>
      <category>CI-CD</category>
      <category>CICD</category>
      <category>jenkins</category>
      <author>dev-hahm</author>
      <guid isPermaLink="true">https://dev-hahm.tistory.com/29</guid>
      <comments>https://dev-hahm.tistory.com/29#entry29comment</comments>
      <pubDate>Wed, 13 Aug 2025 21:14:25 +0900</pubDate>
    </item>
    <item>
      <title>쿠버네티스 (k8s) Core Dns란?</title>
      <link>https://dev-hahm.tistory.com/28</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스를 사용하다보면 파드(pod)끼리 ip로 연결 하는게 아닌 도메인명으로 통신하는 설정을 많이 보게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 도메인은 뜬금없이 어디서 나타났는지 궁금하기도 하죠. 쿠버네티스는 자기만의 DNS서버를 구동합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kube-system이란 네임스페이스에 보면 coredns라는 파드가 보이게 됩니다. &lt;br /&gt;이 파드들이 쿠버네티스 내부에서 DNS서버 역할을 하게 됩니다.&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;A라는 파드가 B라는 파드에 요청을 보내는경우&lt;br /&gt;&amp;lt;B-pod-servicename&amp;gt;.&amp;lt;namespace&amp;gt;.svc.cluster.local 이라는 도메인명으로 요청을 보내게 되면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 네임스페이스에 있는경우는 줄여서 &amp;lt;B-pod-servicename&amp;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;좀 더 깊이 들여다보면 A라는 파드에 접근해서 /etc/resolv.conf를 열어보면 알 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 파일은 지정된 클러스터 DNS로 전달되게 셋팅되어 있습니다.&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; Corefile이라고 CoreDNS에 주요 설정 파일이 있는데 이것도 한번 열어보면 이해하는데 도움이 많이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 열어보면 내용은 아래와 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1750854339325&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl -n kube-system get configmap coredns -o yaml&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;열어보는 명령어니까 참고하시면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1750854353640&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.:53 {
    errors
    health
    ready
    kubernetes cluster.local in-addr.arpa ip6.arpa {
        pods insecure
        fallthrough in-addr.arpa ip6.arpa
        ttl 30
    }
    prometheus :9153
    forward . /etc/resolv.conf
    cache 30
    loop
    reload
    loadbalance
}&lt;/code&gt;&lt;/pre&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;kubernetes : 쿠버네시트에 서비스 이름을 ip로 변환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;forward : 외부 dns 서버로 요청을 전달&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cache : dns 응답 캐싱&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;health, ready : coreDNS상태 체크를 위한 엔드포인트 제공&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;reload 설정 변경 시 자동 재로딩&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;prometheus : 메트릭 수집용 엔드보인트 제공&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에 보통 log, debug 를 추가해서 coredns에 로그를 보기도 합니다. (제가 그러고 있네요)&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;</description>
      <category>Server</category>
      <author>dev-hahm</author>
      <guid isPermaLink="true">https://dev-hahm.tistory.com/28</guid>
      <comments>https://dev-hahm.tistory.com/28#entry28comment</comments>
      <pubDate>Wed, 25 Jun 2025 21:29:24 +0900</pubDate>
    </item>
    <item>
      <title>쿠버네티스 (k8s) 워커노드 추가하기</title>
      <link>https://dev-hahm.tistory.com/27</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;저번에 단일노드로 쿠버네티스를 설정했습니다. 그렇게 계속 사용할 순 없고 슬슬 노드를 추가해야겠지요...?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리하여!&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;blockquote data-ke-style=&quot;style2&quot;&gt;1. swap 메모리 비활성화&lt;/blockquote&gt;
&lt;pre id=&quot;code_1749109899525&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo swapoff -a
sudo sed -i '/ swap / s/^/#/' /etc/fstab&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;2. 커널 모듈 활성화&lt;/blockquote&gt;
&lt;pre id=&quot;code_1749109915956&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo modprobe overlay &amp;amp;&amp;amp; sudo modprobe br_netfilter&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;3. 아이피(IP) 설정 파일&lt;/blockquote&gt;
&lt;pre id=&quot;code_1749109948236&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# /etc/sysctl.d/99-k8s.conf
net.bridge.bridge-nf-call-iptables = 1	
net.ipv4.ip_forward = 1	
net.bridge.bridge-nf-call-ip6tables = 1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/etc/sysctl.d/ 경로에 만들어주면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1749109979758&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo sysctl --system&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 나서 적용되게 해주시면 되구요.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;4. Containerd Install&lt;/blockquote&gt;
&lt;pre id=&quot;code_1749110077042&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 &amp;amp;&amp;amp; sudo apt-get install -y containerd
sudo mkdir -p /etc/containerd
sudo containerd config default | sudo tee /etc/containerd/config.toml
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
sudo systemctl restart containerd &amp;amp;&amp;amp; sudo systemctl enable containerd&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;5. kubeadm &amp;middot; kubelet &amp;middot; kubectl install&lt;/blockquote&gt;
&lt;pre id=&quot;code_1749110150483&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 -d /etc/apt/keyrings

# ② 커뮤니티 저장소용 GPG 키 내려받아 dearmor
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.33/deb/Release.key |
  sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-archive-keyring.gpg
sudo chmod go+r /etc/apt/keyrings/kubernetes-archive-keyring.gpg

# ③ APT 저장소 정의
echo &quot;deb [signed-by=/etc/apt/keyrings/kubernetes-archive-keyring.gpg] \
https://pkgs.k8s.io/core:/stable:/v1.33/deb/ /&quot; | \
  sudo tee /etc/apt/sources.list.d/kubernetes.list
  
sudo apt-get update

# (가장 간단) 최신 패치 수준 설치
sudo apt-get install -y kubelet kubeadm kubectl

# (지정 버전이 필요할 때) 먼저 버전을 확인하고 설치
apt-cache madison kubelet | head        # 1.33.1-1.1 같은 형식 확인
sudo apt-get install -y kubelet=1.33.1-1.1 kubeadm=1.33.1-1.1 kubectl=1.33.1-1.1&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;6. Version Hold&lt;/blockquote&gt;
&lt;pre id=&quot;code_1749110188314&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo apt-mark hold kubelet kubeadm kubectl&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;7. Cluster Join&lt;/blockquote&gt;
&lt;pre id=&quot;code_1749110219339&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# control plane node
sudo kubeadm token create --print-join-command&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 명령어는 control plane node에서 진행해주셔야 합니다. 클러스터로 가입할 키를 발급하는 명령어입니다. 그럼 위 결과로 kubeadm join &amp;lt;IP&amp;gt;:&amp;lt;port&amp;gt; &amp;mdash;token :~ 와 같은 형태가 나타나게 되구요. 잘 복사해주세요.&lt;/p&gt;
&lt;pre id=&quot;code_1749110299890&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# worker node
sudo kubeadm join ~~~
sudo systemctl restart kubelet&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;워커 노드에서 복사한 구문을 입력해주시면 됩니다.&lt;/p&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;8. Confirm Node&lt;/blockquote&gt;
&lt;pre id=&quot;code_1749110350304&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl get nodes -o wide&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;control plane node에서 이렇게 확인해보시면 클러스터로 추가된걸 확인하실 수 있습니다.&lt;/p&gt;</description>
      <category>Server</category>
      <author>dev-hahm</author>
      <guid isPermaLink="true">https://dev-hahm.tistory.com/27</guid>
      <comments>https://dev-hahm.tistory.com/27#entry27comment</comments>
      <pubDate>Thu, 5 Jun 2025 17:00:38 +0900</pubDate>
    </item>
    <item>
      <title>쿠버네티스(k8s) 온프레미스 환경으로 설치하기</title>
      <link>https://dev-hahm.tistory.com/26</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;현재 새로운 프로젝트에 앞서 docker compose를 이용해서 멀티 서버 환경을 구성했습니다. 서버에 장애를 확인하기 위해 모니터링까지 구축해놨지만 장애에 유기적으로 대응하는 부분은 부족하다고 느껴져 k8s에 대해 뒤적뒤적 거리게 되었습니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;k8s를 구축하는데 있어서 클라우드를 활용할 수 있겠지만 우리의 경우 보안으로 인해 클라우드 환경은 이용할 수 없다는 제약이 존재 합니다.&lt;br&gt;그럼 결국 On-premise 환경으로 구축을 해야한다는 것을 의미합니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;이번 k8s은 1.33.1 버전을 이용하여 구축하게 되었습니다. (2025-06-04 기준 최신버전)&lt;br&gt;&amp;nbsp;&lt;br&gt;우선은 서버1대 (Ubuntu 24.04 LTS)에 단일 노드 구성으로 진행하고 추후에 노드를 늘려가는식으로 진행하려 합니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;blockquote data-ke-style=&quot;style2&quot;&gt;1. 시스템 준비&lt;/blockquote&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;sudo swapoff -a
sudo sed -i '/ swap / s/^/#/' /etc/fstab&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# 영구 비활성화
sudo modprobe overlay br_netfilter
cat &amp;lt;&amp;lt;EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables&amp;nbsp;&amp;nbsp;= 1
net.ipv4.ip_forward&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; = 1
net.bridge.bridge-nf-call-ip6tables = 1
EOF
sudo sysctl --system&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 k8s는 swap 메모리를 비활성화 해야합니다. (swap 메모리를 간단히 얘기하면 RAM 용량이 부족할때 디스크용량을 추가로 이용하겠다는 개념입니다.)&lt;/p&gt;&lt;blockquote data-ke-style=&quot;style2&quot;&gt;2. Containerd Install&lt;/blockquote&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;sudo apt-get update &amp;amp;&amp;amp; sudo apt-get install -y containerd
sudo mkdir -p /etc/containerd
sudo containerd config default | sudo tee /etc/containerd/config.toml
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
sudo systemctl restart containerd &amp;amp;&amp;amp; sudo systemctl enable containerd&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;최신 k8s은 docker container를 사용하지않고 Containerd를 자체적으로 사용합니다. (docker container로 내부적으론 containerd를 호출 하는 방식으로 알고 있습니다.)&lt;br&gt;&amp;nbsp;&lt;br&gt;그리고 config.toml파일에 SystemdCgroup = true로 변경해주어야 합니다.&lt;/p&gt;&lt;blockquote data-ke-style=&quot;style2&quot;&gt;3. kubelet, kubeadm, kubectl Install&lt;/blockquote&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# ① 키 보관 디렉터리 생성
sudo install -m 0755 -d /etc/apt/keyrings

# ② 커뮤니티 저장소용 GPG 키 내려받아 dearmor
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.33/deb/Release.key |
&amp;nbsp;&amp;nbsp;sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-archive-keyring.gpg
sudo chmod go+r /etc/apt/keyrings/kubernetes-archive-keyring.gpg

# ③ APT 저장소 정의
echo &quot;deb [signed-by=/etc/apt/keyrings/kubernetes-archive-keyring.gpg] \
https://pkgs.k8s.io/core:/stable:/v1.33/deb/ /&quot; | \
&amp;nbsp;&amp;nbsp;sudo tee /etc/apt/sources.list.d/kubernetes.list
&amp;nbsp;&amp;nbsp;
sudo apt-get update

# (가장 간단) 최신 패치 수준 설치
sudo apt-get install -y kubelet kubeadm kubectl

# (지정 버전이 필요할 때) 먼저 버전을 확인하고 설치
apt-cache madison kubelet | head&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# 1.33.1-1.1 같은 형식 확인
sudo apt-get install -y kubelet=1.33.1-1.1 kubeadm=1.33.1-1.1 kubectl=1.33.1-1.1&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;blockquote data-ke-style=&quot;style2&quot;&gt;4. Version Hold&lt;/blockquote&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;sudo apt-mark hold kubelet kubeadm kubectl&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;혹시 모를 버전 변경을 방지하고자 버전 유지를 진행합니다.&lt;/p&gt;&lt;blockquote data-ke-style=&quot;style2&quot;&gt;5. Cluster Init&lt;/blockquote&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;sudo kubeadm init \
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--pod-network-cidr=10.244.0.0/16 \
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--kubernetes-version=1.33.1
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
# 완료 메시지에 kubeadm join 명령(토큰, hash 포함)이 출력되므로 기록.&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 발생하는 토큰은 나중에 새로운 노드를 클러스터로 join할때 사용하게 됩니다. hash값은 내가 연결하려는 API서버가 올바른 CA 인증서를 가진 클러스터인지를 검증하기 위한 공개키 지문입니다. 이 또한 클러스터로 join할때 사용됩니다.&lt;/p&gt;&lt;blockquote data-ke-style=&quot;style2&quot;&gt;6. kubectl 환경 구성&lt;/blockquote&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config&lt;/code&gt;&lt;/pre&gt;&lt;blockquote data-ke-style=&quot;style2&quot;&gt;7. CNI적용 (e.g., Calico)&lt;/blockquote&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.28.0/manifests/calico.yaml&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;CNI(Container Network Interface)에 줄임말이다. CNI는 새 파드 생성 시 가상 NIC-IP 라우팅 할당을 담당하며 파드 삭제 시 정리도 맡아준다.&amp;nbsp;&lt;/p&gt;&lt;blockquote data-ke-style=&quot;style2&quot;&gt;8. 단일노드 스케줄링 허용&lt;/blockquote&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;kubectl taint nodes --all node-role.kubernetes.io/control-plane-&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;kubeadm은 기본적으로 제어플레인 노드에 &lt;span style=&quot;background-color: #000000;&quot;&gt;&lt;span style=&quot;color: #eb5757;&quot;&gt;node-role.kubernetes.io/control-plane:NoSchedule&lt;/span&gt;&lt;/span&gt; taint를 자동 추가합니다.&lt;br&gt;이는 &quot;시스템 파드만 올라오고 일반 워크로드 파드는 배치 금지&quot;라는 정책입니다. 현재는 단일노드로 일반 파드까지 띄워보기 위해서는 해당 정책을 꺼준다고 생각하면 됩니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Server</category>
      <category>k8s</category>
      <category>쿠버네티스</category>
      <author>dev-hahm</author>
      <guid isPermaLink="true">https://dev-hahm.tistory.com/26</guid>
      <comments>https://dev-hahm.tistory.com/26#entry26comment</comments>
      <pubDate>Wed, 4 Jun 2025 17:15:29 +0900</pubDate>
    </item>
    <item>
      <title>DTO vs VO</title>
      <link>https://dev-hahm.tistory.com/25</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;실무에서 DTO와 VO를 구분짓지 않고 비슷하게 사용하는 경우가 많다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이 둘은 명칭이 다른거처럼 사용하기위한 목적이 엄연히 다르다. 바로 요약부터 하자면 아래와 같다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 147px;&quot; border=&quot;1&quot; data-end=&quot;505&quot; data-start=&quot;110&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #333333; text-align: start;&quot;&gt; 항목 &lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #333333; text-align: start;&quot;&gt; VO (Value Object) &lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #333333; text-align: start;&quot;&gt; DTO (Data Transfer Object) &lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot; data-end=&quot;292&quot; data-start=&quot;226&quot;&gt;
&lt;td style=&quot;height: 21px;&quot; data-end=&quot;231&quot; data-start=&quot;226&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;목적&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot; data-end=&quot;260&quot; data-start=&quot;231&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;값을 표현&lt;/b&gt;하는 객체 (불변, 의미 중심)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot; data-end=&quot;292&quot; data-start=&quot;260&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;데이터 전송&lt;/b&gt;을 위한 객체 (레이어 간 전달)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot; data-end=&quot;345&quot; data-start=&quot;293&quot;&gt;
&lt;td style=&quot;height: 21px;&quot; data-end=&quot;298&quot; data-start=&quot;293&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;용도&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot; data-end=&quot;314&quot; data-start=&quot;298&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;도메인 모델의 구성 요소&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot; data-end=&quot;345&quot; data-start=&quot;314&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;계층 간 (예: 컨트롤러 &amp;harr; 서비스) 데이터 전달&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot; data-end=&quot;398&quot; data-start=&quot;346&quot;&gt;
&lt;td style=&quot;height: 21px;&quot; data-end=&quot;352&quot; data-start=&quot;346&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;불변성&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot; data-end=&quot;379&quot; data-start=&quot;352&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;원칙적으로 불변&lt;/b&gt; (immutable)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot; data-end=&quot;398&quot; data-start=&quot;379&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;변경 가능 (mutable)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot; data-end=&quot;451&quot; data-start=&quot;399&quot;&gt;
&lt;td style=&quot;height: 21px;&quot; data-end=&quot;417&quot; data-start=&quot;399&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;equals/hashCode&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot; data-end=&quot;439&quot; data-start=&quot;417&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;값 기준 비교&lt;/b&gt; (필수 구현)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot; data-end=&quot;451&quot; data-start=&quot;439&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;주로 필요 없음&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot; data-end=&quot;505&quot; data-start=&quot;452&quot;&gt;
&lt;td style=&quot;height: 21px;&quot; data-end=&quot;457&quot; data-start=&quot;452&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;위치&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot; data-end=&quot;469&quot; data-start=&quot;457&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;도메인 모델 내부&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot; data-end=&quot;505&quot; data-start=&quot;469&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;계층 사이 (Controller, Service, etc)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이렇게 봐도 사실 크게 와닿지 않을 수 있다. 추가로 예시코드까지 봐보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이메일에 대한 VO를 예시로 보면&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1744778920999&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Embeddable
public class Email {
    private String value;

    protected Email() {} // JPA 기본 생성자

    public Email(String value) {
        if (!value.matches(&quot;^[A-Za-z0-9+_.-]+@(.+)$&quot;)) {
            throw new IllegalArgumentException(&quot;Invalid email format&quot;);
        }
        this.value = value;
    }

    public String getValue() {
        return value;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Email)) return false;
        Email email = (Email) o;
        return Objects.equals(value, email.value);
    }

    @Override
    public int hashCode() {
        return Objects.hash(value);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이메일에 대한 validation과 equals, hashCode 함수를 구현해놨다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1744779111695&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class UserRequestDto {
    private String email;
    private String password;
    private String name;

    // getter/setter
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;단순히 &lt;/span&gt;요청에대한 값을 담고 get / set 하고 있다.&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 style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그럼 여기서 의문점이 하나 생긴다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&quot;그럼 DTO에 필드에다가 @NotBlank, @Eamil 등... validation 어노테이션을 사용하면 되는거 아닌가?&quot;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;물론 기능적으로 보면 가능하다고 할 수 있지만 위에서 얘기한바와 같이 vo와 dto는 목적이 다르게 생긴거다.&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;vo는 유효성 검사를 넘어 '도메인 모델링'의 목적이 있다. 더 설명을 보태자면.&lt;/span&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt; 1. DTO Validation (@Valid, @Email 등)&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1744780466906&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class CreateUserRequest {
    @NotBlank
    @Email
    private String email;

    @NotBlank
    private String name;

    // getter/setter
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-end=&quot;580&quot; data-start=&quot;572&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;✅ 장점&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;658&quot; data-start=&quot;581&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;604&quot; data-start=&quot;581&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;간단하고 빠르게 유효성 검증 적용 가능&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;633&quot; data-start=&quot;605&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Spring에서 자동으로 @Valid 처리됨&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;658&quot; data-start=&quot;634&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;컨트롤러에서의 잘못된 요청을 쉽게 걸러냄&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;668&quot; data-start=&quot;660&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;❌ 한계&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;784&quot; data-start=&quot;669&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;702&quot; data-start=&quot;669&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;오직 요청 처리 계층에서만 적용됨 (컨트롤러)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;750&quot; data-start=&quot;703&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;서비스, 도메인 레이어에선 &lt;b&gt;다시 검증하거나 검증 안 한 채 사용될 수 있음&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;784&quot; data-start=&quot;751&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;재사용 어려움: 다른 곳에서 같은 규칙을 또 정의해야 함&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-end=&quot;824&quot; data-start=&quot;791&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;  2. VO Validation (생성자에서 검사)&lt;/span&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1744780191143&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Email {
    private final String value;

    public Email(String value) {
        if (!value.matches(&quot;^[A-Za-z0-9+_.-]+@(.+)$&quot;)) {
            throw new IllegalArgumentException(&quot;Invalid email format&quot;);
        }
        this.value = value;
    }

    public String getValue() { return value; }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-end=&quot;1157&quot; data-start=&quot;1149&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;✅ 장점&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1330&quot; data-start=&quot;1158&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1186&quot; data-start=&quot;1158&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;한 번 생성되면 유효함이 보장된 객체&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;1228&quot; data-start=&quot;1187&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Service, Domain, Entity 어디서든 일관되게 사용 가능&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;1294&quot; data-start=&quot;1229&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;도메인 개념으로써의 &lt;b&gt;의미 전달력이 강력&lt;/b&gt; (String 대신 Email이라는 타입 자체가 의미 있음)&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;1330&quot; data-start=&quot;1295&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;테스트 코드나 다른 계층에서도 &lt;b&gt;재사용성, 안전성&lt;/b&gt; 확보&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;1340&quot; data-start=&quot;1332&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;❌ 단점&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1372&quot; data-start=&quot;1341&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1351&quot; data-start=&quot;1341&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;클래스가 많아짐&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;1372&quot; data-start=&quot;1352&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;단순한 프로젝트에선 과할 수 있음&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;요약 정리&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;VO는 &lt;b&gt;Validation 기능 + 도메인 설계 가치를 동시에&lt;/b&gt; 제공하는 도구이다. 단순히 &amp;ldquo;검증만 되면 된다&amp;rdquo;는 관점에서 한 발 더 나아가 **&amp;ldquo;의도를 코드로 표현&amp;rdquo;**하고 싶은 경우에 VO는 훨씬 유의미합니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>JAVA</category>
      <category>DTO</category>
      <category>java</category>
      <category>Vo</category>
      <author>dev-hahm</author>
      <guid isPermaLink="true">https://dev-hahm.tistory.com/25</guid>
      <comments>https://dev-hahm.tistory.com/25#entry25comment</comments>
      <pubDate>Wed, 16 Apr 2025 14:13:21 +0900</pubDate>
    </item>
    <item>
      <title>React 프로젝트 생성 및 React 버전 다운그레이드 방법</title>
      <link>https://dev-hahm.tistory.com/24</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;React를 사용해 프론트엔드 프로젝트를 시작하면서 최신 버전의 React(2024년 12월 27일 기준, React 19 버전)에서 문제가 발생한 경험을 공유합니다. 이를 해결하기 위해 React의 버전을 낮춰 프로젝트를 설정하는 방법을 정리했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;1. 프로젝트 폴더 생성&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;먼저 작업을 진행할 프로젝트 폴더를 생성합니다. 아래 명령어를 순서대로 실행하세요.&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;pre id=&quot;code_1735275566104&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cd \ 
mkdir app\frontend 
cd app\frontend&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;위 명령어를 실행하면 C:\app\frontend 폴더가 생성됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;2. React 프로젝트 설치&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;React 프로젝트를 초기화하려면 npx create-react-app 명령어를 실행합니다.&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;pre id=&quot;code_1735275593644&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npx create-react-app .&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;create-react-app 명령어는 프로젝트 초기 설정을 자동으로 구성해주며, React의 최신 버전(2024년 12월 27일 기준, React 19 버전)을 설치합니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;3. React 버전 문제 및 해결 방법&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;React 19 버전에서 문제가 발생하는 경우, React와 React DOM의 버전을 낮춰야 합니다. 아래 명령어를 통해 React와 React DOM의 버전을 18.3.1로 다운그레이드할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1735275611059&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install react@18.3.1 react-dom@18.3.1&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;4. package.json 확인&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;버전이 제대로 적용되었는지 확인하려면 프로젝트 폴더의 package.json 파일을 열어 아래 내용을 확인합니다.&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1735275660027&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;dependencies&quot;: { 
    &quot;react&quot;: &quot;18.3.1&quot;, 
    &quot;react-dom&quot;: &quot;18.3.1&quot;, ... 
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;React와 React DOM의 버전이 18.3.1로 표시된다면 성공적으로 버전이 낮춰진 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;결론&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;React 프로젝트 초기 설정 시, 최신 버전의 React에서 문제가 발생할 수 있습니다. 이럴 때 React와 React DOM의 버전을 낮추는 방법을 통해 문제를 해결할 수 있습니다. 위 단계를 참고하여 안정적인 환경에서 React 프로젝트를 시작해보세요!  &lt;/span&gt;&lt;/p&gt;</description>
      <category>JS</category>
      <category>init</category>
      <category>React</category>
      <category>react-19</category>
      <author>dev-hahm</author>
      <guid isPermaLink="true">https://dev-hahm.tistory.com/24</guid>
      <comments>https://dev-hahm.tistory.com/24#entry24comment</comments>
      <pubDate>Fri, 27 Dec 2024 14:01:23 +0900</pubDate>
    </item>
  </channel>
</rss>