<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Tae-Jun</title>
    <link>https://tae-jun.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Fri, 3 Jul 2026 22:24:11 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Tae-Jun</managingEditor>
    <item>
      <title>[머신러닝] 01. 머신러닝이란?</title>
      <link>https://tae-jun.tistory.com/28</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글은 스탠퍼드 대학교의 CS229 강의 자료를 바탕으로 작성되었습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;머신러닝이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Field of study that gives computers the ability to learn without being explicitly programmed. (Arthur Samuel 1959)&lt;br /&gt;컴퓨터가 명시적으로 프로그래밍되지 않고 학습할 수 있는 능력을 제공하는 학문 분야를 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;인공지능 &amp;gt; 머신러닝 &amp;gt; 딥러닝&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인공지능의 범주 안에 머신러닝이 포함되고 그 안에 딥러닝이 포함된다. 이 글에서는 머신러닝에 대해 설명한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;머신러닝의 종류&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;지도학습: 훈련 데이터에 레이블이 포함됨&lt;/li&gt;
&lt;li&gt;비지도학습: 훈련데이터에 레이블이 없음 &amp;sect; 준지도학습: 일부 레이블이 있는 데이터를 사용&lt;/li&gt;
&lt;li&gt;강화학습: 주어진 환경(environment)에서 행동(action)을 수행하고 관찰하여 그 결과 얻게 되는 보상(reward)을 통해 다음 행동을 결정&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Supervised Learning(지도학습)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;350&quot; data-origin-height=&quot;262&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhcykk/dJMcahJL7dW/7B44KCqc4oO5L0yednhVr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhcykk/dJMcahJL7dW/7B44KCqc4oO5L0yednhVr0/img.png&quot; data-alt=&quot;Regression 데이터 예시&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhcykk/dJMcahJL7dW/7B44KCqc4oO5L0yednhVr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbhcykk%2FdJMcahJL7dW%2F7B44KCqc4oO5L0yednhVr0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;350&quot; height=&quot;262&quot; data-origin-width=&quot;350&quot; data-origin-height=&quot;262&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Regression 데이터 예시&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;687&quot; data-origin-height=&quot;280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/T2KFP/dJMcahJL7d1/68K1fZciG8RCK4ughBKDd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/T2KFP/dJMcahJL7d1/68K1fZciG8RCK4ughBKDd0/img.png&quot; data-alt=&quot;Classification 데이터 예시&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/T2KFP/dJMcahJL7d1/68K1fZciG8RCK4ughBKDd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FT2KFP%2FdJMcahJL7d1%2F68K1fZciG8RCK4ughBKDd0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;383&quot; height=&quot;156&quot; data-origin-width=&quot;687&quot; data-origin-height=&quot;280&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Classification 데이터 예시&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 데이터가 존재한다고 가정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Learning(학습)&lt;/b&gt;: 데이터 (x, y)에 대하여, x -&amp;gt; y&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Training example&lt;/b&gt; : $(x^i,y^i)$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터는 i번째 데이터에 대해 x 값에 대한 y 라벨을 위와 같이 정의할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Training data set&lt;/b&gt;: $ \left\{ (x^i,y^i); i= 1,...,m \right\}$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;학습 데이터 셋은 이러한 데이터가 m개 있다고 가정할 때 위와 같이 정의할 수 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;회귀와 분류&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Regrssion(회귀)&lt;/b&gt;: 데이터 x를 통해 y의 값을 예측&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Classification(분류)&lt;/b&gt;: 데이터 x를 통해 y를 분류&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;머신러닝은 크게 2가지 방법으로 나뉜다. 데이터 x에 대해 y가 어느 정도 값이 될지 예측하는 &lt;b&gt;회귀&lt;/b&gt;와 데이터 x에 대해 y가 어디에 속하는지 예측하는 &lt;b&gt;분류&lt;/b&gt;이다. 예를 들면, &lt;b&gt;회귀&lt;/b&gt;는 A반의 공부 시간과 성적 점수에 대한 데이터를 가지고 있다고 가정하면 x를 공부 시간, y를 성적 점수로 두고 학습하여 어느 학생의 공부 시간이 주어졌을 때 이 학생의 점수가 몇 점인지를 예측하는 과정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 &lt;b&gt;분류&lt;/b&gt;는 공부 시간과 합격 여부에 대한 데이터를 가지고 있다고 가정했을 때 x를 공부 시간, y를 합격 여부로 두고 학습하여 어느 학생의 공부 시간이 주어졌을 때 이 학생이 합격할지 여부를 예측하는 과정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 &lt;b&gt;지도학습&lt;/b&gt;이란, 입력 x와 레이블(정답) y를 함께 훈련에 사용하고, 학습 알고리즘을 통해 새로운 입력 x가 주어질 때 y를 획득하는 과정이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;303&quot; data-origin-height=&quot;276&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkCTff/dJMcabit6Gh/E8Bj1xdS1ggV8Lkt2HHtC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkCTff/dJMcabit6Gh/E8Bj1xdS1ggV8Lkt2HHtC1/img.png&quot; data-alt=&quot;지도학습&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkCTff/dJMcabit6Gh/E8Bj1xdS1ggV8Lkt2HHtC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkCTff%2FdJMcabit6Gh%2FE8Bj1xdS1ggV8Lkt2HHtC1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;303&quot; height=&quot;276&quot; data-origin-width=&quot;303&quot; data-origin-height=&quot;276&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;지도학습&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 그림을 통해 다시 지도학습을 설명하자면, Training set(데이터셋)이 주어지면 Learning algorithm을 통해 데이터셋을 학습하고 가설 h를 생성한다. 새로운 입력 x가 주어지면 가설 h를 통해 y를 예측한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Unsupervised Learning (비지도 학습)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;543&quot; data-origin-height=&quot;343&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bedEzD/dJMcaaYaRLL/NUXiBJP9jitC4Lk1hNjl0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bedEzD/dJMcaaYaRLL/NUXiBJP9jitC4Lk1hNjl0K/img.png&quot; data-alt=&quot;비지도 학습&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bedEzD/dJMcaaYaRLL/NUXiBJP9jitC4Lk1hNjl0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbedEzD%2FdJMcaaYaRLL%2FNUXiBJP9jitC4Lk1hNjl0K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;413&quot; height=&quot;261&quot; data-origin-width=&quot;543&quot; data-origin-height=&quot;343&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;비지도 학습&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터에 레이블(정답) y 없이 입력 데이터 셋을 통해 데이터의 특징 또는 의미를 추출하는 것을 말한다. 즉 정답이 없는 데이터가 주어졌을 때 특정 알고리즘을 사용하여 데이터 간에 경계를 생성하고 분류하여 데이터의 특징을 추출하는 것을 의미한다.&lt;/p&gt;</description>
      <category>AI</category>
      <category>데이터셋</category>
      <category>머신러닝</category>
      <category>비지도학습</category>
      <category>지도학습</category>
      <author>Tae-Jun</author>
      <guid isPermaLink="true">https://tae-jun.tistory.com/28</guid>
      <comments>https://tae-jun.tistory.com/28#entry28comment</comments>
      <pubDate>Sat, 20 Dec 2025 16:08:39 +0900</pubDate>
    </item>
    <item>
      <title>Hailo Data Compiler 사용</title>
      <link>https://tae-jun.tistory.com/27</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;YOLOv8n 모델을 기준으로 작성된 글입니다.&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Dataflow 컴파일러 흐름&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Tensorflow 또는 ONNX 신경망 그래프를 Hailo와 호환되는 표현으로 변환.(har)&lt;/li&gt;
&lt;li&gt;전체 정밀도 신경망 모델을 8비트 모델로 양자화&lt;/li&gt;
&lt;li&gt;Hailo 장치에서 실행하기 위해 네트워크를 바이너리 파일(HEF)로 컴파일&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;parsing1(pt &amp;rarr; onnx)&lt;/h4&gt;
&lt;pre id=&quot;code_1766036105942&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yolo export model=./can_plastic.pt imgsz=640 format=onnx opset=11&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #cf5148;&quot; data-token-index=&quot;0&quot;&gt;Hailo에서 opset &amp;le;14만 지원&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot; data-token-index=&quot;0&quot;&gt; parsing2(onnx &amp;rarr; har) &lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1766036137725&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;hailomz parse --hw-arch hailo8 --ckpt ./small_best.onnx yolov8n&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;yolov8n은 hailo model zoo에서 지원하기 때문에 hailomz 명령어로 쉽게 컴파일이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;양자화 및 최적화 (har &amp;rarr; optimized.har)&lt;/h4&gt;
&lt;pre id=&quot;code_1766036207439&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;hailo optimize --hw-arch hailo8 --use-random-calib-set ./yolov8n.har&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hailo에서는 calibset data은 학습에 사용된 1024장의 데이터를 사용하는 것을 권장한다. 그러나 편의를 위해 random-calib-set을 사용하여 양자화 및 최적화를 진행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;컴파일&lt;/h4&gt;
&lt;pre id=&quot;code_1766036364916&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;hailomz compile yolov8n --hw-arch hailo8 --har ./yolov8n_optimized.har&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종적으로 .hef 파일이 생성되며 이를 hailo가 장착된 Raspberry Pi 5에 옮겨 추론한다.&lt;/p&gt;</description>
      <category>AI</category>
      <category>data compiler</category>
      <category>hailo</category>
      <category>RaspberryPi</category>
      <author>Tae-Jun</author>
      <guid isPermaLink="true">https://tae-jun.tistory.com/27</guid>
      <comments>https://tae-jun.tistory.com/27#entry27comment</comments>
      <pubDate>Thu, 18 Dec 2025 14:40:19 +0900</pubDate>
    </item>
    <item>
      <title>Hailo 관련 SW 설치</title>
      <link>https://tae-jun.tistory.com/26</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;976&quot; data-origin-height=&quot;751&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0KtPw/dJMcahXiCpK/rfVvhjJCGbnEJLYvL8whI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0KtPw/dJMcahXiCpK/rfVvhjJCGbnEJLYvL8whI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0KtPw/dJMcahXiCpK/rfVvhjJCGbnEJLYvL8whI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0KtPw%2FdJMcahXiCpK%2FrfVvhjJCGbnEJLYvL8whI1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;976&quot; height=&quot;751&quot; data-origin-width=&quot;976&quot; data-origin-height=&quot;751&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;raspberryPi 5&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Hailo-8&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hailo는 모델을 Hailo 환경에 맞추어 컴파일하는 Model Build Environment 부분과 컴파일된 모델을 실행하는 Runtime Environment로 나뉜다. Model Build Environment는 양자화 시 많은 GPU 사용을 필요로 하기 때문에 workstation에서 진행해야 하며 Runtime Environment는 실제 모델이 구동되는 RPI5에 설치되어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Model Build Environment&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;목적: 모델 컴파일&lt;/li&gt;
