<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>훈이의 개발일기</title>
    <link>https://chaehun97.tistory.com/</link>
    <description>천천히, 하나씩</description>
    <language>ko</language>
    <pubDate>Sun, 19 Jul 2026 22:42:01 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>CHun2</managingEditor>
    <item>
      <title>홈 서버 구축기(1) - 미니PC, 서버 환경으로 만들어 보자</title>
      <link>https://chaehun97.tistory.com/entry/%ED%99%88%EC%84%9C%EB%B2%84-%EA%B5%AC%EC%B6%95%EA%B8%B01-%EB%AF%B8%EB%8B%88PC-%EC%84%9C%EB%B2%84-%ED%99%98%EA%B2%BD%EC%9C%BC%EB%A1%9C-%EB%A7%8C%EB%93%A4%EC%96%B4-%EB%B3%B4%EC%9E%90</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;AWS 무료티어 기간이 전부 소모되어 갈쯤 미니PC를 사서 서버를 마이그레이션 해보고자 해서 중고나라에서 구매하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가격은 46만원... ryzen 5700u, ram 32GB, m.2 512GB&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요새 램값이며 ssd값이며 AI 돌풍 덕분에 엄청 가격이 올랐다. 그거 치고는 잘 산듯!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 우리집 환경(?)을 살펴보자면&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메인 pc는 집에서 개발 및 서핑용으로 사용하고 있다&lt;/li&gt;
&lt;li&gt;미니PC는 서버 겸 넷플릭스나 유튜브 보기용으로 사용할 예정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마침 구매한 미니PC가 윈도우 버전이 pro이기도 하고, linux를 바로 깔기에는 좀 아깝다고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제일 처음 생각 프로세스는 이거였다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;미니PC가 윈도우 프로니까 pro버전은 윈도우 원격(MSTSC)가 가능함&lt;/li&gt;
&lt;li&gt;wsl이라고 윈도우에서 리눅스기능을 파워쉘에서 사용할 수 있게 해주는 기능이 있음&lt;/li&gt;
&lt;li&gt;그래서 윈도우 자체에 리눅스 cli를 깔아서 docker 마이그레이션 하려고 했음&lt;/li&gt;
&lt;li&gt;장점이라고 생각한게 원격으로 조정할 수 있으면 gui(꼼수...)니까 파일관리도 쉽다고 생각했음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대충 이렇게 머릿속에 프로세스를 세웠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 윈도우 원격(MSTSC)의 계정 억까&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내방 PC는 와이파이, 거실 미니 PC는 공유기 랜선에 물려있다. 어차피 같은 집 안이라 같은 공인IP를 공유하는 서브넷(정처기에서 배움 ㅋㅋ) 대역일 텐데 ping 신호 조차 안 닿음...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공유기 내부의 AP Isolation 기능이 켜져서 무선과 유선트래픽이 찢어졌나 싶어서 공유기 관리자 페이지를 삽질하다가 다른 원인을 찾아보니까 윈도우 방화벽 인바운드 규칙에서 원격 포트인 3389를 강제로 수동 개방하는 등 온갖 삽질을 다해봤지만 결국 안됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 진짜 원인은 Microsoft 계정 연동 및 Windows Hello 보안 정책 때문이었음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;win11은 최초 설치 시 온라인 계정 로그인을 하지 않으면 그냥 컴퓨터 못쓰게 하더라. 최근에 미니pc 초기화 시켜서 앎... 그리고 보안 번호나 생체 인식을 기본 인증 수단으로 쓴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 mstsc쓰려면 아이디와 비밀번호가 필요한데... 원격 프로토콜(RDP)는 '@'가 들어가버리면 이상해져 버린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RDP 내부는 '@' 포맷이 들어가버리면 대기업 사내 인프라 망 환경에서 쓰는 UPN(User Principal Name)으로 인식한다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;abc@naver.com면 유저ID는 abc고 naver.com라는 회사 인증 서버를 찾아가서 인증하라고 신호를 쏴버린다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 내 아이디는 마이크로소프트 아이디고.. 근데 이메일 형식으로만 가입할 수 있는데? 안되네?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방법 찾아보다가 로컬 계정으로 전환하면 내 아이디에 '@'를 뺄 수 있다고함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 미니PC로 들어가서 로컬 관리자 계정 새로 생성해서 방에서 시도해보니 안된다.. ㅡㅡ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정신차리고 생각을 바꿨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아니 뭐 밖에서도 미니PC 볼수도 있잖아? 하고 그냥 RDP 포트 열어버리고 임의로 외부 포트를 정해주고 내부 포트는 3389로 해줬다. 내부 포트번호 그대로 매핑을 해버리면 해킹 봇들이 랜덤으로 아이피 찔러보다가 내 아이피에 3389를 찔러보니까 비밀번호만 알면되네? 해서 브루트 포스로 비밀 번호를 뚫어버릴 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포트 바꾸는 것만으로는 봇들이 시도할 수 있는 경우를 줄이는 것이고 비밀번호를 복잡하게 거는 것이 관건이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 약 30자에 달하는 영문, 숫자, 특수 문자를 넣어서 비밀번호를 설정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 비밀번호 뚫으려면 3090으로 약 9억 년 걸린다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포트포워딩하니까 한방에 해결!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아 그리고 네트워크 환경이 public 말고 private로 설정이 되어있어야 하고, 'Windows 방화벽에서 앱허용' 에서 '원격 데스크톱'을&amp;nbsp; 꼭 체크 해줘야한다.&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. WSL2의 억까&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원격 화면을 띄우는데 성공했다. 윈도우 자체 리눅스 환경인 WSL2를 깔기 위해서 wsl --install 명령어를 실행하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 윈도우 내부 구성 요소가 꼬였는데 계속 알 수 없는 에러 코드를 내뱉었다. 계속 시스템 파일 고치겠다고 하루 종일 붙잡고 있으면 밤 세울듯 해서 다른 우회 방법을 생각해냈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;윈도우는 써야겠고 리눅스도 써야겠고... 디스크 파티션 나눈 후&amp;nbsp; OS 따로 깔아서 까는 방법이 있지만 나는 가상 머신 + 우분투 LTS를 사용하기로 해서 그것으로 노선으 변경했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;17&quot; data-ke-size=&quot;size26&quot;&gt;3. 네관 2급 짬바&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;음... 컴퓨터를 보니까 랜포트가 두 개네? 그럼 리눅스따로 윈도우 따로 독립적으로 할당이 가능하겠다 싶었는데 랜선이 한 개다. 새벽 시간이라 다이소는 닫았다. 하지만 갑자기 솟아날 구멍이 생각났다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;랜선 길이가 2m 정도 되니까 잘라서 쓰면 되지 않을까? 작년에 네트워크 관리사 2급 자격증을 취득하였는데 집에서 잠자고 있떤 랜툴과 RJ-45 커넥터를 꺼내서 망설힘 없이 랜선을 반으로 잘랐다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;흰주 - 주 - 흰녹 - 파 - 흰파 - 녹 - 흰갈 - 갈&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;순으로 연결해서 랜툴 찝어주니까 랜선이 두 개가 되었고 ipconfig /all 로 테스트 해보니까 두 개 다 잘 물려있다는 것을 확인했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;17&quot; data-ke-size=&quot;size26&quot;&gt;4. 가상 머신 세팅&lt;/h2&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;우분투 LTS를 받고 윈도우가 잡고 있는 랜카드를 피해서 할당해주었다. 원래 윈도우에 물리 랜선 1을 물리고 2를 가상머신에 물릴려고 했는데 이미 윈도우에 static ip를 잡아놓은 상태라 바꾸기 곤란하였다. 잡소리는 치워두고...&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;우분투 안에서 랜선 1을 할당받은 다음 static ip로 지정해주고, 우분투 내부 22(SSH)를 포트포워딩 해주었다. 이것도 비밀번호를 설정하는 것이 중요하고 암호화 키 생성해서 더욱 보안을 강화하는 것이 중요하다. 하지만 9억년 걸린다고 하니 추후에 생각하기로 함...&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;뭐 다른 설정은 iptime에 들어가서 DDNS 설정해서 내 공인 ip 입력하게 하면 내 미니PC로 설정해 주고 SSH 접속을 더 원활하게 할 수 있도록 설정하였다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;그리고 윈도우가 켜지면 내 우분투 서버를 자동으로 실행하게 윈도우에 스크립트 설정을 하고 마쳤다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;17&quot; data-ke-size=&quot;size26&quot;&gt;5. 다시 생각 해보기&lt;/h2&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 하고보니 굳이 SSH를 연결했는데&amp;nbsp; 3389 포트를 굳이 열어야할까 생각들었다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;그래서 더 알아보니&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li data-path-to-node=&quot;17&quot;&gt;RDP 프로토콜 자체에 Windosw OS 보안 취약점이 발견되면 비밀번호 300자 되어도 비밀번호 입력 단계를 우회해서 서버가 털릴 수 있다.&lt;/li&gt;
&lt;li data-path-to-node=&quot;17&quot;&gt;외부에서 IPTime의 VPN 기능을 켜서 집 네트워크로 접속한 뒤 RDP를 쓰거나 Tailscale 같은 Zero Trust Mesh VPN을 쓰는게 보안상 나전하다고 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 바로 닫았다.. ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 과연 22번 포트도 안전한가도 알아보았다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;외부 포트를 내임의로 지정해놔 단순한 봇들은 필터링할 수 있지만 포트 스캐너를 사용하면 무용지물이라고 한다.&lt;/li&gt;
&lt;li&gt;내 공인IP가 타겟이 되거나 무작위 전체 스캔에 걸리면 1번포트부터 65535번까지 포트를 전부 찔러본다고 한다.&lt;/li&gt;
&lt;li&gt;그러면 패킷을 보내보고 SSH가 응답하구나? 해서 털린다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5.1 누가 내서버 찔러봤나 확인 해보기&lt;/h3&gt;
&lt;pre id=&quot;code_1780149889594&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo journalctl _SYSTEMD_UNIT=ssh.service | grep &quot;Failed&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSH 로그인 실패 로그만 쏙 골라서 한번 봤다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1060&quot; data-origin-height=&quot;149&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWfNhN/dJMcagFJ8FZ/OSiK6Hlc2LzsZC3DO2NzP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWfNhN/dJMcagFJ8FZ/OSiK6Hlc2LzsZC3DO2NzP1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWfNhN/dJMcagFJ8FZ/OSiK6Hlc2LzsZC3DO2NzP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWfNhN%2FdJMcagFJ8FZ%2FOSiK6Hlc2LzsZC3DO2NzP1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;650&quot; height=&quot;91&quot; data-origin-width=&quot;1060&quot; data-origin-height=&quot;149&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;5시부터 6시 사이에는 내가 접속한 게 맞다. 192.168.0.1이면 공유기 내부 네트워크 타고 들어온게 맞는데&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;오전 8시 51분 부터 로그 찍혀있는거보고 소름 돋았다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;117.18.XX 이거 중국에서 찌른거란다..&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;그래서 성공기록을 한번 보았다.&lt;/p&gt;
&lt;pre id=&quot;code_1780150293670&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo journalctl _SYSTEMD_UNIT=ssh.service | grep -E &quot;Accepted|session opened&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1183&quot; data-origin-height=&quot;275&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6oflp/dJMcagTfmzO/HFFIEfn0kPArshR8KAEPbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6oflp/dJMcagTfmzO/HFFIEfn0kPArshR8KAEPbK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6oflp/dJMcagTfmzO/HFFIEfn0kPArshR8KAEPbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6oflp%2FdJMcagTfmzO%2FHFFIEfn0kPArshR8KAEPbK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;650&quot; height=&quot;151&quot; data-origin-width=&quot;1183&quot; data-origin-height=&quot;275&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;중간에 211이 있는데 내 휴대폰으로 핫스팟 켜서 노트북으로 접속해 본 거라 예상했고 그 예상은 맞았다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;다행이다 일단 누군가에게 뚫리진 않은거다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;17&quot; data-ke-size=&quot;size23&quot;&gt;5.2 키 페어 방식으로 수정&lt;/h3&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;굉장히 위험하다고 몸소 느낀 후 SSH Key 페어를 만들어 메인PC에는 비밀키를 두고 미니PC 서버의 우분투에는 공개키를 심었다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;가장 보안성이 좋고 최신 트렌드인 ed25519를 사용해서 키를 만들었다. 기존 RSA보다 훨씬 가볍고 강력한 최신 암호화 알고리즘을 쓴다고 한다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1780152607087&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# --------- Windows PowerShell ---------

#1. 키 생성하기
ssh-keygen -t ed25519 -b 4096 -f ~/.ssh/id_minipc

#2.서버에 공개키 보내기
ssh-copy-id -i ~/.ssh/비밀키이름 ubuntu-server@192.168.0.X


#3. 테스트
ssh -i ~/.ssh/비밀키이름c ubuntu-server@192.168.0.X

# --------- ubuntu cli ---------

#5. // sshd-config 에서 비밀번호 형식으로 사용하지 않게 설정
sudo nano /etc/ssh/sshd_config 

PasswordAuthentication no
ChallengeResponseAuthentication no 

# ssh 리스타트
sudo systemctl restart ssh&lt;/code&gt;&lt;/pre&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;이제 내 윈도우 .ssh 폴더 내부에 있는 키가 없으면 이서버에 접속하지 못한다. 그래서 이 키를 사용해 바탕화면에 접속하는 바로가기 아이콘을 만들었다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1780152746312&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cmd.exe /k ssh -p 50022 -i %USERPROFILE%\.ssh\비밀키이름 ubuntu-server@192.168.0.X&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;17&quot; data-ke-size=&quot;size26&quot;&gt;6. 느낀점&lt;/h2&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 그저 AWS 프리티어 만료를 피하기 위해서 가성비 마이그레이션 프로젝트로 시작했다. 그 과정에서 만난 Windows RDP의 억까, wsl2의 알 수 없는 에러들이 나를 시험했지만 나는 아이디어가 넘치기(?) 때문에 다른 방법으로 우회하는 것을 택했다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;특히 다이소 갈 시간도 없는 새벽에 랜선 반으로 잘라 랜툴로 찝어서 가상 머신에 독립된 회선을 만들었으며 자격증을 괜히 딴 거 아니구나 하고 괜히 뿌듯했다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;무엇보다 가장 소름 돋았던 점은 비밀번호 30자로 늘렸으니 안전하겠지 라는 안일한 생각과 다르게 로그를 눈으로 확인했을 때 RDP 취약점과 포트 스캐너 앞에서 얼마나 무기력한지, 보안에는 적당히가 없다는 것을 깨달았다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;편의성을 위해 RDP 3389 포트를 과감히 닫고 최신 암호화 키페어 ed25519를 사용하여 접속하게 하고 패스워드 로그인 자체를 막아버렸다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;그리고 효율성을 위해서 바탕화면에 딸각 한 번으로 접속하는 바로 가기 아이콘까지 만드니까 비로소 나만의 홈서버가 만들어졌다는 느낌이 들었다. 삽질은 너무 많이 했지만 내부 원리와 보안의 본질을 온몸으로 체감하였다.&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;다음에는 80포트(http) 443(https) 포트 열고 내 프로젝트를 마이그레이션 해서 띄워보는 것을 포스팅할 예정이다.&lt;/p&gt;</description>
      <category>홈 서버</category>
      <category>ed25519</category>
      <category>ssh</category>
      <category>sshkey</category>
      <category>네트워크</category>
      <category>미니pc</category>
      <category>삽질기</category>
      <category>우분투</category>
      <category>인프라</category>
      <category>홈서버</category>
      <author>CHun2</author>
      <guid isPermaLink="true">https://chaehun97.tistory.com/83</guid>
      <comments>https://chaehun97.tistory.com/entry/%ED%99%88%EC%84%9C%EB%B2%84-%EA%B5%AC%EC%B6%95%EA%B8%B01-%EB%AF%B8%EB%8B%88PC-%EC%84%9C%EB%B2%84-%ED%99%98%EA%B2%BD%EC%9C%BC%EB%A1%9C-%EB%A7%8C%EB%93%A4%EC%96%B4-%EB%B3%B4%EC%9E%90#entry83comment</comments>
      <pubDate>Sun, 31 May 2026 00:20:50 +0900</pubDate>
    </item>
    <item>
      <title>[Troubleshooting] Nginx 설정이 배포만 하면 초기화되는 현상 (Docker &amp;amp; GitHub Actions)</title>
      <link>https://chaehun97.tistory.com/entry/Troubleshooting-Nginx-%EC%84%A4%EC%A0%95%EC%9D%B4-%EB%B0%B0%ED%8F%AC%EB%A7%8C-%ED%95%98%EB%A9%B4-%EC%B4%88%EA%B8%B0%ED%99%94%EB%90%98%EB%8A%94-%ED%98%84%EC%83%81-Docker-GitHub-Actions</link>
      <description>&lt;h3 data-path-to-node=&quot;3&quot; data-ke-size=&quot;size23&quot;&gt;1. 문제 상황 (Issue)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;4&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4,0,0&quot;&gt;현상&lt;/b&gt;: API 호출 시 400 Bad Request&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4,1,0&quot;&gt;조치 내용&lt;/b&gt;: 운영 서버(EC2)에 접속해 Nginx 컨테이너 설정을 수동으로 수정하여 정상 작동을 확인.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4,2,0&quot;&gt;재발&lt;/b&gt;: GitHub Actions를 통해 &lt;b data-index-in-node=&quot;23&quot; data-path-to-node=&quot;4,2,0&quot;&gt;자동 배포를 진행하면 수정한 설정이 사라지고 다시 에러 발생&lt;/b&gt;. 어제 고쳤는데 오늘 다시 터지는 '무한 루프' 상황 발생.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-path-to-node=&quot;5&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-path-to-node=&quot;6&quot; data-ke-size=&quot;size23&quot;&gt;2. 원인 분석 (Root Cause)&lt;/h3&gt;
