<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>otopligrm 님의 블로그</title>
    <link>https://otopligrm.tistory.com/</link>
    <description>클라우드 엔지니어를 꿈꾸는 대학생입니다. 공부 내용을 블로그 형식으로 올리고 있습니다.  </description>
    <language>ko</language>
    <pubDate>Wed, 27 May 2026 13:01:41 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>otopligrm</managingEditor>
    <image>
      <title>otopligrm 님의 블로그</title>
      <url>https://tistory1.daumcdn.net/tistory/8152776/attach/29ec05f263794d7f97deeaed7674c8c8</url>
      <link>https://otopligrm.tistory.com</link>
    </image>
    <item>
      <title>VDI 시험 감독 대시보드 프론트/백엔드/DB 인수인계 문서</title>
      <link>https://otopligrm.tistory.com/45</link>
      <description>&lt;h2 data-end=&quot;175&quot; data-start=&quot;162&quot; data-section-id=&quot;97ur0u&quot; data-ke-size=&quot;size26&quot;&gt;1. 이원준 인수인계 by GPT와 함께 만듬&lt;/h2&gt;
&lt;p data-end=&quot;247&quot; data-start=&quot;177&quot; data-ke-size=&quot;size16&quot;&gt;현재까지 작업한 내용은 &lt;b&gt;VDI 시험 감독 대시보드를 실제 DB와 연결하기 위한 프론트엔드/백엔드 API 개발 작업&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-end=&quot;409&quot; data-start=&quot;249&quot; data-ke-size=&quot;size16&quot;&gt;하지만 실제 운영에서는 학생 로그인 상태, 좌석 상태, 시험 정보, 교수 정보가 모두 DB에 저장되어야 하기 때문에 백엔드 API와 DB를 연결하는 구조로 전환했다.&lt;/p&gt;
&lt;p data-end=&quot;429&quot; data-start=&quot;411&quot; data-ke-size=&quot;size16&quot;&gt;이번 인수인계의 핵심은 다음이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1. 기존 프론트 화면이 어떤 데이터를 필요로 하는지
2. 그 데이터를 백엔드 API가 어떤 형식으로 내려주는지
3. 백엔드 API가 DB의 어떤 테이블을 조회/수정하는지
4. 학생이 VDI에 접속해서 로그인했을 때 좌석 상태가 어떻게 바뀌는지
5. 대시보드가 어떻게 미접속/접속중 상태를 표시하는지
6. 앞으로 DB관리자가 어떤 부분을 관리해야 하는지&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;645&quot; data-start=&quot;642&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h1 data-end=&quot;660&quot; data-start=&quot;647&quot; data-section-id=&quot;13c8p3z&quot;&gt;2. 전체 구조 요약&lt;/h1&gt;
&lt;p data-end=&quot;682&quot; data-start=&quot;662&quot; data-ke-size=&quot;size16&quot;&gt;현재 구조는 다음 흐름으로 동작한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;[교수 정보 입력 페이지]
        &amp;darr;
교수명 / 교번 / 학과 입력
        &amp;darr;
백엔드 API 호출
        &amp;darr;
ACCOUNT / PROFESSOR_PROFILE 저장 또는 조회

[시험 생성 페이지]
        &amp;darr;
과목명 / 시험 유형 / 장소 / 시간 / 정원 입력
        &amp;darr;
백엔드 API 호출
        &amp;darr;
EXAM_INFO 생성
        &amp;darr;
VDI 이름 / IP / 좌석 매핑 테이블 조회
        &amp;darr;
VDI_SESSION 초기 생성
        &amp;darr;
초기 상태는 OUT, 즉 미접속

[학생 VDI 로그인 페이지]
        &amp;darr;
학생이 VDI 접속
        &amp;darr;
자동으로 로그인 페이지 실행
        &amp;darr;
학번 / 이름 입력
        &amp;darr;
백엔드 로그인 API 호출
        &amp;darr;
백엔드가 요청 IP 또는 VDI 식별값으로 좌석 확인
        &amp;darr;
VDI_SESSION 상태 ACTIVE로 변경

[교수자 대시보드]
        &amp;darr;
5초마다 세션 API 호출
        &amp;darr;
VDI_SESSION 목록 조회
        &amp;darr;