&lt;li&gt;위치: 워크스테이션(GPU 사용이 가능한 컴퓨터)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Runtime Environment&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;목적: 모델 추론&lt;/li&gt;
&lt;li&gt;위치: RaspberryPi 5&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Model Build&amp;nbsp; 환경 설치(hailo sdk suite)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;도커 설치가 선행되어야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Nvidia-docker2 설치&lt;/p&gt;
&lt;pre id=&quot;code_1766035635502&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;distribution=$(. /etc/os-release;echo $ID$VERSION_ID) \
&amp;amp;&amp;amp; curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add - \
&amp;amp;&amp;amp; curl -s -L \
https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list \
| sudo tee /etc/apt/sources.list.d/nvidia-docker.list

sudo apt-get update
sudo apt-get install -y nvidia-docker2
sudo systemctl restart docker&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;docker 파일 설치&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hailo developer zone 홈페이지에서 도커 이미지 파일을 설치(zip 파일)&lt;/p&gt;
&lt;pre id=&quot;code_1766035704212&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;unzip hailo_ai_sw_suite_&amp;lt;version&amp;gt;.zip&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;docker 파일 실행&lt;/h4&gt;
&lt;pre id=&quot;code_1766035726630&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;./hailo_ai_sw_suite_docker_run.sh&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Runtime 환경 설치&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;시스템 업그레이드&lt;/h4&gt;
&lt;pre id=&quot;code_1766035782156&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo apt update &amp;amp;&amp;amp; sudo apt full-upgrade&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Gen 3.0 속도 적용&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PCIe 전송 인터페이스를 Gen 3.0으로 적&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1766035812148&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo vi /boot/firmware/config.txt.
# 아랫줄 추가
dtparam=pciex1_gen=3&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;라이브러리 설치&lt;/h4&gt;
&lt;pre id=&quot;code_1766035899218&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo apt install hailo-all&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Hailo 커널 장치 드라이버 및 펌웨어&lt;/li&gt;
&lt;li&gt;HailoRT 미들웨어 소프트웨어&lt;/li&gt;
&lt;li&gt;Hailo Tappas 핵심 후처리 라이브러리&lt;/li&gt;
&lt;li&gt;Hailo ricam-app 후처리 소프트웨어 데모 단계&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;라이브러리 설치 확인&lt;/h4&gt;
&lt;pre id=&quot;code_1766035961886&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;hailortcli fw-control identify&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;만약 Error 발생 시 update, full-upgrade와 라이브러리 재설치 진행&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>AI</category>
      <category>hailo</category>
      <category>model build</category>
      <category>Runtime</category>
      <author>Tae-Jun</author>
      <guid isPermaLink="true">https://tae-jun.tistory.com/26</guid>
      <comments>https://tae-jun.tistory.com/26#entry26comment</comments>
      <pubDate>Thu, 18 Dec 2025 14:33:25 +0900</pubDate>
    </item>
    <item>
      <title>[k3s] image가 사라지는 문제</title>
      <link>https://tae-jun.tistory.com/25</link>
      <description>&lt;h1&gt;base image&lt;/h1&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;베이스 이미지는 레지스트리/이미지:태그로 구성된다. 예를 들어, &quot; &lt;span&gt;nvcr.io/nvidia/tensorflow:23.11-tf2-py3&quot;와 같은 베이스 이미지가 있다면 아래와 같다.&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&lt;b&gt;레지스트리&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: nvcr.io/nvidia &lt;/span&gt;&lt;br /&gt;&lt;span&gt;&lt;b&gt;이미지&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: tensorflow &lt;/span&gt;&lt;br /&gt;&lt;span&gt;&lt;b&gt;태그&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: 23.11-tf2-py3 &lt;/span&gt;&lt;br /&gt;이미지는 1bit라도 바뀌면 달라지는 다이제스트라는 고유한 해시값(SHA256)으로 구분된다. 즉 같은 이미지명, 태그명이더라도 다이제스트는 달라질 수 있다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;이미지 pull 정책&lt;/h2&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;컨테이너 생성 시 이미지 pull 정책은 3가지가 존재한다.&lt;br /&gt;&lt;b&gt;IfNotPresent&lt;/b&gt;&lt;br /&gt;이미지가 로컬에 없는 경우만 pull한다. 즉 이미지의 이름, 태그명이 같으면 pull하지 않는다. 다이제스트를 질의하지 않으므로 이미지가 업데이트 된 경우 같은 이름, 태그명이더라도 다른 이미지이기 때문에 이미지의 업데이트 여부는 확인할 수 없다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;b&gt;Always&lt;/b&gt;&lt;br /&gt;컨테이너를 실행할 때마다 레지스트리에 이름, 다이제스트가 있는지 질의한다. 일치하는 다이제스트를 가진 컨테이너 이미지가 이미 로컬에 있다면 캐시된 이미지를 사용한다. 그렇지 않으면 검색된 다이제스트를 가진 이미지를 pull한다.&lt;br /&gt;즉 항상 레지스트리에 이미지가 있는지 검색한 후, 로컬 캐시에 다이제스트가 있는지 확인한 후 이미지 pull을 결정한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;태그명이 lastest라면 이미지의 다이제스트가 변경될 수 있지만 사용자는 이를 알 수 없다. 따라서 always를 사용하지 않는다면 lastest 태그 사용을 지양하는 것이 좋다.&lt;/blockquote&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&lt;b&gt;Never&lt;/b&gt;&lt;br /&gt;이미지를 pull하려고 하지 않는다. 만약 이미지가 로컬에 존재한다면 컨테이너를 실행하며, 존재하지 않으면 실행에 실패한다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;Garbage Collection&lt;/h2&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;kubelet에는 클러스터 자원을 정리하기 위한 GC가 존재한다. 사용되지 않는 이미지에 대한 가비지 수집을 5분마다, 컨테이너에 대한 가비지 수집을 1분마다 수행한다.&lt;br /&gt;가비지 수집을 결정하는 기준은 아래와 같다.&lt;br /&gt;&lt;b&gt;HighThresholdPercent&lt;/b&gt;&lt;br /&gt;&amp;nbsp;이미지 디스크 사용량이 이 값을 초과하면 마지막으로 사용된 시간을 기준으로 오래된 이미지 순서대로 이미지를 삭제한다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;b&gt;LowThresholdPercent&lt;/b&gt;&lt;br /&gt;&amp;nbsp;GC는 이 값에 도달할 때까지 이미지를 삭제한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;이러한 변수와 더불어 식별할 수 없고, 삭제된 컨테이너들을 오래된 순서대로 가비지 수집을 수행한다.&lt;/blockquote&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;이미지가 사라지는 문제&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;982&quot; data-origin-height=&quot;56&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eyiG09/btsPptGacSf/RnH5Kw1jZGwv8yj2GQkXtK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eyiG09/btsPptGacSf/RnH5Kw1jZGwv8yj2GQkXtK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eyiG09/btsPptGacSf/RnH5Kw1jZGwv8yj2GQkXtK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeyiG09%2FbtsPptGacSf%2FRnH5Kw1jZGwv8yj2GQkXtK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;982&quot; height=&quot;56&quot; data-origin-width=&quot;982&quot; data-origin-height=&quot;56&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;로그를 살펴보면 현재 highThreshold가 85, lowThreshold가 80으로 설정되어 있다. 현재 사용량이 highThreshold를 초과하였고 GC는 실행중이 아닌 이미지를 삭제시키고 있었다. 문제를 해결하기 위한 방법은 3가지가 존재한다.&lt;br /&gt;&lt;b&gt;1. GC 정책 상향 조정&lt;/b&gt;&lt;br /&gt;임계값을 85보다 높은 값으로 설정하여 GC가 트리거 되지 않게 한다. 그러나 이 값을 변경하면 디스크가 가득 찰 수 있고 이미지 pull 시도도 실패할 수 있다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;2. 디스크 용량 확보&lt;br /&gt;안 쓰는 Docker 이미지를 제거하거나 캐시 저장 위치 디스크를 변경한다.&lt;br /&gt;현재 사용하고 있는 이미지는 모두 사용중인 이미지이고 캐시 저장 위치 디스크를 변경할 수 있는 권한이 없다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;3. DaemonSet 설정:&amp;nbsp;&lt;br /&gt;DaemonSet으로 Sleep 상태의 컨테이너를 띄워 이미지가 사용중인 상태로 변경한다.&lt;br /&gt;GC는 사용되지 앟는 이미지에 대해 가비지 수집을 수행한다. 따라서 이미지를 사용중인 상태로 변경하면 GC가 해당 이미지를 제거하지 못한다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;현재 실행 가능한 방법이 3번밖에 존재하지 않기 때문에 DaemonSet 설정을 수행했다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;DaemonSet 설정&lt;/h2&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;python의 kubernetes api를 사용했다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;class KubeUtils:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def __init__(self):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;config.load_kube_config() # local에서 실행 시
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#config.load_incluster_config() # container에서 실행 시
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.CoreApi = client.CoreV1Api()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.AppApi = client.AppsV1Api()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.batchApi = client.BatchV1Api()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.rbac_api = client.RbacAuthorizationV1Api()&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def applyDaemonSet(self):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;metadata = client.V1ObjectMeta(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;name=&quot;image-prepull&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;labels={&quot;app&quot;: &quot;image-prepull&quot;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;namespace=&quot;default&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;container = client.V1Container(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;name=&quot;puller&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;image=&quot;nvcr.io/nvidia/tensorflow:23.11-tf2-py3&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;command=[&quot;sleep&quot;, &quot;3600&quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# Pod 템플릿 정의
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;pod_spec = client.V1PodSpec(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;containers=[container],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;tolerations=[client.V1Toleration(operator=&quot;Exists&quot;)],&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;restart_policy=&quot;Always&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;node_selector={&quot;role&quot;: &quot;workstation&quot;} # workstation 노드만 적용
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;template = client.V1PodTemplateSpec(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;metadata=client.V1ObjectMeta(labels={&quot;app&quot;: &quot;image-prepull&quot;}),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;spec=pod_spec
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# DaemonSet 정의
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;daemonset_spec = client.V1DaemonSetSpec(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;selector=client.V1LabelSelector(match_labels={&quot;app&quot;: &quot;image-prepull&quot;}),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;template=template
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;daemonset = client.V1DaemonSet(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;api_version=&quot;apps/v1&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;kind=&quot;DaemonSet&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;metadata=metadata,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;spec=daemonset_spec
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# DaemonSet 생성
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;apps_v1 = client.AppsV1Api()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;apps_v1.create_namespaced_daemon_set(namespace=&quot;default&quot;, body=daemonset)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&quot;DaemonSet 'image-prepull' 생성 완료&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;role이 workstation인 node에만 DaemonSet을 적용하였다.&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;nvcr.io/&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;nvidia&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;/tensorflow:23.11-tf2-py3&lt;/span&gt; 이미지로 컨테이너를 실행하며 3600초 동안 sleep 후 컨테이너를 재실행한다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;715&quot; data-origin-height=&quot;53&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SKXb3/btsPrO9t8nE/dWYQKJhXklTP4RUIgo6HV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SKXb3/btsPrO9t8nE/dWYQKJhXklTP4RUIgo6HV0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SKXb3/btsPrO9t8nE/dWYQKJhXklTP4RUIgo6HV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSKXb3%2FbtsPrO9t8nE%2FdWYQKJhXklTP4RUIgo6HV0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;715&quot; height=&quot;53&quot; data-origin-width=&quot;715&quot; data-origin-height=&quot;53&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;DaemonSet 실행 후 이미지가 삭제되지 않는 것을 확인할 수 있다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;추후 workstation 재설정 시 docker의 캐시 디스크를 따로 확보할 예정이다.&lt;/p&gt;</description>
      <category>MLOps</category>
      <category>daemonset</category>
      <category>image pull policy</category>
      <category>K3S</category>
      <category>kubernetes</category>
      <category>이미지 사라짐</category>
      <author>Tae-Jun</author>
      <guid isPermaLink="true">https://tae-jun.tistory.com/25</guid>
      <comments>https://tae-jun.tistory.com/25#entry25comment</comments>
      <pubDate>Sun, 20 Jul 2025 20:01:29 +0900</pubDate>
    </item>
    <item>
      <title>[YOLO] COCO 데이터 형식에서 YOLO 데이터 형식으로 바꾸기</title>
      <link>https://tae-jun.tistory.com/24</link>
      <description>&lt;pre id=&quot;code_1739211942091&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;data
	ㄴ train
		-annotations.json
		-이미지&amp;rsquo;s&amp;hellip;
	ㄴ valid
		-annotations.json
		-이미지&amp;rsquo;s&amp;hellip;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;YOLO 형식으로 바꾸기 전 COCO 형식으로 된 데이터의 폴더 구조이다. 위 데이터를 아래와 같은 형식으로 바꾸려고 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1739211981734&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;datasets
	ㄴ images
		ㄴ train
			- 이미지's...
		ㄴ val
			- 이미지's...
	ㄴ labels
		ㄴ train
			- 라벨's...
		ㄴ val
			- 라벨's...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 COCO 데이터 형식을 YOLO 데이터 형식으로 바꾸는 코드는 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1739211780728&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import json
import os
import shutil

def cocoToYolo(coco_json_path, output_label_dir):
    # COCO JSON 파일 로드
    with open(coco_json_path, &quot;r&quot;) as f:
        coco_data = json.load(f)

    # 카테고리 매핑 (클래스 ID -&amp;gt; 이름)
    category_mapping = {cat[&quot;id&quot;]: cat[&quot;name&quot;] for cat in coco_data[&quot;categories&quot;]}
    # 이미지 ID 매핑
    image_id_mapping = {img[&quot;id&quot;]: img[&quot;file_name&quot;] for img in coco_data[&quot;images&quot;]}

    # 어노테이션 변환
    for ann in coco_data[&quot;annotations&quot;]:
        img_id = ann[&quot;image_id&quot;]
        class_id = ann[&quot;category_id&quot;]
        segmentation = ann[&quot;segmentation&quot;]

        if img_id not in image_id_mapping or not segmentation:
            continue  # 해당 이미지가 없거나 세그멘테이션 데이터가 없으면 건너뜀

        # 파일명 생성
        image_name = image_id_mapping[img_id]
        label_file_path = os.path.join(output_label_dir, image_name.replace(&quot;.jpg&quot;, &quot;.txt&quot;))

        # 세그멘테이션 좌표를 YOLO 형식으로 변환
        yolo_lines = []
        for poly in segmentation:
            normalized_poly = [
                f&quot;{x / coco_data['images'][img_id]['width']} {y / coco_data['images'][img_id]['height']}&quot;
                for x, y in zip(poly[::2], poly[1::2])
            ]
            yolo_lines.append(f&quot;{class_id} &quot; + &quot; &quot;.join(normalized_poly))

        # YOLO 레이블 파일 저장
        with open(label_file_path, &quot;w&quot;) as f:
            f.write(&quot;\n&quot;.join(yolo_lines))

def moveImg(origin_path, moved_path):
    # train 이미지 이동
    for file in os.listdir(origin_path):
        if file.lower().endswith((&quot;.jpg&quot;, &quot;.png&quot;, &quot;.jpeg&quot;)):  # 이미지 파일만 이동
            shutil.move(os.path.join(origin_path, file), os.path.join(moved_path, file))

def convertToZeroIndex():
    label_dir = &quot;./datasets/labels/&quot;
    # 모든 .txt 파일 가져오기
    for subset in [&quot;train&quot;, &quot;val&quot;]:
        path = os.path.join(label_dir, subset)
        for file_name in os.listdir(path):
            if file_name.endswith(&quot;.txt&quot;):
                file_path = os.path.join(path, file_name)

                # 파일 읽고 클래스 ID 변경
                with open(file_path, &quot;r&quot;) as f:
                    lines = f.readlines()

                new_lines = []
                for line in lines:
                    parts = line.strip().split()
                    if len(parts) &amp;gt; 0 and parts[0] == &quot;1&quot;:
                        parts[0] = &quot;0&quot;  # 클래스 ID를 0으로 변경
                        new_lines.append(&quot; &quot;.join(parts))
                    else:
                        print(&quot;error 발생&quot;)
                # 변경된 내용 저장
                with open(file_path, &quot;w&quot;) as f:
                    f.write(&quot;\n&quot;.join(new_lines))

# 데이터 폴더 경로
data_folder_path = &quot;./data&quot;

#경로 설정
coco_train_path = data_folder_path + &quot;/train/_annotations.coco.json&quot;
coco_valid_path = data_folder_path + &quot;/valid/_annotations.coco.json&quot;
output_label_dirs = [&quot;./datasets/images/train&quot;, &quot;./datasets/images/val&quot;, &quot;./datasets/labels/train&quot;, &quot;./datasets/labels/val&quot;]  # YOLO 레이블이 저장될 폴더

# 폴더 생성
for path_dir in output_label_dirs:
    os.makedirs(path_dir, exist_ok=True)

# Convert COCO TO YOLO
for p in [(coco_train_path, 2), (coco_valid_path, 3)]:
    cocoToYolo(p[0], output_label_dirs[p[1]])

# move Img
moveImg(data_folder_path+&quot;/train&quot;, output_label_dirs[0])
moveImg(data_folder_path+&quot;/valid&quot;, output_label_dirs[1])

convertToZeroIndex() # 클래스 1 -&amp;gt; 0으로 변경
shutil.rmtree(data_folder_path) # 기존 폴더 삭제&lt;/code&gt;&lt;/pre&gt;</description>
      <category>AI</category>
      <category>COCO</category>
      <category>yolo</category>
      <category>변환</category>
      <author>Tae-Jun</author>
      <guid isPermaLink="true">https://tae-jun.tistory.com/24</guid>
      <comments>https://tae-jun.tistory.com/24#entry24comment</comments>
      <pubDate>Tue, 11 Feb 2025 03:27:55 +0900</pubDate>
    </item>
    <item>
      <title>[YOLOv8] custom training하여 바닥 Instance Segmentation하기</title>
      <link>https://tae-jun.tistory.com/23</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;데이터&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;데이터 다운로드&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;886&quot; data-origin-height=&quot;308&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RQHGK/btsMcBz0vcS/nZ5ptkD1P3fTWkYlMP8o61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RQHGK/btsMcBz0vcS/nZ5ptkD1P3fTWkYlMP8o61/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RQHGK/btsMcBz0vcS/nZ5ptkD1P3fTWkYlMP8o61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRQHGK%2FbtsMcBz0vcS%2FnZ5ptkD1P3fTWkYlMP8o61%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;886&quot; height=&quot;308&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;886&quot; data-origin-height=&quot;308&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://universe.roboflow.com/coba1/floor_segmentation3&quot;&gt;데이터 다운로드&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Download Dataset &amp;rarr; Download dataset &amp;rarr; Continue &amp;rarr; Format을 COCO Segmentation으로 변경 &amp;rarr; Continue&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;데이터 정보&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TRAIN SET: 930개&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VALID SET: 399개&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 형식: COCO&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LABEL NAME: tehel&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;데이터 구조&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;haml&quot;&gt;&lt;code&gt;data
	ㄴ train
		-annotations.json
		-이미지&amp;rsquo;s&amp;hellip;
	ㄴ valid
		-annotations.json
		-이미지&amp;rsquo;s&amp;hellip;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;데이터 전처리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;YOLOv8에서 COCO 데이터 형식을 지원한다고 들었으나,, 어떤 자료에서도 COCO형식 그대로 학습 하는걸 찾지 못했다. 따라서 COCO형식의 데이터를 YOLO형식의 데이터로 변환하여 학습했다. 데이터를 다운 받은 후 아래 파일을 실행하면 YOLO형식으로 변환된 데이터를 얻을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;convertData.py(&lt;a title=&quot;코드 설명&quot; href=&quot;https://tae-jun.tistory.com/24&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;코드 설명&lt;/a&gt;)&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;1. 새로 생성할 데이터를 담을 폴더 생성
2. COCO DATA TYPE -&amp;gt; YOLO DATA TYPE
3. 이미지를 새로운 폴더로 이동
4. 1 인덱스 -&amp;gt; 0 인덱스(annotation file의 클래스)
5. 기존 데이터 폴더 삭제
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변환된 데이터의 구조는 아래와 같다.&lt;/p&gt;
&lt;pre class=&quot;fsharp&quot;&gt;&lt;code&gt;datasets
	ㄴ images
		ㄴ train
			- 이미지's...
		ㄴ val
			- 이미지's...
	ㄴ labels
		ㄴ train
			- 라벨's...
		ㄴ val
			- 라벨's...
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;학습 및 예측&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;data.yaml&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;학습된 데이터셋의 정보를 알려줄 data.yaml 작성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;data.yaml&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;avrasm&quot;&gt;&lt;code&gt;path: [데이터셋의 경로]/datasets
train: images/train  # 훈련 이미지 경로
val: images/val   # 검증 이미지 및 경로

nc: 1 # 클래스의 개수
names: [&quot;tehel&quot;] #클래스의 이름
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ultralytics와 YOLOV8&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ultralytics를 통해 쉽게 yolov8을 학습 시킬 수 있으며 아래 명령어를 통해 다운받을 수 있다.&lt;/p&gt;
&lt;pre class=&quot;cmake&quot;&gt;&lt;code&gt;pip install ultralytics
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;yolov8의 Instance Segmentation에는 총 5가지의 모델이 존재하며 n에서 x모델로 갈수록 성능은 좋아지고 모델은 무거워진다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;852&quot; data-origin-height=&quot;574&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dB93zF/btsMbEjWBRt/4kUUKbqkO8XsRAbGRqq70K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dB93zF/btsMbEjWBRt/4kUUKbqkO8XsRAbGRqq70K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dB93zF/btsMbEjWBRt/4kUUKbqkO8XsRAbGRqq70K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdB93zF%2FbtsMbEjWBRt%2F4kUUKbqkO8XsRAbGRqq70K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;852&quot; height=&quot;574&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;852&quot; data-origin-height=&quot;574&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문서에서는 &lt;a href=&quot;http://yolov8n-seg.pt&quot;&gt;yolov8n-seg.pt&lt;/a&gt; 모델을 기준으로 설명한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;학습&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드를 통해 모델을 불러오고 학습한다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;from ultralytics import YOLO

model = YOLO(&quot;yolov8n-seg.pt&quot;)  # Segmentation 모델
model.train(data=&quot;./data.yaml&quot;, epochs=50, imgsz=640)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;317&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bySa4R/btsMedLt3LR/fUgefnnFAlBTJ1OvXvpAB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bySa4R/btsMedLt3LR/fUgefnnFAlBTJ1OvXvpAB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bySa4R/btsMedLt3LR/fUgefnnFAlBTJ1OvXvpAB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbySa4R%2FbtsMedLt3LR%2FfUgefnnFAlBTJ1OvXvpAB1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1284&quot; height=&quot;317&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;317&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예측&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;학습이 완료된 모델은 runs/segment/train/weights에 저장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저장된 모델을 불러와 예측하는 코드는 아래와 같다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;from ultralytics import YOLO

model = YOLO(&quot;last.pt&quot;)  # Segmentation 모델
model.predict(source = &quot;ASD.mp4&quot;, save=True, show_labels=True, show_conf=True, conf=0.5, save_txt=False, save_crop=False, line_width=2)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예측이 완료된 결과물&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;924&quot; data-origin-height=&quot;928&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYRoiw/btsMdGN8Qqo/aqKWLqjzzx0eeGQL7mqHP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYRoiw/btsMdGN8Qqo/aqKWLqjzzx0eeGQL7mqHP1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYRoiw/btsMdGN8Qqo/aqKWLqjzzx0eeGQL7mqHP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYRoiw%2FbtsMdGN8Qqo%2FaqKWLqjzzx0eeGQL7mqHP1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;924&quot; height=&quot;928&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;924&quot; data-origin-height=&quot;928&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예측이 완료된 결과물은 runs/segment/predict에 저장된다.&amp;nbsp;&lt;/p&gt;</description>
      <category>AI</category>
      <category>custom training</category>
      <category>instance segmentation</category>
      <category>ultralytics</category>
      <category>YOLOv8</category>
      <author>Tae-Jun</author>
      <guid isPermaLink="true">https://tae-jun.tistory.com/23</guid>
      <comments>https://tae-jun.tistory.com/23#entry23comment</comments>
      <pubDate>Mon, 10 Feb 2025 15:25:48 +0900</pubDate>
    </item>
    <item>
      <title>[docker] 컨테이너 상에서 gpu 사용량 확인 in jetson nano</title>
      <link>https://tae-jun.tistory.com/22</link>
      <description>&lt;pre id=&quot;code_1733939091153&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; tegrastats&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;jetson nano에서 gpu 사용량을 확인하기 위해선&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;tegrastats 명령어를 사용하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 컨테이너 상에서 tegrastats 명령어를 사용하면 command not found 에러가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 컨테이너 내부에서 /usr/bin/tegrastats:/usr/bin/tegrastats에 접근하지 못하기 때문이다. 따라서 컨테이너 실행 시 이 폴더를 마운트 해주면 tegrastats 명령어를 사용할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1733939229015&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo docker run -it 
    -v $(pwd):/workspace 
    -v /usr/bin/tegrastats:/usr/bin/tegrastats 
    --name mlops-platform-env 
    --privileged 
    --runtime=nvidia 
    mlops-platform-env&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프레임워크/docker</category>
      <category>docker</category>
      <category>gpu 사용량</category>
      <category>jetson nano</category>
      <author>Tae-Jun</author>
      <guid isPermaLink="true">https://tae-jun.tistory.com/22</guid>
      <comments>https://tae-jun.tistory.com/22#entry22comment</comments>
      <pubDate>Thu, 12 Dec 2024 02:48:28 +0900</pubDate>
    </item>
    <item>
      <title>[docker] 컨테이너 내에서 flask 서버 구동 시 외부 접속하기</title>
      <link>https://tae-jun.tistory.com/21</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;docker 컨테이너 내에서 flask 서버를 구동시키고 내부망의 다른 컴퓨터에서 접속 시 자꾸 연결이 거부됐다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;710&quot; data-origin-height=&quot;459&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bO7La3/btsLgcUv8bY/lSuCltwwvsBGM0Dg55hk0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bO7La3/btsLgcUv8bY/lSuCltwwvsBGM0Dg55hk0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bO7La3/btsLgcUv8bY/lSuCltwwvsBGM0Dg55hk0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbO7La3%2FbtsLgcUv8bY%2FlSuCltwwvsBGM0Dg55hk0K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;710&quot; height=&quot;459&quot; data-origin-width=&quot;710&quot; data-origin-height=&quot;459&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결 방법은 간단했다. 컨테이너 실행 시 flask 서버 포트를 매핑해주면 해결된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 5000번 포트를 사용할거기 때문에 -p 5000:5000 옵션을 줬다.&lt;/p&gt;
&lt;pre id=&quot;code_1733938705835&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo docker run -it --name mlops-platform-env -p 5000:5000 mlops-platform-env&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 기본적으로 Flask 코드에서 host = '0.0.0.0'으로 해줘야 외부에서 접속이 가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1733938841057&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if __name__ == &quot;__main__&quot;:
    host = '0.0.0.0'
    port = &quot;5000&quot;
    app.run(debug=True, host=host, port=port)&lt;/code&gt;&lt;/pre&gt;</description>
      <category>프레임워크/docker</category>
      <category>docker</category>
      <category>flask</category>
      <category>외부접속</category>
      <category>포트</category>
      <author>Tae-Jun</author>
      <guid isPermaLink="true">https://tae-jun.tistory.com/21</guid>
      <comments>https://tae-jun.tistory.com/21#entry21comment</comments>
      <pubDate>Thu, 12 Dec 2024 02:41:45 +0900</pubDate>
    </item>
    <item>
      <title>JWT Refresh Token을 이용한 로그인 (with Node.js, React Native) - 2. 클라이언트</title>
      <link>https://tae-jun.tistory.com/20</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a title=&quot;서버 구현&quot; href=&quot;https://tae-jun.tistory.com/19&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;서버 구현&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1687300694502&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;JWT Refresh Token을 이용한 로그인 (with Node.js, React Native) - 1. 서버&quot; data-og-description=&quot;진행중인 프로젝트에 로그인 기능을 구현하기 위해 JWT를 사용하여 refresh token/ access token을 구현했다. 글이 길어져 서버 구현 / 클라이언트 구현으로 나누어 작성한다. 또한 JWT의 개념적인 내용보&quot; data-og-host=&quot;tae-jun.tistory.com&quot; data-og-source-url=&quot;https://tae-jun.tistory.com/19&quot; data-og-url=&quot;https://tae-jun.tistory.com/19&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/i5XBk/hyS4mOXyBS/Gk6hWIkezj1XWUfT06dsd0/img.png?width=800&amp;amp;height=414&amp;amp;face=0_0_800_414,https://scrap.kakaocdn.net/dn/GXuri/hyS4suR254/8hMCJC45PnJnTW7jJoZjhk/img.png?width=800&amp;amp;height=414&amp;amp;face=0_0_800_414,https://scrap.kakaocdn.net/dn/yH5E5/hyS4pSs0iN/snlUbbx9Vm944UvSgyeP4k/img.png?width=1242&amp;amp;height=643&amp;amp;face=0_0_1242_643&quot;&gt;&lt;a href=&quot;https://tae-jun.tistory.com/19&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://tae-jun.tistory.com/19&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/i5XBk/hyS4mOXyBS/Gk6hWIkezj1XWUfT06dsd0/img.png?width=800&amp;amp;height=414&amp;amp;face=0_0_800_414,https://scrap.kakaocdn.net/dn/GXuri/hyS4suR254/8hMCJC45PnJnTW7jJoZjhk/img.png?width=800&amp;amp;height=414&amp;amp;face=0_0_800_414,https://scrap.kakaocdn.net/dn/yH5E5/hyS4pSs0iN/snlUbbx9Vm944UvSgyeP4k/img.png?width=1242&amp;amp;height=643&amp;amp;face=0_0_1242_643');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;JWT Refresh Token을 이용한 로그인 (with Node.js, React Native) - 1. 서버&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;진행중인 프로젝트에 로그인 기능을 구현하기 위해 JWT를 사용하여 refresh token/ access token을 구현했다. 글이 길어져 서버 구현 / 클라이언트 구현으로 나누어 작성한다. 또한 JWT의 개념적인 내용보&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;tae-jun.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 서버 구현에 이어 React Native로 로그인 과정을 구현에 대한 글을 작성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 구현 내용을 블로그에 담을 순 없어서 화면 구성에 대한 내용은 제외하고 로그인 로직에 중점을 맞추어 글을 작성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 라이브러리 설치&lt;/h4&gt;
&lt;pre id=&quot;code_1687295572538&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 내부 저장소 사용
npm install @react-native-async-storage/async-storage

# http 통신 사용
npm install axios

# 토스트 사용
npm install react-native-toast-message

# 화면 전환(navigation) 사용
npm install @react-navigation/native 
npm install @react-navigation/stack&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. tokenUtils.js&lt;/h4&gt;
&lt;pre id=&quot;code_1687295855533&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import AsyncStorage from &quot;@react-native-async-storage/async-storage&quot;;
import axios from &quot;axios&quot;;
import { Toast } from &quot;react-native-toast-message/lib/src/Toast&quot;;
const URL = 'http://IP:PORT'

const showToast = (text) =&amp;gt;{
    Toast.show({
        type: 'error',
        position: 'bottom',
        text1: text,
      });
};

export const getTokens = (email, password, navigation) =&amp;gt; {
    axios.post(`${URL}/login`,
    {
      &quot;userId&quot;:email,
      &quot;userpw&quot;:password
    })
    .then(res =&amp;gt;{{
          //accessToken, refreshToken 로컬에 저장
          if (res.status === 200){
            AsyncStorage.setItem('Tokens', JSON.stringify({
              'accessToken': res.data.accessToken,
              'refreshToken': res.data.refreshToken,
              'userId': res.data.userId
            }))
            navigation.navigate('HomeTab');
          }

    }})
    .catch(error =&amp;gt;{
            if(error.response.status === 401){
                showToast(error.response.data)
            }
            else{
                showToast(&quot;알수없는 오류&quot;)
            } 
          
    })
};

const getTokenFromLocal = async () =&amp;gt; {
  try {
    const value = await AsyncStorage.getItem(&quot;Tokens&quot;);
    if (value !== null) {
      return JSON.parse(value)
    }
    else{
      return null;
    }
  } catch (e) {
    console.log(e.message);
  }
};


export const verifyTokens = async (navigation) =&amp;gt; {
  const Token = await getTokenFromLocal();

  // 최초 접속
  if (Token === null){
    navigation.reset({routes: [{name: &quot;AuthPage&quot;}]});
  }
  // 로컬 스토리지에 Token데이터가 있으면 -&amp;gt; 토큰들을 헤더에 넣어 검증 
  else{
    const headers_config = {
      &quot;refresh&quot;: Token.refreshToken,
      Authorization: `Bearer ${Token.accessToken}`   
    };

    try {
      const res = await axios.get(`${URL}/refresh`, {headers: headers_config})

      // accessToken 만료, refreshToken 정상 -&amp;gt; 재발급된 accessToken 저장 후 자동 로그인
      AsyncStorage.setItem('Tokens', JSON.stringify({
        ...Token,
        'accessToken': res.data.data.accessToken,
      }))
      navigation.reset({routes: [{name: &quot;HomeTab&quot;}]});

    } catch(error){
      const code = error.response.data.code; 

      // accessToken 만료, refreshToken 만료 -&amp;gt; 로그인 페이지
      if(code === 401){
        navigation.reset({routes: [{name: &quot;AuthPage&quot;}]});
      }
      // accessToken 정상, refreshToken 정상 -&amp;gt; 자동 로그인
      else{
        navigation.reset({routes: [{name: &quot;HomeTab&quot;}]});
      }
    }

  }
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- showToast(text)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;text를 인자로 받아 해당 text를 토스트로 띄워주는 함수이며 해당 라이브러리에 대한 자세한 사용법은 &lt;a title=&quot;github&quot; href=&quot;https://github.com/calintamas/react-native-toast-message/tree/main&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;링크&lt;/a&gt;에 설명되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- getTokens(email, password, navigation)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 로그인 버튼을 클릭 했을 때 email, password, navigation을 인자로 받아 토큰을 로컬 저장소에 저장하고 화면을 전환해주는 함수이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. axios.post() 함수를 이용하여 서버의 URL/login 경로로&amp;nbsp; email, password 정보를 json형식으로 담아 post한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 서버로부터 응답을 받아 상태 코드가 200(정상)이면 AsyncStorage.setItem() 함수를 통해 로컬 저장소에 accessToken, refreshToken, userId를 Tokens라는 이름으로 저장한 후 홈화면으로 이동한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 상태 코드가 200이 아니라면 showToast() 함수를 호출하여 사용자에게 오류가 발생했다는 토스트를 띄운다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- getTokenFromLocal()&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬 저장소에서 accessToken, refreshToken, userId를 가져오는 함수이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AsyncStorage.getItem(&quot;Tokens&quot;)를 호출하여 Tokens라는 이름으로 저장된 데이터를 가져온다. 오류 없이 데이터를 가져오면 해당 데이터를 JSON 객체로 변환하여 반환하고 오류가 발생하여 가져온 데이터가 없다면 null을 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- verifyTokens(navigation)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;splash 화면에서 토큰값을 검증하는 함수이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. getTokenFromLocal() 함수를 호출하여 로컬 저장소에 있는 토큰값을 불러온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 만약 Token값이 null이라면 앱에 최초 접속했다는 의미이므로 로그인 화면으로 전환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Token값이 있다면 헤더에 refreshToken과 accessToken을 넣어 서버의 URL/refresh 경로로 get 요청을 보낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 이때 accessToken이 만료되고 refreshToken이 정상이면 서버에서 상태 코드(200)과 함께 새로운 accessToken을 발급하여 응답을 보내오고 재발급된&amp;nbsp; accessToken은 로컬 저장소에 저장시킨 후 홈화면으로 이동하여 자동 로그인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 서버에서 상태 코드(401)로 응답한다면 accessToken이 만료되고 refreshToken도 만료되었다는 의미이며 이는 다시 로그인하여 새로운 accessToken, refreshToken을 발급 받아야 한다. 따라서 로그인 페이지로 이동한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 서버에서 401 이외의 상태 코드로 응답한다면 accessToken은 정상, refreshToken도 정상이므로 홈화면으로 이동해 자동 로그인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;AuthStackNavigator.js&lt;/h4&gt;
&lt;pre id=&quot;code_1687297455487&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React from &quot;react&quot;;
import { createStackNavigator } from &quot;@react-navigation/stack&quot;;

import { HomeTapNavigator } from &quot;./HomeTabNavigator&quot;;
import AuthPage from &quot;../pages/AuthPage&quot;;
import SignUpPage from &quot;../pages/SignUpPage&quot;;
import SplashPage from &quot;../pages/SplashPage&quot;;
import ResendMailPage from &quot;../pages/ResendMailPage&quot;;

const AuthStackNavigator = ({navigation}) =&amp;gt; {
    const Stack = createStackNavigator();

    return (
        &amp;lt;Stack.Navigator&amp;gt;
            &amp;lt;Stack.Screen name='SplashPage' component={SplashPage} options={{ headerShown: false}} navigation={navigation}/&amp;gt;
            &amp;lt;Stack.Screen name='AuthPage' component={AuthPage} options={{ headerShown: false}} navigation={navigation}/&amp;gt;
            &amp;lt;Stack.Screen name='HomeTab' component={HomeTapNavigator} options={{ headerShown: false }}/&amp;gt;
            &amp;lt;Stack.Screen name='SignUpPage' component={SignUpPage} options={{ headerShown: false}}/&amp;gt;
            &amp;lt;Stack.Screen name='ResendMailPage' component={ResendMailPage} options={{ headerShown: false}}/&amp;gt;
        &amp;lt;/Stack.Navigator&amp;gt;
  );
}

export { AuthStackNavigator };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SignUpPage와 ResendMailPage는 회원가입에 필요한 화면이므로 로그인 과정에는 사용하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stack navigator에 대한 자세한 사용법은 &lt;a title=&quot;링크&quot; href=&quot;https://reactnavigation.org/docs/stack-navigator/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;링크&lt;/a&gt;를 참고하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;SplashPage.js&lt;/h4&gt;
&lt;pre id=&quot;code_1687297815057&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React, { useEffect } from 'react';
import { Text, View, } from 'react-native';
import { verifyTokens } from '../../utils/networks/tokenUtils';

const SplashPage = ({navigation}) =&amp;gt; {
    useEffect(()=&amp;gt;{
        verifyTokens(navigation);
    },[])
    
  return (
    &amp;lt;View style ={{padding: 50}}&amp;gt;
      &amp;lt;Text style={{padding: 10, fontSize: 42}}&amp;gt;
        SPLASH
      &amp;lt;/Text&amp;gt;
    &amp;lt;/View&amp;gt;
    );
}

export default SplashPage;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱에 최초 접속했을 때 보여지는 화면이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;userEffect() 함수를 사용하여 화면이 처음 그려질 때 verifyTokens() 를 호출하여 토큰을 검증하고 상황에 맞게 홈화면, 로그인 페이지로 이동한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;AuthPage.js&lt;/h4&gt;
&lt;pre id=&quot;code_1687298003181&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { View} from 'react-native';
import { getTokens } from '../../utils/networks/tokenUtils';
import AuthTemplate from &quot;../templates/AuthTemplate&quot;;
import { useState } from &quot;react&quot;;

const AuthPage = ({navigation}) =&amp;gt; {
    const [email, setEmail] = useState('');
    const [password, setPassword] = useState('');
    
    const onPressLoginButton = () =&amp;gt;{
        getTokens(email, password, navigation);
    }

    const onPressButton = () =&amp;gt;{
      navigation.navigate('SignUpPage');
      }
    const onPressResendMail = () =&amp;gt;{
      navigation.navigate('ResendMailPage');
    }

  return (
    &amp;lt;View style ={{padding: 50}}&amp;gt;
      &amp;lt;AuthTemplate 
        onPressButton={onPressButton} 
        onPressLoginButton={onPressLoginButton}
        onPressResendMail={onPressResendMail}
        email={email}
        setEmail={setEmail}
        password={password}
        setPassword={setPassword}
        /&amp;gt;
    &amp;lt;/View&amp;gt;
    );
}

export default AuthPage;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인 페이지이며 useState()를 사용하여 email과 password에 대한 상태값을 갖는다. 사용자가 TextInput에 이메일과 패스워드를 입력하면 email, password의 상태값이 변하며 사용자가 로그인 버튼을 누르면 두 상태값을 getTokens()함수에 넣어 호출하여 로그인 과정을 진행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;결과&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;로그인&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;새로운 프로젝트.gif&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/D7lWs/btskJh2JS0K/vyAPN5ZEqHRLVheNxnTTTK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/D7lWs/btskJh2JS0K/vyAPN5ZEqHRLVheNxnTTTK/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/D7lWs/btskJh2JS0K/vyAPN5ZEqHRLVheNxnTTTK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/D7lWs/btskJh2JS0K/vyAPN5ZEqHRLVheNxnTTTK/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;720&quot; data-filename=&quot;새로운 프로젝트.gif&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;자동로그인&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;새로운 프로젝트2.gif&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btueHP/btskEpgcciR/B3U9kyANSR6Ev7RJqvhv50/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btueHP/btskEpgcciR/B3U9kyANSR6Ev7RJqvhv50/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btueHP/btskEpgcciR/B3U9kyANSR6Ev7RJqvhv50/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/btueHP/btskEpgcciR/B3U9kyANSR6Ev7RJqvhv50/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;720&quot; data-filename=&quot;새로운 프로젝트2.gif&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://jw910911.tistory.com/73&quot;&gt;React Native : AsyncStorage 쉽게 사용해보기. (tistory.com)&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@seohee0112/RN-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EB%B0%8F-%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85-%EA%B5%AC%ED%98%84#1-%EC%9E%90%EB%8F%99%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EB%98%90%EB%8A%94-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B1%B0%EB%B6%80&quot;&gt;[ RN] 로그인 및 JWT (velog.io)&lt;/a&gt;&lt;/p&gt;</description>
      <category>프레임워크/react-native</category>
      <category>JWT</category>
      <category>react-native</category>
      <category>RefreshToken</category>
      <author>Tae-Jun</author>
      <guid isPermaLink="true">https://tae-jun.tistory.com/20</guid>
      <comments>https://tae-jun.tistory.com/20#entry20comment</comments>
      <pubDate>Wed, 21 Jun 2023 07:34:37 +0900</pubDate>
    </item>
    <item>
      <title>JWT Refresh Token을 이용한 로그인 (with Node.js, React Native) - 1. 서버</title>
      <link>https://tae-jun.tistory.com/19</link>
      <description>&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a title=&quot;클라이언트 구현&quot; href=&quot;https://tae-jun.tistory.com/20&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;클라이언트 구현&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1687300810973&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;JWT Refresh Token을 이용한 로그인 (with Node.js, React Native) - 2. 클라이언트&quot; data-og-description=&quot;서버 구현 JWT Refresh Token을 이용한 로그인 (with Node.js, React Native) - 1. 서버 진행중인 프로젝트에 로그인 기능을 구현하기 위해 JWT를 사용하여 refresh token/ access token을 구현했다. 글이 길어져 서버 &quot; data-og-host=&quot;tae-jun.tistory.com&quot; data-og-source-url=&quot;https://tae-jun.tistory.com/20&quot; data-og-url=&quot;https://tae-jun.tistory.com/20&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/goewR/hyS4w47DUa/T85nhki1yvIpZtEnjWXCL0/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400&quot;&gt;&lt;a href=&quot;https://tae-jun.tistory.com/20&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://tae-jun.tistory.com/20&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/goewR/hyS4w47DUa/T85nhki1yvIpZtEnjWXCL0/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;JWT Refresh Token을 이용한 로그인 (with Node.js, React Native) - 2. 클라이언트&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;서버 구현 JWT Refresh Token을 이용한 로그인 (with Node.js, React Native) - 1. 서버 진행중인 프로젝트에 로그인 기능을 구현하기 위해 JWT를 사용하여 refresh token/ access token을 구현했다. 글이 길어져 서버&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;tae-jun.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;진행중인 프로젝트에 로그인 기능을 구현하기 위해 JWT를 사용하여 refresh token/ access token을 구현했다.&lt;br /&gt;글이 길어져 서버 구현 / 클라이언트 구현으로 나누어 작성한다.&lt;br /&gt;또한 JWT의 개념적인 내용보다 구현에 중심을 두어 글을 작성했다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;JWT를 이용한 로그인 과정&lt;/h2&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1242&quot; data-origin-height=&quot;643&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHaqYN/btshAz6p26H/fEbzzg2MylblJTivqC3czK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHaqYN/btshAz6p26H/fEbzzg2MylblJTivqC3czK/img.png&quot; data-alt=&quot;로그인 과정 순차 다이어그램&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHaqYN/btshAz6p26H/fEbzzg2MylblJTivqC3czK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHaqYN%2FbtshAz6p26H%2FfEbzzg2MylblJTivqC3czK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1242&quot; height=&quot;643&quot; data-origin-width=&quot;1242&quot; data-origin-height=&quot;643&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;로그인 과정 순차 다이어그램&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;1. 사용자가 id, pw를 입력한다.&lt;br /&gt;2. 클라이언트는 사용자의 id와 pw를 서버에 POST한다.&lt;br /&gt;3. 서버는 회원 DB를 통해 사용자 id와 pw가 일치한지 확인한 후 accessToken과 refreshToken을 발급한다.&lt;br /&gt;4. 토큰 DB에 사용자 id(FK, PK), refresh Token을 저장한다.&lt;br /&gt;5. 생성된 accessToken과 refreshToken을 클라이언트에 반환한다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;accessToken 재발급 과정&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;681&quot; data-origin-height=&quot;402&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/x1a1h/btskrcunTWz/aG64eVxKxNgFQrfdaShte1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/x1a1h/btskrcunTWz/aG64eVxKxNgFQrfdaShte1/img.png&quot; data-alt=&quot;재발급 과정 순차 다이어그램&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/x1a1h/btskrcunTWz/aG64eVxKxNgFQrfdaShte1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fx1a1h%2FbtskrcunTWz%2FaG64eVxKxNgFQrfdaShte1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;681&quot; height=&quot;402&quot; data-origin-width=&quot;681&quot; data-origin-height=&quot;402&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;재발급 과정 순차 다이어그램&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;그림이 조금 이상하지만..&lt;br /&gt;클라이언트는 로그인 화면을 보여주기 전에 spalsh화면을 띄워 사용자의 token값이 있는지, 그 값이 유효한지 검사한다.&lt;br /&gt;그 중 token값이 유효한지 확인하기 위해 서버에 refresh 요청을 보낸다. 그 과정은 아래와 같다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;1. 클라이언트에서 재발급 요청을 보낸다. 이때 헤더에 accessToken과 refreshToken을 담아서 보낸다.&lt;br /&gt;2. 서버는 받은 accessToken 값을 검증한다. 이때 accessToken값이 조작되거나 분실되어 decode값이 존재하지 않는다면 권한이 없다는 에러 메세지를 클라이언트에게 보낸다.&lt;br /&gt;3. 클라이언트에게 받은 refreshToken값을 검증한다.&lt;br /&gt;4. 새로운 accessToken을 발급될 조건이라면 새로운 accessToken을 발급한다.(조건은 아래에서 설명한다.)&lt;br /&gt;5. 발급된 accessToken을 클라이언트에게 전송한다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;b&gt;새로운 accessToken이 발급 시 시나리오&lt;/b&gt;&lt;br /&gt;1. accessToken 만료되지 않은 경우 = 토큰을 새로 발급 받을 필요 X&lt;br /&gt;2. accessToken이 만료되고 refreshToken도 만료된 경우 = refresh토큰도 새로 발급 받아야 되기 때문에 새로 로그인해야 함&lt;br /&gt;3. accessToken이 만료되고 refreshToken은 만료되지 않은 경우 = 새로운 accessToken 발급&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;DB 테이블&lt;/h4&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;token 테이블&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;Email&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;varchar(200)&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;NOT NULL&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;PK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;Token&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;varchar(5000)&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;NOT NULL&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;user 테이블&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;Email&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;varchar(200)&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;NOT NULL&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;PK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;UserName&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;varchar(10)&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;NOT NULL&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;PassWord&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;varchar(30)&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;NOT NULL&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;AuthStatus&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;tinyint(1)&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;NOT NULL&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;AuthToken&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;varchar(5000)&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;NULL&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;node.js를 이용한 JWT 발급 과정 예제&lt;/h2&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;1. 라이브러리 설치&lt;/h4&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# package.json 설치
npm init -y

# .env 파일 사용
npm install dotenv

# jwt 사용
npm install jsonwebtoken

# express 사용
npm install express

# mysql2 사용
npm install mysql2&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;2. 폴더 구조&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;197&quot; data-origin-height=&quot;285&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blePkH/btskuW5Tdhd/4zaiqOVcypnetmt6VfDFu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blePkH/btskuW5Tdhd/4zaiqOVcypnetmt6VfDFu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blePkH/btskuW5Tdhd/4zaiqOVcypnetmt6VfDFu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblePkH%2FbtskuW5Tdhd%2F4zaiqOVcypnetmt6VfDFu0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;197&quot; height=&quot;285&quot; data-origin-width=&quot;197&quot; data-origin-height=&quot;285&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;controller: 메인 로직 작성&lt;br /&gt;utils: 메인 로직에 필요한 모듈 작성&lt;br /&gt;server.js: 라우터&lt;br /&gt;.env: 시크릿 키 보관&lt;br /&gt;&amp;nbsp;&lt;br /&gt;mail과 관련된 파일은 회원가입 시 이메일 인증에 관한 로직으로 추후 글을 작성할 예정이다.&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;3. .env&lt;/h4&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;ACCESS_TOKEN_SECRET=defaultkey&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;.env 파일에 SECRET_KEY를 저장한다. 시크릿 키로 사용자 정보를 변환할 수 있기 때문에 절대 외부로 유출되면 안된다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;4. server.js&lt;/h4&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;const user = require('./controller/user'); # controller import
const express = require('express'); # express import

const app = express()
const port = 3000

// body 데이터를 json형식으로 사용
app.use(express.json())

// 3000번 포트로 서버 실행
app.listen(port, () =&amp;gt;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;console.log(`app listening on port ${port}`)
})

// 기본 경로
app.get('/', (req, res) =&amp;gt;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;res.send('Hello world!')
})

// 로그인 경로
app.post('/login',user.login)

// access 토큰 재발급 경로
app.get('/refresh',user.refresh)&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;로그인 시 IP/login 경로를 사용하고, 토큰 재발급 시 IP/refresh 경로를 사용한다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;5.utils/tokenUtils.js&lt;/h4&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;require('dotenv').config(); # dotenv import
const getConnection = require('../utils/DbUtils');
const jwt = require('jsonwebtoken'); # jsonwebtoken(jwt) import
const JWT_KEY = process.env.ACCESS_TOKEN_SECRET # SECRET KEY import

// accessToken 발급 함수
exports.makeToken = (Object) =&amp;gt;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const token = jwt.sign(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Object,&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;JWT_KEY, 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{expiresIn: &quot;2m&quot;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;console.log(token)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return token;
};

// refreshToken 발급 함수
exports.makeRefreshToken = () =&amp;gt;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const refreshToken = jwt.sign(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{},&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;JWT_KEY, 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;algorithm: &quot;HS256&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;expiresIn: &quot;10m&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;console.log(refreshToken)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return refreshToken;
};

// refresh token 유효성 검사
exports.refreshVerify = async (token, userId) =&amp;gt; {

&amp;nbsp;&amp;nbsp;const sql = (email) =&amp;gt;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return `select token from token where Email = '${email}';`
&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;try {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// db에서 refresh token 가져오기(DB에 userID로 조회)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const result = await getConnection(sql(userId));

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//받은 refreshToken과 DB에서 조회한 값이 일치하는지 확인
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (token === result['row'][0].token) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;jwt.verify(token, JWT_KEY);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return true;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// refreshToken 검증 에러
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} catch (err) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return false;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} else {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return false;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;// DB 에러
&amp;nbsp;&amp;nbsp;} catch (err) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;console.log(err);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return false;
&amp;nbsp;&amp;nbsp;}
};

// access token 유효성 검사
exports.verify = (token) =&amp;gt; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const decoded = jwt.verify(token, JWT_KEY);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ok: true,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;id: decoded.id
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;};
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} catch (error) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ok: false,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;message: error.message,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;};
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;};&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;- makeAccessToken(Object)&lt;br /&gt;AccessToken을 만드는 함수로 회원정보(Object)를 인자로 받아 시크릿 키, 유효기간을 인자로 jwt.sign() 함수를 호출한다.&lt;br /&gt;jwt.sign() 함수는 jsonwebtoken 라이브러리의 토큰을 발급하는 함수이다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;- makeRefreshToken()&lt;br /&gt;RefreshToken을 만드는 함수이다. RefreshToken은 회원 ID와 함께 DB에 저장되므로 payload에 빈 객체를 할당한다.&lt;br /&gt;따라서 빈 객체, 시크릿 키, 해싱 알고리즘 정보와 유효기간을 인자로 jwt.sign() 함수를 호출한다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;- refreshVerify(token, userId)&lt;br /&gt;refresh token의 유효성을 검사하는 함수이다. userID로 DB에서 refreshToken을 조회한 후 조회된 토큰값과 인자로 받은 토큰값을 비교하여 값이 일치하면 jwt.verify()를 통해 refreshToken이 유효한지 확인한다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;- verify(token)&lt;br /&gt;access token의 유효성을 검사하는 함수이다. 인자로 받은 accessToken을 시크릿 키와 함께 &lt;span style=&quot;color: #333333;&quot;&gt;jwt.verify에&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt; 넣어 호출하여 회원 정보를 얻는다. token값이 유효하다면 디코딩된 userID 를 리턴하고 유효하지 않다면 에러 메세지를 리턴한다.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;6. utils/Dbutils.js&lt;/h4&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;const config = require('../db_config.json')
const mysql = require('mysql2/promise');&amp;nbsp;&amp;nbsp;

const pool = mysql.createPool(config);


async function getConnection(sql, params) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let result = {};
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const connection = await pool.getConnection(async conn =&amp;gt; conn);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const [rows] = await connection.query(sql, params);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;result.state = true;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;result.row = rows;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;connection.release();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return result;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;catch (err){
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;console.log(err);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;result.state = false;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;result.err = err;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;connection.release();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return result;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;catch(err){
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;console.log(err);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;result.state = false;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;result.err = err; 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return result;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}

&amp;nbsp;&amp;nbsp;
module.exports = getConnection;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&lt;b&gt;db_config.js&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;host&quot;: &quot;127.0.0.1&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;port&quot;: &quot;3306&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;user&quot;: &quot;USER NAME&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;password&quot;: &quot;DB PW&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;database&quot;: &quot;DB NAME&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;connectionLimit&quot;: 30
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;폴더의 최상위 위치해 있는 db_config.json 파일에 DB의 기본설정을 저장하고 불러와 pool을 생성한다.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;- getConnection(sql, params)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;실행될 sql문과 insert, update 시 사용할 파라미터를 인자로 받는다. &lt;/span&gt;pool에서 connecton을 하나 가져오고 인자로 받은 sql, params를 인자로 넣어 sql문을 실행한다. sql문이 정상적으로 실행되면 상태값과 반환된 rows를 결과값에 저장하여 클라이언트에게 보내고 connection을 닫는다. 정상적으로 실행되지 않는다면 상태값과 에러 메세지를 결과값에 저장하여 클라이언트에게 보내고 connection을 닫는다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;7. controller/user.js&lt;/span&gt;&lt;/h4&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;require('dotenv').config();
const getConnection = require('../utils/DbUtils');
const { sendVerifyEmail } = require('../utils/mailUtils');
const TokenUtils = require('../utils/tokenUtils');
const jwt = require('jsonwebtoken');

exports.login = async (req, res) =&amp;gt;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const {userId, userpw} = req.body;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const SelectUsersql = (userId) =&amp;gt;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return `select Email, PassWord, AuthStatus from user where Email = '${userId}'`;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const InsertTokenSql =&amp;nbsp;&amp;nbsp;(token) =&amp;gt;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return `insert into token values(?, ?) ON DUPLICATE KEY UPDATE token='${token}';`
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// [EXCEPTION] DB에서 ID 찾기
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const result_id = await getConnection(SelectUsersql(userId));

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// [EXCEPTION] DB 에러
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (result_id.state === false) return res.status(401).send(&quot;DB 에러 관리자에게 문의하세요.&quot;); 

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// [EXCEPTION] ID 불일치
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (result_id.row.length === 0) return res.status(401).send(&quot;아이디가 일치하지 않습니다.&quot;);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// [EXCEPTION] PW 불일치
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (userpw !== result_id.row[0].PassWord) return res.status(401).send(&quot;비밀번호가 일치하지 않습니다.&quot;);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// [EXCEPTION] 이메일 인증이 완료되지 않았을 때
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (result_id.row[0].AuthStatus !== 1) return res.status(401).send(&quot;이메일 인증을 완료해 주세요.&quot;);


&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// ID, PW 같을 경우 토큰 발급
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const accessToken = TokenUtils.makeAccessToken({id: userId});
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const refreshToken = TokenUtils.makeRefreshToken();

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// refreshToken, id DB에 저장
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const result_insert = await getConnection(InsertTokenSql(refreshToken), [userId,refreshToken]);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// [EXCEPTION] DB에 저장 실패
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (result_insert.state === false) return res.status(401).send(&quot;DB 에러 관리자에게 문의하세요.&quot;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return res.status(200).send({userId,accessToken, refreshToken})

};

const successResponse = (code, data) =&amp;gt;{
&amp;nbsp;&amp;nbsp;return({
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;code: code,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data: data,
&amp;nbsp;&amp;nbsp;})
}

const failResponse = (code, message) =&amp;gt;{
&amp;nbsp;&amp;nbsp;return({
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;code: code,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;message: message,
&amp;nbsp;&amp;nbsp;})
}

exports.refresh = async (req, res)=&amp;gt;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// access, refresh 토큰이 헤더에 담겨 온 경우
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (req.headers[&quot;authorization&quot;] &amp;amp;&amp;amp; req.headers[&quot;refresh&quot;]){
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const accessToken = req.headers[&quot;authorization&quot;].split(&quot; &quot;)[1];
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const refreshToken = req.headers[&quot;refresh&quot;];
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// access token 검증 -&amp;gt; expired여야 함.
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const authResult = TokenUtils.verify(accessToken);
 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// access token 디코딩하여 userId를 가져온다.
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const decoded = jwt.decode(accessToken);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 디코딩 결과가 없으면 권한이 없음을 응답.
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (!decoded) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;res.status(401).send(failResponse(401,&quot;No authorized!&quot;));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// access 토큰 만료 시
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (authResult.ok === false &amp;amp;&amp;amp; authResult.message === &quot;jwt expired&quot;) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 1. access token이 만료되고, refresh token도 만료 된 경우 =&amp;gt; 새로 로그인해야합니다.
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const refreshResult = await TokenUtils.refreshVerify(refreshToken, decoded.id);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (refreshResult === false) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;res.status(401).send(failResponse(401,&quot;No authorized! 다시 로그인해주세요.&quot;));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} else {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 2. access token이 만료되고, refresh token은 만료되지 않은 경우 =&amp;gt; 새로운 access token을 발급
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const newAccessToken = TokenUtils.makeAccessToken({ id: decoded.id });
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;res.status(200).send(successResponse(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;200,{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;accessToken: newAccessToken,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;refreshToken,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} else {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 3. access token이 만료되지 않은경우 =&amp;gt; refresh 할 필요가 없습니다.
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;res.status(400).send(failResponse(400,&quot;Acess token is not expired!&quot;));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} else {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// access token 또는 refresh token이 헤더에 없는 경우
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;res.status(401).send(failResponse(400,&quot;Access token and refresh token are need for refresh!&quot;));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
};&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;- login()&lt;br /&gt;위에서 언급한 &quot;JWT를 이용한 로그인 과정&quot;에 대한 로직이다.&amp;nbsp;&lt;br /&gt;1. 클라이언트로부터 userId와 userPw를 받으면 DB에 userId와 userPw가 있는지 확인한다.&lt;br /&gt;2. DB 에러, ID 불일치, PW 불일치, 이메일 미인증의 경우에 클라이언트에게 에러 메세지를 전송한다.&lt;br /&gt;3. 위에서 언급한 에러가 없다면 accessToken과 refreshToken을 발급한다.&lt;br /&gt;4. 발급된 refreshToken과 id를 DB에 저장한 후 클라이언트에게 accessToken과 refreshToken을 전송한다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;- refresh()&lt;br /&gt;위에서 언급한 &quot;&quot;에 대한 로직이며 클라이언트가 헤더에 accessToken과 refreshToken을 담아 보내게 된다.&lt;br /&gt;1. accessToken과 refreshToken이 헤더에 존재하면 TokenUtils.verify()를 이용하여 access토큰을 검증하고 jwt.decode()를 사용하여 accessToken을 디코딩한다.&lt;br /&gt;2.1 디코딩 결과가 없다면 클라이언트에게 권한이 없다는 에러 메세지를 전송한다.&lt;br /&gt;2.2 accessToken이 만료되지 않았으면 클라이언트에게&amp;nbsp;refresh할 필요 없다는 메세지를 전송한다.&amp;nbsp;&lt;br /&gt;2.3 accessToken이 만료되었고 refreshToken이 만료된 경우 클라이언트에게 새로 로그인해야 한다는 메세지를 전송한다.&lt;br /&gt;2.4 accessToken이 만료되었지만 refresToken이 만료되지 않은경우 새로운 accessToken을 발급받아 클라이언트에게 전송한다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;시간이 얼마 남지 않아 급하게 코드를 작성하다 보니 코드가 더럽다... 나중에 시간이 남으면 코드를 한 번 정리해야겠다.&lt;br /&gt;또한 코드 발급, 인증에 관한 코드는 아래 글을 참고했으니 아래 글을 한 번 보면 좋을 것 같다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;참고&lt;br /&gt;&lt;a title=&quot;[Node.js+Express] Refresh Token 구현&quot; href=&quot;https://hello-judy-world.tistory.com/74&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;[Node.js+Express] Refresh Token 구현&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프레임워크/nodejs</category>
      <category>JWT</category>
      <category>nodejs</category>
      <category>RefreshToken</category>
      <category>Token</category>
      <author>Tae-Jun</author>
      <guid isPermaLink="true">https://tae-jun.tistory.com/19</guid>
      <comments>https://tae-jun.tistory.com/19#entry19comment</comments>
      <pubDate>Wed, 21 Jun 2023 05:30:16 +0900</pubDate>
    </item>
  </channel>
</rss>