&lt;h4 data-path-to-node=&quot;7&quot; data-ke-size=&quot;size20&quot;&gt;  기술적 원인: 잘못된 Nginx 설정&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;8&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,0,0&quot;&gt;400 에러&lt;/b&gt;: proxy_set_header Connection &quot;&quot;; 설정 때문. SSE(Server-Sent Events)를 위해 추가했으나, Spring Boot(Tomcat)는 Connection 헤더가 비어 있으면 보안상 비정상 요청으로 판단해 입구를 차단함.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-path-to-node=&quot;9&quot; data-ke-size=&quot;size20&quot;&gt;  구조적 원인: Git과 Docker 빌드 컨텍스트의 괴리 (핵심)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;10&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,0,0&quot;&gt;상황&lt;/b&gt;: 설정 파일(default.prod.conf)을 .gitignore에 등록하여 관리함.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,1,0&quot;&gt;문제&lt;/b&gt;: Docker 빌드 시 COPY 명령어는 Git의 추적 여부와 상관없이 &lt;b data-index-in-node=&quot;43&quot; data-path-to-node=&quot;10,1,0&quot;&gt;로컬 파일 시스템에 있는 파일&lt;/b&gt;을 이미지 속으로 복사(박제)함.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,2,0&quot;&gt;사건의 전말&lt;/b&gt;:
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;10,2,1&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;서버에서 수동으로 고친 건 실행 중인 컨테이너에만 적용된 &lt;b data-index-in-node=&quot;32&quot; data-path-to-node=&quot;10,2,1,0,0&quot;&gt;휘발성 조치&lt;/b&gt;였음.&lt;/li&gt;
&lt;li&gt;로컬 PC의 원본 파일은 여전히 고장 난 상태였고, 이 상태로 이미지가 빌드됨.&lt;/li&gt;
&lt;li&gt;배포 시 &lt;b data-index-in-node=&quot;5&quot; data-path-to-node=&quot;10,2,1,2,0&quot;&gt;고장 난 설정을 품은 새 이미지&lt;/b&gt;가 서버로 내려오며 수동 수정본을 덮어씀.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-path-to-node=&quot;11&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-path-to-node=&quot;11,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,0&quot;&gt;&quot;Git은 파일을 무시했지만, 도커 빌드는 그 파일을 주워 담아 배포했다.&quot;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-path-to-node=&quot;12&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-path-to-node=&quot;13&quot; data-ke-size=&quot;size23&quot;&gt;3. 해결 과정 (Resolution)&lt;/h3&gt;
&lt;h4 data-path-to-node=&quot;14&quot; data-ke-size=&quot;size20&quot;&gt;Step 1: 컨테이너 내부 실시간 검증&lt;/h4&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size16&quot;&gt;docker exec 명령어를 통해 실행 중인 컨테이너의 실제 설정이 로컬 원본과 다르다는 것을 확인했습니다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwimx6mU84WUAxUAAAAAHQAAAAAQzQU&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;&lt;span&gt;Bash&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;gradle&quot;&gt;&lt;code&gt;docker exec moim-nginx cat /etc/nginx/conf.d/default.prod.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-path-to-node=&quot;17&quot; data-ke-size=&quot;size20&quot;&gt;Step 2: 원본 설정 파일 수정&lt;/h4&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size16&quot;&gt;로컬 PC에 있는 default.prod.conf를 올바른 규격으로 수정했습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;19&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Connection &quot;&quot; &amp;rarr; &quot;keep-alive&quot;로 변경 (400 에러 해결)&lt;/li&gt;
&lt;li&gt;proxy_pass 주소를 실제 서비스 명과 일치시킴 (502 에러 해결)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-path-to-node=&quot;20&quot; data-ke-size=&quot;size20&quot;&gt;Step 3: 불변 인프라(Immutable Infrastructure) 원칙 적용&lt;/h4&gt;
&lt;p data-path-to-node=&quot;21&quot; data-ke-size=&quot;size16&quot;&gt;서버에서 수동으로 고치는 습관을 버리고, **&quot;로컬 수정 &amp;rarr; 빌드 &amp;rarr; 푸시&quot;**라는 정석적인 배포 파이프라인을 통해서만 설정을 변경하도록 프로세스를 고정했습니다.&lt;/p&gt;
&lt;hr data-path-to-node=&quot;22&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-path-to-node=&quot;23&quot; data-ke-size=&quot;size23&quot;&gt;4. 교훈 및 배운 점&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;24&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;24,0,0&quot;&gt;도커 이미지는 박제물이다&lt;/b&gt;: 컨테이너 내부에서 수동으로 고치는 것은 일시적일 뿐이다. 모든 변경 사항은 반드시 &lt;b data-index-in-node=&quot;61&quot; data-path-to-node=&quot;24,0,0&quot;&gt;이미지 빌드 단계&lt;/b&gt;에 반영되어야 한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;24,1,0&quot;&gt;Build Context의 이해&lt;/b&gt;: .gitignore는 Git의 영역일 뿐, Dockerfile의 COPY나 ADD는 로컬 파일 시스템을 기준으로 작동한다는 점을 명확히 알게 되었다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;24,2,0&quot;&gt;인프라의 동기화&lt;/b&gt;: 로컬과 서버의 환경 차이를 줄이기 위해 설정 파일을 어떻게 관리할지(볼륨 마운트 vs 이미지 포함)에 대한 고민이 필요함을 느꼈다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-path-to-node=&quot;25&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-path-to-node=&quot;26&quot; data-ke-size=&quot;size23&quot;&gt;5. 최종 수정 코드 (Nginx)&lt;/h3&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwimx6mU84WUAxUAAAAAHQAAAAAQzgU&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;&lt;span&gt;Nginx&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;location /api {
    proxy_pass http://moim-backend:8080; # 서비스 이름 일치
    proxy_http_version 1.1;
    
    # 400 에러 방지 및 SSE 지원을 위한 헤더 설정
    proxy_set_header Connection &quot;keep-alive&quot;; 
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;

    # SSE 설정
    proxy_buffering off;
    proxy_read_timeout 3600s;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-path-to-node=&quot;28&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-path-to-node=&quot;29&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;29&quot;&gt;#Docker #Nginx #GitHubActions #Troubleshooting #Backend #DevOps&lt;/b&gt;&lt;/p&gt;</description>
      <category>Docker</category>
      <category>backend</category>
      <category>DevOps</category>
      <category>docker</category>
      <category>GithubActions</category>
      <category>nignx</category>
      <category>Troubleshooting</category>
      <author>CHun2</author>
      <guid isPermaLink="true">https://chaehun97.tistory.com/82</guid>
      <comments>https://chaehun97.tistory.com/entry/Troubleshooting-Nginx-%EC%84%A4%EC%A0%95%EC%9D%B4-%EB%B0%B0%ED%8F%AC%EB%A7%8C-%ED%95%98%EB%A9%B4-%EC%B4%88%EA%B8%B0%ED%99%94%EB%90%98%EB%8A%94-%ED%98%84%EC%83%81-Docker-GitHub-Actions#entry82comment</comments>
      <pubDate>Sat, 25 Apr 2026 00:21:22 +0900</pubDate>
    </item>
    <item>
      <title>[Troubleshooting] 도커 배포 중 발생한 Redis 연결 오류 해결 (쿠키 문제인 줄 알았으나 인프라 문제였던 건에 대하여)</title>
      <link>https://chaehun97.tistory.com/entry/Troubleshooting-%EB%8F%84%EC%BB%A4-%EB%B0%B0%ED%8F%AC-%EC%A4%91-%EB%B0%9C%EC%83%9D%ED%95%9C-Redis-%EC%97%B0%EA%B2%B0-%EC%98%A4%EB%A5%98-%ED%95%B4%EA%B2%B0-%EC%BF%A0%ED%82%A4-%EB%AC%B8%EC%A0%9C%EC%9D%B8-%EC%A4%84-%EC%95%8C%EC%95%98%EC%9C%BC%EB%82%98-%EC%9D%B8%ED%94%84%EB%9D%BC-%EB%AC%B8%EC%A0%9C%EC%98%80%EB%8D%98-%EA%B1%B4%EC%97%90-%EB%8C%80%ED%95%98%EC%97%AC</link>
      <description>&lt;p data-path-to-node=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;최근 프로젝트를 EC2 운영 환경에 배포하던 중 로컬에서는 정상적이던 로그인이 배포 서버에서만 실패하는 현상을 겪었다. 결론부터 말하면 &lt;b data-index-in-node=&quot;77&quot; data-path-to-node=&quot;3&quot;&gt;사소한 포트 번호 오타와 도커 컴포즈의 환경변수 주입 시점&lt;/b&gt; 문제였으며 그 해결 과정을 기록으로 남긴다.&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;4&quot; data-ke-size=&quot;size26&quot;&gt;1. 문제 발생: &quot;로컬은 되는데 배포 환경만 안 된다?&quot;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;본인은 로컬 개발 시 빠른 피드백과 디버깅을 위해 &lt;b data-index-in-node=&quot;28&quot; data-path-to-node=&quot;5&quot;&gt;Spring Boot와 React는 로컬 호스트(Localhost)에서 직접 실행&lt;/b&gt;하고 &lt;b data-index-in-node=&quot;77&quot; data-path-to-node=&quot;5&quot;&gt;Redis와 DB만 도커 컨테이너&lt;/b&gt;로 띄워 사용한다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;로컬에서는 Spring이 도커에 뜬 Redis 포트(6379:6379 매핑)를 통해 localhost:6379로 바로 접속하므로 아무런 문제가 없었다. 하지만 모든 서비스를 도커 컨테이너화하여 올린 배포 환경에서는 상황이 달랐다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;로그인을 시도했으나 프론트엔드에서 토큰을 받지 못하거나 401 에러가 발생하였다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;8&quot; data-ke-size=&quot;size23&quot;&gt;왜 처음에는 '쿠키' 문제라고 판단하였나?&lt;/h3&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;보통 로컬에서 잘 되던 로그인이 배포 환경(Domain)에서만 안 될 경우, 십중팔구 &lt;b data-index-in-node=&quot;48&quot; data-path-to-node=&quot;9&quot;&gt;CORS나 쿠키 정책&lt;/b&gt; 문제이기 때문이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;10&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,0,0&quot;&gt;도메인 불일치&lt;/b&gt;: 백엔드와 프론트엔드의 도메인이 달라 쿠키 전송이 거부되는 케이스&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,1,0&quot;&gt;Samesite 이슈&lt;/b&gt;: 크롬 브라우저 정책상 HTTPS가 아니거나 Samesite 설정이 없으면 쿠키 전송이 차단됨&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,2,0&quot;&gt;Nginx 프록시&lt;/b&gt;: 앞단에 Nginx를 두면 헤더 정보가 유실되어 세션/쿠키가 꼬이는 경우가 많음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;11&quot; data-ke-size=&quot;size16&quot;&gt;따라서 당연히 쿠키 문제라 확신하고 아래 설정들을 우선적으로 수정하며 시간을 보냈다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;12&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SameSite(&quot;None&quot;) 설정 추가 (크로스 사이트 간 쿠키 전송 허용)&lt;/li&gt;
&lt;li&gt;forward-headers-strategy: native 설정 추가 (Nginx 프록시 헤더 인식)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;하지만 설정을 수정해도 로그인은 여전히 실패하였고 결국 백엔드 코드에 상세 로그를 심어 범위를 좁혔다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;15&quot; data-ke-size=&quot;size26&quot;&gt;2. 원인 파악: 추측하지 말고 명령어로 증명하기&lt;/h2&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;정확한 에러 지점을 찾기 위해 Redis 저장 로직에 로그를 추가하였다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiorfTjlZCSAxUAAAAAHQAAAAAQvQQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;&lt;span&gt;Java&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;try {
    redisTemplate.opsForValue().set(
            REDIS_RT_PREFIX + email,
            tokenDto.getRefreshToken(),
            tokenDto.getRefreshTokenExpirationTime(),
            TimeUnit.MILLISECONDS
    );
    log.info(&quot;6. Redis 저장 완료&quot;);
} catch (Exception e) {
    log.error(&quot;⚠️ Redis 저장 중 에러 발생: {}&quot;, e.getMessage());
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size16&quot;&gt;로그 확인 결과, 범인은 쿠키가 아니라 **Unable to connect to Redis**였다. 데이터가 Redis에 담기지 않으니 로그인 프로세스가 중간에 터져버린 것이다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;19&quot; data-ke-size=&quot;size16&quot;&gt;이제 인프라 설정을 점검할 차례였다. 이때 활용한 **'실제 검증 명령어'**들은 다음과 같다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;20&quot; data-ke-size=&quot;size23&quot;&gt;Redis 서버 비밀번호 설정 상태 확인&lt;/h3&gt;
&lt;p data-path-to-node=&quot;21&quot; data-ke-size=&quot;size16&quot;&gt;먼저 Redis 서버에 비밀번호가 정상적으로 적용되었는지 확인하였다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiorfTjlZCSAxUAAAAAHQAAAAAQvgQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;&lt;span&gt;Bash&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# 1. Redis 컨테이너 CLI 접속
ubuntu@ip-172-31-20-192:~/$ docker exec -it redis-cli

# 2. 비밀번호 설정 확인 명령어 입력
127.0.0.1:6379&amp;gt; CONFIG GET requirepass
1) &quot;requirepass&quot;
2) &quot;&quot;  # ⚠️ 설정값이 빈 값으로 출력됨을 확인&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;23&quot; data-ke-size=&quot;size16&quot;&gt;여기서 docker-compose.yml의 command 섹션에 작성한 ${REDIS_PASSWORD}가 .env 파일을 읽기 전에 실행되어 설정이 누락되었음을 파악하였다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;24&quot; data-ke-size=&quot;size23&quot;&gt;백엔드 내부 환경 변수 전수 조사 (grep의 활용)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;25&quot; data-ke-size=&quot;size16&quot;&gt;백엔드 컨테이너가 실제로 들고 있는 Redis 접속 정보를 확인하였다. &lt;b data-index-in-node=&quot;40&quot; data-path-to-node=&quot;25&quot;&gt;사실상 여기서 결정적인 원인을 발견하였다.&lt;/b&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiorfTjlZCSAxUAAAAAHQAAAAAQvwQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;&lt;span&gt;Bash&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# 백엔드 컨테이너 내부의 REDIS 관련 환경 변수 추출
ubuntu@ip-172-31-20-192:~/$ docker exec -it backend env | grep REDIS

