<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>bab_shunn.log</title>
        <link>https://velog.io/</link>
        <description>마트 시식코너같은 저의 벨로그에 어서오세요.</description>
        <lastBuildDate>Sun, 24 May 2026 03:58:49 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>bab_shunn.log</title>
            <url>https://velog.velcdn.com/images/bab_shunn/profile/157c86f3-9db9-46cb-bfaf-01834628cea1/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. bab_shunn.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/bab_shunn" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[인공신경망 핵심 개념 및 시험 대비 자료]]></title>
            <link>https://velog.io/@bab_shunn/%EC%9D%B8%EA%B3%B5%EC%8B%A0%EA%B2%BD%EB%A7%9D-%ED%95%B5%EC%8B%AC-%EA%B0%9C%EB%85%90-%EB%B0%8F-%EC%8B%9C%ED%97%98-%EB%8C%80%EB%B9%84-%EC%9E%90%EB%A3%8C</link>
            <guid>https://velog.io/@bab_shunn/%EC%9D%B8%EA%B3%B5%EC%8B%A0%EA%B2%BD%EB%A7%9D-%ED%95%B5%EC%8B%AC-%EA%B0%9C%EB%85%90-%EB%B0%8F-%EC%8B%9C%ED%97%98-%EB%8C%80%EB%B9%84-%EC%9E%90%EB%A3%8C</guid>
            <pubDate>Sun, 24 May 2026 03:58:49 GMT</pubDate>
            <description><![CDATA[<h1 id=""></h1>
<blockquote>
<p>기준:  교수님 출제 언급 항목 중심<br>구성: ① 암기해야 할 개념·공식 정리 ② 예상 문제 및 해설</p>
</blockquote>
<hr>
<h2 id="1-암기해야-할-개념과-공식-정리">1. 암기해야 할 개념과 공식 정리</h2>
<h3 id="11-강한-인공지능과-약한-인공지능">1.1 강한 인공지능과 약한 인공지능</h3>
<table>
<thead>
<tr>
<th>구분</th>
<th>의미</th>
<th>예시</th>
</tr>
</thead>
<tbody><tr>
<td>강한 인공지능</td>
<td>인간처럼 다양한 지능을 종합적으로 수행하는 인공지능</td>
<td>영화 속 인조인간, 터미네이터형 AI</td>
</tr>
<tr>
<td>약한 인공지능</td>
<td>한 가지 기능이나 특정 문제 해결에 특화된 인공지능</td>
<td>알파고, 음성인식 스피커, 번역기, 영상 인식기</td>
</tr>
</tbody></table>
<p><strong>시험 포인트</strong><br>예제를 보고 “강한 AI인지, 약한 AI인지” 구별할 수 있어야 한다. 현재 대부분의 AI 제품은 특정 기능에 특화되어 있으므로 <strong>약한 인공지능</strong>에 해당한다.</p>
<hr>
<h3 id="12-1차-인공지능-겨울의-시작-계기">1.2 1차 인공지능 겨울의 시작 계기</h3>
<ul>
<li>일반적인 시험 답안: <strong>라이트힐 보고서</strong>를 계기로 인공지능에 대한 기대가 꺾이고 지원이 줄어들면서 1차 인공지능 겨울이 시작되었다.</li>
<li>함께 알아둘 내용: 1969년 민스키와 페퍼트의 『Perceptrons』는 퍼셉트론이 XOR 문제를 해결하지 못하는 선형 분류기에 불과하다는 한계를 지적했고, 이는 신경망 연구 퇴조에 영향을 주었다.</li>
</ul>
<p><strong>한 줄 암기</strong><br>1차 인공지능 겨울은 <strong>과도한 기대에 비해 성과가 부족했고, 라이트힐 보고서 등으로 지원이 축소되면서 시작</strong>되었다.</p>
<hr>
<h3 id="13-인공지능-기계-학습-신경망-딥러닝의-포함-관계">1.3 인공지능, 기계 학습, 신경망, 딥러닝의 포함 관계</h3>
<p>포함 관계는 다음과 같다.</p>
<pre><code class="language-text">인공지능 ⊃ 기계 학습 ⊃ 신경망 ⊃ 딥러닝</code></pre>
<ul>
<li><strong>인공지능</strong>: 인간의 지능적 기능을 컴퓨터로 구현하려는 큰 분야</li>
<li><strong>기계 학습</strong>: 데이터를 이용해 모델이 규칙을 학습하는 방법</li>
<li><strong>신경망</strong>: 뉴런 구조에서 영감을 받은 기계 학습 모델 계열</li>
<li><strong>딥러닝</strong>: 신경망에 층을 많이 쌓아 깊게 만든 방법</li>
</ul>
<hr>
<h3 id="14-데이터셋의-표현-특징-벡터와-레이블">1.4 데이터셋의 표현: 특징 벡터와 레이블</h3>
<p>기계 학습에서 하나의 샘플은 보통 다음처럼 표현한다.</p>
<pre><code class="language-text">특징 벡터: x = (x1, x2, ..., xd)
레이블: y</code></pre>
<ul>
<li><code>d</code>: 특징의 개수, 즉 특징 벡터의 차원</li>
<li><code>x</code>: 샘플을 수치로 표현한 특징 벡터</li>
<li><code>y</code>: 해당 샘플의 정답 부류</li>
</ul>
<p>예를 들어 iris 데이터에서 특징이 4개라면,</p>
<pre><code class="language-text">x = (꽃받침 길이, 꽃받침 너비, 꽃잎 길이, 꽃잎 너비)</code></pre>
<hr>
<h3 id="15-원핫-코드">1.5 원핫 코드</h3>
<p>레이블이나 이름형 범주형 특징은 원핫 코드로 표현할 수 있다.</p>
<p>예: 붓꽃 3개 부류</p>
<table>
<thead>
<tr>
<th>부류</th>
<th>원핫 코드</th>
</tr>
</thead>
<tbody><tr>
<td>Setosa</td>
<td>(1, 0, 0)</td>
</tr>
<tr>
<td>Versicolor</td>
<td>(0, 1, 0)</td>
</tr>
<tr>
<td>Virginica</td>
<td>(0, 0, 1)</td>
</tr>
</tbody></table>
<p>예: 혈액형</p>
<table>
<thead>
<tr>
<th>혈액형</th>
<th>원핫 코드</th>
</tr>
</thead>
<tbody><tr>
<td>A형</td>
<td>(1, 0, 0, 0)</td>
</tr>
<tr>
<td>B형</td>
<td>(0, 1, 0, 0)</td>
</tr>
<tr>
<td>O형</td>
<td>(0, 0, 1, 0)</td>
</tr>
<tr>
<td>AB형</td>
<td>(0, 0, 0, 1)</td>
</tr>
</tbody></table>
<p><strong>시험 포인트</strong>  </p>
<ul>
<li>수치형 특징: 실수, 정수, 이진값처럼 거리 개념이 있는 특징</li>
<li>순서형 범주형 특징: 학점, 수능 등급처럼 순서가 있는 특징. 정수 부여 가능</li>
<li>이름형 범주형 특징: 혈액형, 지역처럼 거리 개념이 없는 특징. <strong>원핫 코드가 적절</strong></li>
</ul>
<hr>
<h3 id="16-다차원-특징-공간에서-유클리디언-거리">1.6 다차원 특징 공간에서 유클리디언 거리</h3>
<p>두 점이 다음과 같을 때,</p>
<pre><code class="language-text">x = (x1, x2, ..., xd)
y = (y1, y2, ..., yd)</code></pre>
<p>두 점 사이의 유클리디언 거리는 다음과 같다.</p>
<pre><code class="language-text">d(x, y) = sqrt((x1-y1)^2 + (x2-y2)^2 + ... + (xd-yd)^2)
        = sqrt(Σ(i=1 to d) (xi-yi)^2)</code></pre>
<p>예:</p>
<pre><code class="language-text">x = (1, 2, 3), y = (4, 6, 3)
d(x,y) = sqrt((1-4)^2 + (2-6)^2 + (3-3)^2)
       = sqrt(9 + 16 + 0)
       = 5</code></pre>
<hr>
<h3 id="17-혼동-행렬과-fn-fp-tp-tn">1.7 혼동 행렬과 FN, FP, TP, TN</h3>
<p>이진 분류에서는 먼저 무엇을 <strong>긍정 positive</strong>으로 볼지 정해야 한다. 예를 들어 환자 검출 문제라면 “환자”가 긍정이다.</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td>TP</td>
<td>실제 긍정이고, 예측도 긍정</td>
</tr>
<tr>
<td>FN</td>
<td>실제 긍정인데, 예측은 부정</td>
</tr>
<tr>
<td>FP</td>
<td>실제 부정인데, 예측은 긍정</td>
</tr>
<tr>
<td>TN</td>
<td>실제 부정이고, 예측도 부정</td>
</tr>
</tbody></table>
<p><strong>거짓 부정 FN</strong><br>실제로는 긍정인데 모델이 부정이라고 예측한 경우이다. 예를 들어 실제 환자인데 정상이라고 판정한 경우가 FN이다.</p>
<hr>
<h3 id="18-성능-측정-공식">1.8 성능 측정 공식</h3>
<pre><code class="language-text">정확률(Accuracy) = (TP + TN) / (TP + TN + FP + FN)

특이도(Specificity) = TN / (TN + FP)

민감도(Sensitivity) = TP / (TP + FN)

정밀도(Precision) = TP / (TP + FP)

재현률(Recall) = TP / (TP + FN)</code></pre>
<ul>
<li>민감도와 재현률은 공식이 같다.</li>
<li>의료 진단에서는 민감도와 특이도가 중요하게 쓰인다.</li>
<li>정보검색에서는 정밀도와 재현률이 중요하게 쓰인다.</li>
</ul>
<hr>
<h3 id="19-정확률만으로-평가하면-안-되는-이유">1.9 정확률만으로 평가하면 안 되는 이유</h3>
<p>정확률은 전체 샘플 중 맞힌 비율이다. 하지만 <strong>부류가 불균형한 경우</strong> 모델 성능을 제대로 반영하지 못한다.</p>
<p>예:</p>
<pre><code class="language-text">정상인 990명, 환자 10명
모델이 모두 정상이라고 예측</code></pre>
<p>이 경우 정확률은 990/1000 = 99%로 매우 높지만, 환자 10명을 모두 놓쳤으므로 민감도는 0이다. 따라서 불균형 데이터에서는 정확률만 보지 말고 <strong>민감도, 특이도, 정밀도, 재현률</strong> 등을 함께 봐야 한다.</p>
<hr>
<h3 id="110-5-겹-교차-검증">1.10 5-겹 교차 검증</h3>
<p>5-겹 교차 검증은 데이터를 5개 부분으로 나누고, 매번 1개 부분을 검증용으로 남겨둔 뒤 나머지 4개로 학습하는 방법이다. 이렇게 총 5번 실험하고 5개의 성능을 평균낸다.</p>
<p><strong>왜 한 번만 구하지 않는가?</strong><br>훈련/테스트 분할이 우연히 좋거나 나쁘게 될 수 있기 때문이다. 교차 검증은 여러 번 나누어 평가한 뒤 평균을 내므로 우연의 영향을 줄이고 일반화 성능을 더 신뢰성 있게 추정할 수 있다.</p>
<hr>
<h3 id="111-svm의-원리">1.11 SVM의 원리</h3>
<p>SVM의 핵심 목표는 <strong>일반화 능력을 높이는 결정 경계</strong>를 찾는 것이다.</p>
<ul>
<li>SVM은 두 부류 사이의 거리를 최대화하는 결정 경계를 찾는다.</li>
<li>두 부류까지의 거리인 <code>2s</code>를 <strong>여백 margin</strong>이라고 한다.</li>
<li>여백이 클수록 새로운 데이터가 조금 변해도 결정 경계를 넘을 가능성이 낮아 일반화 능력이 좋아진다.</li>
</ul>
<p><strong>x의 역할</strong><br><code>x</code>는 샘플을 표현한 <strong>특징 벡터</strong>이다. SVM에서는 각 샘플 <code>x</code>가 특징 공간의 한 점으로 놓이고, 모델은 이 점들을 잘 나누는 결정 경계를 찾는다.</p>
<hr>
<h3 id="112-퍼셉트론의-구조와-연산">1.12 퍼셉트론의 구조와 연산</h3>
<p>퍼셉트론은 입력층과 출력층으로 구성된 이진 분류기이다.</p>
<ul>
<li>입력층: <code>d+1</code>개의 노드</li>
<li><code>d</code>: 특징 벡터의 차원</li>
<li><code>x0 = 1</code>: 바이어스 노드</li>
<li>각 입력 <code>xi</code>는 가중치 <code>wi</code>와 연결됨</li>
</ul>
<p>퍼셉트론은 다음 값을 계산한다.</p>
<pre><code class="language-text">s = w0x0 + w1x1 + ... + wdxd</code></pre>
<p>그리고 계단 함수를 적용한다.</p>
<pre><code class="language-text">τ(s) = +1, if s &gt; 0
τ(s) = -1, if s &lt;= 0</code></pre>
<p>즉 퍼셉트론은 특징 벡터를 <code>+1</code> 또는 <code>-1</code>로 분류하는 장치이다.</p>
<hr>
<h3 id="113-or-게이트와-and-게이트-구별">1.13 OR 게이트와 AND 게이트 구별</h3>
<h4 id="or-게이트-예시">OR 게이트 예시</h4>
<pre><code class="language-text">w0 = -0.5, w1 = 1, w2 = 1
s = -0.5 + x1 + x2</code></pre>
<table>
<thead>
<tr>
<th>x1</th>
<th>x2</th>
<th>s</th>
<th>출력</th>
</tr>
</thead>
<tbody><tr>
<td>0</td>
<td>0</td>
<td>-0.5</td>
<td>-1</td>
</tr>
<tr>
<td>0</td>
<td>1</td>
<td>0.5</td>
<td>+1</td>
</tr>
<tr>
<td>1</td>
<td>0</td>
<td>0.5</td>
<td>+1</td>
</tr>
<tr>
<td>1</td>
<td>1</td>
<td>1.5</td>
<td>+1</td>
</tr>
</tbody></table>
<p>하나라도 1이면 +1이므로 OR 게이트이다.</p>
<h4 id="and-게이트-예시">AND 게이트 예시</h4>
<pre><code class="language-text">w0 = -1.5, w1 = 1, w2 = 1
s = -1.5 + x1 + x2</code></pre>
<table>
<thead>
<tr>
<th>x1</th>
<th>x2</th>
<th>s</th>
<th>출력</th>
</tr>
</thead>
<tbody><tr>
<td>0</td>
<td>0</td>
<td>-1.5</td>
<td>-1</td>
</tr>
<tr>
<td>0</td>
<td>1</td>
<td>-0.5</td>
<td>-1</td>
</tr>
<tr>
<td>1</td>
<td>0</td>
<td>-0.5</td>
<td>-1</td>
</tr>
<tr>
<td>1</td>
<td>1</td>
<td>0.5</td>
<td>+1</td>
</tr>
</tbody></table>
<p>둘 다 1일 때만 +1이므로 AND 게이트이다.</p>
<hr>
<h3 id="114-경사-하강법의-규칙">1.14 경사 하강법의 규칙</h3>
<p>경사 하강법은 손실 함수 <code>J</code>를 줄이는 방향으로 가중치를 조금씩 수정하는 방법이다.</p>
<p>일반적으로는 다음처럼 쓴다.</p>
<pre><code class="language-text">w ← w - ρ∇J</code></pre>
<p>수업 자료의 표현으로 쓰면,</p>
<pre><code class="language-text">w ← w + ρ(-∇J)</code></pre>
<ul>
<li><code>ρ</code>: 학습률 learning rate</li>
<li><code>∇J</code>: 손실 함수의 기울기</li>
<li>기울기의 반대 방향으로 이동해야 손실이 줄어든다.</li>
</ul>
<hr>
<h3 id="115-퍼셉트론-학습-규칙">1.15 퍼셉트론 학습 규칙</h3>
<p>시험에서 식이 제공될 가능성이 있는 핵심 갱신식이다.</p>
<p>틀린 샘플 집합을 <code>I</code>라고 하면,</p>
<pre><code class="language-text">wi ← wi + ρ Σ(x∈I) y xi</code></pre>
<p>행렬 형태로는,</p>
<pre><code class="language-text">W ← W + ρ Σ(x∈I) yx</code></pre>
<p>샘플 하나만 업데이트한다면,</p>
<pre><code class="language-text">w_new = w_old + ρ y x</code></pre>
<p><strong>주의</strong><br>바이어스까지 포함하려면 <code>x0 = 1</code>을 포함한 벡터로 계산해야 한다.</p>
<hr>
<h3 id="116-세대-epoch">1.16 세대 epoch</h3>
<p>훈련 집합에 있는 샘플 전체를 한 번 처리하는 일을 <strong>세대 epoch</strong>라고 한다.</p>
<p>예:</p>
<pre><code class="language-text">훈련 데이터가 1000개이고, 1000개를 모두 한 번 학습에 사용했다면 1 epoch</code></pre>
<hr>
<h3 id="117-신경망-학습-알고리즘의-반복-절차">1.17 신경망 학습 알고리즘의 반복 절차</h3>
<p>신경망 학습은 다음 절차를 반복한다.</p>
<pre><code class="language-text">전방 계산 → 오차 계산 → 후방 가중치 갱신</code></pre>
<ul>
<li>전방 계산: 입력을 넣어 예측값을 계산</li>
<li>오차 계산: 예측값과 실제 레이블의 차이를 계산</li>
<li>후방 가중치 갱신: 오차를 줄이는 방향으로 가중치 수정</li>
</ul>
<hr>
<h3 id="118-스토캐스틱-경사-하강법-sgd">1.18 스토캐스틱 경사 하강법 SGD</h3>
<p>SGD는 훈련 집합 전체를 한 번에 사용하지 않고, 일부 샘플 또는 미니 배치를 사용해 가중치를 갱신하는 방법이다.</p>
<ul>
<li>배치 모드: 전체 데이터를 모아 한 번에 갱신</li>
<li>패턴 모드: 샘플 하나마다 갱신</li>
<li>미니 배치: 일정한 크기의 부분 집합마다 갱신</li>
</ul>
<p>부분 집합을 만들 때 랜덤 샘플링을 적용하므로 <strong>스토캐스틱 경사 하강법</strong>이라고 부른다.</p>
<hr>
<h3 id="119-다층-퍼셉트론의-동작">1.19 다층 퍼셉트론의 동작</h3>
<p>다층 퍼셉트론은 입력층, 은닉층, 출력층으로 구성된다.</p>
<p>샘플 하나에 대한 기본 흐름은 다음과 같다.</p>
<pre><code class="language-text">입력 x → 은닉층 z → 출력 o</code></pre>
<p>행렬식으로 단순화하면,</p>
<pre><code class="language-text">z = τ1(U1 x^T)
o = τ2(U2 z^T)</code></pre>
<ul>
<li><code>U1</code>: 입력층과 은닉층 사이의 가중치 행렬</li>
<li><code>U2</code>: 은닉층과 출력층 사이의 가중치 행렬</li>
<li><code>τ1</code>, <code>τ2</code>: 활성 함수</li>
</ul>
<p>다층 퍼셉트론은 원래 특징 공간을 더 분류하기 쉬운 은닉 공간으로 변환한 뒤 최종 분류한다.</p>
<hr>
<h3 id="120-소프트맥스-함수">1.20 소프트맥스 함수</h3>
<p>소프트맥스는 출력값을 확률처럼 해석할 수 있게 바꿔주는 함수이다.</p>
<pre><code class="language-text">ok = exp(sk) / Σ(i=1 to c) exp(si)</code></pre>
<p>특징:</p>
<pre><code class="language-text">o1 + o2 + ... + oc = 1</code></pre>
<p>출력값 중 가장 큰 값의 인덱스를 최종 예측 부류로 선택한다.</p>
<hr>
<h3 id="121-1980년대-깊은-신경망이-잘-학습되지-않은-이유">1.21 1980년대 깊은 신경망이 잘 학습되지 않은 이유</h3>
<p>1980년대에도 깊은 신경망의 아이디어는 있었지만 학습이 잘 되지 않았다.</p>
<p>주요 이유는 다음과 같다.</p>
<ol>
<li><p><strong>그레이디언트 소멸 문제</strong><br>층이 깊어질수록 기울기가 점점 작아져 앞쪽 층의 가중치가 거의 갱신되지 않는다.</p>
</li>
<li><p><strong>작은 데이터셋 문제</strong><br>추정해야 할 매개변수는 많은데 데이터가 적어 과잉 적합이 발생하기 쉽다.</p>
</li>
<li><p><strong>과다한 계산 시간</strong><br>당시에는 GPU 등 고속 연산 장치가 부족해 학습 시간이 너무 오래 걸렸다.</p>
</li>
</ol>
<hr>
<h2 id="2-예상-문제-및-해설">2. 예상 문제 및 해설</h2>
<h3 id="문제-1-강한-인공지능과-약한-인공지능을-구분하시오">문제 1. 강한 인공지능과 약한 인공지능을 구분하시오.</h3>
<p>다음 중 약한 인공지능에 해당하는 것을 모두 고르시오.</p>
<ol>
<li>알파고  </li>
<li>영화 속 인간처럼 사고하고 감정을 느끼는 인조인간  </li>
<li>음성인식 스피커  </li>
<li>특정 언어를 다른 언어로 번역하는 번역기</li>
</ol>
<h4 id="정답">정답</h4>
<p>1, 3, 4</p>
<h4 id="해설">해설</h4>
<p>현재 대부분의 인공지능은 특정 기능에 특화되어 있으므로 약한 인공지능이다. 2번은 다양한 지능을 복합적으로 수행하는 강한 인공지능의 예이다.</p>
<hr>
<h3 id="문제-2-1차-인공지능-겨울이-시작된-계기를-서술하시오">문제 2. 1차 인공지능 겨울이 시작된 계기를 서술하시오.</h3>
<h4 id="모범-답안">모범 답안</h4>
<p>1차 인공지능 겨울은 인공지능에 대한 과도한 기대에 비해 실제 성과가 부족했고, 라이트힐 보고서 등을 계기로 연구 지원이 줄어들면서 시작되었다. 또한 퍼셉트론의 한계가 지적되면서 신경망 연구도 퇴조하였다.</p>
<hr>
<h3 id="문제-3-다음-포함-관계를-올바르게-배열하시오">문제 3. 다음 포함 관계를 올바르게 배열하시오.</h3>
<pre><code class="language-text">신경망, 인공지능, 딥러닝, 기계 학습</code></pre>
<h4 id="정답-1">정답</h4>
<pre><code class="language-text">인공지능 ⊃ 기계 학습 ⊃ 신경망 ⊃ 딥러닝</code></pre>
<hr>
<h3 id="문제-4-특징-벡터의-의미를-설명하시오">문제 4. 특징 벡터의 의미를 설명하시오.</h3>
<h4 id="모범-답안-1">모범 답안</h4>
<p>특징 벡터는 하나의 샘플을 여러 개의 특징 값으로 표현한 벡터이다. 보통 <code>x = (x1, x2, ..., xd)</code>로 나타내며, <code>d</code>는 특징의 개수 또는 특징 벡터의 차원이다.</p>
<hr>
<h3 id="문제-5-다음-두-점-사이의-유클리디언-거리를-구하시오">문제 5. 다음 두 점 사이의 유클리디언 거리를 구하시오.</h3>
<pre><code class="language-text">x = (1, 2, 3)
y = (4, 6, 3)</code></pre>
<h4 id="풀이">풀이</h4>
<pre><code class="language-text">d(x,y) = sqrt((1-4)^2 + (2-6)^2 + (3-3)^2)
       = sqrt(9 + 16 + 0)
       = 5</code></pre>
<h4 id="정답-2">정답</h4>
<p>5</p>
<hr>
<h3 id="문제-6-다음-특징-중-원핫-코드로-표현하는-것이-가장-적절한-것을-고르시오">문제 6. 다음 특징 중 원핫 코드로 표현하는 것이 가장 적절한 것을 고르시오.</h3>
<ol>
<li>키  </li>
<li>몸무게  </li>
<li>혈액형  </li>
<li>시험 점수</li>
</ol>
<h4 id="정답-3">정답</h4>
<ol start="3">
<li>혈액형</li>
</ol>
<h4 id="해설-1">해설</h4>
<p>혈액형은 A, B, O, AB 사이에 거리 개념이 없는 이름형 범주형 특징이므로 원핫 코드가 적절하다.</p>
<hr>
<h3 id="문제-7-거짓-부정-fn이-무엇인지-설명하시오">문제 7. 거짓 부정 FN이 무엇인지 설명하시오.</h3>
<h4 id="모범-답안-2">모범 답안</h4>
<p>거짓 부정 FN은 실제로는 긍정인 샘플을 모델이 부정으로 예측한 경우이다. 예를 들어 실제 환자인데 정상인으로 판정한 경우가 FN이다.</p>
<hr>
<h3 id="문제-8-다음-혼동-행렬에서-정확률-특이도-민감도를-구하시오">문제 8. 다음 혼동 행렬에서 정확률, 특이도, 민감도를 구하시오.</h3>
<pre><code class="language-text">TP = 40
FN = 10
FP = 50
TN = 900</code></pre>
<h4 id="풀이-1">풀이</h4>
<pre><code class="language-text">정확률 = (TP + TN) / (TP + TN + FP + FN)
      = (40 + 900) / (40 + 900 + 50 + 10)
      = 940 / 1000
      = 0.94 = 94%

특이도 = TN / (TN + FP)
      = 900 / (900 + 50)
      = 900 / 950
      ≈ 0.947 = 94.7%

민감도 = TP / (TP + FN)
      = 40 / (40 + 10)
      = 40 / 50
      = 0.8 = 80%</code></pre>
<h4 id="정답-4">정답</h4>
<p>정확률 94%, 특이도 약 94.7%, 민감도 80%</p>
<hr>
<h3 id="문제-9-정확률만으로-모델을-평가하면-안-되는-이유를-설명하시오">문제 9. 정확률만으로 모델을 평가하면 안 되는 이유를 설명하시오.</h3>
<h4 id="모범-답안-3">모범 답안</h4>
<p>정확률은 전체 샘플 중 맞힌 비율이므로 부류가 불균형할 때 성능을 제대로 반영하지 못한다. 예를 들어 정상인이 990명, 환자가 10명인 데이터에서 모든 샘플을 정상으로 예측하면 정확률은 99%이지만 환자를 한 명도 찾지 못하므로 민감도는 0이다. 따라서 정확률뿐 아니라 민감도, 특이도, 정밀도, 재현률 등을 함께 봐야 한다.</p>
<hr>
<h3 id="문제-10-5-겹-교차-검증-결과가-다음과-같을-때-평균-정확률을-구하시오">문제 10. 5-겹 교차 검증 결과가 다음과 같을 때 평균 정확률을 구하시오.</h3>
<pre><code class="language-text">0.90, 0.95, 0.88, 0.92, 0.91</code></pre>
<h4 id="풀이-2">풀이</h4>
<pre><code class="language-text">평균 = (0.90 + 0.95 + 0.88 + 0.92 + 0.91) / 5
     = 4.56 / 5
     = 0.912</code></pre>
<h4 id="정답-5">정답</h4>
<p>0.912, 즉 91.2%</p>
<hr>
<h3 id="문제-11-교차-검증에서-정확률을-한-번만-구하지-않고-평균을-내는-이유를-서술하시오">문제 11. 교차 검증에서 정확률을 한 번만 구하지 않고 평균을 내는 이유를 서술하시오.</h3>
<h4 id="모범-답안-4">모범 답안</h4>
<p>데이터를 한 번만 훈련/테스트로 나누면 우연히 쉬운 테스트 집합이 선택되어 정확률이 높게 나오거나, 반대로 어려운 테스트 집합이 선택되어 낮게 나올 수 있다. 교차 검증은 여러 분할에서 성능을 측정하고 평균을 내므로 우연의 영향을 줄이고 일반화 성능을 더 신뢰성 있게 추정할 수 있다.</p>
<hr>
<h3 id="문제-12-svm에서-여백-margin의-의미를-설명하시오">문제 12. SVM에서 여백 margin의 의미를 설명하시오.</h3>
<h4 id="모범-답안-5">모범 답안</h4>
<p>SVM에서 여백은 결정 경계와 두 부류 사이의 거리이며, 두 부류까지의 거리인 <code>2s</code>를 여백이라고 한다. SVM은 이 여백을 최대화하는 결정 경계를 찾아 일반화 능력을 높인다.</p>
<hr>
<h3 id="문제-13-svm에서-x는-어떤-역할을-하는가">문제 13. SVM에서 x는 어떤 역할을 하는가?</h3>
<h4 id="모범-답안-6">모범 답안</h4>
<p><code>x</code>는 하나의 샘플을 나타내는 특징 벡터이다. SVM은 각 샘플 <code>x</code>를 특징 공간의 한 점으로 보고, 이 점들을 잘 분리하는 결정 경계를 찾는다.</p>
<hr>
<h3 id="문제-14-다음-퍼셉트론의-출력을-구하시오">문제 14. 다음 퍼셉트론의 출력을 구하시오.</h3>
<pre><code class="language-text">x0 = 1, x1 = 0, x2 = 1
w0 = -0.5, w1 = 1, w2 = 1
τ(s) = +1 if s &gt; 0, otherwise -1</code></pre>
<h4 id="풀이-3">풀이</h4>
<pre><code class="language-text">s = w0x0 + w1x1 + w2x2
  = (-0.5)(1) + (1)(0) + (1)(1)
  = 0.5</code></pre>
<p><code>s &gt; 0</code>이므로 출력은 <code>+1</code>이다.</p>
<h4 id="정답-6">정답</h4>
<p>+1</p>
<hr>
<h3 id="문제-15-다음-퍼셉트론은-or-게이트인가-and-게이트인가">문제 15. 다음 퍼셉트론은 OR 게이트인가, AND 게이트인가?</h3>
<pre><code class="language-text">w0 = -0.5, w1 = 1, w2 = 1
s = -0.5 + x1 + x2</code></pre>
<h4 id="풀이-4">풀이</h4>
<table>
<thead>
<tr>
<th>x1</th>
<th>x2</th>
<th>s</th>
<th>출력</th>
</tr>
</thead>
<tbody><tr>
<td>0</td>
<td>0</td>
<td>-0.5</td>
<td>-1</td>
</tr>
<tr>
<td>0</td>
<td>1</td>
<td>0.5</td>
<td>+1</td>
</tr>
<tr>
<td>1</td>
<td>0</td>
<td>0.5</td>
<td>+1</td>
</tr>
<tr>
<td>1</td>
<td>1</td>
<td>1.5</td>
<td>+1</td>
</tr>
</tbody></table>
<p>하나라도 1이면 +1이다.</p>
<h4 id="정답-7">정답</h4>
<p>OR 게이트</p>
<hr>
<h3 id="문제-16-다음-퍼셉트론은-or-게이트인가-and-게이트인가">문제 16. 다음 퍼셉트론은 OR 게이트인가, AND 게이트인가?</h3>
<pre><code class="language-text">w0 = -1.5, w1 = 1, w2 = 1
s = -1.5 + x1 + x2</code></pre>
<h4 id="풀이-5">풀이</h4>
<table>
<thead>
<tr>
<th>x1</th>
<th>x2</th>
<th>s</th>
<th>출력</th>
</tr>
</thead>
<tbody><tr>
<td>0</td>
<td>0</td>
<td>-1.5</td>
<td>-1</td>
</tr>
<tr>
<td>0</td>
<td>1</td>
<td>-0.5</td>
<td>-1</td>
</tr>
<tr>
<td>1</td>
<td>0</td>
<td>-0.5</td>
<td>-1</td>
</tr>
<tr>
<td>1</td>
<td>1</td>
<td>0.5</td>
<td>+1</td>
</tr>
</tbody></table>
<p>둘 다 1일 때만 +1이다.</p>
<h4 id="정답-8">정답</h4>
<p>AND 게이트</p>
<hr>
<h3 id="문제-17-경사-하강법의-기본-아이디어를-설명하시오">문제 17. 경사 하강법의 기본 아이디어를 설명하시오.</h3>
<h4 id="모범-답안-7">모범 답안</h4>
<p>경사 하강법은 손실 함수의 값을 줄이기 위해 현재 위치에서 기울기의 반대 방향으로 매개변수를 조금씩 이동시키는 방법이다. 학습률은 한 번에 얼마나 이동할지를 조절하는 하이퍼 매개변수이다.</p>
<hr>
<h3 id="문제-18-퍼셉트론-학습-규칙을-이용해-가중치를-갱신하시오">문제 18. 퍼셉트론 학습 규칙을 이용해 가중치를 갱신하시오.</h3>
<pre><code class="language-text">현재 가중치 w = (0, 0, 0)
학습률 ρ = 0.1
샘플 x = (1, 1, 0)   # x0 포함
레이블 y = +1</code></pre>
<p>퍼셉트론 학습 규칙:</p>
<pre><code class="language-text">w_new = w_old + ρ y x</code></pre>
<h4 id="풀이-6">풀이</h4>
<pre><code class="language-text">w_new = (0, 0, 0) + 0.1 × (+1) × (1, 1, 0)
      = (0.1, 0.1, 0)</code></pre>
<h4 id="정답-9">정답</h4>
<p><code>w_new = (0.1, 0.1, 0)</code></p>
<hr>
<h3 id="문제-19-퍼셉트론-학습-규칙으로-여러-틀린-샘플을-한-번에-반영하시오">문제 19. 퍼셉트론 학습 규칙으로 여러 틀린 샘플을 한 번에 반영하시오.</h3>
<pre><code class="language-text">현재 가중치 w = (0.2, -0.1, 0.3)
학습률 ρ = 0.5
틀린 샘플 집합 I:
  x(1) = (1, 1, 0), y(1) = +1
  x(2) = (1, 0, 1), y(2) = -1</code></pre>
<p>공식:</p>
<pre><code class="language-text">w_new = w_old + ρ Σ yx</code></pre>
<h4 id="풀이-7">풀이</h4>
<pre><code class="language-text">y(1)x(1) = (+1)(1, 1, 0) = (1, 1, 0)
y(2)x(2) = (-1)(1, 0, 1) = (-1, 0, -1)

Σ yx = (1, 1, 0) + (-1, 0, -1)
     = (0, 1, -1)

ρ Σ yx = 0.5 × (0, 1, -1)
       = (0, 0.5, -0.5)

w_new = (0.2, -0.1, 0.3) + (0, 0.5, -0.5)
      = (0.2, 0.4, -0.2)</code></pre>
<h4 id="정답-10">정답</h4>
<p><code>w_new = (0.2, 0.4, -0.2)</code></p>
<hr>
<h3 id="문제-20-epoch의-의미를-설명하시오">문제 20. epoch의 의미를 설명하시오.</h3>
<h4 id="모범-답안-8">모범 답안</h4>
<p>epoch는 훈련 집합에 있는 전체 샘플을 한 번 처리하는 단위이다. 즉 모든 훈련 샘플을 한 번씩 학습에 사용하면 1 epoch가 끝난 것이다.</p>
<hr>
<h3 id="문제-21-신경망-학습-알고리즘의-세-단계를-순서대로-쓰시오">문제 21. 신경망 학습 알고리즘의 세 단계를 순서대로 쓰시오.</h3>
<h4 id="정답-11">정답</h4>
<pre><code class="language-text">전방 계산 → 오차 계산 → 후방 가중치 갱신</code></pre>
<hr>
<h3 id="문제-22-다음-설명에-해당하는-용어를-쓰시오">문제 22. 다음 설명에 해당하는 용어를 쓰시오.</h3>
<blockquote>
<p>훈련 집합 전체를 한 번에 사용하지 않고, 랜덤 샘플링으로 만든 부분 집합 또는 미니 배치를 이용하여 경사 하강법을 수행하는 방법이다.</p>
</blockquote>
<h4 id="정답-12">정답</h4>
<p>스토캐스틱 경사 하강법, SGD</p>
<hr>
<h3 id="문제-23-다음-다층-퍼셉트론의-은닉층-출력을-구하시오">문제 23. 다음 다층 퍼셉트론의 은닉층 출력을 구하시오.</h3>
<p>계단 함수는 <code>s &gt; 0</code>이면 <code>1</code>, 아니면 <code>-1</code>이라고 하자.</p>
<pre><code class="language-text">x = (1, 0, 1)^T     # bias 포함
U1 = [ [-0.5,  1,  1],
       [ 1.5, -1, -1] ]</code></pre>
<h4 id="풀이-8">풀이</h4>
<pre><code class="language-text">U1 x = [ (-0.5)(1) + (1)(0) + (1)(1),
         (1.5)(1) + (-1)(0) + (-1)(1) ]

     = [ 0.5,
         0.5 ]</code></pre>
<p>둘 다 0보다 크므로 계단 함수를 적용하면,</p>
<pre><code class="language-text">z = (1, 1)</code></pre>
<h4 id="정답-13">정답</h4>
<p><code>z = (1, 1)</code></p>
<hr>
<h3 id="문제-24-다음-값에-소프트맥스를-적용했을-때-가장-높은-확률을-갖는-부류를-고르시오">문제 24. 다음 값에 소프트맥스를 적용했을 때 가장 높은 확률을 갖는 부류를 고르시오.</h3>
<pre><code class="language-text">s = (1, 2, 0)</code></pre>
<h4 id="풀이-9">풀이</h4>
<pre><code class="language-text">exp(1) ≈ 2.718
exp(2) ≈ 7.389
exp(0) = 1
합 = 11.107

softmax ≈ (2.718/11.107, 7.389/11.107, 1/11.107)
        ≈ (0.245, 0.665, 0.090)</code></pre>
<h4 id="정답-14">정답</h4>
<p>두 번째 부류</p>
<hr>
<h3 id="문제-25-1980년대에-깊은-신경망이-이미-등장했지만-학습이-잘-안-된-이유를-서술하시오">문제 25. 1980년대에 깊은 신경망이 이미 등장했지만 학습이 잘 안 된 이유를 서술하시오.</h3>
<h4 id="모범-답안-9">모범 답안</h4>
<p>1980년대에도 다층 퍼셉트론에 은닉층을 많이 두면 깊은 신경망을 만들 수 있었지만, 학습이 잘 되지 않았다. 그 이유는 층이 깊어질수록 그레이디언트가 작아지는 그레이디언트 소멸 문제, 매개변수에 비해 데이터가 적어 발생하는 과잉 적합 문제, 그리고 당시 컴퓨터 성능 부족으로 인한 과다한 계산 시간 때문이었다.</p>
<hr>
<h2 id="3-마지막-암기-체크리스트">3. 마지막 암기 체크리스트</h2>
<p>시험 직전에 아래 문장을 빈칸 없이 말할 수 있으면 된다.</p>
<ol>
<li>약한 인공지능은 특정 기능에 특화된 인공지능이다.</li>
<li>1차 인공지능 겨울은 라이트힐 보고서 등을 계기로 지원이 줄며 시작되었다.</li>
<li>포함 관계는 인공지능 ⊃ 기계 학습 ⊃ 신경망 ⊃ 딥러닝이다.</li>
<li>특징 벡터는 <code>x=(x1,x2,...,xd)</code>로 쓴다.</li>
<li>이름형 범주형 특징은 원핫 코드로 표현하는 것이 적절하다.</li>
<li>유클리디언 거리는 <code>sqrt(Σ(xi-yi)^2)</code>이다.</li>
<li>FN은 실제 긍정인데 예측을 부정으로 한 경우이다.</li>
<li>정확률은 불균형 데이터에서 성능을 과대평가할 수 있다.</li>
<li>5-겹 교차 검증은 5개의 성능을 평균내어 우연의 영향을 줄인다.</li>
<li>SVM은 여백 <code>2s</code>를 최대화하는 결정 경계를 찾는다.</li>
<li>퍼셉트론은 <code>s=Σwixi</code>를 계산하고 계단 함수를 적용하는 이진 분류기이다.</li>
<li>OR 게이트는 <code>w0=-0.5, w1=1, w2=1</code>로 표현 가능하다.</li>
<li>AND 게이트는 <code>w0=-1.5, w1=1, w2=1</code>로 표현 가능하다.</li>
<li>경사 하강법은 기울기의 반대 방향으로 이동한다.</li>
<li>퍼셉트론 학습 규칙은 <code>w_new = w_old + ρ y x</code>이다.</li>
<li>epoch는 훈련 집합 전체를 한 번 처리하는 것이다.</li>
<li>신경망 학습은 전방 계산 → 오차 계산 → 후방 가중치 갱신을 반복한다.</li>
<li>SGD는 랜덤 샘플링한 부분 집합으로 갱신하는 경사 하강법이다.</li>
<li>소프트맥스 출력의 합은 1이다.</li>
<li>1980년대 깊은 신경망은 그레이디언트 소멸, 작은 데이터셋, 계산 시간 문제로 학습이 어려웠다.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[개인 프로젝트_Kubernetes Application Update]]></title>
            <link>https://velog.io/@bab_shunn/%EA%B0%9C%EC%9D%B8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8Kubernetes-Application-Update</link>
            <guid>https://velog.io/@bab_shunn/%EA%B0%9C%EC%9D%B8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8Kubernetes-Application-Update</guid>
            <pubDate>Tue, 19 May 2026 09:04:01 GMT</pubDate>
            <description><![CDATA[<h1 id="최종-영상-구조">최종 영상 구조</h1>
<p>10분 내외로 맞추려면 이렇게 진행하면 좋아.</p>
<table>
<thead>
<tr>
<th>시간</th>
<th align="right">슬라이드</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>0:00 ~ 0:25</td>
<td align="right">1장</td>
<td>주제 소개</td>
</tr>
<tr>
<td>0:25 ~ 0:45</td>
<td align="right">2장</td>
<td>세 가지 배포 전략 비교</td>
</tr>
<tr>
<td>0:45 ~ 2:20</td>
<td align="right">3~4장</td>
<td>Blue/Green 설명 및 실습</td>
</tr>
<tr>
<td>2:20 ~ 3:50</td>
<td align="right">5~6장</td>
<td>Canary 설명 및 실습</td>
</tr>
<tr>
<td>3:50 ~ 6:50</td>
<td align="right">7~9장</td>
<td>Rolling Update 및 Rollback 실습</td>
</tr>
<tr>
<td>6:50 ~ 7:10</td>
<td align="right">10장</td>
<td>기본 배포 전략 정리</td>
</tr>
<tr>
<td>7:10 ~ 9:45</td>
<td align="right">11~15장</td>
<td>추가 조사 내용 설명</td>
</tr>
<tr>
<td>9:45 ~ 10:00</td>
<td align="right">마무리</td>
<td>전체 요약</td>
</tr>
</tbody></table>
<p>PPT 1<del>10장은 배포 전략과 실습 흐름을 시각적으로 이해시키는 역할이고, 11</del>15장은 <code>--record</code> deprecated, Gateway API, Progressive Delivery 같은 추가 조사 내용으로 구성되어 있어. </p>
<hr>
<h1 id="녹화-전-준비-상태">녹화 전 준비 상태</h1>
<p>영상 시작 전에 터미널은 YAML 파일이 있는 폴더에서 열어둬.
영상에서 굳이 <code>cd</code> 명령어를 보여주지 않아도 돼.</p>
<p>필요한 파일은 아래와 같아.</p>
<pre><code class="language-text">deployment-blue.yaml
deployment-green.yaml
nginx-service.yaml
deployment-stable.yaml
deployment-canary.yaml
nginx-service-canary.yaml
deployment-rolling-update.yaml</code></pre>
<p>여기서 <code>nginx-service-canary.yaml</code>은 Canary 실습용 서비스 파일이야.
Blue/Green 실습의 <code>nginx-service.yaml</code>은 처음에 <code>version=blue</code>를 바라보고, Canary 실습에서는 <code>app=nginx</code>만 바라보는 Service가 필요하므로 파일을 분리해두는 게 안전해.</p>
<hr>
<h1 id="최종-발표-대본--실습-명령어">최종 발표 대본 + 실습 명령어</h1>
<h2 id="슬라이드-1-제목">슬라이드 1. 제목</h2>
<p><strong>대본</strong></p>
<p>안녕하세요. 클라우드융합 개인 프로젝트 발표를 맡은 박시운입니다.
제가 맡은 주제는 <strong>Kubernetes Application Update</strong>이고, 세부적으로는 <strong>Blue/Green Update, Canary Update, Rolling Update</strong> 세 가지 배포 전략입니다.</p>
<p>이번 영상에서는 왼쪽에는 PPT를 띄워 각 배포 전략의 구조를 설명하고, 오른쪽 터미널에서는 실제 <code>kubectl</code> 명령어로 실습을 진행하겠습니다.
실습이 끝난 뒤에는 추가 조사 내용으로 최신 Kubernetes 운영 방식인 GitOps, Gateway API, Progressive Delivery까지 설명하겠습니다.</p>
<hr>
<h2 id="슬라이드-2-배포-전략-비교">슬라이드 2. 배포 전략 비교</h2>
<p><strong>대본</strong></p>
<p>2장에서는 세 가지 배포 전략을 비교합니다.</p>
<p>Blue/Green은 구버전과 신버전을 동시에 띄운 뒤 트래픽을 한 번에 전환하는 방식입니다. 자원은 많이 필요하지만 롤백이 빠릅니다.</p>
<p>Canary는 신버전을 일부 트래픽에만 먼저 배포해서 정상 동작을 검증하는 방식입니다. 위험을 빠르게 감지할 수 있습니다.</p>
<p>Rolling Update는 Pod를 하나씩 교체하면서 전체 시스템을 무중단으로 업데이트하는 방식입니다.</p>
<p>이제 이 세 가지 전략을 순서대로 실습하겠습니다.</p>
<hr>
<h1 id="1-bluegreen-update-실습">1. Blue/Green Update 실습</h1>
<h2 id="슬라이드-3-bluegreen-update-개념">슬라이드 3. Blue/Green Update 개념</h2>
<p><strong>대본</strong></p>
<p>먼저 Blue/Green Update입니다.</p>
<p>Blue/Green Update는 구버전인 Blue와 신버전인 Green을 나란히 배포한 뒤, 트래픽을 한 번에 Green으로 전환하는 방식입니다.</p>
<p>장점은 롤백이 빠르다는 것입니다. Green 버전에 문제가 생기면 다시 Blue로 트래픽을 돌리면 됩니다.</p>
<p>단점은 Blue와 Green을 동시에 운영해야 하므로 전환 시점에 시스템 자원이 두 배 가까이 필요하다는 점입니다.</p>
<hr>
<h2 id="슬라이드-4-service-selector-변경을-통한-트래픽-전환">슬라이드 4. Service Selector 변경을 통한 트래픽 전환</h2>
<p><strong>대본</strong></p>
<p>4장은 Kubernetes에서 Blue/Green 전환을 Service selector로 구현하는 구조입니다.</p>
<p>처음에는 <code>nginx-svc</code> 서비스가 <code>version=blue</code>인 Pod를 바라보게 하고, 이후 서비스의 selector를 <code>version=green</code>으로 바꾸면 트래픽이 Green Pod로 전환됩니다.</p>
<p>먼저 Blue와 Green Deployment, 그리고 nginx Service를 배포하겠습니다.</p>
<p><strong>명령어</strong></p>
<pre><code class="language-bash">kubectl apply -f deployment-blue.yaml
kubectl apply -f deployment-green.yaml
kubectl apply -f nginx-service.yaml
kubectl get pod -o wide</code></pre>
<p><strong>대본</strong></p>
<p>현재 Blue 버전과 Green 버전의 Pod가 모두 배포되었습니다.
다만 서비스는 아직 Blue 버전을 바라보도록 설정되어 있습니다.</p>
<p>이제 <code>nginx-svc</code> 서비스의 Endpoint를 확인하겠습니다.</p>
<p><strong>명령어</strong></p>
<pre><code class="language-bash">kubectl describe svc nginx-svc</code></pre>
<p><strong>대본</strong></p>
<p>출력 결과에서 <code>Selector</code> 부분을 보면 <code>version=blue</code>로 되어 있습니다.</p>
<p>Endpoints: <none>은 지금 실습 환경의 EndpointSlice 권한/버전 차이 때문에 표시되지않고 있습니다. 실제 Endpoint IP 변화는 kubectl get endpoints nginx-svc 명령으로 확인하겠습니다.</p>
<pre><code class="language-bash">kubectl get endpoints nginx-svc
kubectl get pod -l app=nginx,version=blue -o wide</code></pre>
<p>Endpoint에는 <code>version=blue</code>인 Pod의 IP가 연결되어 있습니다.</p>
<p>이제 실습 PDF와 동일하게 <code>kubectl edit</code> 명령어로 서비스 selector의 <code>version</code> 값을 <code>blue</code>에서 <code>green</code>으로 바꾸겠습니다.</p>
<p><strong>명령어</strong></p>
<pre><code class="language-bash">kubectl edit svc nginx-svc</code></pre>
<p><strong>편집 방법</strong></p>
<p>편집기가 열리면 아래 부분을 찾는다.</p>
<pre><code class="language-yaml">selector:
  app: nginx
  version: blue</code></pre>
<p>이 부분을 아래처럼 바꾼다.</p>
<pre><code class="language-yaml">selector:
  app: nginx
  version: green</code></pre>
<p><code>vi</code> 편집기라면 <code>i</code>를 눌러 수정하고, 수정 후 <code>Esc</code> → <code>:wq</code> → <code>Enter</code>로 저장한다.
<code>nano</code> 편집기라면 수정 후 <code>Ctrl + O</code> → <code>Enter</code> → <code>Ctrl + X</code>로 저장한다.</p>
<p><strong>대본</strong></p>
<p>서비스 selector를 <code>version=green</code>으로 수정했습니다.
이제 다시 <code>nginx-svc</code>를 확인해서 Endpoint가 Green Pod로 바뀌었는지 확인하겠습니다.</p>
<p><strong>명령어</strong></p>
<pre><code class="language-bash">kubectl describe svc nginx-svc</code></pre>
<pre><code class="language-bash">kubectl get endpoints nginx-svc
kubectl get pod -l app=nginx,version=green -o wide</code></pre>
<p><strong>대본</strong></p>
<p>이제 selector가 <code>version=green</code>으로 변경되었고, Endpoint도 Green Pod의 IP로 바뀐 것을 확인할 수 있습니다.</p>
<p>즉, 사용자는 동일한 <code>nginx-svc</code> 서비스로 접근하지만, 내부적으로는 Blue에서 Green으로 트래픽이 전환된 것입니다.</p>
<p>이것이 Blue/Green Update의 핵심입니다.</p>
<p><strong>다음 실습을 위한 최소 정리 명령어</strong></p>
<pre><code class="language-bash">kubectl delete -f deployment-blue.yaml
kubectl delete -f deployment-green.yaml
kubectl delete -f nginx-service.yaml</code></pre>
<p><strong>짧은 설명</strong></p>
<p>이 정리 명령어는 PDF에는 없지만, 한 영상에서 Canary 실습까지 이어서 진행하기 위해 필요합니다.
Blue/Green Pod가 남아 있으면 Canary 실습의 Service가 기존 Pod까지 같이 바라볼 수 있기 때문입니다.</p>
<hr>
<h1 id="2-canary-update-실습">2. Canary Update 실습</h1>
<h2 id="슬라이드-5-canary-update-개념">슬라이드 5. Canary Update 개념</h2>
<p><strong>대본</strong></p>
<p>다음은 Canary Update입니다.</p>
<p>Canary Update는 신버전을 전체 사용자에게 바로 배포하지 않고, 일부 서버나 일부 사용자에게만 먼저 배포해서 정상 동작을 확인한 뒤 전체로 확산하는 방식입니다.</p>
<p>이 방식은 장애가 발생하더라도 일부 트래픽에서만 문제가 나타나므로 위험을 빠르게 감지하고 피해 범위를 줄일 수 있습니다.</p>
<hr>
<h2 id="슬라이드-6-canary-트래픽-분산과-pod-scaling">슬라이드 6. Canary 트래픽 분산과 Pod Scaling</h2>
<p><strong>대본</strong></p>
<p>6장은 Canary에서 트래픽 분산과 Pod Scaling을 보여줍니다.</p>
<p>이번 실습에서는 Stable 버전 Pod 2개와 Canary 버전 Pod 1개를 먼저 배포합니다.
이후 Canary 버전이 정상 동작한다고 가정하고, Canary Pod를 2개로 늘리고 Stable Pod를 0개로 줄여 전체를 Canary 버전으로 전환하겠습니다.</p>
<p>먼저 Stable과 Canary Deployment를 배포하겠습니다.</p>
<p><strong>명령어</strong></p>
<pre><code class="language-bash">kubectl apply -f deployment-stable.yaml
kubectl apply -f deployment-canary.yaml
kubectl apply -f nginx-service-canary.yaml
kubectl get pod &amp;&amp; kubectl get svc
kubectl get deployment -o wide</code></pre>
<p><strong>대본</strong></p>
<p>출력 결과를 보면 <code>nginx-stable</code>은 2개, <code>nginx-canary</code>는 1개가 실행 중입니다.
즉, Stable 버전 Pod 2개와 Canary 버전 Pod 1개가 배포된 상태입니다.</p>
<p>이제 서비스의 Endpoint를 확인하겠습니다.</p>
<p><strong>명령어</strong></p>
<pre><code class="language-bash">kubectl describe service nginx-svc</code></pre>
<p><strong>대본</strong></p>
<p>Service의 selector는 <code>app=nginx</code>이고, Endpoint에는 Stable Pod와 Canary Pod가 함께 포함되어 있습니다.</p>
<p>즉, 총 3개의 Pod 중 1개가 Canary이므로 대략 Stable 66%, Canary 33% 정도의 트래픽 분산을 기대할 수 있습니다.</p>
<p>이제 Canary 버전이 정상 동작한다고 가정하고 전체 업데이트를 진행하겠습니다.</p>
<p><strong>명령어</strong></p>
<pre><code class="language-bash">kubectl scale deployment nginx-canary --replicas=2
kubectl scale deployment nginx-stable --replicas=0
kubectl get deployment -o wide
kubectl get pod -o wide</code></pre>
<p><strong>대본</strong></p>
<p>이제 <code>nginx-canary</code>는 2개로 늘어났고, <code>nginx-stable</code>은 0개로 줄었습니다.</p>
<p>즉, 전체 트래픽이 Canary 버전으로 전환된 상태입니다.</p>
<p>이 방식은 Canary 배포의 기본 원리를 보여주기 좋지만, Pod 개수로 비율을 맞추는 방식이기 때문에 정확한 5%나 10% 같은 정밀한 트래픽 제어에는 한계가 있습니다.
이 한계는 뒤에서 Gateway API 설명과 연결됩니다.</p>
<p><strong>다음 실습을 위한 최소 정리 명령어</strong></p>
<pre><code class="language-bash">kubectl delete -f deployment-stable.yaml
kubectl delete -f deployment-canary.yaml
kubectl delete -f nginx-service-canary.yaml</code></pre>
<hr>
<h1 id="3-rolling-update-실습">3. Rolling Update 실습</h1>
<h2 id="슬라이드-7-rolling-update-개념">슬라이드 7. Rolling Update 개념</h2>
<p><strong>대본</strong></p>
<p>다음은 Rolling Update입니다.</p>
<p>Rolling Update는 전체 시스템을 중단하지 않고 점진적으로 업데이트하는 방식입니다.</p>
<p>새 이미지의 컨테이너가 들어간 Pod를 하나 추가하고, 기존 이미지의 Pod를 하나 삭제하는 과정을 순차적으로 반복합니다.</p>
<p>이 방식은 자원 오버헤드를 최소화하면서 무중단 업데이트를 수행할 수 있기 때문에 Kubernetes Deployment에서 기본적으로 많이 사용됩니다.</p>
<hr>
<h2 id="슬라이드-8-생성과-종료의-cross-replacement">슬라이드 8. 생성과 종료의 Cross Replacement</h2>
<p><strong>대본</strong></p>
<p>8장은 Rolling Update 중 새 Pod 생성과 기존 Pod 종료가 교차로 일어나는 과정을 보여줍니다.</p>
<p>새 Pod가 <code>ContainerCreating</code> 상태가 되는 동시에 기존 Pod가 <code>Terminating</code> 상태가 됩니다.
이 교차 과정 덕분에 서비스가 중단되지 않고 업데이트될 수 있습니다.</p>
<p>먼저 Rolling Update용 Deployment를 배포하겠습니다.</p>
<p><strong>명령어</strong></p>
<pre><code class="language-bash">kubectl apply -f deployment-rolling-update.yaml --record
kubectl get deployment nginx-rolling-update -o wide</code></pre>
<p><strong>대본</strong></p>
<p>현재 <code>nginx-rolling-update</code> Deployment가 배포되었고, 이미지는 <code>nginx:1.14</code>입니다.</p>
<p>이제 이미지를 <code>nginx:1.15</code>로 변경하면서 Rolling Update를 수행하겠습니다.</p>
<p>실시간으로 Pod 생성과 종료를 보기 위해 터미널을 두 개 사용하겠습니다.</p>
<p><strong>터미널 1 명령어</strong></p>
<pre><code class="language-bash">watch kubectl get pod</code></pre>
<p><strong>대본</strong></p>
<p>터미널 1에서는 <code>watch kubectl get pod</code> 명령으로 Pod 상태를 실시간으로 확인합니다.</p>
<p>이제 터미널 2에서 이미지 업데이트 명령을 실행하겠습니다.</p>
<p><strong>터미널 2 명령어</strong></p>
<pre><code class="language-bash">kubectl set image deployment nginx-rolling-update webpage=nginx:1.15 --record</code></pre>
<p><strong>대본</strong></p>
<p>터미널 1을 보면 새 Pod가 <code>ContainerCreating</code> 상태로 생성되고, 기존 Pod가 <code>Terminating</code> 상태로 종료되는 것을 확인할 수 있습니다.</p>
<p>확인이 끝났으면 <code>Ctrl + C</code>로 watch 명령을 종료합니다.</p>
<p>이제 Rolling Update가 완료되었는지 확인하겠습니다.</p>
<p><strong>명령어</strong></p>
<pre><code class="language-bash">kubectl rollout status deployment nginx-rolling-update
kubectl get deployment nginx-rolling-update -o wide</code></pre>
<p><strong>대본</strong></p>
<p>출력 결과에서 이미지가 <code>nginx:1.15</code>로 변경되었고, Deployment가 정상적으로 rollout된 것을 확인할 수 있습니다.</p>
<hr>
<h2 id="슬라이드-9-revision-history와-rollback">슬라이드 9. Revision History와 Rollback</h2>
<p><strong>대본</strong></p>
<p>9장은 Rolling Update의 revision history와 rollback을 보여줍니다.</p>
<p><code>kubectl rollout history</code> 명령어로 업데이트 기록을 확인할 수 있고, <code>kubectl rollout undo</code> 명령어로 이전 버전 또는 특정 revision으로 되돌릴 수 있습니다.</p>
<p>먼저 rollback 실습을 위해 이미지를 1.16, 1.17, 1.18까지 순서대로 업데이트하겠습니다.</p>
<p><strong>명령어</strong></p>
<pre><code class="language-bash">kubectl set image deployment nginx-rolling-update webpage=nginx:1.16 --record
kubectl rollout status deployment nginx-rolling-update

kubectl set image deployment nginx-rolling-update webpage=nginx:1.17 --record
kubectl rollout status deployment nginx-rolling-update

kubectl set image deployment nginx-rolling-update webpage=nginx:1.18 --record
kubectl rollout status deployment nginx-rolling-update</code></pre>
<p><strong>대본</strong></p>
<p>이제 여러 번 이미지가 변경되었기 때문에 revision history가 생성되었습니다.</p>
<p>업데이트 기록을 확인하겠습니다.</p>
<p><strong>명령어</strong></p>
<pre><code class="language-bash">kubectl rollout history deployment nginx-rolling-update
kubectl get deployment nginx-rolling-update -o wide</code></pre>
<p><strong>대본</strong></p>
<p>현재 이미지는 <code>nginx:1.18</code>입니다.</p>
<p>이제 바로 전 업데이트 버전인 <code>nginx:1.17</code>로 rollback하겠습니다.</p>
<p><strong>명령어</strong></p>
<pre><code class="language-bash">kubectl rollout undo deployment nginx-rolling-update</code></pre>
<p><strong>대본</strong></p>
<p><code>kubectl rollout undo</code> 명령은 기본적으로 바로 이전 revision으로 되돌립니다.</p>
<p>현재 이미지 버전을 다시 확인하겠습니다.</p>
<p><strong>명령어</strong></p>
<pre><code class="language-bash">kubectl get deployment nginx-rolling-update -o wide
kubectl rollout history deployment nginx-rolling-update</code></pre>
<p><strong>대본</strong></p>
<p>현재 이미지가 <code>nginx:1.17</code>로 rollback된 것을 확인할 수 있습니다.</p>
<p>이번에는 특정 revision을 지정해서 <code>nginx:1.15</code> 버전으로 rollback하겠습니다.
실습 PDF에서는 revision 2가 <code>nginx:1.15</code> 버전에 해당합니다.</p>
<p><strong>명령어</strong></p>
<pre><code class="language-bash">kubectl rollout undo deployment nginx-rolling-update --to-revision=2</code></pre>
<p><strong>대본</strong></p>
<p>이제 특정 revision으로 rollback했습니다.</p>
<p>현재 이미지와 업데이트 기록을 다시 확인하겠습니다.</p>
<p><strong>명령어</strong></p>
<pre><code class="language-bash">kubectl get deployment nginx-rolling-update -o wide
kubectl rollout history deployment nginx-rolling-update</code></pre>
<p><strong>대본</strong></p>
<p>현재 이미지가 <code>nginx:1.15</code>로 변경된 것을 확인할 수 있습니다.</p>
<p>이처럼 Rolling Update는 점진적으로 배포할 수 있을 뿐 아니라, revision history를 이용해서 원하는 과거 버전으로 안전하게 rollback할 수 있습니다.</p>
<hr>
<h2 id="슬라이드-10-최적의-배포-전략">슬라이드 10. 최적의 배포 전략</h2>
<p><strong>대본</strong></p>
<p>10장은 지금까지 실습한 세 가지 배포 전략을 어떤 상황에서 선택할지 정리하는 슬라이드입니다.</p>
<p>클러스터 자원이 충분하고 빠른 롤백이 중요하다면 Blue/Green이 적합합니다.</p>
<p>실제 사용자 트래픽을 통해 신버전 안정성 검증이 필요하다면 Canary가 적합합니다.</p>
<p>자원 제약 속에서 표준적인 무중단 배포를 원한다면 Rolling Update가 적합합니다.</p>
<p>즉, 완벽한 단일 배포 전략은 없습니다.
인프라 자원, 트래픽 민감도, 롤백의 긴급성을 종합적으로 고려해서 시스템에 맞는 최적의 라우팅 전략을 설계하는 것이 중요합니다.</p>
<p>이제 실습은 마무리하고, 11장부터는 추가 조사 내용인 최신 Kubernetes 운영 방향을 설명하겠습니다.</p>
<hr>
<h1 id="4-추가-조사-내용-설명">4. 추가 조사 내용 설명</h1>
<h2 id="슬라이드-11-최신화-방향">슬라이드 11. 최신화 방향</h2>
<p><strong>대본</strong></p>
<p>11장부터는 추가 조사 내용입니다.</p>
<p>지금까지는 Blue/Green, Canary, Rolling Update라는 기본 배포 전략을 실습했습니다.</p>
<p>하지만 최신 Kubernetes 운영에서는 이 전략들이 단순한 수동 <code>kubectl</code> 명령어 수준을 넘어 GitOps, Gateway API, Progressive Delivery와 결합하는 방향으로 발전하고 있습니다.</p>
<p>즉, 앞의 실습은 배포 전략의 기본 원리를 보여주는 것이고, 11장 이후는 이 원리가 실무 환경에서 어떻게 더 자동화되고 정밀해지는지를 설명하는 부분입니다.</p>
<hr>
<h2 id="슬라이드-12---record-deprecated와-gitops">슬라이드 12. <code>--record</code> deprecated와 GitOps</h2>
<p><strong>대본</strong></p>
<p>12장은 Rolling Update 실습에서 사용한 <code>--record</code> 옵션과 관련된 최신 이슈입니다.</p>
<p>실습에서는 PDF 흐름과 동일하게 <code>kubectl set image ... --record</code> 명령어를 사용했습니다.
이 옵션은 rollout history에서 변경 원인을 남기기 위해 사용됩니다.</p>
<p>하지만 Kubernetes v1.22 changelog에서는 <code>kubectl --record</code> 플래그가 deprecated 되었다고 명시되어 있습니다. 즉, 최신 운영에서는 이 방식이 더 이상 권장되는 중심 방식은 아닙니다. (<a href="https://raw.githubusercontent.com/kubernetes/kubernetes/master/CHANGELOG/CHANGELOG-1.22.md" title="raw.githubusercontent.com">GitHub</a>)</p>
<p>그래서 최신 방식에서는 <code>kubernetes.io/change-cause</code> annotation을 직접 남기거나, 더 나아가 Argo CD나 Flux 같은 GitOps 도구를 사용해 Git commit history를 배포 이력의 기준으로 삼습니다.</p>
<p>예를 들면 이런 방식입니다.</p>
<pre><code class="language-bash">kubectl annotate deployment/nginx-rolling-update \
  kubernetes.io/change-cause=&quot;nginx 이미지를 1.15로 업데이트&quot; \
  --overwrite</code></pre>
<p>즉, 과거에는 kubectl 명령어 자체가 이력 관리의 중심이었다면, 최신 운영에서는 Git commit, Pull Request, merge history가 배포 이력의 중심이 됩니다.</p>
<hr>
<h2 id="슬라이드-13-ingress-nginx-은퇴와-gateway-api-전환">슬라이드 13. Ingress NGINX 은퇴와 Gateway API 전환</h2>
<p><strong>대본</strong></p>
<p>13장은 Canary 배포에서 트래픽 비율을 제어하는 방식의 변화입니다.</p>
<p>앞에서 실습한 Canary는 Stable Pod 2개, Canary Pod 1개처럼 Pod 개수를 조절해서 트래픽 비율을 간접적으로 맞추는 방식이었습니다.</p>
<p>하지만 Pod 개수 기반 방식은 정확한 10%나 5% 같은 비율 제어가 어렵습니다.</p>
<p>기존에는 Ingress-NGINX에서도 annotation을 통해 Canary weight를 지정할 수 있었지만, 이것은 ingress-nginx 컨트롤러 전용 설정에 가까웠습니다.</p>
<p>Gateway API에서는 HTTPRoute의 <code>backendRefs.weight</code>를 사용해서 여러 backend 사이의 트래픽 비율을 지정할 수 있습니다. 공식 Gateway API 문서에서도 HTTPRoute weight는 rollout이나 canary 변경 중 backend 간 트래픽을 나누는 데 사용할 수 있다고 설명합니다. (<a href="https://gateway-api.sigs.k8s.io/guides/traffic-splitting/" title="HTTP traffic splitting | Gateway API">Gateway API</a>)</p>
<p>예시는 다음과 같습니다.</p>
<pre><code class="language-yaml">rules:
  backendRefs:
  - name: nginx-stable
    port: 80
    weight: 90
  - name: nginx-canary
    port: 80
    weight: 10</code></pre>
<p>이렇게 하면 Stable 서비스에는 대부분의 트래픽을 보내고, Canary 서비스에는 일부 트래픽만 보낼 수 있습니다.</p>
<p>다만 weight는 정확히 “퍼센트 값” 자체는 아니고, 전체 weight 합계 대비 비율입니다. Gateway API 스펙에서도 weight는 <code>weight / 전체 weight 합계</code>로 계산된다고 설명합니다. (<a href="https://gateway-api.sigs.k8s.io/references/spec/" title="API Reference | Gateway API">Gateway API</a>)</p>
<p>즉, 기존 실습은 Pod 개수 기반 Canary이고, 최신 방식은 Gateway API의 weight 기반 라우팅으로 더 정밀하게 트래픽을 제어하는 방식입니다.</p>
<hr>
<h2 id="슬라이드-14-메트릭-기반의-자동화된-롤아웃">슬라이드 14. 메트릭 기반의 자동화된 롤아웃</h2>
<p><strong>대본</strong></p>
<p>14장은 Progressive Delivery, 즉 메트릭 기반의 자동화된 롤아웃입니다.</p>
<p>앞의 실습에서는 사람이 직접 <code>kubectl scale</code>, <code>kubectl set image</code>, <code>kubectl rollout undo</code> 명령어를 실행했습니다.</p>
<p>하지만 실무에서는 이 과정을 자동화할 수 있습니다.</p>
<p>예를 들어 처음에는 Canary 버전에 트래픽을 5%만 보냅니다.
그 다음 Prometheus나 Datadog 같은 모니터링 도구로 에러율, 응답 지연 시간, 성공률 같은 메트릭을 확인합니다.</p>
<p>정상이라면 트래픽을 10%, 20%, 50%처럼 점진적으로 늘립니다.
반대로 문제가 발생하면 자동으로 rollback합니다.</p>
<p>이때 Prometheus는 트래픽을 직접 제어하는 도구가 아니라, 메트릭을 수집하고 제공하는 역할입니다.
실제 판단과 제어는 Argo Rollouts나 Flagger 같은 컨트롤러가 담당합니다.</p>
<p>Argo 공식 페이지에서도 Argo Rollouts는 Canary와 Blue-Green 같은 고급 Kubernetes 배포 전략을 쉽게 수행하기 위한 도구라고 설명합니다. (<a href="https://argoproj.github.io/" title="Home | Argo">Argo Project</a>)</p>
<p>즉, Progressive Delivery는 단순 배포가 아니라, 메트릭으로 안전성을 확인하면서 점진적으로 배포하는 방식입니다.</p>
<hr>
<h2 id="슬라이드-15-progressive-delivery-architecture">슬라이드 15. Progressive Delivery Architecture</h2>
<p><strong>대본</strong></p>
<p>마지막 15장은 Progressive Delivery의 전체 아키텍처입니다.</p>
<p>흐름은 왼쪽에서 오른쪽으로 진행됩니다.</p>
<p>먼저 개발자가 코드를 수정하고 Git에 commit합니다.
Argo CD는 Git 저장소의 manifest를 Kubernetes 클러스터와 동기화합니다.</p>
<p>그 다음 Argo Rollouts가 배포 전략을 관리합니다.
Gateway API의 HTTPRoute는 Stable Pod와 Canary Pod 사이의 트래픽 비율을 담당합니다.</p>
<p>예를 들어 Stable Pod에는 90%의 트래픽을 보내고, Canary Pod에는 10%의 트래픽만 보냅니다.</p>
<p>그리고 Gateway 계층과 Pod에서 발생하는 요청 수, 에러율, 지연 시간 같은 메트릭은 Prometheus로 수집됩니다.</p>
<p>중요한 점은 Prometheus가 Gateway API를 직접 제어하지 않는다는 것입니다.
Prometheus는 메트릭을 제공하고, Argo Rollouts나 Flagger가 이 메트릭을 기반으로 배포 성공 여부를 판단합니다.</p>
<p>정상이라면 Argo Rollouts가 Gateway API의 weight를 조정해 Canary 트래픽을 더 늘립니다.
문제가 발생하면 weight를 Stable 쪽으로 되돌려 자동 rollback합니다.</p>
<p>정리하면 흐름은 다음과 같습니다.</p>
<pre><code class="language-text">Git Commit
→ Argo CD
→ Argo Rollouts
→ Gateway API
→ Stable / Canary Pod
→ Prometheus 메트릭 수집
→ Argo Rollouts 판단
→ Gateway API weight 조정 또는 rollback</code></pre>
<p>결론적으로 Blue/Green, Canary, Rolling은 배포 전략의 기본이고, 최신 Kubernetes 운영에서는 GitOps의 선언적 관리, Gateway API의 정밀한 라우팅, Prometheus 기반 관측성, Argo Rollouts 기반 자동화를 결합한 Progressive Delivery로 발전하고 있습니다.</p>
<hr>
<h1 id="최종-마무리-멘트">최종 마무리 멘트</h1>
<p><strong>대본</strong></p>
<p>지금까지 Kubernetes Application Update 주제로 Blue/Green, Canary, Rolling Update를 설명하고 실제 <code>kubectl</code> 명령어로 실습을 진행했습니다.</p>
<p>Blue/Green은 Service selector를 바꿔 트래픽을 빠르게 전환하는 방식입니다.</p>
<p>Canary는 일부 트래픽만 신버전에 보내 위험을 줄이는 방식입니다.</p>
<p>Rolling Update는 Pod를 순차적으로 교체하면서 무중단 업데이트를 수행하는 방식입니다.</p>
<p>그리고 추가 조사 내용으로, 최신 Kubernetes 운영에서는 GitOps 기반 이력 관리, Gateway API 기반 트래픽 weight 제어, Prometheus 메트릭 분석, Argo Rollouts 기반 자동 rollback을 결합한 Progressive Delivery로 발전하고 있다는 점을 확인했습니다.</p>
<p>이상으로 발표와 실습을 마치겠습니다.</p>
<hr>
<h1 id="최종-실습-명령어만-모은-버전">최종 실습 명령어만 모은 버전</h1>
<p>영상 중 복사해서 쓰기 편하게 명령어만 다시 모아두면 아래와 같아.</p>
<h2 id="bluegreen">Blue/Green</h2>
<pre><code class="language-bash">kubectl apply -f deployment-blue.yaml
kubectl apply -f deployment-green.yaml
kubectl apply -f nginx-service.yaml
kubectl get pod -o wide</code></pre>
<pre><code class="language-bash">kubectl describe svc nginx-svc</code></pre>
<pre><code class="language-bash">kubectl edit svc nginx-svc</code></pre>
<p>수정 내용:</p>
<pre><code class="language-yaml">selector:
  app: nginx
  version: green</code></pre>
<pre><code class="language-bash">kubectl describe svc nginx-svc</code></pre>
<pre><code class="language-bash">kubectl delete -f deployment-blue.yaml
kubectl delete -f deployment-green.yaml
kubectl delete -f nginx-service.yaml</code></pre>
<h2 id="canary">Canary</h2>
<pre><code class="language-bash">kubectl apply -f deployment-stable.yaml
kubectl apply -f deployment-canary.yaml
kubectl apply -f nginx-service-canary.yaml
kubectl get pod &amp;&amp; kubectl get svc
kubectl get deployment -o wide</code></pre>
<pre><code class="language-bash">kubectl describe service nginx-svc</code></pre>
<pre><code class="language-bash">kubectl scale deployment nginx-canary --replicas=2
kubectl scale deployment nginx-stable --replicas=0
kubectl get deployment -o wide
kubectl get pod -o wide</code></pre>
<pre><code class="language-bash">kubectl delete -f deployment-stable.yaml
kubectl delete -f deployment-canary.yaml
kubectl delete -f nginx-service-canary.yaml</code></pre>
<h2 id="rolling-update">Rolling Update</h2>
<pre><code class="language-bash">kubectl apply -f deployment-rolling-update.yaml --record
kubectl get deployment nginx-rolling-update -o wide</code></pre>
<p>터미널 1:</p>
<pre><code class="language-bash">watch kubectl get pod</code></pre>
<p>터미널 2:</p>
<pre><code class="language-bash">kubectl set image deployment nginx-rolling-update webpage=nginx:1.15 --record</code></pre>
<p>확인:</p>
<pre><code class="language-bash">kubectl rollout status deployment nginx-rolling-update
kubectl get deployment nginx-rolling-update -o wide</code></pre>
<h2 id="revision-생성">Revision 생성</h2>
<pre><code class="language-bash">kubectl set image deployment nginx-rolling-update webpage=nginx:1.16 --record
kubectl rollout status deployment nginx-rolling-update

kubectl set image deployment nginx-rolling-update webpage=nginx:1.17 --record
kubectl rollout status deployment nginx-rolling-update

kubectl set image deployment nginx-rolling-update webpage=nginx:1.18 --record
kubectl rollout status deployment nginx-rolling-update</code></pre>
<h2 id="rollback">Rollback</h2>
<pre><code class="language-bash">kubectl rollout history deployment nginx-rolling-update
kubectl get deployment nginx-rolling-update -o wide</code></pre>
<pre><code class="language-bash">kubectl rollout undo deployment nginx-rolling-update</code></pre>
<pre><code class="language-bash">kubectl get deployment nginx-rolling-update -o wide
kubectl rollout history deployment nginx-rolling-update</code></pre>
<pre><code class="language-bash">kubectl rollout undo deployment nginx-rolling-update --to-revision=2</code></pre>
<pre><code class="language-bash">kubectl get deployment nginx-rolling-update -o wide
kubectl rollout history deployment nginx-rolling-update</code></pre>
<p>이 버전이 발표용 최종본으로 쓰기 가장 깔끔해. PDF에 있는 핵심 명령어를 유지했고, <code>get nodes</code>, <code>current-context</code>, 시작 전 전체 삭제 같은 부가 명령어는 본편에서 제거했어.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[삼성전자 전문가 특강(전자공학 in 삼성전자)]]></title>
            <link>https://velog.io/@bab_shunn/%EC%82%BC%EC%84%B1%EC%A0%84%EC%9E%90-%EC%A0%84%EB%AC%B8%EA%B0%80-%ED%8A%B9%EA%B0%95%EC%A0%84%EC%9E%90%EA%B3%B5%ED%95%99-in-%EC%82%BC%EC%84%B1%EC%A0%84%EC%9E%90</link>
            <guid>https://velog.io/@bab_shunn/%EC%82%BC%EC%84%B1%EC%A0%84%EC%9E%90-%EC%A0%84%EB%AC%B8%EA%B0%80-%ED%8A%B9%EA%B0%95%EC%A0%84%EC%9E%90%EA%B3%B5%ED%95%99-in-%EC%82%BC%EC%84%B1%EC%A0%84%EC%9E%90</guid>
            <pubDate>Mon, 18 May 2026 08:23:45 GMT</pubDate>
            <description><![CDATA[<h3 id="윤인열-mx-사업부하드웨어-엔지니어-연사님">윤인열, mx 사업부/하드웨어 엔지니어 연사님</h3>
<ul>
<li>focus) agentic ai 기반 설계/해석 자동화</li>
<li>dx: 가전사업부, mx: 모바일사업부 =&gt; +로봇연구또한 최근 하고 있음.</li>
<li>기획(DIA, PIA)-&gt;설계(DVR)-&gt;검증(PVR)-&gt; 양산(PRA,SRA) 단계로 구성되어있음. 보통 해석단계가 설계+검증 단계임.</li>
<li>TEARDOWN: 해당 키워드를 통해 관련 하드웨어를 분해한 모습을 확인가능함.</li>
<li>MX 사업부는 본질적으로 FR(무선통신) 사업부임. (다른 국가로의 망 호환성이나 외부로 부터의 영향을 최소화...)</li>
<li>SI(잘닦인 트랙), PI(단단한 노면), EMI(날씨에 흔들리지 않게) =&gt; 신호 무결성을 보장</li>
<li>공정 단계에서 예기치못한 변수를 제어하기 위해 공정의 균일도에 대한 시뮬레이션을 먼저 돌려, 공정의 오차율을 감소</li>
<li>사람이 반복적으로 하는 일에 대해 AI agent가 대체될 수 있도록 해당 부문에 포커스되고 있음</li>
<li>공통적으로 중시하는 것) 학부의 기본기 + ai 를 활용하는 것 -&gt; 프롬프트/ 컨텍스트 설계/ 결과 검증까지가 활용력이다. 이러한 반복되는 일들을 발견하고, 스크립트-&gt;파이프라인-&gt;에이전트로 확장하는 사고 방식이 업무 자동화 사고이다. -</li>
<li>빠른 의사소통을 위해 영어는 필수적이다. 다다익선</li>
<li>ai 같은 경우 생산성을 위해 보안문제로 금지가 되었던 ds 사업부까지 허용을 검토하여 올해 느슨(?)해져서 사용을 권장하고 있음. (올해 6월 예정) 하지만 여전히 사업관련 기밀은 ai 사용이 금지되고 있음. 본인이 agent를 만들어 사용해도 괜찮음.</li>
<li>보통 대학원을 회사에서 보내주는 사람들은 상위 고과자임. ( 입사하고 나서도 열심히 해야겐네....)</li>
</ul>
<h3 id="최호익-ds-부문경영진단팀-일하는-방식의-변화에-대하여-연사님">최호익, ds 부문/경영진단팀 일하는 방식의 변화에 대하여 연사님</h3>
<ul>
<li>sait(한국기술연구센터): ai 센터 -&gt; 삼성전자: 경영진단팀</li>
<li>메모리사업부: hbm가 수익을 굉장히 많이 내는 줄 알지만, 현재까진 ddr5 같은 레거시 메모리가 잘 팔려서 요즘 핫한거임</li>
<li>파운드리사업부: 해당 사업부가 정말 기술적인 부서임. </li>
<li>s.lsi </li>
<li>기업이 ai 를 본격적으로 도입하고 업무가 변경되었을때 내가 본질적으로 할 수 있는건 무엇일까? 라는 고민을 해봐야함.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[0부터 시작하는 오픈스택] 2주차 과제 상세 분석: 컬럼을 기본 출력에 추가]]></title>
            <link>https://velog.io/@bab_shunn/0%EB%B6%80%ED%84%B0-%EC%8B%9C%EC%9E%91%ED%95%98%EB%8A%94-%EC%98%A4%ED%94%88%EC%8A%A4%ED%83%9D%EC%9D%98-x%EC%8B%A0-2%EC%A3%BC%EC%B0%A8-%EA%B3%BC%EC%A0%9C-%EC%83%81%EC%84%B8-%EB%B6%84%EC%84%9D-%EC%BB%AC%EB%9F%BC%EC%9D%84-%EA%B8%B0%EB%B3%B8-%EC%B6%9C%EB%A0%A5%EC%97%90-%EC%B6%94%EA%B0%80</link>
            <guid>https://velog.io/@bab_shunn/0%EB%B6%80%ED%84%B0-%EC%8B%9C%EC%9E%91%ED%95%98%EB%8A%94-%EC%98%A4%ED%94%88%EC%8A%A4%ED%83%9D%EC%9D%98-x%EC%8B%A0-2%EC%A3%BC%EC%B0%A8-%EA%B3%BC%EC%A0%9C-%EC%83%81%EC%84%B8-%EB%B6%84%EC%84%9D-%EC%BB%AC%EB%9F%BC%EC%9D%84-%EA%B8%B0%EB%B3%B8-%EC%B6%9C%EB%A0%A5%EC%97%90-%EC%B6%94%EA%B0%80</guid>
            <pubDate>Mon, 04 May 2026 11:31:39 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>우린 이전 포스팅에서 <a href="https://velog.io/@bab_shunn/0%EB%B6%80%ED%84%B0-%EC%8B%9C%EC%9E%91%ED%95%98%EB%8A%94-%EC%98%A4%ED%94%88%EC%8A%A4%ED%83%9D%EC%9D%98-x%EC%8B%A0-OpenStackClient-%EA%B0%9C%EB%B0%9C-%ED%99%98%EA%B2%BD-%EC%84%B8%ED%8C%85">OpenStackClient 개발 환경 세팅</a>