OUT이면 회색
ACTIVE면 초록색
AI_DETECTED면 빨간색
RECONNECTED면 노란색&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;1317&quot; data-start=&quot;1314&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h1 data-end=&quot;1336&quot; data-start=&quot;1319&quot; data-section-id=&quot;1f25666&quot;&gt;3. 이번 작업의 핵심 개념&lt;/h1&gt;
&lt;h2 data-end=&quot;1375&quot; data-start=&quot;1338&quot; data-section-id=&quot;1p5i9ei&quot; data-ke-size=&quot;size26&quot;&gt;3-1. 프론트의 임시 데이터 구조를 DB 기반 구조로 바꿨다&lt;/h2&gt;
&lt;p data-end=&quot;1411&quot; data-start=&quot;1377&quot; data-ke-size=&quot;size16&quot;&gt;초기 프론트에서는 다음 localStorage 키를 사용했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;vdi_professor_profile
vdi_exam_info
vdi_sessions
exam_id&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1520&quot; data-start=&quot;1483&quot; data-ke-size=&quot;size16&quot;&gt;이 구조는 화면 확인용으로는 충분하지만 실제 시스템에서는 부족하다.&lt;/p&gt;
&lt;p data-end=&quot;1533&quot; data-start=&quot;1522&quot; data-ke-size=&quot;size16&quot;&gt;이유는 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1. 브라우저마다 데이터가 따로 저장된다.
2. 새 PC나 다른 브라우저에서는 데이터를 공유할 수 없다.
3. 학생이 로그인해도 교수자 대시보드에 실시간 반영되지 않는다.
4. 시험 종료 후 기록을 남길 수 없다.
5. 최종 보고서와 연결할 수 없다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1727&quot; data-start=&quot;1689&quot; data-ke-size=&quot;size16&quot;&gt;그래서 이 구조를 DB 기반으로 바꾸는 것이 이번 작업의 핵심이었다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;vdi_professor_profile  &amp;rarr; ACCOUNT / PROFESSOR_PROFILE
vdi_exam_info          &amp;rarr; EXAM_INFO
vdi_sessions           &amp;rarr; VDI_SESSION
VDI 이름/IP/좌석 정보   &amp;rarr; VDI 매핑 테이블&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;1901&quot; data-start=&quot;1898&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h1 data-end=&quot;1918&quot; data-start=&quot;1903&quot; data-section-id=&quot;17gzl6c&quot;&gt;4. 프론트엔드 인수인계&lt;/h1&gt;
&lt;h2 data-end=&quot;1933&quot; data-start=&quot;1920&quot; data-section-id=&quot;1ijjs97&quot; data-ke-size=&quot;size26&quot;&gt;4-1. 주요 화면&lt;/h2&gt;
&lt;p data-end=&quot;1958&quot; data-start=&quot;1935&quot; data-ke-size=&quot;size16&quot;&gt;현재 프론트는 크게 다음 화면으로 나뉜다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1. 교수 정보 입력 페이지
2. 시험 생성 페이지
3. 학생 로그인 페이지
4. 교수자 대시보드 페이지&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2064&quot; data-start=&quot;2032&quot; data-ke-size=&quot;size16&quot;&gt;일반적인 nginx 배포 기준 경로는 다음과 같이 잡았다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;/usr/share/nginx/html/professor/
/usr/share/nginx/html/exam/
/usr/share/nginx/html/login/
/usr/share/nginx/html/home/&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2229&quot; data-start=&quot;2197&quot; data-ke-size=&quot;size16&quot;&gt;실제 서버에서 정확한 경로는 다음 명령어로 확인하면 된다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;grep -R &quot;root&quot; /etc/nginx/nginx.conf /etc/nginx/conf.d/*.conf
ls -al /usr/share/nginx/html&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;2338&quot; data-start=&quot;2335&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;2360&quot; data-start=&quot;2340&quot; data-section-id=&quot;igbgkl&quot; data-ke-size=&quot;size26&quot;&gt;4-2. 교수 정보 입력 페이지&lt;/h2&gt;
&lt;p data-end=&quot;2388&quot; data-start=&quot;2362&quot; data-ke-size=&quot;size16&quot;&gt;교수 정보 입력 페이지에서는 다음 값을 받는다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;교수명
교번 또는 교수 식별번호
학과&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2451&quot; data-start=&quot;2424&quot; data-ke-size=&quot;size16&quot;&gt;기존 프론트에서는 이 값을 다음 객체로 만들었다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;const professorProfile = {
  username: professorNo,
  role: &quot;professor&quot;,
  name: professorName,
  department,
  createdAt: new Date().toISOString(),
};&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2644&quot; data-start=&quot;2616&quot; data-ke-size=&quot;size16&quot;&gt;기존에는 이것을 localStorage에 저장했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;localStorage.setItem(&quot;vdi_professor_profile&quot;, JSON.stringify(professorProfile));&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2767&quot; data-start=&quot;2738&quot; data-ke-size=&quot;size16&quot;&gt;하지만 실제 운영에서는 백엔드 API로 보내야 한다.&lt;/p&gt;
&lt;p data-end=&quot;2784&quot; data-start=&quot;2769&quot; data-ke-size=&quot;size16&quot;&gt;권장 API는 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;POST /api/professor/setup&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2831&quot; data-start=&quot;2825&quot; data-ke-size=&quot;size16&quot;&gt;요청 예시:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;professor_name&quot;: &quot;홍길동&quot;,
  &quot;professor_no&quot;: &quot;P2025001&quot;,
  &quot;department&quot;: &quot;컴퓨터공학과&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2958&quot; data-start=&quot;2932&quot; data-ke-size=&quot;size16&quot;&gt;백엔드에서는 이 값을 DB에 다음처럼 연결한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;professor_no   &amp;rarr; ACCOUNT.username
role           &amp;rarr; ACCOUNT.role = professor
professor_name &amp;rarr; PROFESSOR_PROFILE.name
department     &amp;rarr; PROFESSOR_PROFILE.department&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3202&quot; data-start=&quot;3135&quot; data-ke-size=&quot;size16&quot;&gt;DB관리자는 이 부분에서 &lt;b&gt;교수 식별번호가 ACCOUNT.username으로 들어가는 구조&lt;/b&gt;를 반드시 기억해야 한다.&lt;/p&gt;
&lt;hr data-end=&quot;3207&quot; data-start=&quot;3204&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;3226&quot; data-start=&quot;3209&quot; data-section-id=&quot;cysxxh&quot; data-ke-size=&quot;size26&quot;&gt;4-3. 시험 생성 페이지&lt;/h2&gt;
&lt;p data-end=&quot;3253&quot; data-start=&quot;3228&quot; data-ke-size=&quot;size16&quot;&gt;시험 생성 페이지에서는 다음 값을 입력받는다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;과목명
시험 장소
정원
시험 시작 시간
시험 종료 시간
시험 유형&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3327&quot; data-start=&quot;3305&quot; data-ke-size=&quot;size16&quot;&gt;기존 프론트 객체는 대략 다음 구조였다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;const examInfo = {
  exam_id: 1,
  subject_name: subjectName,
  exam_type: examType,
  scheduled_at: startAt,
  ended_at: endAt,
  total_target_students: capacity,
  place: examPlace,
  createdAt: new Date().toISOString(),
};&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3597&quot; data-start=&quot;3566&quot; data-ke-size=&quot;size16&quot;&gt;이 값은 DB의 EXAM_INFO 테이블과 연결된다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;subject_name           &amp;rarr; EXAM_INFO.subject_name
exam_type              &amp;rarr; EXAM_INFO.exam_type
scheduled_at           &amp;rarr; EXAM_INFO.scheduled_at
ended_at               &amp;rarr; EXAM_INFO.ended_at
total_target_students  &amp;rarr; EXAM_INFO.total_target_students
place                  &amp;rarr; EXAM_INFO.place&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3910&quot; data-start=&quot;3895&quot; data-ke-size=&quot;size16&quot;&gt;권장 API는 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;POST /api/exam&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3946&quot; data-start=&quot;3940&quot; data-ke-size=&quot;size16&quot;&gt;요청 예시:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;professor_username&quot;: &quot;P2025001&quot;,
  &quot;subject_name&quot;: &quot;데이터베이스&quot;,
  &quot;exam_type&quot;: &quot;중간고사&quot;,
  &quot;place&quot;: &quot;공학관 301호&quot;,
  &quot;scheduled_at&quot;: &quot;2026-05-10T13:00:00&quot;,
  &quot;ended_at&quot;: &quot;2026-05-10T15:00:00&quot;,
  &quot;total_target_students&quot;: 40
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4227&quot; data-start=&quot;4183&quot; data-ke-size=&quot;size16&quot;&gt;이 API가 해야 하는 일은 단순히 EXAM_INFO만 만드는 것이 아니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1. EXAM_INFO에 시험 정보 저장
2. 생성된 exam_id 확보
3. DB에 있는 VDI 이름/IP/좌석 매핑 조회
4. 정원 수만큼 VDI_SESSION 초기 생성
5. 모든 좌석 상태를 OUT으로 설정&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4403&quot; data-start=&quot;4362&quot; data-ke-size=&quot;size16&quot;&gt;즉, 시험 생성 시점에 대시보드에 표시할 좌석 세션도 같이 만들어야 한다.&lt;/p&gt;
&lt;hr data-end=&quot;4408&quot; data-start=&quot;4405&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;4426&quot; data-start=&quot;4410&quot; data-section-id=&quot;vk6lry&quot; data-ke-size=&quot;size26&quot;&gt;4-4. 대시보드 페이지&lt;/h2&gt;
&lt;p data-end=&quot;4449&quot; data-start=&quot;4428&quot; data-ke-size=&quot;size16&quot;&gt;대시보드는 다음 데이터를 필요로 한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;시험 정보
교수 정보
전체 좌석 수
접속 중 좌석 수
미접속 좌석 수
AI 탐지 좌석 수
재접속 좌석 수
좌석별 상태
학생 학번
학생 이름
VDI 이름
VDI IP
최근 이벤트(보류)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4594&quot; data-start=&quot;4563&quot; data-ke-size=&quot;size16&quot;&gt;대시보드에서 핵심적으로 사용하는 API는 다음 세 개다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;GET /api/exam/latest
GET /api/admin/exam?exam_id=1
GET /api/admin/sessions?exam_id=1&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-end=&quot;4716&quot; data-start=&quot;4694&quot; data-section-id=&quot;6obrzv&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;/api/exam/latest&lt;/span&gt;&lt;/h3&gt;
&lt;p data-end=&quot;4758&quot; data-start=&quot;4718&quot; data-ke-size=&quot;size16&quot;&gt;대시보드에 exam_id가 없을 때 최신 시험을 찾기 위해 사용한다.&lt;/p&gt;
&lt;p data-end=&quot;4787&quot; data-start=&quot;4760&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 사용자가 다음 주소로 들어왔다고 하자.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;/home/&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4839&quot; data-start=&quot;4809&quot; data-ke-size=&quot;size16&quot;&gt;이 경우 프론트는 어떤 시험을 보여줘야 하는지 모른다.&lt;/p&gt;
&lt;p data-end=&quot;4860&quot; data-start=&quot;4841&quot; data-ke-size=&quot;size16&quot;&gt;그래서 먼저 최신 시험을 조회한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;GET /api/exam/latest&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4902&quot; data-start=&quot;4896&quot; data-ke-size=&quot;size16&quot;&gt;응답 예시:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;success&quot;: true,
  &quot;exam&quot;: {
    &quot;exam_id&quot;: 1,
    &quot;subject_name&quot;: &quot;데이터베이스&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;5037&quot; data-start=&quot;5003&quot; data-ke-size=&quot;size16&quot;&gt;프론트는 이 응답을 받은 뒤 URL을 다음처럼 바꿀 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;/home/?exam_id=1&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;5072&quot; data-start=&quot;5069&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;5105&quot; data-start=&quot;5074&quot; data-section-id=&quot;tkqcsf&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;/api/admin/exam?exam_id=1&lt;/span&gt;&lt;/h3&gt;
&lt;p data-end=&quot;5136&quot; data-start=&quot;5107&quot; data-ke-size=&quot;size16&quot;&gt;대시보드 상단의 시험 정보를 표시하기 위해 사용한다.&lt;/p&gt;
&lt;p data-end=&quot;5144&quot; data-start=&quot;5138&quot; data-ke-size=&quot;size16&quot;&gt;응답 예시:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;success&quot;: true,
  &quot;exam&quot;: {
    &quot;exam_id&quot;: 1,
    &quot;subject_name&quot;: &quot;데이터베이스&quot;,
    &quot;exam_type&quot;: &quot;중간고사&quot;,
    &quot;professor_name&quot;: &quot;홍길동&quot;,
    &quot;department&quot;: &quot;컴퓨터공학과&quot;,
    &quot;scheduled_at&quot;: &quot;2026-05-10 13:00:00&quot;,
    &quot;ended_at&quot;: &quot;2026-05-10 15:00:00&quot;,
    &quot;place&quot;: &quot;공학관 301호&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;5474&quot; data-start=&quot;5434&quot; data-ke-size=&quot;size16&quot;&gt;이 API는 DB에서 EXAM_INFO만 조회해서는 부족할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;5514&quot; data-start=&quot;5476&quot; data-ke-size=&quot;size16&quot;&gt;교수명과 학과를 표시하려면 다음 테이블과 JOIN이 필요할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;EXAM_INFO
ACCOUNT
PROFESSOR_PROFILE&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;5600&quot; data-start=&quot;5565&quot; data-ke-size=&quot;size16&quot;&gt;DB관리자는 실제 스키마에 맞춰 JOIN 관계를 확인해야 한다.&lt;/p&gt;
&lt;hr data-end=&quot;5605&quot; data-start=&quot;5602&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;5642&quot; data-start=&quot;5607&quot; data-section-id=&quot;115ka2r&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;/api/admin/sessions?exam_id=1&lt;/span&gt;&lt;/h3&gt;
&lt;p data-end=&quot;5662&quot; data-start=&quot;5644&quot; data-ke-size=&quot;size16&quot;&gt;대시보드의 가장 중요한 API다.&lt;/p&gt;
&lt;p data-end=&quot;5694&quot; data-start=&quot;5664&quot; data-ke-size=&quot;size16&quot;&gt;이 API 응답을 기준으로 다음 화면이 모두 갱신된다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;KPI 카드
좌석 맵
응시자 현황 테이블
실시간 이벤트 피드
상태 분포
보고서 요약&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;5762&quot; data-start=&quot;5756&quot; data-ke-size=&quot;size16&quot;&gt;응답 예시:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;success&quot;: true,
  &quot;sessions&quot;: [
    {
      &quot;session_id&quot;: 1,
      &quot;seat_no&quot;: &quot;A-01&quot;,
      &quot;vdi_name&quot;: &quot;vdi-01&quot;,
      &quot;vdi_ip&quot;: &quot;192.168.122.101&quot;,
      &quot;student_no&quot;: &quot;20231042&quot;,
      &quot;student_name&quot;: &quot;김OO&quot;,
      &quot;state_name&quot;: &quot;ACTIVE&quot;,
      &quot;started_at&quot;: &quot;2026-05-10 13:01:00&quot;,
      &quot;last_heartbeat&quot;: &quot;2026-05-10 13:05:10&quot;,
      &quot;reconnect_count&quot;: 0
    },
    {
      &quot;session_id&quot;: 2,
      &quot;seat_no&quot;: &quot;A-02&quot;,
      &quot;vdi_name&quot;: &quot;vdi-02&quot;,
      &quot;vdi_ip&quot;: &quot;192.168.122.102&quot;,
      &quot;student_no&quot;: null,
      &quot;student_name&quot;: null,
      &quot;state_name&quot;: &quot;OUT&quot;,
      &quot;started_at&quot;: null,
      &quot;last_heartbeat&quot;: null,
      &quot;reconnect_count&quot;: 0
    }
  ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;6492&quot; data-start=&quot;6439&quot; data-ke-size=&quot;size16&quot;&gt;대시보드는 이 sessions 배열만 정확히 받으면 대부분의 화면을 자동으로 그릴 수 있다.&lt;/p&gt;
&lt;hr data-end=&quot;6497&quot; data-start=&quot;6494&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h1 data-end=&quot;6515&quot; data-start=&quot;6499&quot; data-section-id=&quot;fczmeb&quot;&gt;5. 대시보드 상태값 규칙&lt;/h1&gt;
&lt;p data-end=&quot;6548&quot; data-start=&quot;6517&quot; data-ke-size=&quot;size16&quot;&gt;프론트에서 사용하는 상태값은 다음 기준으로 맞춰야 한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;OUT          &amp;rarr; 미접속, 회색
ACTIVE       &amp;rarr; 접속중/정상, 초록색
RECONNECTED  &amp;rarr; 재접속, 노란색
WARNING      &amp;rarr; 주의, 노란색
CAUTION      &amp;rarr; 주의, 노란색
AI_DETECTED  &amp;rarr; AI 탐지, 빨간색
BLOCKED      &amp;rarr; 차단, 빨간색

이거는 GPT랑 만들다보니 너무 많아져서 원준이 너가 대시보드 보고 간추려야 해&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;6767&quot; data-start=&quot;6732&quot; data-ke-size=&quot;size16&quot;&gt;DB에 저장된 값이 다르다면 백엔드에서 변환해서 내려줘야 한다.&lt;/p&gt;
&lt;p data-end=&quot;6796&quot; data-start=&quot;6769&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 DB에는 숫자 상태값이 있을 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;1 &amp;rarr; OUT
2 &amp;rarr; ACTIVE
3 &amp;rarr; AI_DETECTED
4 &amp;rarr; RECONNECTED
5 &amp;rarr; BLOCKED&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;6920&quot; data-start=&quot;6874&quot; data-ke-size=&quot;size16&quot;&gt;이 경우 API 응답에서는 프론트가 이해할 수 있도록 문자열로 변환하는 것이 좋다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;state_name&quot;: &quot;ACTIVE&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;7008&quot; data-start=&quot;6964&quot; data-ke-size=&quot;size16&quot;&gt;프론트와 백엔드가 상태값을 다르게 쓰면 좌석 색상이 정상적으로 표시되지 않는다.&lt;/p&gt;
&lt;hr data-end=&quot;7013&quot; data-start=&quot;7010&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h1 data-end=&quot;7035&quot; data-start=&quot;7015&quot; data-section-id=&quot;s7x5me&quot;&gt;6. 학생 로그인 페이지 인수인계&lt;/h1&gt;
&lt;p data-end=&quot;7070&quot; data-start=&quot;7037&quot; data-ke-size=&quot;size16&quot;&gt;학생은 우리가 제공한 VDI 접속 정보로 VDI에 접속한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;VDI 접속 IP
VDI 접속 포트
VDI 비밀번호&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;7170&quot; data-start=&quot;7114&quot; data-ke-size=&quot;size16&quot;&gt;VDI에 접속하면 Windows 시작 프로그램 또는 스케줄러를 통해 자동으로 로그인 페이지가 열린다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;http://서버주소/login/&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;7230&quot; data-start=&quot;7204&quot; data-ke-size=&quot;size16&quot;&gt;학생은 로그인 페이지에서 다음 정보를 입력한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;학번
이름&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;7273&quot; data-start=&quot;7251&quot; data-ke-size=&quot;size16&quot;&gt;그리고 백엔드 로그인 API를 호출한다.&lt;/p&gt;
&lt;p data-end=&quot;7299&quot; data-start=&quot;7275&quot; data-ke-size=&quot;size16&quot;&gt;현재 논의에서 이름이 조금 혼재되어 있었다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;POST /api/vdi/login&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;7341&quot; data-start=&quot;7334&quot; data-ke-size=&quot;size16&quot;&gt;또는 개념상:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;POST /api/session/login&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;7480&quot; data-start=&quot;7380&quot; data-ke-size=&quot;size16&quot;&gt;실제 코드에 이미 /api/vdi/login으로 구현되어 있다면 그걸 유지해도 된다.&lt;br /&gt;다만 인수인계 후에는 DB관리자가 &lt;b&gt;엔드포인트 이름을 하나로 통일&lt;/b&gt;하는 것이 좋다.&lt;/p&gt;
&lt;p data-end=&quot;7491&quot; data-start=&quot;7482&quot; data-ke-size=&quot;size16&quot;&gt;권장 요청 예시:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;student_no&quot;: &quot;20231042&quot;,
  &quot;student_name&quot;: &quot;김OO&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;7568&quot; data-start=&quot;7562&quot; data-ke-size=&quot;size16&quot;&gt;응답 예시:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;success&quot;: true,
  &quot;message&quot;: &quot;로그인되었습니다.&quot;,
  &quot;session&quot;: {
    &quot;session_id&quot;: 1,
    &quot;seat_no&quot;: &quot;A-01&quot;,
    &quot;vdi_name&quot;: &quot;vdi-01&quot;,
    &quot;vdi_ip&quot;: &quot;192.168.122.101&quot;,
    &quot;student_no&quot;: &quot;20231042&quot;,
    &quot;student_name&quot;: &quot;김OO&quot;,
    &quot;state_name&quot;: &quot;ACTIVE&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;7841&quot; data-start=&quot;7838&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h1 data-end=&quot;7878&quot; data-start=&quot;7843&quot; data-section-id=&quot;1me21ja&quot;&gt;7. 학생마다 로그인 URL을 다르게 만들 필요가 없는 이유&lt;/h1&gt;
&lt;p data-end=&quot;7894&quot; data-start=&quot;7880&quot; data-ke-size=&quot;size16&quot;&gt;중요한 인수인계 포인트다.&lt;/p&gt;
&lt;p data-end=&quot;7921&quot; data-start=&quot;7896&quot; data-ke-size=&quot;size16&quot;&gt;현재 DB에는 이미 다음 매핑이 있다고 했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;VDI 이름 &amp;harr; VDI IP &amp;harr; 좌석 이름&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;7973&quot; data-start=&quot;7960&quot; data-ke-size=&quot;size16&quot;&gt;예를 들면 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;vdi-01 &amp;harr; 192.168.122.101 &amp;harr; A-01
vdi-02 &amp;harr; 192.168.122.102 &amp;harr; A-02
vdi-03 &amp;harr; 192.168.122.103 &amp;harr; A-03&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;8120&quot; data-start=&quot;8084&quot; data-ke-size=&quot;size16&quot;&gt;따라서 학생마다 로그인 브라우저 주소를 다르게 만들 필요는 없다.&lt;/p&gt;
&lt;p data-end=&quot;8150&quot; data-start=&quot;8122&quot; data-ke-size=&quot;size16&quot;&gt;모든 VDI에서 같은 로그인 페이지를 열어도 된다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;http://서버주소/login/&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;8216&quot; data-start=&quot;8184&quot; data-ke-size=&quot;size16&quot;&gt;대신 백엔드가 로그인 요청을 받았을 때 다음을 해야 한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;1. 요청이 들어온 클라이언트 IP 확인
2. DB의 VDI 이름/IP/좌석 매핑 테이블 조회
3. 해당 IP가 어느 VDI인지 확인
4. 해당 VDI가 어느 좌석인지 확인
5. 해당 좌석의 VDI_SESSION 업데이트&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;8396&quot; data-start=&quot;8355&quot; data-ke-size=&quot;size16&quot;&gt;즉, 핵심은 URL이 아니라 &lt;b&gt;백엔드가 VDI를 식별할 수 있느냐&lt;/b&gt;다.&lt;/p&gt;
&lt;hr data-end=&quot;8401&quot; data-start=&quot;8398&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h1 data-end=&quot;8449&quot; data-start=&quot;8403&quot; data-section-id=&quot;1rn8hzn&quot;&gt;8. 주의할 점: nginx 프록시를 쓰면 실제 VDI IP가 안 보일 수 있음&lt;/h1&gt;
&lt;p data-end=&quot;8477&quot; data-start=&quot;8451&quot; data-ke-size=&quot;size16&quot;&gt;이 부분은 DB관리자에게 반드시 전달해야 한다.&lt;/p&gt;
&lt;p data-end=&quot;8556&quot; data-start=&quot;8479&quot; data-ke-size=&quot;size16&quot;&gt;학생 브라우저가 직접 FastAPI 서버로 요청하면 FastAPI에서 request.client.host로 VDI IP를 볼 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;8581&quot; data-start=&quot;8558&quot; data-ke-size=&quot;size16&quot;&gt;하지만 보통 운영에서는 다음 구조를 쓴다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;학생 VDI 브라우저
  &amp;darr;
nginx
  &amp;darr;
FastAPI / uvicorn&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;8732&quot; data-start=&quot;8640&quot; data-ke-size=&quot;size16&quot;&gt;이 경우 FastAPI에서 그냥 request.client.host를 보면 실제 VDI IP가 아니라 127.0.0.1 또는 nginx IP로 보일 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;8766&quot; data-start=&quot;8734&quot; data-ke-size=&quot;size16&quot;&gt;그러면 백엔드는 어떤 VDI에서 로그인했는지 알 수 없다.&lt;/p&gt;
&lt;p data-end=&quot;8812&quot; data-start=&quot;8768&quot; data-ke-size=&quot;size16&quot;&gt;이 문제를 해결하려면 nginx에서 실제 클라이언트 IP를 헤더로 넘겨야 한다.&lt;/p&gt;
&lt;p data-end=&quot;8834&quot; data-start=&quot;8814&quot; data-ke-size=&quot;size16&quot;&gt;nginx 설정 예시는 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;location /api/ {
    proxy_pass http://127.0.0.1:8000/api/;

    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;9137&quot; data-start=&quot;9104&quot; data-ke-size=&quot;size16&quot;&gt;FastAPI에서는 다음 순서로 IP를 확인하는 것이 좋다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;def get_client_ip(request):
    x_real_ip = request.headers.get(&quot;x-real-ip&quot;)
    if x_real_ip:
        return x_real_ip.strip()

    x_forwarded_for = request.headers.get(&quot;x-forwarded-for&quot;)
    if x_forwarded_for:
        return x_forwarded_for.split(&quot;,&quot;)[0].strip()

    return request.client.host&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;9536&quot; data-start=&quot;9453&quot; data-ke-size=&quot;size16&quot;&gt;단, X-Forwarded-For는 외부에서 조작될 수 있으므로, 운영 환경에서는 nginx가 신뢰할 수 있는 내부 프록시일 때만 사용해야 한다.&lt;/p&gt;
&lt;hr data-end=&quot;9541&quot; data-start=&quot;9538&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h1 data-end=&quot;9556&quot; data-start=&quot;9543&quot; data-section-id=&quot;1mxwp84&quot;&gt;9. 백엔드 인수인계&lt;/h1&gt;
&lt;h2 data-end=&quot;9575&quot; data-start=&quot;9558&quot; data-section-id=&quot;nmymou&quot; data-ke-size=&quot;size26&quot;&gt;9-1. 백엔드 기술 스택&lt;/h2&gt;
&lt;p data-end=&quot;9614&quot; data-start=&quot;9577&quot; data-ke-size=&quot;size16&quot;&gt;현재 백엔드는 FastAPI 기반으로 구성한 것으로 정리하면 된다.&lt;/p&gt;
&lt;p data-end=&quot;9631&quot; data-start=&quot;9616&quot; data-ke-size=&quot;size16&quot;&gt;사용 패키지는 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;fastapi
uvicorn
pymysql
python-dotenv&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;9690&quot; data-start=&quot;9684&quot; data-ke-size=&quot;size16&quot;&gt;설치 예시:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;cd /opt/vdi-login-api

python3 -m venv venv
source venv/bin/activate

pip install fastapi uvicorn pymysql python-dotenv&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;9828&quot; data-start=&quot;9825&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;9846&quot; data-start=&quot;9830&quot; data-section-id=&quot;1phkpu1&quot; data-ke-size=&quot;size26&quot;&gt;9-2. 백엔드 환경변수&lt;/h2&gt;
&lt;p data-end=&quot;9884&quot; data-start=&quot;9848&quot; data-ke-size=&quot;size16&quot;&gt;DB 접속 정보는 코드에 직접 쓰지 말고 .env에 관리한다.&lt;/p&gt;
&lt;p data-end=&quot;9889&quot; data-start=&quot;9886&quot; data-ke-size=&quot;size16&quot;&gt;예시:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;DB_HOST=127.0.0.1
DB_PORT=3306
DB_USER=DB_USER_NAME
DB_PASSWORD=DB_PASSWORD
DB_NAME=capston_db

STATE_OUT_ID=1
STATE_ACTIVE_ID=2
STATE_AI_DETECTED_ID=3
STATE_RECONNECTED_ID=4
STATE_BLOCKED_ID=5&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;10129&quot; data-start=&quot;10097&quot; data-ke-size=&quot;size16&quot;&gt;인수인계 시 실제 비밀번호는 공개 문서에 넣으면 안 된다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;주의:
DB 계정
DB 비밀번호
서버 공인 IP
SSH 계정
VDI 접속 비밀번호
실제 학생 학번/이름&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;10229&quot; data-start=&quot;10202&quot; data-ke-size=&quot;size16&quot;&gt;위 정보는 블로그나 공개 문서에 올리면 안 된다.&lt;/p&gt;
&lt;hr data-end=&quot;10234&quot; data-start=&quot;10231&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;10255&quot; data-start=&quot;10236&quot; data-section-id=&quot;rwetf5&quot; data-ke-size=&quot;size26&quot;&gt;9-3. systemd 서비스&lt;/h2&gt;
&lt;p data-end=&quot;10292&quot; data-start=&quot;10257&quot; data-ke-size=&quot;size16&quot;&gt;이전 작업에서 FastAPI를 systemd 서비스로 등록했다.&lt;/p&gt;
&lt;p data-end=&quot;10306&quot; data-start=&quot;10294&quot; data-ke-size=&quot;size16&quot;&gt;중요한 점은 이것이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;FastAPI는 사용자가 SSH 접속할 때마다 새로 실행되는 것이 아니다.
systemd 서비스로 등록되어 있으면 서버 부팅 시 한 번 실행된다.
여러 명이 서버에 SSH로 접속해도 FastAPI가 여러 번 자동 실행되지는 않는다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;10472&quot; data-start=&quot;10451&quot; data-ke-size=&quot;size16&quot;&gt;서비스 파일 위치는 보통 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;/etc/systemd/system/vdi-login-api.service&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;10535&quot; data-start=&quot;10529&quot; data-ke-size=&quot;size16&quot;&gt;상태 확인:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;sudo systemctl status vdi-login-api&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;10593&quot; data-start=&quot;10586&quot; data-ke-size=&quot;size16&quot;&gt;서비스 시작:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;sudo systemctl start vdi-login-api&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;10651&quot; data-start=&quot;10643&quot; data-ke-size=&quot;size16&quot;&gt;서비스 재시작:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;sudo systemctl restart vdi-login-api&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;10717&quot; data-start=&quot;10703&quot; data-ke-size=&quot;size16&quot;&gt;서버 부팅 시 자동 실행:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;sudo systemctl enable vdi-login-api&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;10774&quot; data-start=&quot;10768&quot; data-ke-size=&quot;size16&quot;&gt;로그 확인:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;sudo journalctl -u vdi-login-api -f&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;10834&quot; data-start=&quot;10825&quot; data-ke-size=&quot;size16&quot;&gt;최근 로그 확인:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;sudo journalctl -u vdi-login-api -n 100&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;10895&quot; data-start=&quot;10889&quot; data-ke-size=&quot;size16&quot;&gt;포트 확인:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;ss -ltnp | grep 8000&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;10973&quot; data-start=&quot;10931&quot; data-ke-size=&quot;size16&quot;&gt;직접 uvicorn을 여러 터미널에서 실행하면 포트 충돌이 날 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;주의:
개발자가 수동으로 uvicorn main:app --host 0.0.0.0 --port 8000 을 실행한 상태에서
systemd 서비스도 실행하면 8000 포트 충돌이 발생할 수 있다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;11100&quot; data-start=&quot;11097&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h1 data-end=&quot;11118&quot; data-start=&quot;11102&quot; data-section-id=&quot;tigd98&quot;&gt;10. 백엔드 API 목록&lt;/h1&gt;
&lt;p data-end=&quot;11153&quot; data-start=&quot;11120&quot; data-ke-size=&quot;size16&quot;&gt;DB관리자가 앞으로 관리해야 할 주요 API는 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;기능메서드API설명
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;11760&quot; data-start=&quot;11155&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody data-end=&quot;11760&quot; data-start=&quot;11198&quot;&gt;
&lt;tr data-end=&quot;11247&quot; data-start=&quot;11198&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;11206&quot; data-start=&quot;11198&quot;&gt;상태 확인&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;11212&quot; data-start=&quot;11206&quot;&gt;GET&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;11228&quot; data-start=&quot;11212&quot;&gt;/api/health&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;11247&quot; data-start=&quot;11228&quot;&gt;백엔드 서버 정상 여부 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;11309&quot; data-start=&quot;11248&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;11259&quot; data-start=&quot;11248&quot;&gt;교수 정보 저장&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;11266&quot; data-start=&quot;11259&quot;&gt;POST&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;11291&quot; data-start=&quot;11266&quot;&gt;/api/professor/setup&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;11309&quot; data-start=&quot;11291&quot;&gt;교수명, 교번, 학과 저장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;11359&quot; data-start=&quot;11310&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;11318&quot; data-start=&quot;11310&quot;&gt;시험 생성&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;11325&quot; data-start=&quot;11318&quot;&gt;POST&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;11339&quot; data-start=&quot;11325&quot;&gt;/api/exam&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;11359&quot; data-start=&quot;11339&quot;&gt;시험 정보 저장 및 세션 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;11420&quot; data-start=&quot;11360&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;11371&quot; data-start=&quot;11360&quot;&gt;최신 시험 조회&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;11377&quot; data-start=&quot;11371&quot;&gt;GET&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;11398&quot; data-start=&quot;11377&quot;&gt;/api/exam/latest&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;11420&quot; data-start=&quot;11398&quot;&gt;대시보드 기본 exam_id 조회&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;11487&quot; data-start=&quot;11421&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;11432&quot; data-start=&quot;11421&quot;&gt;시험 상세 조회&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;11438&quot; data-start=&quot;11432&quot;&gt;GET&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;11470&quot; data-start=&quot;11438&quot;&gt;/api/admin/exam?exam_id=...&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;11487&quot; data-start=&quot;11470&quot;&gt;대시보드 상단 시험 정보&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;11553&quot; data-start=&quot;11488&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;11499&quot; data-start=&quot;11488&quot;&gt;세션 목록 조회&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;11505&quot; data-start=&quot;11499&quot;&gt;GET&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;11541&quot; data-start=&quot;11505&quot;&gt;/api/admin/sessions?exam_id=...&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;11553&quot; data-start=&quot;11541&quot;&gt;좌석 상태 목록&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;11638&quot; data-start=&quot;11554&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;11563&quot; data-start=&quot;11554&quot;&gt;학생 로그인&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;11570&quot; data-start=&quot;11563&quot;&gt;POST&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;11613&quot; data-start=&quot;11570&quot;&gt;/api/vdi/login 또는 /api/session/login&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;11638&quot; data-start=&quot;11613&quot;&gt;학생 로그인 및 좌석 ACTIVE 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;11703&quot; data-start=&quot;11639&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;11647&quot; data-start=&quot;11639&quot;&gt;접속 유지&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;11660&quot; data-start=&quot;11647&quot;&gt;POST/PATCH&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;11687&quot; data-start=&quot;11660&quot;&gt;/api/session/heartbeat&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;11703&quot; data-start=&quot;11687&quot;&gt;향후 접속 유지 판단용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;11760&quot; data-start=&quot;11704&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;11712&quot; data-start=&quot;11704&quot;&gt;탐지 반영&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;11719&quot; data-start=&quot;11712&quot;&gt;POST&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;11738&quot; data-start=&quot;11719&quot;&gt;/api/detection&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;11760&quot; data-start=&quot;11738&quot;&gt;향후 AI 탐지/차단 상태 반영용&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;11798&quot; data-start=&quot;11762&quot; data-ke-size=&quot;size16&quot;&gt;현재 대시보드가 반드시 필요로 하는 핵심 API는 다음 세 개다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;GET /api/exam/latest
GET /api/admin/exam?exam_id=...
GET /api/admin/sessions?exam_id=...&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;11935&quot; data-start=&quot;11902&quot; data-ke-size=&quot;size16&quot;&gt;학생 로그인까지 정상적으로 연결하려면 다음 API도 필수다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;POST /api/vdi/login&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;11973&quot; data-start=&quot;11970&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h1 data-end=&quot;11995&quot; data-start=&quot;11975&quot; data-section-id=&quot;1xa92vn&quot;&gt;11. DB 테이블 역할 인수인계&lt;/h1&gt;
&lt;p data-end=&quot;12065&quot; data-start=&quot;11997&quot; data-ke-size=&quot;size16&quot;&gt;정확한 테이블명은 실제 DB 스키마를 기준으로 다시 확인해야 하지만, 우리가 개발하면서 기준으로 잡은 역할은 다음과 같다.&lt;/p&gt;
&lt;h2 data-end=&quot;12077&quot; data-start=&quot;12067&quot; data-section-id=&quot;v95zpg&quot; data-ke-size=&quot;size26&quot;&gt;ACCOUNT&lt;/h2&gt;
&lt;p data-end=&quot;12095&quot; data-start=&quot;12079&quot; data-ke-size=&quot;size16&quot;&gt;사용자 계정 정보를 관리한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;교수 계정
학생 계정
관리자 계정
role 구분
username
password 또는 인증 정보&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;12222&quot; data-start=&quot;12164&quot; data-ke-size=&quot;size16&quot;&gt;교수 정보 입력 화면의 professorNo는 ACCOUNT.username으로 연결할 수 있다.&lt;/p&gt;
&lt;hr data-end=&quot;12227&quot; data-start=&quot;12224&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;12249&quot; data-start=&quot;12229&quot; data-section-id=&quot;1txthsy&quot; data-ke-size=&quot;size26&quot;&gt;PROFESSOR_PROFILE&lt;/h2&gt;
&lt;p data-end=&quot;12266&quot; data-start=&quot;12251&quot; data-ke-size=&quot;size16&quot;&gt;교수 상세 정보를 관리한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;tp&quot;&gt;&lt;code&gt;교수명
학과
ACCOUNT와의 연결 키&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;12333&quot; data-start=&quot;12303&quot; data-ke-size=&quot;size16&quot;&gt;대시보드 상단에서 교수명과 학과를 보여줄 때 필요하다.&lt;/p&gt;
&lt;hr data-end=&quot;12338&quot; data-start=&quot;12335&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;12352&quot; data-start=&quot;12340&quot; data-section-id=&quot;wyqx6t&quot; data-ke-size=&quot;size26&quot;&gt;EXAM_INFO&lt;/h2&gt;
&lt;p data-end=&quot;12366&quot; data-start=&quot;12354&quot; data-ke-size=&quot;size16&quot;&gt;시험 정보를 관리한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;exam_id
subject_name
exam_type
place
scheduled_at
ended_at
total_target_students
professor_id 또는 account_id
created_at&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;12532&quot; data-start=&quot;12500&quot; data-ke-size=&quot;size16&quot;&gt;시험 생성 페이지에서 입력한 정보가 이 테이블에 저장된다.&lt;/p&gt;
&lt;hr data-end=&quot;12537&quot; data-start=&quot;12534&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;12561&quot; data-start=&quot;12539&quot; data-section-id=&quot;7zipkv&quot; data-ke-size=&quot;size26&quot;&gt;VDI 이름/IP/좌석 매핑 테이블&lt;/h2&gt;
&lt;p data-end=&quot;12588&quot; data-start=&quot;12563&quot; data-ke-size=&quot;size16&quot;&gt;현재 DB에 이미 존재하는 중요한 테이블이다.&lt;/p&gt;
&lt;p data-end=&quot;12601&quot; data-start=&quot;12590&quot; data-ke-size=&quot;size16&quot;&gt;역할은 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;VDI 이름
VDI IP
좌석 이름 또는 좌석 번호&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;12648&quot; data-start=&quot;12645&quot; data-ke-size=&quot;size16&quot;&gt;예시:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;vdi-01 / 192.168.122.101 / A-01
vdi-02 / 192.168.122.102 / A-02
vdi-03 / 192.168.122.103 / A-03&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;12797&quot; data-start=&quot;12759&quot; data-ke-size=&quot;size16&quot;&gt;이 테이블 덕분에 학생마다 로그인 URL을 다르게 만들 필요가 없다.&lt;/p&gt;
&lt;p data-end=&quot;12842&quot; data-start=&quot;12799&quot; data-ke-size=&quot;size16&quot;&gt;학생이 로그인하면 백엔드가 요청 IP를 확인하고 이 테이블에서 좌석을 찾는다.&lt;/p&gt;
&lt;hr data-end=&quot;12847&quot; data-start=&quot;12844&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;12863&quot; data-start=&quot;12849&quot; data-section-id=&quot;13ob3fj&quot; data-ke-size=&quot;size26&quot;&gt;VDI_SESSION&lt;/h2&gt;
&lt;p data-end=&quot;12881&quot; data-start=&quot;12865&quot; data-ke-size=&quot;size16&quot;&gt;시험별 좌석 세션을 관리한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;session_id
exam_id
seat_no
vdi_name
vdi_ip
student_no
student_name
current_state
started_at
last_heartbeat
reconnect_count&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;13025&quot; data-start=&quot;13019&quot; data-ke-size=&quot;size16&quot;&gt;초기 상태:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;student_no = null
student_name = null
current_state = OUT&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;13107&quot; data-start=&quot;13098&quot; data-ke-size=&quot;size16&quot;&gt;학생 로그인 후:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;student_no = 입력한 학번
student_name = 입력한 이름
current_state = ACTIVE
started_at = 현재 시간
last_heartbeat = 현재 시간&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;13259&quot; data-start=&quot;13229&quot; data-ke-size=&quot;size16&quot;&gt;대시보드는 이 테이블을 조회해서 좌석 색상을 결정한다.&lt;/p&gt;
&lt;hr data-end=&quot;13264&quot; data-start=&quot;13261&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;13282&quot; data-start=&quot;13266&quot; data-section-id=&quot;1ke8ikz&quot; data-ke-size=&quot;size26&quot;&gt;SESSION_STATE&lt;/h2&gt;
&lt;p data-end=&quot;13307&quot; data-start=&quot;13284&quot; data-ke-size=&quot;size16&quot;&gt;상태 코드 관리 테이블로 사용할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;13312&quot; data-start=&quot;13309&quot; data-ke-size=&quot;size16&quot;&gt;예시:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;1 / OUT / 미접속
2 / ACTIVE / 접속중
3 / AI_DETECTED / AI 탐지
4 / RECONNECTED / 재접속
5 / BLOCKED / 차단&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;13470&quot; data-start=&quot;13421&quot; data-ke-size=&quot;size16&quot;&gt;이전에 한글 설명을 insert할 때 MariaDB 문자셋 문제로 오류가 날 수 있었다.&lt;/p&gt;
&lt;p data-end=&quot;13478&quot; data-start=&quot;13472&quot; data-ke-size=&quot;size16&quot;&gt;오류 예시:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;subunit&quot;&gt;&lt;code&gt;ERROR 1366 Incorrect string value&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;13533&quot; data-start=&quot;13527&quot; data-ke-size=&quot;size16&quot;&gt;해결 방향:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;ALTER DATABASE capston_db
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;13652&quot; data-start=&quot;13623&quot; data-ke-size=&quot;size16&quot;&gt;테이블과 컬럼도 utf8mb4인지 확인해야 한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;SHOW FULL COLUMNS FROM SESSION_STATE;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;13707&quot; data-start=&quot;13704&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h1 data-end=&quot;13727&quot; data-start=&quot;13709&quot; data-section-id=&quot;xosny2&quot;&gt;12. 학생 로그인 처리 로직&lt;/h1&gt;
&lt;p data-end=&quot;13753&quot; data-start=&quot;13729&quot; data-ke-size=&quot;size16&quot;&gt;학생 로그인 API의 핵심 로직은 다음이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;1. 학생 학번과 이름을 body로 받는다.
2. 요청 IP를 확인한다.
3. 요청 IP로 VDI 매핑 테이블 조회
4. VDI 이름과 좌석 번호 확인
5. 현재 진행 중인 exam_id 확인
6. 해당 exam_id + seat_no의 VDI_SESSION 조회
7. student_no, student_name 저장
8. current_state를 ACTIVE로 변경
9. started_at, last_heartbeat 갱신
10. 프론트에 success true 반환&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;14041&quot; data-start=&quot;14034&quot; data-ke-size=&quot;size16&quot;&gt;개념 SQL:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;n1ql&quot;&gt;&lt;code&gt;SELECT
  vdi_name,
  vdi_ip,
  seat_no
FROM VDI_SEAT_MAPPING
WHERE vdi_ip = ?;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;14142&quot; data-start=&quot;14134&quot; data-ke-size=&quot;size16&quot;&gt;세션 업데이트:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;gams&quot;&gt;&lt;code&gt;UPDATE VDI_SESSION
SET
  student_no = ?,
  student_name = ?,
  current_state = 'ACTIVE',
  started_at = COALESCE(started_at, NOW()),
  last_heartbeat = NOW()
WHERE
  exam_id = ?
  AND seat_no = ?;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;14385&quot; data-start=&quot;14353&quot; data-ke-size=&quot;size16&quot;&gt;DB에 상태를 숫자로 저장한다면 다음처럼 처리할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;gams&quot;&gt;&lt;code&gt;UPDATE VDI_SESSION
SET
  student_no = ?,
  student_name = ?,
  state_id = 2,
  started_at = COALESCE(started_at, NOW()),
  last_heartbeat = NOW()
WHERE
  exam_id = ?
  AND seat_no = ?;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;14619&quot; data-start=&quot;14584&quot; data-ke-size=&quot;size16&quot;&gt;그리고 API 응답에서는 숫자 대신 문자열로 변환해서 내려준다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;state_name&quot;: &quot;ACTIVE&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;14666&quot; data-start=&quot;14663&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h1 data-end=&quot;14690&quot; data-start=&quot;14668&quot; data-section-id=&quot;d3o5n3&quot;&gt;13. 시험 생성 시 세션 생성 로직&lt;/h1&gt;
&lt;p data-end=&quot;14718&quot; data-start=&quot;14692&quot; data-ke-size=&quot;size16&quot;&gt;시험 생성 API는 다음 순서로 동작해야 한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1. 교수 계정 확인
2. EXAM_INFO insert
3. 생성된 exam_id 확인
4. VDI 매핑 테이블에서 정원 수만큼 좌석 조회
5. VDI_SESSION에 초기 세션 insert
6. 모든 상태는 OUT&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;14862&quot; data-start=&quot;14855&quot; data-ke-size=&quot;size16&quot;&gt;개념 SQL:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;autohotkey&quot;&gt;&lt;code&gt;INSERT INTO EXAM_INFO (
  subject_name,
  exam_type,
  place,
  scheduled_at,
  ended_at,
  total_target_students,
  professor_id
)
VALUES (?, ?, ?, ?, ?, ?, ?);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;15048&quot; data-start=&quot;15038&quot; data-ke-size=&quot;size16&quot;&gt;VDI 매핑 조회:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;n1ql&quot;&gt;&lt;code&gt;SELECT
  vdi_name,
  vdi_ip,
  seat_no
FROM VDI_SEAT_MAPPING
ORDER BY seat_no
LIMIT ?;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;15158&quot; data-start=&quot;15149&quot; data-ke-size=&quot;size16&quot;&gt;세션 초기 생성:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;autohotkey&quot;&gt;&lt;code&gt;INSERT INTO VDI_SESSION (
  exam_id,
  seat_no,
  vdi_name,
  vdi_ip,
  student_no,
  student_name,
  current_state,
  started_at,
  last_heartbeat,
  reconnect_count
)
VALUES (
  ?,
  ?,
  ?,
  ?,
  NULL,
  NULL,
  'OUT',
  NULL,
  NULL,
  0
);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;15457&quot; data-start=&quot;15418&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 해야 대시보드에 처음 들어갔을 때 전체 좌석이 회색으로 보인다.&lt;/p&gt;
&lt;hr data-end=&quot;15462&quot; data-start=&quot;15459&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h1 data-end=&quot;15480&quot; data-start=&quot;15464&quot; data-section-id=&quot;1nzuzjo&quot;&gt;14. 대시보드 갱신 구조&lt;/h1&gt;
&lt;p data-end=&quot;15509&quot; data-start=&quot;15482&quot; data-ke-size=&quot;size16&quot;&gt;대시보드는 일정 주기마다 API를 다시 호출한다.&lt;/p&gt;
&lt;p data-end=&quot;15533&quot; data-start=&quot;15511&quot; data-ke-size=&quot;size16&quot;&gt;현재 기준은 5초 polling 구조다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;loadDashboard();
setInterval(loadDashboard, 5000);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;15608&quot; data-start=&quot;15597&quot; data-ke-size=&quot;size16&quot;&gt;흐름은 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;학생 로그인
  &amp;darr;
POST /api/vdi/login
  &amp;darr;
DB에서 해당 좌석 ACTIVE 변경
  &amp;darr;
대시보드가 5초 후 GET /api/admin/sessions 호출
  &amp;darr;
새로운 sessions 배열 수신
  &amp;darr;
해당 좌석 회색 &amp;rarr; 초록색&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;15804&quot; data-start=&quot;15763&quot; data-ke-size=&quot;size16&quot;&gt;WebSocket을 쓰지 않아도 초기 프로젝트 단계에서는 충분히 동작한다.&lt;/p&gt;
&lt;p data-end=&quot;15850&quot; data-start=&quot;15806&quot; data-ke-size=&quot;size16&quot;&gt;향후 실시간성을 높이고 싶다면 WebSocket 또는 SSE로 변경할 수 있다.&lt;/p&gt;
&lt;hr data-end=&quot;15855&quot; data-start=&quot;15852&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h1 data-end=&quot;15872&quot; data-start=&quot;15857&quot; data-section-id=&quot;1938q8l&quot;&gt;15. API 응답 규칙&lt;/h1&gt;
&lt;p data-end=&quot;15919&quot; data-start=&quot;15874&quot; data-ke-size=&quot;size16&quot;&gt;프론트의 fetchJson 함수는 응답에 success가 있는지 확인한다.&lt;/p&gt;
&lt;p data-end=&quot;15950&quot; data-start=&quot;15921&quot; data-ke-size=&quot;size16&quot;&gt;따라서 모든 API 응답은 다음 형식을 지켜야 한다.&lt;/p&gt;
&lt;p data-end=&quot;15955&quot; data-start=&quot;15952&quot; data-ke-size=&quot;size16&quot;&gt;성공:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;success&quot;: true,
  &quot;data&quot;: {}
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;16009&quot; data-start=&quot;16006&quot; data-ke-size=&quot;size16&quot;&gt;또는:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;success&quot;: true,
  &quot;exam&quot;: {},
  &quot;sessions&quot;: []
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;16081&quot; data-start=&quot;16078&quot; data-ke-size=&quot;size16&quot;&gt;실패:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;success&quot;: false,
  &quot;message&quot;: &quot;오류 메시지&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;16200&quot; data-start=&quot;16142&quot; data-ke-size=&quot;size16&quot;&gt;주의할 점은 HTTP 200이어도 success가 없으면 프론트에서 실패로 처리될 수 있다는 것이다.&lt;/p&gt;
&lt;p data-end=&quot;16207&quot; data-start=&quot;16202&quot; data-ke-size=&quot;size16&quot;&gt;나쁜 예:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;exam&quot;: {
    &quot;exam_id&quot;: 1
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;16264&quot; data-start=&quot;16259&quot; data-ke-size=&quot;size16&quot;&gt;좋은 예:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;success&quot;: true,
  &quot;exam&quot;: {
    &quot;exam_id&quot;: 1
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;16338&quot; data-start=&quot;16335&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h1 data-end=&quot;16357&quot; data-start=&quot;16340&quot; data-section-id=&quot;5w9p5a&quot;&gt;16. 운영 명령어 인수인계&lt;/h1&gt;
&lt;h2 data-end=&quot;16371&quot; data-start=&quot;16359&quot; data-section-id=&quot;1yeq8rw&quot; data-ke-size=&quot;size26&quot;&gt;백엔드 상태 확인&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;sudo systemctl status vdi-login-api&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-end=&quot;16432&quot; data-start=&quot;16422&quot; data-section-id=&quot;1qo44sd&quot; data-ke-size=&quot;size26&quot;&gt;백엔드 재시작&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;sudo systemctl restart vdi-login-api&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-end=&quot;16496&quot; data-start=&quot;16484&quot; data-section-id=&quot;q18dat&quot; data-ke-size=&quot;size26&quot;&gt;백엔드 로그 확인&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;sudo journalctl -u vdi-login-api -f&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-end=&quot;16563&quot; data-start=&quot;16547&quot; data-section-id=&quot;1oeq5s4&quot; data-ke-size=&quot;size26&quot;&gt;FastAPI 포트 확인&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;ss -ltnp | grep 8000&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-end=&quot;16613&quot; data-start=&quot;16599&quot; data-section-id=&quot;1d30myr&quot; data-ke-size=&quot;size26&quot;&gt;nginx 상태 확인&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;sudo systemctl status nginx&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-end=&quot;16668&quot; data-start=&quot;16656&quot; data-section-id=&quot;as51le&quot; data-ke-size=&quot;size26&quot;&gt;nginx 재시작&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;sudo systemctl restart nginx&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-end=&quot;16727&quot; data-start=&quot;16712&quot; data-section-id=&quot;2z2k7m&quot; data-ke-size=&quot;size26&quot;&gt;nginx 설정 테스트&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;sudo nginx -t&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-end=&quot;16768&quot; data-start=&quot;16756&quot; data-section-id=&quot;kmf76k&quot; data-ke-size=&quot;size26&quot;&gt;80번 포트 확인&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;gradle&quot;&gt;&lt;code&gt;ss -ltnp | grep ':80'&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-end=&quot;16824&quot; data-start=&quot;16805&quot; data-section-id=&quot;1j5baiv&quot; data-ke-size=&quot;size26&quot;&gt;API health check&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;curl http://127.0.0.1:8000/api/health&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;16890&quot; data-start=&quot;16877&quot; data-ke-size=&quot;size16&quot;&gt;nginx를 통해 확인:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;curl http://서버주소/api/health&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;16936&quot; data-start=&quot;16933&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h1 data-end=&quot;16955&quot; data-start=&quot;16938&quot; data-section-id=&quot;mbqy6&quot;&gt;17. API 테스트 명령어&lt;/h1&gt;
&lt;h2 data-end=&quot;16968&quot; data-start=&quot;16957&quot; data-section-id=&quot;yx4zc1&quot; data-ke-size=&quot;size26&quot;&gt;최신 시험 조회&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;curl http://서버주소/api/exam/latest&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-end=&quot;17027&quot; data-start=&quot;17016&quot; data-section-id=&quot;1v5iqn8&quot; data-ke-size=&quot;size26&quot;&gt;시험 상세 조회&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;curl &quot;http://서버주소/api/admin/exam?exam_id=1&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-end=&quot;17097&quot; data-start=&quot;17086&quot; data-section-id=&quot;5u7a7h&quot; data-ke-size=&quot;size26&quot;&gt;세션 목록 조회&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;curl &quot;http://서버주소/api/admin/sessions?exam_id=1&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-end=&quot;17173&quot; data-start=&quot;17160&quot; data-section-id=&quot;6gbrd9&quot; data-ke-size=&quot;size26&quot;&gt;학생 로그인 테스트&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;scilab&quot;&gt;&lt;code&gt;curl -X POST &quot;http://서버주소/api/vdi/login&quot; \
  -H &quot;Content-Type: application/json&quot; \
  -d '{
    &quot;student_no&quot;: &quot;20231042&quot;,
    &quot;student_name&quot;: &quot;김OO&quot;
  }'&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;17427&quot; data-start=&quot;17340&quot; data-ke-size=&quot;size16&quot;&gt;주의할 점은 이 테스트를 서버 내부에서 127.0.0.1로 실행하면 요청 IP가 실제 VDI IP가 아니기 때문에 좌석 매핑이 실패할 수 있다는 것이다.&lt;/p&gt;
&lt;p data-end=&quot;17486&quot; data-start=&quot;17429&quot; data-ke-size=&quot;size16&quot;&gt;VDI IP 기반 매핑을 테스트하려면 실제 VDI 안에서 로그인 요청을 보내는 방식으로 확인해야 한다.&lt;/p&gt;
&lt;hr data-end=&quot;17491&quot; data-start=&quot;17488&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h1 data-end=&quot;17509&quot; data-start=&quot;17493&quot; data-section-id=&quot;1jniab1&quot;&gt;18. DB 확인용 SQL&lt;/h1&gt;
&lt;h2 data-end=&quot;17522&quot; data-start=&quot;17511&quot; data-section-id=&quot;1udics8&quot; data-ke-size=&quot;size26&quot;&gt;시험 목록 확인&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;n1ql&quot;&gt;&lt;code&gt;SELECT *
FROM EXAM_INFO
ORDER BY exam_id DESC;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-end=&quot;17594&quot; data-start=&quot;17583&quot; data-section-id=&quot;qq4o68&quot; data-ke-size=&quot;size26&quot;&gt;특정 시험 확인&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;n1ql&quot;&gt;&lt;code&gt;SELECT *
FROM EXAM_INFO
WHERE exam_id = 1;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-end=&quot;17666&quot; data-start=&quot;17651&quot; data-section-id=&quot;1hdecfs&quot; data-ke-size=&quot;size26&quot;&gt;VDI/좌석 매핑 확인&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;n1ql&quot;&gt;&lt;code&gt;SELECT *
FROM VDI_SEAT_MAPPING
ORDER BY seat_no;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;17766&quot; data-start=&quot;17729&quot; data-ke-size=&quot;size16&quot;&gt;실제 테이블명이 다르면 현재 DB의 매핑 테이블명으로 바꿔야 한다.&lt;/p&gt;
&lt;h2 data-end=&quot;17788&quot; data-start=&quot;17768&quot; data-section-id=&quot;5sphhs&quot; data-ke-size=&quot;size26&quot;&gt;특정 IP가 어느 좌석인지 확인&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;SELECT *
FROM VDI_SEAT_MAPPING
WHERE vdi_ip = '192.168.122.101';&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-end=&quot;17882&quot; data-start=&quot;17867&quot; data-section-id=&quot;1inbs1k&quot; data-ke-size=&quot;size26&quot;&gt;특정 시험의 세션 확인&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;n1ql&quot;&gt;&lt;code&gt;SELECT *
FROM VDI_SESSION
WHERE exam_id = 1
ORDER BY seat_no;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-end=&quot;17971&quot; data-start=&quot;17958&quot; data-section-id=&quot;10nj6fo&quot; data-ke-size=&quot;size26&quot;&gt;접속 중 좌석 확인&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;autohotkey&quot;&gt;&lt;code&gt;SELECT
  seat_no,
  vdi_name,
  vdi_ip,
  student_no,
  student_name,
  current_state,
  started_at,
  last_heartbeat
FROM VDI_SESSION
WHERE exam_id = 1
  AND current_state = 'ACTIVE';&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-end=&quot;18182&quot; data-start=&quot;18170&quot; data-section-id=&quot;1kblqpp&quot; data-ke-size=&quot;size26&quot;&gt;미접속 좌석 확인&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;SELECT
  seat_no,
  vdi_name,
  vdi_ip,
  current_state
FROM VDI_SESSION
WHERE exam_id = 1
  AND current_state = 'OUT';&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;18319&quot; data-start=&quot;18316&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h1 data-end=&quot;18341&quot; data-start=&quot;18321&quot; data-section-id=&quot;3lzb34&quot;&gt;19. 주요 시행착오와 해결 방식&lt;/h1&gt;
&lt;h2 data-end=&quot;18384&quot; data-start=&quot;18343&quot; data-section-id=&quot;pbm3sb&quot; data-ke-size=&quot;size26&quot;&gt;19-1. localStorage 기반이라 실제 DB와 연결되지 않음&lt;/h2&gt;
&lt;h3 data-end=&quot;18392&quot; data-start=&quot;18386&quot; data-section-id=&quot;1hrhjdu&quot; data-ke-size=&quot;size23&quot;&gt;문제&lt;/h3&gt;
&lt;p data-end=&quot;18421&quot; data-start=&quot;18394&quot; data-ke-size=&quot;size16&quot;&gt;초기 프론트는 localStorage만 사용했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;vdi_professor_profile
vdi_exam_info
vdi_sessions&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-end=&quot;18491&quot; data-start=&quot;18485&quot; data-section-id=&quot;1hrqn3i&quot; data-ke-size=&quot;size23&quot;&gt;원인&lt;/h3&gt;
&lt;p data-end=&quot;18529&quot; data-start=&quot;18493&quot; data-ke-size=&quot;size16&quot;&gt;프론트 화면 확인을 위해 임시 데이터를 사용한 구조였기 때문이다.&lt;/p&gt;
&lt;h3 data-end=&quot;18537&quot; data-start=&quot;18531&quot; data-section-id=&quot;1hrnhqa&quot; data-ke-size=&quot;size23&quot;&gt;해결&lt;/h3&gt;
&lt;p data-end=&quot;18561&quot; data-start=&quot;18539&quot; data-ke-size=&quot;size16&quot;&gt;백엔드 API를 만들고 DB와 연결했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;tp&quot;&gt;&lt;code&gt;교수 정보 &amp;rarr; ACCOUNT / PROFESSOR_PROFILE
시험 정보 &amp;rarr; EXAM_INFO
좌석 상태 &amp;rarr; VDI_SESSION&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;18653&quot; data-start=&quot;18650&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;18697&quot; data-start=&quot;18655&quot; data-section-id=&quot;vrhobp&quot; data-ke-size=&quot;size26&quot;&gt;19-2. exam_id가 없으면 대시보드가 어떤 시험을 보여줄지 모름&lt;/h2&gt;
&lt;h3 data-end=&quot;18705&quot; data-start=&quot;18699&quot; data-section-id=&quot;1hrhjdu&quot; data-ke-size=&quot;size23&quot;&gt;문제&lt;/h3&gt;
&lt;p data-end=&quot;18742&quot; data-start=&quot;18707&quot; data-ke-size=&quot;size16&quot;&gt;사용자가 /home/으로 접속하면 exam_id가 없다.&lt;/p&gt;
&lt;h3 data-end=&quot;18750&quot; data-start=&quot;18744&quot; data-section-id=&quot;1hrnhqa&quot; data-ke-size=&quot;size23&quot;&gt;해결&lt;/h3&gt;
&lt;p data-end=&quot;18785&quot; data-start=&quot;18752&quot; data-ke-size=&quot;size16&quot;&gt;GET /api/exam/latest API를 만들었다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;/home/
  &amp;darr;
/api/exam/latest 호출
  &amp;darr;
최신 exam_id 확인
  &amp;darr;
/home/?exam_id=1 로 URL 갱신&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;18882&quot; data-start=&quot;18879&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;18926&quot; data-start=&quot;18884&quot; data-section-id=&quot;pejkb0&quot; data-ke-size=&quot;size26&quot;&gt;19-3. API 응답에 success가 없어서 프론트에서 오류 처리됨&lt;/h2&gt;
&lt;h3 data-end=&quot;18934&quot; data-start=&quot;18928&quot; data-section-id=&quot;1hrhjdu&quot; data-ke-size=&quot;size23&quot;&gt;문제&lt;/h3&gt;
&lt;p data-end=&quot;18971&quot; data-start=&quot;18936&quot; data-ke-size=&quot;size16&quot;&gt;백엔드가 데이터를 내려줘도 프론트에서 실패로 처리될 수 있었다.&lt;/p&gt;
&lt;h3 data-end=&quot;18979&quot; data-start=&quot;18973&quot; data-section-id=&quot;1hrqn3i&quot; data-ke-size=&quot;size23&quot;&gt;원인&lt;/h3&gt;
&lt;p data-end=&quot;19024&quot; data-start=&quot;18981&quot; data-ke-size=&quot;size16&quot;&gt;프론트 fetchJson이 data.success를 검사했기 때문이다.&lt;/p&gt;
&lt;h3 data-end=&quot;19032&quot; data-start=&quot;19026&quot; data-section-id=&quot;1hrnhqa&quot; data-ke-size=&quot;size23&quot;&gt;해결&lt;/h3&gt;
&lt;p data-end=&quot;19061&quot; data-start=&quot;19034&quot; data-ke-size=&quot;size16&quot;&gt;모든 API 응답에 success를 포함한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;success&quot;: true
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;19101&quot; data-start=&quot;19098&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;19131&quot; data-start=&quot;19103&quot; data-section-id=&quot;sjv9k8&quot; data-ke-size=&quot;size26&quot;&gt;19-4. DB 상태값과 프론트 상태값이 다름&lt;/h2&gt;
&lt;h3 data-end=&quot;19139&quot; data-start=&quot;19133&quot; data-section-id=&quot;1hrhjdu&quot; data-ke-size=&quot;size23&quot;&gt;문제&lt;/h3&gt;
&lt;p data-end=&quot;19216&quot; data-start=&quot;19141&quot; data-ke-size=&quot;size16&quot;&gt;DB에는 숫자나 다른 문자열로 상태가 저장되어 있는데, 프론트는 ACTIVE, OUT, AI_DETECTED 등을 기대한다.&lt;/p&gt;
&lt;h3 data-end=&quot;19224&quot; data-start=&quot;19218&quot; data-section-id=&quot;1hrnhqa&quot; data-ke-size=&quot;size23&quot;&gt;해결&lt;/h3&gt;
&lt;p data-end=&quot;19242&quot; data-start=&quot;19226&quot; data-ke-size=&quot;size16&quot;&gt;백엔드에서 응답 변환을 한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;1 &amp;rarr; OUT
2 &amp;rarr; ACTIVE
3 &amp;rarr; AI_DETECTED
4 &amp;rarr; RECONNECTED
5 &amp;rarr; BLOCKED&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;19323&quot; data-start=&quot;19320&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;19357&quot; data-start=&quot;19325&quot; data-section-id=&quot;fw51a8&quot; data-ke-size=&quot;size26&quot;&gt;19-5. 학생 로그인 후 어떤 좌석인지 알 수 없음&lt;/h2&gt;
&lt;h3 data-end=&quot;19365&quot; data-start=&quot;19359&quot; data-section-id=&quot;1hrhjdu&quot; data-ke-size=&quot;size23&quot;&gt;문제&lt;/h3&gt;
&lt;p data-end=&quot;19400&quot; data-start=&quot;19367&quot; data-ke-size=&quot;size16&quot;&gt;학생이 학번과 이름만 입력하면 백엔드가 좌석을 알 수 없다.&lt;/p&gt;
&lt;h3 data-end=&quot;19408&quot; data-start=&quot;19402&quot; data-section-id=&quot;1hrnhqa&quot; data-ke-size=&quot;size23&quot;&gt;해결&lt;/h3&gt;
&lt;p data-end=&quot;19442&quot; data-start=&quot;19410&quot; data-ke-size=&quot;size16&quot;&gt;DB에 이미 있는 VDI 이름/IP/좌석 매핑을 사용한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;요청 IP
  &amp;darr;
VDI 매핑 테이블 조회
  &amp;darr;
좌석 확인
  &amp;darr;
VDI_SESSION 업데이트&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;19515&quot; data-start=&quot;19512&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;19560&quot; data-start=&quot;19517&quot; data-section-id=&quot;1vxn7k9&quot; data-ke-size=&quot;size26&quot;&gt;19-6. nginx 프록시 때문에 실제 VDI IP가 안 보일 수 있음&lt;/h2&gt;
&lt;h3 data-end=&quot;19568&quot; data-start=&quot;19562&quot; data-section-id=&quot;1hrhjdu&quot; data-ke-size=&quot;size23&quot;&gt;문제&lt;/h3&gt;
&lt;p data-end=&quot;19616&quot; data-start=&quot;19570&quot; data-ke-size=&quot;size16&quot;&gt;FastAPI에서 클라이언트 IP를 확인하면 127.0.0.1로 나올 수 있다.&lt;/p&gt;
&lt;h3 data-end=&quot;19624&quot; data-start=&quot;19618&quot; data-section-id=&quot;1hrnhqa&quot; data-ke-size=&quot;size23&quot;&gt;해결&lt;/h3&gt;
&lt;p data-end=&quot;19645&quot; data-start=&quot;19626&quot; data-ke-size=&quot;size16&quot;&gt;nginx에서 다음 헤더를 넘긴다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;19788&quot; data-start=&quot;19763&quot; data-ke-size=&quot;size16&quot;&gt;FastAPI에서 해당 헤더를 우선 확인한다.&lt;/p&gt;
&lt;hr data-end=&quot;19793&quot; data-start=&quot;19790&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;19816&quot; data-start=&quot;19795&quot; data-section-id=&quot;8hjffy&quot; data-ke-size=&quot;size26&quot;&gt;19-7. 한글 insert 오류&lt;/h2&gt;
&lt;h3 data-end=&quot;19824&quot; data-start=&quot;19818&quot; data-section-id=&quot;1hrhjdu&quot; data-ke-size=&quot;size23&quot;&gt;문제&lt;/h3&gt;
&lt;p data-end=&quot;19862&quot; data-start=&quot;19826&quot; data-ke-size=&quot;size16&quot;&gt;MariaDB에 한글 설명을 넣을 때 다음 오류가 날 수 있었다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;subunit&quot;&gt;&lt;code&gt;ERROR 1366 Incorrect string value&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-end=&quot;19917&quot; data-start=&quot;19911&quot; data-section-id=&quot;1hrqn3i&quot; data-ke-size=&quot;size23&quot;&gt;원인&lt;/h3&gt;
&lt;p data-end=&quot;19960&quot; data-start=&quot;19919&quot; data-ke-size=&quot;size16&quot;&gt;DB, 테이블, 컬럼 문자셋이 utf8mb4가 아니었을 가능성이 있다.&lt;/p&gt;
&lt;h3 data-end=&quot;19968&quot; data-start=&quot;19962&quot; data-section-id=&quot;1hrnhqa&quot; data-ke-size=&quot;size23&quot;&gt;해결&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;ALTER DATABASE capston_db
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;20079&quot; data-start=&quot;20058&quot; data-ke-size=&quot;size16&quot;&gt;테이블과 컬럼 문자셋도 확인해야 한다.&lt;/p&gt;</description>
      <author>otopligrm</author>
      <guid isPermaLink="true">https://otopligrm.tistory.com/45</guid>
      <comments>https://otopligrm.tistory.com/45#entry45comment</comments>
      <pubDate>Mon, 25 May 2026 11:44:33 +0900</pubDate>
    </item>
    <item>
      <title>FastAPI 백엔드 API 개발 및 프론트 대시보드 연동 기록</title>
      <link>https://otopligrm.tistory.com/44</link>
      <description>&lt;div data-is-intersecting=&quot;true&quot; data-turn-id-container=&quot;request-69ef037f-ae8c-83e8-b5ef-771805e57724-0&quot;&gt;
&lt;div&gt;
&lt;div data-turn-start-message=&quot;true&quot; data-message-model-slug=&quot;gpt-5-5-thinking&quot; data-message-id=&quot;3897fa7b-db7e-4b15-8c21-761d43e42f96&quot; data-message-author-role=&quot;assistant&quot;&gt;
&lt;p data-end=&quot;461&quot; data-start=&quot;322&quot; data-ke-size=&quot;size16&quot;&gt;초기에는 프론트엔드 대시보드가 더미데이터 또는 브라우저 localStorage 기반으로 동작하고 있었다. 즉, 사용자가 시험 정보를 입력하더라도 실제 DB에 저장되지 않았고, 학생이 VDI에 접속해도 대시보드의 좌석 상태가 실제로 바뀌지 않았다.&lt;/p&gt;
&lt;p data-end=&quot;489&quot; data-start=&quot;463&quot; data-ke-size=&quot;size16&quot;&gt;그래서 이번 개발의 핵심 목표는 다음과 같았다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;1. 사용자가 웹 페이지에서 시험 정보를 입력하면 DB에 저장한다.
2. 학생이 VDI 안에서 로그인하면 학생 정보가 DB에 저장된다.
3. 학생이 접속한 VDI를 식별해 해당 좌석 상태를 ACTIVE로 변경한다.
4. 교수 대시보드는 DB 값을 조회해 좌석 상태를 시각화한다.
5. 개발이 끝난 뒤에는 FastAPI 서버를 수동 실행하지 않고 systemd 서비스로 운영한다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;741&quot; data-start=&quot;716&quot; data-ke-size=&quot;size16&quot;&gt;최종적으로 구현하고자 한 흐름은 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;사용자
/professor/ 접속
&amp;rarr; 교수 식별 정보 입력
&amp;rarr; 교수 정보 DB 저장 또는 수정

/exam/ 접속
&amp;rarr; 과목명, 시험 유형, 시작/종료 시간, 정원 입력
&amp;rarr; 시험 정보 DB 저장 또는 수정
&amp;rarr; 기존 VDI 좌석 row에 현재 시험 ID 연결

학생
VDI 접속
&amp;rarr; VDI 내부에서 /login/ 자동 실행
&amp;rarr; 이름과 학번 입력
&amp;rarr; 백엔드가 요청 IP를 기준으로 VDI 좌석 식별
&amp;rarr; 해당 좌석 상태를 ACTIVE로 변경

대시보드
/home/ 접속
&amp;rarr; 시험 정보와 VDI 세션 정보 조회
&amp;rarr; 미접속 좌석은 회색, 접속 좌석은 초록색으로 표시&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;1070&quot; data-start=&quot;1067&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;1087&quot; data-start=&quot;1072&quot; data-section-id=&quot;1nsh8x&quot; data-ke-size=&quot;size26&quot;&gt;2. 전체 시스템 구조&lt;/h2&gt;
&lt;p data-end=&quot;1108&quot; data-start=&quot;1089&quot; data-ke-size=&quot;size16&quot;&gt;이번에 구성한 구조는 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;[교수자 PC / 학생 VDI 브라우저]
        &amp;darr;
[Nginx]
- 정적 파일 제공
- /login/
- /home/
- /professor/
- /exam/

        &amp;darr; /api/ 요청 프록시

[FastAPI 백엔드]
- 교수 정보 저장
- 시험 정보 저장/수정
- 학생 로그인 처리
- 대시보드 조회 API 제공

        &amp;darr;

[MariaDB]
- 교수 정보
- 학생 정보
- 시험 정보
- VDI 세션 상태
- 상태 변경 이력 저장&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1486&quot; data-start=&quot;1382&quot; data-ke-size=&quot;size16&quot;&gt;Nginx는 웹 화면을 제공하고, /api/로 시작하는 요청은 FastAPI 백엔드로 전달한다. FastAPI는 DB와 연결되어 교수자, 학생, 시험, VDI 세션 데이터를 처리한다.&lt;/p&gt;
&lt;hr data-end=&quot;1491&quot; data-start=&quot;1488&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;1504&quot; data-start=&quot;1493&quot; data-section-id=&quot;1qqwvhp&quot; data-ke-size=&quot;size26&quot;&gt;3. 사용 기술&lt;/h2&gt;
&lt;p data-end=&quot;1532&quot; data-start=&quot;1506&quot; data-ke-size=&quot;size16&quot;&gt;이번 작업에서 사용한 주요 기술은 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;운영체제
- Rocky Linux 계열 서버

웹 서버
- Nginx

백엔드
- Python
- FastAPI
- Uvicorn
- PyMySQL
- python-dotenv

DB
- MariaDB

운영 관리
- systemd

프론트엔드
- HTML
- CSS
- JavaScript
- Fetch API
- localStorage&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1758&quot; data-start=&quot;1737&quot; data-ke-size=&quot;size16&quot;&gt;각 기술을 사용한 이유는 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;FastAPI
&amp;rarr; Python 기반으로 빠르게 REST API를 구현할 수 있고, 요청 모델 검증이 편리하다.

Uvicorn
&amp;rarr; FastAPI 앱을 실행하기 위한 ASGI 서버다.

PyMySQL
&amp;rarr; Python에서 MariaDB와 연결하기 위해 사용했다.

python-dotenv
&amp;rarr; DB 접속 정보와 상태값을 코드에 직접 쓰지 않고 .env 파일로 분리하기 위해 사용했다.

Nginx
&amp;rarr; 정적 HTML/CSS/JS 파일 제공과 API reverse proxy 역할을 동시에 수행하기 위해 사용했다.

systemd
&amp;rarr; FastAPI를 터미널 수동 실행이 아닌 운영 서비스로 등록하기 위해 사용했다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;2116&quot; data-start=&quot;2113&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;2136&quot; data-start=&quot;2118&quot; data-section-id=&quot;19pp2wg&quot; data-ke-size=&quot;size26&quot;&gt;4. 주요 DB 테이블 역할&lt;/h2&gt;
&lt;p data-end=&quot;2185&quot; data-start=&quot;2138&quot; data-ke-size=&quot;size16&quot;&gt;프로젝트의 실제 DB 테이블명은 공개하지 않고, 블로그에서는 역할 중심으로 정리했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;계정 테이블
&amp;rarr; 학생과 교수자의 공통 로그인/식별 정보를 저장한다.

학생 프로필 테이블
&amp;rarr; 학번, 이름 등 학생 정보를 저장한다.

교수 프로필 테이블
&amp;rarr; 교수자 이름, 소속 등 교수자 정보를 저장한다.

시험 정보 테이블
&amp;rarr; 과목명, 시험 유형, 시작 시간, 종료 시간, 정원 정보를 저장한다.

세션 상태 테이블
&amp;rarr; OUT, ACTIVE 등 VDI 상태 코드를 저장한다.

VDI 세션 테이블
&amp;rarr; 각 VDI 좌석의 IP, 좌석 번호, 현재 접속 학생, 상태를 저장한다.

상태 이력 테이블
&amp;rarr; VDI 상태가 변경될 때마다 이력을 저장한다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2649&quot; data-start=&quot;2505&quot; data-ke-size=&quot;size16&quot;&gt;이번 개발에서 가장 중요한 테이블은 &lt;b&gt;VDI 세션 테이블&lt;/b&gt;이었다. 이 테이블은 처음에는 시험마다 새 세션 row를 만드는 방식으로 설계하려고 했지만, 최종적으로는 &lt;b&gt;이미 등록된 고정 VDI row의 exam_id만 업데이트하는 방식&lt;/b&gt;으로 수정했다.&lt;/p&gt;
&lt;hr data-end=&quot;2654&quot; data-start=&quot;2651&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;2673&quot; data-start=&quot;2656&quot; data-section-id=&quot;1tho6d2&quot; data-ke-size=&quot;size26&quot;&gt;5. 백엔드 프로젝트 생성&lt;/h2&gt;
&lt;p data-end=&quot;2718&quot; data-start=&quot;2675&quot; data-ke-size=&quot;size16&quot;&gt;FastAPI 백엔드 프로젝트는 서버의 애플리케이션 전용 디렉터리에 구성했다.&lt;/p&gt;
&lt;p data-end=&quot;2749&quot; data-start=&quot;2720&quot; data-ke-size=&quot;size16&quot;&gt;공개 블로그에서는 실제 경로 대신 다음처럼 표현한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;mkdir -p &amp;lt;BACKEND_PROJECT_DIR&amp;gt;
cd &amp;lt;BACKEND_PROJECT_DIR&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2838&quot; data-start=&quot;2820&quot; data-ke-size=&quot;size16&quot;&gt;Python 가상환경을 생성했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;gradle&quot;&gt;&lt;code&gt;python3 -m venv venv
source venv/bin/activate&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3010&quot; data-start=&quot;2899&quot; data-ke-size=&quot;size16&quot;&gt;가상환경을 사용하는 이유는 서버 전체 Python 환경과 프로젝트 의존성을 분리하기 위해서다. 이렇게 하면 특정 프로젝트에서 설치한 패키지가 다른 프로젝트나 시스템 Python에 영향을 주지 않는다.&lt;/p&gt;
&lt;hr data-end=&quot;3015&quot; data-start=&quot;3012&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;3036&quot; data-start=&quot;3017&quot; data-section-id=&quot;7kp2t5&quot; data-ke-size=&quot;size26&quot;&gt;6. Python 패키지 설치&lt;/h2&gt;
&lt;p data-end=&quot;3070&quot; data-start=&quot;3038&quot; data-ke-size=&quot;size16&quot;&gt;FastAPI 백엔드 개발을 위해 다음 패키지를 설치했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;cmake&quot;&gt;&lt;code&gt;pip install fastapi uvicorn pymysql python-dotenv&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3153&quot; data-start=&quot;3135&quot; data-ke-size=&quot;size16&quot;&gt;각 패키지의 역할은 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;mel&quot;&gt;&lt;code&gt;fastapi
&amp;rarr; API 서버 프레임워크

uvicorn
&amp;rarr; FastAPI 앱 실행 서버

pymysql
&amp;rarr; MariaDB 연결 라이브러리

python-dotenv
&amp;rarr; .env 파일에서 환경변수 로드&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3347&quot; data-start=&quot;3281&quot; data-ke-size=&quot;size16&quot;&gt;설치 후에는 main.py에서 FastAPI 앱을 구성하고, DB 접속 정보를 .env에서 불러오도록 구성했다.&lt;/p&gt;
&lt;hr data-end=&quot;3352&quot; data-start=&quot;3349&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;3370&quot; data-start=&quot;3354&quot; data-section-id=&quot;1mslywc&quot; data-ke-size=&quot;size26&quot;&gt;7. 환경변수 파일 구성&lt;/h2&gt;
&lt;p data-end=&quot;3411&quot; data-start=&quot;3372&quot; data-ke-size=&quot;size16&quot;&gt;DB 접속 정보는 코드에 직접 적지 않고 .env 파일로 분리했다.&lt;/p&gt;
&lt;p data-end=&quot;3428&quot; data-start=&quot;3413&quot; data-ke-size=&quot;size16&quot;&gt;공개용 예시는 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;DB_HOST=&amp;lt;DB_HOST&amp;gt;
DB_PORT=3306
DB_USER=&amp;lt;DB_USER&amp;gt;
DB_PASSWORD=&amp;lt;DB_PASSWORD&amp;gt;
DB_NAME=&amp;lt;DB_NAME&amp;gt;

STATE_OUT_ID=1
STATE_ACTIVE_ID=2&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3685&quot; data-start=&quot;3569&quot; data-ke-size=&quot;size16&quot;&gt;실제 개발 중에는 같은 서버 안에서 FastAPI와 MariaDB가 동작했기 때문에 DB 호스트는 내부 접속 주소를 사용했다. 공개 블로그에서는 실제 DB명이나 계정명, 비밀번호 유무를 절대 노출하지 않는다.&lt;/p&gt;
&lt;p data-end=&quot;3720&quot; data-start=&quot;3687&quot; data-ke-size=&quot;size16&quot;&gt;.env 파일은 민감 정보를 포함하므로 권한도 제한했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;프론트엔드 개발을 할때에도 민감한 정보들은 전부 env에 넣어서 작업을 하고&amp;nbsp;&lt;/div&gt;
&lt;div&gt;gitignore에 env를 포함시키면서 작업을 했었다.&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;chmod 600 &amp;lt;BACKEND_PROJECT_DIR&amp;gt;/.env&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;3775&quot; data-start=&quot;3772&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;3791&quot; data-start=&quot;3777&quot; data-section-id=&quot;1mlyy60&quot; data-ke-size=&quot;size26&quot;&gt;8. DB 연결 확인&lt;/h2&gt;
&lt;p data-end=&quot;3840&quot; data-start=&quot;3793&quot; data-ke-size=&quot;size16&quot;&gt;DB 연결 전에는 MariaDB에 직접 접속해 데이터베이스와 테이블 상태를 확인했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;mysql -u &amp;lt;DB_USER&amp;gt; -p&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3883&quot; data-start=&quot;3877&quot; data-ke-size=&quot;size16&quot;&gt;DB 선택:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;USE &amp;lt;DB_NAME&amp;gt;;
SHOW TABLES;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3946&quot; data-start=&quot;3925&quot; data-ke-size=&quot;size16&quot;&gt;이 단계에서 확인한 것은 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1. 프로젝트용 DB가 존재하는지
2. 계정/교수/학생/시험/VDI 세션 관련 테이블이 존재하는지
3. VDI 세션 테이블에 고정 VDI row가 미리 등록되어 있는지&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4153&quot; data-start=&quot;4055&quot; data-ke-size=&quot;size16&quot;&gt;초기에는 DB 이름을 잘못 입력해 접속 오류가 발생했다. 이 경험을 통해 실제 운영에서는 DB명과 .env 값이 정확히 일치하는지 먼저 확인해야 한다는 것을 알 수 있었다.&lt;/p&gt;
&lt;hr data-end=&quot;4158&quot; data-start=&quot;4155&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;4182&quot; data-start=&quot;4160&quot; data-section-id=&quot;11csgnt&quot; data-ke-size=&quot;size26&quot;&gt;9. 한글 저장 오류와 문자셋 수정&lt;/h2&gt;
&lt;p data-end=&quot;4261&quot; data-start=&quot;4184&quot; data-ke-size=&quot;size16&quot;&gt;교수자 이름, 학생 이름, 학과명, 과목명 등 한글 데이터가 DB에 저장되어야 했다. 그런데 초기 테스트 중 다음과 같은 오류가 발생했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;Incorrect string value&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4343&quot; data-start=&quot;4299&quot; data-ke-size=&quot;size16&quot;&gt;원인은 일부 DB 또는 테이블의 문자셋이 한글 저장에 적합하지 않았기 때문이다.&lt;/p&gt;
&lt;p data-end=&quot;4387&quot; data-start=&quot;4345&quot; data-ke-size=&quot;size16&quot;&gt;그래서 주요 한글 입력 테이블을 utf8mb4 기반 문자셋으로 변환했다.&lt;/p&gt;
&lt;p data-end=&quot;4408&quot; data-start=&quot;4389&quot; data-ke-size=&quot;size16&quot;&gt;공개용 SQL 예시는 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;ALTER DATABASE &amp;lt;DB_NAME&amp;gt;
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;

ALTER TABLE &amp;lt;USER_TABLE&amp;gt;
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;

ALTER TABLE &amp;lt;STUDENT_PROFILE_TABLE&amp;gt;
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;

ALTER TABLE &amp;lt;PROFESSOR_PROFILE_TABLE&amp;gt;
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;

ALTER TABLE &amp;lt;EXAM_INFO_TABLE&amp;gt;
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;5023&quot; data-start=&quot;4874&quot; data-ke-size=&quot;size16&quot;&gt;일부 테이블은 외래키 제약 때문에 바로 변환할 수 없었다. 예를 들어 세션 ID가 여러 테이블에서 외래키로 참조되고 있으면 컬럼 변경이 제한된다. 이 경우에는 한글이 실제로 저장되는 주요 테이블을 우선 변환하고, FK가 걸린 테이블은 별도 절차가 필요하다고 판단했다.&lt;/p&gt;
&lt;hr data-end=&quot;5028&quot; data-start=&quot;5025&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;5050&quot; data-start=&quot;5030&quot; data-section-id=&quot;qk6u2u&quot; data-ke-size=&quot;size26&quot;&gt;10. FastAPI 기본 구조&lt;/h2&gt;
&lt;p data-end=&quot;5076&quot; data-start=&quot;5052&quot; data-ke-size=&quot;size16&quot;&gt;백엔드의 핵심 파일은 main.py였다.&lt;/p&gt;
&lt;p data-end=&quot;5094&quot; data-start=&quot;5078&quot; data-ke-size=&quot;size16&quot;&gt;구성한 API는 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;GET /api/health
&amp;rarr; 서버 상태 확인

POST /api/professor/setup
&amp;rarr; 교수 정보 저장 또는 수정

POST /api/exam/setup
&amp;rarr; 시험 정보 저장 또는 수정

GET /api/exam/latest
&amp;rarr; 최신 시험 정보 조회

POST /api/vdi/login
&amp;rarr; 학생 VDI 로그인

POST /api/vdi/heartbeat
&amp;rarr; 학생 VDI 접속 상태 갱신

GET /api/admin/exam
&amp;rarr; 대시보드 시험 정보 조회

GET /api/admin/sessions
&amp;rarr; 대시보드 좌석 목록 조회

GET /api/admin/summary
&amp;rarr; 대시보드 요약 정보 조회&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;5479&quot; data-start=&quot;5450&quot; data-ke-size=&quot;size16&quot;&gt;초기에는 API 서버를 직접 다음 명령어로 실행했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;uvicorn main:app --host 127.0.0.1 --port 8000&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;5585&quot; data-start=&quot;5540&quot; data-ke-size=&quot;size16&quot;&gt;하지만 운영 단계에서는 이 방식을 사용하지 않고 systemd 서비스로 등록했다.&lt;/p&gt;
&lt;hr data-end=&quot;5590&quot; data-start=&quot;5587&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;5613&quot; data-start=&quot;5592&quot; data-section-id=&quot;30n3rm&quot; data-ke-size=&quot;size26&quot;&gt;11. JSON 직렬화 오류 해결&lt;/h2&gt;
&lt;p data-end=&quot;5648&quot; data-start=&quot;5615&quot; data-ke-size=&quot;size16&quot;&gt;대시보드에서 요약 API를 호출할 때 다음 오류가 발생했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Object of type Decimal is not JSON serializable&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;5828&quot; data-start=&quot;5711&quot; data-ke-size=&quot;size16&quot;&gt;원인은 MariaDB에서 COUNT, SUM 등의 결과가 Python의 Decimal 타입으로 넘어왔고, FastAPI의 JSONResponse가 이를 바로 JSON으로 변환하지 못했기 때문이다.&lt;/p&gt;
&lt;p data-end=&quot;5845&quot; data-start=&quot;5830&quot; data-ke-size=&quot;size16&quot;&gt;해결 방향은 다음과 같았다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1. Decimal 값을 int 또는 float로 변환한다.
2. datetime 값은 문자열로 변환한다.
3. dict/list 내부에 중첩된 값까지 재귀적으로 변환한다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;5975&quot; data-start=&quot;5957&quot; data-ke-size=&quot;size16&quot;&gt;공개용 예시 코드는 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;xquery&quot;&gt;&lt;code&gt;def json_safe(value):
    if value is None:
        return None

    if isinstance(value, Decimal):
        if value == value.to_integral_value():
            return int(value)
        return float(value)

    if hasattr(value, &quot;isoformat&quot;):
        return value.isoformat(sep=&quot; &quot;, timespec=&quot;seconds&quot;)

    return value


def to_jsonable(value):
    if isinstance(value, dict):
        return {key: to_jsonable(val) for key, val in value.items()}

    if isinstance(value, list):
        return [to_jsonable(item) for item in value]

    return json_safe(value)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;6622&quot; data-start=&quot;6554&quot; data-ke-size=&quot;size16&quot;&gt;이후 모든 API 응답을 만들 때 to_jsonable()을 거치도록 수정해 Decimal 직렬화 오류를 해결했다.&lt;/p&gt;
&lt;hr data-end=&quot;6627&quot; data-start=&quot;6624&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;6653&quot; data-start=&quot;6629&quot; data-section-id=&quot;1ix0x9i&quot; data-ke-size=&quot;size26&quot;&gt;12. Nginx와 FastAPI 연결&lt;/h2&gt;
&lt;p data-end=&quot;6707&quot; data-start=&quot;6655&quot; data-ke-size=&quot;size16&quot;&gt;Nginx는 정적 파일을 제공하고, /api/ 요청만 FastAPI로 전달하도록 설정했다.&lt;/p&gt;
&lt;p data-end=&quot;6733&quot; data-start=&quot;6709&quot; data-ke-size=&quot;size16&quot;&gt;공개용 Nginx 설정 예시는 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;location /api/ {
    proxy_pass http://127.0.0.1:8000;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;6970&quot; data-start=&quot;6949&quot; data-ke-size=&quot;size16&quot;&gt;이 설정을 추가한 이유는 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;/login/, /home/, /professor/, /exam/
&amp;rarr; Nginx가 정적 HTML/CSS/JS 제공

/api/...
&amp;rarr; Nginx가 FastAPI로 프록시 전달&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;7197&quot; data-start=&quot;7084&quot; data-ke-size=&quot;size16&quot;&gt;특히 X-Real-IP를 전달한 이유가 중요하다. 학생들이 VDI 내부에서 같은 로그인 URL로 접속할 때, 백엔드가 실제 요청 IP를 확인해야 어느 VDI에서 로그인했는지 식별할 수 있기 때문이다.&lt;/p&gt;
&lt;p data-end=&quot;7231&quot; data-start=&quot;7199&quot; data-ke-size=&quot;size16&quot;&gt;설정 변경 후에는 다음 명령어로 문법을 확인하고 적용했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;nginx -t
systemctl reload nginx&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;7281&quot; data-start=&quot;7278&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;7311&quot; data-start=&quot;7283&quot; data-section-id=&quot;z1x5y6&quot; data-ke-size=&quot;size26&quot;&gt;13. Nginx 편집 중 swap 파일 오류&lt;/h2&gt;
&lt;p data-end=&quot;7354&quot; data-start=&quot;7313&quot; data-ke-size=&quot;size16&quot;&gt;Nginx 설정 파일을 편집하다가 vi swap 파일 경고가 발생했다.&lt;/p&gt;
&lt;p data-end=&quot;7408&quot; data-start=&quot;7356&quot; data-ke-size=&quot;size16&quot;&gt;원인은 이전 편집 세션이 비정상 종료되었거나, 누군가 같은 파일을 동시에 편집했기 때문이었다.&lt;/p&gt;
&lt;p data-end=&quot;7425&quot; data-start=&quot;7410&quot; data-ke-size=&quot;size16&quot;&gt;해결 절차는 다음과 같았다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1. 현재 해당 파일을 편집 중인 프로세스가 있는지 확인
2. 실제 편집 중인 사람이 없는지 확인
3. 설정 파일 백업
4. swap 파일 삭제
5. 다시 편집&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;7546&quot; data-start=&quot;7530&quot; data-ke-size=&quot;size16&quot;&gt;사용한 명령어는 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;mel&quot;&gt;&lt;code&gt;ps -ef | grep &amp;lt;CONFIG_FILE_NAME&amp;gt;
who
cp &amp;lt;NGINX_CONFIG_PATH&amp;gt; &amp;lt;NGINX_CONFIG_PATH&amp;gt;.bak_$(date +%Y%m%d_%H%M%S)
rm -f &amp;lt;NGINX_SWAP_FILE&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;7771&quot; data-start=&quot;7692&quot; data-ke-size=&quot;size16&quot;&gt;여러 명이 같은 서버에서 작업할 때는 이런 충돌이 자주 발생할 수 있으므로, 설정 파일은 동시에 편집하지 않도록 규칙을 정하는 것이 중요하다.&lt;/p&gt;
&lt;hr data-end=&quot;7776&quot; data-start=&quot;7773&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;7800&quot; data-start=&quot;7778&quot; data-section-id=&quot;112vza5&quot; data-ke-size=&quot;size26&quot;&gt;14. Nginx 50x 오류 해결&lt;/h2&gt;
&lt;p data-end=&quot;7851&quot; data-start=&quot;7802&quot; data-ke-size=&quot;size16&quot;&gt;초기에는 /api/health를 호출했을 때 Nginx 기본 50x 페이지가 보였다.&lt;/p&gt;
&lt;p data-end=&quot;7869&quot; data-start=&quot;7853&quot; data-ke-size=&quot;size16&quot;&gt;가능한 원인은 다음과 같았다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1. FastAPI가 8000번 포트에서 실행 중이 아님
2. Nginx가 FastAPI로 프록시하지 못함
3. Nginx 설정 변경 후 reload를 하지 않음
4. 방화벽 또는 SELinux 문제&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;8011&quot; data-start=&quot;7996&quot; data-ke-size=&quot;size16&quot;&gt;확인 순서는 다음과 같았다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;curl http://127.0.0.1:8000/api/health
curl http://127.0.0.1/api/health&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;8111&quot; data-start=&quot;8097&quot; data-ke-size=&quot;size16&quot;&gt;판단 기준은 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;127.0.0.1:8000이 실패
&amp;rarr; FastAPI 자체가 꺼져 있음

127.0.0.1:8000은 성공하지만 127.0.0.1/api/health가 실패
&amp;rarr; Nginx 프록시 설정 문제&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;8234&quot; data-start=&quot;8231&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;8255&quot; data-start=&quot;8236&quot; data-section-id=&quot;ncmp1a&quot; data-ke-size=&quot;size26&quot;&gt;15. 학생 로그인 방식 변경&lt;/h2&gt;
&lt;p data-end=&quot;8294&quot; data-start=&quot;8257&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 학생 로그인 URL에 좌석 번호를 붙이는 방식을 생각했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;/login/?exam_id=&amp;lt;EXAM_ID&amp;gt;&amp;amp;seat_no=&amp;lt;SEAT_NO&amp;gt;&amp;amp;vdi_name=&amp;lt;VDI_NAME&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;8490&quot; data-start=&quot;8373&quot; data-ke-size=&quot;size16&quot;&gt;하지만 실제 운영에서는 학생에게 URL을 다르게 제공하는 구조가 적합하지 않았다. 학생들은 이미 각자 다른 VDI IP/PW로 접속하므로, VDI 안에서는 모두 같은 로그인 페이지가 뜨는 것이 더 자연스러웠다.&lt;/p&gt;
&lt;p data-end=&quot;8506&quot; data-start=&quot;8492&quot; data-ke-size=&quot;size16&quot;&gt;최종 방식은 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;모든 VDI
&amp;rarr; 같은 /login/ URL 사용

학생 로그인
&amp;rarr; 이름/학번 입력

백엔드
&amp;rarr; 요청 IP 확인
&amp;rarr; VDI_SESSION.vdi_ip와 매칭
&amp;rarr; 해당 VDI row ACTIVE 처리&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;8679&quot; data-start=&quot;8631&quot; data-ke-size=&quot;size16&quot;&gt;즉 seat_no 기반 로그인에서 request_ip 기반 로그인으로 변경했다.&lt;/p&gt;
&lt;hr data-end=&quot;8684&quot; data-start=&quot;8681&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;8705&quot; data-start=&quot;8686&quot; data-section-id=&quot;10a1307&quot; data-ke-size=&quot;size26&quot;&gt;16. VDI 내부 IP 확인&lt;/h2&gt;
&lt;p data-end=&quot;8757&quot; data-start=&quot;8707&quot; data-ke-size=&quot;size16&quot;&gt;같은 로그인 URL을 사용하려면, 서버에서 VDI별 IP가 다르게 보이는지 확인해야 했다.&lt;/p&gt;
&lt;p data-end=&quot;8787&quot; data-start=&quot;8759&quot; data-ke-size=&quot;size16&quot;&gt;서버에서 Nginx access log를 확인했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;tail -f /var/log/nginx/access.log&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;8873&quot; data-start=&quot;8836&quot; data-ke-size=&quot;size16&quot;&gt;VDI 여러 대에서 /login/에 접속한 뒤 로그를 확인했다.&lt;/p&gt;
&lt;p data-end=&quot;8899&quot; data-start=&quot;8875&quot; data-ke-size=&quot;size16&quot;&gt;공개용 로그 예시는 다음과 같이 마스킹했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;accesslog&quot;&gt;&lt;code&gt;&amp;lt;VDI_PRIVATE_IP_A&amp;gt; - - [timestamp] &quot;GET /login/ HTTP/1.1&quot; 200
&amp;lt;VDI_PRIVATE_IP_B&amp;gt; - - [timestamp] &quot;GET /login/ HTTP/1.1&quot; 200&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;9103&quot; data-start=&quot;9038&quot; data-ke-size=&quot;size16&quot;&gt;로그에서 VDI별 내부 IP가 다르게 찍히는 것을 확인했고, 따라서 vdi_ip 기반 매핑이 가능하다고 판단했다.&lt;/p&gt;
&lt;hr data-end=&quot;9108&quot; data-start=&quot;9105&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;9133&quot; data-start=&quot;9110&quot; data-section-id=&quot;dg89kg&quot; data-ke-size=&quot;size26&quot;&gt;17. VDI 내부 접속 URL 결정&lt;/h2&gt;
&lt;p data-end=&quot;9250&quot; data-start=&quot;9135&quot; data-ke-size=&quot;size16&quot;&gt;VDI가 내부 NAT 네트워크에 있기 때문에 외부 공인 IP로 접속하는 것이 항상 안정적이지는 않았다. 테스트 결과, VDI 내부에서는 VDI 게이트웨이 또는 호스트 내부 주소를 사용하는 것이 더 적합했다.&lt;/p&gt;
&lt;p data-end=&quot;9259&quot; data-start=&quot;9252&quot; data-ke-size=&quot;size16&quot;&gt;공개용 표현:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;교수자 또는 외부 관리 PC
&amp;rarr; http://&amp;lt;SERVER_PUBLIC_IP&amp;gt;/home/

학생 VDI 내부
&amp;rarr; http://&amp;lt;VDI_GATEWAY_IP&amp;gt;/login/&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;9404&quot; data-start=&quot;9368&quot; data-ke-size=&quot;size16&quot;&gt;즉 교수자와 학생 VDI의 접속 URL을 다르게 운용할 수 있다.&lt;/p&gt;
&lt;hr data-end=&quot;9409&quot; data-start=&quot;9406&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;9438&quot; data-start=&quot;9411&quot; data-section-id=&quot;jbtiuc&quot; data-ke-size=&quot;size26&quot;&gt;18. VDI_SESSION 사용 방식 수정&lt;/h2&gt;
&lt;p data-end=&quot;9477&quot; data-start=&quot;9440&quot; data-ke-size=&quot;size16&quot;&gt;가장 중요한 설계 변경은 VDI_SESSION 처리 방식이었다.&lt;/p&gt;
&lt;p data-end=&quot;9517&quot; data-start=&quot;9479&quot; data-ke-size=&quot;size16&quot;&gt;초기 방식은 시험 생성 시 다음과 같은 새 row를 만드는 것이었다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;EXAM&amp;lt;EXAM_ID&amp;gt;-VDI01
EXAM&amp;lt;EXAM_ID&amp;gt;-VDI02
EXAM&amp;lt;EXAM_ID&amp;gt;-VDI03&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;9628&quot; data-start=&quot;9592&quot; data-ke-size=&quot;size16&quot;&gt;하지만 실제 DB에는 이미 고정 VDI row가 등록되어 있었다.&lt;/p&gt;
&lt;p data-end=&quot;9637&quot; data-start=&quot;9630&quot; data-ke-size=&quot;size16&quot;&gt;공개용 예시:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;VDI-1 | seat_no=1 | vdi_ip=&amp;lt;VDI_PRIVATE_IP_1&amp;gt;
VDI-2 | seat_no=2 | vdi_ip=&amp;lt;VDI_PRIVATE_IP_2&amp;gt;
VDI-3 | seat_no=3 | vdi_ip=&amp;lt;VDI_PRIVATE_IP_3&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;9811&quot; data-start=&quot;9790&quot; data-ke-size=&quot;size16&quot;&gt;따라서 최종 방식은 다음처럼 수정했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;시험 생성/수정 시 새 VDI_SESSION row 생성 X

기존 VDI-1, VDI-2, VDI-3 row 유지

해당 row들의 exam_id만 현재 시험 ID로 UPDATE&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;10004&quot; data-start=&quot;9927&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 하면 VDI IP 매핑 정보를 잃지 않고, 학생 로그인 시 request_ip와 vdi_ip를 비교해 좌석을 찾을 수 있다.&lt;/p&gt;
&lt;hr data-end=&quot;10009&quot; data-start=&quot;10006&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;10038&quot; data-start=&quot;10011&quot; data-section-id=&quot;1x5l7kn&quot; data-ke-size=&quot;size26&quot;&gt;19. 시험 정보가 계속 추가되는 문제 해결&lt;/h2&gt;
&lt;p data-end=&quot;10100&quot; data-start=&quot;10040&quot; data-ke-size=&quot;size16&quot;&gt;초기에는 /exam/에서 시험 정보를 저장할 때마다 EXAM_INFO에 새 row가 계속 추가되었다.&lt;/p&gt;
&lt;p data-end=&quot;10134&quot; data-start=&quot;10102&quot; data-ke-size=&quot;size16&quot;&gt;원인은 API가 매번 INSERT만 수행했기 때문이다.&lt;/p&gt;
&lt;p data-end=&quot;10150&quot; data-start=&quot;10136&quot; data-ke-size=&quot;size16&quot;&gt;수정 방향은 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1. 프론트에서 localStorage에 exam_id가 있으면 함께 전송한다.
2. 백엔드는 exam_id가 있으면 해당 시험 UPDATE
3. exam_id가 없으면 해당 교수의 최신 시험 조회
4. 최신 시험이 있으면 UPDATE
5. 기존 시험이 전혀 없으면 최초 1회만 INSERT&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;10415&quot; data-start=&quot;10328&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 수정한 이유는 교수자가 시험 정보를 다시 입력할 때마다 새로운 시험이 생성되는 것이 아니라, 같은 시험 정보가 수정되는 것이 더 자연스럽기 때문이다.&lt;/p&gt;
&lt;hr data-end=&quot;10420&quot; data-start=&quot;10417&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;10452&quot; data-start=&quot;10422&quot; data-section-id=&quot;qri7z5&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;20. /api/exam/setup 최종 정책&lt;/span&gt;&lt;/h2&gt;
&lt;p data-end=&quot;10487&quot; data-start=&quot;10454&quot; data-ke-size=&quot;size16&quot;&gt;최종 /api/exam/setup의 정책은 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1. 교수 정보가 존재하는지 확인한다.
2. exam_id가 전달되면 해당 시험을 찾는다.
3. exam_id가 없으면 해당 교수의 최신 시험을 찾는다.
4. 기존 시험이 있으면 EXAM_INFO를 UPDATE한다.
5. 없으면 최초 1회 INSERT한다.
6. 이전 로직으로 잘못 생성된 EXAM* 세션 row를 정리한다.
7. 기존 VDI-1~VDI-N row에 현재 exam_id를 연결한다.
8. 정원보다 큰 VDI row는 이번 시험에서 제외한다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;10800&quot; data-start=&quot;10758&quot; data-ke-size=&quot;size16&quot;&gt;여기서 핵심은 &lt;b&gt;새 VDI_SESSION row를 만들지 않는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;hr data-end=&quot;10805&quot; data-start=&quot;10802&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;10830&quot; data-start=&quot;10807&quot; data-section-id=&quot;11a0hkx&quot; data-ke-size=&quot;size26&quot;&gt;21. 학생 로그인 API 최종 정책&lt;/h2&gt;
&lt;p data-end=&quot;10864&quot; data-start=&quot;10832&quot; data-ke-size=&quot;size16&quot;&gt;최종 /api/vdi/login의 정책은 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;1. 이름 검증
   - 공백 제거
   - 4글자 이하

2. 학번 검증
   - 숫자만 허용
   - 9자리

3. 시험 정보 존재 확인

4. 계정 테이블에서 학생 계정 조회
   - 없으면 생성
   - 있으면 재사용

5. 학생 프로필 조회
   - 없으면 생성
   - 있으면 이름 업데이트 가능

6. VDI 세션 조회
   - seat_no가 있으면 exam_id + seat_no로 조회
   - seat_no가 없으면 exam_id + request_ip로 조회

7. 이미 다른 학생이 사용 중이면 차단

8. 정상 로그인 시
   - student_id 연결
   - state_id를 ACTIVE로 변경
   - started_at, last_heartbeat 업데이트
   - 상태 변경 이력 기록&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;11341&quot; data-start=&quot;11284&quot; data-ke-size=&quot;size16&quot;&gt;학생은 같은 /login/ URL을 사용하고, 실제 VDI 구분은 백엔드에서 요청 IP로 처리한다.&lt;/p&gt;
&lt;hr data-end=&quot;11346&quot; data-start=&quot;11343&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;11369&quot; data-start=&quot;11348&quot; data-section-id=&quot;sf6jbn&quot; data-ke-size=&quot;size26&quot;&gt;22. 학생 로그인 예외처리 문제&lt;/h2&gt;
&lt;p data-end=&quot;11414&quot; data-start=&quot;11371&quot; data-ke-size=&quot;size16&quot;&gt;학생 로그인 화면에서 학번을 9자리 입력해도 8자리로 인식되는 문제가 있었다.&lt;/p&gt;
&lt;p data-end=&quot;11450&quot; data-start=&quot;11416&quot; data-ke-size=&quot;size16&quot;&gt;원인은 HTML input의 maxlength=&quot;9&quot;였다.&lt;/p&gt;
&lt;p data-end=&quot;11477&quot; data-start=&quot;11452&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 사용자가 다음 값을 붙여넣는 경우:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;2024-12345&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;11521&quot; data-start=&quot;11503&quot; data-ke-size=&quot;size16&quot;&gt;브라우저가 먼저 9글자로 자르면:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;2024-1234&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;11573&quot; data-start=&quot;11546&quot; data-ke-size=&quot;size16&quot;&gt;가 되고, JS가 숫자만 추출하면 8자리가 된다.&lt;/p&gt;
&lt;p data-end=&quot;11593&quot; data-start=&quot;11575&quot; data-ke-size=&quot;size16&quot;&gt;그래서 해결 방식은 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1. HTML input에서 maxlength 제거
2. JS에서 숫자만 추출
3. JS에서 slice(0, 9)로 제한
4. 최종적으로 정확히 9자리인지 검증&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;11719&quot; data-start=&quot;11698&quot; data-ke-size=&quot;size16&quot;&gt;공개용 JS 로직 예시는 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function normalizeStudentNo(value) {
  return String(value || &quot;&quot;).replace(/\D/g, &quot;&quot;).slice(0, 9);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;11913&quot; data-start=&quot;11840&quot; data-ke-size=&quot;size16&quot;&gt;그리고 기존에는 seat_no가 없으면 로그인 자체를 막았지만, 최종 구조에서는 같은 URL을 사용하므로 해당 검사를 제거했다.&lt;/p&gt;
&lt;hr data-end=&quot;11918&quot; data-start=&quot;11915&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;11950&quot; data-start=&quot;11920&quot; data-section-id=&quot;83vgqz&quot; data-ke-size=&quot;size26&quot;&gt;23. 로그인 페이지 localStorage 사용&lt;/h2&gt;
&lt;p data-end=&quot;11980&quot; data-start=&quot;11952&quot; data-ke-size=&quot;size16&quot;&gt;로그인 성공 후 브라우저에는 다음 정보를 저장했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;vdi_session_id
student_no
student_name
exam_id
seat_no
vdi_ip
request_ip&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;12102&quot; data-start=&quot;12068&quot; data-ke-size=&quot;size16&quot;&gt;공개 블로그에서는 실제 값은 노출하지 않고, 역할만 설명한다.&lt;/p&gt;
&lt;p data-end=&quot;12168&quot; data-start=&quot;12104&quot; data-ke-size=&quot;size16&quot;&gt;이 값을 저장한 이유는 이후 heartbeat 또는 세션 연동 기능에서 현재 로그인 세션을 계속 추적하기 위해서다.&lt;/p&gt;
&lt;p data-end=&quot;12189&quot; data-start=&quot;12170&quot; data-ke-size=&quot;size16&quot;&gt;또한 브라우저 UUID도 생성했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;vdi_browser_uuid&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;12355&quot; data-start=&quot;12221&quot; data-ke-size=&quot;size16&quot;&gt;단, 브라우저 UUID는 내부 API 요청 식별에는 사용할 수 있지만, 일반 웹페이지 JS만으로는 외부 사이트 전체 요청에 헤더를 삽입할 수 없기 때문에 프록시 전체 트래픽 식별의 핵심 수단으로는 VDI IP 매핑이 더 적합하다고 판단했다.&lt;/p&gt;
&lt;hr data-end=&quot;12360&quot; data-start=&quot;12357&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;12380&quot; data-start=&quot;12362&quot; data-section-id=&quot;n7igzs&quot; data-ke-size=&quot;size26&quot;&gt;24. 대시보드 API 연동&lt;/h2&gt;
&lt;p data-end=&quot;12401&quot; data-start=&quot;12382&quot; data-ke-size=&quot;size16&quot;&gt;대시보드는 다음 API를 호출한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;GET /api/admin/exam?exam_id=&amp;lt;EXAM_ID&amp;gt;
&amp;rarr; 시험 정보 조회

GET /api/admin/sessions?exam_id=&amp;lt;EXAM_ID&amp;gt;
&amp;rarr; 좌석별 VDI 세션 목록 조회

GET /api/admin/summary?exam_id=&amp;lt;EXAM_ID&amp;gt;
&amp;rarr; 전체 좌석 수, 접속 수, 미접속 수 조회&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;12618&quot; data-start=&quot;12595&quot; data-ke-size=&quot;size16&quot;&gt;좌석 상태 색상은 다음 기준으로 설계했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;OUT
&amp;rarr; 회색

ACTIVE
&amp;rarr; 초록

주의/재접속
&amp;rarr; 노랑

AI 탐지/차단
&amp;rarr; 빨강&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;12760&quot; data-start=&quot;12683&quot; data-ke-size=&quot;size16&quot;&gt;현재 구현 단계에서는 OUT, ACTIVE를 우선 사용했다. 추후 프록시나 탐지 로직과 연결하면 AI 탐지 상태를 추가할 수 있다.&lt;/p&gt;
&lt;hr data-end=&quot;12765&quot; data-start=&quot;12762&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;12796&quot; data-start=&quot;12767&quot; data-section-id=&quot;nxo7vp&quot; data-ke-size=&quot;size26&quot;&gt;25. FastAPI systemd 서비스 등록&lt;/h2&gt;
&lt;p data-end=&quot;12829&quot; data-start=&quot;12798&quot; data-ke-size=&quot;size16&quot;&gt;개발 초기에는 터미널에서 직접 FastAPI를 실행했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;uvicorn main:app --host 127.0.0.1 --port 8000&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;12937&quot; data-start=&quot;12890&quot; data-ke-size=&quot;size16&quot;&gt;하지만 이 방식은 터미널이 닫히면 서버도 종료된다. 실제 시험 환경에서는 부적합하다.&lt;/p&gt;
&lt;p data-end=&quot;12961&quot; data-start=&quot;12939&quot; data-ke-size=&quot;size16&quot;&gt;그래서 systemd 서비스로 등록했다.&lt;/p&gt;
&lt;p data-end=&quot;12985&quot; data-start=&quot;12963&quot; data-ke-size=&quot;size16&quot;&gt;공개용 서비스 파일 예시는 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;[Unit]
Description=VDI Login FastAPI Backend
After=network.target mariadb.service

[Service]
WorkingDirectory=&amp;lt;BACKEND_PROJECT_DIR&amp;gt;
EnvironmentFile=&amp;lt;BACKEND_PROJECT_DIR&amp;gt;/.env
ExecStart=&amp;lt;BACKEND_PROJECT_DIR&amp;gt;/venv/bin/uvicorn main:app --host 127.0.0.1 --port 8000
Restart=always
RestartSec=3
User=&amp;lt;SERVICE_USER&amp;gt;

[Install]
WantedBy=multi-user.target&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;13354&quot; data-start=&quot;13347&quot; data-ke-size=&quot;size16&quot;&gt;등록 명령어:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;systemctl daemon-reload
systemctl start &amp;lt;FASTAPI_SERVICE_NAME&amp;gt;
systemctl enable &amp;lt;FASTAPI_SERVICE_NAME&amp;gt;
systemctl status &amp;lt;FASTAPI_SERVICE_NAME&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;13543&quot; data-start=&quot;13512&quot; data-ke-size=&quot;size16&quot;&gt;이제 서버가 재부팅되어도 FastAPI가 자동 실행된다.&lt;/p&gt;
&lt;hr data-end=&quot;13548&quot; data-start=&quot;13545&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;13565&quot; data-start=&quot;13550&quot; data-section-id=&quot;903tvn&quot; data-ke-size=&quot;size26&quot;&gt;26. 포트 중복 오류&lt;/h2&gt;
&lt;p data-end=&quot;13610&quot; data-start=&quot;13567&quot; data-ke-size=&quot;size16&quot;&gt;서비스 등록 후 직접 uvicorn을 다시 실행하자 다음 오류가 발생했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;address already in use&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;13717&quot; data-start=&quot;13648&quot; data-ke-size=&quot;size16&quot;&gt;원인은 systemd 서비스가 이미 8000번 포트를 사용 중인데, 같은 포트로 FastAPI를 또 실행하려 했기 때문이다.&lt;/p&gt;
&lt;p data-end=&quot;13726&quot; data-start=&quot;13719&quot; data-ke-size=&quot;size16&quot;&gt;확인 명령어:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;ss -ltnp | grep 8000
systemctl status &amp;lt;FASTAPI_SERVICE_NAME&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;13822&quot; data-start=&quot;13802&quot; data-ke-size=&quot;size16&quot;&gt;이후 운영 방식은 다음처럼 정리했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;직접 uvicorn 실행 X

코드 수정 후:
python -m py_compile main.py
systemctl restart &amp;lt;FASTAPI_SERVICE_NAME&amp;gt;
systemctl status &amp;lt;FASTAPI_SERVICE_NAME&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;13976&quot; data-start=&quot;13973&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;14008&quot; data-start=&quot;13978&quot; data-section-id=&quot;1vwqsma&quot; data-ke-size=&quot;size26&quot;&gt;27. 여러 명이 같은 서버에서 개발할 때의 문제&lt;/h2&gt;
&lt;p data-end=&quot;14050&quot; data-start=&quot;14010&quot; data-ke-size=&quot;size16&quot;&gt;팀원 여러 명이 같은 서버에서 작업하면서 다음 문제가 발생할 수 있었다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1. 같은 포트에 FastAPI 중복 실행
2. 같은 설정 파일 동시 편집
3. vi swap 파일 발생
4. main.py 수정 중 다른 사람이 서비스 재시작
5. DB 데이터를 서로 덮어씀&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;14189&quot; data-start=&quot;14173&quot; data-ke-size=&quot;size16&quot;&gt;그래서 작업 규칙을 정리했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1. FastAPI 실행은 systemd 서비스 하나만 사용한다.
2. uvicorn 직접 실행은 금지한다.
3. main.py 수정 전 백업한다.
4. 수정 후 문법 검사를 한다.
5. 이상 없을 때만 서비스를 재시작한다.
6. DB UPDATE/DELETE 전 SELECT로 대상 row를 먼저 확인한다.
7. Nginx 설정 파일은 동시에 편집하지 않는다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;14425&quot; data-start=&quot;14407&quot; data-ke-size=&quot;size16&quot;&gt;백업 명령어 예시는 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;mel&quot;&gt;&lt;code&gt;cp &amp;lt;BACKEND_PROJECT_DIR&amp;gt;/main.py &amp;lt;BACKEND_PROJECT_DIR&amp;gt;/main.py.bak_$(date +%Y%m%d_%H%M%S)
cp &amp;lt;LOGIN_JS_PATH&amp;gt; &amp;lt;LOGIN_JS_PATH&amp;gt;.bak_$(date +%Y%m%d_%H%M%S)
cp &amp;lt;EXAM_JS_PATH&amp;gt; &amp;lt;EXAM_JS_PATH&amp;gt;.bak_$(date +%Y%m%d_%H%M%S)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;14655&quot; data-start=&quot;14652&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;14675&quot; data-start=&quot;14657&quot; data-section-id=&quot;1v6jc5k&quot; data-ke-size=&quot;size26&quot;&gt;28. 운영 전 점검 명령어&lt;/h2&gt;
&lt;p data-end=&quot;14699&quot; data-start=&quot;14677&quot; data-ke-size=&quot;size16&quot;&gt;실제 사용 전에는 다음 순서로 확인한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;systemctl status nginx
systemctl status mariadb
systemctl status &amp;lt;FASTAPI_SERVICE_NAME&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;14808&quot; data-start=&quot;14802&quot; data-ke-size=&quot;size16&quot;&gt;포트 확인:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;gradle&quot;&gt;&lt;code&gt;ss -ltnp | grep ':80'
ss -ltnp | grep ':8000'&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;14876&quot; data-start=&quot;14869&quot; data-ke-size=&quot;size16&quot;&gt;API 확인:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;curl http://127.0.0.1/api/health
curl http://&amp;lt;SERVER_PUBLIC_IP&amp;gt;/api/health&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;14988&quot; data-start=&quot;14966&quot; data-ke-size=&quot;size16&quot;&gt;VDI 내부에서는 다음 주소로 확인한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;http://&amp;lt;VDI_GATEWAY_IP&amp;gt;/api/health&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;15044&quot; data-start=&quot;15038&quot; data-ke-size=&quot;size16&quot;&gt;로그 확인:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;tail -f &amp;lt;NGINX_ACCESS_LOG_PATH&amp;gt;
journalctl -u &amp;lt;FASTAPI_SERVICE_NAME&amp;gt; -f&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;15134&quot; data-start=&quot;15131&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;15154&quot; data-start=&quot;15136&quot; data-section-id=&quot;1rk29a3&quot; data-ke-size=&quot;size26&quot;&gt;29. 정상 DB 상태 예시&lt;/h2&gt;
&lt;p data-end=&quot;15192&quot; data-start=&quot;15156&quot; data-ke-size=&quot;size16&quot;&gt;최종적으로 VDI 세션 테이블은 다음과 같은 형태가 되어야 한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;session_id | exam_id     | student_id | state_id | seat_no | vdi_ip
VDI-1      | &amp;lt;EXAM_ID&amp;gt;   | NULL       | OUT      | 1       | &amp;lt;VDI_PRIVATE_IP_1&amp;gt;
VDI-2      | &amp;lt;EXAM_ID&amp;gt;   | NULL       | OUT      | 2       | &amp;lt;VDI_PRIVATE_IP_2&amp;gt;
VDI-3      | &amp;lt;EXAM_ID&amp;gt;   | NULL       | OUT      | 3       | &amp;lt;VDI_PRIVATE_IP_3&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;15545&quot; data-start=&quot;15515&quot; data-ke-size=&quot;size16&quot;&gt;학생이 두 번째 VDI에서 로그인하면 다음처럼 바뀐다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;session_id | exam_id   | student_id     | state_id | seat_no | vdi_ip
VDI-2      | &amp;lt;EXAM_ID&amp;gt; | &amp;lt;STUDENT_ID&amp;gt;   | ACTIVE   | 2       | &amp;lt;VDI_PRIVATE_IP_2&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;15742&quot; data-start=&quot;15712&quot; data-ke-size=&quot;size16&quot;&gt;대시보드에서는 해당 좌석이 회색에서 초록색으로 바뀐다.&lt;/p&gt;
&lt;hr data-end=&quot;15747&quot; data-start=&quot;15744&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;15765&quot; data-start=&quot;15749&quot; data-section-id=&quot;12kaygg&quot; data-ke-size=&quot;size26&quot;&gt;30. 최종 사용자 흐름&lt;/h2&gt;
&lt;p data-end=&quot;15785&quot; data-start=&quot;15767&quot; data-ke-size=&quot;size16&quot;&gt;최종 사용자 흐름은 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;lsl&quot;&gt;&lt;code&gt;1. 서버 부팅
2. Nginx, MariaDB, FastAPI 서비스 자동 실행
3. 교수자 /professor/ 접속
4. 교수 정보 입력
5. 교수자 /exam/ 접속
6. 시험 정보 입력
7. 기존 VDI row에 현재 exam_id 연결
8. 학생이 VDI 접속
9. VDI 내부에서 /login/ 자동 실행
10. 학생 이름/학번 입력
11. FastAPI가 요청 IP 확인
12. VDI 세션 테이블의 vdi_ip와 매칭
13. 해당 VDI row를 ACTIVE로 변경
14. 대시보드가 세션 API 조회
15. 해당 좌석이 초록색으로 표시&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;16113&quot; data-start=&quot;16110&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;16143&quot; data-start=&quot;16115&quot; data-section-id=&quot;1pslsic&quot; data-ke-size=&quot;size26&quot;&gt;31. 이번 개발에서 발생한 주요 오류와 해결&lt;/h2&gt;
&lt;h3 data-end=&quot;16160&quot; data-start=&quot;16145&quot; data-section-id=&quot;1obrx3o&quot; data-ke-size=&quot;size23&quot;&gt;1) DB 이름 오류&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;원인:
.env 또는 SQL에서 실제 DB 이름과 다른 값을 사용했다.

해결:
SHOW DATABASES로 실제 DB 이름을 확인하고 .env 값을 맞췄다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-end=&quot;16279&quot; data-start=&quot;16264&quot; data-section-id=&quot;260dvc&quot; data-ke-size=&quot;size23&quot;&gt;2) 한글 저장 오류&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;원인:
DB 또는 테이블 문자셋이 한글 저장에 적합하지 않았다.

해결:
주요 한글 저장 테이블을 utf8mb4 계열 문자셋으로 변환했다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-end=&quot;16397&quot; data-start=&quot;16372&quot; data-section-id=&quot;tzr6az&quot; data-ke-size=&quot;size23&quot;&gt;3) Decimal JSON 변환 오류&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;원인:
MariaDB 집계 결과가 Decimal 타입으로 반환되었고 JSONResponse가 변환하지 못했다.

해결:
Decimal, datetime을 JSON 가능한 값으로 바꾸는 변환 함수를 만들었다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-end=&quot;16547&quot; data-start=&quot;16528&quot; data-section-id=&quot;1j1f2ks&quot; data-ke-size=&quot;size23&quot;&gt;4) Nginx 50x 오류&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;원인:
FastAPI가 꺼져 있거나 Nginx 프록시 설정이 올바르지 않았다.

해결:
FastAPI 직접 호출과 Nginx 경유 호출을 분리해서 확인했다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-end=&quot;16670&quot; data-start=&quot;16650&quot; data-section-id=&quot;i8pnrw&quot; data-ke-size=&quot;size23&quot;&gt;5) vi swap 파일 오류&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;원인:
설정 파일 편집 세션이 비정상 종료되었거나 동시 편집 가능성이 있었다.

해결:
프로세스 확인 후 백업하고 swap 파일을 정리했다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-end=&quot;16793&quot; data-start=&quot;16764&quot; data-section-id=&quot;1wsgafh&quot; data-ke-size=&quot;size23&quot;&gt;6) address already in use&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;원인:
FastAPI 서비스가 이미 8000번 포트를 사용 중인데 직접 uvicorn을 다시 실행했다.

해결:
직접 uvicorn 실행을 중단하고 systemd 서비스로만 관리했다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-end=&quot;16928&quot; data-start=&quot;16911&quot; data-section-id=&quot;37fij4&quot; data-ke-size=&quot;size23&quot;&gt;7) 학생 로그인 404&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;원인:
API 경로 자체가 없어서가 아니라, 요청 IP와 VDI 세션 테이블의 vdi_ip가 매칭되지 않았다.

해결:
VDI access log에서 실제 요청 IP를 확인하고, VDI 세션 테이블의 vdi_ip와 exam_id를 맞췄다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-end=&quot;17096&quot; data-start=&quot;17077&quot; data-section-id=&quot;wwjn9e&quot; data-ke-size=&quot;size23&quot;&gt;8) 학번 9자리 검증 오류&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;원인:
HTML maxlength가 먼저 적용되어 붙여넣기 값이 잘리고, JS에서 숫자만 추출하면서 8자리로 인식되었다.

해결:
HTML maxlength를 제거하고 JS에서 숫자만 추출한 뒤 9자리 검증을 수행했다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;17237&quot; data-start=&quot;17234&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;17260&quot; data-start=&quot;17239&quot; data-section-id=&quot;4h431&quot; data-ke-size=&quot;size26&quot;&gt;32. 보안상 공개하지 않은 정보&lt;/h2&gt;
&lt;p data-end=&quot;17292&quot; data-start=&quot;17262&quot; data-ke-size=&quot;size16&quot;&gt;공개 블로그에는 다음 정보는 실제 값으로 적지 않았다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;실제 서버 공인 IP
실제 VDI 내부 IP
실제 DB 이름
실제 DB 계정
실제 프로젝트 경로
실제 로그 원문
실제 학생 이름
실제 학번
실제 교수 식별번호
실제 Nginx 설정 파일 경로
실제 systemd 서비스명&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;17446&quot; data-start=&quot;17430&quot; data-ke-size=&quot;size16&quot;&gt;대신 다음과 같이 마스킹했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;apache&quot;&gt;&lt;code&gt;&amp;lt;SERVER_PUBLIC_IP&amp;gt;
&amp;lt;VDI_GATEWAY_IP&amp;gt;
&amp;lt;VDI_PRIVATE_IP&amp;gt;
&amp;lt;DB_NAME&amp;gt;
&amp;lt;DB_USER&amp;gt;
&amp;lt;BACKEND_PROJECT_DIR&amp;gt;
&amp;lt;FASTAPI_SERVICE_NAME&amp;gt;
&amp;lt;STUDENT_ID&amp;gt;
&amp;lt;PROFESSOR_ID&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;17610&quot; data-start=&quot;17607&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;17624&quot; data-start=&quot;17612&quot; data-section-id=&quot;1hl819n&quot; data-ke-size=&quot;size26&quot;&gt;33. 최종 정리&lt;/h2&gt;
&lt;p data-end=&quot;17686&quot; data-start=&quot;17626&quot; data-ke-size=&quot;size16&quot;&gt;이번 작업을 통해 기존 프론트엔드 중심 대시보드는 실제 DB와 연결된 VDI 시험 감독 대시보드로 발전했다.&lt;/p&gt;
&lt;p data-end=&quot;17702&quot; data-start=&quot;17688&quot; data-ke-size=&quot;size16&quot;&gt;핵심 성과는 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;1. FastAPI 기반 백엔드 API 구축
2. MariaDB와 교수/학생/시험/VDI 세션 데이터 연동
3. Nginx reverse proxy 설정
4. VDI 내부 IP 기반 좌석 식별 구조 설계
5. 교수 대시보드와 DB 상태 연동
6. 학생 로그인 시 좌석 상태 자동 변경
7. FastAPI systemd 서비스 등록
8. 실제 운영을 고려한 서버 관리 방식 정리&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;18059&quot; data-start=&quot;17928&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 단순히 학생 이름과 학번을 DB에 저장하는 기능처럼 보였지만, 실제 구현 과정에서는 VDI 네트워크 구조, NAT 환경, Nginx reverse proxy, DB 세션 설계, systemd 운영 방식까지 함께 고려해야 했다.&lt;/p&gt;
&lt;p data-end=&quot;18153&quot; data-start=&quot;18061&quot; data-ke-size=&quot;size16&quot;&gt;특히 가장 중요한 설계 변경은 &lt;b&gt;학생별 URL을 따로 주지 않고, 같은 로그인 URL을 사용하면서 요청 IP 기반으로 VDI 좌석을 식별하도록 만든 것&lt;/b&gt;이었다.&lt;/p&gt;
&lt;p data-end=&quot;18251&quot; data-start=&quot;18155&quot; data-ke-size=&quot;size16&quot;&gt;이 구조 덕분에 학생은 복잡한 URL을 알 필요 없이 VDI에 접속해 이름과 학번만 입력하면 되고, 교수자는 대시보드에서 실시간으로 어떤 좌석이 접속했는지 확인할 수 있다.&lt;/p&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;18350&quot; data-start=&quot;18253&quot; data-ke-size=&quot;size16&quot;&gt;향후에는 프록시 로그 또는 정책 위반 탐지 결과를 VDI 세션과 연결해, 단순 접속 상태뿐 아니라 시험 중 이상 행위 탐지 결과까지 대시보드와 최종 보고서에 연결할 수 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;</description>
      <category>네크워크 공부</category>
      <author>otopligrm</author>
      <guid isPermaLink="true">https://otopligrm.tistory.com/44</guid>
      <comments>https://otopligrm.tistory.com/44#entry44comment</comments>
      <pubDate>Tue, 19 May 2026 00:01:23 +0900</pubDate>
    </item>
    <item>
      <title>Rocky Linux 기반 KVM/libvirt 가상화 환경 구축</title>
      <link>https://otopligrm.tistory.com/41</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Rocky&amp;nbsp;Linux&amp;nbsp;서버&amp;nbsp;위에&amp;nbsp;KVM/libvirt&amp;nbsp;기반&amp;nbsp;가상화&amp;nbsp;환경을&amp;nbsp;만들고,&amp;nbsp; &lt;br /&gt;그&amp;nbsp;위에&amp;nbsp;Windows&amp;nbsp;VM을&amp;nbsp;생성한&amp;nbsp;뒤,&amp;nbsp;VNC로&amp;nbsp;설치&amp;nbsp;화면에&amp;nbsp;접속하도록&amp;nbsp;설정을하였다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;u&gt;&lt;b&gt;dnf&amp;nbsp;install&amp;nbsp;-y&amp;nbsp;epel-release&lt;/b&gt;&lt;/u&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;/u&gt;EPEL 저장소 를 시스템에 추가해주는 패키지이다. &lt;br /&gt;필요한&amp;nbsp;패키지가&amp;nbsp;기본&amp;nbsp;저장소에&amp;nbsp;없을&amp;nbsp;수도&amp;nbsp;있고,&amp;nbsp;패키지&amp;nbsp;설치의&amp;nbsp;범위를&amp;nbsp;넓히고,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;후에&amp;nbsp;의존성&amp;nbsp;문제를&amp;nbsp;줄이기&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;&lt;u&gt;&lt;b&gt;dnf install -y qemu-kvm libvirt virt-install &lt;/b&gt;&lt;/u&gt;&lt;u&gt;&lt;b&gt;virt-manager virt-viewer bridge-utils libguestfs-tools&lt;/b&gt;&lt;/u&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;필요한&amp;nbsp;핵심&amp;nbsp;패키지인&amp;nbsp;QEMU,KVM&amp;nbsp;등을&amp;nbsp;설치한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;QEMU,KVM에 대한 설명은 &lt;a href=&quot;https://otopligrm.tistory.com/39&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://otopligrm.tistory.com/39&lt;/a&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-ke-size=&quot;size23&quot;&gt;&lt;u&gt;&lt;b&gt;systemctl&amp;nbsp;enable&amp;nbsp;--now&amp;nbsp;libvirtd&lt;/b&gt;&lt;/u&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;libvirtd&amp;nbsp;서비스를&amp;nbsp;지금&amp;nbsp;바로&amp;nbsp;시작하고,&amp;nbsp;부팅할때&amp;nbsp;자동으로&amp;nbsp;시작되도록&amp;nbsp;등록한다 &lt;br /&gt;여기서 libvirt는 가상 머신을 관리하는 표준 관리 계층이다.&lt;u&gt;&lt;/u&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-ke-size=&quot;size23&quot;&gt;&lt;u&gt;&lt;b&gt;virt-host-validate&lt;/b&gt;&lt;/u&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버가KVM&amp;nbsp;호스트로&amp;nbsp;정상&amp;nbsp;동작할&amp;nbsp;준비가&amp;nbsp;되었는지&amp;nbsp;검증하는&amp;nbsp;명령이다.&amp;nbsp; &lt;br /&gt;예를 들면 CPU가상화 지원을 확인하고(VT-x/AMD-V)&lt;br /&gt;KVM모듈&amp;nbsp;상태&amp;nbsp;등&amp;nbsp;적절한&amp;nbsp;상태인지&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;u&gt;&lt;b&gt;virsh&amp;nbsp;net-list&amp;nbsp;--all&lt;/b&gt;&lt;/u&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VM이&amp;nbsp;붙을&amp;nbsp;가상&amp;nbsp;네트워크가&amp;nbsp;있는지확인하고,&amp;nbsp;NAT&amp;nbsp;네트워크가&amp;nbsp;살아&amp;nbsp;있는지&amp;nbsp;확인한다. &lt;br /&gt;왜냐하면&amp;nbsp;VM을&amp;nbsp;만들때&amp;nbsp;네트워크를&amp;nbsp;network&amp;nbsp;=&amp;nbsp;default로&amp;nbsp;붙일&amp;nbsp;예정이기&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;u&gt; default&amp;nbsp; network&lt;/u&gt;는 KVM으로 만든 VM들이 인터넷도 나가고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서로도 연결될 수 있게 해주는 기본 &lt;b&gt;NAT 네트워크&lt;/b&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-ke-size=&quot;size23&quot;&gt;&lt;u&gt;&lt;b&gt;virsh&amp;nbsp;net-autostart&amp;nbsp;default&lt;/b&gt;&lt;/u&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;default&amp;nbsp;네트워크를&amp;nbsp;서버&amp;nbsp;부팅&amp;nbsp;시&amp;nbsp;자동으로&amp;nbsp;올라오게&amp;nbsp;설정한다.&lt;u&gt;&lt;/u&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-ke-size=&quot;size23&quot;&gt;&lt;u&gt;&lt;b&gt;virsh&amp;nbsp;net-start&amp;nbsp;default&lt;/b&gt;&lt;/u&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;/u&gt;default 네트워크를 바로 시작한다.&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-ke-size=&quot;size23&quot;&gt;&lt;u&gt;&lt;b&gt;virt-install&amp;nbsp;--name&amp;nbsp;win10-vdi21&amp;nbsp;--ram&amp;nbsp;3072&amp;nbsp;--vcpus&amp;nbsp;2&amp;nbsp;--disk&amp;nbsp;path=/home/kvm_num_1/win10-vdi24.qcow2,size=50&amp;nbsp;-- &lt;/b&gt;&lt;/u&gt;&lt;br /&gt;&lt;b&gt;&lt;u&gt;os-variant&amp;nbsp;win10&amp;nbsp;--network&amp;nbsp;network=default&amp;nbsp;--graphics&amp;nbsp;vnc,listen=0.0.0.0,port=5903&amp;nbsp;--cdrom&lt;/u&gt;&amp;nbsp;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명령어 순서대로&amp;nbsp; 이름은 win10-vdi21, RAM은 3GB, CPU는 2코어로 할당하고, &lt;br /&gt;&lt;u&gt;path=/home/kvm_num_1/win10-vdi24.qcow2&lt;/u&gt; = VM의 가상 디스크 파일 위치를 지정. &lt;br /&gt;&lt;u&gt;qcow2&lt;/u&gt;는&amp;nbsp;QEMU의&amp;nbsp;디스크&amp;nbsp;포맷&amp;nbsp;중&amp;nbsp;하나이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음&amp;nbsp;다시&amp;nbsp;순서대로&amp;nbsp;가상&amp;nbsp;디스크의&amp;nbsp;용량은&amp;nbsp;50GB, &lt;br /&gt;os는&amp;nbsp;윈도우&amp;nbsp;10&amp;nbsp;계열&amp;nbsp;예정이고,&amp;nbsp;libvirt의&amp;nbsp;default&amp;nbsp;네트워크에&amp;nbsp;이&amp;nbsp;VM을&amp;nbsp;연결한다. &lt;br /&gt;--&lt;u&gt;graphics vnc,listen=0.0.0.0,port=5903&lt;/u&gt; = VM의 그래픽을 VNC방식으로 제공하며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버의 모든 네트워크(0.0.0.0)에서 접속을 허용한다. 그리고 설정 포트는 5903&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;근데&amp;nbsp;이것은&amp;nbsp;모든&amp;nbsp;ip주소에서&amp;nbsp;들어오는&amp;nbsp;vnc&amp;nbsp;접속을&amp;nbsp;받겠다는&amp;nbsp;것이라서&amp;nbsp;보안상&amp;nbsp;조심해야한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;접속&amp;nbsp;ip를&amp;nbsp;제한하거나,&amp;nbsp;방화벽&amp;nbsp;제한&amp;nbsp;등이&amp;nbsp;필요하다고&amp;nbsp;생각한다.&amp;nbsp;&lt;b&gt;&lt;br /&gt;&lt;/b&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-ke-size=&quot;size23&quot;&gt;&lt;u&gt;&lt;b&gt;firewall-cmd&amp;nbsp;--permanent&amp;nbsp;--add-port=5903/tcp&lt;/b&gt;&lt;/u&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서&amp;nbsp;설정한&amp;nbsp;5903&amp;nbsp;포트를&amp;nbsp;서버&amp;nbsp;방화벽에서&amp;nbsp;영구적으로&amp;nbsp;허용을한다. &lt;br /&gt;VNC는&amp;nbsp;일반적으로&amp;nbsp;TCP기반이라서&amp;nbsp;TCP를&amp;nbsp;사용한다.&lt;b&gt;&lt;/b&gt;&lt;u&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/u&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-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;u&gt;firewall-cmd&amp;nbsp;--reload&lt;/u&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p 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-ke-size=&quot;size23&quot;&gt;&lt;u&gt;&lt;b&gt;firewall-cmd&amp;nbsp;--list-all&lt;/b&gt;&lt;/u&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5903/TCP가 열렸는지 확인을하기 위해 현재 방화벽 설정을 보여준다.&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-ke-size=&quot;size23&quot;&gt;&lt;u&gt;&lt;b&gt;virsh&amp;nbsp;edit&amp;nbsp;win10-vdi21&lt;/b&gt;&lt;/u&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;편집기를 열어 &amp;lt;boot&amp;nbsp;dev='cdrom'/&amp;gt;을&amp;nbsp;추가해준다. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그&amp;nbsp;이유는&amp;nbsp;처음&amp;nbsp;부팅할때&amp;nbsp;ISO파일이&amp;nbsp;있는&amp;nbsp;CDROM&amp;nbsp;장치로&amp;nbsp;먼저&amp;nbsp;부팅을하기&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;u&gt;&lt;b&gt;virsh&amp;nbsp;destroy&amp;nbsp;win10-vdi21&lt;/b&gt;&lt;/u&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방금 설정을 수정하였기 때문에 수정한 설정을 적용하기 위해서 VM을 끈다.&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-ke-size=&quot;size23&quot;&gt;&lt;u&gt;&lt;b&gt;virsh&amp;nbsp;start&amp;nbsp;win10-vdi21&lt;/b&gt;&lt;/u&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;win-vdi21을 이제 다시 시작한다&lt;/p&gt;</description>
      <category>리눅스 공부</category>
      <author>otopligrm</author>
      <guid isPermaLink="true">https://otopligrm.tistory.com/41</guid>
      <comments>https://otopligrm.tistory.com/41#entry41comment</comments>
      <pubDate>Wed, 15 Apr 2026 17:58:38 +0900</pubDate>
    </item>
    <item>
      <title>KVM과 QEMU</title>
      <link>https://otopligrm.tistory.com/39</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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;KVM&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kvm은&amp;nbsp;Kernel-based&amp;nbsp;Virtual&amp;nbsp;Machine의&amp;nbsp;줄임말이다.&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;리눅스&amp;nbsp;커널이&amp;nbsp;하이퍼바이저&amp;nbsp;역할을&amp;nbsp;수행하도록&amp;nbsp;만들어주는&amp;nbsp;모듈이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;즉&amp;nbsp;kvm은&amp;nbsp;이러한&amp;nbsp;기능들을&amp;nbsp;수행하기&amp;nbsp;위해서&amp;nbsp;리눅스&amp;nbsp;커널에&amp;nbsp;들어가는&amp;nbsp;모듈이다.&lt;/u&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-ke-size=&quot;size23&quot;&gt;&lt;u&gt;그럼 여기서 &lt;b&gt;커널&lt;/b&gt;이 무엇이냐.&lt;/u&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커널은 일단 운영체제의 핵심이다. 주로 메모리관리, 디바이스제어, cpu 관리&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;하드웨어와&amp;nbsp;응용프로그램&amp;nbsp;사이에서&amp;nbsp;인터페이스를&amp;nbsp;제공해준다.&amp;nbsp;&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 data-ke-size=&quot;size16&quot;&gt;kvm은 vm용 가상 cpu를 실행, vm메모리 관리, 하드웨어 가상화 기능 사용 등 이러한 기능들을 수행하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한마디로 커널은 운영체제의 핵심이지만 kvm에서는 이에 더불어 가상화의 핵심 역할도 맡아서 한다고 생각하면 된다.&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;/b&gt;&lt;br /&gt;&lt;u&gt;&lt;b&gt;하이퍼바이저&lt;/b&gt;는&amp;nbsp;무엇이냐.&amp;nbsp;&lt;/u&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제&amp;nbsp;서버&amp;nbsp;위에서&amp;nbsp;vm을&amp;nbsp;할당해주는&amp;nbsp;관리자라고&amp;nbsp;생각하면&amp;nbsp;된다. &lt;br /&gt;하이버바이저와 관련된 개념은 &lt;a href=&quot;https://otopligrm.tistory.com/30&quot;&gt;가상 데스크톱 인프라(VDI) 개념&lt;/a&gt;&amp;nbsp;을참고하면&amp;nbsp;좋을&amp;nbsp;것&amp;nbsp;같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나도&amp;nbsp;하이퍼바이저&amp;nbsp;개념을&amp;nbsp;까먹었었는데&amp;nbsp;제가&amp;nbsp;적은&amp;nbsp;블로그들을&amp;nbsp;다시&amp;nbsp;한&amp;nbsp;번&amp;nbsp;훑어보면서&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;2가지&amp;nbsp;타입이있는데&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하이퍼 바이저의 타입에 대해서 조사를 해보면서 kvm은 당연히 리눅스 위에서 동작을 하기에&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;u&gt;&lt;b&gt; kvm의 자세한 역할은&lt;/b&gt;&lt;/u&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt; CPU 가상화&lt;/b&gt;: VT-x/AMD-V 같은 하드웨어 가상화 기능을 커널에서 활용&lt;/li&gt;
&lt;li&gt;&lt;b&gt;메모리&amp;nbsp;분리&lt;/b&gt;:&amp;nbsp;VM마다&amp;nbsp;독립된&amp;nbsp;메모리&amp;nbsp;공간을&amp;nbsp;사용하도록&amp;nbsp;격리. &lt;br /&gt;안정성과&amp;nbsp;보안때문에&amp;nbsp;메모리&amp;nbsp;분리는&amp;nbsp;중요하다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;디스크&amp;nbsp;가상화&lt;/b&gt;:&amp;nbsp;가상&amp;nbsp;디스크&amp;nbsp;이미지/스토리지&amp;nbsp;장치를&amp;nbsp;VM에&amp;nbsp;제공. &lt;br /&gt;여기서&amp;nbsp;디스크&amp;nbsp;가상화는&amp;nbsp;SSD를&amp;nbsp;통째로&amp;nbsp;주는것이&amp;nbsp;아닌&amp;nbsp; &lt;br /&gt;호스트&amp;nbsp;pc에&amp;nbsp;있는&amp;nbsp;파일을&amp;nbsp;vm이&amp;nbsp;디스크처럼&amp;nbsp;사용하는&amp;nbsp;것이다.&lt;/li&gt;
&lt;li&gt;&amp;nbsp;&lt;b&gt;네트워크 가상화&lt;/b&gt;: 브리지/가상&amp;nbsp;NIC&amp;nbsp;등을&amp;nbsp;통해&amp;nbsp;VM&amp;nbsp;네트워크&amp;nbsp;구성 &lt;br /&gt;NIC는&amp;nbsp;가상&amp;nbsp;랜카드를&amp;nbsp;말하는&amp;nbsp;것이다.&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-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;u&gt;&lt;b&gt;VT-x, AMD-V&lt;/b&gt;&lt;/u&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;CPU 제조사가 넣은 하드웨어 가상화 기능이다.&lt;/span&gt;&lt;/span&gt;&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;이 기능이 있으면 CPU가 알아서 VM용 코드인지, 호스트용 코드인지 구분을 해준다&lt;/span&gt;&lt;/span&gt;&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;kvm은 이러한 cpu가 제공하는 가상화 기능을 리눅스 커널에서 활용하는거다.&lt;/span&gt;&lt;/span&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;u&gt;&lt;b&gt; &lt;span data-token-index=&quot;0&quot;&gt;QEMU&lt;/span&gt; &lt;/b&gt;&lt;/u&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가상 하드웨어 에뮬레이션 및 VM 실행을 보조해준다.&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;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;CPU는 빠르게 가상화를 할 수 있지만&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 I/O는 가상 장치를 거치기때문에 영향이 생길 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서&lt;b&gt; passthrough&lt;/b&gt;같은 고급 설정이 중요하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한&amp;nbsp; &lt;b&gt;GPU&lt;/b&gt;는 VM 구성에서는 많이 제한적일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 제한적인 문제를 해결하기 위해서는 &lt;b&gt;&lt;u&gt;GPU passthrough&lt;/u&gt;&lt;/b&gt; 같은 고급 설정이 필요하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;이 방식은 물리 GPU를 특정 VM에 거의 직접 할당하는 방식이다.&lt;/u&gt;&lt;/p&gt;</description>
      <category>리눅스 공부</category>
      <author>otopligrm</author>
      <guid isPermaLink="true">https://otopligrm.tistory.com/39</guid>
      <comments>https://otopligrm.tistory.com/39#entry39comment</comments>
      <pubDate>Sat, 11 Apr 2026 21:57:52 +0900</pubDate>
    </item>
    <item>
      <title>리눅스 프로그램 설치 방법과 빌드 개념 정리</title>
      <link>https://otopligrm.tistory.com/38</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;리눅스에서 프로그램을 설치하는 방법 총 3가지가 있습니다&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;b&gt; 1. 패키지로 설치 &lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이미 누군가가 빌드해둔 것을 가져와서 설치하는 방식입니다. &lt;br /&gt;예를&amp;nbsp;들면 &lt;br /&gt;&lt;b&gt;Rocky / RHEL 계열: dnf, yum &lt;/b&gt;&lt;br /&gt;&lt;b&gt;Ubuntu&amp;nbsp;/&amp;nbsp;Debian&amp;nbsp;계열:&amp;nbsp;deb,&amp;nbsp;apt&lt;/b&gt; &lt;br /&gt;&lt;br /&gt;그럼여기서&amp;nbsp;&lt;b&gt;패키지&lt;/b&gt;란&amp;nbsp;무엇이냐.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이&amp;nbsp;프로그램을&amp;nbsp;설치하려면&amp;nbsp;필요한&amp;nbsp;부가적인&amp;nbsp;파일들을&amp;nbsp;전부&amp;nbsp;묶어놓은&amp;nbsp;것입니다. &lt;br /&gt;패키지 안에는 보통 실행 파일(binary), 설정 파일(config), 라이브러리,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의존성(dependency) 정보 등이 있습니다. &lt;br /&gt;&lt;br /&gt;그러면 이제 &lt;b&gt;의존성(dependency)&lt;/b&gt;이 무엇이냐. &lt;br /&gt;의미&amp;nbsp;그대로&amp;nbsp;의존을&amp;nbsp;한다는&amp;nbsp;것입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A라는&amp;nbsp;프로그램이&amp;nbsp;동작하기&amp;nbsp;위해서는&amp;nbsp;다른&amp;nbsp;프로그램이나,&amp;nbsp;모듈이&amp;nbsp;필요하다.&amp;nbsp;이런뜻입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 python이 동작하기 위해서는 requests, flask가 필요합니다.&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;장점:&amp;nbsp;가장&amp;nbsp;편하다.&amp;nbsp;파일을&amp;nbsp;어디에&amp;nbsp;배치할지,&amp;nbsp; &lt;br /&gt;라이브러리는 무엇인지 패키지가 어느정도 관리를 해주기 때문입니다&lt;/li&gt;
&lt;/ul&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; 2. 소스코드로 설치. &lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;개발자가 공개한 소스코드를 직접 컴파일해서 설치하는 방식입니다. &lt;br /&gt;예를&amp;nbsp;들면&amp;nbsp;configure,&amp;nbsp;make,&amp;nbsp;make&amp;nbsp;install&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;단점은 컴파일 도구가 필요하며, 라이브러리가 필요합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서&amp;nbsp;컴파일이&amp;nbsp;무엇이냐.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소스코드는&amp;nbsp;사람이&amp;nbsp;작성한&amp;nbsp;코드입니다.&amp;nbsp;사람이&amp;nbsp;작성했으니&amp;nbsp;사람이&amp;nbsp;읽을&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;코드입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람이&amp;nbsp;작성한&amp;nbsp;이&amp;nbsp;코드를&amp;nbsp;컴퓨터가&amp;nbsp;실행&amp;nbsp;할&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;형태로&amp;nbsp;바꾸는&amp;nbsp;과정이&amp;nbsp;컴파일입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;u&gt;사람이 작성한 코드(소스코드) &amp;rarr;변환(컴파일) &lt;u&gt;&amp;rarr;&lt;/u&gt;&amp;nbsp; 컴퓨터가 실행하는 파일(바이너리)&lt;/u&gt; &lt;br /&gt;&lt;br /&gt;예를 들어&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;462&quot; data-origin-height=&quot;167&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgIUDy/dJMcaiQpql2/zGYGf6g5YvKVqjGaWlP7M0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgIUDy/dJMcaiQpql2/zGYGf6g5YvKVqjGaWlP7M0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgIUDy/dJMcaiQpql2/zGYGf6g5YvKVqjGaWlP7M0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgIUDy%2FdJMcaiQpql2%2FzGYGf6g5YvKVqjGaWlP7M0%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;462&quot; height=&quot;167&quot; data-origin-width=&quot;462&quot; data-origin-height=&quot;167&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드는 사람이 짠 코드이죠? 이게 소스코드입니다. &lt;br /&gt;이&amp;nbsp;소스코드를&amp;nbsp;컴파일하면&lt;b&gt;&amp;nbsp;a.out같은&amp;nbsp;바이너리(실행&amp;nbsp;파일)&lt;/b&gt;이&amp;nbsp;만들어집니다.&amp;nbsp; &lt;br /&gt;당연히&amp;nbsp;사람이&amp;nbsp;읽기&amp;nbsp;어렵고&amp;nbsp;컴퓨터가&amp;nbsp;읽고&amp;nbsp;바로&amp;nbsp;실행&amp;nbsp;할&amp;nbsp;수&amp;nbsp;있습니다 &lt;br /&gt;&lt;br /&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;&lt;b&gt;3.바이너리(binary)로 설치.&lt;/b&gt;&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;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;바이너리를 어디로 옮겨라: 실행 파일을 원하는 경로에 복사하고,&lt;br /&gt;그 경로의 파일에게 실행 권한을줍니다.&lt;u&gt;권한 주는것 잊으면 안됩니다.&lt;/u&gt;&lt;/li&gt;
&lt;li&gt;명령어로&amp;nbsp;잡아서&amp;nbsp;사용을&amp;nbsp;할&amp;nbsp;것이라면 &lt;br /&gt;&lt;b&gt;PATH&lt;/b&gt;에 해당 파일의 경로가 등록되어 있어야합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;sudo&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;cp taewoo&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp;/usr/local/bin/&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;sudo chmod +x /usr/local/bin/taewoo&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;리눅스에서 보통 PATH에 잡혀 있는 대표 경로는&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;/usr.bin(사용자 명령어), /bin(기본적인 필수 명령어), /sbin (시스템 관리용 명령어)&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;/usr/sbin(더 많은 시스템 관리 명령어), /usr/local/bin(직접 설치,수동으로 넣은 파일들)&lt;/span&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 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. Makefile.&lt;/b&gt;&lt;/h3&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5. Make.&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Make는 Makefile을 읽고 실제로 빌드를 실행하는 도구(?)입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 비유를 하자면 Makefile은 설계도이고, Make는 실행자입니다.&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;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;6. Configure.&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 소스코드 프로젝트에서 현재 서버 환경을 검사하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 환경에 맞는 Makefile을 만들어주는 준비 단계입니다.&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;파일 위치 경로&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 실행파일(binary)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;5295&quot; data-start=&quot;5230&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;5242&quot; data-start=&quot;5230&quot; data-section-id=&quot;cbkmu1&quot;&gt;/usr/bin&lt;/li&gt;
&lt;li data-end=&quot;5256&quot; data-start=&quot;5243&quot; data-section-id=&quot;1f4ga5m&quot;&gt;/usr/sbin&lt;/li&gt;
&lt;li data-end=&quot;5275&quot; data-start=&quot;5257&quot; data-section-id=&quot;1js1ad7&quot;&gt;/usr/local/bin&lt;/li&gt;
&lt;li data-end=&quot;5295&quot; data-start=&quot;5276&quot; data-section-id=&quot;1sljqiw&quot;&gt;/usr/local/sbin&lt;/li&gt;
&lt;li data-end=&quot;5295&quot; data-start=&quot;5276&quot; data-section-id=&quot;1sljqiw&quot;&gt;일반적으로 수동 설치한 건 /usr/local/bin에&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2.라이브러리&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;5372&quot; data-start=&quot;5360&quot; data-section-id=&quot;cb78m3&quot;&gt;/usr/lib&lt;/li&gt;
&lt;li data-end=&quot;5387&quot; data-start=&quot;5373&quot; data-section-id=&quot;1h0remx&quot;&gt;/usr/lib64&lt;/li&gt;
&lt;li data-end=&quot;5406&quot; data-start=&quot;5388&quot; data-section-id=&quot;1jrqyux&quot;&gt;/usr/local/lib&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 설정파일&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;5441&quot; data-start=&quot;5426&quot; data-section-id=&quot;1qi532w&quot;&gt;/etc/프로그램명/&lt;/li&gt;
&lt;li data-end=&quot;5461&quot; data-start=&quot;5442&quot; data-section-id=&quot;1lsgahp&quot;&gt;/etc/프로그램명.conf&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4.서비스 파일(systemd)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;/etc/systemd/system/프로그램명.service&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5.&amp;nbsp; 로그&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;5527&quot; data-start=&quot;5508&quot; data-section-id=&quot;hgjdd0&quot;&gt;/var/log/프로그램명/&lt;/li&gt;
&lt;li data-end=&quot;5549&quot; data-start=&quot;5528&quot; data-section-id=&quot;198q51a&quot;&gt;/var/log/messages&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-ke-size=&quot;size23&quot;&gt;&lt;b&gt;요약&lt;/b&gt;&lt;/h3&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 data-end=&quot;7927&quot; data-start=&quot;7894&quot; data-section-id=&quot;1ubtih8&quot;&gt;&lt;b&gt;package&lt;/b&gt;: 설치하기 쉽게 묶어놓은 배포 단위&lt;/li&gt;
&lt;li data-end=&quot;7967&quot; data-start=&quot;7928&quot; data-section-id=&quot;1p5r72a&quot;&gt;&lt;b&gt;dependency&lt;/b&gt;: 프로그램이 동작하려고 필요한 다른 것들&lt;/li&gt;
&lt;li data-end=&quot;8001&quot; data-start=&quot;7968&quot; data-section-id=&quot;1u4n0hs&quot;&gt;&lt;b&gt;binary&lt;/b&gt;: 컴퓨터가 바로 실행할 수 있는 파일&lt;/li&gt;
&lt;li data-end=&quot;8036&quot; data-start=&quot;8002&quot; data-section-id=&quot;769ixd&quot;&gt;&lt;b&gt;Makefile&lt;/b&gt;: 어떻게 빌드할지 적어둔 규칙 파일&lt;/li&gt;
&lt;li data-end=&quot;8072&quot; data-start=&quot;8037&quot; data-section-id=&quot;wbjfr5&quot;&gt;&lt;b&gt;make&lt;/b&gt;: Makefile을 읽고 실제 빌드하는 도구&lt;/li&gt;
&lt;li data-end=&quot;8124&quot; data-start=&quot;8073&quot; data-section-id=&quot;gjxcdv&quot;&gt;&lt;b&gt;configure&lt;/b&gt;: 현재 서버 환경을 검사하고 Makefile을 만드는 준비 단계&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>리눅스 공부</category>
      <author>otopligrm</author>
      <guid isPermaLink="true">https://otopligrm.tistory.com/38</guid>
      <comments>https://otopligrm.tistory.com/38#entry38comment</comments>
      <pubDate>Thu, 9 Apr 2026 20:58:04 +0900</pubDate>
    </item>
    <item>
      <title>TLS에 대하여</title>
      <link>https://otopligrm.tistory.com/37</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;여기에서는 TLS에 대하여 다룰려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그전에 전 블로그에서 TLS를 끊는다는 표현을 사용했는데&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TLS를 끊는다는 표현에 대해서 먼저 설명을하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 TLS에 대하여 다루도록 하겠습니다&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;TLS를 끊는다&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-path-to-node=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;네트워크 용어로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b data-path-to-node=&quot;0&quot; data-index-in-node=&quot;26&quot;&gt;SSL/TLS 복호화&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;또는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b data-path-to-node=&quot;0&quot; data-index-in-node=&quot;61&quot;&gt;SSL Inspection&lt;/b&gt;을 의미합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;일반적인 HTTPS 통신은 클라이언트(내 컴퓨터)와 서버(웹사이트)가&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;서로 암호 키를 주고받아 데이터를 암호화하기 때문에,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;그 중간에 있는 장비(프록시 서버)는 데이터가 어떤 내용인지 전혀 알 수 없습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;단순히 암호화된 택배 상자를 전달만 할 뿐입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-path-to-node=&quot;2&quot; data-ke-size=&quot;size16&quot;&gt;하지만&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b data-path-to-node=&quot;2&quot; data-index-in-node=&quot;4&quot;&gt;SSL Bump&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;기능을 사용하면 프록시 서버가 이 흐름의 중간에 개입하여&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-path-to-node=&quot;2&quot; data-ke-size=&quot;size16&quot;&gt;아래과 같은 과정을 거칩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-path-to-node=&quot;2&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot; data-path-to-node=&quot;3&quot;&gt;&lt;b&gt;1. 연결의 분리 (Termination)&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;프록시 서버가 클라이언트에게는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b data-path-to-node=&quot;4&quot; data-index-in-node=&quot;17&quot;&gt;서버인 척&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;하고, 실제 서버에게는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b data-path-to-node=&quot;4&quot; data-index-in-node=&quot;36&quot;&gt;클라이언트인 척&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot; data-path-to-node=&quot;5&quot;&gt;
&lt;li&gt;&lt;b data-path-to-node=&quot;5,0,0&quot; data-index-in-node=&quot;0&quot;&gt;구간 A:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;클라이언트 &amp;lt;---&amp;gt; 프록시 서버 (암호화 연결)&lt;/li&gt;
&lt;li&gt;&lt;b data-path-to-node=&quot;5,1,0&quot; data-index-in-node=&quot;0&quot;&gt;구간 B:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;프록시 서버 &amp;lt;---&amp;gt; 실제 웹 서버 (암호화 연결)&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;즉, 하나의 긴 연결을 중간에서 '끊고' 두 개의 연결로 나누는 것입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot; data-path-to-node=&quot;7&quot;&gt;&lt;b&gt;2. 데이터의 복호화 및 가공&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;연결이 프록시에서 일단 끊기기 때문에,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;암호화된 데이터는 프록시 서버 내부에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b data-path-to-node=&quot;8&quot; data-index-in-node=&quot;44&quot;&gt;평문&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;상태입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-path-to-node=&quot;8&quot; 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; data-path-to-node=&quot;9&quot;&gt;
&lt;li&gt;어떤 URL로 접속하는 분석&lt;/li&gt;
&lt;li&gt;어떤 프롬포트를 입력하였는지&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot; data-path-to-node=&quot;10&quot;&gt;&lt;b&gt;3. 재암호화 (Re-encryption)&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-path-to-node=&quot;11&quot; data-ke-size=&quot;size16&quot;&gt;내용 확인이 끝나면 프록시 서버는 다시 데이터를 암호화해서 목적지로 보냅니다.&lt;/p&gt;</description>
      <category>네크워크 공부</category>
      <author>otopligrm</author>
      <guid isPermaLink="true">https://otopligrm.tistory.com/37</guid>
      <comments>https://otopligrm.tistory.com/37#entry37comment</comments>
      <pubDate>Tue, 7 Apr 2026 21:57:46 +0900</pubDate>
    </item>
    <item>
      <title>WSL Ubuntu와 Windows 11 환경에서 Squid SSL Inspection과 mitmproxy를 구축한 과정</title>
      <link>https://otopligrm.tistory.com/36</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;프록시&amp;nbsp;서버&amp;nbsp;+&amp;nbsp;트래픽&amp;nbsp;수집&amp;nbsp;구간을&amp;nbsp;만들기&amp;nbsp;위한&amp;nbsp;작업을&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;프록시 서버를 띄우는것도 중요하지만 HTTPS로 암호화된 웹 요청이&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;배경&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 중요한건 SSL Inspection이라고 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSL Inspection에 대해서는 제가 따로 정리해 놓은 글이 있습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTPS는 암호화되어 있기 때문에 Squid의 &lt;b&gt;SSL Bump&lt;/b&gt; 기능을 사용해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프록시가 HTTPS 통신을 중간에서 해독할 수 있도록 구성해야 했습니다.&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 style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;결국 이 실습은 &lt;b&gt;프록시 설치&lt;/b&gt;가 아니라,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프록시를 하나의 내부 인증 기관처럼 동작시키는 과정&lt;/b&gt;이라고 볼 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&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;style6&quot; /&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;squid-openssl 설치&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;sudo&amp;nbsp;apt&amp;nbsp;update&lt;/b&gt;&lt;br /&gt;&lt;b&gt;sudo&amp;nbsp;apt&amp;nbsp;install&amp;nbsp;squid-openssl&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2147&quot; data-start=&quot;1907&quot; data-ke-size=&quot;size16&quot;&gt;중요한 것은 그냥 squid가 아니라 squid-openssl을 설치했다는 것입니다.&lt;/p&gt;
&lt;p data-end=&quot;2147&quot; data-start=&quot;1907&quot; data-ke-size=&quot;size16&quot;&gt;그 이유는 제가 사용하려는 기능이 단순 프록시 기능이 아니라 &lt;b&gt;SSL Bump&lt;/b&gt;,&lt;/p&gt;
&lt;p data-end=&quot;2147&quot; data-start=&quot;1907&quot; data-ke-size=&quot;size16&quot;&gt;즉 &lt;b&gt;HTTPS&lt;/b&gt;를 중간에서 처리하는 기능이었기 때문이다.&lt;/p&gt;
&lt;p data-end=&quot;2147&quot; data-start=&quot;1907&quot; data-ke-size=&quot;size16&quot;&gt;반드시 squid-openssl 버전을 설치해야&lt;/p&gt;
&lt;p data-end=&quot;2147&quot; data-start=&quot;1907&quot; data-ke-size=&quot;size16&quot;&gt;SSL Bump 기능 사용이 가능하다고 정리되어 있습니다.&lt;/p&gt;
&lt;p data-end=&quot;2383&quot; data-start=&quot;2149&quot; data-ke-size=&quot;size16&quot;&gt;HTTPS를 다루지 않을 것이라면 일반 squid도 충분할 수 있지만,&lt;/p&gt;
&lt;p data-end=&quot;2383&quot; data-start=&quot;2149&quot; data-ke-size=&quot;size16&quot;&gt;저는 중간에서 TLS를 끊고 내부 내용을 봐야 했기 때문에&lt;/p&gt;
&lt;p data-end=&quot;2383&quot; data-start=&quot;2149&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;OpenSSL&lt;/b&gt; 지원이 포함된 버전이 필요했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2383&quot; data-start=&quot;2149&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2383&quot; data-start=&quot;2149&quot; data-ke-size=&quot;size16&quot;&gt;TLS를 끊는다는 표현에 대한 정리를 해놓았습니다&lt;/p&gt;
&lt;p data-end=&quot;2383&quot; data-start=&quot;2149&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://otopligrm.tistory.com/37&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://otopligrm.tistory.com/37&lt;/a&gt;&lt;/p&gt;
&lt;p data-end=&quot;2383&quot; data-start=&quot;2149&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;style6&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-start=&quot;2390&quot; data-end=&quot;2417&quot; data-section-id=&quot;tbnf87&quot; data-ke-size=&quot;size26&quot;&gt;내부 CA 인증서를 직접 생성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2711&quot; data-start=&quot;2419&quot; data-ke-size=&quot;size16&quot;&gt;Squid로 HTTPS를 들여다보려면,&lt;/p&gt;
&lt;p data-end=&quot;2711&quot; data-start=&quot;2419&quot; data-ke-size=&quot;size16&quot;&gt;프록시가 클라이언트와 서버 사이에서&amp;nbsp;&lt;b&gt;클라이언트에게는 서버인 척&lt;/b&gt;,&lt;/p&gt;
&lt;p data-end=&quot;2711&quot; data-start=&quot;2419&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실제 서버에게는 클라이언트인 척&lt;/b&gt; 동작해야합니다.&lt;/p&gt;
&lt;p data-end=&quot;2711&quot; data-start=&quot;2419&quot; data-ke-size=&quot;size16&quot;&gt;그런데 브라우저는 아무 인증서나 믿지 않습니다.&lt;/p&gt;
&lt;p data-end=&quot;2711&quot; data-start=&quot;2419&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 사용자가 chatgpt.com에 접속했는데,&lt;/p&gt;
&lt;p data-end=&quot;2711&quot; data-start=&quot;2419&quot; data-ke-size=&quot;size16&quot;&gt;프록시가 그 사이트 대신 인증서를 내밀면 브라우저는 당연히&lt;/p&gt;
&lt;p data-end=&quot;2711&quot; data-start=&quot;2419&quot; data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;이 인증서 발급자는 누구인지&lt;/u&gt;를 확인합니다.&lt;/p&gt;
&lt;p data-end=&quot;2711&quot; data-start=&quot;2419&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2711&quot; data-start=&quot;2419&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그래서 이 문제를 해결하기 위해 내부 CA 인증서를 직접 생성합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;2745&quot; data-start=&quot;2713&quot; data-ke-size=&quot;size16&quot;&gt;먼저 인증서 파일을 저장할 디렉토리를 만들고 권한을 설정했습니다.&lt;/p&gt;
&lt;p data-end=&quot;2745&quot; data-start=&quot;2713&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;2745&quot; data-start=&quot;2713&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;sudo mkdir -p /etc/squid/ssl_cert&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;2745&quot; data-start=&quot;2713&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;sudo&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp;&lt;/span&gt;chown&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp;&lt;/span&gt;-R&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt; proxy:proxy /etc/squid/ssl_cert&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;2745&quot; data-start=&quot;2713&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;sudo&amp;nbsp;chmod&amp;nbsp;700&amp;nbsp;/etc/squid/ssl_cert&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div style=&quot;color: #333333; text-align: start;&quot;&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3192&quot; data-start=&quot;2875&quot; data-ke-size=&quot;size16&quot;&gt;이 작업을 한 이유는 보안 때문입니다.&lt;/p&gt;
&lt;p data-end=&quot;3192&quot; data-start=&quot;2875&quot; data-ke-size=&quot;size16&quot;&gt;일단 인증서? 대충 이름만 들어도 굉장히 중요해보입니다.&lt;/p&gt;
&lt;p data-end=&quot;3192&quot; data-start=&quot;2875&quot; data-ke-size=&quot;size16&quot;&gt;근데이 디렉토리 안에는 나중에 &lt;b&gt;CA 개인키&lt;/b&gt;가 들어가는데,&lt;/p&gt;
&lt;p data-end=&quot;3192&quot; data-start=&quot;2875&quot; data-ke-size=&quot;size16&quot;&gt;이 키는 프록시가 각 사이트의 인증서를 대신 만들어 줄 때 사용하는&lt;/p&gt;
&lt;p data-end=&quot;3192&quot; data-start=&quot;2875&quot; data-ke-size=&quot;size16&quot;&gt;매우 중요한 정보입니다. 그래서 Squid가 동작하는 proxy 계정이 접근할 수 있도록&lt;/p&gt;
&lt;p data-end=&quot;3192&quot; data-start=&quot;2875&quot; data-ke-size=&quot;size16&quot;&gt;소유권을 주고, 다른 사용자는 접근하지 못하도록 700 권한으로 제한한 것입니다.&lt;/p&gt;
&lt;p data-end=&quot;3192&quot; data-start=&quot;2875&quot; data-ke-size=&quot;size16&quot;&gt;만약 이 개인키가 유출되면 그냥 아무래도 큰일이납니다.&lt;/p&gt;
&lt;p data-end=&quot;3192&quot; data-start=&quot;2875&quot; data-ke-size=&quot;size16&quot;&gt;인증서 디렉토리 권한 설정은 선택이 아니라 거의 필수라고 볼 수 있습니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;CA인증서와 키&lt;/h2&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span&gt;그다음 실제로 키와 루트 CA 인증서를 만들었습니다.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span&gt;openssl&lt;/span&gt;&lt;span&gt; genrsa &lt;/span&gt;&lt;span&gt;-out&lt;/span&gt;&lt;span&gt; /etc/squid/ssl_cert/myCA.key &lt;/span&gt;&lt;span&gt;4096&lt;/span&gt;&lt;br /&gt;&lt;span&gt;sudo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;openssl&lt;/span&gt;&lt;span&gt; req &lt;/span&gt;&lt;span&gt;-new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-x509&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-days&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;3650&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-key&lt;/span&gt;&lt;span&gt; /etc/squid/ssl_cert/myCA.key &lt;/span&gt;&lt;span&gt;-out&lt;/span&gt;&lt;span&gt; /etc/squid/ssl_cert/myCA.crt&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;3657&quot; data-start=&quot;3397&quot; data-ke-size=&quot;size16&quot;&gt;여기서 myCA.key는 &lt;b&gt;비밀키이면서 루트키입니다&lt;/b&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;3657&quot; data-start=&quot;3397&quot; data-ke-size=&quot;size16&quot;&gt;myCA.crt는 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;myCA.key로 만들어진 &lt;/span&gt;&lt;b&gt;공개 인증서&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-end=&quot;3657&quot; data-start=&quot;3397&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3657&quot; data-start=&quot;3397&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3657&quot; data-start=&quot;3397&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;비밀키와 루트키&lt;/b&gt;는 프록시가 사이트별 인증서를 발급할 때 서명하는 데 사용하고,&lt;/p&gt;
&lt;p data-end=&quot;3657&quot; data-start=&quot;3397&quot; 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 data-end=&quot;3657&quot; data-start=&quot;3397&quot;&gt;비밀키같은 경우 프록시 서버만 갖고 있어야합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;3657&quot; data-start=&quot;3397&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3657&quot; data-start=&quot;3397&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;공개 인증서&lt;/b&gt;는 나중에 Windows에 설치해서&lt;u&gt; 이 CA가 발급한 인증서는 믿겠다.&lt;/u&gt;&lt;/p&gt;
&lt;p data-end=&quot;3657&quot; data-start=&quot;3397&quot; data-ke-size=&quot;size16&quot;&gt;라고 등록하는 데 사용합니다.&lt;/p&gt;
&lt;p data-end=&quot;3657&quot; data-start=&quot;3397&quot; data-ke-size=&quot;size16&quot;&gt;즉, 프록시가 가짜 인증서를 만들어도 브라우저가 그것을 정상으로 인식하게 하려면,&lt;/p&gt;
&lt;p data-end=&quot;3657&quot; data-start=&quot;3397&quot; data-ke-size=&quot;size16&quot;&gt;이 내부 CA를 먼저 신뢰하게 만들어야 한다.&lt;/p&gt;
&lt;p data-end=&quot;3657&quot; data-start=&quot;3397&quot; data-ke-size=&quot;size16&quot;&gt;왜냐! CA가 인증서를 발급하니까 당연히 CA를 먼저 믿어야겠죠?&lt;/p&gt;
&lt;p data-end=&quot;4009&quot; data-start=&quot;3659&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;4009&quot; data-start=&quot;3659&quot; data-ke-size=&quot;size16&quot;&gt;그리고 -x509 -days 3650은 자체 서명된 루트 CA 인증서를&lt;/p&gt;
&lt;p data-end=&quot;4009&quot; data-start=&quot;3659&quot; data-ke-size=&quot;size16&quot;&gt;오랜 기간 사용할 수 있도록 생성하는 의미입니다.&lt;/p&gt;
&lt;p data-end=&quot;4009&quot; data-start=&quot;3659&quot; data-ke-size=&quot;size16&quot;&gt;인증서 생성 과정에서 국가 코드, 지역, 조직명, Common Name 등을 입력하는 이유는&lt;/p&gt;
&lt;p data-end=&quot;4009&quot; data-start=&quot;3659&quot; data-ke-size=&quot;size16&quot;&gt;암호화 강도 때문이 아니라,인증서의 주체 정보를 명확하게 표시하기 위함입니다.&lt;/p&gt;
&lt;p data-end=&quot;4009&quot; data-start=&quot;3659&quot; data-ke-size=&quot;size16&quot;&gt;나중에 Windows 인증서 저장소에서 이 인증서를 보고&lt;/p&gt;
&lt;p data-end=&quot;4009&quot; data-start=&quot;3659&quot; data-ke-size=&quot;size16&quot;&gt;내가 만든 내부 CA이네....라고 식별할 수 있게됩니다.&lt;/p&gt;
&lt;p data-end=&quot;4009&quot; data-start=&quot;3659&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;4041&quot; data-start=&quot;4011&quot; data-ke-size=&quot;size16&quot;&gt;생성 후에는 아래처럼 파일이 생겼는지 확인했습니다.&lt;/p&gt;
&lt;p data-end=&quot;4041&quot; data-start=&quot;4011&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;4041&quot; data-start=&quot;4011&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;sudo ls /etc/squid/ssl_cert&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4254&quot; data-start=&quot;4085&quot; data-ke-size=&quot;size16&quot;&gt;잘 되었다면 myCA.key, myCA.crt가 보여야합니다.&lt;/p&gt;
&lt;p data-end=&quot;4254&quot; data-start=&quot;4085&quot; data-ke-size=&quot;size16&quot;&gt;이 확인 과정이 중요한 이유는 이후 모든 설정이 이 파일들을 경로로 참조하기 때문입니다.&lt;/p&gt;
&lt;div style=&quot;color: #333333; text-align: start;&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;text-align: center;&quot; 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 style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>네크워크 공부</category>
      <author>otopligrm</author>
      <guid isPermaLink="true">https://otopligrm.tistory.com/36</guid>
      <comments>https://otopligrm.tistory.com/36#entry36comment</comments>
      <pubDate>Tue, 7 Apr 2026 21:19:42 +0900</pubDate>
    </item>
    <item>
      <title>systemctl과 service의 차이, 그리고 systemd까지 한 번에 이해하기</title>
      <link>https://otopligrm.tistory.com/35</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;systemctl&lt;/b&gt;과&amp;nbsp;&lt;b&gt;service&lt;/b&gt;는&amp;nbsp;둘&amp;nbsp;다&amp;nbsp;리눅스에서&amp;nbsp;시스템&amp;nbsp;서비스를&amp;nbsp;관리하는&amp;nbsp;명령어이다. &lt;br /&gt;먼저&amp;nbsp;&lt;b&gt;service&lt;/b&gt;와&amp;nbsp;&lt;b&gt;systemctl&lt;/b&gt;을&amp;nbsp;이해하기&amp;nbsp;위해서는&amp;nbsp;먼저&lt;b&gt;&amp;nbsp;systemd&lt;/b&gt;에&amp;nbsp;대한&amp;nbsp;이해가&amp;nbsp;필요하다.&amp;nbsp; &lt;br /&gt;&lt;br /&gt;먼저 &lt;b&gt;init process&lt;/b&gt;는 예전에 많이 사용했던 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리눅스가 부팅시에 최초로 실행이되는 프로세스이다.&amp;nbsp; &lt;br /&gt;이&amp;nbsp;프로세스는&amp;nbsp;부팅시에&amp;nbsp;OS운영에&amp;nbsp;필요한&amp;nbsp;service을&amp;nbsp;실행을&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;&lt;b&gt;1. systemd&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리눅스가&amp;nbsp;부팅되면&amp;nbsp;제일&amp;nbsp;먼저&amp;nbsp;여러&amp;nbsp;프로그램과&amp;nbsp;서비스들이&amp;nbsp;올라온다.&amp;nbsp; &lt;br /&gt;예를&amp;nbsp;들면&amp;nbsp;네트워크,&amp;nbsp;SSH&amp;nbsp;서버,&amp;nbsp;데이터베이스&amp;nbsp;등&amp;nbsp;&amp;nbsp;OS운영을&amp;nbsp;하기&amp;nbsp;위해&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 서비스들이 자동으로 시작이 되면 시스템이&amp;nbsp;정상적으로&amp;nbsp;동작한다. &lt;br /&gt;예전에는&amp;nbsp;주로&amp;nbsp;&amp;nbsp;&lt;b&gt;init,SysVinit,&amp;nbsp;service&lt;/b&gt;&amp;nbsp;같은&amp;nbsp;방식으로&amp;nbsp;관리를&amp;nbsp;하였지만&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;점점&amp;nbsp;더&amp;nbsp;시스템이&amp;nbsp;복잡해지면서&amp;nbsp;빠르게&amp;nbsp;관리&amp;nbsp;할&amp;nbsp;필요가&amp;nbsp;생겼고,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서&amp;nbsp;지금&amp;nbsp;많은&amp;nbsp;사람들이&amp;nbsp;사용하는&amp;nbsp;것이&amp;nbsp;&lt;b&gt;systemd&lt;/b&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;이러한&amp;nbsp;서비스들과&amp;nbsp;시스템의&amp;nbsp;전반적인것을&amp;nbsp;관리하는것이&amp;nbsp;&lt;b&gt;systemd&lt;/b&gt;이라고&amp;nbsp;생각하면&amp;nbsp;될&amp;nbsp;것&amp;nbsp;같다.&amp;nbsp; &lt;br /&gt;조금 더 쉽게 말하면 &lt;b&gt;service&lt;/b&gt;는 &lt;b&gt;systemd&lt;/b&gt;가 관리하는 여러 대상들중에서 하나이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 점점 systemd로 이용방식이 바뀌어가면서 service의 실행 방식이 바뀌었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;systemd의 장점&lt;/b&gt;은&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;service 병렬 실행가능&lt;/li&gt;
&lt;li&gt;boot time감소&lt;/li&gt;
&lt;li&gt;자동 의존성 관리 등이 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;systemd의&amp;nbsp;역할&lt;/b&gt;은&amp;nbsp;주로&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;부팅 과정 관리&lt;/li&gt;
&lt;li&gt;서비스 시작/중지 관리 &lt;/li&gt;
&lt;li&gt;서비스&amp;nbsp;간&amp;nbsp;의존성&amp;nbsp;관리 &lt;/li&gt;
&lt;li&gt;자동&amp;nbsp;실행&amp;nbsp;관리 &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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;&lt;b&gt;2. service&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리눅스가&amp;nbsp;부팅되었을&amp;nbsp;때&amp;nbsp;생성되어서&amp;nbsp;종료될&amp;nbsp;때까지&amp;nbsp;실행되는&amp;nbsp;프로세스&amp;nbsp;및&amp;nbsp;설정&amp;nbsp;파일을&amp;nbsp;service&amp;nbsp;라고&amp;nbsp;한다. &lt;br /&gt;service는&amp;nbsp;/etc/systemd/system&amp;nbsp;경로에&amp;nbsp;존재한다. &lt;br /&gt;service는&amp;nbsp;주로&amp;nbsp;서비스의&amp;nbsp;시작.중지.재시작,상태&amp;nbsp;확인을&amp;nbsp;하는데에&amp;nbsp;명령어를&amp;nbsp;사용한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;1. service {서비스 이름} start: 서비스 시작 &lt;br /&gt;2. service {서비스 이름} stop: 서비스 종료 &lt;br /&gt;3. service {서비스 이름} restart: 서비스 재시작 &lt;br /&gt;4. service {서비스 이름} condrestart: 서비스가 동작하고 있는 경우 재시작 &lt;br /&gt;5. service {서비스 이름} reload: 서비스 설정 reload &lt;br /&gt;6. service {서비스 이름} status: 서비스 상태 확인&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;&lt;b&gt;3. systemctl&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;systemctl은 systemd를 제어하는 명령어이다.&lt;br /&gt;&lt;br /&gt;systemctl의&amp;nbsp;핵심은&amp;nbsp;서비스를&amp;nbsp;관리한다는거다.&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;&amp;nbsp;SSH 서버를 켜고 싶다&lt;/li&gt;
&lt;li&gt;웹 서버가 살아 있는지 보고 싶다 &lt;/li&gt;
&lt;li&gt;재부팅 후에도 자동 실행되게 하고 싶다 &lt;br /&gt;이런&amp;nbsp;상황일때&amp;nbsp;systemctl를&amp;nbsp;사용한다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면&amp;nbsp;여기서&amp;nbsp;서비스가&amp;nbsp;무엇이냐.&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;서비스는&amp;nbsp;백그라운드에서&amp;nbsp;계속&amp;nbsp;동작을&amp;nbsp;하면서&amp;nbsp;특정&amp;nbsp;기능을&amp;nbsp;제공하는&amp;nbsp;프로그램이다.&amp;nbsp;&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;예를&amp;nbsp;들면&amp;nbsp;sshd:&amp;nbsp;원격&amp;nbsp;접속을&amp;nbsp;제공하는것처럼&amp;nbsp;이러한&amp;nbsp;서비스들을&amp;nbsp;사용자가&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;systemctl&lt;/b&gt;가 한다. &lt;br /&gt;&lt;br /&gt;또한 systemctl이 가장 많이 하는 일은&amp;nbsp; &lt;br /&gt;1. 서비스가 실행중인지 여부를 판단: systemctl status sshd &lt;br /&gt;2. 서비스 실행: systemctl start sshd &lt;br /&gt;3. 서비스 중지: systemctl stop sshd &lt;br /&gt;4. 서비스 다시 시작: systemctl restart sshd &lt;br /&gt;5. 다시 불러오기 reload 가 있다&lt;/p&gt;</description>
      <category>리눅스 공부</category>
      <author>otopligrm</author>
      <guid isPermaLink="true">https://otopligrm.tistory.com/35</guid>
      <comments>https://otopligrm.tistory.com/35#entry35comment</comments>
      <pubDate>Thu, 2 Apr 2026 19:38:02 +0900</pubDate>
    </item>
    <item>
      <title>Vi  편집기 주요 명령어 정리</title>
      <link>https://otopligrm.tistory.com/34</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 vi 편집기는 명령모드, 입력모드, 콜론모(마지막 행) 총 3가지가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 Vi 편집기란 무엇일까?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Vi 편집기: 리눅스에서 기본으로 제공하는 문서 편집기이다.&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;vi의 설정은 vimrc 파일을 변경하여 수정 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vimrc 파일은 cd 명령어로 홈으로 이동하여 만들어주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;cd ~&lt;/b&gt; (홈으로 이동)&amp;nbsp; &amp;rarr;&amp;nbsp; &lt;b&gt;vi .vimrc&lt;/b&gt;(vi 명령어로 vimrc 파일 생성)&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-ke-size=&quot;size23&quot;&gt;&lt;b&gt;VI와 VIM의 차이&amp;nbsp;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vi는 편집기는 유닉스의 가장 기본적인 편집 에디터이며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vim은 vi improved의 약자로 vi의 확장 버전입니다. 즉 vi에 기능들이 더 추가되어있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vi와 vim의 가장 큰 차이는 화살표 방향키로 커서 이동의 여부이다.&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;대부분의 리눅스에는 vi를 호출하여도 vim이 실행되도록 설정 되어있지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도커 이미지를 이용한 설치 등 최소 기준으로 리눅스를 설치한 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vim편집기가 없을 수 도 있다.&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-ke-size=&quot;size23&quot;&gt;&lt;b&gt;명령모드&lt;/b&gt;&lt;/h3&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 data-end=&quot;1037&quot; data-start=&quot;1016&quot;&gt;w : 다음 단어 시작으로 이동&lt;/li&gt;
&lt;li data-end=&quot;1059&quot; data-start=&quot;1038&quot;&gt;b : 이전 단어 시작으로 이동&lt;/li&gt;
&lt;li data-end=&quot;1076&quot; data-start=&quot;1060&quot;&gt;0 : 현재 줄 맨 앞&lt;/li&gt;
&lt;li data-end=&quot;1110&quot; data-start=&quot;1094&quot;&gt;gg : 파일 맨 처음&lt;/li&gt;
&lt;li data-end=&quot;1125&quot; data-start=&quot;1111&quot;&gt;G : 파일 맨 끝&lt;/li&gt;
&lt;li data-end=&quot;1378&quot; data-start=&quot;1359&quot;&gt;x : 현재 문자 1개 삭제&lt;/li&gt;
&lt;li data-end=&quot;1398&quot; data-start=&quot;1379&quot;&gt;dd : 현재 줄 전체 삭제&lt;/li&gt;
&lt;li data-end=&quot;1398&quot; data-start=&quot;1379&quot;&gt;yy : 현재 줄 복사&lt;/li&gt;
&lt;li data-end=&quot;1398&quot; data-start=&quot;1379&quot;&gt;p: 붙여넣기&lt;/li&gt;
&lt;li data-end=&quot;1398&quot; data-start=&quot;1379&quot;&gt;u : 방금 작업 취소&lt;/li&gt;
&lt;li data-end=&quot;1603&quot; data-start=&quot;1584&quot;&gt;/문자열 : 아래 방향 검색&lt;/li&gt;
&lt;li data-end=&quot;1639&quot; data-start=&quot;1623&quot;&gt;n : 다음 검색 결과&lt;/li&gt;
&lt;li data-end=&quot;1639&quot; data-start=&quot;1623&quot;&gt;Shift+Z를 두 번: 저장 후 종료&lt;/li&gt;
&lt;/ul&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;입력모드&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;985&quot; data-start=&quot;961&quot;&gt;i : 현재 커서 위치에서 입력 시작&lt;/li&gt;
&lt;li data-end=&quot;1012&quot; data-start=&quot;986&quot;&gt;a : 현재 커서 다음 칸에서 입력 시작&lt;/li&gt;
&lt;li data-end=&quot;1043&quot; data-start=&quot;1013&quot;&gt;o : 현재 줄 아래에 새 줄 만들고 입력 시작&lt;/li&gt;
&lt;li data-end=&quot;1043&quot; data-start=&quot;1013&quot;&gt;&lt;span&gt;A: &lt;/span&gt;현재 줄의 맨 앞에서 입력 시작&lt;/li&gt;
&lt;li data-end=&quot;1043&quot; data-start=&quot;1013&quot;&gt;I: 현재 줄의 맨 앞에서 입력 시작&lt;/li&gt;
&lt;/ul&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;콜론 모드&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1241&quot; data-start=&quot;1230&quot;&gt;:w &amp;rarr; 저장&lt;/li&gt;
&lt;li data-end=&quot;1253&quot; data-start=&quot;1242&quot;&gt;:q &amp;rarr; 종료&lt;/li&gt;
&lt;li data-end=&quot;1271&quot; data-start=&quot;1254&quot;&gt;:wq &amp;rarr; 저장 후 종료&lt;/li&gt;
&lt;li data-end=&quot;1295&quot; data-start=&quot;1272&quot;&gt;:q! &amp;rarr; 저장 안 하고 강제 종료&lt;/li&gt;
&lt;li data-end=&quot;1295&quot; data-start=&quot;1272&quot;&gt;:wq! : 강제로 저장 후 종료가 필요한 상황에서 사용&lt;/li&gt;
&lt;/ul&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;모드 전환&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-start=&quot;1111&quot; data-end=&quot;1125&quot;&gt;:(콜론) : 콜론 모드로&lt;/li&gt;
&lt;li data-end=&quot;1197&quot; data-start=&quot;1173&quot;&gt;i : 현재 커서 위치에서 입력 시작&lt;/li&gt;
&lt;li data-end=&quot;1224&quot; data-start=&quot;1198&quot;&gt;a : 현재 커서 다음 칸에서 입력 시작&lt;/li&gt;
&lt;li data-end=&quot;1254&quot; data-start=&quot;1225&quot;&gt;o : 현재 줄 아래 새 줄 만들고 입력 시작&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>리눅스 공부</category>
      <author>otopligrm</author>
      <guid isPermaLink="true">https://otopligrm.tistory.com/34</guid>
      <comments>https://otopligrm.tistory.com/34#entry34comment</comments>
      <pubDate>Wed, 1 Apr 2026 15:09:18 +0900</pubDate>
    </item>
    <item>
      <title>Rocky Linux 기반 서버 구축 및 네트워크 초기 설정</title>
      <link>https://otopligrm.tistory.com/32</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;배경&lt;/b&gt;&lt;/h2&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;서버는 총 6대가 있으며, 저는 첫번째 서버를 선택하여서 부팅을 진행하였습니다.&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;Rocky Linux를 설치하게 되었습니다.&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Rocky Linux란?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rocky Linux 란. &lt;b&gt;Red Hat 계열&lt;/b&gt;의 리눅스 운영체제입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 분들이 데비안 계열의 &lt;b&gt;Ubuntu(우분투)&lt;/b&gt;를 많이 사용하실텐데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 굳이 Rocky Linux 를 선택했는지 의문점이 드실겁니다.&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;친숙한 우분투보다는 평소에 거의 사용해보지않은 Rocky Linux를 직접 설치해보고&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;apt&lt;/b&gt; 명령어를 사용하지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rocky Linux 에서는 주로 &lt;b&gt;dnf, yum&lt;/b&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;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;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Rocky Linux 설치 과정&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;먼저 저는 개인 USB에 Rocky Linux 9.7버전을 다운 받았습니다.&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;9.7버전 선택의 이유는 새로운 버전은 업데이트 될 때 마다 9.1, 9.2 이런식으로 수정이됩니다. 10버전이 가장 최신이지만 혹시 모르는 오류와 위험성을 고려하여 구 버전중 가장 최신 버전인 9.7버전을 선택하게 되었습니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;서버 부팅 후 &lt;b&gt;Boot Manager&lt;/b&gt;에 들어온 뒤,아래의 사진처럼&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Boot Menu&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;4320&quot; data-origin-height=&quot;3076&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpmsfC/dJMcacvP3QY/6DtvyHEGkKNyQILxsdCKJk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpmsfC/dJMcacvP3QY/6DtvyHEGkKNyQILxsdCKJk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpmsfC/dJMcacvP3QY/6DtvyHEGkKNyQILxsdCKJk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpmsfC%2FdJMcacvP3QY%2F6DtvyHEGkKNyQILxsdCKJk%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;580&quot; height=&quot;413&quot; data-origin-width=&quot;4320&quot; data-origin-height=&quot;3076&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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그 다음 부팅 장치 목록에서 Rocky Linux 설치 USB를 선택하여,&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;USB에 저장했던 운영체제 파일로 부팅을 진행하였습니다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4320&quot; data-origin-height=&quot;2940&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccO088/dJMcaiW3Drk/X1mJkSIXNO3Z5WKxtfEXJ1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccO088/dJMcaiW3Drk/X1mJkSIXNO3Z5WKxtfEXJ1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccO088/dJMcaiW3Drk/X1mJkSIXNO3Z5WKxtfEXJ1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FccO088%2FdJMcaiW3Drk%2FX1mJkSIXNO3Z5WKxtfEXJ1%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;574&quot; height=&quot;391&quot; data-origin-width=&quot;4320&quot; data-origin-height=&quot;2940&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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;부팅이 진행되고, 이후 &lt;b&gt;GRUB&lt;/b&gt; 화면이 나타났습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;여기서 &lt;b&gt;install Rocky Linux 9.7&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;3680&quot; data-origin-height=&quot;2728&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blqvri/dJMcaiQh0Lg/HzvAyK30eihrvW0gXUbMTk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blqvri/dJMcaiQh0Lg/HzvAyK30eihrvW0gXUbMTk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blqvri/dJMcaiQh0Lg/HzvAyK30eihrvW0gXUbMTk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fblqvri%2FdJMcaiQh0Lg%2FHzvAyK30eihrvW0gXUbMTk%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;581&quot; height=&quot;431&quot; data-origin-width=&quot;3680&quot; data-origin-height=&quot;2728&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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이후, Rocky Linux 설치에 필요한 파일들을 불러오기 시작했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1145&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfeBQI/dJMcaax0LYk/WJQewogOdqW30GvtfHQH3K/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfeBQI/dJMcaax0LYk/WJQewogOdqW30GvtfHQH3K/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfeBQI/dJMcaax0LYk/WJQewogOdqW30GvtfHQH3K/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfeBQI%2FdJMcaax0LYk%2FWJQewogOdqW30GvtfHQH3K%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;581&quot; height=&quot;616&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1145&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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Rocky Linux 설치 화면에 접속해서 먼저 &lt;b&gt;Installation Destination&lt;/b&gt; 항목에서&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;OS를 설치할 &lt;b&gt;하드 디스크&lt;/b&gt;를 선택하였습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;저는 서버 내부의 저장 장치에 OS를 설치할 예정입니다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4320&quot; data-origin-height=&quot;2516&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MM30c/dJMcafswL3K/LW4D1MKdJOzPyFtlMDGfLk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MM30c/dJMcafswL3K/LW4D1MKdJOzPyFtlMDGfLk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MM30c/dJMcafswL3K/LW4D1MKdJOzPyFtlMDGfLk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMM30c%2FdJMcafswL3K%2FLW4D1MKdJOzPyFtlMDGfLk%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;590&quot; height=&quot;344&quot; data-origin-width=&quot;4320&quot; data-origin-height=&quot;2516&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그다음 &lt;b&gt;Software Selection&lt;/b&gt; 항목에서는 &lt;b&gt;Server with GUI&lt;/b&gt;가 아닌 &lt;b&gt;Server&lt;/b&gt;를 선택하였습니다.&lt;br /&gt;이유는 서버를 &lt;b&gt;SSH를 통한 원격 접속&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;4320&quot; data-origin-height=&quot;2428&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1az96/dJMcafswMhE/voM8H5XiP69WAZgkHX1wH1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1az96/dJMcafswMhE/voM8H5XiP69WAZgkHX1wH1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1az96/dJMcafswMhE/voM8H5XiP69WAZgkHX1wH1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1az96%2FdJMcafswMhE%2FvoM8H5XiP69WAZgkHX1wH1%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;575&quot; height=&quot;323&quot; data-origin-width=&quot;4320&quot; data-origin-height=&quot;2428&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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그 다음 &lt;b&gt;Network &amp;amp; Host Name&lt;/b&gt; 항목에서 이더넷 포트 중&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;실제로 연결된 포트를 선택하였습니다.&lt;br /&gt;그치만 세부 네트워크 설정은 자동으로 처리하지 않고,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이후 &lt;b&gt;CLI 환경에서 직접 수동으로 설정&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;2033&quot; data-origin-height=&quot;1334&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ah2zl/dJMcaco3emY/XgLCtP3ldTyLbuzU2oiFG0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ah2zl/dJMcaco3emY/XgLCtP3ldTyLbuzU2oiFG0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ah2zl/dJMcaco3emY/XgLCtP3ldTyLbuzU2oiFG0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAh2zl%2FdJMcaco3emY%2FXgLCtP3ldTyLbuzU2oiFG0%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;567&quot; height=&quot;372&quot; data-origin-width=&quot;2033&quot; data-origin-height=&quot;1334&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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;977&quot; data-start=&quot;804&quot; data-ke-size=&quot;size16&quot;&gt;다음으로 &lt;b&gt;Root Password&lt;/b&gt; 항목에서 root 계정의 비밀번호를 설정하였습니다.&lt;br /&gt;root 계정으로만 작업을 할 예정이라서 일반 user 계정은 생성하지 않았습니다..&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;1139&quot; data-start=&quot;979&quot; data-ke-size=&quot;size16&quot;&gt;모든 기본 설정을 마친 뒤 설치를 시작하였고,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;1139&quot; data-start=&quot;979&quot; data-ke-size=&quot;size16&quot;&gt;설치가 완료된 후에는 &lt;b&gt;CLI 화면에서 root 계정으로 로그인&lt;/b&gt;하였습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-end=&quot;1139&quot; data-start=&quot;979&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: center;&quot;&gt;nmtui에서 네트워크 설정&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;1139&quot; data-start=&quot;979&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;1139&quot; data-start=&quot;979&quot; data-ke-size=&quot;size16&quot;&gt;이후 SSH 환경을 구성하기 위해 &lt;b&gt;dnf install openssh-server를&lt;/b&gt; 입력하여&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;1139&quot; data-start=&quot;979&quot; data-ke-size=&quot;size16&quot;&gt;SSH 서버 설치를 시도하였습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;1284&quot; data-start=&quot;1141&quot; data-ke-size=&quot;size16&quot;&gt;하지만&amp;nbsp; 인터넷 연결이 정상적으로 설정되어 있지 않아 에러가 발생하였습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;1284&quot; data-start=&quot;1141&quot; data-ke-size=&quot;size16&quot;&gt;따라서&lt;b&gt; CLI&lt;/b&gt;에서 &lt;b&gt;nmtui&lt;/b&gt;를 입력하여 &lt;b&gt;nmtui&lt;/b&gt;로 이동하였습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;1284&quot; data-start=&quot;1141&quot; data-ke-size=&quot;size16&quot;&gt;여기서 앞서 확인했었던 4번째 Ethernet를 선택하고,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;1284&quot; data-start=&quot;1141&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;wifi&lt;/b&gt;가 아닌&lt;b&gt; Ethernet&lt;/b&gt;이며, &lt;b&gt;IPv4 &lt;/b&gt;네트워크를 사용할 것이라서&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;1284&quot; data-start=&quot;1141&quot; data-ke-size=&quot;size16&quot;&gt;환경에 맞게 설정을 하였습니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-end=&quot;1284&quot; data-start=&quot;1141&quot; 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;1080&quot; data-origin-height=&quot;948&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQmezL/dJMcah4YgBf/IJKJyPOCru95uNKHNAEueK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQmezL/dJMcah4YgBf/IJKJyPOCru95uNKHNAEueK/img.jpg&quot; data-alt=&quot;nmtui 화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQmezL/dJMcah4YgBf/IJKJyPOCru95uNKHNAEueK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQmezL%2FdJMcah4YgBf%2FIJKJyPOCru95uNKHNAEueK%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;574&quot; height=&quot;504&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;948&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;nmtui 화면&lt;/figcaption&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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;네트워크 설정 방식은 자동이 아닌, &lt;b&gt;manual&lt;/b&gt;를 선택하였습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그 이유는 &lt;b&gt;IP&lt;/b&gt;를 자동으로 할당 받는 것이 아닌 직접 주소를 입력하고, 관리하기 위해서입니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이후 &lt;b&gt;Address, Gateway, DNS&amp;nbsp;&lt;/b&gt;를 수동으로 입력하여&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;네트워크를 설정하였습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4320&quot; data-origin-height=&quot;2884&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cbOQe6/dJMcafswMzC/fCxN8vpKFgtnCzNQYlSu21/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cbOQe6/dJMcafswMzC/fCxN8vpKFgtnCzNQYlSu21/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cbOQe6/dJMcafswMzC/fCxN8vpKFgtnCzNQYlSu21/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcbOQe6%2FdJMcafswMzC%2FfCxN8vpKFgtnCzNQYlSu21%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;597&quot; height=&quot;399&quot; data-origin-width=&quot;4320&quot; data-origin-height=&quot;2884&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;네트워크 설정을 완료 한 뒤, &lt;b&gt;ifconfig&lt;/b&gt;를 입력하여&lt;br /&gt;설정한 IP 주소가 선택한 이더넷에 정상적으로 적용되었는지 확인하였습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이후 &lt;b&gt;ping 목적지 IP &lt;/b&gt;명령어를 사용하여 네트워크 통신 여부를 점검하였습니다.&lt;br /&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; 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;&lt;b&gt;ping&lt;/b&gt;은 설정한 주소와 통신이 가능한지 확인하는 명령어입니다,&lt;br /&gt;네트워크가&amp;nbsp;정상적으로&amp;nbsp;연결되어&amp;nbsp;있는지,&amp;nbsp;그리고&amp;nbsp;학교처럼&amp;nbsp;동일한&amp;nbsp;IP&amp;nbsp;대역을&amp;nbsp;사용하는&amp;nbsp;곳에서 &lt;br /&gt;충돌&amp;nbsp;가능성이&amp;nbsp;있는지를&amp;nbsp;확인하는데&amp;nbsp;활용을&amp;nbsp;했습니다.&lt;/li&gt;
&lt;/ul&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;&lt;b&gt;IP충돌&lt;/b&gt;이 발생하면 접속이 끊기는 현상이 발생하며,&amp;nbsp; &lt;br /&gt;심할&amp;nbsp;경우&amp;nbsp;두&amp;nbsp;장비&amp;nbsp;모두&amp;nbsp;정상적으로&amp;nbsp;네트워크&amp;nbsp;사용이&amp;nbsp;안될&amp;nbsp;수도&amp;nbsp;있습니다&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;b&gt;dnf install openssh-server &lt;/b&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;저장소 설정&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 단순하게 &lt;b&gt;mirror 모드&lt;/b&gt;는 단순하게 읽기 모드이며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 현재 mirror 모드로 설정 되어있기에 패키지가 다운이 안되는 줄 알았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글링을 해본 결과,&lt;b&gt; mirror모드&lt;/b&gt;라서 패키지 설치가 불가능했던 것이 아닌&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;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;dnf install openssh-server&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;명령어에서 dnf가 저장소 정보를&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;&lt;b&gt;dnf&lt;/b&gt;는 저장소(Repository)에&amp;nbsp;접속하여&amp;nbsp;패키지를&amp;nbsp;다운로드&amp;nbsp;한&amp;nbsp;다음,&amp;nbsp; &lt;br /&gt;설치를&amp;nbsp;하는&amp;nbsp;방식입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 저는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;root계정&lt;/b&gt;으로 저장소 파일을 직접 설정하였습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;먼저 dnf가 어디에서 ssh server를 다운로드 하는지 경로를 파악&lt;/li&gt;
&lt;li style=&quot;color: #333333; text-align: start;&quot;&gt;확인한 저장소 주소에 접근.&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;즉 패키지를 저장할 경로를 수정했다고 생각하시면 될 것 같습니다.&lt;/u&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;일단 그래서 저는 &lt;b&gt;&lt;span&gt;cd&lt;/span&gt;&lt;span&gt; /etc/yum.repos.d/&lt;/span&gt;&lt;/b&gt;&lt;span&gt; 명령어로 repos.d 파일로 이동했습니다.&lt;/span&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;b&gt;etc&lt;/b&gt;: 리눅스에서 시스템 전반의 설정 파일들이 모여 있는 디렉터리입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;repos.d&lt;/span&gt;&lt;/b&gt; : 패키지 저장소 설정 파일들이 들어 있는 곳입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;repo&lt;/b&gt;: dnf가 어느 저장소를 사용할지 알려주는 설정 파일입니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 &lt;b&gt;ls&lt;/b&gt; 명령어로 &lt;b&gt;&lt;span&gt;repos.d&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;span&gt;내부 파일들을 확인하였고,&amp;nbsp;&lt;/span&gt;&lt;span&gt;&lt;b&gt;rocky.repo&lt;/b&gt;를 선택했습니다.&lt;/span&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;b&gt; rocky.repo:&lt;/b&gt; Rocky&amp;nbsp;Linux의&amp;nbsp;기본&amp;nbsp;저장소&amp;nbsp;정보를&amp;nbsp;담고&amp;nbsp;있는&amp;nbsp;설정&amp;nbsp;파일입니다.&amp;nbsp; &lt;br /&gt;여러가지&amp;nbsp;정보들이&amp;nbsp;있지만&amp;nbsp;제가&amp;nbsp;받으려는&amp;nbsp;ssh패키지를&amp;nbsp; &lt;br /&gt;어디서&amp;nbsp;다운을&amp;nbsp;받을&amp;nbsp;것인지&amp;nbsp;주소&amp;nbsp;정보가&amp;nbsp;들어있습니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 &lt;b&gt;rocky.repo&lt;/b&gt;를 수정하기 위해서 &lt;b&gt;vi rocky.repo&lt;/b&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;b&gt;vi:&amp;nbsp;&lt;/b&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;파일일들을 확인하면서 mirrow.list 주소를 주석처리하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 주석처리 되어있던 baseurl의 주소를 주석해제 하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mirror.list와 baseurl에 대해서는 저도 잘 몰라서 개인적으로 공부 한 뒤,&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;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;b&gt;원인&lt;/b&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;p data-ke-size=&quot;size16&quot;&gt;원인은 운영체제 설치 및 설정을 하는과정에서 자동이 아닌 수동을 대부분 설정을 하였기에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자동 &lt;b&gt;mirror&lt;/b&gt; 선택 과정에서 문제가 있었으며, 앞서 &lt;b&gt;ip&lt;/b&gt;와 &lt;b&gt;gateway&lt;/b&gt;, &lt;b&gt;DNS&lt;/b&gt;를 수동으로 설정을 하였기에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;baseurl&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;&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;b&gt;결과&lt;/b&gt;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333; font-size: 16px; letter-spacing: 0px;&quot;&gt;외부 노트북에서 &lt;b&gt;ssh root@서버IP주소&lt;/b&gt; 명령어를 입력한 뒤,&lt;/span&gt;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333; font-size: 16px; letter-spacing: 0px;&quot;&gt; root 계정의 비밀번호를 인증하여, 서버에 원격으로 접속하였습니다. &lt;/span&gt;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333; font-size: 16px; letter-spacing: 0px;&quot;&gt;이 과정은 &lt;b&gt;SSH&lt;/b&gt;를 이용한 원격 로그인 과정으로, 네트워크를 통해 서버를 관리할 수 있습니다.&lt;/span&gt;&lt;/h2&gt;</description>
      <category>네크워크 공부</category>
      <author>otopligrm</author>
      <guid isPermaLink="true">https://otopligrm.tistory.com/32</guid>
      <comments>https://otopligrm.tistory.com/32#entry32comment</comments>
      <pubDate>Tue, 31 Mar 2026 19:44:10 +0900</pubDate>
    </item>
  </channel>
</rss>