# 출력 결과
REDIS_PASSWORD=******
REDIS_PORT=6397  # ⚠️ 범인 검거: 6379가 아닌 6397로 오타가 나 있었음
REDIS_HOST=redis&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;27&quot; data-ke-size=&quot;size16&quot;&gt;로컬에서는 환경 차이 때문에 느끼지 못했던 .env.prod 설정의 오타와 호스트 설정 문제가 도커 네트워크 환경에서 수면 위로 드러난 것이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;29&quot; data-ke-size=&quot;size26&quot;&gt;3. 해결: 주입 방식 보정과 오타 수정&lt;/h2&gt;
&lt;h4 data-path-to-node=&quot;30&quot; data-ke-size=&quot;size20&quot;&gt;Step 1. 실행 명령어 보정&lt;/h4&gt;
&lt;p data-path-to-node=&quot;31&quot; data-ke-size=&quot;size16&quot;&gt;도커 컴포즈가 환경변수 파일을 명시적으로 읽도록 실행 명령어를 수정하였다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiorfTjlZCSAxUAAAAAHQAAAAAQwAQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;&lt;span&gt;Bash&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;docker compose \
  --env-file .env.prod \
  -f docker-compose.prod.yml \
  up -d
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-path-to-node=&quot;33&quot; data-ke-size=&quot;size20&quot;&gt;Step 2. 환경변수 및 호스트 수정&lt;/h4&gt;
&lt;p data-path-to-node=&quot;34&quot; data-ke-size=&quot;size16&quot;&gt;.env.prod 파일의 포트 오타(6397 -&amp;gt; 6379)를 수정하고 REDIS_HOST를 도커 내부 네트워크에서 인식 가능한 &lt;b data-index-in-node=&quot;73&quot; data-path-to-node=&quot;34&quot;&gt;컨테이너 이름&lt;/b&gt;으로 맞추어 연결을 성공시켰다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;36&quot; data-ke-size=&quot;size26&quot;&gt;4. 마치며 (교훈)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;37&quot; data-ke-size=&quot;size16&quot;&gt;이번 트러블슈팅을 통해 얻은 교훈은 다음과 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;38&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;38,0,0&quot;&gt;환경 차이를 인지할 것&lt;/b&gt;: 로컬에서 앱을 직접 띄우는 것과 도커 컨테이너로 띄우는 것은 네트워크 해석 방식이 완전히 다르다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;38,1,0&quot;&gt;현상에 속지 말 것&lt;/b&gt;: 쿠키 문제처럼 보여도 실제로는 내부 저장소(Redis) 연결 실패가 원인일 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;38,2,0&quot;&gt;로그는 거짓말하지 않는다&lt;/b&gt;: 상세한 예외 처리 로그가 없었다면 며칠 더 쿠키 설정만 붙잡고 있었을 것이다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;38,3,0&quot;&gt;환경 변수는 반드시 교차 검증할 것&lt;/b&gt;: yml 설정만 믿지 말고 docker exec와 grep을 활용해 실제 주입된 값을 확인해야 한다.&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>Docker</category>
      <category>docker</category>
      <category>dockercompose</category>
      <category>Redis</category>
      <category>Redis연결오류</category>
      <category>springboot</category>
      <category>Troubleshooting</category>
      <category>UnableToConnectToRedis</category>
      <category>도커환경변수</category>
      <category>백엔드</category>
      <category>트러블슈팅</category>
      <author>CHun2</author>
      <guid isPermaLink="true">https://chaehun97.tistory.com/81</guid>
      <comments>https://chaehun97.tistory.com/entry/Troubleshooting-%EB%8F%84%EC%BB%A4-%EB%B0%B0%ED%8F%AC-%EC%A4%91-%EB%B0%9C%EC%83%9D%ED%95%9C-Redis-%EC%97%B0%EA%B2%B0-%EC%98%A4%EB%A5%98-%ED%95%B4%EA%B2%B0-%EC%BF%A0%ED%82%A4-%EB%AC%B8%EC%A0%9C%EC%9D%B8-%EC%A4%84-%EC%95%8C%EC%95%98%EC%9C%BC%EB%82%98-%EC%9D%B8%ED%94%84%EB%9D%BC-%EB%AC%B8%EC%A0%9C%EC%98%80%EB%8D%98-%EA%B1%B4%EC%97%90-%EB%8C%80%ED%95%98%EC%97%AC#entry81comment</comments>
      <pubDate>Fri, 16 Jan 2026 23:40:41 +0900</pubDate>
    </item>
    <item>
      <title>Docker에서만 API가 401로 실패한다면 꼭 확인해야 할 것</title>
      <link>https://chaehun97.tistory.com/entry/Docker%EC%97%90%EC%84%9C%EB%A7%8C-API%EA%B0%80-401%EB%A1%9C-%EC%8B%A4%ED%8C%A8%ED%95%9C%EB%8B%A4%EB%A9%B4-%EA%BC%AD-%ED%99%95%EC%9D%B8%ED%95%B4%EC%95%BC-%ED%95%A0-%EA%B2%83</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;React, Spring Boot, MySQL을 Docker Compose로 묶고 Nginx를 리버스 프록시로 두는 구조를 구성했다.&lt;br&gt;로컬 환경에서는 정상적으로 동작하던 API가 Docker 환경에서 실행하자마자 프론트엔드 요청에 대해 &lt;b&gt;계속 401 에러&lt;/b&gt;를 반환했다.&lt;br&gt;백엔드 서버는 분명 실행 중이었고 Spring Security 설정도 permitAll()로 열어둔 상태였다.&lt;br&gt;그래서 처음에는 자연스럽게 이런 생각이 들었다.&lt;/p&gt;&lt;blockquote data-end=&quot;571&quot; data-start=&quot;523&quot; data-ke-style=&quot;style1&quot;&gt; 
 &lt;p data-end=&quot;571&quot; data-start=&quot;525&quot; data-ke-size=&quot;size16&quot;&gt;“401이면 권한 문제잖아?”&lt;br&gt;“그럼 Security 설정이 문제 아닌가?”&lt;/p&gt; 
&lt;/blockquote&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot;&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;401 에러를 보고 처음 들었던 생각&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;내가 알고 있는 401 에러는 명확했다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;인증이 안 되었을 때&lt;/li&gt;&lt;li&gt;토큰이 없거나 잘못되었을 때&lt;/li&gt;&lt;li&gt;권한이 없는 요청일 때&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이 상황이 더 이상했다. JWT도 아직 적용하지 않음, 모든 요청을 permitAll()로 열어둠, CSRF도 비활성화해둔 상태였다&lt;br&gt;즉, 막히는 이유가 전혀 없는 요청이었다. 그래서 한동안 계속 이런 생각을 반복했다.&lt;/p&gt;&lt;blockquote data-end=&quot;931&quot; data-start=&quot;818&quot; data-ke-style=&quot;style1&quot;&gt; 
 &lt;p data-end=&quot;931&quot; data-start=&quot;820&quot; data-ke-size=&quot;size16&quot;&gt;“내가 Security 설정을 잘못 이해하고 있나?”&lt;br&gt;“Nginx에서 Authorization 헤더가 날아가고 있나?”&lt;br&gt;“혹시 Docker 환경에서는 기본 보안이 더 강하게 적용되나?”&lt;/p&gt; 
&lt;/blockquote&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;401이라는 숫자 하나 때문에&lt;br&gt;문제의 원인을 계속 &lt;b&gt;보안 쪽으로만&lt;/b&gt; 몰아가고 있었다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot;&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;그래도 먼저 Nginx 프록시와 Docker 네트워크부터 확인&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;일단 감정은 접어두고 가장 기본적인 요청 흐름부터 확인했다. 프론트엔드 → Nginx → 백엔드 이 흐름 자체가 깨졌다면 401이 아니라 다른 에러가 나왔을 것이다. Nginx 설정은 다음과 같았다.&lt;/p&gt;&lt;div&gt; 
 &lt;pre id=&quot;code_1765905743208&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;location /api/ {
	proxy_pass http://backend_server;
}&lt;/code&gt;&lt;/pre&gt; 
&lt;/div&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;설정상으로는 문제 없어 보였다. 그래서 Docker 네트워크가 정상인지 확인하기 위해 프론트엔드 컨테이너 내부에서 백엔드로 직접 요청을 보냈다.&lt;/p&gt;&lt;div&gt; 
 &lt;div&gt; 
  &lt;pre id=&quot;code_1765905771286&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker exec -it moimmoim-frontend sh
curl http://moimmoim-backend:8080/api/data -v&lt;/code&gt;&lt;/pre&gt; 
 &lt;/div&gt; 
&lt;/div&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;응답은 다음과 같았다.&lt;/p&gt;&lt;div&gt; 
 &lt;pre id=&quot;code_1765905790527&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;HTTP/1.1 401 WWW-Authenticate: Basic realm=&quot;Realm&quot;&lt;/code&gt;&lt;/pre&gt; 
&lt;/div&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;이 순간 오히려 헷갈렸다.&lt;/p&gt;&lt;blockquote data-end=&quot;1543&quot; data-start=&quot;1501&quot; data-ke-style=&quot;style1&quot;&gt; 
 &lt;p data-end=&quot;1543&quot; data-start=&quot;1503&quot; data-ke-size=&quot;size16&quot;&gt;“어? 그럼 요청은 도착한 거잖아?”&lt;br&gt;“근데 왜 또 401이지?”&lt;/p&gt; 
&lt;/blockquote&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;이 결과로 확실해진 점은 다음이었다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;Docker 컨테이너 간 네트워크는 정상&lt;/li&gt;&lt;li&gt;Nginx 프록시는 정상 동작&lt;/li&gt;&lt;li&gt;요청은 실제로 백엔드 서버까지 도착&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;401은 요청이 막힌 게 아니라 백엔드가 직접 내려주고 있는 응답&lt;/b&gt;이었다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot;&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;다시 Spring Security 설정을 의심하다&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;여기서부터는 완전히 Spring Security만 붙잡고 늘어지게 됐다. 설정을 다시 정리해보면 이랬다.&lt;br&gt;모든 요청을 permitAll(), CSRF 비활성화, formLogin 비활성화, httpBasic 비활성화, Stateless 세션 설정&lt;br&gt;이 설정이라면 이론적으로 401이 나올 수가 없다. 그래서 로그를 더 보고 싶어서 Security 로그 레벨을 DEBUG로 올렸다.&lt;/p&gt;&lt;div&gt; 
 &lt;div&gt; 
  &lt;pre id=&quot;code_1765905906197&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;logging:
    level:
    	org.springframework.security: DEBUG&lt;/code&gt;&lt;/pre&gt; 
 &lt;/div&gt; 
&lt;/div&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;그런데도 의미 있는 로그는 거의 찍히지 않았다. 여기서 또 한 번 이상함을 느꼈다.&lt;/p&gt;&lt;blockquote data-end=&quot;2115&quot; data-start=&quot;2077&quot; data-ke-style=&quot;style1&quot;&gt; 
 &lt;p data-end=&quot;2115&quot; data-start=&quot;2079&quot; data-ke-size=&quot;size16&quot;&gt;“보안에서 막고 있다면 로그라도 남아야 하는 거 아닌가?”&lt;/p&gt; 
&lt;/blockquote&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;이때부터 생각이 바뀌기 시작했다.&lt;/p&gt;&lt;blockquote data-end=&quot;2198&quot; data-start=&quot;2137&quot; data-ke-style=&quot;style1&quot;&gt; 
 &lt;p data-end=&quot;2198&quot; data-start=&quot;2139&quot; data-ke-size=&quot;size16&quot;&gt;“혹시 내가 수정한 Security 설정 자체가 실제로는 적용되지 않고 있는 건 아닐까?”&lt;/p&gt; 
