<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>hyeondooori.log</title>
        <link>https://velog.io/</link>
        <description>노는 게 제일 좋은 뽀로로</description>
        <lastBuildDate>Sun, 27 Apr 2025 14:54:09 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>hyeondooori.log</title>
            <url>https://velog.velcdn.com/images/hyeondooori_/profile/aea9d2c1-b5f9-49b5-93c1-a4f47fd1253c/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. hyeondooori.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/hyeondooori_" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[ONOS Project Retrospective ]]></title>
            <link>https://velog.io/@hyeondooori_/ONOS-Project-Retrospective</link>
            <guid>https://velog.io/@hyeondooori_/ONOS-Project-Retrospective</guid>
            <pubDate>Sun, 27 Apr 2025 14:54:09 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/345e4cc6-bfb0-4dcf-bae5-1297dd6df769/image.png" alt=""></p>
<h2 id="1-introduction">1. Introduction</h2>
<p>In June 2024, I joined the Lee Jae-hoon professor&#39;s Network Research Lab at Dongguk University, focusing on AI-enabled networking technologies.<br>As part of the lab activities, I worked extensively with ONOS (Open Network Operating System) to understand and apply Software-Defined Networking (SDN) concepts through hands-on labs.</p>
<p>This blog post summarizes my journey.
detailed version also exists in korean.</p>
<p><a href="https://velog.io/@hyeondooori_/ONOS-2.7.0-Ubuntu-22.04%EC%9D%B4%EC%9A%A9%ED%95%B4-mininet-%EA%B0%80%EC%83%81-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%97%B0%EA%B2%B0">ONOS Practice - Connecting virtual networks using Mininet with ONOS 2.7.0 on Ubuntu 22.04</a></p>
<p><a href="https://velog.io/@hyeondooori_/%EB%9D%BC%EC%A6%88%EB%B2%A0%EB%A6%AC%ED%8C%8C%EC%9D%B43%EC%97%90-OVS-%EC%84%A4%EC%B9%98-%ED%9B%84-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EA%B5%AC%EC%84%B1%ED%95%B4%EB%B3%B4%EA%B8%B0-ONOS%EB%A1%9C-flow-rule-%EC%84%A4%EC%A0%95%ED%95%98%EC%97%AC-VLAN-%ED%8C%A8%ED%82%B7%EC%A0%9C%EC%96%B4">ONOS Practice - Setting flow rules in ONOS to control VLAN-tagged packets</a></p>
<p><a href="https://velog.io/@hyeondooori_/ONOS-%EC%8B%A4%EC%8A%B5-%EC%98%A4%ED%94%88%EC%86%8C%EC%8A%A4-sw-%ED%99%9C%EC%9A%A9%ED%95%B4-ONOS%EB%A1%9C-%EC%8A%A4%EC%9C%84%EC%B9%98-%ED%8F%AC%ED%8A%B8-%EB%B3%84-%EC%86%A1%EC%88%98%EC%8B%A0-%ED%8A%B8%EB%9E%98%ED%94%BD%EC%B2%98%EB%A6%AC%EB%9F%89-%EC%A0%95%EB%B3%B4-%EC%8B%A4%EC%8B%9C%EA%B0%84-%EA%B7%B8%EB%9E%98%ED%94%84%EB%A1%9C-%EC%B6%9C%EB%A0%A5%ED%95%98%EA%B8%B0">ONOS Practice - Visualizing real-time switch port traffic statistics from ONOS using open-source tools</a></p>
<hr>
<h2 id="2-setting-up-onos-and-mininet">2. Setting up ONOS and Mininet</h2>
<p>To start, I set up the ONOS 2.7.0 controller on Ubuntu 22.04 and connected it with Mininet, a popular network emulator for SDN.<br>I practiced creating virtual networks consisting of multiple switches and hosts, and then linked them to ONOS to control the flow of network traffic.</p>
<p><strong>Key tools:</strong></p>
<ul>
<li>Ubuntu 22.04</li>
<li>Mininet</li>
<li>ONOS 2.7.0</li>
</ul>
<hr>
<h2 id="3-connecting-virtual-networks">3. Connecting Virtual Networks</h2>
<p>I created virtual topologies in Mininet (such as linear and tree structures) and verified that ONOS correctly discovered all devices and links.<br>I also explored how ONOS automatically installs default flow rules to manage packet forwarding in the network.</p>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/b02b242e-a1a6-4c70-9058-eaf3a3794d4f/image.png" alt=""></p>
<p>I built flow rules by two ways.
one in reactive mode, where the SDN controller makes decision, and the other in proactive mode, where the user defines the rules manually.</p>
<hr>
<h2 id="4-installing-open-vswitch-on-raspberry-pi">4. Installing Open vSwitch on Raspberry Pi</h2>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/1e1fac1e-0c41-47bb-9e18-337ca362cd14/image.png" alt=""></p>
<p>Taking a step further, I installed Open vSwitch (OVS) on Raspberry Pi 3 devices to build a real-world SDN testbed.<br>After configuring the OVS bridges and ports, I connected the Raspberry Pi switches to ONOS as external network elements.</p>
<hr>
<h2 id="5-controlling-vlan-packets-with-onos">5. Controlling VLAN Packets with ONOS</h2>
<p>Using ONOS’s Flow Rule functionality, I wrote flow rules to control VLAN-tagged packets.<br>This included filtering specific VLAN IDs and forwarding traffic based on VLAN properties, allowing me to manage network segmentation dynamically.</p>
<hr>
<h2 id="6-visualizing-switch-traffic-in-real-time">6. Visualizing Switch Traffic in Real Time</h2>
<p>To monitor network performance, I visualized real-time switch port traffic data using ONOS metrics combined with open-source visualization tools.<br>This helped me gain insights into traffic patterns, detect bottlenecks, and understand SDN monitoring strategies.</p>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/6d18eab6-66ea-447b-a5b8-2e5a3c90934d/image.png" alt=""></p>
<hr>
<h2 id="7-key-takeaways-and-reflections">7. Key Takeaways and Reflections</h2>
<p>Through these hands-on experiences, I learned:</p>
<ul>
<li>How SDN controllers like ONOS manage and orchestrate network flows.</li>
<li>Practical skills in setting up and controlling virtual and physical network devices.</li>
<li>The importance of real-time network visualization for operational insights.</li>
</ul>
<p>This project sparked my interest in developing custom ONOS applications and exploring deeper into SDN-enabled smart networking.</p>
<p>I look forward to continuing this journey by building my first ONOS application and contributing to open-source networking initiatives!</p>
<hr>
<h2 id="resources">Resources</h2>
<ul>
<li><a href="https://onosproject.org/">ONOS Project Official Website</a></li>
<li><a href="https://wiki.onosproject.org/display/ONOS/Creating+Your+First+ONOS+Application">ONOS Wiki: Setting up Your First App</a></li>
</ul>
<hr>
<p>Thank you for reading! 🚀</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[백준 1929번 cpp로 풀기]]></title>
            <link>https://velog.io/@hyeondooori_/%EB%B0%B1%EC%A4%80-1929%EB%B2%88-cpp%EB%A1%9C-%ED%92%80%EA%B8%B0</link>
            <guid>https://velog.io/@hyeondooori_/%EB%B0%B1%EC%A4%80-1929%EB%B2%88-cpp%EB%A1%9C-%ED%92%80%EA%B8%B0</guid>
            <pubDate>Fri, 31 Jan 2025 07:40:23 GMT</pubDate>
            <description><![CDATA[<p>1929번 문제는 M이상 N이하의 소수를 빠르게 찾고 출력하는 문제이다.</p>
<p>이 문제는 에라토스테네스의 체 알고리즘을 이용하여 풀 수 있다.
먼저, <code>vector&lt;bool&gt; isPrime(N+1,true)</code>를 생성하여 모든 수를 소수로 가정한다.
그리고 2부터 N까지 배수를 제거하여 소수만 남기는 구조이다.
코드는 아래에 적어두었다.</p>
<blockquote>
<p><code>ios::sync_with_stdio(false);</code>와 <code>cin.tie(nullptr);</code>는 c++에서 입출력 성능을 최적화 하기 위한 코드이다.
1️⃣<code>ios::sync_with_stdio(false);</code></p>
</blockquote>
<ul>
<li>c++의 표준 입출력인 cin,cout과 c의 표준 입출력인 scanf,printf의 동기화를 해제하는 역할을 한다.</li>
<li>기존적으로 c++의 cin,cout은 c의 scanf,printf와 동기화되어 같이 사용될 때 정상적으로 동작하도록 보장된다.</li>
<li>하지만 동기화과정이 불필요한 경우 성능을 향상시킬 수 있다.</li>
<li>즉, sacnf,printf를 사용해야한다면 이 옵션은 쓰면 안된다.
2️⃣<code>cin.tie(nullptr);</code></li>
<li>기본적으로 cin과 cout은 연결(tie)되어있다.</li>
<li>즉, cin이 사용될 때마다 자동으로 cout이 flush(출력 버퍼 비움)이 되어 즉시 출력된다.
이를 해제(nullptr로 설정)하려면 불필요한 flush를 방지하여 입출력 속도를 높일 수 있다.</li>
</ul>
<pre><code>#include&lt;iostream&gt;
#include&lt;vector&gt;

using namespace std;

void sieve(int M, int N) {
    vector&lt;bool&gt; isPrime(N+1, true);
    isPrime[0] = isPrime[1] = false;

    //에라토스테네스의 체 알고리즘
    for(int i = 2; i*i &lt;=N; i++){
        if(isPrime[i]){
            for(int j = i * i; j&lt;= N; j+=i){
                isPrime[j] = false;
            }
        }
    }

    //결과 출력
    for(int i = M; i&lt;=N; i++){
        if(isPrime[i]){
            cout&lt;&lt;i&lt;&lt;&#39;\n&#39;;
        }
    }
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int M, N;
    cin &gt;&gt; M &gt;&gt; N;
    sieve(M, N);

    return 0;
}
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[ONOS 실습] 오픈소스 sw 활용해 ONOS로 스위치 포트 별 송수신 트래픽처리량 정보 실시간 그래프로 출력하기]]></title>
            <link>https://velog.io/@hyeondooori_/ONOS-%EC%8B%A4%EC%8A%B5-%EC%98%A4%ED%94%88%EC%86%8C%EC%8A%A4-sw-%ED%99%9C%EC%9A%A9%ED%95%B4-ONOS%EB%A1%9C-%EC%8A%A4%EC%9C%84%EC%B9%98-%ED%8F%AC%ED%8A%B8-%EB%B3%84-%EC%86%A1%EC%88%98%EC%8B%A0-%ED%8A%B8%EB%9E%98%ED%94%BD%EC%B2%98%EB%A6%AC%EB%9F%89-%EC%A0%95%EB%B3%B4-%EC%8B%A4%EC%8B%9C%EA%B0%84-%EA%B7%B8%EB%9E%98%ED%94%84%EB%A1%9C-%EC%B6%9C%EB%A0%A5%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@hyeondooori_/ONOS-%EC%8B%A4%EC%8A%B5-%EC%98%A4%ED%94%88%EC%86%8C%EC%8A%A4-sw-%ED%99%9C%EC%9A%A9%ED%95%B4-ONOS%EB%A1%9C-%EC%8A%A4%EC%9C%84%EC%B9%98-%ED%8F%AC%ED%8A%B8-%EB%B3%84-%EC%86%A1%EC%88%98%EC%8B%A0-%ED%8A%B8%EB%9E%98%ED%94%BD%EC%B2%98%EB%A6%AC%EB%9F%89-%EC%A0%95%EB%B3%B4-%EC%8B%A4%EC%8B%9C%EA%B0%84-%EA%B7%B8%EB%9E%98%ED%94%84%EB%A1%9C-%EC%B6%9C%EB%A0%A5%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 13 Jan 2025 01:15:12 GMT</pubDate>
            <description><![CDATA[<p>이 포스트는 링크된 블로그를 참고하여 작성되었습니다. -&gt; <a href="https://m.blog.naver.com/PostView.naver?blogId=love_tolty&amp;logNo=222646472327&amp;navType=by">CLICK!!</a>
<img src="https://velog.velcdn.com/images/hyeondooori_/post/6a186981-a4eb-4b9b-bb55-a6e3f56c9ff6/image.png" alt="">
사진 출처: 네이버 블로그-톨티의 공작소</p>
<p>이번에는 위 사진의 순서대로 실습을 진행해볼 것이다. 
실습의 편의성을 위해 모든 오픈소스 소프트웨어는 모두 하나의 PC에 설치된다.</p>
<h2 id="1️⃣-onos--mininet-구성">1️⃣ ONOS + Mininet 구성</h2>
<p>Mininet으로 가상 네트워크를 생성하여 SDN 제어기인 ONOS에 연결을 구성하는 방식이다.</p>
<p><a href="https://velog.io/@hyeondooori_/ONOS-2.7.0-Ubuntu-22.04%EC%9D%B4%EC%9A%A9%ED%95%B4-mininet-%EA%B0%80%EC%83%81-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%97%B0%EA%B2%B0">ONOS 2.7.0, Ubuntu 22.04이용해 mininet 가상 네트워크 연결
</a>
링크된 포스트와 동일하게 진행하면 된다.</p>
<p>위 포스팅대로 진행을 완료하면, 현재 ONOS와 연결된 Mininet기반 가상 네트워크 구조를 조회하면 다음과 같은 결과가 출력된다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/a20eaec1-943f-47ec-a5ec-eff2488907c1/image.png" alt="">
dpid가 &#39;0000000000000001&#39;인 OVS스위치(s1) 1대에 1번 포트(s1-eth1)과 2번 포트(s1-eth2)에 각각 호스트가 하나씩 연결된 것을 알 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/45c6845e-d395-4b1c-988c-8b9ddf179605/image.png" alt="">
구성정보를 표현한 그림이다.</p>
<h2 id="2️⃣-celery-구성">2️⃣ Celery 구성</h2>
<p>Celery로 OVS 포트 별 실시간 송/수신 트래픽 처리량 계산</p>
<blockquote>
<p>💡 Celery란 무엇인가?
Celery는 분산 작업 큐(distributed task queue)이다. python으로 작성되었으며, 비동기 작업 및 스케줄링에 널리 사용된다. 즉, 시간이 오래걸리는 작업을 백그라운드에서 실행할 수 있도록 도와주는 도구이다. 
이는 웹 애플리케이션의 응답 속도를 높이기 위해 많이 사용된다.</p>
</blockquote>
<p>Celery를 설치하는 가장 간단한 방법은 python라이브러리 설치패키지 매니저인 pip을 통해 설치하는 방법이다.
이를 위해 python3와 python3를 지원하는 pip을 pc에 설치해준다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/787e5e75-ed4d-4d37-8e3b-6d061484bcf5/image.png" alt="">
pip 설치가 완료되면 pip3를 이용하여 Celery를 설치해준다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/21581c54-3d5b-4d7b-abfc-07b90820b5e8/image.png" alt="">
pip3를 통해 설치된 python모듈을 조회하면 나는 5.4.0 버전이 조회된다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/3bb019dc-5171-47c6-9f43-773427037749/image.png" alt=""></p>
<p>Celery설치를 완료했다면, 이제 Celery 기본 Broker인 RabbitMQ를 아래와같이 설치해준다. </p>
<blockquote>
<p>비유를 들어 설명하자면, </p>
</blockquote>
<ul>
<li><code>Celery</code>는 &quot;심부름 명령을 내리는 사람&quot;이다. 심부름이 많을 때 효율적으로 각 심부름꾼(worker)에게 지시한다.</li>
<li><code>RabbitMQ</code>는 &quot;심부름 메모를 전달하는 게시판&quot;이다. 모든 심부름꾼은 게시판에서 심부름 내용을 확인하고 수행한다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/49d4a52f-b072-4dfd-b5e0-d7ac5e9e386e/image.png" alt="">
설치가 다 되었다면 실행해준다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/25cccd94-bd2f-4c7b-8944-0614dfa9ce17/image.png" alt="">
이제는 <code>Broker(RabbitMQ)</code>를 통해 <code>Worker</code>에 분배 될 <code>Celery Task</code>를 생성하면 된다. 하지만, 그 전에 먼저 Celery Task에 반영해주기 위한, <strong>OVS 스위치 포트 별 트래픽 정보</strong>를 가지고 <strong>트래픽처리량</strong>을 계산하는 과정을 알아보아야 한다.</p>
<p>그래서 일단 REST API를 통해서 ONOS와 연결된 스위치 별 포트정보를 조회해서, 어떤 정보가 수집되는지 확인해볼 필요가 있다. 
이를 위해 우선 <code>cURL</code>, <code>jq</code>를 설치해주자.</p>
<ul>
<li><code>cURL</code>: HTTP 요청을 전송하는 도구</li>
<li><code>jq</code>: JSON 데이터를 읽기 쉽게 포맷하여 출력하는 도구
<img src="https://velog.velcdn.com/images/hyeondooori_/post/eaf40edd-c6bb-4417-b045-613f2fd9902d/image.png" alt="">
그런 다음 다음과같이 ONOS의 REST API를 이용해 Mininet으로 구성한 OVS 스위치 s1의 포트 별 트래픽 정보를 조회하는 스크립트 파일을 만들어준다. ONOS_IP는 본인의 IP로 바꾸어주어야 한다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/44ab0073-e11d-4588-bfda-765dec97bc8c/image.png" alt=""></p>
<p>이 스크립트를 실행하면, ONOS와 연결된 스위치 별 포트 정보를 알 수 있다.
여기서는 OVS 스위치 s1의 1번포트와 2번포트에 대한 정보를 확인할 수 있다.
각 포트 별 bytesReceived, bytesSent, durationSec 값을 이용하면 스위치 포트 별 수신(RX), 송신(TX)패킷에 대한 트래픽처리량 계산이 가능하다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/b12990db-0728-4fd3-a418-2258007e8a4f/image.png" alt=""></p>
<blockquote>
<ul>
<li>bytesReceived() : 포트를 통해 수신받은 패킷의 누적 사이즈(단위: bytes)</li>
</ul>
</blockquote>
<ul>
<li>byteSent() : 포트를 통해 송신된 패킷의 누적 사이즈(단위:bytes)</li>
<li>durationSec() : 포트가 활성화된 시간(sec)
<img src="https://velog.velcdn.com/images/hyeondooori_/post/1b095e66-59fc-4272-8d97-35028ff3a9c1/image.png" alt="">
<img src="https://velog.velcdn.com/images/hyeondooori_/post/d4ea3717-e6b4-43e3-b83d-9ab6ac67007c/image.png" alt=""></li>
</ul>
<p>여기까지 완료되었다면, 이제는 위의 송/수신 트래픽처리량(TX/RX Throughput)에 대한 공식을 가지고, 매 단위시간마다 트래픽 처리량 정보를 계산하는 Celery Task를 생성하겠다. Celery Task를 통하여 ONOS의 REST API를 호출하기 위하여, Python requests 모듈을 설치한다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/9fc10c87-47e0-42e9-9f74-3f6179e623c3/image.png" alt=""></p>
<p>처음 계산되는 송/수신 트래픽 처리량 값을 임시로 저장하기 위한 data디렉토리를 생성하고, 읽고 쓰기 권한을 아래와같이 변경해준다. 
(권한 변경 시 sudo 붙여주는 것을 잊지말자 ㅎㅎ)
<img src="https://velog.velcdn.com/images/hyeondooori_/post/6d59916b-72a6-41a9-823d-b4c493231522/image.png" alt=""></p>
<p>다음으로, 아래와같이 <code>task.py</code>라는 파일을 생성하여 ONOS_CELERY_TASK라는 celery task함수를 하나 작성한다. 
해당 task는 ONOS의 REST API를 통해 ONOS와 연결된 모든 스위치의 활서화된 포트 별 bytesSent,bytesReceived,durationSec값을 조회하고, 앞에서 도출한 공식 두 가지를 통해서 송수신 트래픽에 대한 처리량(TX/RX Throughput)을 계산한다. </p>
<pre><code>from celery import Celery
import json
import requests
import os

# Broker(RabbitMQ) 설정
app = Celery(&#39;ONOS_TASK&#39;, broker=&#39;pyamqp://guest@localhost//&#39;)

# celeryconfig.py에 정의한 Celery Task 환경설정 정보 가져오기
app.config_from_object(&#39;celeryconfig&#39;)

# 파일 저장 및 읽기 함수 정의
def FILE_WRITE(file_dir, data):
    with open(file_dir, &#39;w&#39;) as f:
        f.write(data)

def FILE_READ(file_dir):
    with open(file_dir, &#39;r&#39;) as f:
        return f.readline()

# Celery Task 함수 정의
@app.task
def ONOS_CELERY_TASK(CTRL_IP, CTRL_PORT, CTRL_ID, CTRL_PW):
    try:
        # ONOS REST API를 통해 스위치 정보 조회
        ONOS_URI = f&quot;http://{CTRL_IP}:{CTRL_PORT}/onos/v1/devices&quot;
        result = requests.get(ONOS_URI, auth=(CTRL_ID, CTRL_PW))
        DEVICE_INFO = result.json().get(&quot;devices&quot;, [])

        for device in DEVICE_INFO:
            SW_DPID = device[&quot;id&quot;]
            print(f&quot;Processing SW_DPID: {SW_DPID}&quot;)
            ONOS_URI_PORTS = f&quot;http://{CTRL_IP}:{CTRL_PORT}/onos/v1/statistics/ports&quot;
            result_ports = requests.get(ONOS_URI_PORTS, auth=(CTRL_ID, CTRL_PW))
            SW_STATE_INFO = result_ports.json().get(&quot;statistics&quot;, [])

            for state_info in SW_STATE_INFO:
                if state_info[&quot;device&quot;] == SW_DPID:
                    for port in state_info[&quot;ports&quot;]:
                        MATCH_PORT = port[&quot;port&quot;]
                        RX_BYTE_COUNT = port[&quot;bytesReceived&quot;]
                        TX_BYTE_COUNT = port[&quot;bytesSent&quot;]
                        DURATION_TIME = port[&quot;durationSec&quot;]

                        FILE_NAME = f&quot;dpid_{SW_DPID.replace(&#39;:&#39;, &#39;&#39;)}_port_{MATCH_PORT}&quot;
                        FILE_DIR = f&quot;./data/{FILE_NAME}.log&quot;

                        # 파일이 없으면 새로 생성
                        if not os.path.exists(FILE_DIR):
                            data = f&quot;{TX_BYTE_COUNT}_{RX_BYTE_COUNT}_{DURATION_TIME}&quot;
                            FILE_WRITE(FILE_DIR, data)
                        else:
                            # 파일이 있으면 데이터 읽기
                            DATA = FILE_READ(FILE_DIR).split(&quot;_&quot;)
                            PREVIOUS_TX_BYTE_COUNT = float(DATA[0])
                            PREVIOUS_RX_BYTE_COUNT = float(DATA[1])
                            PREVIOUS_DURATION_TIME = float(DATA[2])

                            CURRENT_TX_BYTE_COUNT = float(TX_BYTE_COUNT)
                            CURRENT_RX_BYTE_COUNT = float(RX_BYTE_COUNT)
                            CURRENT_DURATION_TIME = float(DURATION_TIME)

                            data = f&quot;{TX_BYTE_COUNT}_{RX_BYTE_COUNT}_{DURATION_TIME}&quot;
                            FILE_WRITE(FILE_DIR, data)

                            # Duration Time이 0일 경우 로그 출력
                            if CURRENT_DURATION_TIME - PREVIOUS_DURATION_TIME == 0:
                                print(f&quot;Duration time is 0 for Port No. {MATCH_PORT}. The SDN controller has not updated.&quot;)
                            else:
                                # Throughput 계산
                                TX_THROUGHPUT = (CURRENT_TX_BYTE_COUNT - PREVIOUS_TX_BYTE_COUNT) * 8 / (CURRENT_DURATION_TIME - PREVIOUS_DURATION_TIME)
                                RX_THROUGHPUT = (CURRENT_RX_BYTE_COUNT - PREVIOUS_RX_BYTE_COUNT) * 8 / (CURRENT_DURATION_TIME - PREVIOUS_DURATION_TIME)

                                TX_THROUGHPUT = round(TX_THROUGHPUT, 4)
                                RX_THROUGHPUT = round(RX_THROUGHPUT, 4)

                                print(f&quot;Port No. : {MATCH_PORT}&quot;)
                                print(f&quot;TX_THROUGHPUT = {TX_THROUGHPUT} bps&quot;)
                                print(f&quot;RX_THROUGHPUT = {RX_THROUGHPUT} bps&quot;)

    except Exception as error:
        print(f&quot;Error: {error}&quot;)
</code></pre><blockquote>
<p><code>CTRL</code>: Controller의 준말
<code>DPID</code>: (Device Port Identifier)
OpenFlow 프로토콜에서 각 스위치를 식별하기 위해 사용되는 64비트 길이의 식별자</p>
</blockquote>
<p>이어서, 앞에 작성한 task.py의 Celery Task함수 &quot;ONOS_CELERY_TASK&quot;가 매 3초마다 호출되도록 &quot;celeryconfig.py&quot;파일을 생성해, 스케줄링 내용을 아래와 같이 작성한다. 참고로, celery는 celery Beat이라는 스케줄러를 통해 Task들의 실행 순서를 결정할 수 있는데, 여기서 작성하는 &quot;celeryconfig.py&quot;파일 내용을 가지고 Celery Beat을 통해 Task가 매 3초간 수행되게 된다.</p>
<pre><code>from datetime import timedelta

# ONOS의 IP/Port/ID/PW 정의
CTRL_IP = &quot;192.168.224.128&quot;
CTRL_PORT = &quot;8101&quot;
CTRL_ID = &quot;karaf&quot;
CTRL_PW = &quot;karaf&quot;

# task.ONOS_CELERY_TASK(task.py 파일에 정의된 ONOS_CELERY_TASK)의 스케줄링 정보 정의
beat_schedule = {
    &#39;add-every-3-seconds&#39;: {
        &#39;task&#39;: &#39;task.ONOS_CELERY_TASK&#39;,
        &#39;schedule&#39;: timedelta(seconds=3),
        &#39;args&#39;: (CTRL_IP, CTRL_PORT, CTRL_ID, CTRL_PW)
    }
}

# 타임존 설정
timezone = &#39;UTC&#39;

broker_connection_retry_on_startup = True
</code></pre><hr>
<h3 id="💣트러블-슈팅1---broker_connection_retry_on_startup--true">💣트러블 슈팅(1) - broker_connection_retry_on_startup = True</h3>
<ul>
<li>이 옵션이 필요한 이유는 Celery워커 또는 비트 프로세스가 브로커(RabbitMQ, Redis 등)에 연결할 수 없을 때 자동으로 재시도하도록 설정하기 위함이다. </li>
<li>브로커 서비스가 아직 시작하지 않았거나, 일시적으로 응답하지 않을 수 있다. 위 옵션을 설정하면, 연결 실패 시 바로 종료되지 않고 일정 시간 동안 재시도를 시도한다.</li>
<li>🔍초기값이 False인 이유?
  초기 재시도 시 무한 루프처럼 실행되는 것을 방지하기 위해서이다.</li>
</ul>
<hr>
<p>해당 Task를 CeleryBeat를 통해 3초 단위로 실행할 것이므로 -B 옵션을 포함하여 아래와같은 명령어를 실행한다.</p>
<blockquote>
<p>Celery Beat: 주기적으로 작업을 스케줄링하여 실행할 수 있도록 해주는 Celery의 확장 패키지</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/73ee7109-584b-4e57-a144-434993187f31/image.png" alt="">
성공적으로 Celery가 실행되면 아래와같이 Celery서버의 로그화면이 출력되면서 &quot;task.py&quot;에서 작성했던 Celery Task함수 &quot;ONOS_CELERY_TASK&quot;가 호출되는 것을 확인할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/e432c9b3-5e10-4d65-bc4e-db460e30b4fb/image.png" alt="">
그리고 매 3초마다 DPID가 of:0000000000000001인 OVS 스위치 s1의 현재 활성화된 포트 1번, 2번의 송/수신 트래픽 처리량(TX/RX Troughput)이 계산되어 출력되는 것을 알 수 있다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/f6a7e1ef-95f4-4e2f-995f-0040e0462a08/image.png" alt=""></p>
<hr>
<h3 id="💣트러블슈팅2">💣트러블슈팅(2)</h3>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/3399bb48-1a28-406b-864f-8d17942d9747/image.png" alt=""></p>
<blockquote>
<ol>
<li><strong>celerybeat-schedule 파일 문제</strong>
<code>celerybeat-schedule</code>파일은 Celery Beat가 작업 일정을 관리하기 위해 사용하는 데이터베이스 파일이다. 이 파일은 일정이 저장되어 있으므로, 비정상적인 종료나 파일 손상이 발생하면 Beat 프로세스가 해당 파일을 읽을 수 없어 오류를 발생시킨다.</li>
</ol>
<p>-&gt; 따라서, 파일이 손상된 경우에는 rm 명령어로 해당 파일을 지우고, 다시 Beat 프로세스를 실행해야한다.
2. <strong>-n 옵션을 통해 워커 이름을 실행 때마다 변경해주어야 한다.</strong>
Celery 워커는 이름을 기준으로 프로세스를 구분한다. 기본적으로 여러 개의 동일한 이름을 가진 워커가 동시에 실행되면, 충돌이 발생한다. 즉, PID가 겹치고 메시지 큐 관리에서 문제가 생기는 것이다.
같은 이름의 워커가 이미 실행 중이면 Address already in use 또는 Another worker with the same name exists 등의 오류가 발생할 수 있다.
-&gt; 즉, <code>-n</code>옵션을 사용해 각 워커가 고유한 이름을 가지도록 설정해야한다.</p>
</blockquote>
<hr>
<h2 id="3️⃣-influxdb-구성">3️⃣ InfluxDB 구성</h2>
<p>InfluxDB는 시계열 데이터를 저장하고 관리하는 오픈소스 데이터베이스이다. 주로 온도, 시간, 속도 등 시간별 수치로 측정될 수 있는 시계열 데이터에 특화되어있다. 그래서 이번엔 해당 InfluxDB에다가 앞서 Celery를 통해 매 3초마다 계산되는 송/수신 트래픽처리량(TX/RX Throughput) 정보를 주기적으로 한 번 저장해볼 것이다.</p>
<p>influxdb와 influxdb-client 패키지를 아래와 같이 설치한다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/2140fb68-53c8-4276-b8c9-904ac8f29b4d/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/bab3b146-127d-428d-baa2-5efef61cc3a0/image.png" alt=""></p>
<ul>
<li>sudo service influxdb start: InfluxDB 실행</li>
<li>influx: influxDB CLI환경으로 접속됨. 버전 확인도 가능</li>
<li>트래픽처리량을 저장하기위한 sdn이라는 DB 생성, 확인, influxDB CLI종료</li>
</ul>
<p>그리고, 생성한 데이터베이스 sdn에 매 3초간 Celery Task로 계산되는 송/수신 트래픽처리량을 저장하기 위해 먼저 InfluxDB의 Python패키지를 아래와같이 설치한다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/2d150ef9-450a-4e3f-b4f7-4841c4cd84ef/image.png" alt=""></p>
<p>그리고, task.py파일을 열어서, 최상단에는 InfluxDBClient라는 파이썬 모듈을 가져와 InfluxDB에 데이터를 저장해주기 위한 함수 <code>InfluxDB_InsertData()</code>를 아래와같이 저장해주고, 송/수신 트래픽처리량을 계산하는 소스코드 아래에 계산된 트래픽처리량을 InfluxDB에 저장할 수 있도록 위쪽과 들여쓰기를 맞추어서 InfluxDB_InsertData()를 호출하는 코드를 추가해준다.</p>
<pre><code>from celery import Celery
import json
import requests
import os
from influxdb import InfluxDBClient  # InfluxDB 라이브러리 import

# Broker(RabbitMQ) 설정
app = Celery(&#39;ONOS_TASK&#39;, broker=&#39;pyamqp://guest@localhost//&#39;)

# celeryconfig.py에 정의한 Celery Task 환경설정 정보 가져오기
app.config_from_object(&#39;celeryconfig&#39;)

# InfluxDB에 데이터 저장 함수 정의
def InfluxDB_InsertData(dpid, port_no, tx_throughput, rx_throughput):
    DB_HOST = &quot;192.168.224.128&quot;  # InfluxDB 호스트 IP
    DB_PORT = &quot;8086&quot;         # InfluxDB 포트
    DB_USER = &quot;root&quot;         # InfluxDB 사용자 ID
    DB_PSWD = &quot;root&quot;         # InfluxDB 비밀번호
    DB_NAME = &quot;sdn&quot;          # InfluxDB 데이터베이스 이름

    json_body = [{
        &quot;measurement&quot;: dpid,
        &quot;tags&quot;: {
            &quot;port&quot;: port_no
        },
        &quot;fields&quot;: {
            &quot;tx_throughput&quot;: tx_throughput,
            &quot;rx_throughput&quot;: rx_throughput
        }
    }]

    client = InfluxDBClient(DB_HOST, DB_PORT, DB_USER, DB_PSWD, DB_NAME)
    print(f&quot;Write points: {json_body}&quot;)
    client.write_points(json_body)

# ONOS REST API를 통해 조회된 정보를 파일로 저장하기 위한 함수 정의
def FILE_WRITE(file_dir, data):
    with open(file_dir, &#39;w&#39;) as f:
        f.write(data)

# 파일로 저장된, ONOS REST API를 통해 조회된 정보를 읽어오기 위한 함수 정의
def FILE_READ(file_dir):
    with open(file_dir, &#39;r&#39;) as f:
        data = f.readline()
    return data

# Celery를 통해 실행시켜줄 ONOS TASK &quot;ONOS_CELERY_TASK&quot; 함수 정의
@app.task
def ONOS_CELERY_TASK(CTRL_IP, CTRL_PORT, CTRL_ID, CTRL_PW):
    try:
        ONOS_URI = f&quot;http://{CTRL_IP}:{CTRL_PORT}/onos/v1/devices&quot;
        result = requests.get(ONOS_URI, auth=(CTRL_ID, CTRL_PW))
        DEVICE_INFO = result.json()[&quot;devices&quot;]

        # ONOS REST API를 통해 스위치별 Port 상태 조회
        for device in DEVICE_INFO:
            SW_DPID = device[&quot;id&quot;]
            print(f&quot;SW_DPID: {SW_DPID}&quot;)
            ONOS_URI = f&quot;http://{CTRL_IP}:{CTRL_PORT}/onos/v1/statistics/ports&quot;
            result = requests.get(ONOS_URI, auth=(CTRL_ID, CTRL_PW))
            SW_STATE_INFO = result.json()[&quot;statistics&quot;]

            for state in SW_STATE_INFO:
                if state[&quot;device&quot;] == SW_DPID:
                    for port in state[&quot;ports&quot;]:
                        MATCH_PORT = port[&quot;port&quot;]
                        RX_BYTE_COUNT = port[&quot;bytesReceived&quot;]
                        TX_BYTE_COUNT = port[&quot;bytesSent&quot;]
                        DURATION_TIME = port[&quot;durationSec&quot;]
                        FILE_NAME = f&quot;dpid_{SW_DPID.replace(&#39;:&#39;, &#39;&#39;)}_port_{MATCH_PORT}&quot;
                        FILE_DIR = f&quot;./data/{FILE_NAME}.log&quot;

                        if not os.path.exists(FILE_DIR):
                            data = f&quot;{TX_BYTE_COUNT}_{RX_BYTE_COUNT}_{DURATION_TIME}&quot;
                            FILE_WRITE(FILE_DIR, data)
                        else:
                            DATA = FILE_READ(FILE_DIR).split(&quot;_&quot;)
                            PREVIOUS_TX_BYTE_COUNT = float(DATA[0])
                            PREVIOUS_RX_BYTE_COUNT = float(DATA[1])
                            PREVIOUS_DURATION_TIME = float(DATA[2])

                            CURRENT_TX_BYTE_COUNT = float(TX_BYTE_COUNT)
                            CURRENT_RX_BYTE_COUNT = float(RX_BYTE_COUNT)
                            CURRENT_DURATION_TIME = float(DURATION_TIME)

                            data = f&quot;{TX_BYTE_COUNT}_{RX_BYTE_COUNT}_{DURATION_TIME}&quot;
                            FILE_WRITE(FILE_DIR, data)

                            if CURRENT_DURATION_TIME - PREVIOUS_DURATION_TIME == 0:
                                print(f&quot;The SDN controller has not updated The Port No. {MATCH_PORT} State!!&quot;)
                            else:
                                TX_THROUGHPUT = (CURRENT_TX_BYTE_COUNT - PREVIOUS_TX_BYTE_COUNT) * 8 / (
                                    CURRENT_DURATION_TIME - PREVIOUS_DURATION_TIME)
                                RX_THROUGHPUT = (CURRENT_RX_BYTE_COUNT - PREVIOUS_RX_BYTE_COUNT) * 8 / (
                                    CURRENT_DURATION_TIME - PREVIOUS_DURATION_TIME)

                                TX_THROUGHPUT = round(TX_THROUGHPUT, 4)
                                RX_THROUGHPUT = round(RX_THROUGHPUT, 4)

                                print(f&quot;Port No. : {MATCH_PORT}&quot;)
                                print(f&quot;TX_THROUGHPUT = {TX_THROUGHPUT} bps&quot;)
                                print(f&quot;RX_THROUGHPUT = {RX_THROUGHPUT} bps&quot;)

                                # InfluxDB에 트래픽 처리량 저장
                                InfluxDB_InsertData(SW_DPID, MATCH_PORT, TX_THROUGHPUT, RX_THROUGHPUT)

    except Exception as error:
        print(&quot;Error:&quot;, error)
</code></pre><p>이렇게 수정한 task.py파일이 정상적으로 동작하는지 Celery서버를 실행한다.
아래 사진과같이 정상적으로 실행되었다면, 로그 화면에서 influxDB에 저장되는 데이터 정보를 확인할 수 있다. 
로그 내용을 살펴보면 DPID가 of:0000000000000001인 OVS스위치의 현재 활성화된 1번,2번 포트의 송수신 트래픽 처리량 값이 계산되어 JSON형식으로 influxDB에 전달되는 것을 알 수 있다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/bcab68e8-d131-44b0-ab72-ae8a79ae5ec9/image.png" alt=""></p>
<p>이제는 InfluxDB에 실제로 데이터가 저장되었는지 확인해보자. InfluxDB CLI에 접속하여 앞서 생성했던 &quot;sdn&quot; DB를 선택한다.
그리고 select문을 통해 저장된 데이터를 조회해보면, 시간대별로 Celery Task가 실행되면서 저장된 스위치 포트별 송/수신 트래픽 처리량 데이터 정보를 확인할 수 있다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/216a1c51-9b0c-43b7-bbc3-4d575afb6e4d/image.png" alt=""></p>
<hr>
<h2 id="4️⃣-grafana-구성">4️⃣ Grafana 구성</h2>
<p>지금까지 Celery Task를 통해 송수신 트래픽처리량도 계산했고, 이를 InfluxDB라는 시계열 데이터베이스에 저장도 했다. </p>
<p>이번에는 Grafana라는 데이터 시각화 도구를 통해 InfluxDB에 저장된 트래픽처리량 데이터를 실시간 그래프로 출력하는 과정이다.</p>
<p>먼저, grafana를 다운받아야한다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/7cc486e8-30bb-4c47-a9d7-dac4e15f5fc0/image.png" alt="">
위 사진의 url로 접근하여, Ubuntu and Debien에서 grafana 다운로드 부분을 복사한다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/f7b53a0a-605e-485b-b717-ea50ac70e05b/image.png" alt="">
복사해온 명령어를 실행시켜주면 된다. 위의 wget은 설치파일을 받아오는 것이다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/6b527eff-73a1-4511-8a15-6fe0ee4e755c/image.png" alt="">
이제 받은 파일을 가지고 Grafana를 PC에 설치해준다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/9b39e15d-9ffd-4d19-a285-b71b64fc1faf/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/3e11c15a-bb76-4930-a677-7fc3b7220523/image.png" alt="">
grafana를 실행한 뒤 Grafana UI(192.168.224.128:3000)에 접속하자. 위 사진처럼 로그인 화면이 뜨면 성공이다.</p>
<p>다음으로는 Grafana와 InfluxDB를 연동해야한다. 
로그인화면에서는 초기아이디,패스워드인 admin/admin을 입력하고 로그인한다.
influxDB로 data source를 만들어주면 된다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/5522d453-5eb7-4216-9054-d0d48f7460bc/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/f813ffc8-4184-4770-a339-af6cdecdf769/image.png" alt="">
save&amp;test 버튼을 누른다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/7619d021-dab8-4d7d-80c8-5caa40a9e72d/image.png" alt=""></p>
<p>Mininet CLI로 접속해서 h1에서 h2로 ping메시지를 보낸다.</p>
<hr>
<h3 id="💣-트러블슈팅3">💣 트러블슈팅(3)</h3>
<ol>
<li>Mininet은 ~(root)에서 접속해야만한다.
예를들어, onos디렉토리 안에서는 접근하려하면 오류가 발생한다.</li>
<li>실행했던 Mininet에서 exit하면, 꼭 <code>sudo mn --clear</code> 명령어를 통해 지워주어야한다.
지워주지 않으면 다시 Mininet CLI로 접속이 되지 않는다. </li>
</ol>
<hr>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/cd24ac5a-6ad7-4be1-99aa-32d5eaf44587/image.png" alt="">
그리고, 다른 터미널에서 ping 정보를 DB에 저장해주어야 grafana에 그래프로 반영이 된다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/02204098-da3a-4309-8330-b8a76d1b3142/image.png" alt="">
기존에 만들어두었던 것을 실행해주면 된다.
그래프 설정을 아래 사진을 쭉 따라하면 된다.
대시보드와 그래프의 이름은 원하는 것으로 바꾸어도 좋다.</p>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/bdda0e83-7154-48e7-a405-5ef485e19a55/image.png" alt="">
<img src="https://velog.velcdn.com/images/hyeondooori_/post/faecf20d-aaac-442a-a22b-a542fe26ddd0/image.png" alt="">
<img src="https://velog.velcdn.com/images/hyeondooori_/post/df2d9e89-59e7-43a3-835b-018a5c84aea1/image.png" alt="">
Grafana의 Dashboard를 확인해보면, 현재 가상 호스트 h1에서 h2로 트래픽이 흐르면서 가상 호스트와 연결된 OVS 스위치 1번 포트의 수신 트래픽처리량(RX Throughput)은 약 1.3kbps, 송신 트래픽처리량(TX Throughput)은 약 0.7kbps인 것을 확인할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ONOS 실습] 라즈베리파이3에 OVS 설치 후 네트워크 구성해보기/ ONOS로 flow rule 설정하여 VLAN 패킷제어]]></title>
            <link>https://velog.io/@hyeondooori_/%EB%9D%BC%EC%A6%88%EB%B2%A0%EB%A6%AC%ED%8C%8C%EC%9D%B43%EC%97%90-OVS-%EC%84%A4%EC%B9%98-%ED%9B%84-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EA%B5%AC%EC%84%B1%ED%95%B4%EB%B3%B4%EA%B8%B0-ONOS%EB%A1%9C-flow-rule-%EC%84%A4%EC%A0%95%ED%95%98%EC%97%AC-VLAN-%ED%8C%A8%ED%82%B7%EC%A0%9C%EC%96%B4</link>
            <guid>https://velog.io/@hyeondooori_/%EB%9D%BC%EC%A6%88%EB%B2%A0%EB%A6%AC%ED%8C%8C%EC%9D%B43%EC%97%90-OVS-%EC%84%A4%EC%B9%98-%ED%9B%84-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EA%B5%AC%EC%84%B1%ED%95%B4%EB%B3%B4%EA%B8%B0-ONOS%EB%A1%9C-flow-rule-%EC%84%A4%EC%A0%95%ED%95%98%EC%97%AC-VLAN-%ED%8C%A8%ED%82%B7%EC%A0%9C%EC%96%B4</guid>
            <pubDate>Mon, 13 Jan 2025 01:09:41 GMT</pubDate>
            <description><![CDATA[<p>이 포스트는 아래 링크를 참고하여 작성했다.
<a href="https://blog.naver.com/love_tolty/222609033937">라즈베리파이3에 Open vSwitch(OVS) 설치/OVS 네트워크 구성/ONOS로 직접 Flow Rule 설정하여 VLAN 패킷 제어하기</a>
첨부된 링크와 현재 포스트에는 변경된 사항이 있어, 실습을 해보시려면 위의 링크된 블로그와 이 포스트를 비교해가며 쭉 따라해보면 좋을 것 같다.</p>
<blockquote>
<p>이 실습은 <code>ununtu 22.04버전</code>, <code>ONOS 2.7.0 버전</code>이 필요하다.
vmware와 Ubuntu설치는 검색해보면 자세히 정리된 블로그들이 많다.
onos 설치과정은 아래 블로그에 적혀있으니 그대로 따라하면 된다.
<a href="https://velog.io/@hyeondooori_/ONOS-2.7.0-Ubuntu-22.04%EC%9D%B4%EC%9A%A9%ED%95%B4-mininet-%EA%B0%80%EC%83%81-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%97%B0%EA%B2%B0">ONOS 2.7.0, Ubuntu 22.04이용해 mininet 가상 네트워크 연결</a></p>
</blockquote>
<p>위의 포스팅에서는 Mininet기반의 가상 네트워크 환경을 구축하여 Host간 통신이 되도록 flow rule을 reactive, proactive 방식으로 각각 설정해보았다. 
이번에는 가상으로만 패킷제어를 해보는 방식이 아닌, 물리적 장치인 라즈베리파이3를 추가하여 진행한다. 라즈베리파이3에 OVS를 설치하여 하나의 SDN스위치를 만들고 여기에 연결된 단말 장치 간 VLAN패킷을 주고받을 수 있도록  ONOS로 flow rule을 설정해보는 것이다. </p>
<p>자 이제 실습을 시작해보자!!</p>
<blockquote>
<p>준비물
<img src="https://velog.velcdn.com/images/hyeondooori_/post/09f4e616-8857-428a-9692-86e10dc0ccf4/image.png" alt=""></p>
</blockquote>
<p>참고로, 나는 라즈베리파이의 usb 단자와 노트북을 연결하기 위해 usb 이더넷 어댑터를 산 것이다. 사진에는 두 개 라고 나와있지만, 이번 실습에서는 <mark>하나는 물리적</mark>으로, <mark>하나는 가상</mark>으로 구성하여 실습을 진행할 것이다.
최근 노트북은 대부분 c타입으로 연결하기 때문에 <code>usb to c 선 1개</code> 가 있으면 될 것 같다!</p>
<h2 id="1️⃣-onos-환경-구축">1️⃣ ONOS 환경 구축</h2>
<p>먼저, 실습을 진행할 컴퓨터를 준비한다.
아래 조건을 만족해야한다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/62007576-33eb-4ebe-ae20-cd8e22b7f818/image.png" alt=""></p>
<p>나의 경우 컴퓨터와 유무선공유기 사이에 인터넷 선을 연결하여 인터넷에 연결했다. 
<img src="https://velog.velcdn.com/images/hyeondooori_/post/2ce53998-4aba-460c-8344-175461bda3bc/image.png" alt="">
pc의 IP주소는 아래 사진처럼 확인할 수 있다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/c20ed1cf-e0e4-4c0c-b7b4-ae7c010a082c/image.png" alt=""></p>
<p>선행 post부터 쭉 따라했다면, ONOS가 잘 설치되어있을 것이다. 
<img src="https://velog.velcdn.com/images/hyeondooori_/post/57c30626-1676-4c3b-a146-4f320e05ebbd/image.png" alt="">
확인한 pc주소를 넣어 명령어를 실행하면 ONOS CLI 접속이 잘 될 것이다. 
초기 비밀번호는 karaf이다. 
다시 기본 터미널로 나오고싶다면, ctrl+D버튼을 누르면 된다.</p>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/41bf724c-412e-4621-b844-b34a3561f316/image.png" alt="">
SDN의 openflow프로토콜을 지원하는 ONOS의 애플리케이션인 org.onosproject.openflow를 활성화시킨다.</p>
<hr>
<h3 id="💣-트러블슈팅">💣 트러블슈팅</h3>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/cf950b85-f2ef-4b68-a252-01702052c260/image.png" alt="">
명령어를 올바르게 작성했는데 이런 오류가 나온다면, SSH 호스트 키가 변경되어 발생하는 문제이다. 이를 해결하기 위해 known_hosts파일에 저장된 이전 키를 삭제해야한다.</p>
<pre><code>ssh-keygen -f &quot;/home/hyeonseo/.ssh/known_hosts&quot; -R &quot;[192.168.224.128]:8101&quot;</code></pre><p>이 명령어를 통해 키를 제거한 후 다시 SSH 연결을 시도하면 잘 된다.</p>
<hr>
<h2 id="2️⃣-라즈베리파이-연결">2️⃣ 라즈베리파이 연결</h2>
<p>이제 라즈베리파이에 ubuntu를 설치해주어야 하는데, 이 과정에서는 <code>micro sd카드</code>와 <code>micro sd카드 리더기</code>가 필요하다.</p>
<p>raspberry pi imager를 다운로드 한다.
raspberry pi imager를 설치한 후 실행해 다음과같이 선택해준다. 
<img src="https://velog.velcdn.com/images/hyeondooori_/post/cd6e1a4d-32b1-46c5-acd2-400e8f2751c9/image.png" alt=""></p>
<p>저장소는 micro sd카드를 micro sd card 리더기에 연결하여 pc 본체에 연결해주면 저장소 선택이 된다.
운영체제는 기존 컴퓨터에 설치된 버전과 맞춰주면 된다. 나는 22.04버전이 기존 컴퓨터에 설치되어있어서 동일하게 22.04버전으로 선택했다.</p>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/e93622ac-9270-4192-8d1e-7f3220dfe7af/image.png" alt="">
세부 설정을 이렇게 바꾸어주어야한다.</p>
<p>그런 후, 완료되었다고 나오면, sd카드를 raspberrypi에 연결해준다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/286a8b64-8ee9-4744-8266-e8318d0fa6cf/image.png" alt="">
연결 후 전원 케이블을 뺏다 꽂아주면 초록 불이 깜빡거리며 바로 설치가 된다. </p>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/cba6a050-2bec-4c5a-8917-54b186ae6f9e/image.png" alt="">
내부 네트워크설정으로 들어가 스크롤을 내려보면,
<img src="https://velog.velcdn.com/images/hyeondooori_/post/2ceac169-004c-4fc8-a939-282ffc248472/image.png" alt="">
이렇게 자동할당 되어있는 raspberrypi의 ip주소를 확인할 수 있다. </p>
<p>그리고 putty를 설치해야한다.
동일하게 &quot;putty&quot;를 검색하여 최상단에 나오는 것을 다운로드 받은 후 실행하면 된다. 참고로, sd카드를 라즈베리파이에 삽입한 후 putty에 접속되기까지는 1-2분의 시간이 소요된다!
<img src="https://velog.velcdn.com/images/hyeondooori_/post/bd97931f-2670-447f-aa65-9b5b06fd80c6/image.png" alt="">
hostname 부분에 아까 확인한 raspberrypi의 ip주소를 넣어준 후, open한다.
accept를 하고, imager의 추가 설정에서 설정했던대로 로그인 해주면 된다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/c9b35fe1-9ac6-4673-a8eb-f92d3228c768/image.png" alt="">
ubuntu로 로그인이 잘 되었다.</p>
<hr>
<h2 id="3️⃣-물리-연결-host에-vlan-설정하기">3️⃣ 물리 연결 Host에 VLAN 설정하기</h2>
<p>이번에는 아래와 같이 라즈베리파이에 연결된 usb이더넷 어댑터에 연결되어있는 Host의 vlan을 설정해줄 것이다.</p>
<p>이를 위해서는 호스트에 vmware를 설치하고 ubuntu 22.04LTS 버전을 실행시켜주어야 한다.
ubuntu 설치가 완료되었다면, Host1(노트북)의 ubuntu에 접근하여 라즈베리파이와 연결된 이더넷 장치를 확인한 후 vlan 설정을 해줄 것이다.
먼저 vlan을 설치하고, 802.1q모듈을 커널에 올린다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/99ac776b-18ae-4ff1-bb22-1204304061b0/image.png" alt="">
먼저, 연결된 이더넷의 이름을 확인하기 위해 ip a 명령어를 이용한다.
여기에선 3번에 이더넷이 보인다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/ff3fc054-be11-4ebb-8208-23ae79070ca7/image.png" alt="">
그리곤, vlan을 생성해준다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/95e6f138-8716-40f1-a152-0bb294e15789/image.png" alt="">
다시 ip a를 해보면 vlan이 확인된다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/2160fcec-74d3-478a-a5d1-488c9123e873/image.png" alt=""></p>
<p>물리 연결 Host의 인터페이스 vlan10을 조회해보면 아래와같이 ip주소가 20.0.0.1/24로 할당된 것을 확인할 수 있다.
라우팅 테이블을 확인해보았을 때도 20.0.0.0/24대역의 패킷을 보낼 때 vlan10 인터페이스를 이용하는 것을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/4c898d79-127e-455a-b2b4-36cd070e37b0/image.png" alt=""></p>
<hr>
<h2 id="4️⃣-라즈베리파이-ovs-네트워크-구성하기">4️⃣ 라즈베리파이 OVS 네트워크 구성하기</h2>
<p>일단 라즈베리파이에 Open vSwitch(이하 OVS)를 설치해준다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/48786810-1818-4827-98a9-4d593f585089/image.png" alt="">
설치가 성공적으로 완료되었다면 아래와같이 OVS 설치버전이 조회된다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/9d62ecd4-5444-410f-b837-1502463237e6/image.png" alt=""></p>
<p>br-int라는 브릿지를 하나 생성하자.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/6d515add-aa1b-4409-ad1b-12b240573746/image.png" alt=""></p>
<p>내가 사용하는 ONOS 2.7.0버전은 OpenFlow 1.0 및 1.3을 지원한다고 한다. 따라서, 해당 버전을 사용하도록 설정해준다. 이 설정을 하지 않을 경우 ONOS가 지원하는 OpenFlow버전과 일치하지 않아, OVS 브릿지 &#39;br-int&#39;가 ONOS와 연결 수립이 되지 않을 수 있다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/f8f999d6-4272-484c-9e74-31077bd66b2a/image.png" alt=""></p>
<p>이렇게 생성됭 OVS 브릿지 br-int의 상태를 조회해보면 아래와 같이 br-int란 브릿지에 br-int라는 포트가 현재 생성된 상태임을 알 수 있다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/403659fc-13a0-4bb0-8c32-f73706991219/image.png" alt=""></p>
<p>다음으로 생성한 br-int 브릿지에 포트를 생성해 라즈베리파이의 이더넷 포트(<code>enxc84d4420046d</code>)를 연결해준다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/0a569377-108c-4f77-9366-660885378a1a/image.png" alt=""></p>
<p>이더넷 포트의 이름은 <code>ip a</code>명령어를 통해 확인했다.(2번에 이더넷 포트가 쓰여있다.)
<img src="https://velog.velcdn.com/images/hyeondooori_/post/0a4648cb-7798-41c3-a2e2-c8c39dec8579/image.png" alt=""></p>
<hr>
<h2 id="5️⃣-ovs-브릿지에-onos-연결하기">5️⃣ OVS 브릿지에 ONOS 연결하기</h2>
<p>ONOS가 OVS 브릿지 br-int에 연결되기 위해서는 ONOS와 OVS 브릿지 br-int가 동일한 네트워크 대역으로 묶여 서로 통신을 할 수 있어야 한다.
이를 위해 라즈베리파이에 접속하여 <code>/etc/netplan</code> 내부의 파일을 확인한다.
나는 50-cloud-init.yaml파일이 존재했다. 이는 다를 수 있으니 직접 확인해보아야 한다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/15d1ff44-1dd2-47fe-846b-1dd10c67fa41/image.png" alt="">
그리고, 이 파일을 다음과같이 수정해주어야 한다. 이 또한 라즈베리파이의 ip주소에 따라 다르니, 본인의 상황에 맞는 ip주소,대역 설정을 해주어야 한다.</p>
<pre><code>network:
  version: 2
  renderer: networkd
  wifis:
    wlan0:
      dhcp4: no
      addresses:
        - 192.168.0.137/24  # 실제 무선 네트워크 IP
      gateway4: 192.168.0.1  # 공유기/라우터의 게이트웨이 IP
      nameservers:
        addresses:
          - 8.8.8.8  # Google DNS
      access-points:
        &#39;Wi-Fi SSID&#39;:  
          password: &quot;네트워크 비밀번호&quot;

  bridges:
    br-int:
      interfaces:
        - wlan0  # 무선 네트워크 인터페이스를 브릿지에 연결
      dhcp4: no
      addresses:
        - 192.168.0.200/24  # 브릿지용 고정 IP (무선 네트워크와 같은 대역)
      gateway4: 192.168.0.1  # 공유기/라우터의 게이트웨이
      nameservers:
        addresses:
          - 8.8.8.8
</code></pre><ul>
<li>브릿지용 고정 IP는 처음에 확인했듯이 사용중인 IP주소 정보를 보고, 사용하지 않는 IP로 해주어야 한다. 나의 경우 200은 사용중이지 않다.
  <img src="https://velog.velcdn.com/images/hyeondooori_/post/6ae09315-fde6-40ed-a063-d2d30470a294/image.png" alt=""></li>
<li>공유기/라우터의 게이트 웨이 IP를 확인하는 방법은 다음과 같다. default에 쓰여있는 것이 게이트웨이 ip이다.
  <img src="https://velog.velcdn.com/images/hyeondooori_/post/c7809c4b-cd64-44f6-a24a-1977036ef4b8/image.png" alt=""></li>
</ul>
<hr>
<p>이렇게 netplan내부의 파일을 수정한 후 적용해준다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/697d002b-d239-4cdb-b918-7e77751e1419/image.png" alt=""></p>
<hr>
<p>지난번에 설치했던 onos를 실행해줄 것이다.
먼저, bazel을 이용해 onos를 실행해주어야 한다. 시간이 조금 걸리니, 마음을 편히 먹고 기다리자.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/c093528a-dc00-4eb3-aab2-f7964863d1cd/image.png" alt="">
이렇게하면, 8101포트가 활성화된다. 
다른 터미널을 열어서 다음과같이 확인할 수 있다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/1fe010cf-6cca-48ee-98ab-f1769dd48751/image.png" alt="">
<img src="https://velog.velcdn.com/images/hyeondooori_/post/c117017e-c0f4-4095-83c6-08b7a5dfefe7/image.png" alt="">
접속이 되었다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/6db19828-c3a7-46b3-bc1e-3f8bd6299366/image.png" alt=""></p>
<p>이후, ONOS GUI에 접근한다.
접근 주소는 다음과 같다.</p>
<blockquote>
<p><code>http://{ONOS 설치 IP주소}:8181/onos/ui/index.html</code></p>
</blockquote>
<h2 id="6️⃣-가상-host-설정하기">6️⃣ 가상 HOST 설정하기</h2>
<p>미니넷을 설치하여 접속한다.
<code>sudo mn --controller=remote,ip=192.168.224.128,port=6653 --switch=ovs,protocols=OpenFlow13</code>
이 명령어를 통해 Mininet CLI로 접근할 수 있다.
참고로, 이 CLI에서 나오려면 <code>exit</code>을 치면 된다.
이번 실습에서는 host 하나만 필요하지만, 미니넷에서는 호스트 하나만 만드는게 불가하다. 컨트롤러와 스위치도 함께 만들어진다.</p>
<p>이제는 물리연결 HOST와의 ping을 위해 미니넷 호스트를 설정해야하는데, 
sudo ovs-vsctl set port h1-eth0 tag=10으로 한다.</p>
<h2 id="7️⃣-flow-rule-설정하기">7️⃣ Flow Rule 설정하기</h2>
<p>현재 상태를 보면 라즈베리파이에서 br-int 브릿지가 있고, Mininet에 가상 스위치 s1과 가상 호스트 h1(10.0.0.1) 및 h2(10.0.0.2)가 생성된 상태이다. 또한 br-int에 실제 물리 네트워크 인터페이스가 연결되어 있다.</p>
<p>이제 호스트 간 ping 테스트를 위한 플로우 엔트리 설정을 위해 ONOS에 JSON 파일을 작성하여 플로우 규칙을 추가해야 한다.
<code>flow_h1_to_host2.json</code></p>
<pre><code>{
  &quot;flows&quot;: [
    {
      &quot;priority&quot;: 40000,
      &quot;timeout&quot;: 0,
      &quot;isPermanent&quot;: true,
      &quot;deviceId&quot;: &quot;of:0000000000000001&quot;,
      &quot;treatment&quot;: {
        &quot;instructions&quot;: [
          {
            &quot;type&quot;: &quot;L2MODIFICATION&quot;,
            &quot;subtype&quot;: &quot;VLAN_PUSH&quot;,
            &quot;vlanId&quot;: 10
          },
          {
            &quot;type&quot;: &quot;OUTPUT&quot;,
            &quot;port&quot;: &quot;enxc84d4420046d&quot;
          }
        ]
      },
      &quot;selector&quot;: {
        &quot;criteria&quot;: [
          {
            &quot;type&quot;: &quot;ETH_TYPE&quot;,
            &quot;ethType&quot;: &quot;0x0800&quot;
          },
          {
            &quot;type&quot;: &quot;VLAN_VID&quot;,
            &quot;vlanId&quot;: 10
          },
          {
            &quot;type&quot;: &quot;IPV4_SRC&quot;,
            &quot;ip&quot;: &quot;10.0.0.1/32&quot;
          },
          {
            &quot;type&quot;: &quot;IPV4_DST&quot;,
            &quot;ip&quot;: &quot;20.0.0.1/32&quot;
          }
        ]
      }
    },
    {
      &quot;priority&quot;: 40000,
      &quot;timeout&quot;: 0,
      &quot;isPermanent&quot;: true,
      &quot;deviceId&quot;: &quot;of:0000000000000001&quot;,
      &quot;treatment&quot;: {
        &quot;instructions&quot;: [
          {
            &quot;type&quot;: &quot;OUTPUT&quot;,
            &quot;port&quot;: &quot;s1-eth1&quot;
          }
        ]
      },
      &quot;selector&quot;: {
        &quot;criteria&quot;: [
          {
            &quot;type&quot;: &quot;ETH_TYPE&quot;,
            &quot;ethType&quot;: &quot;0x0800&quot;
          },
          {
            &quot;type&quot;: &quot;VLAN_VID&quot;,
            &quot;vlanId&quot;: 10
          },
          {
            &quot;type&quot;: &quot;IPV4_SRC&quot;,
            &quot;ip&quot;: &quot;20.0.0.1/32&quot;
          },
          {
            &quot;type&quot;: &quot;IPV4_DST&quot;,
            &quot;ip&quot;: &quot;10.0.0.1/32&quot;
          }
        ]
      }
    }
  ]
}</code></pre><blockquote>
<p>deviceId: br-int 브릿지의 OpenFlow ID (onos&gt; devices 명령어로 확인 가능).
port:
h1 -&gt; br-int → Host2: 물리 포트(enxc8...)로 출력.
Host2 -&gt; br-int → h1: 가상 포트(s1-eth1 등)로 출력.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/6ad1a1d0-4194-4e95-9ae6-6a07d75bf31c/image.png" alt="">
그리고 위에 작성한 json파일을 적용하는 명령어이다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/5ca29007-99a4-424a-ba98-59c91cbf54cf/image.png" alt="">
onos에서 flows를 쳤을 때, br-int OpenFlowId부분을 확인해보면, priorty, deviceId, selector 등의 정보가 등록된 것을 확인할 수 있다.</p>
<hr>
<h3 id="❓-vlan을-사용하는-이유">❓ VLAN을 사용하는 이유?</h3>
<blockquote>
<p>VLAN(Virtual Local Area Network)
하나의 물리적 네트워크를 여러개의 가상 네트워크로 나누는 기술이다.
장비에 VLAN태그를 추가하여 특정 네트워크 트래픽을 격리하거나 구분함.
VLAN태그를 통해 같은 네트워크 스위치를 이용하지만, 서로 다른 VLAN에 속한 장치들은 기본적으로 서로 통신할 수 없다.</p>
</blockquote>
<p>현재 구성에서 VLAN을 사용한 이유는 가상 HOST에서 br-int(OVS)를 거쳐 물리 연결 HOST로 패킷이 이동할 때, 네트워크 환경을 VLAN 10으로 구분한다. 즉, VLAN 10 태그가 없는 트래핏은 해당 포트를 통과하지 못하는 것이다.</p>
<p>만일 호스트 간 패킷이 VLAN 태그 없이 전송되면, 다른 VLAN트래픽과 충돌하거나 의도치 않게 외부로 노출될 수 있다.
또한, OVS나 ONOS 플로우 설정에서 VLAN 태그를 필터링하지 않으면, 모든 패킷이 해당 경로로 전송될 수 있어 보안 및 네트워크 정책이 무너질 수 있다.</p>
<hr>
<h2 id="8️⃣-실습-실패의-원인">8️⃣ 실습 실패의 원인</h2>
<p>flow rule까지 등록을 다 했는데, 결론은 ping테스트가 성공하지 못했다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/96805da4-0222-4582-9790-70ca4e9e9c81/image.png" alt=""></p>
<blockquote>
<p>mininet으로 만든 가상 스위치인 s1과 라즈베리파이에서 만든 ovs인 br-int의 컨트롤러가 각각 존재했기 때문에 ONOS를 통한 패킷제어가 애초에 불가능했다.</p>
</blockquote>
<p>기존의 블로그에서는 HOST 두 개와 라즈베리파이, ONOS를 설치한 컴퓨터 전부를 유선으로 연결하여 같은 대역에서 패킷제어를 시도했기 때문에 가능했던 것이었다.</p>
<p>나의 경우 라즈베리파이의 유선연결이 되지 않아, 무선연결로 수행했고, 준비했던 두 개의 Host 중 하나의 Host가 오래된 노트북이었어서 이더넷 선 연결을 인식하지 못하여 Host를 하나밖에 연결하지 못했다.</p>
<p>그럼에도 불구하고, 이번 실습을 통해 ONOS의 패킷 라우팅 구조와 원리에 대해 깊이있게 이해할 수 있게 되었다는 점에 있어서 큰 의의가 있었던 실습이라고 생각한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ONOS 실습] ONOS 2.7.0, Ubuntu 22.04이용해 mininet 가상 네트워크 연결]]></title>
            <link>https://velog.io/@hyeondooori_/ONOS-2.7.0-Ubuntu-22.04%EC%9D%B4%EC%9A%A9%ED%95%B4-mininet-%EA%B0%80%EC%83%81-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%97%B0%EA%B2%B0</link>
            <guid>https://velog.io/@hyeondooori_/ONOS-2.7.0-Ubuntu-22.04%EC%9D%B4%EC%9A%A9%ED%95%B4-mininet-%EA%B0%80%EC%83%81-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%97%B0%EA%B2%B0</guid>
            <pubDate>Sun, 15 Dec 2024 16:24:32 GMT</pubDate>
            <description><![CDATA[<p>이 포스트는 이 블로그를 참고했습니다! -&gt; <a href="https://m.blog.naver.com/love_tolty/222608954525?recommendTrackingCode=2">click!</a>
저 블로그는 ONOS 2.7.0 버전이 아닌 다른 버전을 사용하고 있어 명령어 사용이 달라지는 부분들이 있으니, 최근의 정보를 확인하고 싶다면 제 글을 쭉 따라해보시면 됩니다!</p>
<p>window 사용자 기준
Ubuntu를 실행하기 위한 가상머신이 필요합니다!
저는 vmware을 설치하여 사용했습니다.</p>
<p>그리고, Ununtu 22.04버전 이미지를 다운로드받아서, vmware에서 실행시켜주면 됩니다. </p>
<p><a href="https://m.blog.naver.com/PostView.naver?blogId=love_tolty&amp;logNo=223289610110&amp;navType=by">ONOS 설치 블로그</a>
위 블로그를 참고하여 ONOS를 설치했습니다. 
그대로 쭉 따라하시면 잘 설치 될겁니다!
<img src="https://velog.velcdn.com/images/hyeondooori_/post/92040804-1cd6-4ec9-a0dc-32cfbb1f841b/image.png" alt="">
<img src="https://velog.velcdn.com/images/hyeondooori_/post/4635727b-c39c-4332-b492-3fcd9eec2109/image.png" alt="">
저는 이렇게 설치가 잘 되었습니다.</p>
<p>ONOS CLI를 실행하는데, 먼저 컴퓨터의 IP주소를 알아야 해요!</p>
<pre><code>ip route | grep default </code></pre><p>위 명령어를 통해 공유기의 IP주소를 확인했습니다.
저는 192.168.224.2 인 것으로 확인했습니다.</p>
<p>그 다음으로는 pc의 주소인데,</p>
<pre><code>ip addr show </code></pre><p>위 명령어를 통해 192.168.224.128인 것을 확인했습니다.</p>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/4a72d067-4405-460e-a017-59a7a7bed7f4/image.png" alt="">
그러면 이렇게 본인의 IP주소를 넣어 ONOS에 접속할 수 있습니다.
초기 비밀번호는 karaf입니다.
-p 옵션은 포트번호를 지정합니다. 로컬인 클라이언트가 ONOS 컨트롤러로 SSH 연결을 시도할 때 서버의 8101포트를 통해서 SSH 요청을 보내도록 지정하는 것입니다. 즉, 나가는 포트는 랜덤한 동적 포트로 설정되지만, ONOS컨트롤러는 고정된 포트(여기서는 8101) 포트로 SSH 연결을 수신합니다.</p>
<p>이제는 가상 네트워크를 구성해서 OpenFlow 프로토콜을 기반으로 ONOS와 연결해볼 것입니다.</p>
<blockquote>
<p>여기에서 OpenFlow는 SDN에서 사용하는 프로토콜입니다!</p>
</blockquote>
<p>OpenFlow를 사용하기 위해서는 ONOS CLI로 접속하여 OpenFlow프로토콜을 지원하는 ONOS 애플리케이션인 &#39;org.onosproject.openflow&#39;를 활성화 해주어야 합니다. 
다음과 같이 진행합니다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/05219390-6e48-488e-a6c7-75074ed3b05a/image.png" alt="">
두 번째로 친 명령어는 현재 실행중인 앱을 조회하는 명령어입니다.
openflow 관련 애플리케이션들이 실행되는 것을 확인할 수 있습니다.</p>
<p>그리곤, 다른 터미널을 열어 apt-get을 이용해 mininet을 설치해주어야 합니다.</p>
<pre><code>sudo apt-get install mininet</code></pre><p>오픈소스 SW인 OpenFlow 기반의 가상 스위치를 제공해주는 Open vswitch(이하 OVS)도 패키지 관리도구인 apt-get으로 설치해줍니다.</p>
<pre><code>sudo apt-get install openvswitch-switch</code></pre><p>설치가 완료되면, mininet으로 1개의 OVS 스위치(s1)에 두 개의 가상 Host(h1,h2)와 ONOS 제어기(c0)가 연결되도록 가상네트워크를 구성하기 위해서
<img src="https://velog.velcdn.com/images/hyeondooori_/post/92155e61-2295-4369-93ae-3a43fce12e11/image.png" alt=""></p>
<p>아까 확인했던 pc의 ip주소를 넣어 명령어를 실행시켜 주면 됩니다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/155c26f3-ffd0-4a7c-880a-e5ad02e3bb83/image.png" alt="">
<img src="https://velog.velcdn.com/images/hyeondooori_/post/7c7d68b4-002d-49cb-b8b5-c7f26a83b425/image.png" alt="">
명령어의 옵션은 위와 같습니다.</p>
<p>명령어 실행 후, ONOS CLI에서 현재 ONOS와 연결된 스위치 정보를 조회해보면 OVS 스위치 1대가 ONOS와 연결이 되었음을 확인할 수 있습니다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/ec92f02e-c581-4a5e-a1e3-dfb2db666988/image.png" alt=""></p>
<p>mininet cli에서 ping테스트를 해보면, 아직 flow rule을 설정하지 않아 서로 메시지를 주고받을 순 없다고 나옵니다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/f9f3aff2-9de9-4e7a-b274-93b036036bcf/image.png" alt=""></p>
<h2 id="mininet으로-가상-네트워크-구조-파악하기">Mininet으로 가상 네트워크 구조 파악하기</h2>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/776238c2-b4f4-4fcb-a9e0-efc846e0750b/image.png" alt="">
nodes 명령어를 통해 확인해보면, c0,h1,h2,s1 이렇게 4개의 Node가 생긴 것을 확인할 수 있습니다. 
<img src="https://velog.velcdn.com/images/hyeondooori_/post/a9ede84e-515b-465a-b049-ea2357543c91/image.png" alt=""></p>
<p>이러한 상황입니다.
여기에서 각 Node들의 interface정보들을 확인해보면, </p>
<ul>
<li>h1은 h1-eth0인터페이스에 IP주소 10.0.0.1 할당</li>
<li>h2는 h2-eth0인터페이스에 IP주소 10.0.0.2 할당</li>
<li>OVS 스위치 s1에는 eth1,eth2인터페이스 생성</li>
<li>onos에 해당하는 node인 c0는 ip주소/포트 192.168.224.128/6653
<img src="https://velog.velcdn.com/images/hyeondooori_/post/49699844-cc8d-41a0-be30-41781637f1ec/image.png" alt="">
dump와 net 명령어를 통해 더 자세히 확인할 수 있습니다. 
dump는 인터페이스 ip주소를 보여주고, 
net은 각 node들의 네트워크 인터페이스 별 연결정보를 알려줍니다.
h1의 h1-eth0 인터페이스는 OVS 스위치 s1의 s1-eth1 인터페이스와 연결되고, 호스트 h2의 h2-eth0 인터페이스는 OVS 스위치 s1의 s1-eth2인터페이스와 연결되어있는 것을 확인할 수 있습니다.
ONOS인 c0는 당연하게 OVS 스위치 s1과 local로 연결되므로, Mininet으로 살펴본 가상 네트워크 구성은 이러합니다. 
<img src="https://velog.velcdn.com/images/hyeondooori_/post/41fb3925-0aa5-4651-8219-6bbe030b093a/image.png" alt=""></li>
</ul>
<h2 id="onos가-바라본-가상-네트워크-구조-파악하기">ONOS가 바라본 가상 네트워크 구조 파악하기</h2>
<p>ONOS가 바라보는 가상 네트워크 구조는 앞서 Mininet으로 파악한 가상 네트워크 구조와 동일하지만, ONOS는 직접 Flow Rule을 설정해주어야 하기 때문에 각 장치별 요소를 구분하기 위한 값이 Mininet을 통해 파악한 것과는 조금 다릅니다.</p>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/d89df39c-4eb3-4600-ae07-68c9ee92b798/image.png" alt="">
먼저 ONOS CLI로 접속하여 현재 연결된 OVS 스위치 정보를 조회해보면, Mininet에서는 s1이라는 노드로 표현되었지만, 여기서는 <code>of:0000000000000001</code>라는 고유의 장치 ID 값을 가집니다. </p>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/8c8be2ba-0c36-4593-a6ee-1fb6ee1cf7d0/image.png" alt="">
이번에는 hosts명령어를 통해 확인한 것입니다.
<code>locations=[of:0000000000000001/2]</code>는 호스트 <code>of:0000000000000001</code>가 스위치의 2번포트에 연결되어있음을 의미합니다. </p>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/0a9b7740-c4c9-4a70-a3cc-4ac90ff237a6/image.png" alt="">
이 사진이 최종 연결구조입니다.</p>
<p>h1이 연결된 OVS 스위치(s1)의 s1-eth1 인터페이스는 1번 포트이고, h2가 연결된 s1-eth2는 2번 포트로 인식됨을 확인할 수 있습니다. </p>
<p>이제 이렇게 최종 확인된 가상 네트워크 구조를 가지고, ONOS의 Flow Rule 설정을 통해 두 Host 간 통신이 되도록 해볼 것입니다.</p>
<h2 id="flow-rule-설정하기---reactive-mode">Flow Rule 설정하기 - reactive mode</h2>
<p>Openflow 프로토콜에서는 flow rule 설정 방식이 두 가지 있습니다. 하나는 flow rule이 정의되지 않은 미지의 패킷이 SDN 스위치로 유입되었을 때, SDN 제어기에 의해 flow rule이 결정되는 reactive 모드이고, 다른 하나는 네트워크 관리자에 의하여 SDN 스위치에 미리 flow rule을 등록하는 proactive 모드입니다.</p>
<p>reactive모드를 실행하기 위해서는 ONOS CLI에서 <code>org.onosproject.fwd</code>애플리케이션을 실행시켜야 합니다. 이 애플리케이션은 packet-in메시지를 SDN 제어기인 ONOS로 보내도록 flow rule을 설정하여 ONOS가 reactive모드로 동작하도록 합니다. 
<img src="https://velog.velcdn.com/images/hyeondooori_/post/cbc87ae0-4d28-4eb7-9242-7587df8bac87/image.png" alt="">
명령어가 잘 실행되었습니다. 
<img src="https://velog.velcdn.com/images/hyeondooori_/post/1c99b684-b9b8-4e75-9578-e342f82a7aa8/image.png" alt="">
mininet cli에서 다시 ping을 보내보면, 기존에는 안되던 h1과 h2의 ping이 성공적으로 잘 수행된 것을 확인할 수 있습니다. 
ONOS를 통해 직접 flow rule이 OVS 스위치 s1에 설정된 것입니다. 
<img src="https://velog.velcdn.com/images/hyeondooori_/post/7d9da640-164e-4067-9021-598c770f3158/image.png" alt="">
ONOS CLI에서 flows 명령어를 통해 flow rule을 조회해보면 ADDED상태로 추가된 rule을 확인할 수 있습니다. </p>
<h2 id="flow-rule-설정하기---proactive-모드">Flow Rule 설정하기 - Proactive 모드</h2>
<p>이제부터는 ONOS에서 제공하는 REST API를 활용해서 Proactive모드로써 직접 Flow Rule을 설정해볼 것입니다.
일단 ONOS CLI에 접속하여 Reactive 모드를 테스트하기 위해 앞서 실행했던 <code>org.onosproject.fwd</code>를 비활성화해줍니다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/81522f9f-39f5-4035-943e-3508951c4d89/image.png" alt=""></p>
<p>그리고 flow_rule_post.sh라는 스크립트 파일을 만들 것입니다. 이 스크립트 파일은 ONOS REST API를 이용해서 JSON파일로 정의된 Flow Rule을 해당 OVS 스위치에 설정하는 역할을 수행합니다. 
flow_rule_post.sh에는 아래 내용을 넣어주면 됩니다. 역시 IP주소는 바꾸어 넣어야 합니다.</p>
<pre><code>ONOS_IP=192.168.224.128
ONOS_PORT=8181

curl -X POST --header &quot;Content-Type: application/json&quot; --header &quot;Accept: application/json&quot; -d @$1 &quot;http://$ONOS_IP:$ONOS_PORT/onos/v1/flows/&quot; --user karaf:karaf</code></pre><p><img src="https://velog.velcdn.com/images/hyeondooori_/post/c8722f7b-da76-422a-9ece-33d8962939df/image.png" alt=""></p>
<p>이제 flow rule을 정의해볼 것입니다. 
h1에서 h2로 데이터를 보낼 수 있도록 OVS스위치 s1의 1번포트로 패킷이 들어오면 2번포트로 내보내라는 flow rule을 정의해볼 것입니다. 
<img src="https://velog.velcdn.com/images/hyeondooori_/post/eb808909-c53f-4753-a673-83f2162bfcbb/image.png" alt="">
이를 위해 OVS 스위치 s1의 입력포트(IN_PORT)는 1번, 출력포트(OUTPUT)는 2번으로 지정된 flow rule을 5000이라는 우선순위를 적용하여 <code>flow_h1_to_h2.json</code>라는 JSON포맷 파일로 생성합니다.</p>
<pre><code>{
  &quot;flows&quot;: [
    {
      &quot;priority&quot;: &quot;50000&quot;,
      &quot;timeout&quot;: 0,
      &quot;isPermanent&quot;: true,
      &quot;deviceId&quot;: &quot;of:0000000000000001&quot;,
      &quot;treatment&quot;: {
        &quot;instructions&quot;: [
          {
            &quot;type&quot;: &quot;OUTPUT&quot;,
            &quot;port&quot;: &quot;2&quot;
          }
        ]
      },
      &quot;selector&quot;: {
        &quot;criteria&quot;: [
          {
            &quot;type&quot;: &quot;IN_PORT&quot;,
            &quot;port&quot;: &quot;1&quot;
          }
        ]
      }
    }
  ]
}
</code></pre><p>이번에는 반대로 h2에서 h1으로 데이터를 보낼 수 있도록 OVS스위치 s1의 2번 포트로 패킷이 들어오면 1번 포트로 내보내라는 Flow Rule을 정의해볼 것입니다. 
<img src="https://velog.velcdn.com/images/hyeondooori_/post/a671a47b-d551-4d12-b3d7-d91b2a9e8616/image.png" alt="">
이를 위해 OVS 스위치 s1 (of:0000000000000001) 의 입력포트 (IN_PORT) 는 2번, 출력포트 (OUTPUT) 는 1번으로 지정된 Flow Rule을 5000이라는 우선순위 (Priority) 를 적용해 &#39;flow_h2_to_h1.json&#39; 라는 JSON 포맷 파일로 생성합니다.</p>
<pre><code>{
  &quot;flows&quot;: [
    {
      &quot;priority&quot;: &quot;50000&quot;,
      &quot;timeout&quot;: 0,
      &quot;isPermanent&quot;: true,
      &quot;deviceId&quot;: &quot;of:0000000000000001&quot;,
      &quot;treatment&quot;: {
        &quot;instructions&quot;: [
          {
            &quot;type&quot;: &quot;OUTPUT&quot;,
            &quot;port&quot;: &quot;1&quot;
          }
        ]
      },
      &quot;selector&quot;: {
        &quot;criteria&quot;: [
          {
            &quot;type&quot;: &quot;IN_PORT&quot;,
            &quot;port&quot;: &quot;2&quot;
          }
        ]
      }
    }
  ]
}</code></pre><p><img src="https://velog.velcdn.com/images/hyeondooori_/post/9eeb42fa-a66f-452d-a281-e19403d78ec3/image.png" alt="">
편의를 위해 생성한 파일의 권한까지 변경해준 모습입니다.</p>
<p>JSON 파일로 정의한 Flow Rule 파일 (flow_h2_to_h1.json, flow_h2_to_h1.json) 들을 먼저 앞에서 작성한 flow_rule_post.sh 스크립트 파일을 이용하여, ONOS를 통해 OVS 스위치 s1 (of:0000000000000001) 로 Flow Rule 정보를 등록해줍니다. Flow Rule이 정상적으로 내려갔다면 스위치 s1의 Device ID와 등록한 Flow Rule의 ID 값이 출력됩니다.</p>
<pre><code>./flow_rule_post.sh flow_h1_to_h2.json flow_h2_to_h1.json

./flow_rule_post.sh flow_h2_to_h1.json</code></pre><p><img src="https://velog.velcdn.com/images/hyeondooori_/post/3f99b655-6e22-4086-ab91-7dbb4ffaa87c/image.png" alt="">
ONOS CLI로 접근하여 실제 등록된 FlowRule정보를 확인해보면, 성공적으로 등록된 것을 확인할 수 있습니다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/0edfa7de-957c-44ab-8872-47e6f8f808b1/image.png" alt="">
이제 네트워크 구조를 도식화해보면 다음과 같습니다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/4962fe15-a25b-4f00-b35f-e246441a1d92/image.png" alt="">
이 사진 역시 블로그에서 가져온 것이라 세부적 flow Id, c0 address, MAC address는 다릅니다.
flow rule이 등록된 상태에서 mininet CLI를 통해 h1과 h2의 ping테스트를 해보면 성공적으로 수행되는 것을 확인할 수 있습니다. 
<img src="https://velog.velcdn.com/images/hyeondooori_/post/0033cd40-cb59-46b1-9e14-f7f8de6db61b/image.png" alt=""></p>
<p>먼저 h1에서 h2로 Ping 메시지를 보낼 경우, h1은 h2의 MAC 주소를 알아내기 위해 ARP 요청 메시지를 보내고, 해당 메시지는 OVS 스위치 s1의 1번포트로 전달됩니다. 이때 OVS 스위치 s1은 메시지의 처리를 위해 자신의 Flow Table에 등록된 Flow Rule을 찾아봅니다. Flow Table에서 입력포트 (IN_PORT) 가 1번인 Match Field가 일치하여, 해당 ARP 요청 메시지는 출력포트 (OUTPUT) 인 OVS 스위치 s1의 2번포트를 통해서 h2로 전달됩니다.</p>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/d40f899a-4ccc-4c8d-ab9d-5542f93bf405/image.png" alt=""></p>
<p>h1로부터 ARP 요청 메시지를 받은 h2는 자신의 MAC 주소를 포함한 ARP 응답 메시지를 보내고, 해당 메시지는 OVS 브릿지 s1의 2번포트로 전달됩니다. 이때 OVS 스위치 s1은 메시지의 처리를 위해 자신의 Flow Table에서 Flow Rule을 찾아봅니다. Flow Table에서 입력포트 (IN_PORT) 가 2번인 Match Field가 일치하여, 해당 ARP 응답 메시지는 출력포트 (OUTPUT) 인 OVS 스위치 s1의 1번포트를 통해서 h1로 전달됩니다.</p>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/20a92239-96d3-4a88-bec4-104989bf1abd/image.png" alt=""></p>
<p>h2로부터 ARP 응답 메시지를 전달받은 h1은 해당 메시지에 포함된 h2의 MAC 주소를 파악하고 해당 MAC 주소를 이용하여 ICMP 요청 메시지를 h2에 전달합니다. 이때 해당 메시지는 OVS 스위치 s1의 1번 포트로 제일 먼저 전달되며, OVS 스위치 s1은 메시지의 처리를 위해 자신의 Flow Table에 등록된 Flow Rule을 찾아봅니다. Flow Table에서 입력포트 (IN_PORT) 가 1번인 Match Field가 일치하여, 해당 ICMP 요청 메시지는 출력포트 (OUTPUT) 인 OVS 스위치 s1의 2번포트를 통해서 h2로 전달됩니다.</p>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/b11b48fb-359f-4891-8b2b-3ab2ffde7bc4/image.png" alt=""></p>
<p>h1로부터 ICMP 요청 메시지(echo request)를 받은 h2는 이에 대한 응답으로 ICMP 응답 메시지(echo reply)를 생성해 h1으로 전달합니다. 이때 해당 메시지는 OVS 브릿지 s1의 2번포트로 먼저 전달됩니다. 그리고 OVS 스위치 s1은 메시지의 처리를 위해 자신의 Flow Table에서 Flow Rule을 찾아봅니다. Flow Table에서 입력포트 (IN_PORT) 가 2번인 Match Field가 일치하여, 해당 ARP 응답 메시지는 출력포트 (OUTPUT) 인 OVS 스위치 s1의 1번포트를 통해서 h1으로 전달됩니다.</p>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/b8ae6156-7822-482b-99cb-83fbfef5f0cf/image.png" alt=""></p>
<p>여기까지 h1에서 h2로 Ping 메시지를 보내는 동안 OVS 스위치 s1에 등록된 Flow Rule이 어떻게 적용되어 데이터를 전달하는지 살펴보았습니다. h2에서 h1으로 Ping 메시지를 보내는 경우에도 마찬가지로 동일한 방식으로 Flow Rule이 적용되어 통신이 이루어집니다.</p>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/66ab2c8c-33e6-4f7d-b410-f011df7f32dd/image.png" alt="">
<img src="https://velog.velcdn.com/images/hyeondooori_/post/d47ab9c4-6673-4e72-9554-25780cf58b39/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Mininet 사용해보기]]></title>
            <link>https://velog.io/@hyeondooori_/Mininet-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@hyeondooori_/Mininet-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Sun, 15 Dec 2024 11:40:29 GMT</pubDate>
            <description><![CDATA[<p>SDN관련 실습이 잘 안되어서, 다른 할 것을 찾아보던 중에 mininet을 이용해서 네트워크 환경을 시뮬레이션 하는 것에 대해 알게되었어요!
제가 본 블로그는 아래 링크 첨부합니다.
<a href="https://jelong.tistory.com/entry/Ubuntu%EC%97%90%EC%84%9C-Mininet%EB%AF%B8%EB%8B%88%EB%84%B7-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0">CLick!!</a></p>
<h3 id="mininet이란">mininet이란</h3>
<p>가상 네트워크를 통해 SDN이나 Openflow와 같은 네트워크 환경을 시뮬레이션 할 수 있는 오픈 소스 프로그램입니다.
Mininet은 가상 스위치, 호스트를 사용해 네트워크를 시뮬레이션하는 소프트웨어로 SDN 및 Openflow와 같은 네트워크 프로토콜을 시험하고 개발하는 데 사용된다고 합니다.
Mininet은 python기반으로 작성되었으며, 가상화 기술을 사용해 다중 사용자를 지원하고, 빠르고 쉽게 네트워크를 생성, 시험할 수 있습니다.</p>
<p>Mininet은 학술연구, 프로토타입 및 시스템 테스트, 교육 및 교육용 목적으로 사용됩니다. Mininet은 또한 Openflow 교육 및 SDN 프로토타입 개발 등의 목적으로도 사용됩니다. </p>
<h3 id="mininet-사용해-네트워크-토폴로지-생성해보기">mininet 사용해 네트워크 토폴로지 생성해보기.</h3>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/07ebb2f7-2752-42a6-baf6-cb40dbf49bb4/image.png" alt="">
mininet 설치
<img src="https://velog.velcdn.com/images/hyeondooori_/post/ba81b4f0-637e-48a3-994a-c6f85e8fc401/image.png" alt=""></p>
<pre><code class="language-python">from mininet.net import Mininet
from mininet.cli import CLI
from mininet.node import Host
from mininet.node import OVSKernelSwitch
from mininet.log import setLogLevel, info</code></pre>
<p>Mininet: 네트워크를 시뮬레이션하는 객체.
CLI: Mininet의 명령어 인터페이스로 사용자가 명령을 입력할 수 있게 함.
Host: 가상 호스트를 생성.
OVSKernelSwitch: Open vSwitch 기반 스위치를 생성.
setLogLevel: 로그 출력 레벨을 설정.</p>
<pre><code class="language-python">def myTopo():
    net = Mininet( topo=None, autoSetMacs=True, build=False, ipBase=&#39;10.0.1.0/24&#39; )</code></pre>
<p>net: Mininet 객체를 생성합니다.
topo=None: 사용자 정의 토폴로지를 사용할 것을 의미합니다.
autoSetMacs=True: 자동으로 MAC 주소를 설정합니다.
build=False: 네트워크는 명시적으로 build() 메서드를 호출할 때까지 빌드되지 않습니다.
ipBase=&#39;10.0.1.0/24&#39;: 호스트들이 사용할 기본 IP 주소 대역입니다.</p>
<pre><code class="language-python">h1 = net.addHost( &#39;h1&#39;, cls=Host, defaultRoute=None )
h2 = net.addHost( &#39;h2&#39;, cls=Host, defaultRoute=None )
h3 = net.addHost( &#39;h3&#39;, cls=Host, defaultRoute=None )
s1 = net.addSwitch( &#39;s1&#39;, cls=OVSKernelSwitch, failMode=&#39;standalone&#39; )</code></pre>
<p>net.addHost: 네트워크에 호스트를 추가합니다.
h1, h2, h3라는 이름의 호스트를 추가.
net.addSwitch: 네트워크에 스위치를 추가합니다.
s1이라는 이름의 스위치를 생성.
failMode=&#39;standalone&#39;: 스위치가 컨트롤러 없이 독립적으로 동작합니다.</p>
<pre><code class="language-python">net.addLink(h1, s1)
net.addLink(h2, s1)
net.addLink(h3, s1)</code></pre>
<p>호스트 h1, h2, h3를 스위치 s1과 연결합니다.</p>
<pre><code class="language-python">h1.setIP(intf=&quot;h1-eth0&quot;, ip=&#39;10.0.1.2/24&#39;)
h2.setIP(intf=&quot;h2-eth0&quot;, ip=&#39;10.0.1.3/24&#39;)
h3.setIP(intf=&quot;h3-eth0&quot;, ip=&#39;10.0.1.4/24&#39;)</code></pre>
<p>각 호스트의 네트워크 인터페이스에 IP 주소를 설정합니다.
인터페이스 이름: h1-eth0, h2-eth0, h3-eth0.
IP 주소:</p>
<ul>
<li>h1: 10.0.1.2/24</li>
<li>h2: 10.0.1.3/24</li>
<li>h3: 10.0.1.4/24<pre><code class="language-python">net.build()
net.start()</code></pre>
build(): 네트워크를 빌드합니다.
start(): 네트워크를 시작합니다.<pre><code class="language-python">CLI(net)
net.stop()</code></pre>
CLI(net): Mininet 명령어 인터페이스를 시작합니다. 사용자가 명령을 입력해 네트워크 상태를 확인하거나 테스트할 수 있습니다.
net.stop(): 네트워크를 종료합니다.<pre><code class="language-python">if __name__ == &#39;__main__&#39;:
  setLogLevel(&quot;info&quot;)
  myTopo()</code></pre>
setLogLevel(&quot;info&quot;): 로그 출력 레벨을 &quot;info&quot;로 설정.
myTopo(): 위에서 정의한 네트워크 토폴로지 함수 실행.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/b46b0d8c-7c37-460d-bdcf-ef5df047437c/image.png" alt="">
pingall명령어를 통해 잘 연결되는 것을 확인할 수 있다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[github actions와 docker를 이용한 배포과정]]></title>
            <link>https://velog.io/@hyeondooori_/%EB%B0%B0%ED%8F%AC%EA%B3%BC%EC%A0%95</link>
            <guid>https://velog.io/@hyeondooori_/%EB%B0%B0%ED%8F%AC%EA%B3%BC%EC%A0%95</guid>
            <pubDate>Tue, 26 Nov 2024 02:31:35 GMT</pubDate>
            <description><![CDATA[<p>목차</p>
<ol>
<li>EC2 생성</li>
<li>RDS 생성</li>
<li>Dockerfile 작성</li>
<li>github secrets에 환경변수 선언</li>
<li>github-actions.yml 코드 작성</li>
</ol>
<p>EC2 생성과 RDS 생성은 자료들이 많을 것이라고 생성한다.
주의할 점은 같은 VPC 안에서 생성되어야하기 때문에 RDS 생성 시 기존에 생성해두었던 EC2를 지정해주어야 한다.
같은 VPC안에 있어야 서버에서 RDS를 찾아 생성할 수 있다.</p>
<h1 id="1-ec2-생성">1. EC2 생성</h1>
<h1 id="2-rds-생성">2. RDS 생성</h1>
<h1 id="3-dockerfile-작성">3. DockerFile 작성</h1>
<p>프로젝트의 루트 디렉토리 내에 생성하면 된다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/6542da10-3b65-4d6d-9712-7ef385ae926b/image.png" alt=""></p>
<pre><code># 베이스 이미지: Java 17 (필요에 따라 Java 버전 변경 가능)
FROM openjdk:17-jdk-slim

# 컨테이너 내부의 작업 디렉토리 설정
WORKDIR /app

# 빌드된 JAR 파일을 컨테이너로 복사
COPY ./build/libs/*.jar app.jar

# 컨테이너에서 열릴 포트
EXPOSE 8080

# 애플리케이션 실행 명령어
ENTRYPOINT [&quot;java&quot;, &quot;-jar&quot;, &quot;app.jar&quot;]</code></pre><h1 id="4-github-secrets-환경변수-선언">4. github secrets 환경변수 선언</h1>
<p>github actions를 이용할건데, github에 개인정보는 올리면 안되니까 환경변수를 이용한다.
아래의 github-actions.yml 코드 내부에 이용되는 것들을 환경변수로 미리 선언하는 것이다.</p>
<h2 id="yml">YML</h2>
<pre><code>YML
spring:
  application:
    name: forest
  profiles:
    group:
      dev: dev, local
      deploy: deploy
    active: dev
</code></pre><h2 id="yml_dev">YML_DEV</h2>
<pre><code>spring:
  datasource:
    url: jdbc:mysql://&lt;엔드포인트&gt;:&lt;포트번호&gt;/&lt;DB인스턴스식별자이름&gt;
    username: &lt;rds 생성 시 지정한 마스터사용자 이름&gt;
    password: &lt;rds 생성 시 지정한 마스터암호&gt;
  jpa:
    show-sql: true
    hibernate:
      ddl-auto: update
logging:
  level:
    org.hibernate.SQL: debug</code></pre><p>hibernate:
      ddl-auto: update
데이터베이스 내부에 자동으로 테이블이 만들어지도록 하는 명령어이다. 
이 코드를 써주지 않으면 actions파일을 실행시켰을 때 오류가 나므로 꼭 포함시켜주어야한다.</p>
<h2 id="username">USERNAME</h2>
<p>ubuntu</p>
<h2 id="docker_username">DOCKER_USERNAME</h2>
<p>docker 가입할 때 지정한 사용자 이름</p>
<h2 id="docker_password">DOCKER_PASSWORD</h2>
<p>docker 가입할 때 지정한 비밀번호</p>
<h2 id="host_dev">HOST_DEV</h2>
<p>EC2로 할당받은 public IPv4 주소</p>
<h2 id="private-key">PRIVATE KEY</h2>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/242e5bea-08ea-4812-ab9d-31070c323615/image.png" alt="">
ppk파일을 다운로드 했다면, 이를 cat명령어를 이용해 확인한 후, 키 부분을 떼어 위와같이 작성한 후 <code>PRIVATE KEY</code> 환경변수로 등록해준다.</p>
<h1 id="5-github-actionsyml-코드-작성">5. github-actions.yml 코드 작성</h1>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/b08efa51-2d8e-4c7c-b603-4aa580a483fa/image.png" alt="">
먼저 루트 디렉토리 내부의 .github 패키지 내부에 workflows 패키지를 만들고, 그 안에 github-actions.yml 파일을 만든다.
나의 경우에는 dev를 실행하도록 코드를 작성했다.</p>
<pre><code class="language-yml"># github repository actions 페이지에 나타날 이름
name: CI/CD using github actions &amp; docker

# event trigger
# main브랜치에 push가 되었을 때 실행
on:
  push:
    branches: [ &quot;main&quot; ]

permissions:
  contents: read

jobs:
  CI-CD:
    runs-on: ubuntu-latest
    steps:

      # JDK setting - github actions에서 사용할 JDK 설정 (프로젝트나 AWS의 java 버전과 달라도 무방)
      - uses: actions/checkout@v3
      - name: Set up JDK 17
        uses: actions/setup-java@v3
        with:
          java-version: &#39;17&#39;
          distribution: &#39;temurin&#39;

      # gradle caching - 빌드 시간 향상
      - name: Gradle Caching
        uses: actions/cache@v3
        with:
          path: |
            ~/.gradle/caches
            ~/.gradle/wrapper
          key: ${{ runner.os }}-gradle-${{ hashFiles(&#39;**/*.gradle*&#39;, &#39;**/gradle-wrapper.properties&#39;) }}
          restore-keys: |
            ${{ runner.os }}-gradle-
      # 권한 부여
      - name: Add execution permissions to Gradle Wrapper
        run: chmod +x ./gradlew

      # 환경별 yml 파일 생성(1) - application.yml
      - name: make application.yml
        if: |
          contains(github.ref, &#39;main&#39;)
        run: |
          mkdir ./src/main/resources # resources 폴더 생성
          cd ./src/main/resources # resources 폴더로 이동
          touch ./application.yml # application.yml 생성
          echo &quot;${{ secrets.YML }}&quot; &gt; ./application.yml # github actions에서 설정한 값을 application.yml 파일에 쓰기
        shell: bash

      # 환경별 yml 파일 생성(2) - dev
      - name: make application-dev.yml
        run: |
          cd ./src/main/resources
          touch ./application-dev.yml
          echo &quot;${{ secrets.YML_DEV }}&quot; &gt; ./application-dev.yml
        shell: bash




      # gradle build
      - name: Build with Gradle
        run: ./gradlew build -x test


      # docker build &amp; push to develop
      - name: Docker build &amp; push to dev
        run: |
          docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
          docker build -f Dockerfile-dev -t ${{ secrets.DOCKER_USERNAME }}/docker-test-dev .
          docker push ${{ secrets.DOCKER_USERNAME }}/docker-test-dev



      ## deploy to develop
      - name: Deploy to dev
        uses: appleboy/ssh-action@master
        id: deploy-dev
        with:
          host: ${{ secrets.HOST_DEV }} # EC2 퍼블릭 IPv4 DNS
          username: ${{ secrets.USERNAME }} # ubuntu
          password: ${{ secrets.PASSWORD }}
          port: 22
          key: ${{ secrets.PRIVATE_KEY }}
          script: |
            port=8080
            echo &quot;Checking for containers using port $port...&quot;

            # Find the container ID using the specified port
            container_id=$(docker ps --filter &quot;publish=$port&quot; --format &quot;{{.ID}}&quot;)

            if [ -n &quot;$container_id&quot; ]; then
              echo &quot;Found container with ID: $container_id. Stopping it...&quot;
              docker stop &quot;$container_id&quot;
              docker rm &quot;$container_id&quot;
              echo &quot;Container stopped successfully.&quot;
            else
              echo &quot;No container is using port $port.&quot;
            fi
            sudo docker ps
            sudo docker pull ${{ secrets.DOCKER_USERNAME }}/docker-test-dev
            sudo docker run --name team-18 -d -p 8080:8080 ${{ secrets.DOCKER_USERNAME }}/docker-test-dev</code></pre>
<hr>
<hr>
<hr>
<hr>
<h3 id="applicationyml코드-작성">application.yml코드 작성</h3>
<pre><code class="language-yml">spring:
  application:
    name: &lt;애플리케아션 이름&gt;
  profiles:
    group:
      dev: dev, local
      deploy: deploy
    active: dev</code></pre>
<p>💻 <strong>코드 설명</strong></p>
<ul>
<li>spring.application<ul>
<li>name
Spring Boot 애플리케이션의 이름을 지정하는데 사용된다. 지정된 이름은 주로 로그 출력, Spring Cloud 환경에서 서비스 식별, 그리고 애플리케이션 상태 모니터링 등 여러 곳에서 활용된다. 
로깅에 표시되는 예시는 다음과같다. 예를들어 애플리케이션 이름을 &quot;hyeondooori&quot;로 했다면
<code>2024-11-22 12:00:00.000  INFO 12345 --- [           main] hyeondooori.Application : Started Application in 5.123 seconds (JVM running for 5.678)</code></li>
</ul>
</li>
<li>spring.profiles<ul>
<li>group
프로파일 그룹을 정의한다. dev그룹은 dev와 local프로파일이 함께 활성화되고,
deploy그룹은 deploy 프로파일만 활성화된다.
이를통해 여러 프로파일일을 하나의 그룹으로 묶어서 사용할 수 있다.</li>
<li>active
현재 활성화된 프로파일을 지정한다. 여기서는 dev프로파일이 활성화된 상태이다.</li>
</ul>
</li>
</ul>
<p>💻<strong>동작 방식</strong></p>
<ol>
<li>spring.profiles.active에 따라 프로파일 로드</li>
</ol>
<ul>
<li>active: dev 설정에 따라 application-dev.yml 또는 dev 관련 설정이 로드된다.</li>
</ul>
<ol start="2">
<li>그룹 활성화</li>
</ol>
<ul>
<li>dev 프로파일이 활성화되면, 그룹 설정에 따라 dev와 local 두 프로파일의 설정이 모두 적용된다.</li>
</ul>
<ol start="3">
<li>우선순위</li>
</ol>
<ul>
<li>기본설정(application.yml) -&gt; 활성화된 프로파일(application-dev.yml 또는 application-deploy.yml) 순서로 설정이 병합된다.</li>
<li>동일한 키가 중복되면, 활성화된 프로파일의 설정이 우선된다.<blockquote>
<p>개발 환경에 따라 application.yml 내부의 <code>spring.profiles.active</code>를 변경하여 활성되는 프로파일을 변경할 수 있다.
로컬환경에서 작업할 때는 active를 local로 하고, 개발환경에서는 dev로 하는 방식!</p>
</blockquote>
</li>
</ul>
<h3 id="❓aws-서버에서의-docker-desktop">❓AWS 서버에서의 Docker Desktop</h3>
<p>로컬에서 Docker컨테이너를 실행하려면 Docker Desktop이 실행중이어야만 했다.
이는 Windows나  Mac과 같은 운영체제에서 Docker engine을 실행하는데 필요하다.</p>
<p>하지만, AWS에서 실행되는 Docker는 해당 서버(AWS 인스턴스)에 설치된 Docker engine 위에서 동작한다. 이 Docker engine은 AWS 서버 자체에서 관리되며, 로컬 Docker Desktop과는 전혀 관계가 없다.</p>
<p>AWS에서 퍼블릭IP를 할당받은 인스턴스(AWS EC2 등)에서 Docker컨테이너를 실행했다면:</p>
<ul>
<li>docker 컨테이너는 AWS 인스턴스 내에서 자체적으로 동작한다.</li>
<li>퍼블릭 IP를 통해 외부에서 접근할 수 있다.</li>
</ul>
<p>컨테이너 실행 유지 조건</p>
<ol>
<li>AWS EC2 인스턴스가 계속 실행 중이어야 한다.
EC2가 중지되면, Docker 컨테이너도 중단된다.</li>
<li>Docker 컨테이너가 백그라운드에서 실행중이어야한다.
컨테이너를 실행할 때 -d(detached mode)옵션을 사용하여 컨테이너를 백그라운드에 유지한다.
<code>docker run -d -p 3306:3306 --name mysql-container -e MYSQL_ROOT_PASSWORD=&lt;password&gt; mysql</code></li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring]Intellij IDEA에서 run버튼을 누르면 일어나는 일들]]></title>
            <link>https://velog.io/@hyeondooori_/SpringIntellij-IDEA%EC%97%90%EC%84%9C-run%EB%B2%84%ED%8A%BC%EC%9D%84-%EB%88%84%EB%A5%B4%EB%A9%B4-%EC%9D%BC%EC%96%B4%EB%82%98%EB%8A%94-%EC%9D%BC%EB%93%A4</link>
            <guid>https://velog.io/@hyeondooori_/SpringIntellij-IDEA%EC%97%90%EC%84%9C-run%EB%B2%84%ED%8A%BC%EC%9D%84-%EB%88%84%EB%A5%B4%EB%A9%B4-%EC%9D%BC%EC%96%B4%EB%82%98%EB%8A%94-%EC%9D%BC%EB%93%A4</guid>
            <pubDate>Fri, 22 Nov 2024 06:33:22 GMT</pubDate>
            <description><![CDATA[<p>IntelliJ IDEA에서 <strong><code>Run</code> 버튼</strong>을 누르면, Spring Boot 애플리케이션(또는 다른 프로젝트)이 실행되기까지 여러 단계가 순차적으로 실행됩니다. 아래는 IntelliJ IDEA에서 <strong>Run 버튼의 동작 과정</strong>을 설명합니다.</p>
<hr>
<h3 id="1-실행-설정-run-configuration-확인"><strong>1. 실행 설정 (Run Configuration 확인)</strong></h3>
<ul>
<li>IntelliJ IDEA의 <strong>Run Configuration</strong>에 정의된 설정을 기반으로 애플리케이션을 실행합니다.</li>
<li>기본적으로 Spring Boot 프로젝트에서는 다음을 설정할 수 있습니다:<ul>
<li>실행할 <strong>클래스</strong>: 예를 들어 <code>@SpringBootApplication</code>이 포함된 메인 클래스.</li>
<li>활성화할 <strong>프로파일</strong>: <code>spring.profiles.active</code> 값을 설정 가능.</li>
<li>JVM 옵션 및 환경 변수 설정 가능.</li>
</ul>
</li>
</ul>
<h4 id="run-configuration-설정-확인-방법"><strong>Run Configuration 설정 확인 방법</strong></h4>
<ul>
<li>IntelliJ 메뉴에서 <code>Run &gt; Edit Configurations</code>를 클릭.</li>
<li>실행하려는 Run Configuration을 선택하여 실행 환경을 확인하고 수정할 수 있습니다.</li>
</ul>
<hr>
<h3 id="2-mavengradle-빌드-과정"><strong>2. Maven/Gradle 빌드 과정</strong></h3>
<p>Spring Boot 프로젝트에서는 실행 전에 <strong>Maven</strong> 또는 <strong>Gradle</strong> 빌드 작업이 수행됩니다.</p>
<ol>
<li><p><strong>프로젝트 컴파일</strong>:</p>
<ul>
<li>소스 코드(Java/Kotlin)를 컴파일하여 <code>.class</code> 파일을 생성.</li>
<li>컴파일된 파일은 기본적으로 <code>/target/classes</code>(Maven) 또는 <code>/build/classes/java/main</code>(Gradle) 디렉토리에 저장.</li>
</ul>
</li>
<li><p><strong>의존성 확인 및 다운로드</strong>:</p>
<ul>
<li>Maven 또는 Gradle이 <code>pom.xml</code> 또는 <code>build.gradle</code> 파일을 읽어 프로젝트에 필요한 라이브러리 의존성을 확인.</li>
<li>누락된 라이브러리가 있다면, 원격 저장소에서 자동으로 다운로드.</li>
</ul>
</li>
<li><p><strong>Spring Boot 애플리케이션 실행 파일 생성</strong>:</p>
<ul>
<li>Gradle/Maven에 의해 빌드된 <code>.jar</code> 또는 <code>.war</code> 파일이 생성됩니다(필요한 경우).</li>
</ul>
</li>
</ol>
<hr>
<h3 id="3-jvm-실행"><strong>3. JVM 실행</strong></h3>
<p>빌드가 완료된 후, IntelliJ는 Java Virtual Machine(JVM)을 실행하여 애플리케이션을 시작합니다.</p>
<ol>
<li><p><strong>메인 클래스 실행</strong>:</p>
<ul>
<li>Spring Boot 애플리케이션에서는 기본적으로 <code>@SpringBootApplication</code>이 정의된 클래스(보통 <code>Application.java</code>)가 실행됩니다.</li>
<li>이 클래스는 내부적으로 <code>SpringApplication.run()</code> 메서드를 호출하여 애플리케이션을 부트스트랩(초기화)합니다.</li>
</ul>
<p>예:</p>
<pre><code class="language-java">@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}</code></pre>
</li>
<li><p><strong>JVM 옵션 적용</strong>:</p>
<ul>
<li>Run Configuration에서 설정된 JVM 옵션이 적용됩니다. 예: <code>-Xmx1024m</code> 같은 메모리 관련 설정.</li>
</ul>
</li>
<li><p><strong>환경 변수 적용</strong>:</p>
<ul>
<li>Run Configuration에서 정의한 환경 변수도 JVM에 전달됩니다.</li>
<li>예: <code>spring.profiles.active=dev</code></li>
</ul>
</li>
</ol>
<hr>
<h3 id="4-spring-boot-애플리케이션-초기화"><strong>4. Spring Boot 애플리케이션 초기화</strong></h3>
<p>Spring Boot 애플리케이션이 JVM 위에서 실행되며 초기화 과정을 거칩니다:</p>
<ol>
<li><p><strong>Spring 컨텍스트 초기화</strong>:</p>
<ul>
<li><code>ApplicationContext</code>가 생성되고, 모든 <strong>빈(Bean)</strong>이 초기화됩니다.</li>
<li><code>@Component</code>, <code>@Service</code>, <code>@Repository</code> 등의 애너테이션으로 정의된 클래스가 빈으로 등록됩니다.</li>
</ul>
</li>
<li><p><strong>외부 설정 로드</strong>:</p>
<ul>
<li><code>application.yml</code>, <code>application.properties</code>, 또는 환경 변수에서 설정 값을 읽어옵니다.</li>
<li>활성화된 프로파일(<code>spring.profiles.active</code>)에 따라 추가 설정을 병합합니다.</li>
</ul>
</li>
<li><p><strong>내장 웹 서버 실행</strong>:</p>
<ul>
<li>Spring Boot 프로젝트는 기본적으로 내장형 웹 서버(Tomcat, Jetty 등)를 사용합니다.</li>
<li>내장 웹 서버가 지정된 포트(기본값: 8080)에서 시작됩니다.</li>
</ul>
</li>
<li><p><strong>애플리케이션 실행 준비 완료</strong>:</p>
<ul>
<li>초기화가 완료되면 로그에 <code>Started Application in [time] seconds</code> 메시지가 표시됩니다.</li>
<li>이 시점에서 애플리케이션이 요청을 받을 준비가 됩니다.</li>
</ul>
</li>
</ol>
<hr>
<h3 id="5-intellij에서-로그-출력"><strong>5. IntelliJ에서 로그 출력</strong></h3>
<p>IntelliJ IDEA의 <strong>Run 창</strong>에 애플리케이션 로그가 출력됩니다:</p>
<ul>
<li><strong>Spring Boot 로고와 버전 정보</strong>.</li>
<li>로드된 설정 파일(<code>application.yml</code> 또는 <code>application.properties</code>).</li>
<li>초기화된 빈(Bean) 목록.</li>
<li>내장 웹 서버가 시작된 포트 정보.</li>
</ul>
<p>예:</p>
<pre><code class="language-plaintext">2024-11-22 10:00:00.000  INFO 12345 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http)
2024-11-22 10:00:00.000  INFO 12345 --- [           main] com.example.Application                  : Started Application in 5.123 seconds (JVM running for 5.678)</code></pre>
<hr>
<h3 id="6-애플리케이션-실행-중-동작"><strong>6. 애플리케이션 실행 중 동작</strong></h3>
<ul>
<li>애플리케이션이 실행되면서 <strong>HTTP 요청을 수신</strong>하고, Controller, Service, Repository 계층이 동작.</li>
<li>로컬에서 실행 중인 애플리케이션에 접근하려면 브라우저나 Postman 등을 사용하여 다음 URL에 접근:<pre><code>http://localhost:8080</code></pre></li>
</ul>
<hr>
<h3 id="7-정리"><strong>7. 정리</strong></h3>
<p>IntelliJ IDEA에서 <strong>Run 버튼</strong>을 누르면 아래 과정이 실행됩니다:</p>
<ol>
<li><strong>Run Configuration</strong>을 기반으로 애플리케이션 실행 설정 확인.</li>
<li><strong>Maven/Gradle 빌드</strong>로 소스 코드 컴파일 및 의존성 다운로드.</li>
<li><strong>JVM 실행</strong> 및 <code>main()</code> 메서드 실행.</li>
<li><strong>Spring Boot 초기화</strong>:<ul>
<li>빈 등록, 설정 파일 로드, 내장 웹 서버 시작.</li>
</ul>
</li>
<li><strong>애플리케이션 시작 및 로그 출력</strong>.</li>
</ol>
<hr>
<p>이 과정을 통해 로컬에서 Spring Boot 애플리케이션이 실행되고, 브라우저를 통해 접근할 수 있는 상태가 됩니다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링의 예외 처리 방법 이해하기(ExceptionHandler, ControllerAdvice 등)]]></title>
            <link>https://velog.io/@hyeondooori_/%EC%8A%A4%ED%94%84%EB%A7%81%EC%9D%98-%EC%98%88%EC%99%B8-%EC%B2%98%EB%A6%AC-%EB%B0%A9%EB%B2%95-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0ExceptionHandler-ControllerAdvice-%EB%93%B1</link>
            <guid>https://velog.io/@hyeondooori_/%EC%8A%A4%ED%94%84%EB%A7%81%EC%9D%98-%EC%98%88%EC%99%B8-%EC%B2%98%EB%A6%AC-%EB%B0%A9%EB%B2%95-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0ExceptionHandler-ControllerAdvice-%EB%93%B1</guid>
            <pubDate>Tue, 19 Nov 2024 10:27:04 GMT</pubDate>
            <description><![CDATA[<p>스프링의 예외 처리 방식을 이해하기 위래 먼저 스프링의 전체적 흐름을 이해해야한다.</p>
<hr>
<h1 id="스프링의-요청-처리-과정">스프링의 요청 처리 과정</h1>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/10ae3e0a-ced2-4f93-b00c-424f9ec212f1/image.png" alt="">
Spring이 요청에 대한 처리를 어떤 흐름으로 진행하는지에 대한 그림이다. </p>
<ol>
<li>클라이언트의 요청을 디스패처 서블릿이 받음. </li>
<li>요청 정보를 통해 요청을 위임할 컨트롤러 찾음</li>
<li>요청을 컨트롤러로 위임할 핸들러 어댑터를 찾아 전달함.</li>
<li>핸들러어댑터가 컨트롤러로 요청을 위임함.</li>
<li>비즈니스 로직을 처리함.</li>
<li>컨트롤러가 반환값을 처리함.</li>
<li>핸들러어댑터가 반환값을 처리함.</li>
<li>서버의 응답을 클라이언트로 반환함.</li>
</ol>
<ul>
<li>Dispatcher-Servlet
디스패처 서블릿의 dispatch는 &quot;보내다&quot;라는 뜻을 가지고 있다. 그리고 이러한 단어를 포함하는 디스패처 서블릿은 <mark>HTTP 프로토콜로 들어오는 모든 요청을 가장 먼저 받아 적합한 컨트롤러에 위임해주는 프론트 컨트롤러(front Controller)</mark>라고 정의할 수 있다.
클라이언트로부터 어떤 요청이 오면 톰캣(Tomcat)과 같은 서블릿 컨테이너가 요청을 받게된다. 그리고 이 모든 요청을 프론트 컨트롤러인 디스패처 서블릿이 가장 먼저 받게된다. 그러면 디스패처 서블릿은 공통작업을 먼저 처리한 후 해당 요청을 처리해야하는 컨트롤러를 찾아 작업을 위임한다.
여기서 프런트 컨트롤러(front controller)라는 용어가 사용되는데, Front Controller는 주로 서블릿 컨테이너의 제일 앞에서 서버로 들어오는 클라이언트의 모든 요청을 받아 처리해주는 컨트롤러로서, MVC 구조에서 함께 사용되는 디자인 패턴이다.
과거에는 모든 서블릿을 URL 매핑을 위해 web.xml에 모두 등록해주어야 했지만, dispatcher-servlet이 해당 어플리케이션으로 들어오는 모든 요청을 핸들링해주고 공통 작업을 처리면서 상당히 편리하게 이용할 수 있게 되었습니다. 우리는 컨트롤러를 구현해두기만 하면 디스패처 서블릿가 알아서 적합한 컨트롤러로 위임을 해주는 구조가 되었습니다.</li>
</ul>
<hr>
<p>❓ 만약 Controller에서 로직을 처리하던 중 예상치못한 에러가 발생한다면 어떻게될까?</p>
<h1 id="스프링의-기본적인-예외-처리-방법">스프링의 기본적인 예외 처리 방법</h1>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/6e8f9a2e-1f64-4108-b90d-861b1ca8a29e/image.png" alt="">
이와같은 컨트롤러의 getProduct에서 NoSuchElementFoundException 예외가 발생했다면 우리는 접속한 환경에 따라 다른 에러처리를 받게 된다. 만약 웹페이지로 접속했다면 Whitelabel Error Page를 반환받게된다.</p>
<p>Spring은 만들어질 때부터 에러처리를 위한 BasicErrorController를 구현해두었고, 스프링 부트는 예외가 발생하면 기본적으로 /error로 에러 요청을 다시 전달하도록 WAS 설정을 해두었다. </p>
<blockquote>
<p>WAS는 웹 서버와 웹 컨테이너가 합쳐진 형태로서 웹 서버 단독으로는 처리할 수 없는 데이터베이스의 조회나 다양한 로직 처리가 필요한 동적 컨텐츠를 제공한다. 
대표적인 WAS 종류: Tomcat
WAS에 대한 더욱 자세한 설명은! -&gt;<a href="https://velog.io/@hyeondooori_/JAR-WAR">CLICK!!</a></p>
</blockquote>
<p>그래서 별도의 설정이 없다면 예외 발생 시에 BasicErrorController로 에러 처리 요청이 전달된다. 참고로 이는 스프링 부트의 WebMvcAutoConfiguration을 통해 자동 설정이 되는 WAS의 설정이다. 
여기서 요청이 /error로 다시 전달된다는 부분에 주목해야한다. 일반적인 요청 흐름은 다음과 같이 진행된다.
<code>WAS(톰캣) -&gt; 필터 -&gt; 서블릿(디스패처 서블릿) -&gt; 인터셉터 -&gt; 컨트롤러</code>
그리고 컨트롤러 하위에서 예외가 발생했을 때, 별도의 예외 처리를 하지 않으면 WAS까지 에러가 전달된다. 그러면 WAS는 애플리케이션에서 처리를 못하는 예외라 exception이 올라왔다고 판단을 하고 대응 작업을 진행한다. 
<code>컨트롤러(예외발생) -&gt; 인터셉터 -&gt; 서블릿(디스패처 서블릿) -&gt; 필터 -&gt; WAS(톰캣)</code></p>
<p>WAS는 스프링 부트가 등록한 에러 설정(/error)에 맞게 요청을 전달하는데, 이러한 흐름을 총 정리하면 다음과 같다.
<code>WAS(톰캣) -&gt; 필터 -&gt; 서블릿(디스패처 서블릿) -&gt; 인터셉터 -&gt; 컨트롤러</code>
<code>-&gt; 컨트롤러(예외발생) -&gt; 인터셉터 -&gt; 서블릿(디스패처 서블릿) -&gt; 필터 -&gt; WAS(톰캣)</code>
<code>-&gt; WAS(톰캣) -&gt; 필터 -&gt; 서블릿(디스패처 서블릿) -&gt; 인터셉터 -&gt; 컨트롤러(BasicErrorController)</code></p>
<p>기본적인 에러 처리 방식은 <mark>결국 에러 컨트롤러를 한 번 더 호출</mark>하는 것이다. 그러므로 필터나 인터셉터가 다시 호출될 수 있는데, 이를 제어하기 위해서는 별도의 설정이 필요하다. </p>
<p>서블릿은 <code>dispatcherType</code>으로 요청의 종류를 구분하는데, 일반적인 요청은 <code>REQUEST</code>이며 에러 처리 요청은 <code>ERROR</code>이다. 
필터는 서블릿 기술이므로 <code>필터등록(FilterRegistrationBean)</code> 시에 호출될 <code>dispatcherType</code> 타입을 설정할 수 있고, 별도의 설정이 없다면 <code>REQUEST</code>일 경우에만 필터가 호출된다. 
하지만, 인터셉터는 스프링 기술이므로 dispatcherType을 설정할 수 없어 URI 패턴으로 처리가 필요하다. 스프링 부트에서는 WAS까지 직접 제어하게 되면서 이러한 WAS의 에러 설정까지 가능해졌다. 또한 이는 요청이 2번 생기는 것은 아니고, 1번의 요청이 2번 전달되는 것이다. 그러므로 클라이언트는 이러한 에러 처리 작업이 진행되었는지 알 수 없다. </p>
<h3 id="basicerrorcontroller">BasicErrorController</h3>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/63855eb9-34ac-45ea-aef2-b3575729de00/image.png" alt="">
BasicErrorController는 accept헤더에 따라 에러 페이지를 반환하거나 에러 메시지를 반환한다. 에러 경로는 기본적으로 /error로 정의되어있으며 properties에서 server.error.path로 변경할 수 있다.</p>
<p>errorHtml()과 errer()는 모두 getAttributeOptions를 호출해 반환할 에러 속성을 얻는데, 기본적으로 DefaultErrorAttributes로부터 반환할 정보를 가져온다. DefaultErrorAttributes는 전체 항목들에서 설정에 맞게 불필요한 속성들을 제거한다.</p>
<ul>
<li>timestamp: 에러가 발생한 시간</li>
<li>status: 에러의 http상태</li>
<li>error: 에러코드</li>
<li>path: 에러가 발생한 uri</li>
<li>exception: 최상위 예외 클래스의 이름(설정 필요)</li>
<li>message: 에러에 대한 내용(설정 필요)</li>
<li>errors: BindingException에 의해 생긴 에러 목록(설정 필요)</li>
<li>trace: 에러 스택 트레이스(설정 필요)
<img src="https://velog.velcdn.com/images/hyeondooori_/post/8ec61c93-7977-4454-ade0-b9bdeb880b1c/image.png" alt=""></li>
</ul>
<p>위는 기본 설정으로 받는 에러 응답이다. 나름 잘 갖추어져 있지만 클라이언트 입장에서 유용하지 못하다. 클라이언트는 &quot;Item with id 5000 not found&quot;라는 메시지와 함께 404 status로 에러 응답을 받으면 훨씬 유용할 것이다.
다음과 같이 properties를 통해 에러 응답을 조정할 수 있다. 물론 운영 환경에서 구현이 노출되는 trace는 제공하지 않는 것이 좋다.</p>
<blockquote>
<p>참고로 SpringBoot 2.3 이전에는 message를 기본적으로 제공했었지만, SpringBoot 2.3부터는 클라이언트에게 너무 많은 정보가 노출되는 것을 방지하기 위해 기본적으로 제공하지 않게 되었다. </p>
</blockquote>
<h1 id="스프링이-제공하는-다양한-예외-처리-방법">스프링이 제공하는 다양한 예외 처리 방법</h1>
<p>JAVA에서는 예외 처리를 위해 try-catch를 사용해야 하지만 try-catch를 모든 코드에 붙이는 것은 비효율적이다. Spring은 에러 처리라는 공통 관심사(cross-cutting concerns)를 메인 로직으로부터 분리하는 다양한 예외 처리 방식을 고안했고, 예외 처리 전략을 추상화한 <code>HandlerExceptionResolver</code> 인터페이스를 만들었다. (전략 패턴이 사용된 것이다.)</p>
<p>대부분의 HandlerExceptionResolver는 발생한 Exception을 catch하고 HTTP상태나 응답메시지 등을 설정한다. 그래서 WAS입장에서는 해당 요청이 정상적인 응답인 것으로 인식되며, 위에서 설명한 복잡한 WAS의 에러 전달이 진행되지 않는다. </p>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/c83c8937-6df7-4e6c-a615-f943d10a5426/image.png" alt="">
위의 Object 타입인 handler는 예외가 발생한 컨트롤러 객체이다. 예외가 던져지면 디스패처 서블릿까지 전달되는데, 적합한 예외처리를 위해 HandlerExceptionResolver구현체들을 빈으로 등록해서 관리한다. 그리고 적용 가능한 구현체를 찾아 예외를 처리하는데, 우선순위대로 아래의** 4가지 구현체**들이 빈으로 등록되어있다. </p>
<ul>
<li><code>DefaultErrorAttributes</code>: 에러 속성을 저장하며 직접 예외를 처리하지 않는다.</li>
<li><code>ExceptionHandlerExceptionResolver</code>: 에러 응답을 위한 Controller나 ControllerAdvice에 있는 ExceptionHandler를 처리한다. </li>
<li><code>ResponseStatusExceptionResolver</code>: Http상태코드를 지정하는 @ResponseStatus 또는 ResponseStatusException을 처리한다.</li>
<li><code>DefaultHandlerExceptionResolver</code>: 스프링 내부의 기본 예외들을 처리한다.</li>
</ul>
<hr>
<p>Spring에서 <code>ExceptionResolver</code>를 동작시켜 에러를 처리할 때 사용하는 도구들을 살펴보자.
<code>ExceptionHandler</code>, <code>RestControllerAdvice</code>의 두 개가 있다.
<code>ResponseStatusException</code>의 한계점들을 보완해준 방식들이라 이것들을 사용한다.</p>
<blockquote>
<p>ResponseStatusException의 한계점</p>
</blockquote>
<ul>
<li>직접 예외처리를 프로그래밍하므로 일관된 예외처리가 어려움</li>
<li>예외처리 코드가 중복될 수 있음</li>
<li>Spring 내부의 예외를 처리하는 것이 어려움</li>
<li>예외가 WAS까지 전달되고, WAS의 에러 요청 전달이 진행됨</li>
</ul>
<h3 id="exceptionhandler">@ExceptionHandler</h3>
<ul>
<li>매우 유연한 에러처리 제공</li>
<li>에러 응답(payload)자유롭게 다룰 수 있음.</li>
<li>컨트롤러의 메소드에 해당 어노테이션을 적용할 수 있는데, 이는 전역적으로 사용할 수 없다.
전역적인 사용을 위해서는 @RestControllerAdvice 또는 @ControllerAdvice 애노테이션을 사용해야 한다.</li>
</ul>
<h3 id="restcontrolleradvice">@RestControllerAdvice</h3>
<ul>
<li>Spring 4.3부터 제공하는 애노테이션</li>
<li>@ExceptionHandler를 전역적으로 사용할 수 있도록 도와줌</li>
<li>@ControllerAdvice와의 차이점은 에러응답을 JSON으로 내려준다는 것이다.</li>
<li>사용방법: 애노테이션을 적용해 전역적으로 에러를 핸들링하는 class를 만들어 사용한다.(에러처리 위임)</li>
</ul>
<hr>
<p>출처: <a href="https://mangkyu.tistory.com/18">https://mangkyu.tistory.com/18</a> [MangKyu&#39;s Diary:티스토리]
출처: <a href="https://mangkyu.tistory.com/204">https://mangkyu.tistory.com/204</a> [MangKyu&#39;s Diary:티스토리]</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[docker] 도커 컨테이너 실행 제대로 이해하기]]></title>
            <link>https://velog.io/@hyeondooori_/docker</link>
            <guid>https://velog.io/@hyeondooori_/docker</guid>
            <pubDate>Sun, 03 Nov 2024 08:09:25 GMT</pubDate>
            <description><![CDATA[<h2 id="nodejs-앱-만들기">Node.js 앱 만들기</h2>
<hr>
<p>먼저, </p>
<h3 id="nodejs-앱이란">Node.js 앱이란?</h3>
<blockquote>
<p>Node.js 앱은 Node.js 런타임 환경에서 실행되는 애플리케이션이다. Node.js는 자바스크립트(JavaScript)코드를 서버 측에서도 실행할 수 있게 만들어주며, 주로 웹 서버, API서버, 또는 백엔드 애플리케이션을 개발하는 데 사용된다. </p>
</blockquote>
<p>일반적으로 Node.js앱은 자바스크립트코드, 패키지관리(package.json), 주요 프레임워크(Express.js)로 구성된다.</p>
<ol>
<li>JavaScript 코드
Node.js는 자바스크립트를 사용하여 서버 기능을 구현할 수 있다. 기본적인 웹 서버나 데이터베이스 연결, 파일 시스템 제어 등을 모두 자바스크립트로 작성할 수 있다.</li>
<li>패키지 관리
Node.js앱에서는 npm(Node Package Manager)을 통해 다양한 라이브러리와 모듈을 설치하고 관리한다. package.json파일에 의존성 정보가 저장되어 있어 필요한 라이브러리들을 손쉽게 설치하고 관리할 수 있다.</li>
<li>주요 프레임워크
Node.js 앱에서 가장 널리 사용되는 웹 프레임워크는 Express.js이다. Express는 웹 서비스나 RESTful API를 쉽게 구축할 수 있도록 해준다. 예를 들어, Express를 사용하면 클라이언트의 요청을 처리하고 응답을 반환하는 웹 서버를 간단하게 만들 수 있다. </li>
</ol>
<hr>
<h3 id="nodejs-앱을-만드는데-필요한-두-가지-파일">Node.js 앱을 만드는데 필요한 두 가지 파일</h3>
<ol>
<li><p><code>package.json</code>
프로젝트의 정보와 프로젝트에서 사용 중인 패키지의 의존성을 관리하는 곳이다. 
<code>npm init</code> 명령어를 사용해 만들 수 있다.
(Node.js와 WSL설치가 되어있어야한다!)
<img src="https://velog.velcdn.com/images/hyeondooori_/post/7c58f90d-acff-42a7-883d-d0537ed17fbf/image.png" alt="">
dependencies의 express부분을 추가한 이유는 Node.js를 더욱 쉽고 유용하게 사용할 수 있도록 하기 위함이다.
Javascript와 jQuery의 관계처럼 Node.js의 API를 단순화하고 새로운 기능을 추가해준다.</p>
</li>
<li><p><code>server.js</code>
Node.js에서 진입점이 되는 파일이다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/574fef5b-8ff7-49df-a433-bf3921c732c1/image.png" alt="">
<code>const express = require(&#39;express&#39;);</code> : express 모듈 불러온다.
<code>const PORT = 8080;</code> : express 서버를 위한 포트 설정
<code>const app = express();</code> : 새로운 express어플 생성
<code>app.get(&#39;/&#39;,(req,res)=&gt;{
 res.send(&quot;Hello World&quot;)
});</code> 
: &quot;/&quot; 이 경로로 요청이 오면 Hello World를 결과값으로 전달
<code>app.listen(PORT);</code>: 해당 포트에서 HTTP서버를 시작</p>
</li>
</ol>
<p>이렇게 두 파일을 이용해 기본적인 Node.js애플리케이션을 완성했다. 
이제 이 Node.js 앱을 도커 환경에서 실행하기 위해서 도커와 관련된 부분을 만들어야한다.</p>
<hr>
<h3 id="dockerfile-작성하기">Dockerfile 작성하기</h3>
<p>Node.js앱을 도커 환경에서 실행하려면 먼저 이미지를 생성하고 그 이미지를 이용해서 컨테이너를 실행한 후 그 컨테이너 안에서 Nodejs앱을 실행해야한다. 그래서 그 이미지를 먼저 생성하려면 Dockerfile을 먼저 작성해야한다. 
<img src="https://velog.velcdn.com/images/hyeondooori_/post/fc3fcd54-6bd8-4ffb-96fa-b5d2e0c68a40/image.png" alt="Dockerfile만들기 내용 복습 - FROM,RUN,CMD 개념">
<img src="https://velog.velcdn.com/images/hyeondooori_/post/2af05692-d521-4556-b008-c6fdf3cdf0ac/image.png" alt="도커파일,도커이미지,도커 컨테이너 간의 관계 도식화 그림">
FROM,RUN,CMD에 명시한 것 각각이 무엇인지는 밑에서 차차 설명할 예정이다. </p>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/c2308bee-2dbb-40bc-84d6-e99f3c0201ae/image.png" alt="Dockerfile 변경코드"></p>
<h4 id="alpine-베이스-이미지-대신-node이미지-사용하는-이유">alpine 베이스 이미지 대신 node이미지 사용하는 이유?</h4>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/628415e1-25c9-4daa-a3db-b78e49330826/image.png" alt="Dockerfile에서 FROM을 apline으로 변경한 버전 - 오류발생!">
alpine으로 하면 npm not found 오류가 나는 것을 확인할 수 있다.
그 이유는 alpine이미지는 가장 최소한의 경량화된 파일들이 들어있기에 npm을 위한 파일이 들어있지 않아서 RUN부분에 npm install을 할 수가 없다.
알파인 이미지의 사이즈는 5MB정도밖에 안된다.
그래서 이미지 사이즈가 더 큰 node를 사용해야 오류가 발생하지 않는다.</p>
<h4 id="npm-install">npm install?</h4>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/4a310a77-8492-4bad-821e-611f24143be8/image.png" alt=""></p>
<ul>
<li>npm은 Node.js로 만들어진 모듈을 웹에서 받아서 설치하고 관리해주는 프로그램이다.</li>
<li>npm install은 package.json에 적혀있는 종속성들을 웹에서 자동으로 다운받아 설치해주는 명령어이다. <ul>
<li>그러니까, package.json내부의 dependencies를 설치해주는 역할!</li>
</ul>
</li>
<li>결론적으로는 현재 Node.js앱을 만들 때 필요한 모듈들을 다운받아 설치하는 역할을 한다.</li>
</ul>
<h4 id="cmd-안의-nodesever-js">CMD 안의 &quot;node&quot;,&quot;sever. js&quot;?</h4>
<ul>
<li>노드 웹 서버를 작동시키려면 &quot;node&quot;와 &quot;엔트리 파일 이름&quot;을 입력해야 한다.</li>
</ul>
<hr>
<p>이제, 만들어둔 Dockerfile을 <code>docker build .</code>으로 실행시켜보면 오류가 나는 것을 확인할 수 있다. 
<img src="https://velog.velcdn.com/images/hyeondooori_/post/9d3e8e04-6a12-4167-9fbd-53ceb7b85c98/image.png" alt=""></p>
<p>오류 화면을 보면, RUN npm install 부분에서 오류가 발생한다.
원인이 무엇일까?</p>
<hr>
<h3 id="이미지를-빌드할-때-packagejson-파일이-없다고-나오는-이유">이미지를 빌드할 때 package.json 파일이 없다고 나오는 이유</h3>
<p>지금 우리는 docker의 이미지를 만들고 있다. 이미지를 만들 때에는 Node 베이스 이미지를 통해 임시 컨테이너를 만든 다음에 임시 컨테이너로 실제 사용할 이미지를 만드는 것이다. 베이스 이미지에는 파일 스냅샷이 존재한다. 그래서 그것을 하드디스크에 넣어주고, RUN에 있는 npm install을 하게 된다. 
RUN은 추가적으로 필요한 파일들을 다운로드 받는 역할을 한다. 
RUN에 쓴 npm install은 package.json파일에 명시된 모든 패키지를 설치하는 역할을 수행한다.
Node.js 프로젝트에서는 package.json 파일에 의존성 정보가 포함되어 있으며, npm install을 실행하면 해당 의존성들이 node_modules 폴더에 설치됩니다.</p>
<p>그러나, <strong>package.json이 컨테이너의 밖에 존재하여 파일을 보고 설치하는 것이 불가능하다.</strong></p>
<p>그 이유는 Node 베이스 이미지의 스냅샷이 임시 컨테이너의 하드디스크로 들어오게 되는데, Node 베이스 이미지의 스냅샷에는 두 파일이 없기 때문이다.</p>
<p>아래의 사진을 보면 이 말을 더욱 잘 이해할 수 있다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/9b364380-1933-4d3e-baef-a161604ac851/image.png" alt="package.json이 임시컨테이너 밖에 존재하는!"></p>
<hr>
<p>그렇다면, 이 문제를 어떻게 해결해야할까?</p>
<h3 id="copy를-이용해-packagejson을-컨테이너-안으로-넣어주기">COPY를 이용해 Package.json을 컨테이너 안으로 넣어주기</h3>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/9be63561-eac0-4550-ac0f-010fe5174922/image.png" alt="">
<img src="https://velog.velcdn.com/images/hyeondooori_/post/1ddd5d8b-5284-4513-8497-094b82a9e17f/image.png" alt=""></p>
<p>Dockerfile내부에 COPY를 적은 뒤,
<code>docker build -t &lt;docker account&gt;/&lt;앱 이름&gt;</code>을 통해 실행시켜보았다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/e9c0d041-5c71-4f09-b31a-e2b563ccd43a/image.png" alt="">
빌드가 잘 되는 것을 확인할 수 있다.</p>
<p>이제 run을 시켜볼건데, 잘 되는지 확인해보기 위해
<img src="https://velog.velcdn.com/images/hyeondooori_/post/5a8d11ff-aa9f-46f4-b83e-acc7a6bcd92c/image.png" alt="">
이렇게 콘솔 로그를 server.js에 넣어주었다.
그리고 다시 빌드시킨 후 run해보면,
<img src="https://velog.velcdn.com/images/hyeondooori_/post/78032fe2-2122-4036-a594-27b00669003c/image.png" alt="">
이렇게 잘 실행되는 것을 확인할 수 있다.</p>
<p>하지만, localhost:8080주소로 가보면, 실행이 되지 않은 것을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/159c4875-642f-48e4-b3ce-b932ae780216/image.png" alt=""></p>
<hr>
<h3 id="생성한-이미지로-애플리케이션-실행-시-접근이-안-되는-이유포트-매핑">생성한 이미지로 애플리케이션 실행 시 접근이 안 되는 이유(포트 매핑)</h3>
<p>위에서 컨테이너를 실행하기 위해 사용한 명령어는 다음과 같다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/f569387f-d38e-424f-af9e-ac4258d84e6b/image.png" alt=""></p>
<p>여기에서 추가해야할 부분이 있다. 
<img src="https://velog.velcdn.com/images/hyeondooori_/post/da59d1a3-9c83-40de-bc65-5730fffbe1d8/image.png" alt=""></p>
<p>이미지를 만들 때 로컬에 있던 파일(package.json)등을 컨테이너에 복사해주어야했다. 
그것과 유사하게 네트워크도 로컬 네트워크에 있던 것을 컨테이너 내부에 있는 네트워크에 연결을 시켜주어야한다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/29c01ac1-4387-4f4c-8718-357cfdd9b2dc/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/c6777ba0-9efd-4ad0-bc91-9b65dbf98cec/image.png" alt="run -p 명령어 실행화면">
브라우저에서 5001번 포트로 접속하고, 컨테이너 내부에서 컨테이너 속 8080포트로 접근하는 방식이다. 
<img src="https://velog.velcdn.com/images/hyeondooori_/post/15027e30-0951-450b-8581-5d58fd044f2c/image.png" alt="포트 접근방식 도식화 그림"></p>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/c4991f11-4b1b-4267-af6b-b302a6f38dca/image.png" alt="localhost:5001 실행화면">
이제 hello world가 잘 보이는 것을 확인할 수 있다!</p>
<hr>
<h2 id="working-directory-명시해주기">WORKING DIRECTORY 명시해주기</h2>
<p>도커 파일에 WORKDIR이라는 부분을 추가해주어야 한다. </p>
<p>이미지 안에서 애플리케이션 소스 코드를 가지고 있을 디렉터리를 생성하는 것이다. 그리고 이 디렉터리가 애플리케이션에 working directory가 된다.</p>
<p>그런데, 왜 따로 working directory가 있어야할까?
<img src="https://velog.velcdn.com/images/hyeondooori_/post/3e556b4a-6f77-4d60-8dde-4876d518afd4/image.png" alt="">
현재 만들어놓은 이미지의 Root 디렉토리를 살펴보기 위해 
<code>docker run -it &lt;이미지이름&gt; sh</code>
명령어를 사용했고, 그 터미널 안에서 ls 명령어를 입력했다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/a4311192-c70f-4220-abc2-142ef369dea0/image.png" alt="">
여기 파일 중에서 COPY를 해서 컨테이너 안으로 들어온 것들은 다음과 같다.</p>
<ul>
<li>Dockerfile</li>
<li>package.json</li>
<li>package-lock.json</li>
<li>node_module
이렇게 workdir을 지정하지 않고 그냥 COPY할 때 생기는 문제점</li>
</ul>
<ol>
<li>원래 이미지에 있던 파일과 이름이 같다면?
예를 들어, 베이스 이미지에 이미 home이라는 폴더가 있고 COPY를 함으로써 새로 추가되는 폴더 중에 home이라는 폴더가 있다면 중복이 되므로 원래 있던 폴더가 덮어씌워져버린다.</li>
<li>모든 파일이 한 디렉토리 내부에 들어가게 되어 정돈이 되어있지 않아 식별이 어렵다.</li>
</ol>
<p>그래서 모든 어플리케이션을 위한 소스들은 WORK 디렉토리를 따로 만들어서 보관한다.</p>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/6b5a3655-6f5c-4556-8fa2-7a46ce16e0d0/image.png" alt="WORKDIR 추가된 Dockerfile 코드">
Dockerfile에 WORKDIR을 추가시켰다.</p>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/58f3fa0c-ef1e-4e01-b542-ebb4b9641e68/image.png" alt="build화면">
<img src="https://velog.velcdn.com/images/hyeondooori_/post/b6d2bb03-52e7-4a08-a75c-624385349063/image.png" alt="디렉토리 내부 확인">
처음 ls를 실행한 결과에 COPY된 것들만 존재한다. 그 이유는 WORKDIR을 지정하면 root가 아니라 WORKDIR로 가게 되기 때문이다. 따라서 처음 ls실행환경이 WORKDIR로 지정했던 /usr/src/app인 것이다.
그 후, <code>cd /</code>를 통해 root로 가서 ls를 치면, 원래 존재하는 다른 디렉토리들이 있는 것을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/6d8e8862-cc0f-4c85-8ef5-f0f5565c059d/image.png" alt=""></p>
<hr>
<h3 id="애플리케이션-소스-변경-시-재빌드-문제점">애플리케이션 소스 변경 시 재빌드 문제점</h3>
<p>어플리케이션을 만들다보면 소스 코드를 계속 변경시켜주어야하며 그에따라 변경된 부분을 확인하면서 개발을 해나가야한다. 그래서 도커를 이용해 어떻게 실시간으로 소스가 반영되게 하는지 알아본다.</p>
<p>현재까지 만든 어플을 소스 변경 시 변경된 소스를 반영시켜주기 위해서는 
현재까지 도커 컨테이너로 어플을 실행한 순서를 보면
<img src="https://velog.velcdn.com/images/hyeondooori_/post/e3fe9ec9-be96-438d-a037-9f1a72fa8648/image.png" alt="">
<img src="https://velog.velcdn.com/images/hyeondooori_/post/53dd30e1-f275-41db-9add-e4be5907c959/image.png" alt=""></p>
<p>그러면, 이러한 비효율적인 부분을 어떻게 해결할까?</p>
<hr>
<h3 id="애플리케이션-소스-변경으로-재-빌드-시-효율적으로-하는-법">애플리케이션 소스 변경으로 재 빌드 시 효율적으로 하는 법</h3>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/472c6f2b-d0c4-4388-8ad2-28e2c635662c/image.png" alt="">
위의 도표를 보면, 완성본 Dockerfile에는 RUN 위에 COPY package.json ./이 하나가 추가되고 원래의 COPY가 RUN 아래로 내려갔다.</p>
<p>그 이유는 무엇일까?
npm install할 때 불필요한 다운로드를 피하기 위해서이다.
코드 변경이 있을 때 문제점) COPY ./ ./ 명령어로 모든 파일을 복사하게 되면, 작은 소스 코드 변경이 있어도 Docker는 npm install 단계 이전의 모든 캐시를 무효화하고, 모듈을 다시 다운로드하게 된다.</p>
<p>그래서 npm install 단계 전에는 의존성파일(package.json)만 COPY해서 필요한 모듈만 설치하고, 그 이후에 다른 파일들을 다시 COPY해서 Docker이미지가 효율적으로 빌드되도록 한다.</p>
<p>이렇게 함으로써 코드 수정 시 npm install 이전 단계를 다시 실행하지 않아도 되며, 불필요한 다운로드를 방지할 수 있다.</p>
<hr>
<h3 id="docker-volume">Docker Volume</h3>
<p>이제는 npm install 전에 package.json만 따로 COPY 해주어서 모든 캐시를 무효화하고 모듈을 다시 다운로드 하는 일을 없앴다. </p>
<p>하지만 아직도 소스를 변경할 때마다 변경된 소스 부분은 COPY한 후 이미지를 다시 빌드를 해주고 컨테이너를 다시 실행해주어야지 변경된 소스가 화면에 반영이 된다.</p>
<p>이러한 작업은 너무나 시간 소요가 크며 이미지도 너무나 많이 빌드하게 된다. </p>
<p>이러한 문제점을 해결하기 위해!!! Volume을 사용한다.</p>
<p>💻 <strong>Docker Volume?</strong>
지금까지는 컨테이너에 소스 코드를 COPY로 넣어주었다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/2699b02d-7eed-43c8-a810-4764dd10a0ec/image.png" alt="">
COPY는 호스트 디렉터리에 있는 파일을 도커 컨테이너네 복사하는 것이고, 볼륨은 도커 컨테이너에서 호스트 디렉터리에 있는 파일들을 매핑해서 사용한다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/d5ef6909-0067-432b-9e43-33d3f5cfabfb/image.png" alt=""></p>
<p>Volume을 사용하여 어플리케이션을 실행해보려면,
<code>docker run -p 5000:8080 -v /usr/src/app/node_modules -v $(pwd):/usr/src/app &lt;이미지 아이디&gt;</code></p>
<ul>
<li><code>-p 5000:8080</code>: 호스트의 5000번 포트를 컨테이너의 8080포트에 매핑한다.</li>
<li><code>-v /usr/src/app/node_modules</code>: node_modules 폴더를 호스트에 공유하지 않고, 컨테이너 내부에만 유지시킨다. <ul>
<li>호스트 시스템에는 node_modules폴더를 포함하지 않는 이유는 컨테이너 내부에서만 필요한 의존성 파일을 관리하려는 것이다. 이는 호환성 문제를 줄이고, Docker의 독립적 환경 내에서 의존성을 유지할 수 있도록 도와준다.</li>
</ul>
</li>
<li><code>-v $(pwd):/usr/src/app</code>: 현재 작업 디렉토리(<code>$(pwd)</code>)를 <code>/usr/src/app</code>경로에 매핑하여 소스 코드 파일이 컨테이너와 동기화되도록 한다.</li>
</ul>
<hr>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/caf5369d-682b-48e0-a8b8-80eb7644159b/image.png" alt="">
일단, &quot;Hello World&quot; 출력하는 것에서 &quot;안녕하세요.&quot;로 소스코드를 변경했다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/0b65dac4-0160-499e-be15-8bd182ec07ad/image.png" alt="">
재빌드 하지 않고 볼륨도 사용하지 않고 서버 실행시키면, 
<img src="https://velog.velcdn.com/images/hyeondooori_/post/31d137d8-695b-4096-9bf5-0b6f58acb097/image.png" alt="">
이렇게 기존대로 실행이 된다. </p>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/3e78187c-30f1-4691-b809-330b2596481a/image.png" alt="">
재빌드 없이, 도커 볼륨을 이용해서 실행해보았다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/673b0cc3-210e-4f22-9b91-d74e763772d8/image.png" alt="">
변경한 코드가 반영된 것을 확인할 수 있다!!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[docker] Dockerfile]]></title>
            <link>https://velog.io/@hyeondooori_/docker-Dockerfile</link>
            <guid>https://velog.io/@hyeondooori_/docker-Dockerfile</guid>
            <pubDate>Sun, 27 Oct 2024 07:13:51 GMT</pubDate>
            <description><![CDATA[<p>도커 이미지를 도커 허브에 있던 것 말고, </p>
<ol>
<li>직접 생성해서도 사용할 수 있고</li>
<li>도커 허브에 올려서 공유할 수도 있다.</li>
</ol>
<hr>
<h2 id="간단한-도커-이미지-복습">간단한 도커 이미지 복습</h2>
<ol>
<li>도커 이미지는 컨테이너를 만들기 위해 필요한 설정이나 종속성들을 갖고있는 소프트웨어 패키지이다. </li>
<li>지금까지 해왔듯이 도커 이미지는 Dockerhub에 이미 다른 사람들이 만들어놓은 것을 이용할 수도 있으며, 직접 도커 이미지를 만들어서 사용할 수도 있고 직접 만든 것을 Dockerhub에 업로드할 수도 있다.</li>
</ol>
<p>도커 이미지를 이용해 도커 컨테이너 생성
<code>docker create &lt;이미지이름&gt;</code></p>
<hr>
<h2 id="도커-이미지-생성-순서">도커 이미지 생성 순서</h2>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/ab9028e1-5634-4d1b-8232-6fc2049c5359/image.png" alt=""></p>
<hr>
<h2 id="dockerfile-만드는-법">Dockerfile 만드는 법</h2>
<blockquote>
<p><code>도커파일(Docker file)</code>이란?
도커 이미지를 만들기 위한 설정 파일
컨테이너가 어떻게 행동해야하는지에 대한 설정들을 정의해주는 곳이다. </p>
</blockquote>
<p>도커 이미지를 만들기 위한 설정 파일이므로, 도커 이미지가 필요한 것이 무엇인지를 생각해야한다. 도커 이미지는 시작 시 실행 될 명령어와 파일 스냅샷으로 구성되어있으므로, 이 두 가지를 만들어야 한다. </p>
<p>도커 파일 만드는 순서</p>
<ol>
<li>베이스 이미지 명시(파일 스냅샷에 해당)</li>
<li>추가적으로 필요한 파일을 다운 받기 위한 몇 가지 명령어를 명시(파일 스냅샷에 해당)</li>
<li>컨테이너 시작 시 실행 될 명령어를 명시해준다. (시작시 실행 될 명령어에 해당)</li>
</ol>
<h3 id="베이스-이미지는-무엇인가">베이스 이미지는 무엇인가?</h3>
<p>도커 이미지는 여러 개의 레이어들로 되어있다. 그 중에서 베이스 이미지는 이 이미지의 기반이 되는 부분이다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/f73dba6a-41ae-4558-94db-95c64822e134/image.png" alt=""></p>
<hr>
<h2 id="실습---hello문구-출력">실습 - Hello문구 출력</h2>
<h3 id="dockerfile작성">Dockerfile작성</h3>
<ol>
<li>도커 파일을 만들 폴더 하나 만들기 ex) dockerfile-folder</li>
<li>방금 생성한 도커 파일 폴더를 에디터를 이용해서 실행(Visual Studio Code 추천)</li>
<li>파일 하나를 생성, 이름은 dockerfile</li>
<li>그 안에 먼저 어떻게 진행해 나갈지 기본적 토대 명시</li>
</ol>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/904cd07b-6540-42da-9d4f-95f4910ca12d/image.png" alt=""></p>
<p>FROM RUN CMD 등은 도커 서버에게 무엇을 하라고 알려주는 것이다.</p>
<p><strong>FROM</strong>
이미지 생성 시 기반이 되는 이미지 레이어
<code>&lt;이미지이름&gt;:&lt;태그&gt;</code> 형식으로 작성
태그를 안 붙이면 자동적으로 가장 최신 것으로 다운 받음
ex) ubuntu:14.04</p>
<p><strong>RUN</strong>
도커 이미지가 생성되기 전에 수행할 쉘 명령어</p>
<p><strong>CMD</strong>
컨테이너가 시작되었을 때
실행할 실행 파일 또는 셸 스크립트이다.
해당 명령어는 DockerFile 내 1회만 쓸 수 있다.</p>
<ol start="5">
<li>이제 베이스 이미지부터 실제 값으로 추가해주기.</li>
<li>베이스 이미지는 ubuntu를 써도 되고 centos등을 써도 되지만 hello를 출력하는 기능은 굳이 사이즈가 큰 베이스 이미지를 쓸 필요가 없기에 사이즈가 작은 alpine베이스 이미지를 사용한다.</li>
<li>hello문자를 출력해주기 위해 echo를 사용해야하는데 이미 apline안에 echo를 사용하게 할 수 있는 파일이 있기에 RUN부분은 생략함.</li>
<li>마지막으로 컨테이너 시작 시 실행될 명령어 echo hello를 적어준다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/cbef4349-af2b-4dd4-a7d5-bce4deb4eef1/image.png" alt=""></li>
</ol>
<hr>
<h3 id="도커-클라이언트-도커-서버-이미지-생성">도커 클라이언트, 도커 서버, 이미지 생성</h3>
<p>도커 파일에 입력된 것들이 도커 클라이언트에 전달되어서 도커 서버가 인식하게 해야한다.
그렇게 하기 위해서 <code>docker build ./</code> 또는 <code>docker build .</code></p>
<p>build명령어는 </p>
<ul>
<li>해당 디렉토리 내에서 dockerfile이라는 파일을 찾아서 도커 클라이언트에 전달시켜준다.</li>
<li>docker build 뒤에 <code>./</code>와 <code>.</code>는 둘 다 현재 디렉토리를 가리킨다.</li>
</ul>
<p>그래서 <code>docker build .</code>을 해보면
<img src="https://velog.velcdn.com/images/hyeondooori_/post/373ea07c-8a35-4329-a426-64d3ae94a611/image.png" alt=""></p>
<h4 id="build-과정-설명">build 과정 설명</h4>
<p>이미지를 빌드할 때 Docker는 Dockerfile을 읽고 각 명령어에 따라 레이어(layer)를 쌓아 이미지를 생성한다. 
위 사진의 빌드 로그에 나타난 주요 단계는 다음과 같다.</p>
<ol>
<li><code>load build definition from Dockerfile</code>
Docker가 현재 디렉터리에서 Dockerfile을 찾고, 이를 읽어서 빌드를 시작한다. </li>
<li><code>transferring Dockerfile</code>
Dockerfile의 내용을 Docker데몬에 전송하여 빌드를 준비한다. 여기서 264B는 Dockerfile의 크기이다.</li>
<li><code>load metadata for docker.io/library/alpine</code>
<code>FROM alpine</code> 명령에 따라, <code>alpine</code>이미지를 기반으로 사용한다. 여기서는 Docker가 최신 버전(apline:latest)의 메타데이터를 가져온다.</li>
<li><code>load .dockerignore</code>
Docker는 .dockerignore 파일을 로드하여 빌드 과정에서 제외할 파일이나 폴더를 확인한다. <code>.dockerignore</code>파일에 지정된 내용은 빌드 시 제외된다.</li>
<li><code>transferring context</code>
현재 디렉터리의 모든 내용을 Docker 데몬에 전송한다. 이때 전송된 데이터의 크기는 2B로 매우 작다. 컨텍스트(context)는 Docker가 빌드할 때 필요로하는 파일 및 폴더의 집합이다. </li>
<li><code>CACHED[1/1]FROM docker.io/library/alpine</code>
캐시된 alpine이미지를 사용하여 빌드를 빠르게 처리한다. 캐시된 레이어가 있으면 이를 재사용하여 빌드 시간을 단축!</li>
<li><code>exporting to image</code>
모든 명령을 처리한 후, 최종 이미지를 생성하여 저장</li>
<li><code>exporting layers / writing image</code>
빌드가 완료된 이미지를 저장하고, 이미지의 해시 값을 출력한다. 이 해시값은 이미지의 고유ID이다. </li>
</ol>
<p>결과적으로 이 빌드과정은 alpine기반 이미지 위에 CMD[&quot;echo&quot;,&quot;hello&quot;]명령을 추가하여 최종 Docker이미지를 생성하는 과정을 나타낸다.</p>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/4094dad7-7a59-4150-b437-12ce754f6275/image.png" alt="">
원래는 이런식으로 빌드 과정에서 각 레이어와 컨테이너 ID가 터미널에 표시되었었다. 그런데, 최근 Docker버전에서는 빌드 과정의 출력을 간소화하면서 세부적인 레이어 ID와 컨테이너 ID 대신 요약된 정보를 출력하도록 변경되었다. </p>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/0b7a0a8a-b0da-42f4-97bc-80f7f5bf2ad0/image.png" alt="">
ID를 확인하고싶다면, <code>docker images</code> 명령어를 통해서 확인할 수 있다.</p>
<p>결론적으로</p>
<ul>
<li>베이스 이미지에서 다른 종속성이나 새로운 커맨드를 추가할 때는 임시 컨테이너를 만든 후 그 컨테이너를 토대로 새로운 이미지를 만든다. 
그리고 그 임시 컨테이너는 지워준다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/1a18cf9d-706d-44ce-804a-62af0eac6743/image.png" alt=""></li>
</ul>
<hr>
<p>만든 도커 이미지를 실행하려면, docker images 명령어를 통해 확인한 이미지ID를 이용해야 한다. 
ex. <code>docker run b7a652f17135</code>
IP Address가 기억하기 힘들기에 Domain이름을 새로 만들어서 이용하는 것과 비슷하게 우리가 만드는 도커 이미지에 이름을 줄 수 있다.</p>
<p>이를 위해 도커 이미지에 이름을 주면, 그 이름으로 실행시킬 수 있다.</p>
<h3 id="도커-이미지에-이름-주는-방법">도커 이미지에 이름 주는 방법</h3>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/f7b18e6d-8405-4fba-89db-d21729ed60b3/image.png" alt=""></p>
<p>도커 이미지를 사실 아무렇게나 주어도 상관없다.
그런데, 사람들이 통상적으로 사용하는 규칙이 아래의 주황색 박스로 표현되어있다.</p>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/624250d5-bb9e-43cb-8242-25eb9a2f6e3e/image.png" alt="">
실제로 명령어를 사용해 보았을 때, 로그의 마지막에 naming to~ 로 이름설정이 잘 적용되었음을 확인할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/a05691d1-e591-4737-b6c3-86ad0260cf42/image.png" alt="">
설정한 이름을 기반으로 실행한 화면이다.
의도했던 대로 hello가 잘 출력된 것을 확인할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[docker] 도커로 레디스 실행해보기]]></title>
            <link>https://velog.io/@hyeondooori_/docker-%EB%8F%84%EC%BB%A4%EB%A1%9C-%EB%A0%88%EB%94%94%EC%8A%A4-%EC%8B%A4%ED%96%89%ED%95%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@hyeondooori_/docker-%EB%8F%84%EC%BB%A4%EB%A1%9C-%EB%A0%88%EB%94%94%EC%8A%A4-%EC%8B%A4%ED%96%89%ED%95%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Mon, 21 Oct 2024 03:18:31 GMT</pubDate>
            <description><![CDATA[<ol>
<li>레디스 서버 실행
첫 번째 터미널을 실행 후 다음 명령어를 입력한다.
<code>docker run redis</code></li>
<li>레디스 클라이언트 실행
첫 번째 터미널에서는 실행 후 다시 명령어를 입력할 수 없으니, 두 번째 터미널을 켜서 레디스 클라이언트를 작동시킨다.
<code>redis-cli</code></li>
<li>오류발생 🚨!! 무엇이 잘못된 것일까?</li>
</ol>
<hr>
<h3 id="현재-레디스-클라이언트와-서버-상황">현재 레디스 클라이언트와 서버 상황</h3>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/9c4cb43e-877f-4c91-b570-c67617077fdd/image.png" alt="">
레디스 클라이언트가 레디스 서버가 있는 컨테이너 밖에서 실행을 하려 하니 레디스 서버에 접근을 할 수가 없기에 레디스 클라이언트를 작동하려 할 때 에러가 발생한다.</p>
<p>답은 레디스 클라이언트도 컨테이너 안에서 실행을 시켜야 한다.</p>
<ol>
<li>먼저 이전과 똑같이 첫 번째 터미널을 킨 후, 레디스 서버를 작동시키자.</li>
<li>이미 실행중인 컨테이너에 명령어를 전달할 때 exec을 쓴다. 그러니 redis 서버가 실행 중인 컨테이너에 exec을 이용하여 redis 클라이언트도 실행해보아야 한다.
<code>docker exec -it &lt;컨테이너 아이디&gt; redis-cli</code>
여기에서 <code>-it</code>를 붙여줘야 명령어를 실행한 후 계속 명령어를 적을 수 있다.</li>
</ol>
<ul>
<li><code>-i</code> interactive 상호적인</li>
<li><code>-t</code> terminal 
<code>-i + -t = -it</code></li>
</ul>
<p>만약, <code>-it</code>가 없다면, 그냥 redis-cli를 켜기만 하고 밖으로 다시 나와버린다. 
<img src="https://velog.velcdn.com/images/hyeondooori_/post/5f59276d-0f03-4c4b-924d-3ca5f4f06248/image.png" alt=""></p>
<p>이 사진에서 보이다싶이, 윗 명령어(-it가 없는 명령어)에서는 바로 나와지는데, 밑에서는 클라이언트로 들어가진 상태가 유지되어 추가적인 명령어를 더 입력할 수 있는 것을 볼 수 있다.</p>
<hr>
<h3 id="컨테이너에-쉘-환경으로-접근">컨테이너에 쉘 환경으로 접근</h3>
<p>지금까지 실행 중인 컨테이너에 명령어를 전달할 때에는 
<code>docker exec -it &lt;컨테이너 아이디&gt; &lt;명령어&gt;</code>
이런식으로 명령어 하나마다, 이 코드가 필요했다.</p>
<p>이러한 문제점을 해결해주기 위해 컨테이너 안에 쉘이나 터미널 환경으로 접속을 해줄 수가 있다. 
➡ 마지막 명령어를 sh로 해주면 된다.</p>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/ad967736-e88f-472e-b935-3ff601722cfe/image.png" alt=""></p>
<ol>
<li>첫 번째 터미널을 실행한 후, alpine 이미지를 이용해 컨테이너를 실행한다.
<code>docker run apline ping localhost</code></li>
<li>그 후 exec을 이용하고 마지막 명령어 부분에 sh를 입력 후 컨테이너 안에서 터미널 환경을 구축한다.
<code>docker exec -it &lt;컨테이너 아이디&gt; sh</code></li>
<li>그 안에서 여러 가지 터미널에서 원래 할 수 있는 작동들을 해본다.
ex.</li>
</ol>
<ul>
<li><code>ls</code>  - 컨테이너 디렉토리에 있는 내용(디렉토리, 파일 확인)</li>
<li><code>touch new file</code> - 파일 생성</li>
<li><code>export hello=hi echo $hello</code>  -변수 생성 출력</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/db654c14-4264-4d39-9af6-8a616f9901ab/image.png" alt=""></p>
<p>📌 이 터미널 환경에서 나오려면 <code>Control+D</code> !!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[docker] run vs exec]]></title>
            <link>https://velog.io/@hyeondooori_/docker-run-vs-exec</link>
            <guid>https://velog.io/@hyeondooori_/docker-run-vs-exec</guid>
            <pubDate>Mon, 21 Oct 2024 02:41:40 GMT</pubDate>
            <description><![CDATA[<p><code>docker exec &lt;컨테이너 아이디&gt;</code>
이것은 이미 실행중인 컨테이너에 명령어를 전달해주는 역할을 한다.</p>
<ol>
<li>먼저 터미널 2개를 실행한다.</li>
<li>첫 번째 터미널에서 컨테이너 하나를 실행한다.
(docker alpine ping localhost)</li>
<li>두 번째 터미널에서 컨테이너가 잘 작동하고 있는지 확인하고 다른 명령어를 전달한다.
ex) docker exec &lt;컨테이너 아이디&gt; ls</li>
</ol>
<p>똑같은 결과를 내주는 것 : docker run &lt;이미지이름&gt;</p>
<ul>
<li>docker run은 새로 컨테이너를 만들어서 실행</li>
<li>docker exec은 이미 실행 중인 컨테이너에서 명령어를 전달
<img src="https://velog.velcdn.com/images/hyeondooori_/post/fcbe96ec-9d2a-4f04-99ac-4a91588b9615/image.png" alt="">
여기에서도 두 개의 터미널을 띄워서 한 터미널에 실행을 시킨다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/2bcc65cc-8767-473d-9dc4-b6631c2c7f3c/image.png" alt="">
그 실행 정보를 다른 터미널에서 보고, exec 실행여부를 확인해보았다. 디렉토리 정보가 잘 출력되는 것을 확인할 수 있다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[docker] 도커 컨테이너의 생명주기]]></title>
            <link>https://velog.io/@hyeondooori_/docker-%EB%8F%84%EC%BB%A4-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88%EC%9D%98-%EC%83%9D%EB%AA%85%EC%A3%BC%EA%B8%B0</link>
            <guid>https://velog.io/@hyeondooori_/docker-%EB%8F%84%EC%BB%A4-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88%EC%9D%98-%EC%83%9D%EB%AA%85%EC%A3%BC%EA%B8%B0</guid>
            <pubDate>Sun, 20 Oct 2024 13:33:41 GMT</pubDate>
            <description><![CDATA[<h2 id="생명주기">생명주기</h2>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/71f5e178-74e3-41ba-bf99-9b099d35a13d/image.png" alt=""></p>
<hr>
<h3 id="생성-시작-실행">생성, 시작, 실행</h3>
<p>지금까지는 docker run&lt;이미지 이름&gt;으로 컨테이너 생성,실행을 했는데, 이것을 docker create와 docker start로 쪼개서 볼 수 있다.</p>
<p><code>docker run</code>는 파일 스냅숏을 하드디스크로 옮기고, 시작 시 실행할 명령어를 컨테이너에서 실행시켜주는 두 역할을 모두 수행한다.
하지만, <code>docker create</code>는 파일 스냅샷만 하드디스크로 옮겨주고, <code>docker start</code>는 시작 시 실행할 명령어만 컨테이너 내에서 실행시켜준다. </p>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/4a8b035d-6daf-48a8-aab6-743ef4f36f6b/image.png" alt=""></p>
<hr>
<h3 id="중지">중지</h3>
<p><code>docker stop</code>과 <code>docker kill</code>로 중지할 수 있다.</p>
<p>stop과 kill은 둘 다 실행중인 컨테이너를 중지시키는 역할을 수행한다.
하지만, </p>
<ol>
<li>Stop은 Gracefully하게 중지시킨다.
자비롭게 그동안 하던 작업들을 완료하고 컨테이너를 중지시킨다.
예를 들어, 메시지를 보내고 있었다면, 보내던 메시지는 다 보내고 컨테이너를 중지시킨다.</li>
<li>Kill은 Stop과 달리 어떠한 것도 기다리지 않고 바로 컨테이너를 중지시킨다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/52993604-0b48-4ebe-bda1-b1fdd37e8b8f/image.png" alt="">
kill은 SIGTERM이 없다.</p>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/7f4ad847-c8f4-42c6-a90f-eaad4c85ba29/image.png" alt="">
먼저 실행시킨 뒤,
<img src="https://velog.velcdn.com/images/hyeondooori_/post/5c3ba73e-64b0-4fb5-b754-14cd1ff78546/image.png" alt="">
<code>docker ps</code>로 아이디나 이름을 알아내어 <code>docker stop &lt;NAMES&gt;</code>명령어를 쳤다.
바로 중지되지 않고, 특정 시간이 지난 후에 종료된다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/36ddb64f-2a82-4219-854a-941194638853/image.png" alt="">
<code>kill</code>은 명령어가 입력되는 순간 바로 종료된다.</p>
<hr>
<h3 id="삭제">삭제</h3>
<p><code>docker rm &lt;중지할 컨테이너 아이디/이름&gt;</code>
<img src="https://velog.velcdn.com/images/hyeondooori_/post/37d0a631-60a0-4b0a-ad08-162214381a68/image.png" alt="">
삭제된 것을 확인할 수 있다.</p>
<ul>
<li>실행중인 컨테이너는 먼저 중지한 후에 삭제가 가능하다.</li>
</ul>
<p>모든 컨테이너를 삭제하고싶다면?</p>
<pre><code>docker rm \`docker ps -a -q\`</code></pre><p><img src="https://velog.velcdn.com/images/hyeondooori_/post/a8fd80da-f6ed-49fa-9be6-fcce6260d30f/image.png" alt="">
윈도우의 powerShell에서는 백틱을 지원하지 않아서 $()로 수행했다.</p>
<p>이미지를 삭제하고 싶다면?
<code>docker rmi &lt;이미지 id&gt;</code></p>
<p>한 번에 사용하지 않는 컨테이너, 이미지, 네트워크 모두 삭제하고 싶다면?
<code>docker system prune</code></p>
<p>도커를 쓰지 않을 때 모두 정리하고싶다면, 위의 docker system prune 명령어를 사용하면 된다.
하지만 이것도 실행 중인 컨테이너에는 영향을 주지 않음!
<img src="https://velog.velcdn.com/images/hyeondooori_/post/ab4085d0-2a31-4d5a-bfcb-5f00377336b1/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[docker]컨테이너들 나열하기]]></title>
            <link>https://velog.io/@hyeondooori_/docker%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88%EB%93%A4-%EB%82%98%EC%97%B4%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@hyeondooori_/docker%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88%EB%93%A4-%EB%82%98%EC%97%B4%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 20 Oct 2024 12:46:01 GMT</pubDate>
            <description><![CDATA[<h3 id="docker-ps">docker ps</h3>
<p>ps: process status
<img src="https://velog.velcdn.com/images/hyeondooori_/post/6a61c391-aee3-44b9-9456-c223e1f80249/image.png" alt=""></p>
<hr>
<h3 id="실습">실습</h3>
<ol>
<li>2개의 Terminal을 작동시킨다.</li>
<li>첫 번째 Terminal에서 container 하나를 실행
(하지만 이때 컨테이너를 켰다가 바로 끄면 3번을 할 때 이미 프로세스가 꺼져있기 때문에 리스트에서 볼 수 없음.)</li>
<li>두 번째 Terminal에서 docker ps로 확인.</li>
<li>그러면 꺼져있는 container도 확인하고 싶다면?</li>
</ol>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/62e377a6-ebf8-4d31-9f56-90ad19ebea57/image.png" alt="">
<img src="https://velog.velcdn.com/images/hyeondooori_/post/5a9d8661-287a-4c82-9c2c-29fb5ae6280f/image.png" alt=""></p>
<ol>
<li>CONTAINER ID : 컨테이너의 고유한 아이디 해시값
실제로는 더욱 길지만, 일부분만 출력</li>
<li>IMAGE : 컨테이너 생성 시 사용한 도커 이미지</li>
<li>COMMAND : 컨테이너 시작 시 실행될 명령어</li>
<li>CREATED : 컨테이너가 생성된 시간</li>
<li>STATUS : 컨테이너의 상태
실행중은 UP, 종료는 Excited, 일시정지 Pause</li>
<li>PORTS : 컨테이너가 개방한 포트와 호스트에 연결한 포트
특별한 설정을 하지 않은 경우 출력되지 않음</li>
<li>NAMES : 컨테이너의 고유한 이름
컨테이너 생성 시 --name 옵션으로 이름 설정하지 않으면 도커 엔진이 임의로 형용사와 명사를 조합해 설정한다.
id와 마찬가지로 중복이 안되고 docker rename 명령어로 이름 변경 가능.
ex. <code>docker rename original-name changed-name</code></li>
</ol>
<hr>
<h3 id="원하는-항목만-보려면">원하는 항목만 보려면?</h3>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/b7d518fd-214e-4a54-9340-0d18f9f93795/image.png" alt="">
<img src="https://velog.velcdn.com/images/hyeondooori_/post/b02173a7-cb0a-4555-82ff-2358a7849c85/image.png" alt="">
이것 또한, 실행되고 있는 것만이 출력된다.</p>
<hr>
<h3 id="실행되고있지-않은-컨테이너도-모두-나열하고싶다면">실행되고있지 않은 컨테이너도 모두 나열하고싶다면?</h3>
<p><code>docker ps -a</code>
<code>-a</code>가 all을 의미한다.
이 명령어를 치면, 실행되고있지 않은 것이라도 출력된다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/3af05efa-5924-46bb-af1e-ca870b18b7f8/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[이미지 내부 파일 시스템 구조 보기]]></title>
            <link>https://velog.io/@hyeondooori_/%EC%9D%B4%EB%AF%B8%EC%A7%80-%EB%82%B4%EB%B6%80-%ED%8C%8C%EC%9D%BC-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EA%B5%AC%EC%A1%B0-%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@hyeondooori_/%EC%9D%B4%EB%AF%B8%EC%A7%80-%EB%82%B4%EB%B6%80-%ED%8C%8C%EC%9D%BC-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EA%B5%AC%EC%A1%B0-%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Sun, 20 Oct 2024 12:19:57 GMT</pubDate>
            <description><![CDATA[<hr>
<h3 id="docker-run-hello-world">docker run hello-world</h3>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/4c3ba75a-cba7-47cb-90ec-6852075f28a6/image.png" alt=""></p>
<ul>
<li>ls가 쓰여있는 자리는 원래 이미지가 가지고 있는 시작 명령어를 무시하고, 여기에 있는 커맨드를 실행하도록 하는 것이다.</li>
<li>ls커맨드는 현재 디렉터리의 파일 리스트를 표출한다.</li>
</ul>
<h4 id="작동-순서">작동 순서</h4>
<ol>
<li>도커 클라이언트에 명령어 입력 후 도커 서버로 보냄.</li>
<li>도커 서버에서 컨테이너를 위한 이미지가 이미 캐시되어있는지 확인</li>
<li>없으면 도커 허브에서 다운받아옴. 있다면 그 이미 가지고있는 이미지로 컨테이너 생성</li>
</ol>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/9f620b77-156e-41d4-9d8c-8339e07af5f9/image.png" alt="">
이 예시를 보면, 파일 리스트를 보여준 것을 확인할 수 있다.
로컬에는 alpine이라는 것이 없어서 도커 허브에서 Pull 해온 것을 확인할 수 있다. </p>
<p>설명</p>
<ol>
<li>Alpine 이미지를 이용해 컨테이너 생성</li>
<li>생성할 때 Alpine 이미지 안에 들어있던 파일 스냅샷들(bin,dev,etc 등등 ..)이 컨테이너 안에 있는 하드디스크로 다운로드됨.</li>
<li>이미지 이름 뒤에 다른 명령어를 더 붙여서 원래 이미지 안에 들어있는 기본 커맨드는 무시되고, ls 명령어가 실행됨.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/5b20c885-3933-4a35-857c-2c92c7d36b99/image.png" alt=""></li>
</ol>
<hr>
<h3 id="하지만-어떻게-alpine-이미지를-이용해-ls명령어를-실행시킬-수-있는거지">하지만, 어떻게 Alpine 이미지를 이용해 ls명령어를 실행시킬 수 있는거지?</h3>
<p>Alpine 이미지 파일 스냅샷 내부에 이미 ls를 사용 가능하게 하는 파일이 있다!</p>
<p>hello-world 이미지로는 ls 명령어를 사용할 수 없다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/aaccdf71-7d06-4e8b-8793-2f81bf753bcd/image.png" alt="">
설명을 보면, executable file not found.(실행할 수 있는 파일을 못찾음)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[도커와 기존의 가상화 기술과의 차이를 통한 컨테이너의 이해]]></title>
            <link>https://velog.io/@hyeondooori_/%EB%8F%84%EC%BB%A4%EC%99%80-%EA%B8%B0%EC%A1%B4%EC%9D%98-%EA%B0%80%EC%83%81%ED%99%94-%EA%B8%B0%EC%88%A0%EA%B3%BC%EC%9D%98-%EC%B0%A8%EC%9D%B4%EB%A5%BC-%ED%86%B5%ED%95%9C-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88%EC%9D%98-%EC%9D%B4%ED%95%B4</link>
            <guid>https://velog.io/@hyeondooori_/%EB%8F%84%EC%BB%A4%EC%99%80-%EA%B8%B0%EC%A1%B4%EC%9D%98-%EA%B0%80%EC%83%81%ED%99%94-%EA%B8%B0%EC%88%A0%EA%B3%BC%EC%9D%98-%EC%B0%A8%EC%9D%B4%EB%A5%BC-%ED%86%B5%ED%95%9C-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88%EC%9D%98-%EC%9D%B4%ED%95%B4</guid>
            <pubDate>Sat, 19 Oct 2024 08:27:21 GMT</pubDate>
            <description><![CDATA[<ul>
<li><p>가상화 기술이 나오기 전
한대의 서버를 하나의 용도로만 사용했다. 남는 서버공간은 그대로 방치!
하나의 서버에 하나의 운영체제, 하나의 프로그램만을 운영했기에 안정적이지만 비효율적이었다.</p>
</li>
<li><p>하이퍼바이저 기반의 가상화 출현 후
논리적으로 공간을 분할하여 VM이라는 독립적인 가상 환경의 서버를 이용한다.
<mark>하이퍼바이저는 호스트 시스템에서 다수의 게이트 OS를 구동할 수 있게 하는 소프트웨어이다.</mark> 그리고 하드웨어를 가상화하면서 하드웨어와 각각의 VM을 모니터링하는 중간관리자이다. </p>
</li>
</ul>
<blockquote>
<p>하이퍼바이저(Hipervisor)는 가상머신을 생성하고 관리하는 소프트웨어이다. VM을 통해 다양한 운영체제를 실행할 수 있다. 이때 호스트는 OS 하이퍼바이저를 통해 VM들이 CPU, 메모리, 저장장치같은 하드웨어 자원을 사용할 수 있도록 지원한다. </p>
</blockquote>
<p><code>예시</code></p>
<ul>
<li>호스트 OS: Windows, macOS, Linux 등</li>
<li>하이퍼바이저: VMware, VirtualBox, Hiper-V, KVM 등</li>
<li>게스트 OS: 호스트 OS 위의 하이퍼바이저에서 구동되는 VM에 설치된 OS로, Windows, Linux, macOS 등 다양할 수 있음.</li>
</ul>
<p>게스트 OS는 호스트OS에서 생성된 가상머신에 설치된 운영체제이다. 실제 물리적 하드웨어가 아니라 가상화된 하드웨어 자원을 활용한다. </p>
<hr>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/58b3e89c-7642-4567-aa01-4cf1daa53a24/image.png" alt=""></p>
<ul>
<li><p><code>네이티브 하이퍼 바이저</code>
하이퍼바이저가 하드웨어를 직접 제어하기 때문에 자원을 효율적으로 사용하며, 별도의 호스트 os가 없으므로 오버헤드가 적다. 
하지만 여러 하드웨어 드라이버를 세팅해야하므로 설치가 어렵다.</p>
</li>
<li><p><code>호스트형 하이퍼바이저</code>
일반적인 소프트웨어처럼 호스트 os 위에서 실행되며, 하드웨어 자원을 VM 내부의 게스트 os에 에뮬레이트 하는 방식으로 오버헤드가 크다. 
하지만 게스트 os 종류에 대한 제약이 없고 구현이 다소 쉽다. 
이것이 일반적으로 많이 이용하는 방법이다.</p>
</li>
</ul>
<blockquote>
<p>에뮬레이트(emulate)란, 특정 하드웨어나 소프트웨어 환경을 다른 환경에서 똑같이 모방하거나 흉내내는 과정을 말한다. </p>
</blockquote>
<hr>
<h3 id="하이퍼바이저-기반의-vm-구조">하이퍼바이저 기반의 VM 구조</h3>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/c7fd2d25-4439-4d5c-9e41-c49efacd4a56/image.png" alt=""></p>
<ul>
<li>하이퍼바이저에 의해 구동되는 VM은 각 VM마다 독립된 가상 하드웨어 자원을 할당받는다. </li>
<li>논리적으로 분리 되어 있어서 한 VM에 오류가 발생하도 다른 VM으로 퍼지지 않는다는 장점이 있다. </li>
</ul>
<hr>
<h3 id="이러한-가상화-기술에서-나온-컨테이너-가상화-기술">이러한 가상화 기술에서 나온 컨테이너 가상화 기술</h3>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/b2c1427a-632c-4bde-9776-22d0a80ec940/image.png" alt=""></p>
<ul>
<li>VM과 비교했을 때 컨테이너는 하이퍼바이저와 게스트 OS가 필요하지 않으므로 더 가볍다.</li>
<li>애플리케이션을 실행할 때는 컨테이너 방식에서는 호스트 os 위에 애플리케이션의 실행 패키지인 이미지를 배포하기만 하면 되는데, VM은 애플리케이션을 실행하기 위해서 VM을 띄우고 자원을 할당한 다음, 게스트 OS를 부팅하여 애플리케이션을 실행시켜야해서 훨씬 복잡하고 무겁게 실행해야 한다. </li>
</ul>
<h4 id="공통점">공통점</h4>
<p>도커 컨테이너와 가상머신은 기본 하드웨어에서 격리된 환경 내에 애플리케이션을 배치하는 방법이다.</p>
<h4 id="차이점">차이점</h4>
<p>가장 큰 차이점은 격리된 환경을 얼마나 격리를 시키는지의 차이이다. </p>
<ul>
<li>도커 컨테이너에서 돌아가는 애플리케이션은 격리된 환경에서 실행되지만, 호스트 시스템의 커널을 공유한다.</li>
<li>가상머신은 독립적인 운영체제를 완전히 시뮬레이션 하는 방식으로 동작한다. 컨테이너보다 무겁고 성능이 느릴 수 있지만, 완전히 독립된 환경에서 다양한 운영 체제를 동시에 실행할 수 있다는 장점이 있다. </li>
</ul>
<hr>
<h3 id="도커-컨테이너-격리-방법">도커 컨테이너 격리 방법</h3>
<p>먼저 리눅스에서 쓰이는 Cgroup(control groups)과 네임스페이스(namespaces)에 대해 알아야한다.</p>
<p>이것들은 컨테이너와 호스트에서 실행되는 다른 프로세스 사이에 벽을 만드는 리눅스 커널 기능들이다. </p>
<ul>
<li><code>C Group</code>
CPU, 메모리, Network bandwidth, 입출력 등 프로세스 그룹의 시스템 리소스 사용량을 관리한다.
예를 들어, A 어플의 사용량이 너무 많다면, A 어플을 C Group에 집어넣어서 CPU와 메모리의 사용을 제한할 수 있다.</li>
<li><code>namespaces</code>
하나의 시스템에서 프로세스를 격리시킬 수 있는 가상화 기술
별개의 독립된 공간을 사용하는 것처럼 격리된 환경을 제공하는 경량 프로세스 가상화 기술이다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/e8048960-e436-4a14-813e-3bebc235b1e2/image.png" alt="">
여기에서 보면, 커널은 공유되고있고, 하드디스크와 CPU,메모리가 격리되어 있는 것을 확인할 수 있다. 
cpu와 메모리는 C Group에 의해 격리된다.
각 컨테이너가 격리되는 것은 namespace를 이용한 것이다.</p>
<hr>
<h3 id="나는-현재-윈도우를-사용-중인데-어떻게-리눅스의-기능인-c-group과-namespace를-사용할-수-있는것일까">나는 현재 윈도우를 사용 중인데, 어떻게 리눅스의 기능인 C Group과 namespace를 사용할 수 있는것일까?</h3>
<p>터미널에서 docker version을 입력하면
Server 내부의 OS/Arch를 보면 linux/amd64라고 쓰여있는 것을 확인할 수 있다. 
<img src="https://velog.velcdn.com/images/hyeondooori_/post/ef13398e-81f3-444d-836e-7fee73416ff9/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/9c541fb0-280b-4f79-aadb-581058655d09/image.png" alt="">
Mac과 window 모두 리눅스 커널, 리눅스 VM에서 비롯된 것이므로 리눅스 명령어를 사용할 수 있는 것이다.</p>
<p>🚨 Docker 자체는 하이퍼바이저를 사용하지 않고 컨테이너를 구동한다.
하지만 Windows에서 Docker Desktop은 리눅스 커널 기능을 제공하기 위해 WSL 2 기반의 리눅스 VM을 사용한다. 
이 VM은 Windows와 리눅스 컨테이너 간의 호환성을 높여주며 리눅스의 CGroup과 Namespace기능을 사용할 수 있게 해준다. </p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[Docker란?]]></title>
            <link>https://velog.io/@hyeondooori_/Docker-%EC%82%AC%EC%9A%A9-%EC%9D%B4%EC%9C%A0</link>
            <guid>https://velog.io/@hyeondooori_/Docker-%EC%82%AC%EC%9A%A9-%EC%9D%B4%EC%9C%A0</guid>
            <pubDate>Sat, 19 Oct 2024 04:37:36 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>도커는 컨테이너를 사용하여 응용프로그램을 더 쉽게 만들고 배포하고 실행할 수 있도록 설계된 도구이며, 컨테이너 기반의 오픈소스 가상화 플랫폼이자 생태계이다. </p>
</blockquote>
<h3 id="컨테이너">컨테이너?</h3>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/93a87d72-9140-44dc-940b-c5ed4db70015/image.png" alt="">
<img src="https://velog.velcdn.com/images/hyeondooori_/post/a7c93151-9f57-416a-80d9-15d59a15fb5a/image.png" alt=""></p>
<h3 id="도커-이미지와-도커-컨테이너-정의">도커 이미지와 도커 컨테이너 정의</h3>
<p><code>컨테이너</code>는 코드와 모든 종속성을 패키지화하여 응용 프로그램이 한 컴퓨팅 환경에서 다른 컴퓨팅 환경으로 빠르고 안정적으로 실행되도록 하는 소프트웨어의 표준 단위이다.</p>
<p>➡ 현재까지 여러 가지 방향으로 컨테이너를 정의할 때 간단하고 편리하게 프로그램을 실행시켜주는 것으로 정의를 내리고 있다. </p>
<hr>
<p>컨테이너 이미지는 코드, 런타임, 시스템 도구, 시스템 라이브러리 및 설정과 같은 응용 프로그램을 실행하는데 필요한 모든 것을 포함하는 가볍고 독립적이며 실행 가능한 소프트웨어 패키지이다. 
또한, 컨테이너 이미지는 런타임에 컨테이너가 되고 도커 컨테이너의 경우 도커 엔진에서 실행될 때 이미지가 컨테이너가 된다.
리눅스와 윈도우 기반 애플리케이션 모두에서 사용할 수 있는 컨테이너화 된 소프트웨어는 인프라에 관계 없이 항상 동일하게 실행된다.</p>
<p>컨테이너는 소프트웨어를 환경으로부터 격리시키고 개발과 스테이징의 차이에도 불구하고 균일하게 작동하도록 보장한다.</p>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/57c4d961-765f-4bb1-a583-fbe6768f1efe/image.png" alt=""></p>
<p>도커 이미지는 프로그램을 실행하는데 필요한 설정이나 종속성을 가지고 있으며 도커 이미지를 이용해 컨테이너를 생성하며 도커 컨테이너를 이용해 프로그램을 실행한다. </p>
<hr>
<h3 id="도커를-쓰는-이유는-무엇인가">도커를 쓰는 이유는 무엇인가?</h3>
<p>어떤 프로그램을 다운로드 하는 과정을 굉장히 간단하게 만들기 위해서 사용한다.</p>
<p>도커 없이 프로그램을 받을 때는 installer 내려받고, 그걸 실행하면 된다. 그런데, 갖고있는 서버, 패키지 버전, 운영체제 등등에 따라 프로그램을 설치하는 과정에서 많은 에러가 발생한다. 그것만이 아니라 설치 과정이 다소 복잡하다.</p>
<p>실제 redis 다운로드 과정으로 예시를 들어보면,
도커 없이 다운로드를 받기 위해서는 
<img src="https://velog.velcdn.com/images/hyeondooori_/post/b79ddfb7-b102-45be-ab2c-f92b71597a44/image.png" alt="">
이런 과정을 거쳐야한다. 
하지만 wget 명령어를 사용하기 위해서는 또 설치과정이 필요하여 오류가 발생하고 복잡하다. 
즉, 특정 프로그램을 받을 때 거기에 맞는 부수적인 것들도 계속 받으며 설치하는 과정이 복잡해지고 에러도 많이 생긴다.</p>
<p>반면!
도커로 Redis를 받으려고 하면,
<code>docker run -it redis</code>만 치면 다운로드가 완료된다. </p>
<blockquote>
<p>즉, 도커를 이용해 프로그램을 설치하면 예상치 못한 에러도 덜 발생하며, 설치하는 과정도 훨씬 간단해지기 때문에 도커를 사용한다.</p>
</blockquote>
<hr>
<h3 id="이미지로-컨테이너-생성하는-방법">이미지로 컨테이너 생성하는 방법</h3>
<p>그렇다면, 어떻게해서 컨테이너를 생성하는 것일까?</p>
<p>이미지는 응용 프로그램을 실행하는데 <mark>필요한 모든 것</mark>을 포함하고 있다.</p>
<h4 id="필요한-모든-것">필요한 모든 것</h4>
<ol>
<li>컨테이너가 시작될 때 실행되는 명령어</li>
<li>파일 스냅샷
 ex. 컨테이너에서 카카오톡을 실행하고싶다면, 카카오톡 파일(카카오톡을 실행하는데 필요한 파일) 스냅샷</li>
</ol>
<p>파일 스냅샷은 디렉토리나 파일을 카피한 것이다.</p>
<hr>
<h3 id="이미지로-컨테이너-만드는-순서">이미지로 컨테이너 만드는 순서</h3>
<ol>
<li>Docker 클라이언트에 docker run&lt;이미지&gt; 입력해준다.</li>
<li>도커 이미지에 있는 파일 스냅샷을 컨테이너 하드 디스크에 옮겨준다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/32c0eb7b-a165-40f8-96f1-9b24d37b4aeb/image.png" alt=""></li>
<li>이미지에서 가지고 있는 명령어(컨테이너가 실행될 때 사용될 명령어)를 이용해서 카카오톡을 실행시켜준다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/9614cc5b-5364-4376-9521-7856b1ac5a07/image.png" alt=""></li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[spring boot] 마이크로미터, 프로메테우스, 그라파나]]></title>
            <link>https://velog.io/@hyeondooori_/spring-boot-%EB%A7%88%EC%9D%B4%ED%81%AC%EB%A1%9C%EB%AF%B8%ED%84%B0-%ED%94%84%EB%A1%9C%EB%A9%94%ED%85%8C%EC%9A%B0%EC%8A%A4-%EA%B7%B8%EB%9D%BC%ED%8C%8C%EB%82%98</link>
            <guid>https://velog.io/@hyeondooori_/spring-boot-%EB%A7%88%EC%9D%B4%ED%81%AC%EB%A1%9C%EB%AF%B8%ED%84%B0-%ED%94%84%EB%A1%9C%EB%A9%94%ED%85%8C%EC%9A%B0%EC%8A%A4-%EA%B7%B8%EB%9D%BC%ED%8C%8C%EB%82%98</guid>
            <pubDate>Wed, 16 Oct 2024 14:40:23 GMT</pubDate>
            <description><![CDATA[<h1 id="마이크로미터">마이크로미터</h1>
<p>모니터링은 잘 대응하는 것이 중요하다. 
서비스를 운영할 때는 애플리케이션의 CPU, 메모리, 커넥션 사용, 고객 요청수 같은 많은 지표들을 확인하는 것이 필요하다. 그래야 어디에 어떤 문제가 발생했는지 사전에 대응도 할 수 있고, 실제 문제가 발생하도 원인을 빠르게 파악해서 대처할 수 있다. 예를 들어서 메모리 사용량이 가득 찼다면 메모리 문제와 관련있는 곳을 빠르게 찾아서 대응할 수 있을 것이다. </p>
<p>세상에는 수 많은 모니터링 툴이 있고, 시스템의 다양한 정보를 이 모니터링 툴에 전달해서 사용할 수 있다. </p>
<p><strong>그라파나 대시보드</strong>
<img src="https://velog.velcdn.com/images/hyeondooori_/post/453e1882-1176-4929-adda-eb14aa66cccc/image.png" alt="">
<strong>핀포인트</strong>
<img src="https://velog.velcdn.com/images/hyeondooori_/post/1055581e-7fb8-4599-ba3b-3817b9a092a1/image.png" alt="">
이런 모니터링 툴이 작동하려면 시스템의 다양한 지표들을 각각의 모니터링 툴에 맞도록 만들어서 보내주어야 한다. (실제로는 라이브러리 등을 통해서 자동화되는 경우가 많다.)</p>
<p><strong>모니터링 툴에 지표 전달</strong>
<img src="https://velog.velcdn.com/images/hyeondooori_/post/612535a5-2c73-401b-965c-e1269b7dc858/image.png" alt="">
예를 들어서 CPU, JVM, 커넥션 정보 등을 JMX 툴에 전달한다고 가정해보자. 그러면 각각의 정보를 JMX 모니터링 툴이 정한 포멧에 맞추어 측정하고 전달해야 한다. </p>
<p><strong>모니터링 툴 변경</strong>
<img src="https://velog.velcdn.com/images/hyeondooori_/post/22193df7-4722-4008-887b-2d89d0732dd0/image.png" alt="">
그런데 중간에 사용하는 모니터링 툴을 변경하면 어떻게 될까?
기존에 측정했던 코드를 모두 변경한 툴에 맞도록 다시 변경해야 한다. 
개발자 입장에서는 단순히 툴 하나를 변경했을 뿐인데, 측정하는 코드까지 모두 변경해야 하는 문제가 발생한다.
이런 문제를 해결하는 것이 바로 마이크로미터(Micrometer)라는 라이브러리이다.</p>
<p><strong>마이크로미터 추상화</strong>
<img src="https://velog.velcdn.com/images/hyeondooori_/post/75554523-211c-4ab2-a247-6c3e7dc4b0a2/image.png" alt="">
<strong>마이크로미터 전체 그림</strong>
<img src="https://velog.velcdn.com/images/hyeondooori_/post/9e478456-46f1-42ca-a2d2-99fc6fc60306/image.png" alt=""></p>
<ul>
<li>마이크로미터는 애플리케이션 메트릭 파사드라고 불리는데, 애플리케이션의 메트릭(측정 지표)를 마이크로미터가 정한 표준 방법으로 모아서 제공해준다. </li>
<li>쉽게 이야기해서 마이크로미터가 추상화를 통해 구현체를 쉽게 갈아끼울 수 있도록 해두었다.</li>
<li>보통은 스프링이 이런 추상화를 직접 만들어서 제공하지만, 마이크로미터라는 이미 잘 만들어진 추상화가 있기 때문에 스프링은 이것을 활용한다. 스프링 부트 액츄에이터는 마이크로미터를 기본으로 내장해서 사용한다.<ul>
<li>로그를 추상화하는 <code>SLF4J</code>를 떠올려보면 쉽게 이해가 될 것이다. </li>
</ul>
</li>
<li>개발자는 마이크로미터가 정한 표준 방법으로 메트릭(측정 지표)를 전달하면 된다. 그리고 사용하는 모니터링 툴에 맞는 구현체를 선택하면 된다. 이후에 모니터링 툴이 변경되어도 해당 구현체만 변경하면 된다. 애플리케이션 코드는 모니터링 툴이 변경되어도 그대로 유지할 수 있다. </li>
</ul>
<p>마이크로미터가 지원하는 모니터링 툴은 다음과 같다.
AppOptics
Atlas
CloudWatch
Datadog
Dynatrace
Elastic
Ganglia
Graphite
Humio
Influx
Instana
JMX
KairosDB
New Relic
Prometheus
SignalFx
Stackdriver
StatsD
Wavefront</p>
<h2 id="메트릭-확인하기">메트릭 확인하기</h2>
<p>CPU, JVM, 커넥션 사용 등등 수 많은 지표들을 어떻게 수집해야 할까?
개발자가 각각의 지표를 직접 수집해서 그것을 마이크로미터가 제공하는 표준 방법에 따라 등록하면 된다. 
다행히도 마이크로미터는 다양한 지표 수집 기능을 이미 만들어서 제공한다.
그리고 스프링 부트 액츄에이터는 마이크로미터가 제공하는 지표 수집을 @AutoConfiguration을 통해 자동으로 등록해준다.</p>
<p>쉽게 이야기해서 스프링 부트 액츄에이터를 사용하면 수 많은 메트릭(지표)를 편리하게 사용할 수 있다.
이제 기본으로 제공하는 메트릭들을 확인해보자. 
아직 모니터링 툴을 연결한 것은 아니고, 등록된 메트릭들을 확인해보는 단계이다. </p>
<p><strong>matrics 엔드포인트</strong>
matrics 엔드포인트를 사용하면 기본으로 제공되는 메트릭들을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/81bd4bcb-2b12-43b4-a449-98053a6e09b9/image.png" alt=""></p>
<ul>
<li>액츄에이터가 마이크로미터를 통해 등록한 기본 메트릭들을 확인할 수 있다. </li>
<li>내용이 너무 많아 일부만 남겨두었다.</li>
</ul>
<p>metrics 엔드포인트는 다음과 같은 패턴을 사용해서 더 자세히 확인할 수 있다.
<code>http://localhost:8080/actuator/metrics/{name}</code></p>
<p>JVM 메모리 사용량을 확인해보자.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/3e8b3bfb-e11b-4a9e-aa40-634a89396ef2/image.png" alt=""></p>
<ul>
<li>현재 메모리 사용량을 확인할 수 있다.</li>
</ul>
<p>Tag필터
availableTags를 보면 다음과 같은 항목을 확인할 수 있다.</p>
<ul>
<li>tag:area, values[heap, nonheap]</li>
<li>tag:id, values[G1 Survivor Space, ...]
해당 Tag를 기반으로 정보를 필터링해서 확인할 수 있다.
tag=KEY:VALUE와 같은 형식을 사용해야 한다.</li>
</ul>
<p>다음과 같이 실행해보자.</p>
<ul>
<li><a href="http://localhost:8080/actuator/metrics/jvm.memory.used?tag=area:heap">http://localhost:8080/actuator/metrics/jvm.memory.used?tag=area:heap</a></li>
<li><a href="http://localhost:8080/actuator/metrics/jvm.memory.used?tag=area:nonheap">http://localhost:8080/actuator/metrics/jvm.memory.used?tag=area:nonheap</a>
tag 를 사용해서 힙 메모리, 힙이 아닌 메모리로 분류해서 데이터를 확인할 수 있다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hyeondooori_/post/8fc8c4ee-f532-485f-afdf-28526651ff86/image.png" alt=""></p>
<p>또 다른 예시는 다음과 같다.
<img src="https://velog.velcdn.com/images/hyeondooori_/post/066eafa4-f684-4d72-bf15-096b6c91f22f/image.png" alt=""></p>
<h2 id="다양한-메트릭">다양한 메트릭</h2>
<p>마이크로미터와 액츄에이터가 기본으로 제공하는 다양한 메트릭을 확인해보자.</p>
<ul>
<li>JVM 메트릭</li>
<li>시스템 메트릭</li>
<li>애플리케이션 시작 메트릭</li>
<li>스프링 MVC 메트릭</li>
<li>톰캣 메트릭</li>
<li>데이터 소스 메트릭</li>
<li>로그 메트릭
기타 수 많은 메트릭이 있다.
사용자가 메트릭을 직접 정의하는 것도 가능하다. 뒤에서 예제로 만들어본다.</li>
</ul>
<h3 id="jvm-메트릭">JVM 메트릭</h3>
<p>JVM 관련 메트릭을 제공한다. <code>jvm.</code>으로 시작한다.</p>
<ul>
<li>메모리 및 버퍼 풀 세부 정보</li>
<li>가비지 수집 관련 통계</li>
<li>스레드 활용</li>
<li>로드 및 언로드된 클래스 수</li>
<li>JVM 버전 정보</li>
<li>JIT 컴파일 시간</li>
</ul>
<h3 id="시스템-메트릭">시스템 메트릭</h3>
<p>시스템 메트릭을 제공한다. <code>system.</code> , <code>process.</code> , <code>disk.</code> 으로 시작한다.</p>
<ul>
<li>CPU 지표</li>
<li>파일 디스크립터 메트릭</li>
<li>가동 시간 메트릭</li>
<li>사용 가능한 디스크 공간</li>
</ul>
<h3 id="애플리케이션-시작-메트릭">애플리케이션 시작 메트릭</h3>
<p>애플리케이션 시작 메트릭을 제공한다.</p>
<ul>
<li><code>application.started.time</code>: 애플리케이션을 시작하는데 걸리는 시간 (ApplicationStartedEvent로 측정)</li>
<li><code>application.ready.time</code>: 애플리케이션이 요청을 처리할 준비가 되는데 걸리는 시간(ApplicationReadyEvent로 측정)</li>
</ul>
<p>스프링은 내부에 여러 초기화단계가 있고 각 단계별로 내부에서 애플리케이션 이벤트를 발행한다.</p>
<ul>
<li>ApplicationStartEvent: 스프링 컨테이너가 완전히 실행된 상태이다. 이후에 커맨드 라인 러너가 호출된다.</li>
<li>ApplicationReadyEvent: 커맨드 라인 러너가 실행된 이후에 호출된다.</li>
</ul>
<h3 id="스프링-mvc-메트릭">스프링 MVC 메트릭</h3>
<p>스프링 MVC 컨트롤러가 처리하는 모든 요청을 다룬다.
메트릭 이름: <code>http.server.requests</code></p>
<p>TAG 를 사용해서 다음 정보를 분류해서 확인할 수 있다.</p>
<ul>
<li>uri : 요청 URI</li>
<li>method : GET , POST 같은 HTTP 메서드</li>
<li>status : 200 , 400 , 500 같은 HTTP Status 코드</li>
<li>exception : 예외</li>
<li>outcome : 상태코드를 그룹으로 모아서 확인 1xx:INFORMATIONAL , 2xx:SUCCESS , 3xx:REDIRECTION , 4xx:CLIENT_ERROR , 5xx:SERVER_ERROR</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JAVA] 제네릭(Generic)타입]]></title>
            <link>https://velog.io/@hyeondooori_/JAVA-%EC%A0%9C%EB%84%A4%EB%A6%ADGeneric%ED%83%80%EC%9E%85</link>
            <guid>https://velog.io/@hyeondooori_/JAVA-%EC%A0%9C%EB%84%A4%EB%A6%ADGeneric%ED%83%80%EC%9E%85</guid>
            <pubDate>Wed, 16 Oct 2024 07:55:44 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>자바에서 클래스나 메서드를 정의할 때, 특정 데이터 타입에 의존하지 않고 다양한 타입을 처리할 수 있도록 만드는 방법이다.
제네릭을 사용하면 데이터 타입을 일반화시켜 코드의 재사용성과 타입 안전성을 높일 수 있다. 제네릭을 통해 컴파일 시점에 타입을 확정하고, 그로인해 잘못된 타입 사용을 방지할 수 있다.</p>
</blockquote>
<p>제네릭은 클래스나 메서드를 정의할 때 사용되며, 그 안에서 다룰 데이터 타입을 나중에 지정할 수 있도록 해준다. 예를 들어 특정 클래스가 Integer,String과 같은 다양한 데이터 타입을 처리해야하는 경우, 제네릭을 사용하면 같은 코드를 중복 작성하지 않고 한 번에 다양한 데이터 타입을 처리할 수 있다.</p>
<h3 id="제네릭-타입의-문법">제네릭 타입의 문법</h3>
<pre><code class="language-java">class 클래스이름&lt;T&gt; {
    // 클래스 내용
}</code></pre>
<p>여기서 <code>T</code>는 제네릭 타입 매개변수로 Type을 나타내는 약어이다. <code>T</code> 외에도 <code>E</code>, <code>K</code>, <code>V</code> 등 다양한 문자를 사용할 수 있다.
하지만 일반적으로는 T를 많이 사용한다. </p>
<p>사용 예시</p>
<pre><code class="language-java">public class Box&lt;T&gt; {
    private T item;

    // 아이템 저장
    public void setItem(T item) {
        this.item = item;
    }

    // 아이템 반환
    public T getItem() {
        return item;
    }

    public static void main(String[] args) {
        // String 타입의 Box
        Box&lt;String&gt; stringBox = new Box&lt;&gt;();
        stringBox.setItem(&quot;Hello&quot;);
        System.out.println(stringBox.getItem());  // 출력: Hello

        // Integer 타입의 Box
        Box&lt;Integer&gt; intBox = new Box&lt;&gt;();
        intBox.setItem(123);
        System.out.println(intBox.getItem());  // 출력: 123
    }
}</code></pre>
<p>제네릭 메서드 예시 </p>
<ul>
<li><p>클래스 뿐만 아니라 메서드에도 적용할 수 있다. 제네릭 메서드는 메서드 정의에서 제네릭 타입을 사용하는 것이다.</p>
<pre><code class="language-java">public class GenericMethodExample {

  // 제네릭 메서드
  public static &lt;T&gt; void printArray(T[] array) {
      for (T element : array) {
          System.out.println(element);
      }
  }

  public static void main(String[] args) {
      // Integer 배열 출력
      Integer[] intArray = {1, 2, 3, 4, 5};
      printArray(intArray);

      // String 배열 출력
      String[] strArray = {&quot;A&quot;, &quot;B&quot;, &quot;C&quot;, &quot;D&quot;};
      printArray(strArray);
  }
}</code></pre>
</li>
</ul>
<h3 id="제네릭의-장점">제네릭의 장점</h3>
<ol>
<li>타입 안전성(Type Safety)</li>
</ol>
<ul>
<li>제네릭을 사용하면 컴파일 시점에서 타입 검사를 하기 때문에 잘못된 타입을 사용하는 오류를 방지할 수 있다. 예를 들어, 제네릭을 사용하지 않고 Object 타입을 사용할 경우, 각 데이터를 처리할 때마다 타입 변환(casting)을 해야하고, 잘못된 변환 시 런타임 오류가 발생할 수 있다.</li>
<li>제네릭 사용 시 미리 타입을 지정하고 잘못된 타입의 데이터가 들어가는 것을 방지할 수 있다. </li>
</ul>
<ol start="2">
<li>코드 재사용성</li>
</ol>
<ul>
<li>제네릭을 사용하면 하나의 클래스나 메서드로 다양한 데이터 타입을 처리할 수 있으므로, 중복 코드를 줄이고 코드 재사용성을 높일 수 있다.</li>
</ul>
<ol start="3">
<li>캐스팅을 줄임</li>
</ol>
<ul>
<li>제네릭을 사용하지 않으면 Object로 데이터를 처리하게 되므로 데이터 타입에 맞게 형변환(casting)을 해야한다. 제네릭을 사용하면 이 과정이 생략되어 코드가 더욱 간결해진다.</li>
</ul>
<h3 id="제네릭을-사용하지-않았을-때의-문제점">제네릭을 사용하지 않았을 때의 문제점</h3>
<p>제네릭이 없다면 모든 객체를 Object로 처리해야하며, 이로인해 불필요한 형변환이 필요하게 된다. </p>
<pre><code class="language-java">public class NonGenericBox {
    private Object item;

    public void setItem(Object item) {
        this.item = item;
    }

    public Object getItem() {
        return item;
    }

    public static void main(String[] args) {
        NonGenericBox box = new NonGenericBox();
        box.setItem(&quot;Hello&quot;);

        // 형 변환 필요
        String item = (String) box.getItem(); 
        System.out.println(item);

        // 형 변환 오류 가능성
        box.setItem(123);
        String wrongType = (String) box.getItem();  // 실행 시 ClassCastException 발생
    }
}</code></pre>
<ul>
<li><code>setItem()</code> 메서드는 매개변수로 <code>Object</code> 타입을 받기 때문에 어떤 객체든 저장할 수 있다. 예를 들어, <code>String</code> 객체도 저장할 수 있고, <code>Integer</code> 객체도 저장할 수 있다.</li>
<li><code>getItem()</code> 메서드는 저장된 객체를 반환하는데, 반환 타입이 <strong>Object</strong>이다. 이 반환된 객체를 사용할 때, 해당 객체를 구체적인 타입(예: String)으로 형 변환(casting) 해야한다.</li>
<li>처음에 <code>setItem(&quot;Hello&quot;)</code>로 <code>String</code> 객체를 저장했고, 이를 <code>String</code>으로 형 변환하여 문제없이 사용했다. 하지만 두 번째로 <code>setItem(123)</code>로 <code>Integer</code> 객체를 저장하고 나서 이를 <code>String</code>으로 잘못 형 변환하려고 하므로, <code>ClassCastException</code>이 발생하게 된다.</li>
</ul>
<h3 id="오류의-원인">오류의 원인</h3>
<ul>
<li>setItem()을 통해 어떤 타입의 객체든 저장할 수 있지만, getItem으로 객체를 꺼내올 때 그 객체가 원래 어떤 타입이었는지 알 수 없기 때문에 잘못된 타입으로 형 변환을 시도할 경우 런타임 오류가 발생한다.</li>
<li>Integer객체는 String으로 직접 형변환을 할 수 없다.<ul>
<li>형변환은 일반적으로 두 클래스가 상속관계에 있을 때만 가능하다. </li>
<li>예를 들어, Integer는 Object를 상속받기 때문에 Object로 형 변환이 가능하다. 그러나 Integer와 String은 전혀 상관없는 클래스이므로 직접 형변환을 시도하면 <code>ClassCastException</code>이 발생한다. </li>
</ul>
</li>
</ul>
<h4 id="integer를-string으로-변환-방법">Integer를 String으로 변환 방법</h4>
<ul>
<li><code>String.valueOf()</code>메서드를 사용해야 한다. 이 메서드는 <code>int</code>나 <code>Integer</code> 값을 문자열로 반환해준다. <pre><code class="language-java">Integer intObj = 123;
String str = String.valueOf(intObj);
System.out.println(str);  // 출력: &quot;123&quot;</code></pre>
</li>
<li><code>Integer.toString()</code>메서드를 통해 Integer 객체의 값을 문자열로 변환할 수 있다.<pre><code class="language-java">Integer intObj = 123;
String str = intObj.toString();
System.out.println(str);  // 출력: &quot;123&quot;</code></pre>
</li>
<li>문자열 결합 연산자(+) 사용 
숫자를 문자열과 더하면 자동으로 문자열로 변환된다.<pre><code class="language-java">Integer intObj = 123;
String str = intObj + &quot;&quot;;  // 문자열 결합
System.out.println(str);  // 출력: &quot;123&quot;</code></pre>
</li>
</ul>
<h4 id="string을-integer로-변환하는-방법">String을 Integer로 변환하는 방법</h4>
<ul>
<li>Integer.parseInt() 사용
int형으로 변환해준다.<pre><code class="language-java">String str = &quot;123&quot;;
int number = Integer.parseInt(str);
System.out.println(number);  // 출력: 123</code></pre>
</li>
<li>Integer.valueOf() 사용
객체형(Integer)로 변환해줌.<pre><code class="language-java">String str = &quot;123&quot;;
Integer number = Integer.valueOf(str);
System.out.println(number);  // 출력: 123</code></pre>
</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>