<code>openstack server list</code> 명령어를 통해 인스턴스들을 확인해 보았습니다</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/bab_shunn/post/22e46816-3a7b-4c3c-b413-9f14f94b70a4/image.png" alt=""></p>
<h3 id="미션-1">미션 1</h3>
<p>openstack server list 결과에 <code>user name</code>과 <code>project name</code> 이라는 컬럼을 기본 출력에 추가하고, 그 값을 출력하도록 만들어주세요
<img src="https://velog.velcdn.com/images/bab_shunn/post/c6c78ef3-03b7-4815-9a4e-4aee658a087b/image.png" alt=""></p>
<h3 id="미션-2">미션 2</h3>
<p>미션 1에서 컬럼을 추가하면, 실패하는 테스트 케이스가 다수 발생합니다.
모든 테스트 케이스가 통과하도록, 테스트 케이스 자체를 수정해주세요</p>
<p>조건) 새로 추가한 컬럼이 기본 값인 상태입니다. 이 컬럼이 기본인 조건을 만들고 테스트 케이스를 수정해야합니다</p>
<hr>
<h3 id="①-어느-부분을-수정해야-할까">① 어느 부분을 수정해야 할까?</h3>
<p>server list 라는 명령어를 처리하는 <code>python-openstackclient/openstackclient/compute/v2/server.py</code> 부분을 살펴보겠습니다
<img src="https://velog.velcdn.com/images/bab_shunn/post/cbf4a504-1997-4bed-8000-df0a42cf4445/image.png" alt="">
이 함수가 칼럼에 관여하는 친구인거 같다 <del><code>column_headers</code> 투성이</del>
<code>server.py</code> 파일</p>
<pre><code class="language-py">            columns = (
                &#39;ID&#39;,
                &#39;Name&#39;,
                &#39;Status&#39;,
                &#39;Networks&#39;,
                &#39;Image Name&#39;,
                &#39;Flavor Name&#39;,
            )
        column_headers = (
            &#39;ID&#39;,
            &#39;Name&#39;,
            &#39;Status&#39;,
            &#39;Networks&#39;,
            &#39;Image&#39;,
            &#39;Flavor&#39;,
        )