&lt;/blockquote&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot;&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;실행 중인 JAR 파일을 확인하다&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이 의심을 확인하기 위해 SecurityConfig에 일부러 눈에 띄는 로그를 추가했다.&lt;/p&gt;&lt;div&gt; 
 &lt;div&gt; 
  &lt;div&gt;
   &amp;nbsp;
  &lt;/div&gt; 
 &lt;/div&gt; 
 &lt;div&gt; 
  &lt;pre id=&quot;code_1765905974434&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public SecurityConfig() {
	System.out.println(&quot;########## SecurityConfig Bean Loaded Successfully ##########&quot;); 
}&lt;/code&gt;&lt;/pre&gt; 
 &lt;/div&gt; 
&lt;/div&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;blockquote data-end=&quot;2471&quot; data-start=&quot;2427&quot; data-ke-style=&quot;style1&quot;&gt; 
 &lt;p data-end=&quot;2471&quot; data-start=&quot;2429&quot; data-ke-size=&quot;size16&quot;&gt;“이 로그만 찍히면 최소한 내 코드가 실행되고 있다는 건 확인된다.”&lt;/p&gt; 
&lt;/blockquote&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;하지만 컨테이너 실행 로그를 아무리 봐도 이 로그는 &lt;b&gt;끝내 등장하지 않았다. &lt;/b&gt;여기서 상황이 명확해졌다. 코드는 수정했는데실행 결과는 전혀 바뀌지 않는다. 라고 결론을 내렸다.&lt;br&gt;&amp;nbsp;&lt;br&gt;Dockerfile을 다시 열어보니&lt;/p&gt;&lt;div&gt; 
 &lt;pre id=&quot;code_1765906029602&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;COPY build/libs/backend-0.0.1-SNAPSHOT.jar /app/app.jar&lt;/code&gt;&lt;/pre&gt; 
 &lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt; 
&lt;/div&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;로컬에서 gradlew build를 다시 하지 않으면 과거에 빌드된 JAR 파일이 그대로 실행되고 있었다&lt;br&gt;정리하면 상태는 이랬다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;소스 코드는 최신&lt;/li&gt;&lt;li&gt;실행 중인 애플리케이션은 옛날 JAR&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;로컬에서는 잘 되는데Docker 환경에서만 계속 401이 났던 이유가 여기서 정확히 설명됐다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot;&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;Docker 멀티 스테이지 빌드로 구조 개선하기&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 근본적으로 해결하기 위해 Docker 이미지 빌드 과정에 &lt;b&gt;Gradle 빌드를 직접 포함&lt;/b&gt;시켰다.&lt;/p&gt;&lt;div&gt; 
 &lt;div&gt; 
  &lt;div&gt;
   &amp;nbsp;
  &lt;/div&gt; 
 &lt;/div&gt; 
 &lt;div&gt; 
  &lt;pre id=&quot;code_1765906113190&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FROM gradle:8.5-jdk17 AS builder 

WORKDIR /app

COPY . . 

RUN gradle clean build -x test 
FROM eclipse-temurin:17-jdk-alpine 
COPY --from=builder /app/build/libs/*.jar app.jar&lt;/code&gt;&lt;/pre&gt; 
 &lt;/div&gt; 
&lt;/div&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조로 변경한 뒤에는 docker-compose up --build만 실행해도 항상 최신 코드가 빌드되어 실행되도록 바뀌었다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot;&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;실행 로그로 최종 확인&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;다시 컨테이너를 실행했고 이번에는 로그가 달라져 있었다.&lt;/p&gt;&lt;div&gt; 
 &lt;div&gt; 
  &lt;pre id=&quot;code_1765906140384&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;########## SecurityConfig Bean Loaded Successfully ########## ########## 
SecurityFilterChain Configuration Applied ########## Tomcat started on port 8080 (http) Started BackendApplication in 4.794 seconds&lt;/code&gt;&lt;/pre&gt; 
 &lt;/div&gt; 
&lt;/div&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이 로그를 보고서야 확신이 들었다. 이제 진짜 최신 코드가 실행 중이고 SecurityConfig도 정상적으로 로딩되었고 필터 체인도 실제로 적용되고 있다 마지막으로 프론트엔드에서 API 요청을 보냈다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;div&gt; 
 &lt;div&gt; 
  &lt;pre id=&quot;code_1765906163607&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;GET /api/data HTTP/1.1&quot; 200&lt;/code&gt;&lt;/pre&gt; 
 &lt;/div&gt; 
&lt;/div&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;드디어 정상 응답을 확인할 수 있었다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot;&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리하며 얻은 교훈&lt;/h2&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;401 에러가 발생했다고 해서 무조건 “권한 문제”라고 단정하면 안 된다.&lt;/li&gt;&lt;li&gt;Docker 환경에서는 &lt;b&gt;항상 실행 중인 JAR 파일이 무엇인지부터 의심해야 한다.&lt;/b&gt;&lt;/li&gt;&lt;li&gt;로컬과 Docker 환경의 차이는 대부분 “빌드 과정”에서 발생한다.&lt;/li&gt;&lt;li&gt;로그는 문제를 해결하는 도구이기 이전에 &lt;b&gt;문제의 범위를 줄여주는 지도&lt;/b&gt;다&lt;/li&gt;&lt;/ul&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot;&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이번 문제는 Spring Security 설정 문제가 아니라 Docker 빌드 구조 때문에 &lt;b&gt;과거 JAR 파일이 실행되고 있던 문제&lt;/b&gt;였다.&lt;br&gt;401이라는 결과만 보고 한동안 보안 설정만 파고들었지만 결국 문제는 전혀 다른 곳에 있었다. Docker 환경에서 “설정은 분명 맞는데 동작이 이상하다”는 느낌이 들면 가장 먼저 &lt;b&gt;지금 실행 중인 코드가 정말 최신인지&lt;/b&gt;부터 확인해봐야 한다는 걸 다시 한 번 느꼈다.&lt;/p&gt;</description>
      <category>Docker</category>
      <author>CHun2</author>
      <guid isPermaLink="true">https://chaehun97.tistory.com/80</guid>
      <comments>https://chaehun97.tistory.com/entry/Docker%EC%97%90%EC%84%9C%EB%A7%8C-API%EA%B0%80-401%EB%A1%9C-%EC%8B%A4%ED%8C%A8%ED%95%9C%EB%8B%A4%EB%A9%B4-%EA%BC%AD-%ED%99%95%EC%9D%B8%ED%95%B4%EC%95%BC-%ED%95%A0-%EA%B2%83#entry80comment</comments>
      <pubDate>Wed, 17 Dec 2025 15:17:31 +0900</pubDate>
    </item>
    <item>
      <title>Nginx 기본 개념</title>
      <link>https://chaehun97.tistory.com/entry/Nginx-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;638&quot; data-origin-height=&quot;169&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwml2l/dJMcacPaFEz/9dNdrer7Zw2FRoNnEhgfg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwml2l/dJMcacPaFEz/9dNdrer7Zw2FRoNnEhgfg0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwml2l/dJMcacPaFEz/9dNdrer7Zw2FRoNnEhgfg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcwml2l%2FdJMcacPaFEz%2F9dNdrer7Zw2FRoNnEhgfg0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;521&quot; height=&quot;138&quot; data-origin-width=&quot;638&quot; data-origin-height=&quot;169&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Nginx의 주요 역할&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;웹서버 (Web Server): 사용자의 요청(Request)을 받아 정적인 파일(HTML, CSS, 이미지 등)을 전달(Serve)하는 기본 역할이다.&lt;/li&gt;
&lt;li&gt;리버스 프록시 (Reverse Proxy): 사용자의 요청을 받아서, 그요청을 대신 내부의 다른 서버(Node.js, Java 애플리케이션)로 전달하고 응답을 다시 사용자에게 보내주는 중개자 역할이다. 이 역할이 핵심 강점이다.&lt;/li&gt;
&lt;li&gt;로드 밸런서(Load Banancer): 요청이 폭주할 때, 여러 개의 서버에 요청을 균등하게 나누어 분산시켜주는 역할이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Nginx의 장점: 비동기 이벤트 기반 아키텍처&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nginx가 빠르고 강력한 이유는 비동기 이벤트 기반 아키텍처 덕분이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;동시성 처리: 대부분의 서버들은 요청마다 새로운 프로세스나 스레드를 생성하는 방식(Blocking I/O)을 사용하는 반면 Nginx는 비동기 이벤트 기반(Event-Driven) 방식을 사용한다.&lt;/li&gt;
&lt;li&gt;적은 자원 소모: 적은 수의 프로세스로 수많은 동시 접속을 효율적으로 처리할 수 있다. 이는 메모리 소모를 줄이고 처리 속도를 높이는 핵심 이유이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Nginx 설정 파일의 이해&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nginx의 모든 동작은 설정 파일(nginx.conf)을 통해 제어 된다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 26.6279%;&quot;&gt;구성 요소&lt;/td&gt;
&lt;td style=&quot;width: 73.3721%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 26.6279%;&quot;&gt;events 블록&lt;/td&gt;
&lt;td style=&quot;width: 73.3721%;&quot;&gt;서버의 이벤트 처리 방식, 특히 워커 프로세스당 최대 연결 수 등을 설정한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 26.6279%;&quot;&gt;http 블록&lt;/td&gt;
&lt;td style=&quot;width: 73.3721%;&quot;&gt;웹 서버의 일반적인 동작(MIME 타입, 로그 형식 등)을 정의하는 최상위 블록이다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 26.6279%;&quot;&gt;server 블록&lt;/td&gt;
&lt;td style=&quot;width: 73.3721%;&quot;&gt;특정 도메인이나 포트(가상 호스트)를 처리하는 단위이다. 하나의 http 블록 안에 여러 개를 둘 수 있다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 26.6279%;&quot;&gt;location 블록&lt;/td&gt;
&lt;td style=&quot;width: 73.3721%;&quot;&gt;server 블록 안에서 특정 url 패턴에 따라 요청을 어떻게 처리할지 상세하게 정의한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;리버스 프록시(Reverse Proxy)의 이해&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리버스 프록시는 쉽게 말해, Nginx가 사용자와 실제 애플리케이션 서버 사이에 서서 요청을 대신 받아주는 중개자 역할을 하는 것이다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-style=&quot;style12&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 26.6279%;&quot;&gt;역할&lt;/td&gt;
&lt;td style=&quot;width: 73.3721%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 26.6279%;&quot;&gt;클라이언트(사용자)&lt;/td&gt;
&lt;td style=&quot;width: 73.3721%;&quot;&gt;웹 브라우저를 통해 &lt;a href=&quot;http://www.service.com에&quot;&gt;www.service.com에 &lt;/a&gt;접속한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 26.6279%;&quot;&gt;Nginx(Reverse Proxy)&lt;/td&gt;
&lt;td style=&quot;width: 73.3721%;&quot;&gt;이 요청을 Nginx가 받는다. 그리고 내부 네트워크에 있는 실제 애플리케이션 서버로 요청을 전달한ㄷ.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 26.6279%;&quot;&gt;애플리케이션 서버&lt;/td&gt;
&lt;td style=&quot;width: 73.3721%;&quot;&gt;Nginx로부터 요청을 받아 처리한 후, 응답을 다시 Nginx에게 전달한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 26.6279%;&quot;&gt;최종 응답&lt;/td&gt;
&lt;td style=&quot;width: 73.3721%;&quot;&gt;Nginx는 이 응답을 받아 클라이언트에게 최종적으로 전달한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3000&quot; data-origin-height=&quot;2000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkTdLe/dJMcafSFFm7/3hU8JfaxK4pi9Xp2CSpwE1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkTdLe/dJMcafSFFm7/3hU8JfaxK4pi9Xp2CSpwE1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkTdLe/dJMcafSFFm7/3hU8JfaxK4pi9Xp2CSpwE1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkTdLe%2FdJMcafSFFm7%2F3hU8JfaxK4pi9Xp2CSpwE1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3000&quot; height=&quot;2000&quot; data-origin-width=&quot;3000&quot; data-origin-height=&quot;2000&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;왜 리버스 프록시를 사용해야 하는가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션 서버가 직접 사용자 요청을 받지 않고 Nginx가 대신 받는 이유는 핵심적인 이점들 떄문이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;보안(Security)&lt;br /&gt;실제 서버 IP 숨기기: 클라이언트가 보는 것은 Nginx의 IP 주소뿐이다. 실제 애플리케이션 서버의 IP와 포트가 외부에 노출되지 않아 해킹 시도로부터 보호된다.&lt;/li&gt;
&lt;li&gt;SSL/TLS 처리(Termination): 암호화/복호화에 자원을 많이 소모하는 SSL 인증서 처리를 Nginx가 대신한다. 내부의 애플리케이션 서버는 보안 부담 없이 HTTP로 통신할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;로드 밸런싱(Load Balancing)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nginx는 여러 대의 애플리케이션 서버 중 트래픽 부하가 적은 서버를 선택하여 요청을 분산시킬 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;캐싱 및 압축(Caching &amp;amp; Compression)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자주 요청되는 응답을 Nginx가 저장(캐싱)해 두었다가 사용자에게 바로 전달할 수 있다. 실제 서버에 부하를 주지 않고도 응답 속도를 극적으로 높일 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Nginx 설정 예시(Reverse Proxy)&lt;/h2&gt;
&lt;pre id=&quot;code_1765538718138&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;server {
	listen 80; #Nginx가 외부에서 80포트로 요청을 받는다.
    server_name example.com;
    
    location / {
    	# 요청을 내부의 3000번 포트에서 실행 중인 서버로 전달한다.
        proxy_pass http://localhost:3000;
        
        #프록시 시 중요한 헤더를 추가하여 실제 서버가 클라이언트 정보를 알도록 한다.
        proxy_set_header Host $host;
        proxy_set_header Xreal-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nginx 이야기를 하다 보면 자연스럽게 &lt;b&gt;로드 밸런싱과 캐싱&lt;/b&gt;으로 흐르게 되지만 이 두 가지는 설정 방법도 다양하고 실제 운영과 직결되는 중요한 요소라서 여기서 간단히 다루기엔 아쉬움이 있다. 트래픽 분산 방식, 스티키 세션, 프록시 캐시 전략, TTL 설정처럼 깊이 있는 내용들이 많기 때문에 이번 글에서는 기본적인 Nginx 설정에만 집중하고 &lt;b&gt;로드 밸런싱과 캐싱은 다음 글에서 따로 정리해 자세히 다뤄보도록 하겠다.&lt;/b&gt;&lt;/p&gt;</description>
      <category>Nginx</category>
      <category>DevOps</category>
      <category>infra</category>
      <category>nginx</category>
      <category>ReverseProxy</category>
      <category>배포환경</category>
      <category>백엔드개발</category>
      <category>서버구조</category>
      <category>서버운영</category>
      <category>웹서버</category>
      <category>웹아키텍처</category>
      <author>CHun2</author>
      <guid isPermaLink="true">https://chaehun97.tistory.com/79</guid>
      <comments>https://chaehun97.tistory.com/entry/Nginx-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90#entry79comment</comments>
      <pubDate>Fri, 12 Dec 2025 20:38:47 +0900</pubDate>
    </item>
    <item>
      <title>도커(Docker)란?</title>
      <link>https://chaehun97.tistory.com/entry/%EB%8F%84%EC%BB%A4Docker%EB%9E%80</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cPn96d/dJMcagRyxOx/v20oFE9T15L1NtdaDD8yBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cPn96d/dJMcagRyxOx/v20oFE9T15L1NtdaDD8yBK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cPn96d/dJMcagRyxOx/v20oFE9T15L1NtdaDD8yBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcPn96d%2FdJMcagRyxOx%2Fv20oFE9T15L1NtdaDD8yBK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1536&quot; height=&quot;1024&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;141&quot; data-start=&quot;132&quot; data-ke-size=&quot;size26&quot;&gt;도커란?&lt;/h2&gt;
&lt;p data-end=&quot;247&quot; data-start=&quot;143&quot; data-ke-size=&quot;size16&quot;&gt;도커(Docker)는 애플리케이션을 실행하는 데 필요한 환경을 **컨테이너(Container)**라는 형태로 묶어 언제 어디서나 동일한 환경에서 실행할 수 있게 만들어주는 플랫폼이다.&lt;/p&gt;
&lt;p data-end=&quot;297&quot; data-start=&quot;249&quot; data-ke-size=&quot;size16&quot;&gt;즉 &amp;ldquo;내 컴퓨터에서는 되는데?&amp;rdquo; 같은 문제를 해결해주는 강력한 개발&amp;middot;배포 도구다. 도커는 운영체제를 통째로 가상화하지 않고 필요한 실행 환경만 가볍게 격리해서 실행한다는 점에서 기존의 VM과 큰 차이가 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;413&quot; data-start=&quot;383&quot; data-ke-size=&quot;size26&quot;&gt;Virtual Machine vs Docker&lt;/h2&gt;
&lt;p data-end=&quot;481&quot; data-start=&quot;415&quot; data-ke-size=&quot;size16&quot;&gt;도커를 이해하려면 먼저 기존 가상화 방식인 **Virtual Machine(버추얼머신)**과 비교하는 것이 가장 쉽다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-end=&quot;516&quot; data-start=&quot;488&quot; data-ke-size=&quot;size23&quot;&gt;Virtual Machine(버추얼머신)&lt;/h3&gt;
&lt;p data-end=&quot;574&quot; data-start=&quot;518&quot; data-ke-size=&quot;size16&quot;&gt;버추얼머신은 하드웨어 위에 또 하나의 운영체제(OS)를 통째로 올리는 방식의 가상화 기술이다.&lt;/p&gt;
&lt;h4 data-end=&quot;584&quot; data-start=&quot;576&quot; data-ke-size=&quot;size20&quot;&gt;구조&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1765360365984&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[Hardware]
   └─ [Host OS]
         └─ [Hypervisor]
               └─ [Guest OS]
                     └─ App + Libraries
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-end=&quot;723&quot; data-start=&quot;715&quot; data-ke-size=&quot;size20&quot;&gt;특징&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;841&quot; data-start=&quot;724&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;752&quot; data-start=&quot;724&quot;&gt;OS를 통째로 띄우므로 격리 수준이 높음&lt;/li&gt;
&lt;li data-end=&quot;796&quot; data-start=&quot;753&quot;&gt;Windows에서 Linux 실행 같은 서로 다른 OS 테스트 가능&lt;/li&gt;
&lt;li data-end=&quot;841&quot; data-start=&quot;797&quot;&gt;하지만 무겁고 부팅도 느림&lt;br /&gt;&amp;rarr; OS 자체를 하나 더 돌려야 하기 때문&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-end=&quot;864&quot; data-start=&quot;848&quot; data-ke-size=&quot;size23&quot;&gt;Docker(도커)&lt;/h3&gt;
&lt;p data-end=&quot;933&quot; data-start=&quot;866&quot; data-ke-size=&quot;size16&quot;&gt;도커는 운영체제를 공유하면서 필요한 실행 환경만 분리하고 그 환경을 &quot;컨테이너&quot;라는 가벼운 단위로 관리한다.&lt;/p&gt;
&lt;h4 data-end=&quot;943&quot; data-start=&quot;935&quot; data-ke-size=&quot;size20&quot;&gt;구조&lt;/h4&gt;
&lt;pre id=&quot;code_1765360433096&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[Hardware]
   └─ [Host OS]
         └─ [Docker Engine]
               └─ [Container (App + Lib만 포함)]&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-end=&quot;1062&quot; data-start=&quot;1054&quot; data-ke-size=&quot;size20&quot;&gt;특징&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1151&quot; data-start=&quot;1063&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1087&quot; data-start=&quot;1063&quot;&gt;게스트 OS 없음 &amp;rarr; 매우 가벼움&lt;/li&gt;
&lt;li data-end=&quot;1104&quot; data-start=&quot;1088&quot;&gt;실행 속도 1~3초&lt;/li&gt;
&lt;li data-end=&quot;1130&quot; data-start=&quot;1105&quot;&gt;이미지 기반 &amp;rarr; 어디서든 동일한 환경 실행&lt;/li&gt;
&lt;li data-end=&quot;1151&quot; data-start=&quot;1131&quot;&gt;배포 및 확장(스케일링)에 최적화&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;1170&quot; data-start=&quot;1158&quot; data-ke-size=&quot;size26&quot;&gt;도커 구성요소&lt;/h2&gt;
&lt;p data-end=&quot;1197&quot; data-start=&quot;1172&quot; data-ke-size=&quot;size16&quot;&gt;도커는 크게 3가지 구성요소로 이루어져 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-end=&quot;1227&quot; data-start=&quot;1204&quot; data-ke-size=&quot;size23&quot;&gt;1) Docker Client&lt;/h3&gt;
&lt;p data-end=&quot;1302&quot; data-start=&quot;1228&quot; data-ke-size=&quot;size16&quot;&gt;개발자가 docker run, docker build 등을 입력하는 CLI(Command Line Interface).명령을 Docker Daemon(서버)에게 전달함.&lt;/p&gt;
&lt;h3 data-end=&quot;1372&quot; data-start=&quot;1339&quot; data-ke-size=&quot;size23&quot;&gt;2) Docker Daemon (dockerd)&lt;/h3&gt;
&lt;p data-end=&quot;1428&quot; data-start=&quot;1373&quot; data-ke-size=&quot;size16&quot;&gt;도커의 핵심 엔진. 컨테이너 실행, 이미지 생성, 네트워크/볼륨 관리 등 모든 기능을 담당한다.&lt;/p&gt;
&lt;h3 data-end=&quot;1460&quot; data-start=&quot;1435&quot; data-ke-size=&quot;size23&quot;&gt;3) Docker Registry&lt;/h3&gt;
&lt;p data-end=&quot;1472&quot; data-start=&quot;1461&quot; data-ke-size=&quot;size16&quot;&gt;도커 이미지 저장소. 대표적인 것이 Docker Hub이며 기업에서는 프라이빗 레지스트리도 운영한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;1584&quot; data-start=&quot;1532&quot; data-ke-size=&quot;size26&quot;&gt;도커 이미지(Docker Image)와 도커 컨테이너(Docker Container)&lt;/h2&gt;
&lt;p data-end=&quot;1623&quot; data-start=&quot;1586&quot; data-ke-size=&quot;size16&quot;&gt;도커를 제대로 이해하려면 이미지와 컨테이너를 반드시 구분해야 한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-end=&quot;1657&quot; data-start=&quot;1630&quot; data-ke-size=&quot;size23&quot;&gt;도커 이미지(Docker Image)&lt;/h3&gt;
&lt;p data-end=&quot;1712&quot; data-start=&quot;1659&quot; data-ke-size=&quot;size16&quot;&gt;도커 이미지는 애플리케이션 실행에 필요한 모든 파일&amp;middot;설정&amp;middot;라이브러리를 묶어둔 템플릿이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1797&quot; data-start=&quot;1714&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1731&quot; data-start=&quot;1714&quot;&gt;불변(Immutable)&lt;/li&gt;
&lt;li data-end=&quot;1759&quot; data-start=&quot;1732&quot;&gt;실행 가능한 상태의 청사진(BluePrint)&lt;/li&gt;
&lt;li data-end=&quot;1797&quot; data-start=&quot;1760&quot;&gt;여러 계층(Layer)으로 구성 &amp;rarr; 재사용성과 관리 효율성 증가&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-end=&quot;1805&quot; data-start=&quot;1799&quot; data-ke-size=&quot;size20&quot;&gt;예시&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1870&quot; data-start=&quot;1806&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1822&quot; data-start=&quot;1806&quot;&gt;ubuntu:22.04&lt;/li&gt;
&lt;li data-end=&quot;1834&quot; data-start=&quot;1823&quot;&gt;mysql:8&lt;/li&gt;
&lt;li data-end=&quot;1851&quot; data-start=&quot;1835&quot;&gt;nginx:latest&lt;/li&gt;
&lt;li data-end=&quot;1870&quot; data-start=&quot;1852&quot;&gt;openjdk:17-jdk&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1906&quot; data-start=&quot;1872&quot; data-ke-size=&quot;size16&quot;&gt;이미지는 실행되지 않는다.&lt;br /&gt;실행하려면 컨테이너가 필요하다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-end=&quot;1944&quot; data-start=&quot;1913&quot; data-ke-size=&quot;size23&quot;&gt;도커 컨테이너(Docker Container)&lt;/h3&gt;
&lt;p data-end=&quot;1975&quot; data-start=&quot;1946&quot; data-ke-size=&quot;size16&quot;&gt;컨테이너는 이미지를 실제로 실행한 인스턴스다.&lt;/p&gt;
&lt;p data-end=&quot;1979&quot; data-start=&quot;1977&quot; data-ke-size=&quot;size16&quot;&gt;즉 이미지는 &amp;lsquo;설계도', 컨테이너는 &amp;lsquo;실제로 실행 중인 집&amp;rsquo;같은 관계라고 보면 된다.&lt;/p&gt;
&lt;h4 data-end=&quot;2039&quot; data-start=&quot;2033&quot; data-ke-size=&quot;size20&quot;&gt;특징&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2109&quot; data-start=&quot;2040&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2051&quot; data-start=&quot;2040&quot;&gt;독립된 실행 환경&lt;/li&gt;
&lt;li data-end=&quot;2087&quot; data-start=&quot;2052&quot;&gt;필요할 때 생성하고, 삭제하고, 수 초 만에 다시 실행 가능&lt;/li&gt;
&lt;li data-end=&quot;2109&quot; data-start=&quot;2088&quot;&gt;가볍고 빠름 (OS 전체 실행 X)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-end=&quot;2117&quot; data-start=&quot;2111&quot; data-ke-size=&quot;size20&quot;&gt;예시&lt;/h4&gt;
&lt;p data-end=&quot;2117&quot; data-start=&quot;2111&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-size: 16px; letter-spacing: 0px;&quot;&gt;docker run -d -p &lt;/span&gt;&lt;span style=&quot;color: #333333; font-size: 16px; letter-spacing: 0px;&quot;&gt;3306&lt;/span&gt;&lt;span style=&quot;color: #333333; font-size: 16px; letter-spacing: 0px;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #333333; font-size: 16px; letter-spacing: 0px;&quot;&gt;3306&lt;/span&gt;&lt;span style=&quot;color: #333333; font-size: 16px; letter-spacing: 0px;&quot;&gt; mysql:&lt;/span&gt;&lt;span style=&quot;color: #333333; font-size: 16px; letter-spacing: 0px;&quot;&gt;8 &lt;/span&gt;이 명령이 실행되면 mysql:8 이미지로부터 컨테이너가 하나 생성되고 실행됨.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-end=&quot;335&quot; data-start=&quot;132&quot; data-ke-size=&quot;size16&quot;&gt;도커를 이해하면 단순히 개발 환경을 통일하는 수준을 넘어 &lt;b&gt;프로젝트 전체의 개발&amp;ndash;테스트&amp;ndash;배포 과정이 한층 효율적이고 안정적으로 관리&lt;/b&gt;된다. 새로운 팀원이 합류해도 &amp;ldquo;환경 세팅에 하루 종일 걸리는&amp;rdquo; 상황이 사라지고 서버 배포 또한 이미지 하나만 교체하면 되기 때문에 &lt;b&gt;오류 없는 배포&lt;/b&gt;가 가능하다.&lt;/p&gt;
&lt;p data-end=&quot;493&quot; data-start=&quot;337&quot; data-ke-size=&quot;size16&quot;&gt;또한 도커는 마이크로서비스 아키텍처(MSA)처럼 여러 서비스를 독립적으로 운영하는 환경에 최적화되어 있어 &lt;b&gt;서비스 확장성, 장애 격리, 자동화된 배포 파이프라인(CI/CD) 구성&lt;/b&gt; 등 현대 백엔드 시스템에서 요구되는 다양한 기술적 요구사항을 손쉽게 충족시킬 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2439&quot; data-start=&quot;2383&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Docker</category>
      <category>docker</category>
      <category>MSA</category>
      <category>virtualMachine</category>
      <category>VM vs Docker</category>
      <category>가상화</category>
      <category>개발환경 구축</category>
      <category>도커</category>
      <category>배포 자동화</category>
      <category>버츄얼머신</category>
      <category>컨테이너</category>
      <author>CHun2</author>
      <guid isPermaLink="true">https://chaehun97.tistory.com/78</guid>
      <comments>https://chaehun97.tistory.com/entry/%EB%8F%84%EC%BB%A4Docker%EB%9E%80#entry78comment</comments>
      <pubDate>Wed, 10 Dec 2025 19:04:35 +0900</pubDate>
    </item>
    <item>
      <title>TIL : POST 요청에서도 Response에 데이터를 포함하는 이유</title>
      <link>https://chaehun97.tistory.com/entry/TIL-POST-%EC%9A%94%EC%B2%AD%EC%97%90%EC%84%9C%EB%8F%84-Response%EC%97%90-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%A5%BC-%ED%8F%AC%ED%95%A8%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0</link>
      <description>&lt;p data-end=&quot;343&quot; data-start=&quot;117&quot; data-ke-size=&quot;size16&quot;&gt;API 설계를 하면서 POST 요청에서 Response에 데이터를 포함하는 것에 대해 중요한 깨달음을 얻었다.&lt;br /&gt;처음에는 회원가입이나 글 작성 같은 POST 요청에서는 &quot;회원가입 완료&quot;, &quot;글 작성 완료&quot; 같은 단순 메시지만 반환하면 충분하다고 생각했다. GET 요청에서는 데이터를 포함해서 반환하는 것이 당연하지만 POST는 결과 자체만 알면 된다고 설계 초기에는 판단했기 때문이다.&lt;/p&gt;
&lt;p data-end=&quot;343&quot; data-start=&quot;117&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;426&quot; data-start=&quot;345&quot; data-ke-size=&quot;size16&quot;&gt;고민하면서 깨달은 점은 POST 요청에서도 데이터를 함께 반환하면 클라이언트 입장에서 &lt;b&gt;효율성이 크게 증가&lt;/b&gt;한다는 것이다.&lt;/p&gt;
&lt;p data-end=&quot;695&quot; data-start=&quot;428&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 회원가입을 생각해보자.&lt;/p&gt;
&lt;p data-end=&quot;695&quot; data-start=&quot;428&quot; data-ke-size=&quot;size16&quot;&gt;사용자가 회원가입 폼에 정보를 입력하고 완료한 뒤 로그인을 하는 플로우가 있을 때 POST Response에 새로 생성된 회원 정보를 포함하면 클라이언트는 &lt;b&gt;추가적인 GET 요청 없이 바로 화면에 회원 정보를 렌더링&lt;/b&gt;할 수 있다. 이미 Response에 포함된 데이터를 활용하면 서버에서 같은 데이터를 다시 가져올 필요가 없다.&lt;/p&gt;
&lt;p data-end=&quot;695&quot; data-start=&quot;428&quot; data-ke-size=&quot;size16&quot;&gt;실제로 요즘 많은 홈페이지에서 회원가입 후 &lt;b&gt;자동으로 로그인되는 기능&lt;/b&gt;이 바로 이런 설계에서 비롯된다.&lt;/p&gt;
&lt;p data-end=&quot;867&quot; data-start=&quot;697&quot; data-ke-size=&quot;size16&quot;&gt;다만 POST 후 바로 로그인을 구현하려면 &lt;b&gt;인증(Auth) 문제&lt;/b&gt;도 고려해야 한다. 회원가입만으로는 사용자가 인증된 상태가 아니기 때문에 POST Response에 JWT 토큰이나 세션 정보 등 인증 정보를 함께 반환해야 클라이언트가 별도의 로그인 요청 없이 바로 인증 상태를 유지할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;867&quot; data-start=&quot;697&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1019&quot; data-start=&quot;869&quot; data-ke-size=&quot;size16&quot;&gt;이번 설계 고민을 통해 깨달은 점은 POST 요청이라고 해서 단순히 성공/실패 메시지만 반환하는 것이 최선이 아니라는 것이다. &lt;b&gt;사용자 경험과 클라이언트 효율성을 고려하고 필요 시 인증 정보까지 포함하는 설계&lt;/b&gt;가 API 설계에서 훨씬 유리하다는 것을 배웠다.&lt;/p&gt;</description>
      <category>TIL</category>
      <category>Til</category>
      <author>CHun2</author>
      <guid isPermaLink="true">https://chaehun97.tistory.com/77</guid>
      <comments>https://chaehun97.tistory.com/entry/TIL-POST-%EC%9A%94%EC%B2%AD%EC%97%90%EC%84%9C%EB%8F%84-Response%EC%97%90-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%A5%BC-%ED%8F%AC%ED%95%A8%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0#entry77comment</comments>
      <pubDate>Tue, 18 Nov 2025 18:13:27 +0900</pubDate>
    </item>
    <item>
      <title>프로토콜은 왜 중요할까, 백엔드 개발자가 알아야 할 OSI 7계층별 프로토콜</title>
      <link>https://chaehun97.tistory.com/entry/%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C%EC%9D%80-%EC%99%9C-%EC%A4%91%EC%9A%94%ED%95%A0%EA%B9%8C-%EB%B0%B1%EC%97%94%EB%93%9C-%EA%B0%9C%EB%B0%9C%EC%9E%90%EA%B0%80-%EC%95%8C%EC%95%84%EC%95%BC-%ED%95%A0-OSI-7%EA%B3%84%EC%B8%B5%EB%B3%84-%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;프로토콜을 알아야하는 이유는 실제 서비스의 동작 원리를 정확히 이해하고 문제를 해결하며 보안과 성능을 개선하기 위해서도 있지만 근본적인 것은 역시 문제해결인 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터넷과 네트워크는 도로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로토콜은 그 위를 달리는 차와 신호체계&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드 개발자는 차를 운전하며 도로 설계까지 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로토콜을 모르면 눈 가리고 운전하는 격.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코딩을 할 때 내부적인 오류 Exception, 런타임 에러등의 문제도 있지만 만약 DNS를 아예 모른다고 가정하면 우리가 브라우저에 도메인을 입력해도 404, DNS_PROBE_FINISHED_NXDOMAIN같은 오류를 만나면 원인을 알수 없다. 서버에 배포했는데 도메인을 연결이 안되는 경우에 DNS의 A 나 CNAME 설정 개념이 없으면 문제 해결이 안된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 우리가 클라우드 서버를 사용했을 시 레코드 설정을 잘못하면 서비스 전체가 접속이 안된다. DNS 캐시, TTL의 의미, 전파 시간 등을 몰라서 원인 분석이 늦어질 것이다. 이 때 DNS 프로토콜 지식을 가진 사람은 금방해결할 수 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로토콜이 뭔가.. 바로 통신 규약이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;규약(規約, Protocol) 이란&lt;br /&gt;&lt;br /&gt;조직이나 단체, 또는 여러 사람이 모여 공동의 목표를 달성하거나 특정 사안을 효율적으로 처리하기 위해 &lt;b&gt;정해놓은 규칙이나 약속을 의미&lt;/b&gt;한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; 규약은 약속&lt;/b&gt;이다. 대부분의 라이브러리는 이 약속을 지키도록 만들어진다. 우리가 그 규약을 알고 있다면, 코드를 볼 때 &amp;lsquo;왜 이렇게 동작하는지&amp;rsquo;를 자연스럽게 이해할 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;백엔드 개발자를 위한 OSI 7계층별 필수 프로토콜&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 8.48841%;&quot;&gt;&lt;b&gt;7계층&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 16.6279%;&quot;&gt;응용 계층 (Application Layer)&lt;/td&gt;
&lt;td style=&quot;width: 32.6743%;&quot;&gt;HTTP, HTTPS, FTP, SMTP, DNS, WebSocket, SSH&lt;/td&gt;
&lt;td style=&quot;width: 41.9768%;&quot;&gt;웹 서비스 동작의 핵심. 사용자와 가장 가까움.- HTTP/HTTPS: REST API, 웹 통신- SMTP: 메일 전송- DNS: 도메인 &amp;rarr; IP 변환- WebSocket: 실시간 통신&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 8.48841%;&quot;&gt;&lt;b&gt;6계층&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 16.6279%;&quot;&gt;표현 계층 (Presentation Layer)&lt;/td&gt;
&lt;td style=&quot;width: 32.6743%;&quot;&gt;SSL/TLS, JPEG, MPEG, JSON, XML&lt;/td&gt;
&lt;td style=&quot;width: 41.9768%;&quot;&gt;데이터 인코딩/암호화/압축을 담당- TLS/SSL: HTTPS 암호화- JSON/XML: 데이터 직렬화 포맷&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 8.48841%;&quot;&gt;&lt;b&gt;5계층&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 16.6279%;&quot;&gt;세션 계층 (Session Layer)&lt;/td&gt;
&lt;td style=&quot;width: 32.6743%;&quot;&gt;NetBIOS, RPC, gRPC, WebSocket&lt;/td&gt;
&lt;td style=&quot;width: 41.9768%;&quot;&gt;세션 관리 (연결 유지)- WebSocket 연결 유지- RPC/gRPC: 원격 호출 프레임워크&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 8.48841%;&quot;&gt;&lt;b&gt;4계층&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 16.6279%;&quot;&gt;전송 계층 (Transport Layer)&lt;/td&gt;
&lt;td style=&quot;width: 32.6743%;&quot;&gt;TCP, UDP&lt;/td&gt;
&lt;td style=&quot;width: 41.9768%;&quot;&gt;실제 데이터 전송 제어- TCP: 신뢰성 있는 연결 (HTTP 등)- UDP: 빠른 전송 (DNS, 게임 등)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 8.48841%;&quot;&gt;&lt;b&gt;3계층&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 16.6279%;&quot;&gt;네트워크 계층 (Network Layer)&lt;/td&gt;
&lt;td style=&quot;width: 32.6743%;&quot;&gt;IP, ICMP, ARP, BGP, OSPF&lt;/td&gt;
&lt;td style=&quot;width: 41.9768%;&quot;&gt;주소 지정과 라우팅- IP: 주소 및 경로 지정- ICMP: ping 등 네트워크 상태 체크&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 8.48841%;&quot;&gt;&lt;b&gt;2계층&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 16.6279%;&quot;&gt;데이터 링크 계층 (Data Link Layer)&lt;/td&gt;
&lt;td style=&quot;width: 32.6743%;&quot;&gt;Ethernet, PPP, ARP, VLAN&lt;/td&gt;
&lt;td style=&quot;width: 41.9768%;&quot;&gt;MAC 주소 기반으로 프레임 전송- Ethernet: 유선 LAN 표준- ARP: IP &amp;harr; MAC 변환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 8.48841%;&quot;&gt;&lt;b&gt;1계층&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 16.6279%;&quot;&gt;물리 계층 (Physical Layer)&lt;/td&gt;
&lt;td style=&quot;width: 32.6743%;&quot;&gt;RS-232, USB, IEEE 802.11 (Wi-Fi), 광케이블, UTP&lt;/td&gt;
&lt;td style=&quot;width: 41.9768%;&quot;&gt;전기 신호/물리적 연결- 실제 케이블, 무선 주파수 등&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;계층별 간단한 설명&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;응용 계층&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;b&gt;HTTP(HyperText Transfer Protocol)&lt;/b&gt; : &lt;b&gt;웹 브라우저&lt;/b&gt;와 &lt;b&gt;서버&lt;/b&gt;가 웹페이지, 이미지 등 &lt;b&gt;데이터를 주고받는 통신 규약&lt;/b&gt;이다.&lt;br /&gt;비연결성과 무상태성을 특징으로 하며, 주로 포트 &lt;b&gt;80&lt;/b&gt;을 사용한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;HTTPS&lt;/b&gt; : &lt;b&gt;HTTP에 SSL/TLS 암호화가 추가&lt;/b&gt;된 보안 프로토콜이다. &lt;b&gt;데이터&lt;/b&gt;를 &lt;b&gt;암호화&lt;/b&gt;해 중간자 &lt;b&gt;공격과 도청을 방지&lt;/b&gt;하며 기본 포트는 &lt;b&gt;443&lt;/b&gt;이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;FTP(File Trasfer Protocol)&lt;/b&gt; : &lt;b&gt;서버와 클라이언트 간 파일을 업로드 및 다운로드&lt;/b&gt;할 때 사용하는 프로토콜이다. 주로 포트 &lt;b&gt;21&lt;/b&gt;을 사용하며 데이터 전송에 비암호화 방식을 쓴다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;SMTP(Simple Mail Transfer Protocol)&lt;/b&gt; : &lt;b&gt;이메일을 전송&lt;/b&gt;하기 위한 프로토콜로 메일 서버 간 메일 송수신에 사용한다. 기본 포트는 &lt;b&gt;25&lt;/b&gt;이며 주로 발신 메일 서버에 적용된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;DNS(Domain Name System)&lt;/b&gt; : &lt;b&gt;도메인 이름&lt;/b&gt;을 &lt;b&gt;IP&lt;/b&gt; &lt;b&gt;주소로 변환&lt;/b&gt;하는 시스템이자 프로토콜이다. 인터넷 접속 시 사람이 읽기 쉬운 도메인을 &lt;b&gt;컴퓨터가 이해하는 IP로 매핑&lt;/b&gt;한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;WebSocket&lt;/b&gt; : 클라이언트와 서버 간 &lt;b&gt;실시간, 양방향 통신&lt;/b&gt;을 가능하게 하는 프로토콜이다. HTTP로 초기 연결을 시작하며 이후 TCP 소켓으로 업그레이드된다. &lt;br /&gt;SSH(Secure Shell) &lt;b&gt;원격 서버에&lt;/b&gt; 안전하게 접속하기 위한 암호화된 통신 프로토콜이다. 주로 시스템 관리자가 원격으로 서버를 제어할 때 사용하며 포트 22를 쓴다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;표현 계층&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li data-end=&quot;59&quot; data-start=&quot;46&quot;&gt;&lt;b&gt;SSL/TLS&lt;/b&gt; : &lt;b&gt;데이터 통신을 암호화&lt;/b&gt;해 보안을 제공하는 프로토콜이다. 인터넷에서 &lt;b&gt;HTTPS로 안전한 연결을 할 때&lt;/b&gt; 주로 사용된다.&lt;/li&gt;
&lt;li data-end=&quot;142&quot; data-start=&quot;132&quot;&gt;&lt;b&gt;JPEG&lt;/b&gt; : &lt;b&gt;이미지 파일을 압축&lt;/b&gt;하는 &lt;b&gt;손실 압축 방식의 포맷&lt;/b&gt;이다. 사진과 같은 복잡한 이미지를 저장할 때 널리 쓰인다.&lt;/li&gt;
&lt;li data-end=&quot;220&quot; data-start=&quot;210&quot;&gt;&lt;b&gt;MPEG&lt;/b&gt; : &lt;b&gt;동영상&lt;/b&gt;과 &lt;b&gt;오디오&lt;/b&gt; &lt;b&gt;데이터&lt;/b&gt;를 &lt;b&gt;압축&lt;/b&gt;해 &lt;b&gt;저장&lt;/b&gt;하고 &lt;b&gt;전송&lt;/b&gt;하는 &lt;b&gt;표준 포맷&lt;/b&gt;이다. 스트리밍과 동영상 재생에 자주 활용된다.&lt;/li&gt;
&lt;li data-end=&quot;299&quot; data-start=&quot;289&quot;&gt;&lt;b&gt;JSON&lt;/b&gt; :&lt;b&gt;데이터를&lt;/b&gt; &lt;b&gt;사람과&lt;/b&gt; &lt;b&gt;기계가&lt;/b&gt; 모두 &lt;b&gt;읽기 쉬운 텍스트 형식&lt;/b&gt;으로 &lt;b&gt;표현&lt;/b&gt;하는 포맷이다. 웹 API에서 서버와 클라이언트 간 데이터 교환에 널리 사용된다.&lt;/li&gt;
&lt;li data-end=&quot;396&quot; data-start=&quot;387&quot;&gt;&lt;b&gt;XML&lt;/b&gt; : &lt;b&gt;데이터를 구조화해 표현하는 마크업 언어&lt;/b&gt;이다. 복잡한 데이터와 문서 교환에 주로 사용되며, 태그 기반으로 작성된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;세션 계층&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li data-end=&quot;66&quot; data-start=&quot;53&quot;&gt;&lt;b&gt;NetBIOS&lt;/b&gt; : &lt;b&gt;로컬 네트워크에서 컴퓨터 이름을 식별하고 통신&lt;/b&gt;하는 프로토콜이다. 주로 Windows 네트워크 환경에서 파일과 프린터 공유에 사용된다.&lt;/li&gt;
&lt;li data-end=&quot;183&quot; data-start=&quot;150&quot;&gt;&lt;b&gt;RPC (Remote Procedure Call)&lt;/b&gt; :&lt;b&gt;원격 서버의 함수를 마치 로컬 함수처럼 호출할 수 있게 하는 통신&lt;/b&gt; 프로토콜이다. 분산 시스템에서 클라이언트와 서버 간 함수 호출에 주로 활용된다.&lt;/li&gt;
&lt;li data-end=&quot;183&quot; data-start=&quot;150&quot;&gt;&lt;b&gt;gRPC&lt;/b&gt; : 구글에서 개발한 고성능 원격 프로시저 호출(RPC) 프레임워크이다. 프로토콜 버퍼(Protobuf)를 사용해 데이터 직렬화와 효율적인 통신을 지원한다.&lt;/li&gt;
&lt;li data-end=&quot;393&quot; data-start=&quot;378&quot;&gt;&lt;b&gt;WebSocket&lt;/b&gt; : 클라이언트와 서버 간 실시간, 양방향 통신을 가능하게 하는 프로토콜이다. HTTP로 초기 연결 후 TCP 소켓으로 업그레이드되어 지속적인 통신을 유지한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-end=&quot;393&quot; data-start=&quot;378&quot; data-ke-style=&quot;style3&quot;&gt;WebSocket은 응용 계층 프로토콜이면서 세션 계층에서 하는 연결 관리 역할도 함께 하기 때문에 두 계층에서 모두 언급된다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #333333; text-align: start;&quot;&gt;전송 계층&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li data-end=&quot;93&quot; data-start=&quot;52&quot;&gt;&lt;b&gt;TCP&lt;/b&gt; (Transmission Control Protocol) :&lt;b&gt;신뢰성 있는 데이터 전송&lt;/b&gt;을 보장하는 &lt;b&gt;연결 지향&lt;/b&gt; 프로토콜이다. 데이터가 순서대로 정확하게 도착하도록 오류 검출과 재전송을 수행한다.&lt;/li&gt;
&lt;li data-end=&quot;209&quot; data-start=&quot;175&quot;&gt;&lt;b&gt;UDP&lt;/b&gt; (User Datagram Protocol) : &lt;b&gt;빠른 전송&lt;/b&gt;을 위해 &lt;b&gt;신뢰성 검사 없이 데이터를 보내는&lt;/b&gt; &lt;b&gt;비연결형&lt;/b&gt; 프로토콜이다. 주로 실시간 스트리밍, 게임, DNS 조회 등 지연 시간이 중요한 서비스에 사용된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;77&quot; data-start=&quot;49&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;77&quot; data-start=&quot;49&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;네트워크 계층&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li data-end=&quot;77&quot; data-start=&quot;49&quot;&gt;&lt;b&gt;IP&lt;/b&gt; (Internet Protocol) : 네트워크에서 &lt;b&gt;데이터를 목적지 IP 주소로 전달&lt;/b&gt;하는 기본 프로토콜이다. 패킷을 라우팅해 인터넷 상에서 데이터를 이동시키는 역할을 한다.&lt;/li&gt;
&lt;li data-end=&quot;207&quot; data-start=&quot;161&quot;&gt;&lt;b&gt;ICMP&lt;/b&gt; (Internet Control Message Protocol) : &lt;b&gt;네트워크 장비 간 오류 메시지와 상태 정보를 전달&lt;/b&gt;하는 프로토콜이다. ping 명령어로 네트워크 연결 상태를 확인할 때 사용된다.&lt;/li&gt;
&lt;li data-end=&quot;327&quot; data-start=&quot;288&quot;&gt;&lt;b&gt;ARP&lt;/b&gt; (Address Resolution Protocol) : &lt;b&gt;IP 주소&lt;/b&gt;를 실제 물리 주소인 &lt;b&gt;MAC 주소로 변환&lt;/b&gt;하는 프로토콜이다. 로컬 네트워크 내에서 장치 간 통신을 위해 필수적이다. (MAC 주소를 IP주소로 변환하는 프로토콜인 RARP도 있음)&lt;/li&gt;
&lt;li data-end=&quot;440&quot; data-start=&quot;405&quot;&gt;&lt;b&gt;BGP&lt;/b&gt; (Border Gateway Protocol) &lt;b&gt;인터넷 상의 자율 시스템(AS) 간 경로 정보를 교환&lt;/b&gt;하는 핵심 &lt;b&gt;라우팅 프로토콜&lt;/b&gt;이다. 대규모 인터넷 라우팅 경로를 관리하고 최적 경로를 선택한다.&lt;/li&gt;
&lt;li data-end=&quot;567&quot; data-start=&quot;530&quot;&gt;&lt;b&gt;OSPF&lt;/b&gt; (Open Shortest Path First) 내부 네트워크에서 &lt;b&gt;최단 경로&lt;/b&gt;를 계산해 패킷을 전달하는 &lt;b&gt;링크 상태 라우팅&lt;/b&gt; &lt;b&gt;프로토콜&lt;/b&gt;이다. 효율적인 경로 탐색과 빠른 네트워크 재구성을 지원한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;567&quot; data-start=&quot;530&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;567&quot; data-start=&quot;530&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;데이터 링크 계층&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li data-end=&quot;63&quot; data-start=&quot;49&quot;&gt;&lt;b&gt;Ethernet&lt;/b&gt; : 유선 LAN에서 데이터를 프레임 단위로 전송하는 데이터 링크 계층 프로토콜이다. 컴퓨터 간 물리적 연결과 MAC 주소를 이용한 통신을 담당한다.&lt;/li&gt;
&lt;li data-end=&quot;188&quot; data-start=&quot;153&quot;&gt;&lt;b&gt;PPP&lt;/b&gt; (Point-to-Point Protocol) 두 지점 간 직접 연결된 네트워크에서 데이터 전송을 위한 프로토콜이다. 주로 전화선, DSL 등 WAN 접속에 사용된다.&lt;/li&gt;
&lt;li data-end=&quot;304&quot; data-start=&quot;265&quot;&gt;&lt;b&gt;ARP&lt;/b&gt; (Address Resolution Protocol) IP 주소를 MAC 주소로 변환하는 프로토콜이다. 로컬 네트워크 내 장치 간 통신에 필수적이다.&lt;/li&gt;
&lt;li data-end=&quot;406&quot; data-start=&quot;367&quot;&gt;&lt;b&gt;VLAN&lt;/b&gt; (Virtual Local Area Nework) 하나의 물리 네트워크를 여러 개의 논리적 네트워크로 분할하는 기술이다. 네트워크 트래픽 분리와 보안을 위해 사용된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;406&quot; data-start=&quot;367&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;406&quot; data-start=&quot;367&quot; data-ke-size=&quot;size23&quot;&gt;물리 계층&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li data-end=&quot;59&quot; data-start=&quot;47&quot;&gt;&lt;b&gt;RS-232&lt;/b&gt; : 컴퓨터와 주변기기 간 직렬 통신을 위한 표준 인터페이스이다. 저속 데이터 전송에 주로 사용되며 초기 모뎀 연결에 활용되었다.&lt;/li&gt;
&lt;li data-end=&quot;171&quot; data-start=&quot;139&quot;&gt;&lt;b&gt;USB&lt;/b&gt; (Universal Serial Bus) : 컴퓨터와 다양한 외부 장치를 연결하는 범용 직렬 버스 규격이다. 빠른 속도와 플러그 앤 플레이를 지원해 널리 쓰인다.&lt;/li&gt;
&lt;li data-end=&quot;263&quot; data-start=&quot;246&quot;&gt;&lt;b&gt;IEEE 802.11&lt;/b&gt; : 무선 LAN(Wi-Fi)의 표준 규격을 정의한 프로토콜이다. 무선으로 데이터를 송수신하며 다양한 주파수 대역과 속도를 지원한다.&lt;/li&gt;
&lt;li data-end=&quot;355&quot; data-start=&quot;345&quot;&gt;&lt;b&gt;광케이블&lt;/b&gt; : 빛을 이용해 데이터를 전송하는 고속 통신 매체이다. 장거리 전송에 적합하며 전기적 간섭에 강하다.&lt;/li&gt;
&lt;li data-end=&quot;455&quot; data-start=&quot;420&quot;&gt;&lt;b&gt;UTP&lt;/b&gt; (Unshielded Twisted Pair) : 일반적으로 LAN 케이블에 쓰이는 꼬임선 형태의 구리선 케이블이다. 전기적 간섭을 줄여 안정적인 데이터 전송이 가능하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;455&quot; data-start=&quot;420&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-end=&quot;204&quot; data-start=&quot;74&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;204&quot; data-start=&quot;74&quot; data-ke-size=&quot;size16&quot;&gt;전화를 걸면 우리는 자연스럽게 &amp;ldquo;여보세요&amp;rdquo;라고 말하고 상대방은 &amp;ldquo;누구세요?&amp;rdquo;라고 응답한다. 대화를 마치면 &amp;ldquo;끊을게요&amp;rdquo; 같은 인사로 마무리한다. 이런 흐름은 누구도 따로 가르쳐주지 않아도 익숙하게 따르는 &lt;b&gt;일종의 대화 규칙&lt;/b&gt;이다. &lt;b&gt;프로토콜도 이와 다르지 않다.&lt;/b&gt; 컴퓨터와 컴퓨터 혹은 클라이언트와 서버가 통신할 때도 &lt;b&gt;정해진 순서와 약속&lt;/b&gt;을 따라야 서로를 이해할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;401&quot; data-start=&quot;206&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;401&quot; data-start=&quot;206&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 HTTP 프로토콜에서는 요청(Request)을 보내고 응답(Response)을 받는 순서가 있고 TCP에서는 연결을 맺는 3-way handshake와 같은 절차가 존재한다. 이처럼 우리가 자연스럽게 전화를 걸고 끊듯이 시스템 간에도 &lt;b&gt;&amp;ldquo;언제 어떻게 무엇을 주고받을지&amp;rdquo;에 대한 정확한 규칙&lt;/b&gt;이 필요하다.&lt;br /&gt;이 규칙이 바로 &lt;b&gt;프로토콜&lt;/b&gt;&amp;nbsp;즉 &lt;b&gt;통신의 약속&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-end=&quot;621&quot; data-start=&quot;518&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;621&quot; data-start=&quot;518&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;개발자는 이 약속을 이해해야 한다.&lt;/b&gt; 그래야 시스템이 어떤 식으로 작동하는지 왜 그런 흐름으로 구성되어 있는지 문제가 생겼을 때 어디에서 발생했는지를 올바르게 파악할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;455&quot; data-start=&quot;420&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;455&quot; data-start=&quot;420&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Network</category>
      <category>OSI 7계층</category>
      <category>개발</category>
      <category>네트워크</category>
      <author>CHun2</author>
      <guid isPermaLink="true">https://chaehun97.tistory.com/76</guid>
      <comments>https://chaehun97.tistory.com/entry/%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C%EC%9D%80-%EC%99%9C-%EC%A4%91%EC%9A%94%ED%95%A0%EA%B9%8C-%EB%B0%B1%EC%97%94%EB%93%9C-%EA%B0%9C%EB%B0%9C%EC%9E%90%EA%B0%80-%EC%95%8C%EC%95%84%EC%95%BC-%ED%95%A0-OSI-7%EA%B3%84%EC%B8%B5%EB%B3%84-%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C#entry76comment</comments>
      <pubDate>Sat, 21 Jun 2025 00:29:10 +0900</pubDate>
    </item>
    <item>
      <title>운영체제 - 프로세스와 스레드</title>
      <link>https://chaehun97.tistory.com/entry/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4%EC%99%80-%EC%8A%A4%EB%A0%88%EB%93%9C</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프로그램, 프로세스, 스레드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스와 스레드의 정의를 먼저 알아보자.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;프로세스(Process): 운영체제로부터 자원을 할당받은 작업의 단위&lt;br /&gt;스레드(Thraed) : 프로세스가 할당받은 자원을 이용하는 실행 흐름의 단위&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 프로세스와 스레드에 대해 이해하기 전에 프로그램을 이해해야한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;프로그램(Program): 파일이 저장 장치에 저장되어 있지만 메모리에는 올라가 있지 않은 정적인 상태&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한마디로 운영체제가 메모리 공간을 할당해주지 않은 것이 프로그램이며 정적인 상태는 실행되지 않고 가만히 있다는 뜻이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론은 &lt;b&gt;프로그램&lt;/b&gt;은 &lt;b&gt;아직 실행되지 않은 파일 그 자체&lt;/b&gt;를 가리키는 말이다. 예를 들면 우리가 자주사용하는 크롬, 카카오톡 등 사용자가 눌러서 &lt;b&gt;실행하기 전의 상태&lt;/b&gt;를 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 그 &lt;b&gt;프로그램을 실행 시키면&lt;/b&gt; 컴퓨터 메모리상에 올라가게 되고, 이 상태를 &lt;b&gt;동적인 상태&lt;/b&gt;라고 하며 &lt;b&gt;이 상태의 프로그램을 프로세스&lt;/b&gt;라고 한다. 스케줄링 단계에서의 작업과 같은 단어라고 봐도 무방하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;516&quot; data-origin-height=&quot;116&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPiq01/btsOc7XSbF1/ySc1NFklpFik6MmCvKKUPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPiq01/btsOc7XSbF1/ySc1NFklpFik6MmCvKKUPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPiq01/btsOc7XSbF1/ySc1NFklpFik6MmCvKKUPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPiq01%2FbtsOc7XSbF1%2FySc1NFklpFik6MmCvKKUPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;516&quot; height=&quot;116&quot; data-origin-width=&quot;516&quot; data-origin-height=&quot;116&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ctrl + Shift + esc -&amp;gt; 프로세스 탭을 들어가면 쉽게 확인 가능하다. 실행중인 상태의 프로그램을 확인할 수 있고 동적인 상태인 Google Chrome가 프로세스이다. 만약 작업을 종료시키면 다시 프로그램이 되는것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간이 지날 수록 프로그램은 복잡해지고 프로세스 하나만으로 사용해서 프로그램을 실행하기는 제약이 너무 많아졌다. 그러면 프로세스를 여러 개 만들면 되지 않을까? 라고 생각을 할 수 있지만 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;운영체제는 안전성을 위해 프로세스마다 자신에게 할당된 메모리 내의 정보에만 접근할 수 있도록 제약을 두었다. 예를 들어 엑셀에서 작성한 표 데이터를 카카오톡 대화창에 자동으로 입력하고 싶다해서 엑셀이 직접 카카오톡 메모리에 접근하는 것은 불가능하다. 이처럼 각 애플리케이션이 독립된 프로세스로 실행되며 서로의 메모리 공간에는 접근할 수 없도록 운영체제가 보호하고 있다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 프로그램 하나가 단순히 한 가지 작업만을 하는 경우는 드물다. 예를 들어 웹브라우저를 생각해보면 브라우저는 단순히 HTML을 렌더링 하는것만으로 끝나지 않는다. 이미지 로딩, 자바스크립트 실행, 사용자 입력 처리, 네트워크 통신 등 다양한 작업이 동시에 이뤄진다. 이처럼 &lt;b&gt;다양한 작업&lt;/b&gt;을 동시에 수행하려면, &lt;b&gt;하나의 프로세스만으로는 부족&lt;/b&gt;하므로 &lt;b&gt;하나의 프로세스 안에서 작업 단위를 동시에 실행할 수 있게 한 것&lt;/b&gt;이 &lt;u&gt;&lt;b&gt;스레드&lt;/b&gt;&lt;/u&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스에 대해서 더 알아보자...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프로세스의 구조&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;318&quot; data-origin-height=&quot;434&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eDp0vz/btsOd7bZa9U/Le1xM2OAmz3HEncKdkHosK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eDp0vz/btsOd7bZa9U/Le1xM2OAmz3HEncKdkHosK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eDp0vz/btsOd7bZa9U/Le1xM2OAmz3HEncKdkHosK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeDp0vz%2FbtsOd7bZa9U%2FLe1xM2OAmz3HEncKdkHosK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;256&quot; height=&quot;349&quot; data-origin-width=&quot;318&quot; data-origin-height=&quot;434&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;CODE: 프로그램의 &lt;b&gt;컴파일된 기계어 코드가 저장되는 영역&lt;/b&gt;이다. 수정은 불가능하며 실행 중인 함수들의 명령어들이 이 영역에서 실행된다.&lt;/li&gt;
&lt;li&gt;DATA: &lt;b&gt;전역변수 또는 정적 변수 중 초기값이 &lt;u&gt;있는&lt;/u&gt; 변수&lt;/b&gt;들이 저장된다. 프로그램이 시작될 때 메모리에 로드되며 종료 시까지 유지된다.&lt;/li&gt;
&lt;li&gt;BSS: DATA 영역과 함께 구성되며 &lt;b&gt;초기값이 없는 전역/정적 변수&lt;/b&gt;가 저장된다. 실제로 실행 파일에는 크기만 기록되고 &lt;b&gt;실행 시 0으로 초기화&lt;/b&gt;되어 메모리에 할당된다.&lt;/li&gt;
&lt;li&gt;STACK: &lt;b&gt;함수 호출&lt;/b&gt;시&lt;b&gt; 지역 변수, 매개변수, 반환 주소 등이 저장&lt;/b&gt;되는 영역이다. 함수가 호출되면 스택 프레임(Stack Frame)이 생성되고 함수가 끝나면 제거된다.&lt;/li&gt;
&lt;li&gt;HEAP: &lt;b&gt;실행 중에 할당되는 메모리&lt;/b&gt;가 저장된다. 프로그래머가 직접 할당/해제를 해야 하며(C의 경우) 해제하지 않으면 메모리 누수가 발생한다. 자바의 경우는 가비지 컬렉터(GC)가 사용하지 않는 객체를 감지하고 정리한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;스택 프레임(Stack Frame)이란?&lt;br /&gt;Stack Frame은 함수가 호출될 때 스택에 쌓이는 데이터 블록으로 해당 함수의 지역 변수, 매개변수, 리턴 주소, 이전 함수의 상태 등을 담고있다.&lt;br /&gt;&lt;br /&gt;왜 필요한데?&lt;br /&gt;프로그램이 여러 함수를 호출하면 함수마다 자신의 실행 정보를 따로 저장해야한다. 그래야 함수가 끝났을 때 이전 상태로 정확히 돌아갈 수 있다.&lt;br /&gt;&lt;br /&gt;스택을 사용하는 이유는 후입선출(LIFO)구조이기 때문이다. 마지막에 호출된 함수가 가장 먼저 끝나야 하니까 스택이 함수 호출과 복귀에 맞는 구조이다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;코드 예시&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작성자는 주 언어인 JAVA위주로 설명하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CODE 영역: 컴파일된 바이트코드 (JVM에서 네이티브 명령어로 실행) 수정 불가, 클래스, 메서드 등 실행 가능한 코드가 저장된다.&lt;/p&gt;
&lt;pre id=&quot;code_1748271874238&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class A {
	public static void Hello() {
    	System.out.printLn(&quot;Hello World!!!&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hello() 메서드는 JVM이 실행 가능한 바이트코드로 변환되어 CODE 영역에 저장되며 메서드는 JVM이 메모리에 로드한 후 이 영역에서 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;바이트 코드란?&lt;br /&gt;&lt;br /&gt;바이트코드(Byercode)는 가상 머신(Virtual Machine)이 이해하고 실행할 수 있도록 컴파일된 중간 단계의 이진 코드이다.&lt;br /&gt;1. .java 파일에 코드를 작성한다.&lt;br /&gt;2. 컴파일하면 -&amp;gt; .class 파일이 생기는데, 이 안에 들어있는 것이 바이트 코드이다.&lt;br /&gt;3. 이 바이트 코드를 JVM(Java Virtual Machine)이 실행한다.&lt;br /&gt;&lt;br /&gt;바이트 코드는 운영체제나 CPU에 종속되지 않고 가상 머신에서 실행할 수 있도록 만들어진 코드이다.&lt;br /&gt;&lt;br /&gt;바이트 코드는 이식성과 보안, 안정성, 성능과 같은 이점에서 사용한다. 어떠한 운영체제서든 실행가능하며 가상머신 위에서 실행되므로 실제 시스템에 영향을 덜 준다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DATA(BSS) 영역: 초기값이 있는 static 변수, final 상수 그리고 초기값이 없는 static 변수를 함께 저장한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1748272392666&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Ex {
    static int count = 10;  // DATA 영역
    static final String APP_NAME = &quot;MyApp&quot;; // 역시 DATA 영역
    static int value; // 초기값 X &amp;rarr; 0으로 자동 초기화됨 (자바에서는 BSS와 DATA 구분 없음)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;count와 APP_NAME은 클래스가 로딩될 때 JVM에 의해 메모리에 고정 배치된다. 초기값이 존재하므로 DATA 영역에 저장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바에서는 BSS 영역이 따로 없다. 초기값이 라도 JVM이 자동으로 0 또는 null로 초기화 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;STACK 영역: 지역변수, 매개변수, 함수 호출 정보를 저장한다. 함수가 호출될 때마다 스택 프레임 생성, 종료되면 제거된다.&lt;/p&gt;
&lt;pre id=&quot;code_1748272764463&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Ex {
    public static void main(String[] args) {
        int local = 5;  // STACK 영역
        printValue(local);
    }

    public static void printValue(int val) { // val도 STACK에 저장
        System.out.println(val);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HEAP 영역: 동적 할당된 객체가 저장되는 공간이다. new연산자로 생성된 인스턴스가 저장되며 자바에서는 GC가 자동으로 메모리 관리한다.&lt;/p&gt;
&lt;pre id=&quot;code_1748272836381&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Ex {
    public static void main(String[] args) {
        Person p = new Person(&quot;Alice&quot;); // HEAP에 Person 객체 생성
    }
}

class Person {
    String name;

    Person(String name) {
        this.name = name;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;new Person(&quot;Alice&quot;)로 생성된 객체는 HEAP 영역에 저장, p는 STACK에 저장된 참조 변수이며 객체 내부 필드 name도 HEAP에 저장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리 영역을 알고 있으면 더 효율적이고 안정적인 코드를 작성하며 문제를 더 잘 디버깅할 수 있는 장점이 있다. 눈에 보이지 않는 프로그램의 내부 동작을 시각화할 수 있게 되고, 더 깊은 사고를 가능하게 한다!!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프로세스 상태 전이와 문맥교환&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영체제에서 프로세스는 실행되는 동안 여러 상태를 거친다. 이 상태들을 프로세스의 생명 주기를 나타내며 각 상태 간의 이동을 프로세스 상태 전이라고 한다. 프로세스 상태 전이는 운영체제가 CPU, 메모리 등의 자원을 효율적으로 관리하고 여러 프로세스가 동시에 실행되는 것처럼 보이게 하는 핵심 개념이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주요 프로세스 상태&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;생성(New): 프로세스가 처음 생성되는 상태, 이 상태에서 운영체제는 프로세스를 실행하기 위한 자원을 할당한다.&lt;/li&gt;
&lt;li&gt;준비(Ready): 프로세스가 CPU를 할당 받아 실행될 준비가 된 상태, CPU 스케줄러가 다음 실행할 프로세스를 선택하기 위해 대기하는 큐에 포함된다.&lt;/li&gt;
&lt;li&gt;실행(Running): 프로세스가 CPU를 할당받아 명령어를 실행하고 있는 상태, 한 번에 하나의 프로세스만 이 상태에 있을 수 있으나 멀티 코어 시스템에서는 코어 수만큼 할당 가능하다.&lt;/li&gt;
&lt;li&gt;대기(Waiting/Blocked): 프로세스가 I/O 작업 완료, 특정 이벤트 발생, 자원 할당 등과 같은 특정 조건을 기다리는 상태, 이 상태에서는 CPU를 사용하지 않는다.&lt;/li&gt;
&lt;li&gt;종료(Terminated): 프로세스가 실행을 완료했거나 어떤 이유로든 중단되어 메모리에서 제거되기 전의 상태&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주요 상태 전이&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 생성 -&amp;gt; 준비 : 프로세스가 생성되고 필요한 자원을 할당받으면 CPU를 할당받기 위해 준비 큐로 이동&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 준비 -&amp;gt; 실행 : CPU 스케줄러가 준비 큐에 있는 프로세스 중 하나를 선택하여 CPU를 할당하면 해당 프로세스는 실행 상태로 전이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 실행 -&amp;gt; 대기&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;I/O 요청 : 파일 읽기/쓰기와 같은 I/O 작업을 요청하면, I/O 작업이 완료될 때까지 대기 상태로 전이&lt;/li&gt;
&lt;li&gt;자원 요청 : 프로세스가 사용 불가능한 자원(프린터 등)을 요청하면, 해당 자원이 사용가능 해질 때까지 대기 상태로 전이&lt;/li&gt;
&lt;li&gt;이벤트 대기 : 특정 이벤트를 기다려야 하는 경우 대기 상태로 전이&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 실행 -&amp;gt; 준비&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;타임 슬라이스 만료(Time Slice Expired): 시분할 시스템에서 프로세스에 할당된 CPU 사용 시간이 만료되면 강제로 CPU를 빼앗기고 준비 상태로 돌아가 다음 차례를 기다림&lt;/li&gt;
&lt;li&gt;인터럽트 (Interrupt) : 우선순위가 높은 인터럽트가 발생하면 현재 실행 중인 프로세스 중단되고 준비 상태로 돌아감&lt;/li&gt;
&lt;li&gt;우선순위가 더 높은 프로세스 : 선점형 스케줄링에서 더 높은 우선순위의 프로세스가 준비 큐에 들어오면, 현재 실행 중인 프로세스는 중단되고 준비 상태로 돌아감&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 대기 -&amp;gt; 준비 : 프로세스가 대기하던 I/O 작업이 완료되거나 요청했던 자원이 할당되거나 기다리던 이벤트가 발생하면 다시 CPU를 할당받을 준비를 위해 준비 상태로 전이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6 실행 -&amp;gt; 종료 : 프로세스가 할당된 작업을 모두 완료하거나 오류가 발생하거나 외부적인 요인(프로세스 강제종료, kill 시그널)에 의해 중단되면 종료 상태로 전이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. 준비 -&amp;gt; 종료 : 비정상적인 상황으로 인해 준비 상태에 있던 프로세스가 강제로 종료될 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문맥교환 (Context Switching)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문맥 교환은 특정 프로세스 상태 전이가 일어날 때 동반되는 작업인데 CPU를 사용하는 주체가 변경될 때마다 발생하는 것이 문맥 교환이다. 한마디로 CPU가 다른 프로세스에게 점유되는 상황이라고 할 수 있으며 CPU의 제어권이 한 프로세스에서 다른 프로세스로 넘어가는 과정이라고 할 수 있다. CPU가 Google Chrome를 실행하고 있을 때 타임 슬라이스 만료로 카카오톡이 CPU를 선점하면 Google Chorme 프로세스의 문맥을 PCB(Process Control Block)에 저장하고 준비 큐에서 다음으로 실행할 프로세스를 선택한다. 그 후 선택된 프로세스의 문맥을 복원하여 CPU 에 로드하고, 해당 프로세스의 실행을 재개한다. 이 과정이 문맥 교환이다. 다시 한 번 되새겨보자면 프로세스는 동시에 실행되는 것이 아닌 동시에 실행되는 것처럼 보이는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;인터럽트 (Interrupt)가 뭔데?&lt;br /&gt;CPU가 프로그램을 실행하고 있을 때, 현재 실행 중인 작업을 잠시 중단하고 다른 특정 작업을 수행하도록 하는 메커니즘이다. 사람이 일하다가 전화오면 하던 일을 멈추고 전화를 하는 것과 같다.&lt;br /&gt;&lt;br /&gt;타임 슬라이스 (Time Silce /Quantum)가 뭔데?&lt;br /&gt;시분할 시스템에서 CPU가 한 프로세스에 할당되는 최대 시간 단위를 의미한다. 쿼텀(Quantum)이라고도 한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;br /&gt;PCB(Process Control Block)이 뭔데?&lt;br /&gt;운영체제가 특정 프로세스를 관리하고 제어하기 위해 필요한 모든 정보를 담고 있는 데이터 구조이다. 문맥 교환 발생시 Google Chrome 프로세스가 어디까지 실행했는지 어떤 자원을 사용하고 있었는지 정확히 알아야 다시 이 프로세스를 실행 할 때 중단되었던 시점부터 이어서 처리할 수 있다.&lt;br /&gt;&lt;br /&gt;PCB의 주요정보&lt;br /&gt;&amp;nbsp;- 프로세스 식별자 (Process ID - PID) : 각 프로세스를 고유하게 식별하는 번호&lt;br /&gt;&amp;nbsp;- 프로세스 상태 (Process State) : 프로세스의 현재 상태 (생성, 준비 실행, 대기, 종료)&lt;br /&gt;&amp;nbsp;- 프로그램 카운터 (Program Counter - PC) : 이 프로세스가 다음에 실행할 명령어의 주소, 프로세스가 일시 중단되었다가 다시 실행될 때 이 정보를 통해 중단된 지점부터 이어서 실행함&lt;br /&gt;&amp;nbsp;- CPU 레지스터 정보 (CPU registers) : CPU 내부에 있는 범용 레지스터, 스택 포인터, 인덱스 레지스터 등 모든 레지스터 값을 저장, 문맥 교환 시 프로세스의 레지스터 값을 PCB에 저장하고, 다음 프로세스의 레지스터 값을 복원하는데 사용&lt;br /&gt;&amp;nbsp;- CPU 스케줄링 정보 : 프로세스의 우선순위, 스케줄링 큐에서의 위치, CPU 사용 시간, 최종 실행 시간 등 CPU 할당과 관련된 정보&lt;br /&gt;&amp;nbsp;- 메모리 관리 정보 : 프로세스의 가상 주소 공간에 대한 정보&lt;br /&gt;&amp;nbsp;- 계정 정보 (Accounting Information) : CPU 사용 시간, 실제 사용 시간, 프로세스 ID, 소유자 정보 등 프로세스의 자원 사용 통계 및 관리에 필요한 정보&lt;br /&gt;&amp;nbsp;- 입출력 상태 정보 (I/O Status Infomation) : 프로세스에 할당된 입출력 장치 목록, 열린 파일 목록 등 입출력 관련 정보&lt;br /&gt;&amp;nbsp;- 포인터 : 부모 프로세스, 자식 프로세스, 다음 PCB의 주소 등 프로세스 간의 관계나 연결된 데이터 구조를 가리키는 포인터 정보&lt;br /&gt;&lt;br /&gt;PCB의 주요정보는 커널(Kernel) 영역의 메모리에 저장된다. 일반 사용자는 직접 접근 할 수 없도록 보호하며 운영체제는 이 PCB들을 효율적으로 관리하기 위해 프로세스 테이블이라는 자료구조(연결리스트 or 배열 형태)를 사용하요 PCB에 대한 포인터를 저장한다.&lt;br /&gt;&lt;br /&gt;PCB는 프로세스를 생성, 실행, 중단, 재개, 종료 등 생명 주기를 관리하는 키 카드이며 DB라고 할 수 있음, 문맥 교환시 이 PCB에 저장된 정보들이 사용된다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추후에 프로세스 스케줄링을 포스팅할 예정. 프로세스 스케줄링은 CPU를 어떤 프로세스에 할당할 것인가 결정하는 과정이며 한정된 자원을 여러 프로세스들이 효율적으고 공정하게 공유할 수 있도록 하는 작업이다. 프로세스 스케줄링을 이해하기 전에 프로세스 상태 전이를 기억하고 있으면 더욱 좋음!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프로세스와 스레드의 작동방식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스가 메모리에 올라갈 때 운영체제로부터 시스템 자원을 할당받는다. 운영체제는 프로세스마다 각각 독립된 메모리 영역을 Code, Data, Stack, Heap의 형식으로 할당 해주는데 각각 독립된 메모리 영역을 할당해주기 때문에 프로세스는 다른 프로세스의 변수나 자료에 접근할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 스레드는 메모리를 서로 공유할 수 있다 프로세스가 할당받은 메모리 영역 내에서 Stack 형식으로 할당된 메모리 영역은 따로 할당 받고, 나머지 Code, Data, Heap 형식으로 할당된 메모리 영역을 공유한다. 각각의 스레드는 별도의 스택을 가지고 있지만 힙 메모리는 서로 읽고 쓸 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 중요한 차이를 알 수 있는데 프로세스를 실행하다가 오류가 발생해서 프로세스가 강제로 종료되면 다른 프로세스에게 별 다른 영향을 주지 않지만, 스레드는 Code, Data, Heap 영역을 공유하기 때문에&amp;nbsp; 어떠한 스레드 하나에 오류가 발생하면 같은 프로세스 내의 다른 모든 스레드가 종료된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;멀티프로세싱, 멀티스레딩&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;멀티 프로세싱(Multi-processing)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티 프로세싱은 둘 이상의 프로세스를 사용하고 두 개 이상의 프로세스를 동시에 실행하는 것을 의미한다. '동시에' 말 그대로 병렬적으로 실행된다는 의미이다. 각각의 프로세스는 운영체제로 부터 독립적인 메모리 공간(코드, 데이터, 힙 스택)과 자원(파일 핸들, 열린 소켓)을 할당 받는다. 따라서 한 프로세스의 요류나 종료가 다른 프로세스에 직접적인 영향을 미치지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;구현 방식&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;다중 CPU 시스템 : 물리적으로 여러 개의 CPU를 장착한 시스템&lt;/li&gt;
&lt;li&gt;멀티 코어 시스템 : 하나의 CPU 칩 내부에 여러 개의 처리 코어(Core)가 있는 시스템. 현대의 대부분의 CPU가 해당&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;장점&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;안정성 (Fault Isolation) : 각 프로세스가 독립적인 메모리 공간을 가지므로 프로세스 하나가 문제가 생겨도 다른 프로세스에는 영향을 주지 않아 시스템 전체의 안정성이 높다&lt;/li&gt;
&lt;li&gt;병렬성 (Parallelism) : 여러 개의 CPU 코어를 효과적으로 화룡ㅇ하여 동시에 여러 작업을 처리할 수 있어서 CPU 연산량이 많은 작업에서 높은 성능 향상을 보인다.&lt;/li&gt;
&lt;li&gt;자원 관리 용이 : 각 프로세스가 독립적이므로, 운영체제 입장에서는 자원 할당 및 회수가 명확하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;단점&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;높은 자원 소모: 각 프로세스가 독립적인 메모리 공간과 자원을 가지므로 스레드에 비해 더 많은 메모리와 시스템 자원을 필요로 한다.&lt;/li&gt;
&lt;li&gt;느린 문맥교환 : 프로세스간 문맥 교환은 스레드간 문맥교환보다 훨씬 많은 정보를 저장하고 복원해야 하므로 오버헤드가 크고 속도가 느리다.&lt;/li&gt;
&lt;li&gt;복잡한 IPC : 프로세스 간에 데이터를 주고 받으려면 파이프, 소켓, 공유 메모리 등 운영체제가 제공하는 복잡한 IPC 메커니즘을 사용해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;오버헤드 (Overhead)가 뭔데?&lt;br /&gt;어떠한 작업을 수행하는 데 필수적이지는 않지만 그 작업을 가능케 하거나 효율적으로 만들기 위해 소모되는 자원을 말한다. 시간, 메모리, CPU 사이클 등 의미한다. 문맥교환시 상태를 저장하고 불러오는 데 드는 시간은 문맥 교환 오버헤드, 함수를 호출하고 리턴하는 가정에서 스택에 변수를 푸시하고 팝하며 함수 주소로 점프하는 등 추가적인 CPU 사이클이 도는 시간등이 해당된다.&lt;br /&gt;과도한 오버헤드는 시스템의 성능을 저하시키는 원인이 된다. 하지만 과도하게 성능을 강조하다보면 보안이 취약해지므로 적절하게 균형을 잡는 것이 중요하다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;IPC(Inter-Process Communication)가 뭔데?&lt;br /&gt;각 프로세스는 독립적으로 존재하므로 프로세스들이 메모리 영역에 함부로 접근하지 못하지만 그 프로세스들이 정보를 주고받거나 작업을 조율해야 할 필요가 생긴다. 이처럼 독립적인 프로세스들이 서로 데이터를 교혼하고 활동을 동기화하기 위한 메커니즘이며 게임에서 서버 프로세스와 클라이언트 프로세스가 서로 게임 상태를 주고 받거나 파일 복사 프로세스가 다른 파일 관리 프로세스에게 복사 완료를 알릴 때 사용한다. 이것을 프로세스 간 통신, IPC라고 한다.&lt;br /&gt;&lt;br /&gt;프로세스는 독립적이고 고립된 환경에서 실행되기 때문에&amp;nbsp; 전반적인 시스템의 유연성과 확장성을 높이려면 프로세스간 협업이 필요하기 때문에 주로 사용된다.&lt;br /&gt;&lt;br /&gt;주요 목적은 정보 공유, 계산 가속화, 모듈성, 편의성을 위해 사용되며 IPC의 종류에는 Pipe, Message Queue, Shared Memory, Semaphore등이 있다.&lt;br /&gt;&lt;br /&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;멀티 스레딩(Multi-threading)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티 스레딩은 하나의 프로세스 내에서 둘 이상의 스레드를 사용하여 갖겁을 동시에 수행하는 것을 의미한다. CPU 코어 수에 따라 병렬적일 수 도 있고 단일 코어에서는 동시적(Concurrent)으로 번갈아 가며 실행될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 프로세스는 하나이상의 스레드를 가질 수 있고 같은 프로스세 내의 스레드들은 프로세스의 코드, 데이터, 힙 영역과 같은 대부분의 자원을 공유하지만 각각 독립적은 Stack과 Register 값을 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;구현 방식&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;운영체제 커널이 직접 스레드를 관리하는 커널 수준 스레드(Kernel-level-Threads) 방식이 주로 사용된다.&lt;/li&gt;
&lt;li&gt;단일 CPU 코어에서도 시분할 방식으로 스레드 간 문맥 교환이 빠르게 일어나 여러 작업이 동시에 진행되는 것처럼 보이게한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;장점&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;자원 효율성 : 같은 프로세스 내의 스레드들이 대부분의 자원을 공유, 프로세스를 새로 생성하는 것 보다 훨씬 적은 메모리와 시스템 자원을 소모한다.&lt;/li&gt;
&lt;li&gt;빠른 문맥 교환 : 스레드 간 문맥 교환은 프로세스 간 문맥 교환보다 저장하고 복원할 정보가 적기 때문에 오버헤드가 적고 속도가 빠르다.&lt;/li&gt;
&lt;li&gt;빠른 스레드 간 통신 : 스레드 들이 프로세스의 힙 영역이나 전역 변수 같은 메모리 공간을 공유하므로 별도의 IPC 메커니즘 없이 직접 데이터를 주고바다을 수 있어서 통신 비용이 낮다.&lt;/li&gt;
&lt;li&gt;응답성 향상 : 하나의 스레드가 긴 작업을 수행하더라도, 다른 스레드가 사용자 입력이나 다른 작업을 처리할 수 있어 프로그램의 응답성이 향사오딘다.&lt;/li&gt;
&lt;li&gt;멀티코어 활용 : 멀티코어 CPU 환경에서 여러 스레드를 병렬적으로 실행하여 성능이 향상된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;단점&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;동기화 문제(Synchronization Issues): 여러 스레드가 공유 자원에 동시에 접근할 경우 데이터 손상이 발생할 수 있다. 이를 방지하기 위해 Mutex, Semaphore 등 복잡한 동기화 메커니즘을 사용하며, 이 과정에서 Deadlock과 같은 문제가 발생할 수 있다.&lt;/li&gt;
&lt;li&gt;안정성 취약 : 하나의 스레드가 문제가 발생하여 비정상적으로 종료되면 해당 프로세스가 공유하는 자원을 사용하는 프로세스들의 영향을 미쳐 프로세스 자체가 종료될 수 있다.&lt;/li&gt;
&lt;li&gt;디버깅의 어려움 : 동시성 문제와 공유 자원으로 인한 버그는 디버깅이 까다롭다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;커널 수준 스레드 (Kernel-Level Thread)가 뭔데?&lt;br /&gt;운영체제의 커널이 직접 관리하고 스케줄링하는 스레드이다. 쉽게 생각하면 운영체제가 개별 스레드의 존재를 인지하고 직접 제어하는 스레드이다.&lt;br /&gt;&lt;br /&gt;커널 수준 스레드는 사용자 수준 스레드, 커널 수준 스레드로 나뉘는데&lt;br /&gt;사용자 수준 스레드는 라이브러리 수준에서 구현되며 커널입장에서는 하나의 프로세스 내에서 단일 스레드만 실행되는 것처럼 보인다. 커널 수준 스레드는 커널이 스레드를 직접 생성하고 관리하며 각 스레드는 커널이 관리하는 독립적인 실행 단위로 간주한다. 주로 Windows, Linux, macOS 등에서 사용되는 스레드 모델이다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;데드락 (Deadlock)이 뭔데?&lt;br /&gt;여러 프로세스 또는 스레드가 서로가 가지고 있는 자원을 기다리면서 영원히 다음 작업을 진행하지 못하고 멈춰 있는 상태를 의미&lt;br /&gt;데드락이 발생하는 4가지 조건은 상호 배제, 점유와 대기, 비선점, 순환 대기 등이 있다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티프로세싱은 하나의 운영체제 안에서 여러 프로세스가 실행되는 것을 의미하지만 자세원 원리를 알아보면 동시에 실행되는 것처럼 보이게 하는 운영체제의 프로세스 스케줄링 방식에 의해 설명될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티프로세싱은 하나의 운영체제 안에서 여러 프로세스가 실행되는 것이고, 멀티스레드는 하나의 프로세스가 여러 작업을 여러 스레드를 사용해 동시에 처리하는 것을 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Operating System</category>
      <category>PCB</category>
      <category>멀티스레딩</category>
      <category>멀티프로세싱</category>
      <category>문맥 교환</category>
      <category>스레드</category>
      <category>오버헤드</category>
      <category>운영체제</category>
      <category>프로세스</category>
      <author>CHun2</author>
      <guid isPermaLink="true">https://chaehun97.tistory.com/75</guid>
      <comments>https://chaehun97.tistory.com/entry/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4%EC%99%80-%EC%8A%A4%EB%A0%88%EB%93%9C#entry75comment</comments>
      <pubDate>Mon, 2 Jun 2025 13:10:18 +0900</pubDate>
    </item>
    <item>
      <title>운영체제 - 커널(Kernul)</title>
      <link>https://chaehun97.tistory.com/entry/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EC%BB%A4%EB%84%90Kernul</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;커널(Kernul)이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;634&quot; data-origin-height=&quot;604&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/coP2rO/btsOhosCeoo/aSmHHLqcb4crg6QciCVm5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/coP2rO/btsOhosCeoo/aSmHHLqcb4crg6QciCVm5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/coP2rO/btsOhosCeoo/aSmHHLqcb4crg6QciCVm5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcoP2rO%2FbtsOhosCeoo%2FaSmHHLqcb4crg6QciCVm5k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;381&quot; data-origin-width=&quot;634&quot; data-origin-height=&quot;604&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커널은 운영체제(OS)의 가장 핵심적이고 근본적인 부분이다. 컴퓨터 시스템의 모든 하드웨어와 소프트웨어 자원을 관리하고 애플리케이션이 이 자원들을 효율적이고 안전하게 사용할 수 있도록 추상화된 서비스를 제공하는 역할을 한다. 커널이 없으면 단순히 하드웨어 덩어리일 뿐이며 사용자가 직접 하드웨어를 제어해야 하는 불편함을 직면하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커널은 하드웨어, 프로세스 및 스레드, 메모리, 파일 시스템, 입출력, 보안등을 제공하며 시스템 콜 인터페이스를 제공한다. 시스템 콜은 사용자 애플리케이션이 커널의 기능을 요청할 수 있는 표준화 방법을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;시스템 콜(System Call)&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;시스템 콜(System Call)이 뭔데?&lt;br /&gt;&lt;br /&gt;시스템 콜(SystemCall)은 커널(kernel)이 제공하는 서비스나 기능에 애플리케이션이 접근하기 위한 인터페이스다. 사용자 프로그램이 운영체제에게 작업을 대신해달라고 요청하는 유일한 통로이다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템 콜은 크게 두 가지 모드로 작동한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자모드(User Mode) : 우리가 사용하는 일반 애플리케이션(웹 브라우저, 게임, 워드 등)이 실행되는 모드이다. 이 모드에서는 하드웨어 자원(CPU, 메모리, 입출력장치)에 직접 접근하는 것이 제한된다. 그 이유는 시스템의 안정성과 보안을 위해서이다. 만약 어떠한 출처도 없는 프로그램을 받아버렸는데 하드웨어를 제어해버려서 컴퓨터를 조져(?!)버릴 수 있기 때문이다.&lt;/li&gt;
&lt;li&gt;커널 모드(Kernel Mode / Privileged Mode) : 운영체제의 커널이 실행되는 모드이다. 이 모드에서는 모든 하드웨어 자원에 대한 접근 권한과 제어권을 가진다. 이 모드는 시스템의 핵심적인 작업을 수행한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;그래서 시스템콜이 왜 필요한가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 일반 사용자 프로그램이 파일 저장, 네트워크 통신, 메모리 할당 등의 작업을 수행해야 할 때이다. 이런 작업들은 하드웨어에 직접 접근해야 하는 경우가 많고, 이는 커널&amp;nbsp; 모드에서만 가능하다. 이럴 때 사용자 프로그램은 커널에게 대신해달라고 요청해야 하는데, 이때 사용하는 것이 시스템 콜이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;시스템 호출 과정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 사용자가 프로그램에서 특정한 OS 서비스(파일 열기, 메모리 할당, 프로세스 생성)를 요청하는 시스템 함수를 호출한ㄷ.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. CPU는 모드를 사용자 모드에서 커널 모드로 전환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 커널은 요청된 시스템 호출을 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 처리 완료 후, CPU는 다시 사용자 모드로 전환되어 사용자 프로그램의 다음 명령어를 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;커널의 종류&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커널은 구현 방식과 구조에 따라 여러 종류이다. 각각은 장단점이 있고, 특정 시스템의 요구사항에 맞춰 선택된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모놀리식 커널 (Monolithic Kernel)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;운영체제의 모든 핵심 서비스(프로세스 관리, 메모리 관리, 파일 시스템, 장치 드라이버 등)가 하나의 큰 모듈로 통합되어 커널 공간에서 실행된다.&lt;/li&gt;
&lt;li&gt;장점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 서비스가 동일한 주소 공간에 있어 서비스 간 통신이 매우 빠르다.(시스템 호출 오버헤드가 적음)&lt;/li&gt;
&lt;li&gt;구현이 비교적 간단하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;단점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;커널의 크기가 매우 커진다.&lt;/li&gt;
&lt;li&gt;하나의 모듈에서 오류가 발생하면 전체 시스템이 중단될 수 있다.&lt;/li&gt;
&lt;li&gt;새로운 기능을 추가하거나 수정하기 어렵고 커널 전체를 다시 컴파일해야 할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Linux, 초기의 Unix, Windows가 해당한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;마이크로 커널 (Microkernel)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가장 기본적인 서비스(프로세스 통신, 메모리 관리, 스케줄링)만 커널 공간에 두고, 나머지 서비스(파일 시스템, 장치 드라이버, 네트워크 등)는 사용자 공간의 서버 프로세스로 분리하여 실행된다.&lt;/li&gt;
&lt;li&gt;장점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;커널이 크기가 매우 작고 간결하다.&lt;/li&gt;
&lt;li&gt;각 서비스가 독립적인 사용자 프로세스로 실행되므로 안정성이 높다. (한 서버에 오류가 발생해도 전체 시스템에 영향을 주지 않음)&lt;/li&gt;
&lt;li&gt;새로운 기능을 추가하거나 수정하기 쉽다.&lt;/li&gt;
&lt;li&gt;분산 시스템 환경에 적합핟.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;단점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서비스들이 서로 다른 주소 공간에 있으므로 IPC에 시스템 호출 오버헤드가 발생하여 성능이 저하될 수 있다.&lt;/li&gt;
&lt;li&gt;구현이 더 복잡하다.&lt;/li&gt;
&lt;li&gt;Mach (macOS 기반), QNX, Minix&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;하이브리드 커널 (Hybird Kernel / Modular Kernel)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모놀리식 커널과 마이크로 커널의 장점을 결합한 형태이다. 핵심 서비스는 커널 공간에 포함되지만 모듈화되어 필요에 따라 동적으로 로드/언로드 할 수 있다. 일부 서비스는 사용자 공간에서 실행될 수 있다.&lt;/li&gt;
&lt;li&gt;장점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;성능과 안정성 사이의 균형을 추구하는 형식&lt;/li&gt;
&lt;li&gt;필요한 기능만 커널에 로드하므로 유연성이 높다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;단점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모놀리식이나 마이크로 커널만큼 명확한 구조성이 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Windows NT 커널, Linux(최신 버전은 모듈을 동적으로 로드하는 등 하이브리적 특성을 가짐)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;엑소커널 (Exo Kernel)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;매우 최소한의 기능(자원 할당, 보호)만 커널에 두고, 하드웨어 자원에 대한 낮은 수준의 접근 권한을 사용자 애플리케이션에게 직접적으로 위임한다. 애플리케이션이 자신의 자원 관리 정책을 직접 구현할 수 있도록 한다.&lt;/li&gt;
&lt;li&gt;장점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;최고의 성능 최적화 가능성이 있다.(애플리케이션이 자신의 필요에 따라 자원을 관리하므로 오히려 최적화가 안될 수도)&lt;/li&gt;
&lt;li&gt;매우 유연하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;단점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;애플리케이션 개발이 매우 복잡해진다&lt;/li&gt;
&lt;li&gt;범용 운영체제로는 거의 사용되지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;MIT Exokernul&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커널은 컴퓨터 시스템의 중앙 통제실과 같다. 사용자가 프로그램을 실행, 저장, 웹브라우저 사용등 모든 활동은 커널이 뒤에서 하드웨어와 소통하며 자원을 조정하고 배분하기 때문에 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커널의 효율성과 안정성은 전체 시스템의 성능과 신뢰성에 직결된다.&lt;/p&gt;</description>
      <author>CHun2</author>
      <guid isPermaLink="true">https://chaehun97.tistory.com/74</guid>
      <comments>https://chaehun97.tistory.com/entry/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EC%BB%A4%EB%84%90Kernul#entry74comment</comments>
      <pubDate>Fri, 30 May 2025 11:21:59 +0900</pubDate>
    </item>
  </channel>
</rss>