<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>_030702.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Sun, 30 Aug 2026 14:17:19 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>_030702.log</title>
            <url>https://velog.velcdn.com/images/_030702/profile/4c268cb3-eab3-4616-9e56-2cbef1947349/social_profile.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. _030702.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/_030702" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[VM에 IPMI을 설치하고 사용해보자]]></title>
            <link>https://velog.io/@_030702/VM%EC%97%90-IPMI%EC%9D%84-%EC%84%A4%EC%B9%98%ED%95%98%EA%B3%A0-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@_030702/VM%EC%97%90-IPMI%EC%9D%84-%EC%84%A4%EC%B9%98%ED%95%98%EA%B3%A0-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Sun, 30 Aug 2026 14:17:19 GMT</pubDate>
            <description><![CDATA[<h2 id="ipmi란-intelligent-platform-management-interface">IPMI란? (Intelligent Platform Management Interface)</h2>
<p>IPMI는 표준화된 메세지 기반의 하드웨어 관리 interface로, 서버나 장비의 전원 상태, 하드웨어 이상 유무등을 네트워크를 통해 원격으로 모니터링하거나, 직접 제어할 수 있는 기능을 제공한다. IPMI 기능이 탑재된 메인보드나 BMC(Baseboard Management Controller)를 이용하여 운영체제와는 독립적으로 하드웨어를 직접 관리할 수 있다.</p>
<p>즉, 서버 전원의 on/off 여부와 상관없이, 운영체제가 제대로 동작하지 않아도, 기타 문제가 생겨 접속이 불가하더라도 IPMI로 조작할 수 있다. 당연히 모든 문제가 발생해도 IPMI를 사용할 수 있다는 것을 의미하지는 않으나, BMC에 문제가 생기지 않고 서버에 전력이 공급되고 있다면, 대체로 IPMI를 사용할 수 있다.</p>
<h2 id="그렇다면-ipmi를-왜-사용해야하는가">그렇다면, IPMI를 왜 사용해야하는가?</h2>
<p>→ 물리적 접근이 어려운 상황에서의 관리 편의성</p>
<p>서버 랙 셋업, 네트워크 설치, OS 설치 및 IP 할당과 같은 서버 랙 세팅을 해야할 경우, 문제가 발생할 때마다 서버실을 직접 방문해 수정해야한다. 또한, 여러 노드에 OS를 설치해야할 때, 각 노드에 모니터를 연결하고, 부팅 USB를 꽂고, 키보드를 직접 연결한 후 OS를 설치해야하는 번거로움이 있다.이 번거로움을 IPMI는 해결한다. 서버에 접근할 때 물리적인 접근이 필요하지 않게 IPMI는 도와준다. </p>
<h2 id="ipmi-주요-기능">IPMI 주요 기능</h2>
<ol>
<li><p>전원 제어 </p>
<p> 서버가 응답하지 않는 상황에도 물리적으로 서버실에 갈 필요없이 IPMI를 통해 재부팅 명령을 내릴 수 있다. </p>
<p> 원격 전원 제어가 가능하다.</p>
</li>
<li><p>콘솔 접근</p>
<p> 서버 앞에 직접 모니터와 키보드, 마우스를 연결한 것처럼 원격에서 서버 화면을 보고, 서버를 조작할 수 있다.</p>
<p> 원격 Media Mount가 가능하기 때문에, 내 로컬 PC에 존재하는 파일(ex. iso)을 서버의 가상 드라이브로 인식시켜 운영체제 설치나 복구를 진행할 수 있다.</p>
</li>
<li><p>하드웨어 모니터링</p>
<p> CPU 온도, 팬 속도, 전압, 전력 사용량과 같은 정보를 실시간으로 모니터링 할 수 있다.</p>
<p> 하드웨어 오류나 일정 온도 이상등의 이벤트 발생시 로그 확인이 가능하고, 미리 지정된 관리자에게 알림을 보낼 수 있다.</p>
</li>
<li><p>펌 웨어/ BIOS 관리</p>
<p> BMC나 BIOS를 최신 버전으로 업데이트 하거나, 설정을 변경할 수 있다. </p>
</li>
</ol>
<h2 id="ipmi-설치-과정">IPMI 설치 과정</h2>
<p><a href="https://charliekim.tistory.com/6">https://charliekim.tistory.com/6</a> 를 참고하여 설치를 진행했다.
<img src="https://velog.velcdn.com/images/_030702/post/e4dd60b0-79d3-41b1-9600-69839f2f9ba1/image.png" alt=""></p>
<p>우선 IPMI를 이전에 만들어둔 vm 환경에 설치해서 확인해보려고 한다. 하지만, vm이기때문에 당연히 물리적 하드웨어가 조회되지 않아 IPMI를 설치할 수 없는 환경이다. </p>
<p>이 경우에는 OpenIPMI의 ipmisim을 사용하거나, VirtualBMC(vBMC)를 사용하거나, QEMU/KVM IPMI 애뮬레이션을 통해 IPMI를 소프트웨어적으로 시뮬레이션 할 수 있다고 한다.</p>
<h2 id="virtualbmcvbmc">VirtualBMC(vBMC)</h2>
<p>vBMC 는 리눅스 KVM 기반 가상머신에 대해 가짜 BMC 기능 (IPMI 프로토콜 제공)을 구현해준다. </p>
<p>보통 vBMC이 사용되는 사례는 다음과 같다.</p>
<ul>
<li><p>베어메탈 프로비전이 기능 개발 및 테스트에 활용하는 Openstack Ironic 개발 및 테스트 진행</p>
</li>
<li><p>실제 장비 없이 BMC(IPMI) 인터페이스를 연동해야하는 IPMI 자동화 실습</p>
</li>
<li><p>교육용, 데모, PoC 환경에서 사용</p>
</li>
<li><p>가상 환경에서 자동화된 인프라 테스트 진행시 사용</p>
</li>
</ul>
<p>필자의 경우 VirtualBMC(vBMC)을 사용해 실습을 진행하였다. </p>
<h2 id="vbmc-설치">vBMC 설치</h2>
<p>기본적으로 설치해야하는 설치 패키지</p>
<pre><code>sudo apt update
sudo apt install python3-pip python3-venv ipmitool
sudo apt install pkg-config
# 하이퍼 바이저 설치 
sudo apt install -y qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils virt-manager</code></pre><p>python 가상환경 생성 및 활성화</p>
<p>시스템 영향 최소화를 목적으로 가상환경을 생성해 진행한다. </p>
<pre><code># python 가상환경 생성 (시스템 영향 최소화를 목적으로)
python3 -m venv --system-site-packages /opt/virtualbmc
# python 가상환경 활성화 
source /opt/virtualbmc/bin/activate</code></pre><p>가상환경 내부에 virtualBMC를 설치</p>
<pre><code># 가상환경 안에 virtualBMC 설치
/opt/virtualbmc/bin/pip3 install virtualbmc
# 제대로 설치되었는지 확인
vbmc --version</code></pre><p>하이퍼 바이저 상태 확인</p>
<pre><code># 서비스 시작
sudo systemctl start libvirtd
# 부팅 시 자동 시작 설정
sudo systemctl enable libvirtd
# 상태 다시 확인
sudo systemctl status libvirtd</code></pre><p>실습용 VM (IPMI를 설치할 VM) 생성 및 vbmc에 등록</p>
<pre><code># 실습용 VM 생성하기
virt-install --name hee \
--ram 128 \
--disk none \
--boot network \
--os-variant ubuntu22.04 \
--graphics none \
--dry-run --print-xml &gt; hee.xml

# XML 설정을 바탕으로 VM 등록
virsh define hee.xml

# 현재 사용중인 가상머신을 vbmc에 등록
vbmc add hee --port 6230 --username admin --password admin</code></pre><p>vBMC 활성화</p>
<pre><code># BMC 활성화
vbmc start hee

# 리스트 확인 
vbmc list </code></pre><p>vbmc list에 실습용으로 생성한 vm이 보이면 준비 완료 !</p>
<p><img src="https://velog.velcdn.com/images/_030702/post/d60cffd2-317d-40cb-9f62-f12c644683a5/image.png" alt=""></p>
<h2 id="ipmi-명령어로-제어-실습">IPMI 명령어로 제어 실습</h2>
<p>ipmitool을 설치한다.</p>
<pre><code>sudo apt install ipmitool -y</code></pre><p>전원 상태 확인, 전원 켜기 끄기</p>
<pre><code># 1. 현재 전원 상태 확인 (아마 off일 겁니다)
ipmitool -I lanplus -H 127.0.0.1 -p 6230 -U admin -P admin chassis power status

# 2. 전원 켜기
ipmitool -I lanplus -H 127.0.0.1 -p 6230 -U admin -P admin chassis power on

# 3. 다시 상태 확인 (on으로 바뀌었는지 확인!)
ipmitool -I lanplus -H 127.0.0.1 -p 6230 -U admin -P admin chassis power status</code></pre><p><img src="https://velog.velcdn.com/images/_030702/post/8281dc51-272e-4606-a90c-36fd0d4b44b9/image.png" alt=""></p>
<p>vBMC는 가상으로 BMC 시뮬레이션을 진행한 것이기 때문에 아래의 실습은 진행이 불가하였다. (값을 가져올 수 없으므로) 다만, 실제 상황에서는 사용이 가능하여 자주 사용되는 명령어를 몇가지 정리해보았다.</p>
<ul>
<li><p>하드웨어 센서 모니터링 (온도, 팬 속도, 전압 상태)</p>
<ul>
<li>서버 내부의 온도, 팬 속도, 전압 상태를 실시간으로 확인할 수 있음.</li>
</ul>
</li>
</ul>
<pre><code>ipmitool -I lanplus -H 127.0.0.1 -p 6230 -U admin -P admin sdr list</code></pre><ul>
<li><p>시스템 이벤트 로그 확인 (SEL)</p>
<ul>
<li>하드웨어 수준에서 발생한 모든 경고 기록을 확인할 수 있음. OS가 부팅되기 전의 에러도 기록됨.</li>
</ul>
</li>
</ul>
<pre><code>ipmitool -I lanplus -H 127.0.0.1 -p 6230 -U admin -P admin sel list</code></pre><ul>
<li><p>시리얼 터미널 접속 (Serial Over LAN)</p>
<ul>
<li>서버에 모니터 연결 안해도 네트워크를 통해 텍스트 기반의 부팅 화면이나 콘솔을 직접 확인 가능</li>
</ul>
</li>
</ul>
<pre><code>ipmitool -I lanplus -H 127.0.0.1 -p 6230 -U admin -P admin sol activate</code></pre><ul>
<li><p>식별용 LED 제어</p>
<ul>
<li>수많은 랙 중 작업해야할 장비가 무엇인지 알려줌</li>
</ul>
</li>
</ul>
<pre><code>ipmitool -I lanplus -H 127.0.0.1 -p 6230 -U admin -P admin chassis identify force</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[VM 다중 IP 할당 및 컨테이너 포트 포워딩 (Ubuntu 24.04)]]></title>
            <link>https://velog.io/@_030702/VM-%EB%8B%A4%EC%A4%91-IP-%ED%95%A0%EB%8B%B9-%EB%B0%8F-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-%ED%8F%AC%ED%8A%B8-%ED%8F%AC%EC%9B%8C%EB%94%A9-Ubuntu-24.04-icsbwoeg</link>
            <guid>https://velog.io/@_030702/VM-%EB%8B%A4%EC%A4%91-IP-%ED%95%A0%EB%8B%B9-%EB%B0%8F-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-%ED%8F%AC%ED%8A%B8-%ED%8F%AC%EC%9B%8C%EB%94%A9-Ubuntu-24.04-icsbwoeg</guid>
            <pubDate>Sun, 30 Aug 2026 14:12:22 GMT</pubDate>
            <description><![CDATA[<h2 id="무엇을-공부하는지">무엇을 공부하는지</h2>
<pre><code>하나의 NIC가 여러 IP를 가지는 원리 
물리적 랜카드는 하나여도, OS는 이를 논리적으로 쪼개서 인식할 수 있음.
Secondary IP  : 하나의 인터페이스에 여러개의 IP 주소를 할당 
Sub-Interface : 인터페이스를 가상으로 나눔 → 물리적으로 하나, 논리적으로는 완전 분리 </code></pre><pre><code>OS 레벨에서의 트래픽 구분
VM 혹은 서버 내부로 패킷이 들어오면, OS 커널은 
L3에서 패킷 헤더의 Destination IP 확인 후 내 Secondary IP 중 하나와 일치하면 패킷 수용
L4에서 Port 번호 확인 후, 해당 포트를 열고 기다리는 프로세스에게 데이터를 전달</code></pre><pre><code>포트 충돌 해결
동일 IP에서 동일 Port를 동시에 사용할 수 없어서, 다중 IP 나누어 동일 포트를 할당함</code></pre><pre><code>트래픽 분리
멀티 테넌트 (여러 고객이 한 인프라 공유)  환경에서 필수적
VXLAN (가상 터널링), 보안 그룹</code></pre><h2 id="개념">개념</h2>
<h3 id="포트포워딩">포트포워딩</h3>
<p>공유기, 공유네트워크는 공인 IP를 가지며, 우리가 공유기, 공유 네트워크에 연결해서 사용하는 핸드폰과 같은 기기의 IP는 사설 IP로 사용한다. 따라서, 공인 IP의 몇번 포트로 신호가 들어오면, 공인 IP와 연결된 내부의 누군가에게 전달을 해야하는데, 이때 필요한 것이 포트포워딩이다.</p>
<p>NAT 기술은 공인 IP를 사설 IP로 변환하는 기술로, 포트포워딩을 이해하기 위해 필요한 개념이다.</p>
<p>포트포워딩 테이블은 8080포트는 내부 IP의 80번 포트로 보낸다 와 같은 규칙을 가지고 있다.</p>
<h3 id="다중-ip를-vm에-부여하는-이유">다중 IP를 VM에 부여하는 이유</h3>
<p>다중 IP를 VM에 부여하는 이유는 다양하다.</p>
<ol>
<li>포트 중복을 회피하기 위함</li>
</ol>
<p>보통 웹서비스는 HTTP/HTTPS의 80, 443 포트를 사용하는데, IP가 하나라면 한 서비스가 하나의 포트를 점유했을 경우 다른 서비스는 8080과 같은 생소한 포트를 사용해야한다. 따라서, VM에 다양한 서비스가 존재한다면 서비스별로 IP를 나누고 포트 번호를 80, 443을 사용할 수 있도록 한다.</p>
<ol start="2">
<li>서버 장애 대응</li>
</ol>
<p>서버 장애 발생시 서비스용 IP를 따로 만들어두었다면, 메인 서버에 장애가 생겼을시, 그 IP를 즉시 대기중인 다른 서버로 옮길 수 있다.</p>
<ol start="3">
<li>트래픽 분산</li>
</ol>
<p>서버로 드나드는 데이터의 성격에 따라, 외부 사용자가 접속하는 IP와 내부 관리자가 백업/모니터링을 위해 사용하는 IP를 분리해 관리용 트래픽이 폭주해도 실제 서비스 트래픽에 영향을 주지않으며, 방화벽도 IP 별로 다르게 지정할 수 있어서 안전하다.</p>
<h2 id="vm-다중-ip-할당-및-컨테이너-포트-포워딩">VM 다중 IP 할당 및 컨테이너 포트 포워딩</h2>
<h3 id="준비사항">준비사항</h3>
<ul>
<li><p>VM 1대 (Vmware Workstation) / cpu-2core, mem-4GB, DISK-20GB</p>
<ul>
<li>Ubuntu 24.04</li>
<li>테스트 컨테이너 test-nginx1</li>
<li>테스트 컨테이너 test-nginx2</li>
</ul>
</li>
<li><p>설치</p>
<ul>
<li>docker gnu</li>
<li>docker compose</li>
<li>nginx</li>
</ul>
</li>
</ul>
<p>NGINX 컨테이너 2대 실행</p>
<ul>
<li>프로젝트 파일 생성<ul>
<li>html 파일 (nginx1, nginx2) + log 파일 생성</li>
</ul>
</li>
</ul>
<ul>
<li><p>docker compose 파일 작성</p>
<ul>
<li>프로젝트 파일 하단에 생성<pre><code>version: &#39;3.7&#39;
services:
nginx1:
container_name: test-nginx1
image: &quot;nginx:latest&quot;
restart: always
volumes:
- /var/log/nginx:/var/log/nginx # log파일의 위치
- ./html/index1.html:/usr/share/nginx/html/index.html # index1.html 위치  
environment:
- TZ=Asia/Seoul
networks:
mynet1:
  ipv4_address: 192.168.1.10
</code></pre></li>
</ul>
<p>nginx2:
  container_name: test-nginx2
  image: &quot;nginx:latest&quot;
  restart: always
  volumes:</p>
<ul>
<li>/var/log/nginx:/var/log/nginx # log파일의 위치</li>
<li>./html/index2.html:/usr/share/nginx/html/index.html # index2.html 위치
environment:</li>
<li>TZ=Asia/Seoul
networks:
mynet2:
 ipv4_address: 192.168.2.10</li>
</ul>
</li>
</ul>
<p>networks:
  mynet1:
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: 192.168.1.0/24</p>
<p>  mynet2:
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: 192.168.2.0/24</p>
<pre><code>

- docker 실행

docker-compose up -d



### rc.local

- rc.local
    - 리눅스 시스템에서 부팅시 자동으로 실행되는 스크립트 파일 중 하나로, 사용자 지정 명령이나 스크립트를 포함할 수 있는 장소로 사용되며, 실행 권한이 없으면 서버 재가동시 스크립트가 실행되지 않는다.
</code></pre><p>// 상태 체크
systemctl status rc-local
systemctl start rc-local
systemctl enable rc-local
systemctl list-unit-files | grep rc.local // enable 확인</p>
<pre><code>


### 다중 포트 할당

- rc.local 실행 권한 부여 및 실행
</code></pre><p>chmod +x /etc/rc.local</p>
<p>systemctl start rc-local.service</p>
<pre><code>
- 그래픽 모드 아닌, 멀티 유저 모드로 부팅시 서비스 파일에 부팅 모드 설정 및 실행
    - `/usr/lib/systemd/system/rc-local.service`
</code></pre><p>#제일 하단에 추가 후 저장
[Install]
WantedBy=multi-user.target</p>
<h1 id="rc-local서비스-자동-실행-등록">rc-local서비스 자동 실행 등록</h1>
<p>systemctl enable rc-local.service</p>
<pre><code>
- 추가 IP 부여</code></pre><h1 id="서버가-부팅될때마다-아이피-추가-하는-명렁어-작성">서버가 부팅될때마다 아이피 추가 하는 명렁어 작성</h1>
<p> vi /etc/rc.local</p>
<h1 id="vm에-아이피-부여">vm에 아이피 부여</h1>
<p> ip addr add 192.168.130.234/24 dev ens33</p>
<h1 id="재시작">재시작</h1>
<p> systemctl restart rc-local.service</p>
<pre><code>
 ![](https://velog.velcdn.com/images/_030702/post/30eaac84-9d47-418b-8cfa-fb09871e4f88/image.png)




### 네트워크 설정 파일 수정

- 설정 파일 이름 확인
    - cd /etc/netplan 으로 확인
- 설정 파일 수정
    - 추가 IP 를 하단에 작성

![](https://velog.velcdn.com/images/_030702/post/c0061781-93d6-4ed1-94db-be4539c0c9ae/image.png)


- 네트워크 재시작
    - systemctl restart NetworkManager


## VM IP 별 포트 포워딩 설정

- 경로
    - 192.168.11.133 / TCP 80 → test-nginx1 / TCP 80
    - 192.168.130.134 / TCP 80 → test-nginx2 / TCP 80

- 포트포워딩 등록
    - /etc/rc.local 에 작성
![](https://velog.velcdn.com/images/_030702/post/36deb4c9-d0c2-437d-86aa-28f44cbcd3a7/image.png)

- 포트포워딩 등록 순서
    - nat 테이블에 192.168.11.133:80으로 들어오는 패킷을 192.168.1.10:80으로 nat 포워딩하는 rule을 추가.
    - 192.168.1.10:80인 TCP 패킷이 서버를 Forward할때, 이를 허용하는 rule을 추가
    - nat 테이블에 192.168.130.134:80으로 들어오는 패킷을 192.168.2.10:80으로 nat 포워딩하는 rule을 추가
    - 192.168.2.10:80인 TCP 패킷이 서버를 Forward할때, 이를 허용하는 rule을 추가

- 포트포워딩 된 ip 주소 조회 및 네트워크 포워딩 조회
</code></pre><h1 id="포트포워딩-된-ip-주소-조회">포트포워딩 된 ip 주소 조회</h1>
<p>iptables -t nat -L -n -v</p>
<h1 id="네트워크-포워딩-조회">네트워크 포워딩 조회</h1>
<p>iptables -L -n -v</p>
<h1 id="iptables-rule-조회">iptables rule 조회</h1>
<p>iptables -L </p>
<h1 id="nat-테이블-조회">nat 테이블 조회</h1>
<p>iptables -t nat -nL</p>
<pre><code>


### Nginx 설정 파일 수정으로 IP 별로 다른 화면 나오도록 수정

- /etc/nginx/sites-available/default
</code></pre><p>map $server_addr $display_file {
    192.168.11.133      index_133.html;
    192.168.130.134     index_134.html;
    default             index.html;
}</p>
<p>server {
...
index $display_file;
...
}</p>
<pre><code>
- nginx 재가동



### 결과
![](https://velog.velcdn.com/images/_030702/post/2d74b9b3-e21c-4771-892c-14dd22ce40e9/image.png)



### 참고자료

https://m.blog.naver.com/anjae83/222064015099 

https://fehoon.tistory.com/103 

https://velog.io/@watervottle/linux-%EC%84%9C%EB%B2%84%EC%9D%98-%EC%83%81%ED%83%9C-%ED%99%95%EC%9D%B8top-free-m-df-Th </code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Reflection]]></title>
            <link>https://velog.io/@_030702/Reflection</link>
            <guid>https://velog.io/@_030702/Reflection</guid>
            <pubDate>Tue, 19 May 2026 23:47:59 GMT</pubDate>
            <description><![CDATA[<p>자바의 리플렉션은 클래스나 메서드의 메타정보를 이용해 메서드를 동적으로 호출하는 방법이다.</p>
<pre><code class="language-jsx">@Slf4j
static class Hello {
    public String callA() {
        log.info(&quot;callA&quot;);
        return &quot;A&quot;;
    }
    public String callB() {
        log.info(&quot;callB&quot;);
        return &quot;B&quot;;
    }
}</code></pre>
<pre><code class="language-jsx">@Test
void reflection0 {
        Hello target = new Hello();

    log.info(&quot;start&quot;); // 부가기능
    String result1 = target.callA(); // 핵심기능
    log.info(&quot;result = {}&quot;, result1); // 부가기능

    log.info(&quot;start&quot;);
    String result2 = target.callB();
    log.info(&quot;result = {}&quot;, result2);
}</code></pre>
<p>이 예시코드에서는 부가 기능 사이에 핵심 기능이 있고, 해당 핵심 기능은 계속해서 변화하기에 메서드로 따로 빼내기에 애매하다.</p>
<pre><code class="language-jsx">// 이렇게 쪼개면...
void logStart() { log.info(&quot;start&quot;); }
void logResult(String result) { log.info(&quot;result = {}&quot;, result); }

// 쓸 때는 여전히 중복이 남고 지저분합니다.
logStart();
String result1 = target.callA();
logResult(result1);

logStart();
String result2 = target.callB();
logResult(result2);</code></pre>
<p>예를들어 메서드로 따로 뺀다 하더라도 이렇게 중복이 남고 지저분한 코드가 완성된다.</p>
<p>따라서, 해당 문제를 해결하기 위해 리플렉션을 사용한다. 클래스와 메서드의 정보를 빼내어 동적으로 사용하는 방식이다. </p>
<pre><code class="language-jsx">@Test
void reflection1() throws ClassNotFoundException, NoSuchMethodException,
            InvocationTargetException, IllegalAccessException {

    // 경로로 클래스의 메타정보를 가져와 리플렉션 객체를 생성한다.
    Class classHello = Class.forName(&quot;hello.proxy.jdkdynamic.ReflectionTest$Hello&quot;);
    // 실제 객체를 생성한다.
    Hello target = new Hello();

    // 리픒렉션 객체에서 매서드 정보를 가져온다.
    Method methodCallA = classHello.getMethod(&quot;callA&quot;);
    // 매서드가 호출될 객체를 파라미터로 넣는다.
    // 호출의 결과를 Object로 반환한다.
    Object result1 = methodCallA.invoke(target);
    log.info(&quot;result = {}&quot;, result1);

    Method methodCallB = classHello.getMethod(&quot;callB&quot;);
    Object result2 = methodCallB.invoke(target);
    log.info(&quot;result = {}&quot;, result2);
}</code></pre>
<p>invoke() 메서드를 이용해 해당 메서드를 호출하게 되고, 결과를 Object 타입으로 변환시키는 것이다. 이때 주의해야할 점은 invoke() 메서드의 파리미터로는 실제 객체가 들어가야한다. </p>
<pre><code class="language-jsx">@Test
void reflection2() throws Exception {

    Class classHello = Class.forName(&quot;hello.proxy.jdkdynamic.ReflectionTest$Hello&quot;);
    Hello target = new Hello();

    Method methodCallA = classHello.getMethod(&quot;callA&quot;);
    dynamicCall(methodCallA, target);

    Method methodCallB = classHello.getMethod(&quot;callB&quot;);
    dynamicCall(methodCallB, target);
}

// 동적으로 리플렉션 매서드와 실제 객체를 받아 사용한다.
private void dynamicCall(Method method, Object target) throws Exception {
    log.info(&quot;start&quot;);
    Object result = method.invoke(target);
    log.info(&quot;result = {}&quot;, result);
}</code></pre>
<p>조금 더 간소화 하자면 이렇다.</p>
<p>하지만 리플렉션은 런타임시 동작하기 때문에 컴파일 시점에 오류를 알 수 없다는 단점이 존재한다. 따라서, 리플렉션의 사용을 최대한 지양한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[AI 컴퓨터 분야 ]]></title>
            <link>https://velog.io/@_030702/AI-%EC%BB%B4%ED%93%A8%ED%84%B0-%EB%B6%84%EC%95%BC</link>
            <guid>https://velog.io/@_030702/AI-%EC%BB%B4%ED%93%A8%ED%84%B0-%EB%B6%84%EC%95%BC</guid>
            <pubDate>Mon, 18 May 2026 23:32:17 GMT</pubDate>
            <description><![CDATA[<h3 id="computer-vision-cv">computer vision (CV)</h3>
<p>컴퓨터에게 시각적인 부분을 제공</p>
<p>컴퓨터가 이미지나 동영상 같은 시각 데이터를 이해하고 해석할 수 있도록 만드는 기술</p>
<ul>
<li>이미지 분류 : 사진을 보고 고양이인지, 강아지인지 맞추는 작업</li>
<li>객체 탐지 : 사진 속에서 물체의 위치를 네모 박스로 찾고 무엇인지 알아내는 작업</li>
<li>생성 : 존재하지 않는 가상의 이미지나 실사 같은 영상을 만들어내는 작업</li>
</ul>
<h3 id="natural-language-processing-nlp">Natural Language Processing (NLP)</h3>
<p>컴퓨터에게 사람의 말과 글을 알아듣고 소통하게 만드는 분야</p>
<p>인간이 일상적으로 쓰는 언어는 컴퓨터가 이해하는 이진수와 달리 맥락, 비유, 유머가 섞여있어 아주 복잡하나 이를 컴퓨터가 분석, 이해, 생성할 수 있도록 처리하는 기술임.</p>
<ul>
<li>텍스트 분류 및 감성 분석 : 리뷰 글을 읽고 긍정, 부정 파악하기</li>
<li>기계 번역 : 파파고나 구글 번역기 처럼 한 언어를 다른 언어로 바꾸기</li>
<li>챗봇 및 LLM (대형 언어 모델) : chatGPT 처럼 사람이 질문을 던지면 맥락을 이해하고 자연스럽게 대답하거나 글을 창작해주는 작업</li>
</ul>
<h3 id="computer-code">Computer Code</h3>
<p>AI가 프로그래밍 언어를 이해하고 직접 코드를 짜거나 버그를 잡는 분야이며, 최근 LLM의 발전으로 가장 가파르게 성장함</p>
<ul>
<li>코드 자동 완성</li>
<li>코드 리뷰 및 버그 탐지 : 작성된 코드에서 에러가 날만한 부분 NPE나 보안 취약점을 자동으로 찾아내는 작업</li>
<li>리팩토링 및 버전 업그레이드</li>
</ul>
<h3 id="playing-games">Playing Games</h3>
<p>게임 규칙을 배우고, 스스로 판단해서 게임에서 이기는 법을 터득하는 분야. 바둑, 체스, 스타크래프트같은 환경에서 시행착오를 겪으며 최적의 행동을 학습하는 분야</p>
<p>강화학습이라고도 부름</p>
<ul>
<li>보드게임 정복 (AlphaGo)</li>
<li>e스포츠 및 시뮬레이션</li>
<li>현실 세계 적용 (게임에서 검증된 자율적 판단 알고리즘을 현실 세계의 로봇 제어나 자율주행 주차 시스템에 적용)</li>
</ul>
<h3 id="methodology">Methodology</h3>
<p>앞서 말한 모든 AI 기술을 어떻게 하면 더 빠르고 정확하고 효율적으로 만들지에 대한 이론과 방법</p>
<p>특정 데이터에 집중하기 보다 모델을 학습시키는 알고리즘 자체의 구조, 수학적 최적화, 데이터 효율성을 공부하는 원천 기술 연구 분야임</p>
<ul>
<li>최적화 : AI 모델이 정답을 더 빠르게 찾아가도록 수학적 공식을 개선하게 하는 연구</li>
<li>MLOps (머신러닝 운영 방법론) : 개발된 AI 모델을 서버에 안정적으로 배포하고 모니터링하고 지속적으로 업데이트하는 시스템적 절차 연구</li>
<li>경량화 : 거대한 AI 모델의 성능은 유지하며 크기를 줄여 스마트폰이나 로컬 서버에서도 가볍게 돌아가도록 만드는 기술</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[XSS 공격 및 lucy-xss-filter 대안]]></title>
            <link>https://velog.io/@_030702/XSS-%EA%B3%B5%EA%B2%A9-%EB%B0%8F-lucy-xss-filter-%EB%8C%80%EC%95%88</link>
            <guid>https://velog.io/@_030702/XSS-%EA%B3%B5%EA%B2%A9-%EB%B0%8F-lucy-xss-filter-%EB%8C%80%EC%95%88</guid>
            <pubDate>Mon, 18 May 2026 23:31:09 GMT</pubDate>
            <description><![CDATA[<p>XSS</p>
<ul>
<li>Cross Site Scripting</li>
<li>게시판이나 웹 메일에 자바 스크립트와 같은 스크립트 코드를 삽입해 개발자가 고려하지 않은 기능이 작동하도록 하는 공격</li>
<li>사용자를 대상으로 한 공격임 대부분</li>
</ul>
<p>위험성</p>
<ul>
<li>쿠키 정보 및 세션 ID를 획득할 수 있음</li>
<li>시스템 관리자 권한 획득할 수 있음 (사용자의 브라우저가 악성 데이터를 실행할 수 있도록 함)</li>
<li>악성 프로그램으로 redirect 하거나, 트로이 목마 프로그램을 다운로드 하도록 유도할 수 있다</li>
</ul>
<p>대안책</p>
<ul>
<li>script 문자 필터링<ul>
<li>&lt;, &gt;, “, ‘ 과 같은 문자를 필터링 함</li>
<li>htmlentities 함수를 사용해 특수문자를 HTML 엔티티로 변환함</li>
</ul>
</li>
</ul>
<p>java-lucy-filter </p>
<ul>
<li>지원이 끊겨서 javax→jakarta로 바꾸어 사용 가능 (직접 커스텀)</li>
<li>화이트 리스트 방식 채택<ul>
<li>모두가 차단된 상태에서 예외적으로 접근 가능한 대상을 지정</li>
<li>블랙리스트는 기본 정책이 모두 허용된 상태에서 에외적으로 차단하는 대상을 지정</li>
</ul>
</li>
</ul>
<p>lucy-filter github</p>
<ul>
<li><a href="https://github.com/naver/lucy-xss-servlet-filter">https://github.com/naver/lucy-xss-servlet-filter</a></li>
</ul>
<p>취약점 발견</p>
<ul>
<li>입력단계에서 escape하지만, sanitize-html 모듈 누락 + 출력단계에서는 unescape</li>
</ul>
<pre><code class="language-java">const sanitizeHtml = require(&#39;sanitize-html&#39;);

const dirty = `h1태그는 &lt;h1&gt;링크&lt;/h1&gt; 무시가 될까?`;

const sanitizedDescription = sanitizeHtml(dirty, {
    allowedTags:[&#39;h1&#39;, &#39;a&#39;], // h1 , a 태그 허용
    allowedAttributes: { a: [&#39;href&#39;] }, // a 태그의 href 속성 허용
    allowedFrameHostnames: [&#39;www.youtube.com&#39;] // iframe 허용하되 유튜브 사이트만 허용
});

console.log(sanitizedDescription); // 출력 : h1태그는 &lt;h1&gt;링크&lt;/h1&gt; 무시가 될까?</code></pre>
<ul>
<li>Lucy-XSS-filter가 Json 요청은 처리하지 않음<ul>
<li>서블릿 필터 동작방식과 데이터 읽기 방식에 차이가 존재하기 때문</li>
<li>Lucy는 HttpServletRequest.getParameters()를 가로채도록 설계되어있는데, JSON은 이 메서드를 통해 전달되지않음</li>
</ul>
</li>
<li>JSON은 request.getInputStream(), getReader()를 호출해야하는데, 전자의 한계는 HTTP요청의 Body 스트림은 한번만 읽을 수 있어, ContentCachingRequestWrapper와 같은 아이가 본문을 복사해두어야함 후자의 한계는 목적 자체가 form 태그를 통한 공격을 막기 위해 만들어졌다는 것</li>
</ul>
<p>대안 방법
→ Jackson ObjectMapper custom 시행</p>
<p>참고 문서
<a href="https://riverblue.tistory.com/46">https://riverblue.tistory.com/46</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Security와 로그인 ]]></title>
            <link>https://velog.io/@_030702/Spring-Security%EC%99%80-%EB%A1%9C%EA%B7%B8%EC%9D%B8</link>
            <guid>https://velog.io/@_030702/Spring-Security%EC%99%80-%EB%A1%9C%EA%B7%B8%EC%9D%B8</guid>
            <pubDate>Mon, 18 May 2026 23:28:45 GMT</pubDate>
            <description><![CDATA[<h3 id="로그인의-시작">로그인의 시작</h3>
<p>사용자가 로그인하면:</p>
<ul>
<li>일반 로그인: <code>username + password</code>를 검증</li>
<li>OAuth 로그인: 외부 서비스에서 받은 토큰 검증</li>
</ul>
<p>로그인이 성공하면 <strong>Spring Security가 사용자 정보를 세션이나 JWT 등에 저장</strong>함.</p>
<hr>
<h3 id="spring-security가-인증-정보를-관리하는-방식">Spring Security가 인증 정보를 관리하는 방식</h3>
<p>Spring Security에는 <strong>SecurityContext</strong>라는 공간이 있음. 여기서 인증된 사용자 정보(<code>Authentication</code>)를 저장.</p>
<p>예를 들어, 로그인 후엔 다음 구조과 같은 구조가 생김.</p>
<pre><code>SecurityContext
   └── Authentication
          ├── principal-&gt; UserDetails 객체 (사용자 ID, 권한 등)
          ├── credentials-&gt; 비밀번호 등 (보통 null)
          ├── authorities-&gt; 권한 목록</code></pre><ul>
<li><code>principal</code>이 <strong>현재 로그인한 사용자 정보</strong></li>
<li>Spring MVC 컨트롤러에서 쉽게 가져오기 가능</li>
</ul>
<hr>
<h3 id="컨트롤러에서-로그인-사용자-확인하기">컨트롤러에서 로그인 사용자 확인하기</h3>
<pre><code class="language-java">@GetMapping(&quot;/me&quot;)public StringgetMyInfo(Authentication authentication) {UserDetailsuserDetails= (UserDetails) authentication.getPrincipal();
    System.out.println(&quot;현재 로그인한 사용자 ID: &quot; + userDetails.getUsername());return&quot;ok&quot;;
}</code></pre>
<p>또는 더 간단하게:</p>
<pre><code class="language-java">@GetMapping(&quot;/me&quot;)public StringgetMyInfo(@AuthenticationPrincipal UserDetails userDetails) {
    System.out.println(&quot;현재 로그인한 사용자 ID: &quot; + userDetails.getUsername());return&quot;ok&quot;;
}</code></pre>
<p>즉, <strong>아이디 변경 API, 비밀번호 변경 API 등</strong>에서는 요청을 보낸 사람이 누구인지 <code>Authentication</code>이나 <code>@AuthenticationPrincipal</code>로 확인 가능.</p>
<hr>
<h3 id="jwt를-사용하는-경우">JWT를 사용하는 경우</h3>
<p>로그인 후 JWT를 발급하면:</p>
<ul>
<li>요청 시 HTTP Header에 <code>Authorization: Bearer &lt;토큰&gt;</code> 추가</li>
<li>Spring Security Filter가 토큰을 검증하고, 유효하면 <strong>SecurityContext에 사용자 정보 저장</strong></li>
</ul>
<p>결과적으로 JWT 방식이든 세션 방식이든, <strong>컨트롤러에서는 항상 <code>Authentication</code>에서 로그인 사용자 정보를 꺼낼 수 있음</strong>.</p>
<hr>
<h3 id="아이디비밀번호-변경-시-체크">아이디/비밀번호 변경 시 체크</h3>
<p>API 예시:</p>
<pre><code class="language-java">@PutMapping(&quot;/user/username&quot;)public ResponseEntity&lt;?&gt; changeUsername(@RequestBody ChangeUsernameRequest req,@AuthenticationPrincipal UserDetails userDetails) {
    userService.changeUsername(userDetails.getUsername(), req.getNewUsername());return ResponseEntity.ok().build();
}</code></pre>
<ul>
<li>여기서 중요한 점: <strong>클라이언트가 전달한 사용자 ID를 사용하지 않고, SecurityContext에서 가져온 로그인 사용자 ID를 사용</strong></li>
<li>안 그러면 다른 사람의 ID를 바꿀 수도 있음 → 보안 취약점</li>
</ul>
<hr>
<p>정리하면:</p>
<ol>
<li>로그인 성공 → SecurityContext에 사용자 정보 저장</li>
<li>요청 시 Spring Security가 SecurityContext에서 사용자 정보 확인</li>
<li>컨트롤러에서 <code>Authentication</code> 또는 <code>@AuthenticationPrincipal</code>로 로그인 사용자 확인</li>
<li>이 정보로만 아이디/비밀번호 변경 등 민감한 작업 수행</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Ubuntu 리눅스 쿠버네티스 클러스터 구축하기]]></title>
            <link>https://velog.io/@_030702/Ubuntu-%EB%A6%AC%EB%88%85%EC%8A%A4-%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-%ED%81%B4%EB%9F%AC%EC%8A%A4%ED%84%B0-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@_030702/Ubuntu-%EB%A6%AC%EB%88%85%EC%8A%A4-%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-%ED%81%B4%EB%9F%AC%EC%8A%A4%ED%84%B0-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 14 Mar 2026 14:21:42 GMT</pubDate>
            <description><![CDATA[<h2 id="0-쿠버네티스란">0. 쿠버네티스란?</h2>
<p>쿠버네티스 클러스터는 컨테이너화 된 애플리케이션을 실행하는 컴퓨팅 노드이다.</p>
<p>컨테이너화 된 애플리케이션의 대규모 배포, 스케일링 및 관리를 간편하게 만들어주며, 오픈 소스 기반의 컨테이너 오케스트레이션이다.</p>
<pre><code>오케스트레이션(Orchestration)은 
컨테이너, 서버, 애플리케이션, 데이터 파이프라인 등 
여러 IT 구성 요소와 자동화된 태스크들을 연동하여 
복잡한 워크플로우를 효율적으로 관리 및 조율하는 기술을 의미한다.</code></pre><p>도커는 애플리케이션의 프로세스 격리 기술을 사용해 컨테이너로 관리할 수 있는 도구라면, </p>
<p>쿠버네티스는 그런 컨테이너를 간편하게 관리할 수 있게 도와준다고 보면 된다.</p>
<h2 id="1-준비사항">1. 준비사항</h2>
<p>Ubuntu 24.04.1 LTS / VMware workstation Pro</p>
<ul>
<li><p>Master Node 1대 (CPU 2 Core, RAM 2GB)  </p>
</li>
<li><p>Worker Node 2대 (CPU 1 Core, RAM 1GB)</p>
</li>
</ul>
<p>사양 확인</p>
<ul>
<li><p>코어 nproc</p>
</li>
<li><p>메모리 free -h</p>
</li>
<li><p>ifconfig -a</p>
</li>
<li><p>uuid sudo cat /sys/class/dmi/id/product_uuid</p>
</li>
</ul>
<p>모든 명령어 입력은 root 계정에서 진행한다.</p>
<ul>
<li>sudo su</li>
</ul>
<h2 id="2-순서">2. 순서</h2>
<ol>
<li><p>쿠버네티스 설치 준비</p>
</li>
<li><p>컨테이너 런타임 설치</p>
</li>
<li><p>쿠버네티스 설치</p>
</li>
<li><p>Master Node 설정</p>
</li>
<li><p>Worker Node 등록</p>
</li>
<li><p>최종 확인</p>
</li>
</ol>
<h2 id="3-swap-비활성화">3. swap 비활성화</h2>
<p>쿠버네티스의 대표적인 역할은 스케줄링(어느 노드에 어떤 컨테이너 (=Pod)를 배치할까)</p>
<p>스케줄러는 각 노드의 가용자원(CPU, Memory)를 정확히 파악해서 Pod를 배치함</p>
<p>만약 swap이 켜져있으면, 노드의 실제 물리 메모리가 부족할 때 디스크의 일부를 메모리처럼 사용하게 됨 </p>
<p>이럴 경우, Pod를 계속 보낼 수 있지만, 실제로는 디스크 I/O 성능이 급격히 저하될 수 있음</p>
<p><strong>따러서, 스케줄러가 자원 상태를 100% 예측 가능하게 관리하기 위해서 변수가 되는 스왑을 제거하는 것임.</strong></p>
<pre><code># 영구 비활성화
sudo sed -i &#39;/swap/s/^/#/&#39; /etc/fstab

# 메모리 상태확인
sudo free -m

# swap 메모리 상태 확인 (출력값 없으면 비활성화 상태)
sudo swapon -a</code></pre><p> <img src="https://velog.velcdn.com/images/_030702/post/2270e998-e87f-4343-8449-d833b3bb00b9/image.png" alt=""></p>
<h2 id="4-방화벽-설정">4. 방화벽 설정</h2>
<p>쿠버네티스 포트만 개방 / 방화벽 비활성화 두가지 방법 중 하나를 선택해서 진행함.</p>
<p>필자의 경우 전자를 선택해서 진행. </p>
<pre><code># 방화벽 설치 및 예외 설정 
sudo apt-get install -y firewalld
sudo systemctl start firewalld
sudo systemctl enable firewalld
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https</code></pre><p><img src="https://velog.velcdn.com/images/_030702/post/9538d210-d623-42b8-974c-64c00ac2fb06/image.png" alt="">
<img src="https://velog.velcdn.com/images/_030702/post/bd2256cf-2520-4e04-84b3-d67ea083d1d5/image.png" alt=""></p>
<h3 id="master-node-인-경우">Master node 인 경우</h3>
<p>TCP</p>
<ul>
<li><p>6443 Kubernetes API Server</p>
</li>
<li><p>2379-2380 etcd server API (DB)</p>
</li>
<li><p>10250 Kubernetes API</p>
</li>
<li><p>10251 kube-scheduler</p>
</li>
<li><p>10252 kube-controller-manager </p>
</li>
</ul>
<p>UDP</p>
<ul>
<li><p>CNI (Container network interface) 용도</p>
</li>
<li><p>8285 Flannel (UDP)</p>
</li>
<li><p>8472 Flannel (VXLAN)</p>
</li>
</ul>
<pre><code>sudo firewall-cmd --permanent --add-port=6443/tcp
sudo firewall-cmd --permanent --add-port=2379-2380/tcp
sudo firewall-cmd --permanent --add-port=10250-10252/tcp
sudo firewall-cmd --permanent --add-port=8285/udp
sudo firewall-cmd --permanent --add-port=8472/udp
sudo firewall-cmd --reload</code></pre><p><img src="https://velog.velcdn.com/images/_030702/post/6d3beb0a-5259-4921-bf09-663c58bb5bd2/image.png" alt=""></p>
<h3 id="worker-node-인-경우">Worker node 인 경우</h3>
<p>TCP</p>
<ul>
<li><p>10250 kubelet API</p>
</li>
<li><p>30000-32767 Nodeport services (외부에서 쿠버네티스 내부 서비스에 접속할 때 사용함</p>
</li>
</ul>
<p>UDP</p>
<ul>
<li>8285/8472 Flannel </li>
</ul>
<pre><code>sudo firewall-cmd --permanent --add-port=10250/tcp
sudo firewall-cmd --permanent --add-port=30000-32767/tcp
sudo firewall-cmd --permanent --add-port=8285/udp
sudo firewall-cmd --permanent --add-port=8472/udp
sudo firewall-cmd --permanent --add-port=26443/tcp
sudo firewall-cmd --reload</code></pre><p><img src="https://velog.velcdn.com/images/_030702/post/d4e02d92-eaea-47ea-83de-e0114a34bf5e/image.png" alt=""></p>
<h3 id="방화벽-오픈-상태-확인">방화벽 오픈 상태 확인</h3>
<pre><code>#열린 포트 확인
sudo firewall-cmd --list-all</code></pre><p><img src="https://velog.velcdn.com/images/_030702/post/646a1d2a-c272-46b2-96ad-9c9382b41100/image.png" alt="">👆 worker node 
<img src="https://velog.velcdn.com/images/_030702/post/6bee8421-57b7-46f2-9e98-16c1b36d7c76/image.png" alt="">👆 master node</p>
<h2 id="5-네트워크-옵션-설정">5. 네트워크 옵션 설정</h2>
<p>리눅스 서버의 네트워크 기능을 쿠버네티스용 (= 컨테이너 통신용)으로 바꾸는 과정</p>
<p><code>br_netfilter</code></p>
<ul>
<li><p>리눅스 서버는 기본적으로 물리적인 통신을 담당</p>
</li>
<li><p>하지만, 쿠버네티스는 서버 안에 가상 네트워크를 만들고, 그 안에서 Pod들이 통신하도록 함</p>
</li>
<li><p>br_netfilter는 리눅스 커널의 bridge 레이어를 통과하는 패킷들을 iptables(방화벽/네트워크 규칙)이 검사할 수 있게 함</p>
</li>
</ul>
<p><code>k8s.conf</code></p>
<ul>
<li>컨테이너 간의 통신이 리눅스 방화벽 규칙을 따르도록 강제하는 설정 </li>
</ul>
<pre><code># /etc/modules-load.d/k8s.conf 파일 생성
sudo cat &lt;&lt;EOF | sudo tee /etc/modules-load.d/k8s.conf
br_netfilter
EOF
# /etc/sysctl.d/k8s.conf 파일 생성
sudo cat &lt;&lt;EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
# 시스템 재시작 없이 stysctl 파라미터 반영
sudo sysctl --system</code></pre><p><img src="https://velog.velcdn.com/images/_030702/post/db2352e4-8f94-4fdc-b071-35874a0d4b82/image.png" alt=""></p>
<h2 id="6-컨테이너-런타임-설치">6. 컨테이너 런타임 설치</h2>
<p>쿠버네티스는 컨테이너를 관리하는 오케스트레이션 플랫폼이기 때문에, 컨테이너 런타임을 별도로 설치해야한다.</p>
<p>세가지 방법이 존재하는데, 두번째 방법 (containerd만 설치해서 사용하는 방법을 선택했다)</p>
<ol>
<li><p>docker 설치 시 Depends로 함께 설치되는 containerd 패키지 사용</p>
</li>
<li><p>containerd만 설치 사용</p>
</li>
<li><p>docker engine + cri-dockerd </p>
</li>
</ol>
<h3 id="containerd-설치-후-사용">Containerd 설치 후 사용</h3>
<h4 id="apt-업데이트-후-필수-패키지-설치">apt 업데이트 후 필수 패키지 설치</h4>
<pre><code>sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl gnupg</code></pre><p><img src="https://velog.velcdn.com/images/_030702/post/f0f899d2-6438-4a6f-9e75-8c2c95cbc07d/image.png" alt=""></p>
<h4 id="공개키-다운로드-및-저장소-등록">공개키 다운로드 및 저장소 등록</h4>
<p>공개키는 인터넷에서 파일을 다운로드할때, 해커가 심어놓은 악성코드인지 혹은 진짜 Docker인지 확인하기 위해서 사용함</p>
<pre><code>#공개키 다운로드
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
# 저장소 등록
echo &quot;deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable&quot; | sudo tee /etc/apt/sources.list.d/docker.list &gt; /dev/null</code></pre><p><img src="https://velog.velcdn.com/images/_030702/post/d69f9f7d-a249-4190-af9e-6344016d3bab/image.png" alt=""></p>
<h4 id="containerd-패키지-설치">containerd 패키지 설치</h4>
<p>쿠버네티스의 표준 런타임 </p>
<pre><code>sudo apt-get update
#containerd 패키지 설치
sudo apt-get install containerd
#설치 확인
sudo systemctl status containerd</code></pre><h4 id="containerd-config-설정">containerd config 설정</h4>
<p>리눅스의 주인은 systemd 인데, 쿠버네티스의 kubelet도 자원관리를 위해 이 systemd를 사용하길 바람.</p>
<p>이 설정을 안해주면 컨테이너 런타임과 시스템이 충돌하게 됨.</p>
<pre><code>#containerd 구성 파일 생성
sudo mkdir -p /etc/containerd
#containerd 기본 설정값으로 config.toml 생성
sudo containerd config default | sudo tee /etc/containerd/config.toml
#config.toml 파일 수정 SystemCgroup 설정을 true로 바꿈
vi /etc/containerd/config.toml
#수정사항 적용 및 재실행
sudo systemctl restart containerd</code></pre><p><img src="https://velog.velcdn.com/images/_030702/post/e4cfd685-ddd4-4a70-9f7a-c03ae6c3558e/image.png" alt="">
<img src="https://velog.velcdn.com/images/_030702/post/2d051d7c-f2a2-4185-b067-4052dfa78705/image.png" alt="">
<img src="https://velog.velcdn.com/images/_030702/post/224cbe77-3dca-4528-8b67-1f5ad1767d53/image.png" alt=""></p>
<h2 id="7-쿠버네티스-클러스터-설치">7. 쿠버네티스 클러스터 설치</h2>
<h4 id="필수-패키지-설치-및-구글-클라우드-공개키-다운로드-및-쿠버네티스-레포지토리-추가">필수 패키지 설치 및 구글 클라우드 공개키 다운로드 및 쿠버네티스 레포지토리 추가</h4>
<pre><code>sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl gnupg
#공개 키 다운로드
curl -fsSLo /etc/apt/keyrings/kubernetes-archive-keyring.gpg https://dl.k8s.io/apt/doc/apt-key.gpg
#apt 저장소에 쿠버네티스 저장소 추가
sudo echo &quot;deb [signed-by=/etc/apt/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main&quot; | sudo tee /etc/apt/sources.list.d/kubernetes.list</code></pre><p><img src="https://velog.velcdn.com/images/_030702/post/bf3e058c-3b6f-49c1-8195-8fe50cb5843d/image.png" alt=""></p>
<h4 id="쿠버네티스-패키지-설치">쿠버네티스 패키지 설치</h4>
<p>kubeadm : 클러스터를 구축(init, join)하는 도구</p>
<p>kubelet : 각 노드에서 컨테이너를 실제로 실행/관리함</p>
<p>kubectl : 사용자가 쿠버네티스에 명령을 내리는 도구</p>
<pre><code>#저장소 추가되었기 때문에 apt 업데이트
sudo apt-get update
#쿠버네티스 패키지 설치
sudo apt-get install -y kubelet kubeadm kubectl
#쿠버네티스 패키지 버전 고정
sudo apt-mark hold kubelet kubeadm kubectl
#쿠버네티스 설치 버전 조회
kubelet --version
kubeadm version
kubectl version
#kubelet service 확인
sudo systemctl status kubelet.service</code></pre><p><img src="https://velog.velcdn.com/images/_030702/post/42ad9550-6d93-4b79-a4d0-0d2aff912957/image.png" alt="">
<img src="https://velog.velcdn.com/images/_030702/post/c6a99a27-6108-4fb4-b9a0-03bab5be487d/image.png" alt=""></p>
<h4 id="문제-발생">문제 발생</h4>
<p>→ kubelet, kubeadm, kubectl 모두 설치됨</p>
<p>→ 하지만 kubelet.services가 안열림 </p>
<p><strong><em>대안책</em></strong></p>
<p>Docker 저장소 정보 삭제하고 재설치</p>
<p><img src="https://velog.velcdn.com/images/_030702/post/26abb6cc-d33a-4d66-a245-b67ec24bcede/image.png" alt="">
<img src="https://velog.velcdn.com/images/_030702/post/899f9ff1-3eac-41b7-ad69-26ff5966c034/image.png" alt=""></p>
<p>제대로 작동함을 확인 ! Master Node 구성</p>
<h2 id="8-control-plane-구성-및-cni-설정">8. Control-Plane 구성 및 CNI 설정</h2>
<p>Calico 기본 설정값 <code>192.168.0.0/16</code></p>
<pre><code># default
kubeadm init
# calico
sudo kubeadm init --pod-network-cidr=192.168.0.0/16</code></pre><p><img src="https://velog.velcdn.com/images/_030702/post/36bf4a1b-d184-4cd1-b46c-ba5cbcba9d28/image.png" alt="">
<img src="https://velog.velcdn.com/images/_030702/post/32e6a70a-058a-4940-8b2a-985335d9cbfd/image.png" alt="">
<img src="https://velog.velcdn.com/images/_030702/post/7b56d931-521d-40d6-9f57-f6a24d57cfe5/image.png" alt=""></p>
<p>netstat 보냈는데 답이 없음 = API 서버 죽어있음 </p>
<p><img src="https://velog.velcdn.com/images/_030702/post/343560f3-b7c6-4bd4-8809-d0225c7074a0/image.png" alt="">
<img src="https://velog.velcdn.com/images/_030702/post/a6a4f622-24e8-47a0-a5a5-72340a25cdca/image.png" alt=""></p>
<p>API 서버가 죽어있어서, kubectl 설정 삭제하고 다시 작성함.</p>
<p>후에, CNI(container network interface) 설정 진행</p>
<pre><code>*CNI (container network interface)

컨테이너 간의 네트워크를 제어할 수 있는 플러그인으로 컨테이너 런타임에서 컨테이너의 네트워크를 사용하게 하는 인터페이스임.</code></pre><h2 id="9-최종-확인">9. 최종 확인</h2>
<pre><code># connection
nc -vz [ip] [port]
# master node의 kubeadm token create --print-join-command
# 알아온 ip, port, token, hash 값을 worker node에서 실행
# master node와 연동됨
sudo kubeadm join [ip] [port] [token] [hash]
# master node에서 연결된 node 확인
hostname -I</code></pre><p><img src="https://velog.velcdn.com/images/_030702/post/ab53d960-82aa-452c-99ae-c8766036b6ac/image.png" alt="">👆 worker node
<img src="https://velog.velcdn.com/images/_030702/post/aaf268b3-2297-4b1d-953d-fa9d84394b27/image.png" alt="">👆 master node</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[vSphere, vCenter, ESXi, VMFS]]></title>
            <link>https://velog.io/@_030702/vSphere-vCenter-ESXi-VMFS</link>
            <guid>https://velog.io/@_030702/vSphere-vCenter-ESXi-VMFS</guid>
            <pubDate>Sat, 14 Mar 2026 14:11:00 GMT</pubDate>
            <description><![CDATA[<h3 id="esxi">ESXi</h3>
<ul>
<li>VMware에서 개발한 Hypervisor( 가상화 환경을 만들 수 있도록 최소한의 기능만을 지원하는 축소버전의 OS)</li>
<li>Type1 Hypervisor (bare-metal, os에 종속되지 않고 하드웨어에 직접 설치되는 구조)</li>
</ul>
<h3 id="vcenter">vCenter</h3>
<ul>
<li><p>가상화환경을 만드는 가장 큰 이유는 여러 컴퓨터를 하나의 큰 성능과 용량의 컴퓨터처럼 사용하기 위함</p>
<p>  ⇒ 이 기능을 지원하는 것이 vCenter</p>
</li>
<li><p>vCenter는 가상화 환경 구축을 위한 핵심 소프트웨어 ESXi가 설치된 다수의 머신을 하나의 가상화환경으로 만들어주는 기능을 제공함</p>
</li>
<li><p>vCenter를 이용해서 여러 ESXi 호스트들을 쉽게 추가/제거 할 수 있고, 그 외에도 vMotion, DRS&amp;DPM과 같은 기능을 제공함</p>
</li>
</ul>
<h3 id="vsphere">vSphere</h3>
<ul>
<li><p>VMware의 정의에 따르면 엔터프라이즈 급 가상화 플랫폼임</p>
</li>
<li><p>위에 언급한 소프트웨어들을 전부 포함하고 있는 소프트웨어 패키지를 의미</p>
<ul>
<li>Excel, Word= ESXi, vCenter</li>
<li>Microsoft Offic = vSphere</li>
</ul>
</li>
<li><p>소프트웨어 정의 데이터센터 (SDDC)</p>
<ul>
<li>소프트웨어-정의 데이터 센터는 서버, 네트워크, 스토리지 그리고 기반 설비 등의 모든 인프라가 가상화됨</li>
<li>데이터 센터의 제어 또한 소프트웨어에 의해 자동화 되는 것을 뜻함</li>
<li>vSphere은 가상 인프라 계층에서 ESXi 하이퍼바이저를 통해 서버 가상화 환경을 구현하고, 기능을 제공하는 분산 소프트웨어 시스템임</li>
<li>단순히 호스트를 분할하여 사용하는 것이 아니라, 인프라 자원을 하나의 거대한 가상 컴퓨터로 통합해 자원을 효율성있게 분할하여 사용함</li>
</ul>
</li>
<li><p>클라우드 환경</p>
<ul>
<li>vSphere은 규모에 맞는 간편하고 효율적인 관리, 원활한 하이브리드 클라우드 환경을 제공하여 향상된 애플리케이션 성능을 제공하고 모든 클라우드의 기반을 만들어줌</li>
<li>이를 통해서 IT 조직은 유연하고 안정적인 IT 서비스를 제공할 수 있음</li>
</ul>
</li>
</ul>
<h3 id="vsphere-환경을-사전에-관리하는-방법">vSphere 환경을 사전에 관리하는 방법</h3>
<ul>
<li><p>VMware Skyline</p>
<ul>
<li><p>잠재적인 문제를 사전에 식별하고 해결 시간을 단축하기 위해 고객별 제품 사용 데이터를 자동으로 안전하게 수집, 집계 및 분석을 하는 기술</p>
</li>
<li><p>VMware Skyline Collector 라는 독립형 어플라이언스 기기를 설치해야함</p>
<ul>
<li>문제사항회피 : 잠재적인 문제를 사전에 식별하고, 문제가 발생하기 전에 해결해서 환경에 대한 신뢰성과 안정성 향상을 도모함</li>
<li>문제해결시간단축 : 환경 별 데이터 기반 분석으로 문제 해결 시간을 단축함</li>
<li>사용자 맞춤 추천 : 사용자 환경에 따른 해결방안을 제시함.</li>
</ul>
<p>⇒ vSphere 구독시 기본으로 제공함</p>
</li>
</ul>
</li>
</ul>
<h3 id="vsphere-vmfs-가상-시스템-파일-시스템">vSphere VMFS (가상 시스템 파일 시스템)</h3>
<ul>
<li>클러스터 파일 시스템<ul>
<li>클러스터 : 여러대의 컴퓨터들이 연결되어 하나의 시스템처럼 동작하는 집합</li>
</ul>
</li>
<li>여러 ESXi 호스트가 공유 스토리지를 동시에 읽거나 쓸 수 있음을 뜻함 ⇒ 분산 스토리지 구조 제공</li>
</ul>
<h3 id="vsphere가-cpu-메모리-네트워크-disk와-어떤-방식으로-상호작용하는가">vSphere가 CPU, 메모리, 네트워크, Disk와 어떤 방식으로 상호작용하는가?</h3>
<ul>
<li>물리적 구성요소를 소프트웨어 구성 요소로 추상화 ⇒ 가상화 기술을 통해 상호작용</li>
</ul>
<p>vCenter Server system과 ESXi hosts에 연결하기위한 사용자 인터페이스는 무엇이 있는가?</p>
<ul>
<li>vCenter Server System → vSphere Client</li>
<li>ESXi hosts → VMware Host Client<ul>
<li>vCenter Server를 사용할 수 없을 때, ESXi 호스트를 직접 관리하는데 사용할 수 있는 HTML5 기반 사용자 인터페이스</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[가상화, 컨테이너, 하이퍼바이저]]></title>
            <link>https://velog.io/@_030702/%EA%B0%80%EC%83%81%ED%99%94-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-%ED%95%98%EC%9D%B4%ED%8D%BC%EB%B0%94%EC%9D%B4%EC%A0%80</link>
            <guid>https://velog.io/@_030702/%EA%B0%80%EC%83%81%ED%99%94-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-%ED%95%98%EC%9D%B4%ED%8D%BC%EB%B0%94%EC%9D%B4%EC%A0%80</guid>
            <pubDate>Sat, 14 Mar 2026 14:10:02 GMT</pubDate>
            <description><![CDATA[<h3 id="가상화">가상화</h3>
<p>회사에서는 하나의 물리서버를 보다 효율적으로 사용하기를 바람
이런 필요에 의해 가상화 기술이 발전하게 됨 ⇒ VM이나 컨테이너와 같은 기술이 생김
물리적인 하드웨어를 논리적으로 구분하는 것 = 가상화
하나의 하드웨어에서 여러개의 시스템을 효율적으로 사용할 수 있게 해줌</p>
<h3 id="가상화의-유형">가상화의 유형</h3>
<p>가상머신 vm - 하이퍼바이저를 이용해 리소스 전체를 가상화하는 방법</p>
<p>컨테이너 container - OS 수준에서 프로세스를 컨테이너 형태로 격리하는 방법</p>
<p>⇒ virtual machine 은 물리적인 컴퓨터와 동일한 기능을 제공하는 소프트웨어 컴퓨터임</p>
<p><img src="https://velog.velcdn.com/images/_030702/post/60bd4a52-ecdc-4720-acad-c8eb30b72451/image.png" alt=""></p>
<h3 id="각-os-마다-커널이-존재함">각 OS 마다 커널이 존재함</h3>
<p>하드웨어를 제어하기 위한 명령어 : 커널</p>
<p>애플리케이션이 OS 커널에 요청을 보내는 방식이 다르고 (시스템 콜), 이를 하드웨어가 이해할 수 있는 CPU 명령어 셋으로 변환하는 과정이 필요함</p>
<p>Hypervisor은?
    -&gt; 이때 !!! 각 OS 마다 말하는 명령을 하드웨어가 이해할 수 있게 하나의 명령어로 번역해줌</p>
<pre><code>Windows - NT Kernel

Linux - Linux Kernel

mac - Darwin</code></pre><p>프로그램과 하드웨어(CPU) 사이에 <strong>CPU에 직접적으로 명령을 실행</strong>하기 위한 함수 리스트가 존재함 (ex. Linux  system call)</p>
<pre><code>만약 프로그램에서 파일을 연다고 생각해보자.

프로그램 파일 열기 클릭

Kernel 파일 Open 함수 실행

CPU 내부적으로 파일을 여닫고 하는 과정들에 포함되는 명령어 셋들을 실행

=&gt; OS 마다 차이가 발생하는 부분은 2번 과정

3번은 명령어 셋들이 호출되는 과정이 다를 뿐 최종적으로 CPU에 전달되는 명령어는 동일함</code></pre><h3 id="hypervisor">Hypervisor</h3>
<p>VM과 하드웨어 간의 I/O명령을 처리하는 인터페이스</p>
<p>물리적인 하드웨어를 논리적으로 가상화하는 것을 담당하는 기술이 하이퍼 바이저</p>
<p>가상머신을 생성하고 구동하는 프로그램 (=가상머신 모니터) → 다수의 운영체제를 동시에 실행하기 위한 논리적 플랫폼</p>
<p>host(=ESXI에 리소스를 제공하는 물리적 컴퓨터)에서 여러 가상의 GuestOS(=VM에서 실행되는 운영체제)를 실행하기 위한 플랫폼</p>
<p>VM 위에 올라간 OS들의 명령을 하드웨어가 이해할 수 있게 하나의 명령어로 번역해주고, 반대로 각 OS들에게 하드웨어의 리소스들을 나눠주면서 조율해줌 (운용역할도 진행)</p>
<p>⇒ VMware를 설치하고 CentOS를 올렸다면, CentOS에서 명령들을 하드웨어와 중재하는 역할을 한다고 이해하면 될듯</p>
<p>추가로, PC에서 핸드폰용 Android Emulator, iOS Emulator을 돌릴때 혹은 x86_64 OS에서 ARM / ARM64 OS를 테스트하고 싶을 때 사용하기도 함.</p>
<h3 id="hypervisor-type">Hypervisor Type</h3>
<p><img src="https://velog.velcdn.com/images/_030702/post/80c971e8-6710-4b1b-b11b-0c8a51285ba5/image.png" alt=""></p>
<h3 id="컨테이너">컨테이너</h3>
<p>컨테이너의 시작은 화물을 어떻게 하면 효율적으로 옮길 수 있을까 → 표준화된 컨테이너 박스를 만들고, 무엇을 넣어도 표준화된 운송선만 있으면 똑같이 세워서 효율적으로 운송할 수 있음</p>
<p>이 컨셉을 IT에 적용한 것이 컨테이너</p>
<p>소프트웨어 코드, 애플리케이션 코드가 작동하기 위해 필요한 컴포넌트와 함께 표준화된 방식으로 패키징해서, 컨테이너 엔진만 있으면 돌아갈 수 있도록 하는 구조를 컨테이너라고 함.</p>
<p>⇒ 컴퓨팅 환경 간의 이식성이 높아짐</p>
<p>가상화 레이어 없어서 실행 속도가 프로세스 실행 수준으로 빠름</p>
<p>리눅스 커널의 기능인 Namespace(자원 격리), cgroups(자원 제한)을 사용해 프로세스 단위로 격리함 </p>
<p>하이퍼 바이저로 구동된 OS는 각자 다른 Kernel을 가질 수 있음</p>
<p>컨테이너는 호스트와 동일한 커널을 사용함 </p>
<pre><code>하드웨어 → 하이퍼바이저 → 가상머신 (Guest OS Kernel + App)

하드웨어 → HostOS (Kernel) → container 엔진 (Docker) → Application</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Kubernetes k8s 접근 구조]]></title>
            <link>https://velog.io/@_030702/Kubernetes-k8s-%EC%A0%91%EA%B7%BC-%EA%B5%AC%EC%A1%B0</link>
            <guid>https://velog.io/@_030702/Kubernetes-k8s-%EC%A0%91%EA%B7%BC-%EA%B5%AC%EC%A1%B0</guid>
            <pubDate>Sat, 14 Mar 2026 14:04:28 GMT</pubDate>
            <description><![CDATA[<pre><code>Credential(Kubeconfig/Token) 배포영역

    ㄴ Cluster

        ㄴ Namespace

            ㄴ Pod

                ㄴ container

사용자가 인증해서

    ㄴ 어느 클러스터에

        ㄴ 어느 공간에

            ㄴ 어떤 실행단위에

                ㄴ 어떤 컨테이너에 접근하느냐
</code></pre><ul>
<li><p>credential</p>
<ul>
<li><p>PaaS에서 접근할 때 가장 먼저 필요함</p>
</li>
<li><p>kubernetes API 서버에 접속 인증 정보</p>
</li>
</ul>
</li>
<li><p>cluster</p>
<ul>
<li><p>kubernetes가 실제로 돌아가는 전체 인프라</p>
</li>
<li><p>노드 (Contol Plane+Worker)의 집합<br>→ API Server/ Scheduler / Controller Manager / Nodes</p>
</li>
</ul>
</li>
<li><p>Namespace</p>
<ul>
<li><p>클러스터 내부 논리적 격리 공간</p>
</li>
<li><p>멀티팀/ 멀티서비스 분리<br>→ 이름충돌방지/ 권한 분리</p>
</li>
</ul>
</li>
<li><p>Pod</p>
<ul>
<li><p>Kubernetes의 최소 배포 단위</p>
</li>
<li><p>1개 이상의 컨테이너 묶음 
→ 동일 IP 공유, storage 공유, 같이 스케줄링 됨</p>
</li>
</ul>
</li>
</ul>
<h4 id=""></h4>
]]></description>
        </item>
        <item>
            <title><![CDATA[Cloud 종류 (Public, Private, Hybrid)]]></title>
            <link>https://velog.io/@_030702/Cloud-%EC%A2%85%EB%A5%98-Public-Private-Hybrid</link>
            <guid>https://velog.io/@_030702/Cloud-%EC%A2%85%EB%A5%98-Public-Private-Hybrid</guid>
            <pubDate>Sat, 14 Mar 2026 13:59:50 GMT</pubDate>
            <description><![CDATA[<h3 id="개요">개요</h3>
<p>Public Cloud, Private Cloud, Hybrid Cloud에 대해서 알아보자</p>
<h3 id="public-cloud">Public Cloud</h3>
<ul>
<li><p>누구나 함께 이용할 수 있도록 구축된 대규모 클라우드 서비스</p>
</li>
<li><p>사용자는 필요한 때에 필요한 만큼의 클라우드 자원을 할당받아 이용할 수 있도록 제공하는 서비스 방식</p>
</li>
<li><p>필요한 서버를 즉시 생성하여 이용할 수 있음</p>
</li>
<li><p>트래픽과 자원 사용량 증가 시 바로 확장이 가능한 확장성</p>
</li>
<li><p>저렴한 서비스 비용으로 인한 경제성</p>
</li>
<li><p>시스템 유지보수나 장애 대응을 위한 효율성과 같은 클라우드 서비스 장점에 최적화 된 서비스</p>
</li>
<li><p>KT cloud, Amazon AWS, IBM SoftLayer과 같이 대중적인 클라우드 서비스를 제공하는 업체들의 서비가 공용클라우드에 속하며, 일반적인 웹 서비스나 게임, 동영상 서비스가 주로 공용 클라우드 환경으로 구성됨</p>
</li>
</ul>
<h3 id="private-cloud">Private Cloud</h3>
<ul>
<li>자사 내부에 직접 클라우드 인프라를 구축한 형태</li>
<li>클라우드 인프라에 대한 직접적인 통제 권한을 가진 클라우드 서비스 구축 방식을 말함</li>
<li>사설 클라우드를 구축하는 경우에는 보안을 위해 외부의 인터넷 망과 IT 인프라를 분리하거나, 운영 회사에서 원하는 형태로 보안 시스템과 내부 IT 인프라를 구성할 수 있음</li>
<li>그러나, 공용 클라우드와 달리 구성된 여유자원이 한정되어있어 자원의 필요량이 증가하면 확장에 제약이 있을 수 있음</li>
<li>추가로, 직접 구축을 해야하기 때문에, 클라우드 인프라를 관리하기 위한 관리 인력이 필요함</li>
<li>많은 기업과 공공기관들이 보안에 대한 민감도가 높은 IT 시스템을 운영하기 때문에 사설 클라우드를 구축함</li>
<li>주로 외부의 접속이 필요없이 회사 내부 서비스를 위한 그룹웨어, ERP와 같은 서비스들이 주로 사설 클라우드 환경임</li>
</ul>
<h3 id="hybrid-cloud">Hybrid cloud</h3>
<ul>
<li>내부에 사설 클라우드를 구축하여 운영하다가 필요에 따라 외부의 공용 클라우드를 함께 이용하는 것을 일반적으로 하이브리드 클라우드라고 함.</li>
<li>여기에 기존의 물리 서버를 포함해 비즈니스 환경에 따라 공용, 사설, 물리 서버 중 두가지 이상의 클라우드를 혼용하여 구성하기도 함.</li>
<li>공용 서비스를 위한 웹, WAS 서버는 공용 클라우드를 이용하여 클라우드의 장점(비용과 확장성)에 최적화 시키고, 보안이 중요한 서버 (database와 같은 경우)들은 사설 클라우드 환경을 구성하여 외부의 직접적인 접속을 차단함</li>
<li>기존에 운영중이거나, 높은 성능을 필요로 하는 물리 서버들과 연동해 사용자의 요구에 맞는 최적의 인프라를 설계할 수 있는 장점 존재</li>
<li>외부 서비스와 보안이 모두 중요한 금융, 개인정보 취급 웹서비스, 교육, 시스템들이 하이브리드 클라우드 환경으로 구성됨</li>
</ul>
<p><img src="https://velog.velcdn.com/images/_030702/post/dfefcc92-7f49-48f3-9418-1060089a3e28/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[OpenStack, Kubernetes]]></title>
            <link>https://velog.io/@_030702/OpenStack-Kubernetes</link>
            <guid>https://velog.io/@_030702/OpenStack-Kubernetes</guid>
            <pubDate>Sat, 14 Mar 2026 13:58:04 GMT</pubDate>
            <description><![CDATA[<ul>
<li><p>Kubernetes</p>
<ul>
<li>cmp의 실행 기반으로 사용됨</li>
<li>cmp 솔루션 자체가 복잡한 MSA로 되어있어 cmp 소프트웨어를 안정적으로 돌리기 위한 인프라로 OKD나 K8s를 사용하게 함<ul>
<li>k8s : 가볍고, 표준적이지만 운영환경 구축에 손이 많이 감</li>
<li>OKD : 빌드 도구(S21) 통합 모니터링, 향상된 보안 정책이 기본 포함되어있어서 CMP같은 복잡한 시스템을 올리기에 운영 부담이 적음</li>
</ul>
</li>
<li>cmp의 관리대상으로 포함됨<ul>
<li>cmp의 주 목적인 멀티 클라우드 관리 항목 중 하나로, k8s 클러스터나 okd를 클러스터를 등록해 제어하는 경우</li>
<li>cmp 화면 하나에서 aws ec2도 관리하고, 내부 서버의 okd 노드 상태도 체크하며 자원을 배분하는 형태</li>
</ul>
</li>
<li>okd vs k8s<ul>
<li>두 기술은 뿌리는 같으나 패키징이 다름</li>
<li>kubernetes (k8s) = 컨테이너 오케스트레이션의 순수 엔진<ul>
<li>엔진만 있어서 모니터링, 로깅, 보안, CI CD를 위해 별도의 오픈소스 도구를 직접 조합해야함</li>
</ul>
</li>
<li>OKD (Openshift community Distribution)<ul>
<li>k8s라는 엔진 위에 redhat이 편의도구들을 미리 다 조립해놓은 완성차 같은 플랫폼</li>
<li>Openshift의 오픈소스 버전이며, 기업의 ocp로 가기전의 상위 프로젝트</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li><p>openstack vs kubernetes</p>
<ul>
<li>무엇을 가상화하느냐?<ul>
<li>openstack (IaaS) : 컴퓨터(VM) 자체를 빌려줌<ul>
<li>CPU 4코어, 램 16GB, 윈도우 설치된 가상 컴퓨터 한대를 통째로 만드는 도구</li>
<li>지금 업무에서의 VMWare와 역할이 거의 같음</li>
<li>VMWare 유료, OpenStack 무료</li>
</ul>
</li>
<li>쿠버네티스(PaaS/Container Platform) : 컨테이너인 Pod를 빌려줌<ul>
<li>OS가 깔린 컴퓨터를 통째로 만드는게 아니라, 그 위에 돌아가는 도커 컨테이너만 띄워줌</li>
</ul>
</li>
<li>정리하자면 오픈스택은 운동장(컴퓨터 자원)을 만들고 쿠버네티스는 그 운동장  위에서 축구시합이 돌아가게 관리한다고 이해하면 될듯</li>
</ul>
</li>
<li>사용 목적<ul>
<li>쿠버네티스를 사용하는 주된 이유는 도커 컨테이너를 관리하고 옮기기 위해서가 맞음</li>
<li>오픈스택은 목적이 조금 더 넓음</li>
<li>쿠버네티스 = 도커 컨테이너 (소스코드와 디비)를 어느서버에 띄울까? 서버가 죽으면 옆으로 옮겨야지</li>
<li>오픈스택 = 쿠버네티스가 돌아갈 수 있는 가상서버 10대를 만들어야지 or DB를 컨테이너가 아니라 성능을 위해 가상서버에 직접 설치해야지</li>
<li>현업에서<ul>
<li>물리서버 → 오픈스택으로 가상 서버 → 그 가상 서버 위에 쿠버네티스 설치 → 그 위에 도커 배포</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring에서 실제 Client IP를 가져오는 방법 (getRemoteAddr를 쓰면 안 되는 이유)]]></title>
            <link>https://velog.io/@_030702/Spring%EC%97%90%EC%84%9C-%EC%8B%A4%EC%A0%9C-Client-IP%EB%A5%BC-%EA%B0%80%EC%A0%B8%EC%98%A4%EB%8A%94-%EB%B0%A9%EB%B2%95-getRemoteAddr%EB%A5%BC-%EC%93%B0%EB%A9%B4-%EC%95%88-%EB%90%98%EB%8A%94-%EC%9D%B4%EC%9C%A0</link>
            <guid>https://velog.io/@_030702/Spring%EC%97%90%EC%84%9C-%EC%8B%A4%EC%A0%9C-Client-IP%EB%A5%BC-%EA%B0%80%EC%A0%B8%EC%98%A4%EB%8A%94-%EB%B0%A9%EB%B2%95-getRemoteAddr%EB%A5%BC-%EC%93%B0%EB%A9%B4-%EC%95%88-%EB%90%98%EB%8A%94-%EC%9D%B4%EC%9C%A0</guid>
            <pubDate>Sun, 08 Mar 2026 13:44:13 GMT</pubDate>
            <description><![CDATA[<h3 id="목적">목적</h3>
<p>회사 코드에서 Client의 IP 주소를 가져오는 공통 유틸리티 메서드를 발견했다.
처음에는 단순히 request.getRemoteAddress()를 사용하면 클라이언트 IP를 얻을 수 있을 것이라고 생각했지만, 실제 코드를 살펴보니 여러 HTTP 헤더를 확인하는 비교적 복잡한 로직이 구현되어 있었다.</p>
<p>이러한 구현 방식이 필요한 이유를 살펴보던 중, 프록시 서버나 로드밸런서를 거치는 환경에서는 getRemoteAddress()만으로는 실제 사용자 IP를 확인하기 어렵다는 사실을 알게 되었다.</p>
<p>특히 최근의 서비스 환경에서는 <code>Reverse Proxy</code>, <code>Load Balancer</code>, <code>API Gateway</code> 등 여러 네트워크 계층을 거쳐 요청이 전달되기 때문에, 실제 클라이언트 IP를 확인하기 위해서는 추가적인 처리가 필요하다.</p>
<p>따라서 이번 글에서는 다음 내용을 정리해보고자 한다.</p>
<ol>
<li><p>request.getRemoteAddress()로 실제 사용자 IP를 알 수 없는 이유</p>
</li>
<li><p>Proxy / Load Balancer 환경에서 요청이 전달되는 방식</p>
</li>
<li><p>X-Forwarded-For 헤더를 활용한 실제 Client IP 확인 방법</p>
</li>
<li><p>실무에서 Client 정보를 처리하는 일반적인 방식</p>
</li>
</ol>
<h3 id="왜-getremoteaddress를-사용하지-않을까">왜 getRemoteAddress()를 사용하지 않을까?</h3>
<p>보통의 클라이언트 IP를 가져올때는 <code>request.getRemoteAddress()</code>를 사용한다.
하지만 실제 서비스 환경에서는 이 방법만으로는 사용자의 실제 IP를 확인하기 어려운 경우가 많다.</p>
<p>그 이유는 서비스 구조 사이에 여러 네트워크 장비가 존재하기 때문이다.</p>
<p>예를 들어 다음과 같은 구조를 생각해볼 수 있다.</p>
<pre><code>Client 
   ↓
Load Balancer (L4/L7)
   ↓
Nginx (Reverse Proxy)
   ↓
Spring Boot Server</code></pre><p>이 상태에서 서버가 <code>request.getRemoteAddress()</code>를 호출하면 실제 사용자 IP가 아니라 바로 앞 단계 장비의 IP가 반환된다.</p>
<p>즉 다음 중 하나가 반환될 가능성이 높다.</p>
<ul>
<li><p><code>Load Balancer IP</code></p>
</li>
<li><p><code>Reverse Proxy IP</code></p>
</li>
</ul>
<p>따라서 서버 입장에서는 실제 사용자 IP를 직접 알 수 없다.</p>
<h3 id="그렇다면-실제-client-ip는-어디에-있을까">그렇다면, 실제 Client IP는 어디에 있을까?</h3>
<p>프록시나 로드밸런서를 거칠 경우, 실제 사용자 <code>IP</code>는 <code>HTTP Header</code>를 통해 전달된다.</p>
<p>대표적으로 다음과 같은 헤더들이 사용된다.</p>
<h4 id="주요-client-ip-관련-헤더">주요 <code>Client IP</code> 관련 헤더</h4>
<ul>
<li>X-Forwarded-For</li>
<li><blockquote>
<p>가장 표준적으로 사용되는 헤더이며, 프록시 서버를 거칠 때마다 IP가 누적된다.</p>
</blockquote>
</li>
</ul>
<pre><code>X-Forwarded-For: clientIP, proxy1, proxy2

Proxy-Client-IP : Apache 등 특정 프록시 서버에서 사용되는 헤더

WL-Proxy-Client-IP : WebLogic 서버에서 사용하는 헤더

HTTP_CLIENT_IP : PHP 환경에서 사용되는 경우가 있음

마지막으로 위 헤더에 값이 없는 경우에만

request.getRemoteAddress()를 fallback 방식으로 사용하는 것이 일반적이다.</code></pre><p>📌 ClientUtil 같은 유틸리티가 필요한 이유</p>
<p>이러한 이유 때문에 실무에서는 단순히 <code>getRemoteAddress()</code>를 호출하기보다
여러 헤더를 순차적으로 확인하는 공통 유틸리티 메서드를 사용하는 경우가 많다.</p>
<p>예를 들어 다음과 같은 방식이다.</p>
<pre><code>X-Forwarded-For

Proxy-Client-IP

WL-Proxy-Client-IP

HTTP_CLIENT_IP

request.getRemoteAddress()</code></pre><p>이 순서대로 값을 확인하여 실제 사용자 IP를 찾아내는 방식이다.</p>
<h3 id="client-정보-파싱-ip--os--browser">Client 정보 파싱 (IP / OS / Browser)</h3>
<p>일부 서비스에서는 사용자 IP뿐 아니라 <code>OS</code>, <code>Browser</code>, <code>Device</code>와 같은 정보도 함께 파싱하기도 한다.</p>
<p><strong><em>하지만</em></strong> 최근 실무에서는 이러한 로직을 직접 구현하기보다는 다른 방식으로 처리하는 경우가 많다.</p>
<p>브라우저와 디바이스 종류가 계속 증가하기 때문에
개발자가 직접 if-else 로직으로 관리하기에는 유지보수 비용이 너무 크기 때문에 그렇다고 한다.</p>
<h3 id="실무에서의-일반적인-처리-방식">실무에서의 일반적인 처리 방식</h3>
<p>최근 서비스 환경에서는 다음과 같은 방식이 많이 사용된다.</p>
<pre><code>1. API Gateway에서 처리

Kong API Gateway
Spring Cloud Gateway

Gateway에서 Client 정보를 파싱하여 전달</code></pre><pre><code>2. WAF (Web Application Firewall)

AWS WAF

보안 장비에서 Client 정보를 분석</code></pre><pre><code>3. 전문 라이브러리 사용

uap-java

User-Agent 파싱을 전문적으로 처리</code></pre><h3 id="정리">정리</h3>
<p>로드밸런서나 프록시 서버가 존재하는 환경에서는
<code>request.getRemoteAddress()</code>만으로 실제 사용자 IP를 확인하기 어렵다.</p>
<p>이러한 환경에서는 요청이 전달되는 과정에서 <code>HTTP Header</code>에 실제 <code>Client IP</code>가 포함되기 때문에
<code>X-Forwarded-For</code>와 같은 헤더를 활용하여 <code>IP</code>를 확인해야 한다.</p>
<p>따라서 실무에서는 다양한 헤더를 순차적으로 확인하는 공통 유틸리티 메서드를 사용하거나,
<code>API Gateway</code>나 보안 장비에서 해당 정보를 처리하는 방식이 일반적으로 사용된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[MSA에서 HttpSession을 사용하면 안 되는 이유]]></title>
            <link>https://velog.io/@_030702/MSA-JWT-token-%EC%82%AC%EC%9A%A9</link>
            <guid>https://velog.io/@_030702/MSA-JWT-token-%EC%82%AC%EC%9A%A9</guid>
            <pubDate>Sun, 08 Mar 2026 13:35:33 GMT</pubDate>
            <description><![CDATA[<h3 id="목적">목적</h3>
<p>회사 프로젝트의 코드를 살펴보던 중 <code>Service</code>에서 <code>HttpSession</code>을 사용해 RSA 키를 생성하고 관리하는 로직을 발견했다.
하지만 현재 프로젝트는 <code>Microservices Architecture(MSA)</code> 구조로 구성되어 있으며, MSA에서는 일반적으로 Stateless(무상태성)을 지향한다는 점에서 의문이 생겼다.</p>
<p><code>HttpSession</code>은 특정 서버의 메모리에 사용자 상태를 저장하는 방식인데, 서버가 여러 대로 구성된 환경에서는 요청이 다른 서버로 전달될 수 있기 때문에 세션 기반 로직이 정상적으로 동작하지 않을 가능성이 있다.</p>
<p>이러한 의문을 계기로 다음과 같은 내용을 정리해보고자 한다.</p>
<ol>
<li><p>MSA 환경에서 HttpSession 사용이 왜 문제가 될 수 있는지</p>
</li>
<li><p>세션 기반 인증과 JSON Web Token(JWT) 기반 인증의 차이</p>
</li>
<li><p>MSA 환경에서 인증 및 암호화 키 관리 대안</p>
</li>
<li><p>RSA 키를 세션 단위로 생성하는 방식의 한계와 대안</p>
</li>
</ol>
<p>이를 통해 현재 코드 구조가 어떤 환경을 가정하고 작성되었는지 이해하고, MSA 환경에서는 어떤 방식으로 개선할 수 있을지 정리해보고자 한다.</p>
<h3 id="기술적-고민을-시작한-이유">기술적 고민을 시작한 이유</h3>
<p>현재 회사 코드를 살펴보면서 한 가지 공통적인 설계 방향을 발견할 수 있었다.</p>
<p><code>인터페이스 / Impl 분리</code>
→ 구현체를 분리하여 유지보수와 확장성을 높이기 위한 객체지향 설계</p>
<p><code>MyBatis의 BaseVO / Interceptor 사용</code>
→ 반복되는 로직을 줄이고 공통 기능을 재사용하기 위한 구조</p>
<p><code>Stateless 구조 지향</code>
→ 서비스 확장성과 장애 대응을 고려한 MSA 아키텍처의 핵심 원칙</p>
<p>결국 이러한 기술적 선택들은 단순한 구현 방식의 차이가 아니라,
“어떻게 하면 시스템을 더 쉽게 유지보수하고, 안정적으로 확장할 수 있을까?”
라는 고민에서 출발한다고 볼 수 있다.</p>
<p>따라서 나도 동일한 관점을 갖고, 발전시키고 싶은 부분에 대해 고민을 시작하였다.</p>
<h3 id="msa-환경에서-httpsession-사용이-왜-문제가-될-수-있는지">MSA 환경에서 HttpSession 사용이 왜 문제가 될 수 있는지</h3>
<p>현재 <code>Service</code> 코드에서는 <code>HttpSession</code>을 사용하여 RSA 키를 생성하고 관리하고 있다.
하지만 현재 프로젝트는 <code>Microservices Architecture(MSA)</code> 구조로 구성되어 있으며, MSA는 기본적으로 Stateless(무상태성)을 지향한다.</p>
<p><code>Stateless</code> 구조에서는 특정 서버가 사용자 상태를 기억하지 않는다. 
즉, 어떤 서버가 요청을 처리하더라도 동일한 결과를 반환할 수 있어야 한다.</p>
<p>하지만 <code>HttpSession</code>은 서버 메모리에 사용자 상태를 저장하는 방식이기 때문에, 서버가 여러 대로 구성된 환경에서는 문제가 발생할 수 있다. 
예를 들어, 사용자의 요청이 로드밸런서를 통해 다른 서버로 전달될 경우 해당 서버에는 기존 세션 정보가 존재하지 않기 때문에 인증이나 암호화 과정에서 오류가 발생할 수 있다.</p>
<p>이러한 이유로 <code>MSA</code> 환경에서는 <code>HttpSession과</code> 같은 서버 의존적인 상태 관리 방식보다는, <code>JSON Web Token(JWT)</code>이나 공통 저장소 기반의 상태 관리 방식을 사용하는 경우가 많다. 
예를 들어 인증 정보나 암호화 키를 <code>Redis</code>와 같은 중앙 저장소에서 관리하면 어떤 서버가 요청을 처리하더라도 동일한 데이터를 참조할 수 있다. 
또한 보안 키 관리가 필요한 경우에는 <code>AWS Key Management Service(KMS)</code>와 같은 키 관리 시스템을 사용하는 방법도 있다.</p>
<p>현재 코드에서 <code>HttpSession</code>을 사용하는 이유는 몇 가지 가능성을 생각해볼 수 있다.</p>
<ol>
<li><p>초기에는 단일 서버 환경을 가정하고 작성된 로직일 가능성</p>
</li>
<li><p><code>MSA</code>로 전환되는 과정에서 레거시 코드가 그대로 유지되었을 가능성</p>
</li>
</ol>
<p><code>MSA</code> 환경에서 안정적으로 확장하기 위해서는 세션 기반 상태 관리보다는 공통 저장소 기반의 상태 관리 구조로 전환하는 것이 필요하다.</p>
<h3 id="세션-기반-인증과-json-web-tokenjwt-기반-인증의-차이">세션 기반 인증과 JSON Web Token(JWT) 기반 인증의 차이</h3>
<p><code>SESSION</code>은 서버에서 기억해야한다면, <code>JWT</code>는 클라이언트가 기억하는것이다.</p>
<h3 id="msa-환경에서-인증-및-암호화-키-관리-대안">MSA 환경에서 인증 및 암호화 키 관리 대안</h3>
<h4 id="예시-1-서버-다중화-환경에서의-세션-문제">예시 1. 서버 다중화 환경에서의 세션 문제</h4>
<p>현재 서버가 다중화된 환경이라고 가정해보자.
단일 서버 환경이라면 세션에 RSA 키를 저장하더라도 언제든지 동일한 서버에서 해당 키를 사용할 수 있다.</p>
<p>하지만 <code>Microservices Architecture(MSA) 환경</code>에서는 상황이 달라진다.</p>
<p>예를 들어 1번 서버에서 <code>generateRsaKey</code>를 호출하여 세션에 RSA 키를 저장했다고 가정해보자.
이후 암호화 요청이 발생했을 때 로드밸런서에 의해 요청이 2번 서버로 전달될 수 있다.
이 경우 2번 서버에는 해당 세션 정보가 존재하지 않기 때문에 RSA 키를 찾을 수 없고, 인증 또는 복호화 과정이 실패할 수 있다.</p>
<p>이 문제를 해결하기 위해 다음과 같은 방법을 사용할 수 있다.</p>
<ul>
<li><p><code>Sticky Session</code></p>
</li>
<li><p><code>Session Clustering</code></p>
</li>
</ul>
<p>하지만 이러한 방식은 특정 서버에 요청이 고정되거나 세션 동기화가 필요해지기 때문에 <code>MSA</code>의 유연성과 확장성을 떨어뜨릴 수 있다.</p>
<h4 id="예시-2-서비스-간-세션-공유-문제">예시 2. 서비스 간 세션 공유 문제</h4>
<p>또 다른 문제는 서비스 간 세션 공유가 어렵다는 점이다.</p>
<p>예를 들어 인증 서비스에서 <code>HttpSession</code>에 RSA 키를 저장했다고 가정하자.
하지만 해당 키는 다른 서비스에서 접근할 수 없다.
각 서비스는 서로 다른 물리적 서버나 컨테이너에서 실행되기 때문이다.</p>
<p>결과적으로 서비스 간에 암호화된 데이터를 주고받아야 하는 상황에서는 세션 기반 키 관리 방식이 의미를 잃게 된다.</p>
<h4 id="msa에서-권장되는-방식">MSA에서 권장되는 방식</h4>
<p>이러한 이유로 <code>MSA</code>에서는 서버 세션에 의존하는 방식 대신 <code>무상태(Stateless)</code> 구조를 지향한다.</p>
<p>무상태 구조에서는 서버가 사용자 상태를 기억하지 않기 때문에
어떤 서버가 요청을 처리하더라도 동일한 결과를 반환할 수 있어야 한다.</p>
<p>따라서 인증 및 상태 관리를 위해 다음과 같은 방식이 사용된다.</p>
<p><code>JSON Web Token(JWT)</code>
→ 인증 정보를 서버 세션이 아닌 토큰 자체에 포함하여 클라이언트가 보관</p>
<p><code>Redis 기반 중앙 저장소</code>
→ 인증 정보나 암호화 키를 모든 서버가 접근할 수 있는 공통 저장소에 관리</p>
<h3 id="rsa-암호화가-필요한-경우">RSA 암호화가 필요한 경우</h3>
<p>만약 서비스 로직에서 <code>RSA 암호화</code>가 필요한 경우에는
<code>HttpSession</code> 대신 공유 저장소 기반 구조를 사용하는 것이 바람직하다.</p>
<p>예를 들어 Redis 기반 공유 세션, 공통 데이터베이스 기반 키 저장 또는 <code>Key Management System(KMS)</code> 과 같은 방식을 활용하면 어떤 서버가 요청을 처리하더라도 동일한 키에 접근할 수 있다.</p>
<h3 id="rsa-키를-세션-단위로-생성하는-방식의-한계와-대안">RSA 키를 세션 단위로 생성하는 방식의 한계와 대안</h3>
<p>현재 코드에서는 <code>HttpSession</code>을 사용하여 RSA 키를 생성하고 세션에 저장하는 방식으로 암호화를 처리하고 있다.
하지만 이러한 방식은 MSA 환경에서는 여러 가지 한계를 가진다.</p>
<p>먼저, 세션 기반으로 RSA 키를 관리할 경우 특정 서버의 메모리에 키가 저장되기 때문에 서버가 여러 대로 구성된 환경에서는 동일한 키를 공유하기 어렵다.
사용자의 요청이 로드밸런서를 통해 다른 서버로 전달될 경우 해당 서버에는 기존 세션 정보가 존재하지 않기 때문에 암호화 또는 복호화 과정에서 문제가 발생할 수 있다.</p>
<p>또한 세션마다 RSA 키를 생성하는 방식은 성능 측면에서도 비효율적일 수 있다.
RSA 키 생성은 비교적 비용이 큰 연산이기 때문에 요청이 많아질수록 서버에 불필요한 부하가 발생할 수 있다.</p>
<p>실제 서비스 환경에서는 애플리케이션 레벨에서 RSA 암호화를 직접 처리하기보다는, <code>HTTPS(SSL/TLS)</code>와 같은 표준 보안 프로토콜을 활용하는 것이 일반적이다.
<code>HTTPS</code>는 전송 구간에서 이미 강력한 암호화를 제공하기 때문에 대부분의 경우 애플리케이션에서 별도의 RSA 키를 생성하여 관리할 필요가 없다.</p>
<p>만약 서비스 내부 로직에서 암호화 키 관리가 필요하다면 다음과 같은 방법을 고려할 수 있다.</p>
<p>공통 저장소 기반 키 관리
→ <code>Redis</code>와 같은 중앙 저장소를 활용하여 여러 서버가 동일한 키에 접근할 수 있도록 구성</p>
<p>키 관리 시스템(KMS) 활용
→ 보안 키를 전문적으로 관리하는 시스템을 통해 안전하게 키를 저장하고 사용할 수 있음</p>
<p>결과적으로, MSA 환경에서는 특정 서버의 세션에 의존하는 방식보다는 모든 서버가 동일한 정보를 공유할 수 있는 구조를 사용하는 것이 중요하다.
또한 가능하다면 애플리케이션 코드에서 직접 암호화를 구현하기보다는 인프라 레벨의 표준 보안 프로토콜을 활용하는 것이 더 안정적이고 효율적인 방법이라고 볼 수 있다.</p>
<h3 id="정리">정리</h3>
<p>이번 내용을 정리해보면, MSA 환경에서는 서버 세션에 의존하는 방식이 확장성과 안정성 측면에서 한계를 가질 수 있다. 
따라서 인증 정보나 암호화 키는 특정 서버에 종속되지 않는 방식으로 관리하는 것이 중요하며, 
<code>Redis</code>와 같은 공통 저장소나 <code>KMS</code>와 같은 키 관리 시스템을 활용하는 방법을 고려할 수 있다. 
또한 대부분의 경우 애플리케이션 레벨의 암호화보다는 <code>HTTPS와 같은 표준 보안 프로토콜</code>을 활용하는 것이 더 효율적인 선택이 될 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Nexus]]></title>
            <link>https://velog.io/@_030702/Nexus</link>
            <guid>https://velog.io/@_030702/Nexus</guid>
            <pubDate>Sun, 08 Mar 2026 13:15:14 GMT</pubDate>
            <description><![CDATA[<h3 id="목적">목적</h3>
<p>회사에서 IntelliJ 환경을 초기 설정한 후 프로젝트를 빌드하는 과정에서 Nexus에 접근하지 못해 빌드가 실패하는 문제가 발생했다.</p>
<p>의존성 라이브러리를 내려받는 과정에서 문제가 발생한 것으로 보였고, 이를 계기로 Sonatype Nexus Repository(Nexus)가 어떤 역할을 하는지 알아보게 되었다.</p>
<h3 id="nexus-repository란">Nexus Repository란?</h3>
<ul>
<li>메이븐 사용하면 pom.xml 파일로 jar 파일 다운받아옴</li>
<li>Nexus는 메이븐에서 사용할 수 있는 Repository.</li>
<li>외부 의존을 덜고, local nexus(cache)로 사용함으로써 빠르게 라이브러리 끌어올 수 있음.<ul>
<li>한번 다운로드받은 dependency는 로컬에 저장되나, 컴퓨터를 포맷하거나 새롭게 시작하는 상황에서는 설정을 해야함</li>
</ul>
</li>
</ul>
<h3 id="nexus-repository-특징">Nexus Repository 특징</h3>
<ul>
<li>회사/단체의 화이트 리스트로 인해 외부 repository에 접속하기 어려운 프록스 역할</li>
<li>비상시 외부 인터넷이 느리거나 repository가 다운되는 상황에서도 빠른 다운 가능</li>
<li>현재 maven에 올라와있지 않은 자료들을 효율적으로 관리 가능</li>
<li>서버에도 동일한 설정을 해줘야함으로 서버 구조 복잡하면 잔업도 늘어남</li>
<li>예외 파일로 인한 설정이 줄어들어 일관성 증가함</li>
</ul>
<h3 id="주의사항">주의사항</h3>
<p>한번 다운 받은 Nexus는 로컬에 반영구적으로 저장되나, 새롭게 시작하는 상황에서는 설정 필요</p>
<h3 id="참고자료">참고자료</h3>
<p><a href="https://m.blog.naver.com/qhdqhdekd261/221827574154">https://m.blog.naver.com/qhdqhdekd261/221827574154</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JPA & MyBatis]]></title>
            <link>https://velog.io/@_030702/JPA-MyBatis</link>
            <guid>https://velog.io/@_030702/JPA-MyBatis</guid>
            <pubDate>Sun, 08 Mar 2026 13:13:07 GMT</pubDate>
            <description><![CDATA[<h3 id="목적">목적</h3>
<p>지금까지 프로젝트를 진행하면서 데이터베이스를 사용할 때는 주로 Jakarta Persistence API(JPA)를 사용해왔다. 
하지만 이번 프로젝트에서는 처음으로 MyBatis를 사용하게 되었다.
MyBatis는 SQL을 직접 작성하여 데이터베이스와 연동하는 방식이라는 점에서 JPA와 차이가 있다. 
이번 글에서는 MyBatis의 기본 개념과 동작 방식에 대해 정리해보고자 한다.</p>
<h3 id="mybatis란-">MyBatis란 ?</h3>
<ul>
<li><p>자바 애플리케이션에서 데이터 베이스와 상호작용하는데 사용되지만, 접근방식과 특징이 많이 다름</p>
</li>
<li><p>여기서의 상호작용 : 저장/조회</p>
</li>
<li><p>SQL Mapper 기술 </p>
</li>
<li><p>개발자가 작성한 SQL 실행 결과를 객체에 매핑</p>
<pre><code>💡 Mybatis</code></pre></li>
<li><p>SQL Mapper 프레임 워크</p>
</li>
<li><p>직접 SQL 작성</p>
</li>
<li><p>반복적인 SQL 작성 필요</p>
</li>
<li><p>복잡한 SQL 제어에 유리</p>
<pre><code></code></pre></li>
</ul>
<h3 id="jpa란-">JPA란 ?</h3>
<ul>
<li><p>ORM (Object Relational Mapping) </p>
</li>
<li><p>DB에 자동으로 매핑해주는 프레임 워크</p>
</li>
</ul>
<pre><code>💡 JPA

- Java persistence API, 객체 - 관계 매핑 표준
- JPQL 또는 메서드 이름 기반 쿼리
- 영속성 컨텍스트, JPQL 학습 필요
- CRUD 자동화로 생산성 높음
- 객체 중심 설계에 적합</code></pre><h3 id="jpa-vs-mybatis-아키텍처-비교">JPA vs MyBatis 아키텍처 비교</h3>
<pre><code class="language-java">[JPA]
Controller (REST API 받음)
    ↓
Service (비즈니스 로직)
    ↓
Repository (인터페이스만, 구현체 자동 생성)
    ↓
Entity (객체 ↔ 테이블 자동 매핑)
    ↓
Database</code></pre>
<pre><code>[MyBatis]
Controller (REST API 받음)
    ↓
Service (비즈니스 로직)
    ↓
Mapper (인터페이스) + XML (SQL 작성)
    ↓
DTO/VO (데이터 전달 객체) -&gt; model/도메인/dto, vo 존재 
* DAO는 DB접근담당이고
* DTO는 계층간 데이터 전달용
    ↓
Database</code></pre><h3 id="jpa-특징">JPA 특징</h3>
<ul>
<li>JPA = Java Persistence API는 자바 표준 ORM(Object - Relational Mapping) 기술</li>
<li>객체지향 프로그래밍 방식으로 데이터베이스와 상호작용 할 수 있도록 설계</li>
<li>JPA를 사용하면 SQL 대신 <code>Entity</code>를 통해 데이터 처리 가능</li>
</ul>
<ul>
<li>대표 구현체 : Hibernate, EclipseLink, OpenJPA</li>
<li>특징 :<ul>
<li>데이터베이스 테이블과 자바 객체를 매핑해 객체 지향적으로 데이터 관리</li>
<li>JPQL(Java Persistence Query Language) 라는 쿼리 언어 사용</li>
<li>영속성 컨텍스트 (Persistence Context) 를 통해 데이터 변경 사항 자동으로 추적</li>
</ul>
</li>
</ul>
<pre><code class="language-java">@Entity 
public class User {
    @Id
    @GeneratedValue
    private Long id;
    private String name;
}</code></pre>
<h3 id="mybatis-특징">MyBatis 특징</h3>
<ul>
<li>SQL Mapper 프레임 워크, SQL 쿼리를 직접 작성하면서도 자바 객체와 매핑할 수 있는 기능 제공</li>
<li>SQL 작성의 자유도 보장, 복잡한 쿼리나 데이터베이스 특화 기능을 활용하기 좋음</li>
</ul>
<aside>

<ul>
<li>SQL을 직접 제어할 수 있어서 복잡한 쿼리 작성에 유리</li>
<li>XML 혹은 어노테이션 기반으로 SQL 관리</li>
<li>동적 쿼리를 지원하여 조건에 따라 SQL 유연하게 변경할 수 있음</aside>

</li>
</ul>
<pre><code class="language-java">&lt;select id=&quot;findUserById&quot; parameterType=&quot;Long&quot; resultType=&quot;User&quot;&gt;
    SELECT * FROM users WHERE id = #{id}
&lt;/select&gt;</code></pre>
<ul>
<li>JDBC 프로그래밍 단순화하고 불필요한 Boilerplate 코드 제거하고 Java 소스코드에서 SQL문을 별도의 XML로 저장해 이를 연결함</li>
</ul>
<ul>
<li>동적 SQL 생성 기능<ul>
<li>MyBatis 는 Boilerplate 코드 제거 및 SQL 문 분리 외에도 동적인 SQL 생성 기능을 제공해 프로그램 실행 중에 입력되는 파라미터에 따라 서로 다른 SQL문을 동적으로 생성함</li>
</ul>
</li>
</ul>
<h3 id="mybatis-사용-순서와-예시">MyBatis 사용 순서와 예시</h3>
<ol>
<li><p>스프링 프로젝트 생성 후 필요한 의존성 추가 </p>
<ul>
<li>아마 미리 추가 되어있었을 것</li>
<li>spring-core</li>
<li>spring-jdbc</li>
<li>mybatis</li>
<li>mybatis-spring</li>
</ul>
</li>
<li><p>DB 설정</p>
<ul>
<li><p>JDBC로 DB연결을 하기에 DB 연결 정보 설정해야함</p>
</li>
<li><p>properties나 XML에 DataSource 등록</p>
<pre><code class="language-java">&lt;bean id=&quot;dataSource&quot; class=&quot;org.springframework.jdbc.datasource.DriverManagerDataSource&quot;&gt;
 &lt;property name=&quot;driverClassName&quot; value=&quot;com.mysql.jdbc.Driver&quot;/&gt; // mysql 드라이버
 &lt;property name=&quot;url&quot; value=&quot;jdbc:mysql://localhost:3306/mydatabase&quot;/&gt; // mysql 사용
 &lt;property name=&quot;username&quot; value=&quot;username&quot;/&gt; // 아이디 
 &lt;property name=&quot;password&quot; value=&quot;password&quot;/&gt; // 비밀번호 
&lt;/bean&gt;</code></pre>
</li>
</ul>
</li>
<li><p>MyBatis 설정</p>
<ul>
<li><p>MyBatis XML 설정 파일 만들고 필요한 설정 추가</p>
<pre><code class="language-java">&lt;bean id=&quot;sqlSessionFactory&quot; class=&quot;org.mybatis.spring.SqlSessionFactoryBean&quot;&gt;
 &lt;property name=&quot;dataSource&quot; ref=&quot;dataSource&quot;/&gt;
 &lt;property name=&quot;configLocation&quot; value=&quot;classpath:mybatis-config.xml&quot;/&gt;
&lt;/bean&gt;

&lt;bean id=&quot;sqlSession&quot; class=&quot;org.mybatis.spring.SqlSessionTemplate&quot;&gt;
 &lt;constructor-arg ref=&quot;sqlSessionFactory&quot;/&gt;
&lt;/bean&gt;</code></pre>
</li>
</ul>
</li>
<li><p>MyBatis Mapper 작성과 등록</p>
<ul>
<li><p>Mapper = DB 쿼리 ↔ 자바 메서드()를 매핑하는 역할</p>
</li>
<li><p>데이터 베이스에 접근하기 위한 SQL 쿼리를 작성하고, 이를 실행하는 자바 메서드 정의함</p>
<p>⇒ 정리하자면 XML에 SQL 쿼리들을 작성할 건데 그 SQL이 연결된 메서드를 정의할 파일 </p>
<p>ex) CRUD 쿼리문을 XML에 작성하고. InsertItems(), Getitems(), modifyItems(), DeleteItems() 메서드가 있는 인터페이스 만들거임. 그리고 그 클래스를 Mapper라는 명칭으로 부르자 !</p>
<pre><code class="language-java">@Mapper // 등록 
// 작성
public interface UserMapper {
 User getUserById(int id);
 void insertUser(User user);
 void updateUser(User user);
 void deleteUser(int id);
}</code></pre>
</li>
</ul>
</li>
<li><p>Mapper 인터페이스와 연결될 XML 작성</p>
<ul>
<li><p>Mapper 메서드() 별로 SQL 쿼리문을 작성함</p>
<pre><code class="language-xml">&lt;mapper namespace=&quot;com.exmaple.mapper.UserMapper&quot;&gt;
  &lt;select id=&quot;getUserById&quot; parameterTytpe=&quot;int&quot; resultType=&quot;com.example.model.User&quot;&gt;
  SELECT * FROM users WHERE id = #{id}
  &lt;/select&gt;
  &lt;insert id=&quot;insertUser&quot; parameterType=&quot;com.example.model.User&quot;&gt;    
  INSERT INTO users (name, email) VALUES (#{name}, #{email}) 
  &lt;/insert&gt;  

  &lt;!-- 다른 메서드들에 대한 쿼리도 추가할 수 있다 --&gt;

&lt;/mapper&gt;</code></pre>
</li>
</ul>
</li>
<li><p>Mapper 사용</p>
<ul>
<li><p>Mapper 인터페이스를 스프링 Bean으로 등록하고 주입받아 사용함</p>
<pre><code class="language-java">@Service
public class UserSerivce {
  priate final UserMapper userMapper;

  public UserService(UserMapper userMapper){
      this.userMapper = userMapper;
  }

// 유저 조회
public User getUsedById(int id){
   return userMapper.getUserById(id);
}
// 유저 등록
public User insertUser(User user){
   return userMapper.insertUser(user);
}</code></pre>
</li>
</ul>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Maven & Gradle]]></title>
            <link>https://velog.io/@_030702/Maven-Gradle</link>
            <guid>https://velog.io/@_030702/Maven-Gradle</guid>
            <pubDate>Sun, 08 Mar 2026 13:02:06 GMT</pubDate>
            <description><![CDATA[<h3 id="목적">목적</h3>
<p>현재까지 프로젝트를 진행하며 프로젝트 관리 도구로 Gradle만 사용해봤는데, 현재 회사 프로젝트에서는 Maven으로 프로젝트 관리 도구를 사용하기에 Gradle, Maven이 무슨 역할을 하는지 알아보고, 이 둘을 비교해보려고 한다.</p>
<h3 id="maven-이란">Maven 이란?</h3>
<ul>
<li>자바용 프로젝트 관리 도구 → 프로젝트를 빌드, 패키지, 배포등의 역할을 수행하고 각종 라이브러리를 관리함</li>
<li>아파치 라이센스로 배포되는 오픈소스 소프트웨어 (Ant 대안)</li>
<li>2개의 설정 파일을 통해 관리 가능 → <code>setting.xml</code> <code>pom.xml</code><ul>
<li>setting.xml 은 메이븐 툴과 관련 설정 파일</li>
<li>pom.xml<ul>
<li>자바 프로젝트의 빌드 툴로 메이븐을 설정했다면 생성될 것</li>
<li>프로젝트에 필요한 라이브러리를 정의하는 공간 (해당 라이브러리가 작동하는데 필요한 다른 라이브러리 자동으로 설치</li>
</ul>
</li>
</ul>
</li>
</ul>
<h4 id="maven-빌드-순서-life-cycle">Maven 빌드 순서 (Life Cycle)</h4>
<ul>
<li>메이븐은 미리 정의하고 있는 빌드 순서가 있음. → <code>라이프 사이클</code></li>
</ul>
<p><img src="https://velog.velcdn.com/images/_030702/post/1a03d70f-dbeb-44e5-9337-a91154ea0f0b/image.png" alt=""></p>
<ol>
<li>Default(=Build) : 소스코드 컴파일. 컴파일 성공하면 target/classes 폴더가 만들어지고 컴파일 된 class 파일 생성<ul>
<li>target 폴더 = 빌드 결과물이 위치</li>
<li>즉 최종적으로 배포 가능한 WAR/JAR 폴더가 생성됨 (6번 참고)</li>
</ul>
</li>
<li>Clean : 빌드 후 생성된 target 삭제 → 생성된 파일 삭제한다</li>
<li>Validate : 프로젝트가 올바른지 확인하고, 필요한 모든 정보를 사용할 수 있는지 확인</li>
<li>Compile : 프로젝트의 소스코드를 컴파일 하는 단계</li>
<li>Test : 유닛 (단위) 테스트를 수행하는 단계 (테스트 실패시 빌드로 처리, 스킵 가능)</li>
<li>Package : 실제 컴파일 된 소스코드와 리소스들을 <code>jar</code>, <code>war</code> 과 같은 파일등의 배포를 위한 패키지로 만드는 단계 → 타켓 directory 만듦</li>
<li>Verify : 통합 테스트 결과에 대한 검사를 실행하여 품질 기준을 충족하는지 확인</li>
<li>Install : 패키지를 로컬 저장소에 설치하는 단계, Package와 다른 점은 로컬 저장소에 있는 다른 프로젝트 들이 접근이 가능하다.</li>
<li>Site : 프로젝트 문서와 사이트 작성, 생성하는 단계</li>
<li>Deploy : 만들어진 package를 원격 저장소에 release 하는 단계</li>
</ol>
<p>→ 코드를 수정한 후 package하면 오래걸림 </p>
<p>→ 오래걸리니 target 파일의 코드를 바꾸게 하면 package를 할 필요가 없음</p>
<p>→ 무지성 clean → package</p>
<h4 id="maven-라이브러리-저장-장소-pomxml">Maven 라이브러리 저장 장소 (pom.xml)</h4>
<ul>
<li>maven은 필요한 라이브러리를 <code>pom.xml</code> 에 저장함 ⇒ 프로젝트 모델링</li>
</ul>
<pre><code class="language-xml">&lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;
&lt;groupId&gt;com.회사이름&lt;/groupId&gt;
&lt;artifactId&gt;모듈이름&lt;/artifactId&gt; &lt;!-- jar, war 파일 이름을 의미 --&gt;
&lt;version&gt;artifact의 버전&lt;/version&gt; &lt;!-- 0.0.1-SNAPSHOT = 아직 개발 중 --&gt;
&lt;packaging&gt;artifact의 포맷&lt;/packaging&gt;

&lt;properties&gt;...&lt;/properties&gt; &lt;!-- 값을 가진 상수 ${propertyname} 이런식으로 참조 가능  --&gt;

&lt;repositories&gt;...&lt;/repositories&gt; &lt;!-- 라이브러리 받아올 저장소 지정  --&gt;

&lt;dependencies&gt;...&lt;/dependencies&gt;
&lt;!-- 의존성은 POM에서 가장 중요함 (일관성 있는 프로젝트에 필수)
Maven은 dependencies 요소에 명시된 의존성을 자동 다운하고,
해당 의존성이 의존하는 또다른 의존성(transitive dependencies) 까지 가져 옴
외부에서 jar 파일을 일일이 다운받아 관리하던 것에비해 매우 개선됨--&gt;

&lt;build&gt;...&lt;/build&gt; &lt;!-- 플러그인 목록  --&gt;</code></pre>
<h3 id="그렇다면-빌드란-무엇인가-">그렇다면 빌드란 무엇인가 ?</h3>
<p>Maven이 자바 기반 프로젝트를 관리하는 도구라는 것을 알게 되었다. 
Maven은 프로젝트를 빌드하고, 패키징하며, 배포하는 과정을 관리하고 필요한 라이브러리 의존성까지 효율적으로 관리해준다. 
그렇다면 여기서 말하는 빌드(Build)란 무엇일까?</p>
<p><img src="https://velog.velcdn.com/images/_030702/post/aaeb153b-dcd8-48f8-a9dd-dbea44d14e38/image.png" alt=""></p>
<ul>
<li>빌드<ul>
<li>소스코드 혹은 프로젝트에 쓰인 각각의 파일 및 자원을 JVM이나 WAS가 인식할 수 있는 구조로 패키징하는 과정 및 결과물을 의미</li>
</ul>
</li>
<li>빌드 관리 도구 (Build Tool)<ul>
<li>프로젝트 생성, 테스트 빌드, 배포 → 를 위한 전용 프로그램</li>
<li>소스코드를 실행 가능한 어플리케이션으로 만들어주는 도구</li>
<li>if 라이브러리가 계속 늘어나거나, 라이브러리 버전 동기화가 어렵다 ? → build tool이 해소</li>
<li>다시 말해 빌드 과정을 자동화해서 관리해주는 역할</li>
</ul>
</li>
<li>특징<ul>
<li>빌드 도구의 특징은 개발자가 스크립트를 작성해 다양한 작업을 수행할 수 있게 함</li>
<li>소스코드를 컴파일, 패키징 할 수 있으며 테스트를 자동으로 수행하거나 의존성 주입 및 배포 작업을 할 수 있음</li>
<li>종류에 따라 스크립트 작성시 사용하는 문법이 다르긴 하나 일반적으로 <code>XML</code> 혹은 특정 도메인 언어를 사용함</li>
<li>빌드 도구 종류에는 <code>Ant</code> <code>Maven</code> <code>Gradle</code> 이 있음</li>
</ul>
</li>
</ul>
<pre><code>💡 빌드 자동화란 ?

1. 종속성 다운로드

2. 소스코드를 바이너리 코드로 컴파일

3. 바이너리 코드를 패키징

4. 테스트 실행

5. 프로덕션 시스템에 배포

를 스크립팅하거나 자동화하는 행위 </code></pre><h3 id="gradle이란-">Gradle이란 ?</h3>
<ul>
<li>Maven 을 대체할 수 있는 프로젝트 구성 관리 및 범용 빌드 툴</li>
<li>Ant Builder 와 Groovy script를 기반으로 구축되어 기존 Ant 역할 + 배포 스크립 기능 모두 사용 가능</li>
<li>스프링 부트와 안드로이드에 사용</li>
<li>빌드 속도가 Maven에 비해 10~100배 빠름, Java, C/C++, Python을 지원</li>
<li>XML 사용안함, JVM위에서 동작하는 Groovy나 Kotlin을 통해 작성함</li>
<li>Maven의 pom.xml은 선언형으로 설정하는 정적인 문서인 반면, Gradle의 build.gradle은 스크립트로 작성하는 동적인 소스 파일</li>
<li>즉, Maven은 정적인 문서와 엄격한 규칙으로 인해 어떤 설정이 필요할 때 한계가 있으나 Gradle은 로직을 넣을 수 있으니 한계가 필요없음. → 필요하다면 로직에 플러그인을 호출하거나 직접 코드를 짜면됨</li>
</ul>
<pre><code>정리하자면,

Groovy는 JVM에서 실행되는 스크립트 언어

JVM에서 동작하지만, 소스코드를 컴파일 할 필요없음

Java와 호환되며, Java class file을 Groovy class로 사용 가능함

Java와 문법 유사 </code></pre><h4 id="gradle-특징">Gradle 특징</h4>
<pre><code>1. 가독성이 좋다 : 코딩에 의한 **간결한 정의**가 가능하므로 가독성이 좋다.
2. 재사용에 용이 : 설정 주입 방식(Configuration Injection)을 사용하므로 재사용에 용이하다.
3. 구조적인 장점 : Build Script를 Groovy 기반의 DSL(Domail Specific Language)를 사용하여 코드로서 설정 정보를 구성하므로 구조적인 장점이 있다.
4. 편리함 : Gradle 설치 없이 Gradle wrapper를 이용하여 빌드를 지원한다.
5. 멀티 프로젝트 : Gradle은 멀티 프로젝트 빌드를 지원하기 위해 설계된 빌드 관리 도구이다.
6. 지원: Maven을 완전 지원한다.</code></pre><h3 id="maven-vs-gradle">Maven vs Gradle</h3>
<p><img src="https://velog.velcdn.com/images/_030702/post/a398abb1-9956-40fd-9df1-632162fe7920/image.png" alt=""></p>
<ol>
<li><p>스크립트 길이와 가독성 면에서는 Gradle이 우세하다</p>
</li>
<li><p>Build와 테스트 실행 결과 Gradle이 더 빠르다</p>
</li>
</ol>
<ul>
<li>Gradle은 캐시를 사용하기 때문에 테스트 반복 시 실행 결과 시간의 차이가 더 커진다</li>
</ul>
<ol start="3">
<li>의존성이 늘어날 수 록 스크립트의 품질의 차이가 커진다</li>
</ol>
<ul>
<li>Maven은 멀티 프로젝트에서 특정 설정을 다른 모듈에서 사용하려면 상속 받아야하지만, Gradle은 설정 주입 방식을 사용하므로 멀티 프로젝트에 적합하다</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[마이크로서비스 아키텍처 (MSA)]]></title>
            <link>https://velog.io/@_030702/%EB%A7%88%EC%9D%B4%ED%81%AC%EB%A1%9C%EC%84%9C%EB%B9%84%EC%8A%A4-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-MSA</link>
            <guid>https://velog.io/@_030702/%EB%A7%88%EC%9D%B4%ED%81%AC%EB%A1%9C%EC%84%9C%EB%B9%84%EC%8A%A4-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-MSA</guid>
            <pubDate>Sun, 08 Mar 2026 12:49:52 GMT</pubDate>
            <description><![CDATA[<h3 id="목적">목적</h3>
<p>지금까지 진행했던 프로젝트는 대부분 동아리나 개인 프로젝트 형태였기 때문에, 하나의 애플리케이션 중심으로 개발을 진행하는 경우가 많았다.
하지만 현재 회사에서 진행 중인 프로젝트는 각 기능을 모듈 단위로 분리하여 여러 개의 애플리케이션으로 구성되어 있다. 
이러한 구조를 Microservices Architecture(MSA) 라고 한다. 
이번 글에서는 MSA의 개념과 특징에 대해 정리해보고자 한다.</p>
<h3 id="msa-기반-모듈이란">MSA 기반 모듈이란?</h3>
<ul>
<li>Microservices Architecture</li>
<li>마이크로 서비스 아키텍처로 설계된 독립적인 서비스 단위를 의미</li>
<li>하나의 큰 애플리케이션을 작고 독립적인 서비스들로 쪼개는 아키텍처 방식을 의미함</li>
</ul>
<h3 id="모놀리식-vs-msa">모놀리식 vs MSA</h3>
<pre><code class="language-java">[모놀리식 - 전통 방식]
┌─────────────────────────┐
│  하나의 큰 애플리케이션      │
│  - 회원 관리              │
│  - 주문 처리              │
│  - 결제 처리              │
│  - 상품 관리              │
└─────────────────────────┘
→ 한 부분만 수정해도 전체 재배포

[MSA - 마이크로서비스]
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│회원 서비스 │  │주문 서비스 │  │결제 서비스 │  │상품 서비스 │
└──────────┘ └──────────┘ └──────────┘ └──────────┘
→ 각각 독립적으로 개발/배포/운영</code></pre>
<h3 id="msa-특징">MSA 특징</h3>
<p>각 모듈(서비스)은:</p>
<ul>
<li><strong>독립적인 데이터베이스</strong> 보유</li>
<li><strong>독립적인 배포</strong> 가능</li>
<li><strong>다른 언어/기술</strong> 사용 가능 (회원은 Java, 결제는 Python 등)</li>
<li><strong>API로 통신</strong> (REST API, gRPC 등)</li>
<li><strong>독립적인 확장</strong> (주문 서비스만 서버 추가 가능)</li>
</ul>
<h3 id="실제-예시">실제 예시</h3>
<pre><code class="language-java">쿠팡 같은 서비스:
- 회원 모듈 (로그인, 회원가입)
- 상품 모듈 (상품 검색, 상세)
- 주문 모듈 (장바구니, 주문)
- 결제 모듈 (결제 처리)
- 배송 모듈 (배송 추적)
- 리뷰 모듈 (상품 리뷰)

→ 각각 별도 팀이 개발/관리</code></pre>
<h3 id="장단점">장단점</h3>
<p><strong>장점:</strong></p>
<ul>
<li>부분 수정/배포 용이</li>
<li>장애 격리 (결제 오류가 검색에 영향 X)</li>
<li>기술 선택 자유</li>
<li>팀별 독립 개발</li>
</ul>
<p><strong>단점:</strong></p>
<ul>
<li>복잡도 증가</li>
<li>분산 시스템 관리 어려움</li>
<li>서비스 간 통신 오버헤드<ul>
<li>처리 시간 및 메모리가 추가적으로 사용되는 현상을 의미<ul>
<li>원래 A처리는 3초걸리는데 안정성을 위해 B처리 10초로 진행</li>
<li>이때의 오버헤드는 7초</li>
</ul>
</li>
<li>기존의 단일 Application인 Monolithic과 MSA의 정보 교환 방식은 근본적으로 다름</li>
<li>모놀리식 → 같은 메모리 공간을 쓰기 때문에, 단순히 함수를 호출하고 데이터가 담긴 메모리 주소값만 넘기면 됨.</li>
<li>MSA → 서비스가 서로 다른 서버에 존재. 정보를 주려면 데이터를 패킷으로 쪼개서 네트워크(HTTP, gRPC)를 타고 옆 동네로 넘어가야함<ul>
<li>추가로 다른 애플리케이션이기때문에 인증 및 인가도 매번 증명해야하며,</li>
<li>데이터 일관성을 유지하는데에 어려움 → 트랜잭션 하나로는 안됨. 2PC, Saga패턴과 같은 복잡한 로직을 사용해야함</li>
</ul>
</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[UMC 스터디 2주차] Spring Boot의 코어개념]]></title>
            <link>https://velog.io/@_030702/UMC-%EC%8A%A4%ED%84%B0%EB%94%94-2%EC%A3%BC%EC%B0%A8-Spring-Boot%EC%9D%98-%EC%BD%94%EC%96%B4%EA%B0%9C%EB%85%90</link>
            <guid>https://velog.io/@_030702/UMC-%EC%8A%A4%ED%84%B0%EB%94%94-2%EC%A3%BC%EC%B0%A8-Spring-Boot%EC%9D%98-%EC%BD%94%EC%96%B4%EA%B0%9C%EB%85%90</guid>
            <pubDate>Mon, 22 Sep 2025 07:21:06 GMT</pubDate>
            <description><![CDATA[<h2 id="spring이란">Spring이란?</h2>
<p><img src="https://velog.velcdn.com/images/_030702/post/02b01212-061d-40f8-9078-4d890a71f932/image.png" alt=""></p>
<h4 id="기업에서-사용하는-규모가-큰-자바-프로그램을-더-쉽게-만들-수-있게-도와주는-가볍고-오픈소스로-제공되는-미리-만들어준-코드의-뼈대를-제공하는-프로그램-개발-도구-프레임워크">기업에서 사용하는 규모가 큰 자바 프로그램을 더 쉽게 만들 수 있게 도와주는, 가볍고 오픈소스로 제공되는 미리 만들어준 코드의 뼈대를 제공하는 프로그램 개발 도구 (프레임워크)</h4>
<p>-&gt; Spring을 사용한다는건 Frame안에서 work를 한다고 할 수 있음</p>
<h2 id="api란">API란?</h2>
<p>프레임워크가 미리 구성된 것을 가져와서 사용한다는 의미에서 API와도, 라이브러리와도 비슷한 느낌이 들어 셋의 정확한 개념을 알고 있는 것이 중요하다.</p>
<h4 id="api는-애플리케이션-프로그래밍-인터페이스의-약자로-interface라는-이름에서-알-수-있듯-2개-이상의-소프트웨어-컴포넌트-사이에서-상호작용-할-수-있도록-정의된-인터페이스를-의미함">API는 <code>애플리케이션 프로그래밍 인터페이스</code>의 약자로 interface라는 이름에서 알 수 있듯 2개 이상의 소프트웨어 컴포넌트 사이에서 상호작용 할 수 있도록 정의된 인터페이스를 의미함</h4>
<p>-&gt; 즉, 소프트웨어 애플리케이션이 서로 데이터를 주고 받으며 통신할 수 있도록 만들어진 일련의 규칙이자 약속이라 할 수 있음. </p>
<h2 id="라이브러리란">라이브러리란?</h2>
<h4 id="라이브러리는-프로그램-개발에-필요한-함수-클래스-객체들을-모아둔-도구-상자와-같음">라이브러리는 프로그램 개발에 필요한 함수, 클래스, 객체들을 모아둔 도구 상자와 같음</h4>
<p>-&gt; 라이브러리의 제어권은 개발자에게 있음. 단순히 필요한 기능을 제공할 뿐임</p>
<hr/>

<h2 id="ioc">IoC?</h2>
<h4 id="inversion-of-control">Inversion of Control</h4>
<p>-&gt; 라이브러리의 제어권은 개발자에게 있음
-&gt; but, 프레임워크의 제어권은 프레임워크에 있음</p>
<p>따라서, 개발자가 프레임워크가 정해둔 규칙과 구조에 맞게 코드를 작성하고 삽입하면, 실제 실행 흐름은 프레임워크가 관리함</p>
<p>이를 우리는 <code>제어의 역전</code>이라고 부름</p>
<p>즉, <code>spring</code>은 <code>제어의 역전</code>에 해당하는 프레임워크임</p>
<h3 id="제어의-역전">제어의 역전</h3>
<p>그렇다면 우리가 왜 제어의 역전에 대해 이해하고 있어야하는지 궁금할 것 같음</p>
<p>위에 계속 언급했듯, 프로그램의 제어 흐름을 개발자가 직접 제어하는 것이 아니라 외부에서 관리하는 것을 제어의 역전이라고 함</p>
<p>일반적인 프로그램을 작성할 때는 객체의 생성, 설정, 초기화, 메서드 호출, 소멸까지의 모든 과정을 개발자가 직접 제어해야 함. 
-&gt; <code>애플리케이션의 실행 흐름, 생명주기는 모두 전적으로 개발자가 관리하는 구조임</code></p>
<p>But, 프레임 워크를 사용하면 객체의 생명 주기를 모두 프레임 워크에 위임함.</p>
<p>제어의 역전에서는 오브젝트가 스스로 사용할 오브젝트를 결정하지도 생성하지도 않음</p>
<h2 id="di란">DI란?</h2>
<h4 id="dependency-injection--의존성-주입">Dependency Injection = 의존성 주입</h4>
<p>-&gt; IoC가 제어권을 외부로 넘긴다면, 이를 실행하기 위해 DI를 사용함</p>
<p>DI는 객체가 필요로 하는 것(=종속성)을 직접 만들지 않고 외부에서 주입받는 방식을 의미함</p>
<blockquote>
<p>토비의 스프링에서는 다음의 세 가지 조건을 충족하는 작업을 의존관계 주입이라고 말함.
    1. 클래스 모델이나 코드에는 런타임 시점과 의존관계가 들어나지 않음. 즉, 인터페이스에만 의존하고 있어야 함.
    2. 런타임 시점의 의존관게는 컨테이너나 팩토리와 같은 제 3의 존재가 결정함.
    3. 의존관게는 사용할 오브젝트에 대한 래퍼런스를 외부에서 주입해주며 만들어짐.</p>
</blockquote>
<p>여기서 객체를 생성하고 관리하며 의존관계를 연결해주는 컨테이너를 우리는 IoC 컨테이너 혹은 DI 컨테이너라고 부름
-&gt; 객체가 생성자 인수, 메서드에 대한 팩토리 메서드에서 생성되거나 반환된 후에 객체 인스턴스에 설정된 속성을 통해서만 종속성을 정의하는 프로세스라고 보면 됨.</p>
<p>정리해보면, </p>
<ol>
<li>객체가 자기 안에서 필요한 것들을 직접 만들지 않고,</li>
<li>생성자 인자, 메서드, 혹은 팩토리 메서드를 통해 외부에서 만들어진 것을 전달받아 사용함</li>
<li>전달 받은 후에는 객체 안에서 설정된 속성을 통해서만 종속성을 사용함</li>
</ol>
<h2 id="그렇다면-종속성은-무엇인가--">그렇다면 종속성은 무엇인가 .. ?</h2>
<h4 id="의존성이란-한-객체가-다른-객체에-기능이나-데이터를-필요로-하는-관계를-의미---즉-한-객체는-다른-객체없이-제대로-동작할-수-없는-상태">의존성이란 한 객체가 다른 객체에 기능이나 데이터를 필요로 하는 관계를 의미 -&gt; 즉 한 객체는 다른 객체없이 제대로 동작할 수 없는 상태</h4>
<h2 id="외부로부터-의존성-주입하는-것이-왜-필요한가-">외부로부터 의존성 주입하는 것이 왜 필요한가 ?</h2>
<p>개발 상황에서는 객체를 마주했을때 서로 의존되어있는 경우가 대다수임.
독립적으로 존재하는 객체도 존재할 수 있으나, 로직을 구현하면서 필연적으로 서로 의존성을 지닌 경우가 많을 것임</p>
<p>ex1) RegularCoffeeBean을 커피 원두로 사용 -&gt; <code>원두 일체형</code></p>
<pre><code>public static class RegularCoffeeBean{
        public void grind() {
        System.out.println(&quot;기본 원두로 갈고 있습니다&quot;);
    }
}

public static class CoffeeMachine {
    private RegularCoffeeBean regularCoffeeBean;

    public CoffeeMachine() {
        this.regularCoffeeBean = new RegularCoffeeBean();
    }

    public void brew() {
        regularCoffeeBean.grind();
        System.out.println(&quot;기본 원두로 커피를 내립니다.&quot;);
    }
}</code></pre><p>ex2) <code>원두 분리형</code></p>
<pre><code>// 인터페이스
public interface CoffeeBeans {
    void grind();
}

// 인터페이스 구현하는 구현체 클래스
@Component(&quot;regularBean&quot;)
public static class RegularCoffeeBeans implements CoffeeBeans {
      @Override
    public void grind() {
        System.out.println(&quot;기본 원두로 갈고 있습니다&quot;);
    }
}

// 원두 종류 바꾸고싶을땐? 구현체 클래스를 또 만들면 됨
@Component(&quot;decafBean&quot;)
public static class DecafCoffeeBeans implements CoffeeBeans {

      @Override
    public void grind() {
        System.out.println(&quot;디카페인 원두로 갈고 있습니다&quot;);
    }
}

// 커피머신
@Component
public static class CoffeeMachine {

        @Autowired
        @Qualifier(&quot;decafBean&quot;)
    private CoffeeBeans coffeeBeans;

    public void brew() {
        coffeeBeans.grind();
        System.out.println(&quot;원두로 커피를 내립니다.&quot;);
    }
}
</code></pre><p>원두일체형은 <code>강한 결합</code> 원두분리형은 <code>느슨한 결합</code>이라고 할 수 있음
-&gt; 각 객체끼리 의존성이 존재할 때 의존성을 외부에서 주입받는 것이 바람직 함.</p>
<hr/>

<h2 id="서블릿">서블릿</h2>
<h4 id="자바-서블릿은-웹페이지를-동적으로-생성하는-서버-측-프로그램을-의미함">자바 서블릿은 웹페이지를 동적으로 생성하는 서버 측 프로그램을 의미함</h4>
<p>기존의 서버는 정적인 자료만을 주고 받고 있었음. 
웹이 다양한 기능을 요구하게 되면서 사용자의 요구에 맞춘 동적인 페이지를 만들 필요가 생김 -&gt; 이를 위해 만들어진 것이 서블릿 !</p>
<pre><code>1. WS (Web Server)
- 웹서버를 뜻하며 주로 정적인 컨텐츠 (HTML, img ..)를 클라이언트에 전달함
- 요청 -&gt; 응답 (대부분 HTTP) 
- 정적인 웹페이지 처리에 최적화
- 동적인 처리 (데이터 베이스 접근 불가) 
- Apache HTTP Server, Nginx 존재

2. WAS (Web Application)
- WAS는 웹 애플리케이션 서버라고 부르며, 동적인 웹 서비스를 처리함
- 클라이언트 요청 -&gt; 서버 측에서 로직 처리 -&gt; 결과 응답
- DB 접근, 비즈니스 로직 실행 가능
- 주로 Servlet, JSP, Spring 등 동적인 웹 서비스 제공 가능
- Tomcat, JBoss, WebLogic, Spring Boot embedded Tomcat 존재</code></pre><h2 id="서블릿-동작-방식--범용">서블릿 동작 방식 : 범용</h2>
<p><img src="https://velog.velcdn.com/images/_030702/post/25517aaa-9164-484a-9e98-1100ad7fc7fb/image.png" alt=""></p>
<h2 id="서블릿-동작-방식--스프링">서블릿 동작 방식 : 스프링</h2>
<p><img src="https://velog.velcdn.com/images/_030702/post/b6f17d10-087c-4e86-8006-0494830bd85a/image.png" alt=""></p>
<hr/>

<h2 id="servlet-container란">Servlet Container란?</h2>
<h4 id="서블릿-객체를-생성-초기화-호출-종료하는-생명주기의-담당함">서블릿 객체를 생성, 초기화, 호출, 종료하는 생명주기의 담당함.</h4>
<p>Servlet이 어떤 역할을 수행하는 메뉴얼이라면, 
Servlet Container는 클라이언트의 요청을 받아주고 응답할 수 있게 웹 서버과 Socket으로 통신하며 직접 핸들링한다고 볼 수 있음.</p>
<pre><code>클라이언트의 요청이 들어오면 ...
서블릿 컨테이너는 web.xml을 기반으로 사용자가 요청한 URL이 어느 서블릿에 대한 요청인지 찾음

그렇다면 web.xml은 무엇인가?
-&gt; 웹 애플리케이션의 배치 파일로 서블릿, 필터, 리스너 등 웹 구성 요소의 설정 정보를 정의하는 환경설정 파일임
-&gt; 여기서 정의된 서블릿과 URL 매핑 정보를 확인하고, 매핑된 서블릿이 있으면 해당 서블릿으로 전달하고, 서블릿이 처리한 응답을 브라우저로 보내줌.</code></pre><p>본래 네트워크를 통한 소통을 위해서는 <code>Socket</code>을 만들고 <code>listen()</code>, <code>accept()</code>, <code>connect()</code>를 통해 구현해야함.
하지만 servlet 컨테이너는 이러한 기능을 api로 제공하여 간편화 함</p>
<p>-&gt; 우리는 servlet으로 구현해야 할 비즈니스 로직에 대해서만 집중하면 되는 것임 !!!</p>
<hr/>

<h2 id="스프링에서의-di-ioc-servlet">스프링에서의 DI, IoC, Servlet</h2>
<h4 id="bean">Bean</h4>
<p>빈은 Spring 컨테이너가 관리하는 자바의 객체를 의미함.
Spring은 Bean을 통해 객체를 인스턴스화한 후, 객체 간의 의존관계를 관리함.
객체가 의존관계를 등록할 때, Spring 컨테이너에서 해당하는 빈을 찾고, 그 빈과 의존성을 만들게 됨.</p>
<h3 id="일반적인-자바를-사용하는-경우">일반적인 자바를 사용하는 경우</h3>
<pre><code>public class UMC9thMember {
        private int age;
        private String nickname;
        private String school;
        private String studyPart;

        public UMC9thMember(){            
        }

        public UMC9thMember(int age, String nickname, String school, String studayPart) {
                this.age = age;
                this.nickname = nickname;
                this.school = school;
                this.studyPart = studyPart;
        }
}

// 여기서 객체를 생성하기 위해서는 
// new 생성자 사용 -&gt; 개발자가 전적으로 생명주기, 의존성 관리 진행

UMC9thMember umc9thMember = new UMC9thMember(23, &quot;마크&quot;,&quot;상명대학교&quot;, &quot;Server(SpringBoot)&quot;);</code></pre><h3 id="bean을-사용하는-경우">Bean을 사용하는 경우</h3>
<p><code>@Component</code> → <code>@Autowired</code> : <strong>묵시적 빈 정의</strong></p>
<ul>
<li>클래스에 어노테이션을 추가하고, Autowired로 다른 클래스에서 해당 Bean을 끌어온다.</li>
<li>@Controller, @Service, @Repository를 사용하면 그 자체로 묵시적 빈 정의를 한 것이라고 볼 수 있음.</li>
</ul>
<p><code>@Configuration</code> → <code>@Bean</code> : <strong>명시적 빈 정의</strong> </p>
<ul>
<li>Spring 설정 파일에 Configuration 어노테이션을 추가하고, Bean 어노테이션을 붙여 명시적으로 빈을 지정한다.</li>
<li>스프링 라이브러리에 없는 외부 라이브러리에서도 Bean 생명주기를 사용할 수 있음.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[초록스터디] Spring core ]]></title>
            <link>https://velog.io/@_030702/%EC%B4%88%EB%A1%9D%EC%8A%A4%ED%84%B0%EB%94%94-Spring-core</link>
            <guid>https://velog.io/@_030702/%EC%B4%88%EB%A1%9D%EC%8A%A4%ED%84%B0%EB%94%94-Spring-core</guid>
            <pubDate>Wed, 25 Jun 2025 08:34:54 GMT</pubDate>
            <description><![CDATA[<p>시작에 앞서
Spring을 초록스터디를 통해 처음 공부해보았는데, 첫번째 방탈출 미션을 드디어 완료하였다. JAVA 미션을 진행할때는 잘 몰랐던 것을 배울 수 있어서 좋았다. 특히, 멘토 오찌님이 공부하고 생각할 거리를 많이 주셔서 배울 수 있었던 것 같다. 이번 core 미션은 Spring context를 이해하는데에 초점을 맞추어 공부했으나, 어렵다 . . . 따라서, 이번 미션을 진행하며 고민해보았거나, 오찌님이 질문해주신 부분들을 정리해두려 한다.</p>
<h2 id="1-spring-context란">1. Spring Context란?</h2>
<blockquote>
<p><strong>Spring Context</strong> 란 빈(Bean) 객체들을 생성하고 관리하는 스프링의 핵심 컨테이너이다.</p>
</blockquote>
<ul>
<li>Bean: 스프링이 관리하는 객체 (Controller, Service, Repository)</li>
<li>Container: Bean의 생명주기, 의존성 주입 등을 관리하는 스프링 내부 시스템</li>
<li>Context: Bean 등록, 조회, 설정 등을 처리하는 객체 = ApplicationContext</li>
</ul>
<h2 id="2-예외처리-위치">2. 예외처리 위치</h2>
<blockquote>
<p>예외처리는 도메인에서 처리하는 것이 좋을까, 서비스에서 처리하는 것이 좋을까</p>
</blockquote>
<p>도메인에서 처리할 수 있는 예외는 도메인에서, 그 외에는 서비스에서 처리하는 것이 좋다.
-&gt; 최대한 더 낮은 계층에서 처리하는 것을 원칙으로 삼자.</p>
<p>시간 객체의 시간 값에 null이 들어왔다 -&gt; 도메인에서 예외처리
시간 객체의 시간 값은 중복이면 안된다 -&gt; 도메인 객체만으로는 안되고 저장된 다른 객체를 봐야함 -&gt; 서비스에서 예외처리</p>
<h2 id="3-controller---repository-직접-의존">3. Controller -&gt; Repository 직접 의존</h2>
<p>[Controller]
↓ 요청 전달 / 응답 반환 [DTO]
[Service]
↓ 비즈니스 로직 처리
[Repository]
↓ DB 접근
[Domain]
↔ DB 매핑</p>
<p>+) [Exception] : 예외처리
+) [View] : 화면에 보이는 것</p>
<p>이와 같은 관계를 갖고 애플리케이션은 설계되는데, 
Controller 는 요청/응답 처리에 집중해야하며, Repository는 DB 접근에 집중해야함.
또한, 비즈니스 로직은 Service에서 진행해야함.</p>
<p>-&gt; 이 부분 제발 좀 ... 지키자 희정아 ...
✅ 설계 시작 전에 계층별 책임 적어보기    &quot;이건 Service 책임인가?&quot; 먼저 생각하기
✅ 단위 테스트 작성    비즈니스 로직은 Service에서 테스트할 수 있어야 함
✅ 객체 간 의존 구조 그려보기    간단한 UML, 화살표 그리기 등으로 시각화
✅ Controller에서 로직이 길어지면 &quot;서비스로 옮길 수 있지 않을까?&quot; 되묻기</p>
<h2 id="4-autowired-없이도-작동-">4. <code>@Autowired</code> 없이도 작동 ?</h2>
<pre><code>public class ReservationController {
    private final ReservationService reservationService;

    @Autowired
    public ReservationController(ReservationService reservationService) {
        this.reservationService = reservationService;
    } </code></pre><p>-&gt; 이 코드에서는 <code>@Autowired</code> 없이도 작동 가능함.
Spring 4.3부터 클래스에 생성자가 하나 뿐이라면, 자동으로 주입가능한 Bean을 찾아서 주입함.
당연히, 생성자 2개 이상일 때는 생략하지 않아도 됨.</p>
<h2 id="5-상태코드-메서드">5. 상태코드 메서드</h2>
<pre><code>ResponseEntity
.status(HttpStatus.CREATED)
.location(location)
.body(ReservationResponse.from(savedReservation));</code></pre><p>↓</p>
<pre><code>ResponseEntity
.created(location)
.body(ReservationResponse.from(savedReservation));</code></pre><p>로 축약할 수 있음. </p>
<p>+) 
ok = 200
created(URI) = 201
accepted() = 202
noContent() = 204
badRequest() = 400
notFound() = 404</p>
<h2 id="6-삭제를-의도했는데-삭제가-되지-않은-상황의-예외처리는-서비스에서-">6. 삭제를 의도했는데 삭제가 되지 않은 상황의 예외처리는 서비스에서 !</h2>
<ul>
<li>TimeController<pre><code>boolean deleted = timeService.deleteTime(id);
      if (!deleted) {
          return ResponseEntity.badRequest().build();
      }</code></pre>본래 Controller에서 단순 삭제 성공 여부만 판단하고 있다고 생각하여 Controller에 예외처리 로직을 작성하였는데, <code>삭제 실패와 같은 비즈니스 로직 실패는 Service</code>에서 다루는 것이 맞는 방식이다.</li>
</ul>
<p>따라서, 말씀해주신대로,
삭제 실패 -&gt; Service
성공 흐름 -&gt; Controller
나머지 예외는 exception에서 처리하는 것으로 수정하였다. </p>
<p>&lt;수정 후&gt;</p>
<ol>
<li>Controller는 성공한 흐름만 다룸 <pre><code>@DeleteMapping(&quot;/{id}&quot;)
 public ResponseEntity&lt;Void&gt; deleteTime(@PathVariable Long id) {
     timeService.deleteTime(id);
     return ResponseEntity.noContent().build();
 }</code></pre></li>
</ol>
<p>2.Service에서 예외 표현함(비즈니스 로직 예외)</p>
<pre><code>public boolean deleteTime(Long id) {
        if(!timeRepository.deleteById(id)) {
            throw new InvalidTimeException(&quot;없는 시간입니다.&quot; + id);
        }
        return timeRepository.deleteById(id);
    }</code></pre><h2 id="7-도메인에-setter">7. 도메인에 setter</h2>
<p>도메인 객체에 setter를 열어두는 것은 객체 상태를 마음대로 바꿀 수 있어, 캡슐화에 어긋나고, 객체 불변성 유지에 어려워짐.</p>
<p>보통 도메인 객체를 수정해야하는 경우
서비스 클래스에서 도메인 객체를 받아
서비스 클래스에서 상태 변경을 수행하고,
변경된 도메인 객체를 Repository에 저장하는 방식을 사용할 것 !</p>
<h2 id="8-validation-꼭-유효성-검증-꼬옥-">8. validation 꼭. 유효성 검증 꼬옥 !!!</h2>
<p>까먹지 말긔 ... </p>
<h2 id="9-localtime-vs-string">9. LocalTime vs String</h2>
<ol>
<li>LocalTime</li>
</ol>
<ul>
<li>타입 안정성: 시간 정보를 명확한 타입으로 표현해서, 시간 연산(더하기, 빼기 등)을 안전하게 수행 가능</li>
<li>유효성 검사 내장: 잘못된 시간 값(예: 25:00)은 생성 자체가 안 됨</li>
<li>표준 API 지원: 포맷팅, 파싱, 비교, 계산 등 편리한 메서드 제공</li>
<li>명확한 의미 전달: 변수 타입만 보고도 시간 정보임을 알 수 있음</li>
<li>DB 매핑 주의 필요: JDBC에서 LocalTime 직접 매핑이 안 될 수 있어, java.sql.Time으로 변환 필요</li>
<li>직렬화/역직렬화 설정 필요: JSON 직렬화 시 기본 String 포맷 지정 필요(예: HH:mm:ss)</li>
</ul>
<ol start="2">
<li>String</li>
</ol>
<ul>
<li>단순 저장/전송: 포맷을 문자열로 자유롭게 표현 가능 (예: &quot;10:00&quot;, &quot;10시&quot;, &quot;10:00 AM&quot;)</li>
<li>빠른 구현: 복잡한 타입 변환 없이 바로 사용 가능</li>
<li>유효성 검사 어려움: &quot;25:00&quot;, &quot;abc&quot; 같은 잘못된 시간 값도 들어갈 수 있음</li>
<li>시간 연산 불가: 시간 더하기 빼기 등 로직 작성 시 문자열 파싱 필요</li>
<li>타입 의미 모호: 그냥 문자열이어도 시간으로 처리 가능</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>