</code></pre>
<p>이런식으로 담겨있을 줄 알았으나,,,</p>
<pre><code class="language-py">             columns: tuple[str, ...] = (
                &#39;id&#39;,
                    &#39;name&#39;,
                &#39;status&#39;,
            )
        column_headers: tuple[str, ...] = (
            &#39;ID&#39;,
            &#39;Name&#39;,
            &#39;Status&#39;,
        )</code></pre>
<p><del>어림도 없지 암</del> 이렇게 되어있어서 봤더니</p>
<pre><code class="language-py">        if sdk_utils.supports_microversion(compute_client, &#39;2.47&#39;):
            columns += (&#39;flavor_name&#39;,)
            column_headers += (&#39;Flavor&#39;,)
        else:
            if parsed_args.long:
                columns += (
                    &#39;flavor_name&#39;,
                    &#39;flavor_id&#39;,
                )
                column_headers += (
                    &#39;Flavor Name&#39;,
                    &#39;Flavor ID&#39;,
                )
            else:
                if parsed_args.no_name_lookup:
                    columns += (&#39;flavor_id&#39;,)
                else:
                    columns += (&#39;flavor_name&#39;,)
                column_headers += (&#39;Flavor&#39;,)

        #새로 추가한 부분
        columns += (
            &#39;project_name&#39;,
            &#39;user_name&#39;,
        )
        column_headers += (
            &#39;Project Name&#39;,
            &#39;User Name&#39;,
        )
</code></pre>
<p>명령어에 따라 <strong>동적으로 추가</strong>시키려고 코드가 이런식으로 작성되어있었다.
<code>project_name</code>이나 <code>user_name</code> 같은 경우 조건 기반 동적 구성할 필요는 없어서 이렇게 코드를 추가해줬습니다</p>
<blockquote>
<p>이제 ListServer의 기본 columns에 <code>project_name</code>과 <code>user_name</code>을 추가했고,
server list의 기본 출력 순서가 기존 
ID, Name, Status, Networks, Image, Flavor에서 
ID, Name, Status, Networks, Image, Flavor, Project Name, User Name으로 바뀝니다.</p>
</blockquote>
<p>+) 추가적으로</p>
<pre><code class="language-py">if parsed_args.columns:
            for c in parsed_args.columns:
                if c in (&#39;Project ID&#39;, &#39;project_id&#39;):
                    columns += (&#39;project_id&#39;,)
                    column_headers += (&#39;Project ID&#39;,)
                if c in (&#39;User ID&#39;, &#39;user_id&#39;):
                    columns += (&#39;user_id&#39;,)
                    column_headers += (&#39;User ID&#39;,)
                    ...
                #새로 추가한 부분
                if c in (&#39;Project Name&#39;, &#39;project_name&#39;):
                    columns += (&#39;project_name&#39;,)
                    column_headers += (&#39;Project Name&#39;,)
                if c in (&#39;User Name&#39;, &#39;user_name&#39;):
                    columns += (&#39;user_name&#39;,)
                    column_headers += (&#39;User Name&#39;,)</code></pre>
<p>같은 파일에 컬럼 옵션 처리가 되어 있는 코드가 보이는데,
Project Name과 User Name을 넣어서, <strong>-c 옵션으로도 선택 가능</strong>하게 했습니다. </p>
<h3 id="②-반환-되는-것들을-확인해보자">② 반환 되는 것들을 확인해보자</h3>
<table>
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Status</th>
<th>Networks</th>
<th>Image</th>
<th>Flavor</th>
<th>User Name</th>
<th>Project Name</th>
</tr>
</thead>
<tbody><tr>
<td>6cae97c9-eb91-4834-9ec4-b6a7d239bfc1</td>
<td>test_instance_user10</td>
<td>ACTIVE</td>
<td>shared=192.168.233.41</td>
<td>cirros-0.6.3-x86_64-disk</td>
<td>m1.nano</td>
<td>e4d2d84dfd764831a7f4a78affe3e7c2</td>
<td>f6351174806d45e98876063199f447af</td>
</tr>
</tbody></table>
<p>보다시피 새로 추가한 칼럼인 유저 네임과 프로젝트 네임이 굉장히 러프(?)하다
여기서 필요하다고 느끼는건 저 값을 받아서 대조하여 <code>출력값을 채워줄 무언가</code>가 필요하다.</p>
<blockquote>
<p>즉, OpenStack의 기본 서버 응답에는 user_id, project_id만 포함되어 있으며,
<strong>실제 사람이 읽을 수 있는 이름(name)은 별도의 Identity 서비스 조회</strong>를 통해 가져와야 한다.</p>
</blockquote>
<pre><code class="language-py">project_names = {}
        user_names = {}
        if data:
            for project_id in {
                s.project_id for s in data if getattr(s, &#39;project_id&#39;, None)
            }:
                try:
                    project_names[project_id] = identity_common.find_project(
                        identity_client,
                        project_id,
                    ).name
                except Exception:  # noqa: S110
                    # retrieving project names is not crucial, so we swallow
                    # any exceptions
                    pass

            for user_id in {
                s.user_id for s in data if getattr(s, &#39;user_id&#39;, None)
            }:
                try:
                    user_names[user_id] = identity_common.find_user(
                        identity_client,
                        user_id,
                    ).name
                except Exception:  # noqa: S110
                    # retrieving user names is not crucial, so we swallow any
                    # exceptions
                    pass</code></pre>
<p><code>출력 값 채우는 로직</code>에서는 각 서버의 project_id와 user_id를 모아 <code>identity_common.find_project</code>와 <code>identity_common.find_user</code>로 이름을 조회한 뒤, server 객체의 project_name과 user_name에 넣도록 구현하였다. 이미지와 플레버 이름 조회 흐름은 그대로 유지했고, 새 컬럼만 추가로 계산하는 구조이다</p>
<p>+) 근데 이게 정말 최선일까?
분명 기존 코드에 데이터를 가져오는 로직이 있을것이다. </p>
<blockquote>
<p>&quot;<em>서버 단위 데이터 처리 흐름을 통합하여 응집도를 높이고, 조건 기반 로직과 캐싱을 자연스럽게 적용하기 위한 구조 개선을 하고싶다...</em>&quot; 라는 생각이 들때쯤</p>
</blockquote>
<pre><code class="language-py"> # Populate image_name, image_id, flavor_name and flavor_id attributes
        # of server objects so that we can display those columns.
        project_names = {}
        user_names = {}
        for s in data:
            if sdk_utils.supports_microversion(compute_client, &#39;2.69&#39;):
                if getattr(s, &#39;status&#39;) == &#39;UNKNOWN&#39;:
                    continue

            if &#39;id&#39; in s.image and s.image.id is not None:
                image = images.get(s.image[&#39;id&#39;])
                if image:
                    s.image_name = image.name
                s.image_id = s.image[&#39;id&#39;]
            else:            
                s.image_name = IMAGE_STRING_FOR_BFV
                s.image_id = IMAGE_STRING_FOR_BFV
            ## 해당 for문 내부에 코드를 구현했다
            project_id = getattr(s, &#39;project_id&#39;, None)
            if project_id and project_id not in project_names:
                try:
                    project_names[project_id] = identity_common.find_project(
                        identity_client,
                        project_id,
                    ).name
                except Exception:  # noqa: S110
                    pass
            s.project_name = project_names.get(project_id)

            user_id = getattr(s, &#39;user_id&#39;, None)
            if user_id and user_id not in user_names:
                try:
                    user_names[user_id] = identity_common.find_user(
                        identity_client,
                        user_id,
                    ).name
                except Exception:  # noqa: S110
                    pass
            s.user_name = user_names.get(user_id)
</code></pre>
<p>합치려고 보니
서버마다 <code>find_project</code>와 <code>find_user</code>를 매번 호출하면? 
-&gt; 오히려 중복 조회가 늘어나지 않을까...?</p>
<p>그래서 루프를 합치되 ID별 캐시는 유지하는 방향으로 개선을 시도 
즉, 지금의 딕셔너리 캐시 방식은 유지하고, 그 캐시 채우기와 서버 객체 값 세팅을 같은 for s in data: 안에서 처리하는 쪽이 균형이 좋을 거 같아 구조화를 해보았다</p>
<p><img src="https://velog.velcdn.com/images/bab_shunn/post/1b1ce774-95c9-43c1-8055-97888dfcf73b/image.png" alt=""></p>
<p><span style="color:olivedrab"><em>사실 실제로 기여하는 사람들 입장에선 저 밈 수준으로 매우 간단한 작업이고 당연한 수순일 순 있다.
하지만 난 <code>오픈소스 기여 호소인</code>이므로 자세하게 설명할거다.</em></span></p>
<p>정리) 단도직입적으로 남들이 &quot;그래서 그냥 비슷한 기능의 for문 찾아서 내부에 포함시켰다는 거냐?&quot;라고 질문한다면 무시하지말고 이렇게 답변하면 된다</p>
<p>첫번째 구현 방식: 캐시를 먼저 만들고, 나중에 한 번 더 서버를 순회
개선한 방식: 한 번의 서버 순회 안에서 캐시 조회/갱신과 <code>s.project_name</code>, <code>s.user_name</code> 세팅을 같이 수행</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[0부터 시작하는 오픈스택] 1주차 과제 상세 분석: openstack server show는 어떻게 작동하는가?]]></title>
            <link>https://velog.io/@bab_shunn/0%EB%B6%80%ED%84%B0-%EC%8B%9C%EC%9E%91%ED%95%98%EB%8A%94-%EC%98%A4%ED%94%88%EC%8A%A4%ED%83%9D%EC%9D%98-x%EC%8B%A0-1%EC%A3%BC%EC%B0%A8-%EA%B3%BC%EC%A0%9C-%EC%83%81%EC%84%B8-%EB%B6%84%EC%84%9D-openstack-server-show%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EC%9E%91%EB%8F%99%ED%95%98%EB%8A%94%EA%B0%80</link>
            <guid>https://velog.io/@bab_shunn/0%EB%B6%80%ED%84%B0-%EC%8B%9C%EC%9E%91%ED%95%98%EB%8A%94-%EC%98%A4%ED%94%88%EC%8A%A4%ED%83%9D%EC%9D%98-x%EC%8B%A0-1%EC%A3%BC%EC%B0%A8-%EA%B3%BC%EC%A0%9C-%EC%83%81%EC%84%B8-%EB%B6%84%EC%84%9D-openstack-server-show%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EC%9E%91%EB%8F%99%ED%95%98%EB%8A%94%EA%B0%80</guid>
            <pubDate>Sun, 26 Apr 2026 07:05:40 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>해당 포스팅은 1주차 과제였던 <em>&quot;개발환경에서 openstack server list 명령어 실행 결과 확인&quot;</em> 을 기반으로 내부적으로 코드가 어떻게 흐르는지 파악하는 것입니다</p>
</blockquote>
<p>방향 설정: nova 의 정보를 어느시점에서 가져오고 렌더링할 서버 정보를 가져오는 건 take_action 의 어느 시점인지를 중점으로 분석</p>
<p><img src="https://velog.velcdn.com/images/bab_shunn/post/5d13afc5-32fc-44f8-8074-d90ec4bf279c/image.png" alt="">
<img src="https://velog.velcdn.com/images/bab_shunn/post/40024729-c6c7-4b7c-a990-1dfba68fa019/image.png" alt=""></p>
<h2 id="작은-강의-이런-오픈소스코드-분석은-어떻게-하면-좋은가">[작은 강의] 이런 오픈소스코드 분석은 어떻게 하면 좋은가?</h2>
<p><code>openstack --debug server list</code></p>
<p>해당 로그들 속에서</p>
<p><code>greb -R &quot;{궁금한 문자열}&quot;</code></p>
<p>만약 검색이 안된다면 가상환경에 설치된 라이브러리들이 호출을 통해 문자열을 출력하고 있는것</p>
<p>가상환경으로 들어가서 <code>greb -R &quot;{궁금한 문자열}&quot;</code></p>
<p>호출이 되고 있는 경로를 찾았다면, </p>
<p>해당 경로 파일을 열어서 hook, 함수 등 종속적으로 호출하고 있는 것을 확인</p>
<p>만약, 속한 class 를 확인 하였다면 해당 클래스 검색 -&gt; 호출하고 있는 것들 중 진짜 내가 찾는 것과 관련된 것들 걸러내기</p>
<p>=&gt; 단편적인 요소를 찾고 상향식으로 분석</p>
<p>추천) <a href="https://wikidocs.net/230415">https://wikidocs.net/230415</a> traceback를 사용하면 좀 더 수월하게 확인이 가능</p>
<p>주요 라이브러리</p>
<ul>
<li>OSC_lib ( 다른 lib, cli, sdl)</li>
<li>stevedore ( 동적 louder )( csl 에서 사용하기 위한 것들을 동적으로 불러와 사용하기 위한 것들 )</li>
<li>keystoneouth ( 인증 관련)</li>
</ul>
<p>-&gt; stevedore, keystoneouth 에 빠지면 return 값 정도만 확인하고 빠져나오세요 </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[0부터 시작하는 오픈스택] OpenStackClient 개발 환경 세팅]]></title>
            <link>https://velog.io/@bab_shunn/0%EB%B6%80%ED%84%B0-%EC%8B%9C%EC%9E%91%ED%95%98%EB%8A%94-%EC%98%A4%ED%94%88%EC%8A%A4%ED%83%9D%EC%9D%98-x%EC%8B%A0-OpenStackClient-%EA%B0%9C%EB%B0%9C-%ED%99%98%EA%B2%BD-%EC%84%B8%ED%8C%85</link>
            <guid>https://velog.io/@bab_shunn/0%EB%B6%80%ED%84%B0-%EC%8B%9C%EC%9E%91%ED%95%98%EB%8A%94-%EC%98%A4%ED%94%88%EC%8A%A4%ED%83%9D%EC%9D%98-x%EC%8B%A0-OpenStackClient-%EA%B0%9C%EB%B0%9C-%ED%99%98%EA%B2%BD-%EC%84%B8%ED%8C%85</guid>
            <pubDate>Fri, 24 Apr 2026 17:18:55 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>OpenStack CLI 클라이언트(python-openstackclient)를 로컬에서 직접 개발하고 디버깅하기 위한 <strong>VSCode</strong> 환경 구축 방법 (+근데 <strong>윈도우</strong> 환경을 곁들인...)</p>
</blockquote>
<p>이 게시물을 읽고 계신다면, 분명 주위에서 파이참을 사용하라고 권유했을거고 당신의 윈도우 환경에 안타까움을 표했을겁니다...</p>
<p>그럼에도 불구하고 꿋꿋하게 환경세팅을 하려고 제 게시물을 읽고 있는 당신에게 경의를 표하며... <del>지금이라도 주변의 말을 들으세요</del></p>
<p>_<span style="color:olivedrab">참고) 본격적인 포스팅에 앞서 윈도우 명령어 앞엔 🪟으로 기입하겠습니다</span>_</p>
<hr>
<h3 id="환경세팅을-하기에-앞서-간략한-사전-지식-및-전체-흐름">[환경세팅을 하기에 앞서, 간략한 사전 지식 및 전체 흐름]</h3>
<ol>
<li>클라우드 컴퓨팅은 원격지의 리소스를 API 호출을 통해 제어합니다. 
사용자는 웹 대시보드(GUI)를 사용할 수도 있지만, 
개발 생산성과 자동화를 위해 CLI(Command Line Interface) 도구인 <code>python-openstackclient</code>를 사용</li>
</ol>
<p>-&gt; 마치 쿠버네티스의 kubectl과 비슷한 역할이랄까</p>
<ol start="2">
<li>해당 프로젝트는 파이썬을 사용합니다. 그러므로 의존성 문제를 예방하기 위해 가상환경을 사용합니다 -&gt; 시스템 전역에 패키지 설정하면^^..... <del>버전 충돌쇼</del> </li>
</ol>
<p>-&gt; 정리하자면 가상환경(내방) &gt; 라이브러리 모음집 설치 &gt; 구동</p>
<hr>
<h4 id="①-소스코드-clone">① 소스코드 clone</h4>
<p>자신의 작업 디렉토리에 python-openstackclient 코드를 clone 받습니다.
<code>git clone https://opendev.org/openstack/python-openstackclient</code></p>
<h4 id="②-cloudsyaml-생성">② clouds.yaml 생성</h4>
<p>python-openstackclient를 clone 받은 위치에 아래 내용으로 clouds.yaml 파일을 생성합니다.</p>
<pre><code class="language-yaml">clouds:
  my-cloud:
    auth:
      auth_url: http://&lt;ip&gt;/identity
      username: &quot;admin&quot; # 할당 받은 값 넣으세요
      password: &quot;1234&quot; # 할당 받은 값 넣으세요
      project_name: &quot;admin&quot; # 할당 받은 값 넣으세요
      user_domain_name: &quot;Default&quot;
      project_domain_name: &quot;Default&quot;
    region_name: &quot;RegionOne&quot;
    interface: &quot;public&quot;
    identity_api_version: &quot;3&quot;</code></pre>
<h4 id="③-visual-studio-code에서-python-openstackclient-폴더-열기">③ Visual Studio Code에서 python-openstackclient 폴더 열기</h4>
<p>visual studio code를 이용할 경우, IDE로서의 기능보다 코드를 분석하고 수정하는 용도로 주로 활용하게 됩니다. 따라서, openstackclient 실행과 로그 분석은 터미널에서 합니다.
code내에서 터미널을 열고 작업을 이어갑니다.</p>
<blockquote>
<p>vscode 자체를 관리자 권한으로 설치하면 git bash도 관리자 권한으로 내부에서 사용가능하다고 Reddit에 토론기록이 남아있긴한데, <del>그 누구도 성공적인 시도였다는 후기가 없다</del> <span style="color:olivedrab">옆에 git bash 관리자 권한으로 열어서 작업하는 걸 추천 드립니다</span></p>
</blockquote>
<h4 id="④-virtualenv-생성-가상-환경-만들고-만든-환경에-들어가기">④ virtualenv 생성 (가상 환경 만들고 만든 환경에 들어가기)</h4>
<blockquote>
<p><code>python3</code> -&gt; <code>python</code>: 윈도우 환경에서는 python 명령어가 3.x 버전을 가리키는 기본 실행 파일명이므로 교체</p>
</blockquote>
<blockquote>
<p><code>.venv/bin/activate</code> -&gt; <code>.venv/Scripts/activate</code>: 윈도우 파이썬 가상 환경은 실행 파일(Script)을 bin이 아닌 Scripts 디렉토리에 저장</p>
</blockquote>
<p>아래 명령어로 virtualenv를 생성합니다.
<code>python3 -m venv .venv</code>
🪟<code>python -m venv .venv</code>
그리고 virtualenv 를 활성화합니다.
<code>source .venv/bin/activate</code>
🪟<code>source .venv/Scripts/activate</code> -&gt; ex) 이게 가상환경(내 방) 가는 길인거다 
정상적으로 활성화되었다면 다음과 같이 터미널에 표시됩니다.
<img src="https://velog.velcdn.com/images/bab_shunn/post/59256475-ccb3-43b3-af10-7c28cdf1b59b/image.png" alt=""></p>
<h4 id="⑤-python-openstackclient-의-의존-라이브러리-설치하기">⑤ python-openstackclient 의 의존 라이브러리 설치하기</h4>
<blockquote>
<p><code>pip3</code>-&gt;<code>pip</code>: 리눅스와 달리 윈도우 가상 환경(.venv/Scripts)에는 pip3.exe 파일이 생성되지 않는 경우가 많습니다. </p>
</blockquote>
<p>이제 의존 라이브러리를 설치합니다.
아래 명령어로 의존 라이브러리를 설치합니다.
<code>pip3 install -r requirements.txt</code> -&gt; 실행용
🪟<code>pip install -r requirements.txt</code> 
<code>pip3 install -r test-requirements.txt</code> -&gt; 테스트나 코드 품질 검사용
🪟<code>pip install -r test-requirements.txt</code> </p>
<h4 id="⑥-python-openstackclient를-develop-모드로-설치하기">⑥ python-openstackclient를 develop 모드로 설치하기</h4>
<p>이제 virtualenv 에 python-openstackclient 자체를 설치합니다.
일반적으로 파이썬 코드를 받아 설치할 때는 <code>python setup.py install</code>을 이용하지만, 개발을
위해서는 아래 명령어로 develop 모드로 설치해아 합니다.
<code>python3 setup.py develop</code>
🪟<code>python setup.py develop</code></p>
<h4 id="⑦-openstack-server-list-실행해보기">⑦ openstack server list 실행해보기</h4>
<p>python-openstackclient의 main 함수는 openstackclient/shell.py 에 있습니다.
하지만 PyCharm 과 다르게 우리는 openstackclient를 virtualenv에 설치했으므로 openstack
명령어를 그대로 사용해도 됩니다.
아래 명령어로 결과를 확인해봅니다.
<code>openstack server list --os-cloud my-cloud</code>
<img src="https://velog.velcdn.com/images/bab_shunn/post/01cf23f0-cc11-4177-967c-57c043a19770/image.png" alt="">
이 때 인스턴스 이름은 test_instance_계정명 으로 된 것이 나와야합니다.
여기까지 되었다면 openstackclient 코드를 개발할 준비가 완료되었습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[멋사] 리액트 라우터/클라이언트 라우팅 직접 구현]]></title>
            <link>https://velog.io/@bab_shunn/%EB%A9%8B%EC%82%AC-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%9D%BC%EC%9A%B0%ED%84%B0%ED%81%B4%EB%9D%BC%EC%9D%B4%EC%96%B8%ED%8A%B8-%EB%9D%BC%EC%9A%B0%ED%8C%85-%EC%A7%81%EC%A0%91-%EA%B5%AC%ED%98%84</link>
            <guid>https://velog.io/@bab_shunn/%EB%A9%8B%EC%82%AC-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%9D%BC%EC%9A%B0%ED%84%B0%ED%81%B4%EB%9D%BC%EC%9D%B4%EC%96%B8%ED%8A%B8-%EB%9D%BC%EC%9A%B0%ED%8C%85-%EC%A7%81%EC%A0%91-%EA%B5%AC%ED%98%84</guid>
            <pubDate>Tue, 17 Jun 2025 09:00:00 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/bab_shunn/post/118a8864-11e6-436c-9327-0a94db4f07ae/image.png" alt=""></p>
<ul>
<li>routes.tsx</li>
</ul>
<pre><code class="language-js">import { createBrowserRouter } from &quot;react-router&quot;;
import Home from &quot;@pages/Home&quot;;
import Page1 from &quot;@pages/Page1&quot;;
import Page2 from &quot;@pages/Page2&quot;;

const router = createBrowserRouter([
  { path: &#39;/home&#39;, element: &lt;Home /&gt; },
  { path: &#39;/page1&#39;, element: &lt;Page1 /&gt; }, 
  { path: &#39;/page2&#39;, element: &lt;Page2 /&gt; },
]);

export default router;</code></pre>
<ul>
<li>App.tsx</li>
</ul>
<pre><code class="language-js">import { RouterProvider } from &#39;react-router&#39;;
import router from &#39;./routes&#39;;

function App() {
  return (
    &lt;&gt;
      &lt;RouterProvider router={ router } /&gt;
    &lt;/&gt;
  );
}

export default App;</code></pre>
<p><img src="https://velog.velcdn.com/images/bab_shunn/post/5e67bb70-d478-4c59-920f-abf653147ff7/image.png" alt=""></p>
<ul>
<li>Header.tsx<pre><code class="language-js">import { Link } from &quot;react-router&quot;;
</code></pre>
</li>
</ul>
<p>function Header() {
  return (
    <header>
      <h1>02 리액트 라우터 사용</h1>
      <Link to="/home">home</Link><br/>
      <Link to="/page1">page1</Link><br/>
      <Link to="/page2">page2</Link>
    </header>
  );
}</p>
<p>export default Header;</p>
<pre><code>
![](https://velog.velcdn.com/images/bab_shunn/post/160998d1-9775-4b6b-8c59-bbbbf77f6e8f/image.png)
![](https://velog.velcdn.com/images/bab_shunn/post/398acf05-e81c-4194-bd5c-b2dc16e000f4/image.png)
![](https://velog.velcdn.com/images/bab_shunn/post/15d64cc6-dc16-4caa-9f44-a6f88d6fa749/image.png)</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[멋사] API fetch/custom hook 구현]]></title>
            <link>https://velog.io/@bab_shunn/%EB%A9%8B%EC%82%AC-API-fetchcustom-hook-%EA%B5%AC%ED%98%84</link>
            <guid>https://velog.io/@bab_shunn/%EB%A9%8B%EC%82%AC-API-fetchcustom-hook-%EA%B5%AC%ED%98%84</guid>
            <pubDate>Mon, 16 Jun 2025 08:57:21 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/bab_shunn/post/018fd4fa-a45b-406c-afad-84857ed14151/image.png" alt="">
<img src="https://velog.velcdn.com/images/bab_shunn/post/6bf8d2a7-6cde-43b3-8a8c-e9235b8b331b/image.png" alt="">
<img src="https://velog.velcdn.com/images/bab_shunn/post/c6f84f25-9aae-489b-ab67-01be3c61018e/image.png" alt="">
<img src="https://velog.velcdn.com/images/bab_shunn/post/15b0dca9-04d5-4e61-96eb-87c71718ad24/image.png" alt="">
<img src="https://velog.velcdn.com/images/bab_shunn/post/54d40e68-1cdb-4e7a-af94-71e05dab6414/image.png" alt="">
<img src="https://velog.velcdn.com/images/bab_shunn/post/728690b3-d701-4411-a699-e6ef12645aa6/image.png" alt="">
<img src="https://velog.velcdn.com/images/bab_shunn/post/7e2e89c6-631e-43d4-98bc-19a7298de5f5/image.png" alt=""></p>
<p>물론이야. 아래는 <code>fetch</code> 직접 사용과 <code>useFetch</code> 커스텀 훅을 비교 정리한 velog용 글 형식의 간단한 개념 정리야:</p>
<hr>
<h2 id="fetch-직접-사용-vs-커스텀-훅usefetch-사용-비교">fetch 직접 사용 vs 커스텀 훅(useFetch) 사용 비교</h2>
<h3 id="1-fetch-직접-사용">1. fetch 직접 사용</h3>
<h4 id="개요">개요</h4>
<p>React 컴포넌트 내부에서 <code>fetch()</code> 함수를 직접 호출하여 데이터를 요청하고, <code>useState</code>, <code>useEffect</code> 등을 통해 상태를 관리하는 방식.</p>
<h4 id="장점">장점</h4>
<ul>
<li>코드 흐름이 직관적이고 이해하기 쉬움</li>
<li>API 요청마다 다른 로직을 유연하게 작성 가능</li>
</ul>
<h4 id="단점">단점</h4>
<ul>
<li>여러 컴포넌트에서 동일한 요청을 구현할 경우 코드 중복이 많아짐</li>
<li>로딩, 에러, 데이터 상태 등을 매번 직접 관리해야 함</li>
<li>UI와 비즈니스 로직이 섞여 코드가 복잡해질 수 있음</li>
</ul>
<hr>
<h3 id="2-usefetch-커스텀-훅-사용">2. useFetch 커스텀 훅 사용</h3>
<h4 id="개요-1">개요</h4>
<p>API 요청 및 관련 상태 관리(<code>loading</code>, <code>error</code>, <code>data</code>)를 커스텀 훅으로 분리하여 관리하는 방식. 주로 재사용성과 관심사 분리를 위해 사용함.</p>
<h4 id="장점-1">장점</h4>
<ul>
<li>로직 재사용이 가능하여 코드 중복 최소화</li>
<li>로딩, 에러 처리 등의 상태 관리가 일관됨</li>
<li>컴포넌트는 UI에만 집중할 수 있어 코드 구조가 깔끔해짐</li>
<li>유지보수가 쉬움 (훅 하나만 수정하면 됨)</li>
</ul>
<h4 id="단점-1">단점</h4>
<ul>
<li>초기에 구조를 이해하고 설정하는 데 시간이 필요함</li>
<li>훅 내부를 커스터마이징하려면 추가 작업이 필요할 수 있음</li>
<li>단순한 요청에도 훅 사용이 과할 수 있음</li>
</ul>
<hr>
<h3 id="결론">결론</h3>
<ul>
<li>작은 프로젝트나 간단한 요청에는 <code>fetch</code> 직접 사용이 편리</li>
<li>여러 컴포넌트에서 같은 API를 호출하거나 상태 관리가 필요한 경우에는 <code>useFetch</code> 커스텀 훅을 사용하는 것이 효율적</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[멋사] useReducer/useRef]]></title>
            <link>https://velog.io/@bab_shunn/%EB%A9%8B%EC%82%AC-useReduceruseRef</link>
            <guid>https://velog.io/@bab_shunn/%EB%A9%8B%EC%82%AC-useReduceruseRef</guid>
            <pubDate>Thu, 12 Jun 2025 08:55:23 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/bab_shunn/post/fbd45258-78ec-484c-9548-f61edfb4cde1/image.png" alt="">
<img src="https://velog.velcdn.com/images/bab_shunn/post/c5714463-dacf-4f59-a25c-f806bdc59241/image.png" alt=""><img src="https://velog.velcdn.com/images/bab_shunn/post/a31a0ef5-a294-49cf-8663-d5b78ae598b5/image.png" alt="">
<img src="https://velog.velcdn.com/images/bab_shunn/post/d7c0da3f-d29c-41db-85ac-936e94ec365a/image.png" alt=""></p>
<h2 id="3-usereducer">3) <code>useReducer</code></h2>
<p>useState와 역할은 비슷한데, useState가 제공하는 기능 이외에도 상태 업데이트 로직을 <u><strong>외부 함수로 분리할 수 있는 기능</strong></u>을 제공한다.</p>
<p><img src="https://velog.velcdn.com/images/bab_shunn/post/6615f8ec-d615-482c-9d2c-128b8f194538/image.png" alt=""></p>
<p>useState와 useReducer의 차이는 위 그림과 같다. <strong>상태를 관리한다는 목적은 동일</strong>한데 <strong>상태를 업데이트하는 방법이 약간 다르다.</strong>
useReducer는 미리 정의된 reducer 함수에 action과 이전 상태값을 입력값으로 넣어서 새로운 상태를 계산한다는 차이점이다.</p>
<hr>
<h2 id="4-useref">4) <code>useRef</code></h2>
<p>useRef는 <u><strong>특정 DOM에 접근하여 DOM 조작</strong></u>을 가능하게 하는 훅이다.
리액트 프로젝트에서도 특정 요소를 선택해야하는 상황이 생길 수 있는데 이러한 상황에서 useRef 함수를 사용할 수 있게 된다.</p>
<br/>

<p>가장 큰 특징은 useRef는 따로 상태 변경 함수가 없고 직접 <code>.current</code>의 상태를 변경하기 때문에 <u><strong>변경 사항이 자동으로 브라우저 화면에 반영되지 않는다는 점</strong></u>이다.</p>
<br/>

<p>하지만 컴포넌트 지역 변수와 다르게 useRef 상태는 컴포넌트가 언마운트될 때까지 유지되기 때문에, 여러 이유로 인해 해당 컴포넌트 render 함수가 실행되면 useRef 변경 사항이 화면에 반영될 수 있다.</p>
<pre><code class="language-js">{/* 변경 사항이 자동으로 화면에 반영된다. */}
&lt;button onClick={() =&gt; setCount((prev) =&gt; prev + 1)}&gt;
  useState 상태 업데이트
&lt;/button&gt;

{/* 변경 사항이 자동으로 화면에 반영되지 않는다. 상태는 유지된다. */}
&lt;button onClick={() =&gt; reference.current++}&gt;
  useRef 상태 업데이트
&lt;/button&gt;</code></pre>
<br/>

<p>그래서 useRef는 주로 <strong>자동으로 화면을 렌더링하지 않아도 되는 상태</strong>를 관리할 때 사용한다.
화면 렌더링과 관련이 없는 상태를 useState로 관리하면 render 함수가 불필요하게 실행되기 때문이다.</p>
<h2 id="참고">참고:</h2>
<ul>
<li><a href="https://velog.io/@gwak2837/React-Hooks-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-2">React Hooks 이해하기 (2)</a> </li>
<li><a href="https://ko.legacy.reactjs.org/docs/hooks-intro.html">Hook의 개요</a></li>
<li><a href="https://choijying21.tistory.com/entry/React-%EB%A6%AC%EC%95%A1%ED%8A%B8-%ED%9B%85%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0React-Hooks%EB%9E%80">JDevelog:티스토리</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[멋사] 클래스 컴포넌트와 컴포넌트의 라이프 사이클/리액트 훅]]></title>
            <link>https://velog.io/@bab_shunn/%EB%A9%8B%EC%82%AC-%ED%81%B4%EB%9E%98%EC%8A%A4-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EC%99%80-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EC%9D%98-%EB%9D%BC%EC%9D%B4%ED%94%84-%EC%82%AC%EC%9D%B4%ED%81%B4%EB%A6%AC%EC%95%A1%ED%8A%B8-%ED%9B%85</link>
            <guid>https://velog.io/@bab_shunn/%EB%A9%8B%EC%82%AC-%ED%81%B4%EB%9E%98%EC%8A%A4-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EC%99%80-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EC%9D%98-%EB%9D%BC%EC%9D%B4%ED%94%84-%EC%82%AC%EC%9D%B4%ED%81%B4%EB%A6%AC%EC%95%A1%ED%8A%B8-%ED%9B%85</guid>
            <pubDate>Wed, 11 Jun 2025 08:35:01 GMT</pubDate>
            <description><![CDATA[<h2 id="컴포넌트-생명-주기">컴포넌트 생명 주기</h2>
<p><img src="https://velog.velcdn.com/images/bab_shunn/post/d2186af6-534c-4421-8b10-5a69e024faef/image.png" alt="">
<img src="https://velog.velcdn.com/images/bab_shunn/post/2b0dab47-3395-4ae4-af52-a3a5478eaeea/image.png" alt=""><img src="https://velog.velcdn.com/images/bab_shunn/post/f3f7115d-0dc1-43b9-98a0-5f7b54a7b78d/image.png" alt="">
<img src="https://velog.velcdn.com/images/bab_shunn/post/78a2ef85-5676-47d5-bb55-47ea851ec8e7/image.png" alt=""></p>
<hr>
<h2 id="리액트-훅">리액트 훅</h2>
<p><img src="https://velog.velcdn.com/images/bab_shunn/post/abbc6c80-065b-4eb6-89e8-b5ccb9daf30c/image.png" alt=""><img src="https://velog.velcdn.com/images/bab_shunn/post/b399a6c6-fe80-406c-ab40-736b217cf03e/image.png" alt="">
<img src="https://velog.velcdn.com/images/bab_shunn/post/800308cb-eee2-44d6-a1cb-fa6aa79de825/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[멋사] useState 기반 수동 검증 방식/react-hook-form을 사용한 선언적 방식/클래스형 컴포넌트]]></title>
            <link>https://velog.io/@bab_shunn/%EB%A9%8B%EC%82%AC-js-useState-%EA%B8%B0%EB%B0%98-%EC%88%98%EB%8F%99-%EA%B2%80%EC%A6%9D-%EB%B0%A9%EC%8B%9Dreact-hook-form%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%9C-%EC%84%A0%EC%96%B8%EC%A0%81-%EB%B0%A9%EC%8B%9D%ED%81%B4%EB%9E%98%EC%8A%A4%ED%98%95-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8</link>
            <guid>https://velog.io/@bab_shunn/%EB%A9%8B%EC%82%AC-js-useState-%EA%B8%B0%EB%B0%98-%EC%88%98%EB%8F%99-%EA%B2%80%EC%A6%9D-%EB%B0%A9%EC%8B%9Dreact-hook-form%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%9C-%EC%84%A0%EC%96%B8%EC%A0%81-%EB%B0%A9%EC%8B%9D%ED%81%B4%EB%9E%98%EC%8A%A4%ED%98%95-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8</guid>
            <pubDate>Tue, 10 Jun 2025 08:38:47 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/bab_shunn/post/0c5317f3-3098-4110-a143-7c0cbcf6148c/image.png" alt="">
<img src="https://velog.velcdn.com/images/bab_shunn/post/aa5a0258-acfb-4bb0-9b03-b8f47cd7320b/image.png" alt="">
<img src="https://velog.velcdn.com/images/bab_shunn/post/aa6961b7-c3b0-4fdf-8a0c-4cea85ad0f8e/image.png" alt="">
<img src="https://velog.velcdn.com/images/bab_shunn/post/5c3a6991-c92f-493e-8cf0-9744f6a6f3e9/image.png" alt="">
<img src="https://velog.velcdn.com/images/bab_shunn/post/1fafc08b-6228-4f2b-80e5-4907b70df57f/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[멋사] state 관리 시작]]></title>
            <link>https://velog.io/@bab_shunn/%EB%A9%8B%EC%82%AC-js-state-%EA%B4%80%EB%A6%AC</link>
            <guid>https://velog.io/@bab_shunn/%EB%A9%8B%EC%82%AC-js-state-%EA%B4%80%EB%A6%AC</guid>
            <pubDate>Mon, 09 Jun 2025 09:23:25 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/bab_shunn/post/f8e7ce90-51f5-4c1f-b9cf-6d8efd570a0c/image.png" alt="">
<img src="https://velog.velcdn.com/images/bab_shunn/post/95898e04-55f5-4d5b-b545-903bf5ec555b/image.png" alt="">
<img src="https://velog.velcdn.com/images/bab_shunn/post/46340f41-035f-4a90-9089-856ac6658cfa/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[멋사] react start]]></title>
            <link>https://velog.io/@bab_shunn/%EB%A9%8B%EC%82%AC-react-start</link>
            <guid>https://velog.io/@bab_shunn/%EB%A9%8B%EC%82%AC-react-start</guid>
            <pubDate>Wed, 04 Jun 2025 08:44:59 GMT</pubDate>
            <description><![CDATA[<h3 id="리액트-관련-개념들과-특징-정리-25-06-04">리액트 관련 개념들과 특징 정리 (25-06-04)</h3>
<p><img src="https://velog.velcdn.com/images/bab_shunn/post/c9e1de32-0527-408a-b373-5d3307233a48/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[멋사] 리액트 시작?]]></title>
            <link>https://velog.io/@bab_shunn/%EB%A9%8B%EC%82%AC-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%8B%9C%EC%9E%91</link>
            <guid>https://velog.io/@bab_shunn/%EB%A9%8B%EC%82%AC-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%8B%9C%EC%9E%91</guid>
            <pubDate>Fri, 30 May 2025 09:01:32 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/bab_shunn/post/4933deef-7003-42a4-b9ad-b9748f96ba4c/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/bab_shunn/post/36f2649b-60da-4db5-bfe3-d021d521f1e9/image.png" alt="">
<img src="https://velog.velcdn.com/images/bab_shunn/post/140ecda9-064c-4069-bdd3-1a3cd937112d/image.png" alt="">
<img src="https://velog.velcdn.com/images/bab_shunn/post/da202d4a-762a-44e3-be92-6277f173b69a/image.png" alt="">
<img src="https://velog.velcdn.com/images/bab_shunn/post/f294dbd2-416b-47f4-849b-64ee6174f0a3/image.png" alt="">
<img src="https://velog.velcdn.com/images/bab_shunn/post/30a08338-8823-4ab2-8341-ac738fda826f/image.png" alt="">
<img src="https://velog.velcdn.com/images/bab_shunn/post/6ed90810-2d35-4b25-a223-2d9e47cea88c/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[멋사] React 빌드업 ]]></title>
            <link>https://velog.io/@bab_shunn/%EB%A9%8B%EC%82%AC-React-%EB%B9%8C%EB%93%9C%EC%97%85</link>
            <guid>https://velog.io/@bab_shunn/%EB%A9%8B%EC%82%AC-React-%EB%B9%8C%EB%93%9C%EC%97%85</guid>
            <pubDate>Tue, 27 May 2025 08:05:01 GMT</pubDate>
            <description><![CDATA[<p>1장 리액트 빌드업</p>
<ul>
<li><ol>
<li>웹 개발의 변천사</li>
</ol>
</li>
<li><ol start="2">
<li>리액트 개발에 자주 사용하는 자바스크립트 문법
<img src="https://velog.velcdn.com/images/bab_shunn/post/90a11512-b7b0-47b4-9e5b-335d1f81d5ac/image.jpeg" alt=""></li>
</ol>
</li>
<li><p>간단한 todoList 실습 (dom 객체를 다루는데에 좀 더 익숙해지기 위한)
<img src="https://velog.velcdn.com/images/bab_shunn/post/c0fe6655-bb63-4793-937a-30d8b8d72eb6/image.png" alt="">
<img src="https://velog.velcdn.com/images/bab_shunn/post/d5c9b6cd-9c43-455a-b2ca-c67ade038db7/image.png" alt="">
<img src="https://velog.velcdn.com/images/bab_shunn/post/455185ee-cced-462e-a990-b67dc31aeb72/image.png" alt=""></p>
</li>
<li><p>간단한 counter 실습 (dom 객체를 다루는 코드는 js 파일에서 관리하고 해당 코드를 함수로 만들어 html 파일에서 dom조작을 함수를 통해 더 간결한 코드를 작성합니다. 마지막엔 코드를 하나의 함수로 만들고, 좀 더 유연한 사용을 위해 컴포넌트로 쪼개어 사용할 수 있는 것들을 함수로 만들어 최종적으로 rootNode에 추가합니다.)
<img src="https://velog.velcdn.com/images/bab_shunn/post/4416eea6-4f28-4e7a-8d0b-83656e56f5f4/image.png" alt=""></p>
</li>
</ul>
<p>+)사진이 깨져서 pdf로도 저장했는데 벨로그는 pdf가 안올라가네요...? 호호,,</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[멋사] -js 내장객체 object]]></title>
            <link>https://velog.io/@bab_shunn/%EB%A9%8B%EC%82%AC-js-%EB%82%B4%EC%9E%A5%EA%B0%9D%EC%B2%B4-object</link>
            <guid>https://velog.io/@bab_shunn/%EB%A9%8B%EC%82%AC-js-%EB%82%B4%EC%9E%A5%EA%B0%9D%EC%B2%B4-object</guid>
            <pubDate>Wed, 30 Apr 2025 04:39:49 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/bab_shunn/post/95770459-cd49-4149-bea5-de6a3bed99db/image.png" alt=""></p>
<ul>
<li><code>const haru2 = haru;</code> → <strong>얕은 복사(X)</strong> → <strong>참조 복사</strong></li>
<li><code>const haru3 = Object.assign({}, haru);</code> 또는 <code>{ ...haru }</code> → <strong>얕은 복사(O)</strong>  <blockquote>
<p>JavaScript 자체 내장 메서드로는 진정한 깊은 복사(deep copy)는 제공되지 않음! (라이브러리를 사용하거나 로데쉬..., json을 통해 부분적으로 구현 가능하긴함</p>
</blockquote>
</li>
</ul>
<hr>
<h4 id="haru2--haru"><code>haru2 = haru;</code></h4>
<ul>
<li><strong>같은 객체를 가리키는 <u>참조 복사</u></strong>  </li>
<li>그래서 <code>haru.age++</code> 하면 <code>haru2.age</code>도 같이 바뀝니다.  <pre><code class="language-js">const haru2 = haru;
haru.age++; // haru2.age도 같이 증가함</code></pre>
</li>
</ul>
<h4 id="objectassign-haru-또는--haru-"><code>Object.assign({}, haru)</code> 또는 <code>{ ...haru }</code></h4>
<ul>
<li><strong><u>얕은 복사(shallow copy)</u></strong>  </li>
<li>)</li>
<li><code>haru</code> 객체의 <strong>최상위 속성</strong>만 새 객체로 복사 </li>
<li><strong>중첩 객체</strong>가 있다면 그 안은 <strong>참조로 복사</strong></li>
</ul>
<p>예시:</p>
<pre><code class="language-js">const haru = { name: &#39;하루&#39;, age: 5, pet: { type: &#39;dog&#39; } };
const copy = { ...haru };
copy.pet.type = &#39;cat&#39;;
console.log(haru.pet.type); // &#39;cat&#39; (내부 객체는 공유됨)</code></pre>
<hr>
<p>정리하면:</p>
<table>
<thead>
<tr>
<th>복사 방법</th>
<th>복사 종류</th>
<th>중첩 객체까지 복사?</th>
</tr>
</thead>
<tbody><tr>
<td><code>haru2 = haru</code></td>
<td>참조 복사</td>
<td>X (같은 객체 공유)</td>
</tr>
<tr>
<td><code>Object.assign({}, haru)</code></td>
<td>얕은 복사</td>
<td>X</td>
</tr>
<tr>
<td><code>{ ...haru }</code></td>
<td>얕은 복사</td>
<td>X</td>
</tr>
<tr>
<td><code>JSON.parse(JSON.stringify())</code></td>
<td>깊은 복사</td>
<td>O (단점 있음)</td>
</tr>
<tr>
<td><code>lodash.cloneDeep()</code></td>
<td>깊은 복사</td>
<td>O</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[[멋사] js-unknown type]]></title>
            <link>https://velog.io/@bab_shunn/%EB%A9%8B%EC%82%AC-js-unknown-type</link>
            <guid>https://velog.io/@bab_shunn/%EB%A9%8B%EC%82%AC-js-unknown-type</guid>
            <pubDate>Mon, 28 Apr 2025 01:47:51 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/bab_shunn/post/b36b003d-4f8a-4bf6-a74b-e09ff0bdbe35/image.png" alt=""></p>
<h2 id="unknown-type">unknown type</h2>
<p><code>TypeScript 3.0</code> 부터 새로 추가된 타입
문자 그대로 <code>알려지지 않은 타입</code> 이다.
unknown 타입으로 선언된 변수의 값으로는 모든 타입이 올 수 있다. (any타입과 비슷)
그렇다면 <code>unknown 과 any 의 차이점</code>은 무엇일까?</p>
<h3 id="unknown-과-any의-차이">unknown 과 any의 차이</h3>
<blockquote>
<p>unknown에는 메서드를 호출할 수 없다.</p>
</blockquote>
<p>어떤 타입인지 확실하지 않기 때문에 타입스크립트는 어떠한 연산이든 실행하지 않는다.</p>
<pre><code class="language-js">let userInput: unknown;
console.log(userInput.length); // ❌ 에러</code></pre>
<p>unknown 타입을 가진 변수는 any를 제외하고 다른 타입을 가진 변수에 할당할 수 없다.
다시 말해, <u>고정된 타입을 가지고 있는 변수에 unknown 타입을 가진 변수를 할당할 수 없다.</u></p>
<pre><code class="language-js">let userInput: unknown;
let userName: string;

userInput = 5; // ⭕️ 작동
userInput = &#39;Harry&#39;; // ⭕️ 작동
userName = userInput; // ❌ 에러</code></pre>
<blockquote>
<p>unknown 타입을 가진 변수를 다른 변수에 할당하기 위해서는 타입 체크를 해주면 된다.</p>
</blockquote>
<pre><code class="language-js">let userInput: unknown;
let userName: string;

userInput = 5;
userInput = &#39;Harry&#39;;
if (typeof userInput === &#39;string&#39;) { 
  userName = userInput; // ⭕️ 작동
}</code></pre>
<h3 id="unknown-타입을-쓰는-이유">unknown 타입을 쓰는 이유</h3>
<p><strong>변수의 값으로 어떤 타입이 올지 미정인 상태에서 any를 쓴다면 타입스크립트를 사용하는 이유가 없다.</strong>(typescipt: 그럴거면 js로 돌아가.)</p>
<p>이럴 때 any 보다 깐깐한(?) unknown 타입을 사용하는 것이 좋다. (타입 선언 이후 조건문 등으로 타입 체크 코드 작성하면 unknown 타입을 잘 사용할 수 있다!)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[멋사] js-var사용으로 인해 초래될 수 있는 문제]]></title>
            <link>https://velog.io/@bab_shunn/%EB%A9%8B%EC%82%AC-js-var%EC%82%AC%EC%9A%A9%EC%9C%BC%EB%A1%9C-%EC%9D%B8%ED%95%B4-%EC%B4%88%EB%9E%98%EB%90%A0-%EC%88%98-%EC%9E%88%EB%8A%94-%EB%AC%B8%EC%A0%9C</link>
            <guid>https://velog.io/@bab_shunn/%EB%A9%8B%EC%82%AC-js-var%EC%82%AC%EC%9A%A9%EC%9C%BC%EB%A1%9C-%EC%9D%B8%ED%95%B4-%EC%B4%88%EB%9E%98%EB%90%A0-%EC%88%98-%EC%9E%88%EB%8A%94-%EB%AC%B8%EC%A0%9C</guid>
            <pubDate>Fri, 25 Apr 2025 04:48:52 GMT</pubDate>
            <description><![CDATA[<h3 id="var은-함수-스코프를-가진다-이것이-어떤-문제를-초래할-수-있는지-알아보자">&quot;<em>var은 함수 스코프를 가진다. 이것이 어떤 문제를 초래할 수 있는지 알아보자.</em>&quot;</h3>
<h3 id="요구사항">요구사항</h3>
<blockquote>
<p><strong>문서내의 모든 버튼을 클릭했을때</strong>,
각각 &quot;btn-0 클릭&quot;, &quot;btn-1 클릭&quot;, &quot;btn-2 클릭&quot;
... &quot;btn-n 클릭&quot;을 콘솔에 출력하시오.</p>
</blockquote>
<pre><code class="language-js">&lt;script&gt;
      window.onload = function () {
        const btns = document.querySelectorAll(&quot;button&quot;);
        // IIFE pattern
        for (var i = 0; i &lt; btns.length; i++) {
          btns[i].addEventListener(&quot;click&quot;, function () {
            console.log(`btn-${num}클릭`);
          });
        }
      };
    &lt;/script&gt;</code></pre>
<p><img src="https://velog.velcdn.com/images/bab_shunn/post/b40ecfe6-56af-456c-ba97-feac936aea67/image.png" alt="">
위의 그림에서 알수 있듯이 var을 사용하면 원하는 결과를 얻을 수 없다.</p>
<h3 id="왜-그런걸까">왜 그런걸까?</h3>
<ol>
<li><p>i = 0일 때, function() { console.log(i); } 함수를 배열에 저장.</p>
</li>
<li><p>i = 1일 때도 또 함수 저장.</p>
</li>
<li><p>i = 2일 때도 또 함수 저장.</p>
</li>
</ol>
<p>그래서 funcs에는 함수가 3개 저장:</p>
<pre><code class="language-js">[
  function() { console.log(i); },
  function() { console.log(i); },
  function() { console.log(i); }
]</code></pre>
<blockquote>
<p>이 세 함수는 모두 같은 i를 바라보고 있다! (어매나)</p>
</blockquote>
<blockquote>
<p>현재 위 코드에서 i는 모든 함수들이 같이 보는 공용 메모장 같은 것
반복문은 메모장에 숫자 적으면서 함수들을 저장해두는 거고,
함수들은 <strong>&quot;나중에 저 메모장에 적힌 거 보고 출력할게!&quot;</strong> 라고 약속해놓은 상황.
근데 반복문이 끝나고 나면, 메모장에는 3만 써 있음. <del>(var라는 놈이 이래서 문제다,,)</del>
그래서 함수들이 실행될 때마다 3만 출력</p>
</blockquote>
<pre><code class="language-js">&lt;script&gt;
      window.onload = function () {
        // 문서내의 모든 버튼을 클릭했을때
        // 각각 &quot;btn-0 클릭&quot;, &quot;btn-1 클릭&quot;, &quot;btn-2 클릭&quot;
        // ... &quot;btn-n 클릭&quot;을 콘솔에 출력하시오.
        const btns = document.querySelectorAll(&quot;button&quot;);
        // IIFE pattern
        for (var i = 0; i &lt; btns.length; i++) {
          ((num) =&gt; {
            btns[i].addEventListener(&quot;click&quot;, function () {
              console.log(`btn-${num}클릭`);
            });
          })(i);
        }
      };
    &lt;/script&gt;</code></pre>
<p><img src="https://velog.velcdn.com/images/bab_shunn/post/2e7fa1d1-9fac-42db-97e6-ef56e0af0c7e/image.png" alt="">
이런 방식으로 해결할 수 있다.</p>
<p>하지만 지금은 2025년. </p>
<h3 id="ecma6의-시대">ECMA6의 시대.</h3>
<p><u>우리는 let과 forEach를 사용함으로써 이를 해결할 수 있다.</u></p>
<pre><code class="language-js">    // ECMA5 forEach
    // Array.prototype.forEach: 배열의 각 요소를 순회하면서 지정한 콜백 함수를 실행
    // 콜백 함수는 첫번째 인자로 배열의 요소, 두번째 인자로 요소의 인덱스를 전달받는다.
    // [btn1, btn2, btn3].forEach((btn, i) =&gt; { });
    btns.forEach(function(btn, i){
      btn.addEventListener(&#39;click&#39;, function(){
        console.log(`btn-${i} 클릭`);
      });
    });</code></pre>
<pre><code class="language-js">    // ECMA6 let
    for(let i=0; i&lt;btns.length; i++){
      btns[i].addEventListener(&#39;click&#39;, function(){
        console.log(`btn-${i} 클릭`);
      });
        }</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[7. Event]]></title>
            <link>https://velog.io/@bab_shunn/7.-Event</link>
            <guid>https://velog.io/@bab_shunn/7.-Event</guid>
            <pubDate>Thu, 17 Apr 2025 15:55:04 GMT</pubDate>
            <description><![CDATA[<h2 id="1-introduction">1. Introduction</h2>
<p>이벤트(event)는 어떤 사건을 의미한다. 브라우저에서의 이벤트란 예를 들어 사용자가 버튼을 클릭했을 때, 웹페이지가 로드되었을 때와 같은 것인데 이것은 DOM 요소와 관련이 있다.</p>
<p>이벤트가 발생하는 시점이나 순서를 사전에 인지할 수 없으므로 일반적인 제어 흐름과는 다른 접근 방식이 필요하다. <u><strong>즉, 이벤트가 발생하면 누군가 이를 감지할 수 있어야 하며 그에 대응하는 처리를 호출해 주어야 한다.</strong></u></p>
<p>브라우저는 이벤트를 감지할 수 있으며 이벤트 발생 시에는 통지해 준다. 이 과정을 통해 사용자와 웹페이지는 상호작용(Interaction)이 가능하게 된다.</p>
<pre><code class="language-js">&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;body&gt;
  &lt;button class=&quot;myButton&quot;&gt;Click me!&lt;/button&gt;
  &lt;script&gt;
    document.querySelector(&#39;.myButton&#39;).addEventListener(&#39;click&#39;, function () {
      alert(&#39;Clicked!&#39;);
    });
  &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<p>이벤트가 발생하면 그에 맞는 반응을 하여야 한다. 이를 위해 <u><strong>이벤트는 일반적으로 함수에 연결되며 그 함수는 이벤트가 발생하기 전에는 실행되지 않다가 이벤트가 발생되면 실행된다.</strong> 이 함수를 이벤트 핸들러라 하며 이벤트에 대응하는 처리를 기술한다.</u></p>
<h2 id="2-이벤트-루프event-loop와-동시성concurrency">2. 이벤트 루프(Event Loop)와 동시성(Concurrency)</h2>
<p>브라우저는 <strong>단일 쓰레드(single-thread)</strong>에서 이벤트 드리븐(event-driven) 방식으로 동작한다.</p>
<p>단일 쓰레드는 쓰레드가 하나뿐이라는 의미이며 이말은 곧 하나의 작업(task)만을 처리할 수 있다는 것을 의미한다. 하지만 실제로 동작하는 웹 애플리케이션은 많은 task가 동시에 처리되는 것처럼 느껴진다. <strong>이처럼 자바스크립트의 동시성(Concurrency)을 지원하는 것이 바로 이벤트 루프(Event Loop)이다.</strong></p>
<p>브라우저의 환경을 그림으로 표현하면 아래와 같다.
<img src="https://velog.velcdn.com/images/bab_shunn/post/6ef7dbcd-5680-406f-88be-5d0cddc42f71/image.png" alt="event-loop">
이벤트 루프(Event Loop)와 브라우저의 환경</p>
<p>구글의 V8을 비롯한 대부분의 자바스크립트 엔진은 크게 2개의 영역으로 나뉜다.</p>
<blockquote>
<p><strong>Call Stack(호출 스택)</strong>
작업이 요청되면(함수가 호출되면) 요청된 작업은 순차적으로 Call Stack에 쌓이게 되고 순차적으로 실행된다. 자바스크립트는 단 하나의 Call Stack을 사용하기 때문에 해당 task가 종료하기 전까지는 다른 어떤 task도 수행될 수 없다.</p>
</blockquote>
<blockquote>
<p><strong>Heap</strong>
동적으로 생성된 객체 인스턴스가 할당되는 영역이다.</p>
</blockquote>
<p>이와 같이 자바스크립트 엔진은 단순히 작업이 요청되면 Call Stack을 사용하여 요청된 작업을 순차적으로 실행할 뿐이다. 앞에서 언급한 동시성(Concurrency)을 지원하기 위해 필요한 비동기 요청(이벤트를 포함) 처리는 자바스크립트 엔진을 구동하는 환경 즉 브라우저(또는 Node.js)가 담당한다.</p>
<p>Event Queue(Task Queue)
비동기 처리 함수의 콜백 함수, 비동기식 이벤트 핸들러, Timer 함수(setTimeout(), setInterval())의 콜백 함수가 보관되는 영역으로 이벤트 루프(Event Loop)에 의해 특정 시점(Call Stack이 비어졌을 때)에 순차적으로 Call Stack으로 이동되어 실행된다.
Event Loop(이벤트 루프)
Call Stack 내에서 현재 실행중인 task가 있는지 그리고 Event Queue에 task가 있는지 반복하여 확인한다. 만약 Call Stack이 비어있다면 Event Queue 내의 task가 Call Stack으로 이동하고 실행된다.
아래의 예제가 어떻게 동작할지 살펴보자.</p>
<p>function func1() {
  console.log(&#39;func1&#39;);
  func2();
}</p>
<p>function func2() {
  setTimeout(function () {
    console.log(&#39;func2&#39;);
  }, 0);</p>
<p>  func3();
}</p>
<p>function func3() {
  console.log(&#39;func3&#39;);
}</p>
<p>func1();
함수 func1이 호출되면 함수 func1은 Call Stack에 쌓인다. 그리고 함수 func1은 함수 func2을 호출하므로 함수 func2가 Call Stack에 쌓이고 setTimeout가 호출된다. setTimeout의 콜백함수는 즉시 실행되지 않고 지정 대기 시간만큼 기다리다가 “tick” 이벤트가 발생하면 태스크 큐로 이동한 후 Call Stack이 비어졌을 때 Call Stack으로 이동되어 실행된다.</p>
<p>event-loop</p>
<p>이벤트 루프(Event Loop)에 의한 setTimeout 콜백함수의 실행</p>
<p>DOM 이벤트 핸들러도 이와 같이 동작한다.</p>
<p>function func1() {
  console.log(&#39;func1&#39;);
  func2();
}</p>
<p>function func2() {
  // <button class="foo">foo</button>
  const elem = document.querySelector(&#39;.foo&#39;);</p>
<p>  elem.addEventListener(&#39;click&#39;, function () {
    this.style.backgroundColor = &#39;indigo&#39;;
    console.log(&#39;func2&#39;);
  });</p>
<p>  func3();
}</p>
<p>function func3() {
  console.log(&#39;func3&#39;);
}</p>
<p>func1();
함수 func1이 호출되면 함수 func1은 Call Stack에 쌓인다. 그리고 함수 func1은 함수 func2을 호출하므로 함수 func2가 Call Stack에 쌓이고 addEventListener가 호출된다. addEventListener의 콜백함수는 foo 버튼이 클릭되어 click 이벤트가 발생하면 태스크 큐로 이동한 후 Call Stack이 비어졌을 때 Call Stack으로 이동되어 실행된다.</p>
<p>#3. 이벤트의 종류
대표적인 이벤트를 소개한다. 자세한 사항은 Event reference을 참조 바란다.</p>
<p>#3.1 UI Event
Event    Description
load    웹페이지의 로드가 완료되었을 때
unload    웹페이지가 언로드될 때(주로 새로운 페이지를 요청한 경우)
error    브라우저가 자바스크립트 오류를 만났거나 요청한 자원이 존재하지 않는 경우
resize    브라우저 창의 크기를 조절했을 때
scroll    사용자가 페이지를 위아래로 스크롤할 때
select    텍스트를 선택했을 때
#3.2 Keyboard Event
Event    Description
keydown    키를 누르고 있을 때
keyup    누르고 있던 키를 뗄 때
keypress    키를 누르고 뗏을 때
#3.3 Mouse Event
Event    Description
click    마우스 버튼을 클릭했을 때
dbclick    마우스 버튼을 더블 클릭했을 때
mousedown    마우스 버튼을 누르고 있을 때
mouseup    누르고 있던 마우스 버튼을 뗄 때
mousemove    마우스를 움직일 때 (터치스크린에서 동작하지 않는다)
mouseover    마우스를 요소 위로 움직였을 때 (터치스크린에서 동작하지 않는다)
mouseout    마우스를 요소 밖으로 움직였을 때 (터치스크린에서 동작하지 않는다)
#3.4 Focus Event
Event    Description
focus/focusin    요소가 포커스를 얻었을 때
blur/foucusout    요소가 포커스를 잃었을 때
#3.5 Form Event
Event    Description
input    input 또는 textarea 요소의 값이 변경되었을 때
     contenteditable 어트리뷰트를 가진 요소의 값이 변경되었을 때
change    select box, checkbox, radio button의 상태가 변경되었을 때
submit    form을 submit할 때 (버튼 또는 키)
reset    reset 버튼을 클릭할 때 (최근에는 사용 안함)
#3.6 Clipboard Event
Event    Description
cut    콘텐츠를 잘라내기할 때
copy    콘텐츠를 복사할 때
paste    콘텐츠를 붙여넣기할 때
#4. 이벤트 핸들러 등록
이벤트가 발생했을 때 동작할 이벤트 핸들러를 이벤트에 등록하는 방법은 아래와 같이 3가지이다.</p>
<p>#4.1 인라인 이벤트 핸들러 방식
HTML 요소의 이벤트 핸들러 어트리뷰트에 이벤트 핸들러를 등록하는 방법이다.</p>
<!DOCTYPE html>
<html>
<body>
  <button onclick="myHandler()">Click me</button>
  <script>
    function myHandler() {
      alert('Button clicked!');
    }
  </script>
</body>
</html>

<p>이 방식은 더 이상 사용되지 않으며 사용해서도 않된다. 오래된 코드에서 간혹 이 방식을 사용한 것이 있기 때문에 알아둘 필요는 있다. HTML과 Javascript는 관심사가 다르므로 분리하는 것이 좋다.</p>
<p>최근 관심을 받고 있는 CBD(Component Based Development) 방식의 Angular/React/Vue.js와 같은 프레임워크/라이브러리에서는 인라인 이벤트 핸들러 방식으로 이벤트를 처리한다. CBD에서는 HTML, CSS, 자바스크립트를 뷰를 구성하기 위한 구성 요소로 보기 때문에 관심사가 다르다고 생각하지 않는다.</p>
<p>주의할 것은 onclick과 같이 on으로 시작하는 이벤트 어트리뷰트의 값으로 함수 호출을 전달한다는 것이다. 다음에 살펴볼 이벤트 핸들러 프로퍼티 방식에는 DOM 요소의 이벤트 핸들러 프로퍼티에 함수 호출이 아닌 함수를 전달한다.</p>
<p>이때 이벤트 어트리뷰트의 값으로 전달한 함수 호출이 즉시 호출되는 것은 아니다. 사실은 이벤트 어트리뷰트 키를 이름으로 갖는 함수를 암묵적으로 정의하고 그 함수의 몸체에 이벤트 어트리뷰트의 값으로 전달한 함수 호출을 문으로 갖는다. 위 예제의 경우, button 요소의 onclick 프로퍼티에 함수 function onclick(event) { foo(); }가 할당된다.</p>
<p>즉, 이벤트 어트리뷰트의 값은 암묵적으로 정의되는 이벤트 핸들러의 문이다. 따라서 아래와 같이 여러 개의 문을 전달할 수 있다.</p>
<!DOCTYPE html>
<html>
<body>
  <button onclick="myHandler1(); myHandler2();">Click me</button>
  <script>
    function myHandler1() {
      alert('myHandler1');
    }
    function myHandler2() {
      alert('myHandler2');
    }
  </script>
</body>
</html>

<p>#4.2 이벤트 핸들러 프로퍼티 방식
인라인 이벤트 핸들러 방식처럼 HTML과 Javascript가 뒤섞이는 문제는 해결할 수 있는 방식이다. 하지만 이벤트 핸들러 프로퍼티에 하나의 이벤트 핸들러만을 바인딩할 수 있다는 단점이 있다.</p>
<!DOCTYPE html>
<html>
<body>
  <button class="btn">Click me</button>
  <script>
    const btn = document.querySelector('.btn');

<pre><code>// 이벤트 핸들러 프로퍼티 방식은 이벤트에 하나의 이벤트 핸들러만을 바인딩할 수 있다
// 첫번째 바인딩된 이벤트 핸들러 =&gt; 실행되지 않는다.
btn.onclick = function () {
  alert(&#39;① Button clicked 1&#39;);
};

// 두번째 바인딩된 이벤트 핸들러
btn.onclick = function () {
  alert(&#39;① Button clicked 2&#39;);
};

// addEventListener 메소드 방식
// 첫번째 바인딩된 이벤트 핸들러
btn.addEventListener(&#39;click&#39;, function () {
  alert(&#39;② Button clicked 1&#39;);
});

// 두번째 바인딩된 이벤트 핸들러
btn.addEventListener(&#39;click&#39;, function () {
  alert(&#39;② Button clicked 2&#39;);
});</code></pre><p>  </script></p>
</body>
</html>

<p>#4.3 addEventListener 메소드 방식
addEventListener 메소드를 이용하여 대상 DOM 요소에 이벤트를 바인딩하고 해당 이벤트가 발생했을 때 실행될 콜백 함수(이벤트 핸들러)를 지정한다.</p>
<p>Event Listener</p>
<p>addEventListener 메소드</p>
<p>addEventListener 함수 방식은 이전 방식에 비해 아래와 같이 보다 나은 장점을 갖는다.</p>
<p>하나의 이벤트에 대해 하나 이상의 이벤트 핸들러를 추가할 수 있다.
캡처링과 버블링을 지원한다.
HTML 요소뿐만아니라 모든 DOM 요소(HTML, XML, SVG)에 대해 동작한다. 브라우저는 웹 문서(HTML, XML, SVG)를 로드한 후, 파싱하여 DOM을 생성한다.
addEventListener 메소드는 IE 9 이상에서 동작한다. IE 8 이하에서는 attachEvent 메소드를 사용한다.</p>
<p>if (elem.addEventListener) {    // IE 9 ~
  elem.addEventListener(&#39;click&#39;, func);
} else if (elem.attachEvent) {  // ~ IE 8
  elem.attachEvent(&#39;onclick&#39;, func);
}
addEventListener 메소드의 사용 예제를 살펴보자.</p>
<!DOCTYPE html>
<html>
<body>
  <script>
    addEventListener('click', function () {
      alert('Clicked!');
    });
  </script>
</body>
</html>

<p>위와 같이 대상 DOM 요소(target)를 지정하지 않으면 전역객체 window, 즉 DOM 문서를 포함한 브라우저의 윈도우에서 발생하는 click 이벤트에 이벤트 핸들러를 바인딩한다. 따라서 브라우저 윈도우 어디를 클릭하여도 이벤트 핸들러가 동작한다.</p>
<!DOCTYPE html>
<html>
<body>
  <label>User name <input type='text'></label>

  <script>
    const input = document.querySelector('input[type=text]');

    input.addEventListener('blur', function () {
      alert('blur event occurred!');
    });
  </script>
</body>
</html>

<p>위 예제는 input 요소에서 발생하는 blur 이벤트에 이벤트 핸들러를 바인딩하였다. 사용자 이름이 최소 2자 이상이야한다는 규칙을 세우고 이에 부합하는지 확인해보자.</p>
<!DOCTYPE html>
<html>
<body>
  <label>User name <input type='text'></label>
  <em class="message"></em>

  <script>
    const input = document.querySelector('input[type=text]');
    const msg = document.querySelector('.message');

    input.addEventListener('blur', function () {
      if (input.value.length < 2) {
        msg.innerHTML = '이름은 2자 이상 입력해 주세요';
      } else {
        msg.innerHTML = '';
      }
    });
  </script>
</body>
</html>

<p>2자 이상이라는 규칙이 바뀌면 이 규칙을 확인하는 모든 코드를 수정해야 한다. 따라서 이러한 방식의 코딩은 바람직하지 않다. 이유는 규모가 큰 프로그램의 경우 수정과 테스트에 소요되는 자원의 낭비도 문제이지만 수정에는 거의 대부분 실수가 동반되기 때문이다.</p>
<p>2자 이상이라는 규칙을 상수화하고 함수의 인수로 전달도록 수정하자. 이렇게 하면 규칙이 변경되어도 함수는 수정하지 않아도 된다.</p>
<p>그런데 addEventListener 메소드의 두번째 매개변수는 이벤트가 발생했을 때 호출될 이벤트 핸들러이다. 이때 두번째 매개변수에는 함수 호출이 아니라 함수 자체를 지정하여야 한다.</p>
<p>function foo() {
  alert(&#39;clicked!&#39;);
}
// elem.addEventListener(&#39;click&#39;, foo()); // 이벤트 발생 시까지 대기하지 않고 바로 실행된다
elem.addEventListener(&#39;click&#39;, foo);      // 이벤트 발생 시까지 대기한다
따라서 이벤트 핸들러 프로퍼티 방식과 같이 이벤트 핸들러 함수에 인수를 전달할 수 없는 문제가 발생한다. 이를 우회하는 방법은 아래와 같다.</p>
<!DOCTYPE html>
<html>
<body>
  <label>User name <input type='text'></label>
  <em class="message"></em>

  <script>
    const MIN_USER_NAME_LENGTH = 2; // 이름 최소 길이

    const input = document.querySelector('input[type=text]');
    const msg = document.querySelector('.message');

    function checkUserNameLength(n) {
      if (input.value.length < n) {
        msg.innerHTML = '이름은 ' + n + '자 이상이어야 합니다';
      } else {
        msg.innerHTML = '';
      }
    }

    input.addEventListener('blur', function () {
      // 이벤트 핸들러 내부에서 함수를 호출하면서 인수를 전달한다.
      checkUserNameLength(MIN_USER_NAME_LENGTH);
    });

    // 이벤트 핸들러 프로퍼티 방식도 동일한 방식으로 인수를 전달할 수 있다.
    // input.onblur = function () {
    //   // 이벤트 핸들러 내부에서 함수를 호출하면서 인수를 전달한다.
    //   checkUserNameLength(MIN_USER_NAME_LENGTH);
    // };
  </script>
</body>
</html>

<p>#5. 이벤트 핸들러 함수 내부의 this
#5.1 인라인 이벤트 핸들러 방식
인라인 이벤트 핸들러 방식의 경우, 이벤트 핸들러는 일반 함수로서 호출되므로 이벤트 핸들러 내부의 this는 전역 객체 window를 가리킨다.</p>
<!DOCTYPE html>
<html>
<body>
  <button onclick="foo()">Button</button>
  <script>
    function foo () {
      console.log(this); // window
    }
  </script>
</body>
</html>
#5.2 이벤트 핸들러 프로퍼티 방식
이벤트 핸들러 프로퍼티 방식에서 이벤트 핸들러는 메소드이므로 이벤트 핸들러 내부의 this는 이벤트에 바인딩된 요소를 가리킨다. 이것은 이벤트 객체의 currentTarget 프로퍼티와 같다.

<!DOCTYPE html>
<html>
<body>
  <button class="btn">Button</button>
  <script>
    const btn = document.querySelector('.btn');

<pre><code>btn.onclick = function (e) {
  console.log(this); // &lt;button id=&quot;btn&quot;&gt;Button&lt;/button&gt;
  console.log(e.currentTarget); // &lt;button id=&quot;btn&quot;&gt;Button&lt;/button&gt;
  console.log(this === e.currentTarget); // true
};</code></pre><p>  </script></p>
</body>
</html>
#5.3 addEventListener 메소드 방식
addEventListener 메소드에서 지정한 이벤트 핸들러는 콜백 함수이지만 이벤트 핸들러 내부의 this는 이벤트 리스너에 바인딩된 요소(currentTarget)를 가리킨다. 이것은 이벤트 객체의 currentTarget 프로퍼티와 같다.

<!DOCTYPE html>
<html>
<body>
  <button class="btn">Button</button>
  <script>
    const btn = document.querySelector('.btn');

<pre><code>btn.addEventListener(&#39;click&#39;, function (e) {
  console.log(this); // &lt;button id=&quot;btn&quot;&gt;Button&lt;/button&gt;
  console.log(e.currentTarget); // &lt;button id=&quot;btn&quot;&gt;Button&lt;/button&gt;
  console.log(this === e.currentTarget); // true
});</code></pre><p>  </script></p>
</body>
</html>
#6. 이벤트의 흐름
계층적 구조에 포함되어 있는 HTML 요소에 이벤트가 발생할 경우 연쇄적 반응이 일어난다. 즉, 이벤트가 전파(Event Propagation)되는데 전파 방향에 따라 버블링(Event Bubbling)과 캡처링(Event Capturing)으로 구분할 수 있다.

<p>자식 요소에서 발생한 이벤트가 부모 요소로 전파되는 것을 버블링이라 하고, 자식 요소에서 발생한 이벤트가 부모 요소부터 시작하여 이벤트를 발생시킨 자식 요소까지 도달하는 것을 캡처링이라 한다. 주의할 것은 버블링과 캡처링은 둘 중에 하나만 발생하는 것이 아니라 캡처링부터 시작하여 버블링으로 종료한다는 것이다. 즉, 이벤트가 발생했을 때 캡처링과 버블링은 순차적으로 발생한다.</p>
<p>캡처링은 IE8 이하에서 지원되지 않는다.</p>
<p>event flow</p>
<p><a href="http://www.w3.org/TR/DOM-Level-3-Events">www.w3.org/TR/DOM-Level-3-Events</a></p>
<p>addEventListener 메소드의 세번째 매개변수에 true를 설정하면 캡처링으로 전파되는 이벤트를 캐치하고 false 또는 미설정하면 버블링으로 전파되는 이벤트를 캐치한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[6. window/document에서 발생하는 이벤트?]]></title>
            <link>https://velog.io/@bab_shunn/6.-windowdocument%EC%97%90%EC%84%9C-%EB%B0%9C%EC%83%9D%ED%95%98%EB%8A%94-%EC%9D%B4%EB%B2%A4%ED%8A%B8</link>
            <guid>https://velog.io/@bab_shunn/6.-windowdocument%EC%97%90%EC%84%9C-%EB%B0%9C%EC%83%9D%ED%95%98%EB%8A%94-%EC%9D%B4%EB%B2%A4%ED%8A%B8</guid>
            <pubDate>Wed, 16 Apr 2025 00:46:43 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/bab_shunn/post/02625c5b-2b40-4af4-ad7d-86f599003067/image.png" alt=""></p>
<pre><code class="language-js">      //HTML 문서 로딩 완료, 이미지, CSS 같은 외부 컨텐츠 로딩 완료 후에 발생하는 이벤틏
      window.addEventListener(&quot;load&quot;, function () {});

      //HTML 문서 로딩 완료(DOM 객체 생성 완료) 후에 발생하는 이벤트
      document.addEventListener(&quot;DOMContentLoaded&quot;, function () {};</code></pre>
<blockquote>
<p>자바스크립트 코드를 짜며 window객체와 document객체의 차이에 대해 잘 알지 못하고 사용한 점이 생각나서 포스팅한 게시물입니다.</p>
</blockquote>
<p>평소에 window / document 객체를 참조하는 흔한 작업들은 다음과 같았다.</p>
<ol>
<li>이벤트 리스너 등록</li>
</ol>
<pre><code class="language-js">window.addEventListener(&quot;scroll&quot;, function () {});
document.addEventListener(&quot;click&quot;, function() {});</code></pre>
<ol start="2">
<li>dom에 접근</li>
</ol>
<pre><code class="language-js">document.body.style.backgroundColor = &quot;#fafafc&quot;;
document.getElementById(&quot;nextForm&quot;).focus();</code></pre>
<ol start="3">
<li>스크롤 위치 이동</li>
</ol>
<pre><code class="language-js">window.scrollTo(pos);</code></pre>
<ol start="4">
<li>창 닫기 / 열기</li>
</ol>
<pre><code class="language-js">window.close();
window.open(url);</code></pre>
<p>나열해놓고 보니 window객체와 document객체에서 하는 일이 뭔가 미묘하게 다른 건 알겠다.</p>
<p>하지만 여전히 명확한 차이가 무엇인지, 두 객체의 addEventListener가 어떤 차이인지 모르겠다.</p>
<p>다행히 이러한 궁금증을 나만 가진것이 아니었기 때문에 다른 분들이 stackoverflow에 작성하신 질문들이 있었고, 거기엔 좋은 답변들이 준비되어 있었다.</p>
<p><a href="https://stackoverflow.com/questions/9895202/what-is-the-difference-between-window-screen-and-document">https://stackoverflow.com/questions/9895202/what-is-the-difference-between-window-screen-and-document</a></p>
<p><a href="https://stackoverflow.com/questions/12045440/difference-between-document-addeventlistener-and-window-addeventlistener">https://stackoverflow.com/questions/12045440/difference-between-document-addeventlistener-and-window-addeventlistener</a></p>
<p>결론만 요약하자면 다음과 같다.</p>
<ul>
<li><p><strong>window 객체는 브라우저라는 host 환경에서 동작하는 최상위 객체</strong>로, 브라우저 환경에서 동작하는 여러 API들의 가교 역할을 한다. document, history, location, setTimout, cookieStore 등 다른 브라우저 api에 접근을 제공한다.</p>
</li>
<li><p><strong>JSDom등의 환경에서는 표준 window 객체의 인터페이스를 바탕으로 구현한 더미 window 객체(더미라고 해도 몇몇 기능들은 동작한다)를 사용</strong>하게 되는데 이 경우에는 실제 브라우저의 인터페이스와 다를 수 있으니 주의해야 한다.</p>
</li>
<li><p><strong>document 객체는 window에 로드되는 HTML 문서 그 자체를 나타내며(document 객체가 존재)</strong>, 해당 문서 내부의 HTML Element, Css 등을 제어할 수 있는 여러 메서드를 제공한다. 보편적으로 window 객체의 프로퍼티로 제공된다. <strong>document 객체는 window.document 혹은 document로 접근이 가능하다.</strong></p>
</li>
<li><p><strong>일반적으로 querySelector 등의 메서드로 css selector 기반의 element 쿼리를 많이 하게 된다.</strong></p>
</li>
<li><p><strong>window 객체는 전역으로 선언되어 있기 때문에 window객체 안에 있는 요소는 &quot;window.&quot;와 같이 window객체를 참조하지 않고도 property 이름으로 바로 접근이 가능하다. 예컨대 window.innerHeight는 그냥 innerHeight로 접근이 가능하다.</strong> ( 오... 신기... 하지만 혼동이나 scope 등의 문제로 window.innerHeight 이런 식으로 사용하는 게 좋을 것 같다. )
document객체와 window객체에서 수용 가능한 eventList가 다르기 때문에, 같은 addEventListener이 있다고 하더라도, 각 용도에 맞게 호출해야 한다.</p>
</li>
</ul>
<h2 id="결론-windowaddeventlister-vs-documentaddeventlister">[결론] <code>window.addEventLister</code> VS <code>document.addEventLister</code></h2>
<blockquote>
<p>사실 둘의 차이는 명확하지 않다. stackoverflow 에 나와 있는 설명에 의하면 각각이 제어하는 이벤트 리스너에 차이가 있기 때문에 구분해서 써야한다고 한다. 대표적으로 resize 이벤트가 있는데 이 이벤트의 경우 window 이벤트 리스너에는 있지만 document 이벤트 리스너에는 있지 않다.</p>
</blockquote>
<p>하지만 click 과 같이 window 와 document 에 공통으로 들어가 있는 이벤트도 있는데 이 경우stackoverflow 의 답변은 이렇다.</p>
<blockquote>
<p><strong>전파된 이벤트의 유일한 주요 차이점은 타이밍</strong>입니다. 이벤트는 계층 구조에서 document 가 window 보다 먼저 발생하지만 일반적으로 그 차이는 중요하지 않으므로 둘 중 하나를 선택해도 됩니다. 일반적으로 전파된 이벤트를 처리할 때는 이벤트의 소스와 가장 가까운 객체를 선택하는 것이 필요에 부합합니다. <strong>즉, 둘 다 작동할 때 window 보다 document 를 선택하는 것이 좋습니다.</strong></p>
</blockquote>
<p>직접 같은 이벤트를 등록하고 이벤트를 실행해보면, document 로 등록한 이벤트가 먼저 실행이 된다.</p>
<p><img src="https://velog.velcdn.com/images/bab_shunn/post/0919eb82-500c-4816-baf5-816285504aca/image.png" alt=""></p>
<p>참조: <a href="https://nookpi.tistory.com/56">https://nookpi.tistory.com/56</a></p>
]]></description>
        </item>
    </channel>
</rss>