<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>cse23_ewha.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Wed, 15 Jul 2026 05:55:04 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>cse23_ewha.log</title>
            <url>https://velog.velcdn.com/images/cse23_ewha/profile/37bfbdc7-cca0-4dfc-a705-cbc4a8a7dbf8/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. cse23_ewha.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/cse23_ewha" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Prometheus + Grafana로 오픈소스 모니터링 시스템 구축하기]]></title>
            <link>https://velog.io/@cse23_ewha/Prometheus-Grafana%EB%A1%9C-%EC%98%A4%ED%94%88%EC%86%8C%EC%8A%A4-%EB%AA%A8%EB%8B%88%ED%84%B0%EB%A7%81-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@cse23_ewha/Prometheus-Grafana%EB%A1%9C-%EC%98%A4%ED%94%88%EC%86%8C%EC%8A%A4-%EB%AA%A8%EB%8B%88%ED%84%B0%EB%A7%81-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 15 Jul 2026 05:55:04 GMT</pubDate>
            <description><![CDATA[<h1 id="1-이걸-왜-쓰는가">1. 이걸 왜 쓰는가</h1>
<p>서버나 애플리케이션이 잘 돌아가고 있는지 눈으로 확인하고 싶을 때, 가장 먼저 부딪히는 문제는 데이터를 어떻게 모으고, 어떻게 보여줄까이다. Prometheus와 Grafana는 이 두 문제를 각각 나눠서 해결하는 오픈소스 조합이다.</p>
<p>Prometheus</p>
<ul>
<li>데이터 수집과 저장, 질의를 담당
정해진 주기로 서버, 컨테이너, 애플리케이션에서 지표(metric)를 긁어와 시계열 데이터베이스에 쌓고, PromQL이라는 쿼리 언어로 원하는 조건을 뽑아냄
CPU 사용률이 5분 동안 80%를 넘었는지, 특정 API의 응답 시간이 얼마나 튀었는지 같은 질문에 답 가능</li>
</ul>
<p>Grafana</p>
<ul>
<li>이 데이터를 그래프와 대시보드로 보여주는 역할만함
Grafana 자체는 데이터를 수집하지X
Prometheus 외에도 MySQL, Loki, Elasticsearch 등 수십 가지 데이터소스를 붙일 수 있어서, 나중에 로그나 다른 DB까지 한 화면에서 보고 싶을 때 확장하기 쉬움</li>
</ul>
<p>정리하면 두 도구는 경쟁 관계가 아니라 역할 분담 관계
Prometheus가 모으고, Grafana가 보여줌
(대부분의 모니터링 스택에서 이 둘은 세트로 함께 쓰인다)</p>
<h1 id="2-왜-이-조합을-선택해야-하는가">2. 왜 이 조합을 선택해야 하는가</h1>
<p>첫째, 완전한 오픈소스라 비용 부담이 없다. 
Datadog이나 New Relic 같은 SaaS형 모니터링 툴은 사용량에 따라 과금되는데, 트래픽이 늘수록 비용이 엄청나게 늘어난다. Prometheus와 Grafana는 직접 서버에 설치해서 쓰는 자체 호스팅(self-hosted) 방식이라 라이선스 비용이 없다.</p>
<p>둘째, 쿠버네티스와 클라우드 네이티브 환경의 사실상 표준이다. 
Kubernetes 생태계에서 Prometheus는 CNCF(Cloud Native Computing Foundation) 프로젝트로 채택되어 있고, 파드나 노드의 상태를 긁어오는 exporter가 이미 풍부하게 존재한다. 커뮤니티가 크기 때문에 문제가 생겼을 때 검색으로 답을 찾기 쉽다.</p>
<p>셋째, 유연한 커스터마이징이 가능하다. 
코드가 공개되어 있으니 회사 내부 정책에 맞게 데이터 보관 정책, 알림 규칙, 대시보드 디자인을 원하는 대로 바꿀 수 있다. SaaS 툴은 이런 부분이 벤더 정책에 묶여있는 경우가 많다.</p>
<h1 id="3-어떤-상황에서-이-조합이-좋은가">3. 어떤 상황에서 이 조합이 좋은가</h1>
<ul>
<li><p>지표(metric) 중심의 모니터링이 필요할 때 적합하다. 
CPU, 메모리, 요청 수, 에러율처럼 숫자로 떨어지는 데이터를 실시간에 가깝게 보고 싶은 경우다. 반대로 로그 원문을 뒤지거나 분산 트레이싱까지 필요하다면 Loki, Tempo 같은 도구를 추가로 붙여야 한다.</p>
</li>
<li><p>인프라를 직접 운영하는 팀에 적합하다. 
서버든 컨테이너든 직접 관리하고 있고, 그 안에 모니터링 서버 하나 정도는 추가로 운영할 여력이 있는 경우다. 반대로 운영 인력이 아예 없거나 인프라를 신경 쓰고 싶지 않다면 Datadog 같은 매니지드 SaaS가 더 편할 수 있다.</p>
</li>
<li><p>데이터 보관 기간이 길지 않아도 되는 경우에 적합하다. 
Prometheus는 기본적으로 로컬 디스크에 데이터를 저장하기 때문에, 1년 이상의 장기 이력을 저장하려면 Thanos나 VictoriaMetrics 같은 별도 컴포넌트를 추가해야 한다. 최근 몇 주 정도의 데이터만 보면 충분한 경우라면 기본 구성만으로 충분하다.</p>
</li>
<li><p>소규모 개인 프로젝트라면 오히려 과할 수 있다. 
서비스가 5개 미만인 개인 프로젝트나 단순히 &quot;서버가 죽었는지 살았는지&quot;만 확인하고 싶다면, Uptime Kuma 같은 훨씬 가벼운 툴이 낫다. Prometheus + Grafana는 어느 정도 규모가 있는 서비스에서 적합하다</p>
</li>
</ul>
<p>요약!!! 다음과 같은 상황에서 이 조합 선택하기</p>
<ul>
<li>여러 서버/컨테이너의 지표를 한 곳에서 모아 보고 싶을 때</li>
<li>쿠버네티스 클러스터를 운영 중일 때</li>
<li>비용을 들이지 않고 자체 인프라에 모니터링을 구축하고 싶을 때</li>
<li>알림 규칙이나 대시보드를 세밀하게 커스터마이징하고 싶을 때</li>
</ul>
<h1 id="4-설치-준비물">4. 설치 준비물</h1>
<p>진짜 최소 필요사양</p>
<ul>
<li>Docker Engine 20.10 이상</li>
<li>Docker Compose 1.29 이상 (또는 Docker Desktop에 내장된 Compose)</li>
<li>여유 메모리 4GB, 여유 디스크 2GB 이상</li>
<li>3000번(Grafana), 9090번(Prometheus) 포트가 비어있을 것</li>
</ul>
<p>Docker가 없다면 아래 명령으로 설치 여부를 먼저 확인</p>
<pre><code class="language-bash">docker --version
docker compose version</code></pre>
<p>둘 다 버전이 출력되면 준비 완료</p>
<h1 id="5-폴더-구조-만들기">5. 폴더 구조 만들기</h1>
<p>작업할 폴더를 하나 만들고 그 안에 다음과 같은 구조를 준비</p>
<pre><code>monitoring/
├── docker-compose.yml
└── prometheus/
    └── prometheus.yml</code></pre><p>터미널에서 실행하면 한 번에 만들 수 있음</p>
<pre><code class="language-bash">mkdir -p monitoring/prometheus
cd monitoring</code></pre>
<h1 id="6-prometheus-설정-파일-작성">6. Prometheus 설정 파일 작성</h1>
<p><code>monitoring/prometheus/prometheus.yml</code> 파일을 아래 내용으로 만듦
(이 파일은 Prometheus가 &quot;어디서, 얼마나 자주&quot; 데이터를 긁어올지 정의)</p>
<pre><code class="language-yaml">global:
  scrape_interval: 15s     # 15초마다 지표 수집
  evaluation_interval: 15s

scrape_configs:
  # Prometheus 자기 자신을 모니터링
  - job_name: &#39;prometheus&#39;
    static_configs:
      - targets: [&#39;localhost:9090&#39;]

  # 서버(호스트)의 CPU, 메모리, 디스크 지표 수집
  - job_name: &#39;node-exporter&#39;
    static_configs:
      - targets: [&#39;node-exporter:9100&#39;]

  # 컨테이너별 리소스 사용량 수집
  - job_name: &#39;cadvisor&#39;
    static_configs:
      - targets: [&#39;cadvisor:8080&#39;]</code></pre>
<ul>
<li>node-exporter : 리눅스 서버의 CPU/메모리/디스크 같은 하드웨어 지표를 뽑아주는 보조 프로그램</li>
<li>cAdvisor : 도커 컨테이너별 리소스 사용량을 뽑아주는 보조 프로그램</li>
<li><blockquote>
<p>둘 다 아래 Compose 파일에서 같이 띄움</p>
</blockquote>
</li>
</ul>
<h1 id="7-docker-composeyml-작성">7. docker-compose.yml 작성</h1>
<p><code>monitoring/docker-compose.yml</code> 파일을 아래처럼 만듦</p>
<pre><code class="language-yaml">version: &#39;3.8&#39;

services:
  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    volumes:
      - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus
    ports:
      - &quot;9090:9090&quot;
    restart: unless-stopped

  node-exporter:
    image: prom/node-exporter:latest
    container_name: node-exporter
    ports:
      - &quot;9100:9100&quot;
    restart: unless-stopped

  cadvisor:
    image: gcr.io/cadvisor/cadvisor:latest
    container_name: cadvisor
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:ro
      - /sys:/sys:ro
      - /var/lib/docker/:/var/lib/docker:ro
    ports:
      - &quot;8080:8080&quot;
    restart: unless-stopped

  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    volumes:
      - grafana_data:/var/lib/grafana
    ports:
      - &quot;3000:3000&quot;
    environment:
      - GF_SECURITY_ADMIN_USER=admin
      - GF_SECURITY_ADMIN_PASSWORD=admin
    restart: unless-stopped
    depends_on:
      - prometheus

volumes:
  prometheus_data:
  grafana_data:</code></pre>
<p><code>GF_SECURITY_ADMIN_PASSWORD</code>는 반드시 실제 사용할 비밀번호로 바꾸는 걸 추천
볼륨(<code>prometheus_data</code>, <code>grafana_data</code>)을 지정해 뒀기 때문에 컨테이너를 재시작해도 데이터는 유지되긴함</p>
<h1 id="8-실행하기">8. 실행하기</h1>
<p><code>monitoring</code> 폴더에서 아래 명령을 실행</p>
<pre><code class="language-bash">docker compose up -d</code></pre>
<p><code>-d</code>는 백그라운드 실행 옵션
실행 후 아래 명령으로 4개 컨테이너가 모두 정상 기동됐는지 확인</p>
<pre><code class="language-bash">docker compose ps</code></pre>
<p>-&gt; <code>prometheus</code>, <code>node-exporter</code>, <code>cadvisor</code>, <code>grafana</code> 4개 컨테이너 상태가 모두 <code>Up</code>이면 성공</p>
<h1 id="9-prometheus-접속-확인">9. Prometheus 접속 확인</h1>
<ul>
<li>브라우저에서 <code>http://localhost:9090</code>에 접속</li>
<li>상단 메뉴의 Status → Targets로 들어가면 방금 등록한 job(prometheus, node-exporter, cadvisor)들이 모두 <code>UP</code> 상태로 보여야 정상</li>
<li>하나라도 <code>DOWN</code>이면 <code>prometheus.yml</code>의 target 주소나 컨테이너 이름을 다시 확인</li>
</ul>
<h1 id="10-grafana-접속-및-데이터소스-연결">10. Grafana 접속 및 데이터소스 연결</h1>
<ul>
<li>브라우저에서 <code>http://localhost:3000</code>에 접속</li>
<li>초기 로그인 정보는 위 compose 파일에서 지정한 <code>admin</code> / <code>admin</code>이며, 최초 로그인 시 비밀번호 변경을 요구할 수 있음</li>
</ul>
<p>로그인 후 데이터소스를 연결</p>
<ol>
<li>왼쪽 메뉴에서 Connections → Data sources로 이동</li>
<li>Add new data source 클릭 후 Prometheus 선택</li>
<li>URL 입력란에 <code>http://prometheus:9090</code> 입력 (컨테이너 이름으로 접속하므로 localhost가 아님에 주의)</li>
<li>하단 Save &amp; Test 클릭 → 초록색 성공 메시지 확인</li>
</ol>
<h1 id="11-대시보드-만들기">11. 대시보드 만들기</h1>
<p>직접 패널을 만들 수도 있지만, 커뮤니티에 이미 잘 만들어진 대시보드를 가져다 쓰는 게 훨씬 빠르긴함</p>
<ol>
<li>왼쪽 메뉴 Dashboards → New → Import 클릭</li>
<li>Import via grafana.com 항목에 대시보드 ID 입력<ul>
<li>Node Exporter Full: <code>1860</code></li>
<li>Docker and system monitoring (cAdvisor): <code>893</code></li>
</ul>
</li>
<li>Load 클릭 → 데이터소스로 앞서 만든 Prometheus 선택 → Import 클릭</li>
</ol>
<h1 id="12-종료-및-정리">12. 종료 및 정리</h1>
<p>모니터링 스택을 끄고 싶을 때는 다음 명령을 사용</p>
<pre><code class="language-bash">docker compose stop</code></pre>
<p>컨테이너와 네트워크까지 완전히 제거하되 데이터는 보존하려면 다음을 사용</p>
<pre><code class="language-bash">docker compose down</code></pre>
<p>데이터까지 완전히 삭제하려면 <code>-v</code> 옵션을 추가</p>
<pre><code class="language-bash">docker compose down -v</code></pre>
<h1 id="13-다음-단계로-고려할-것들">13. 다음 단계로 고려할 것들</h1>
<p>기본 설치가 끝난 뒤 실무에서 더 필요해지는 것</p>
<p>Alertmanager를 추가하면 특정 조건(예: CPU 90% 초과 5분 지속)에서 슬랙이나 이메일로 알림을 받을 수 있다. 데이터 보관 기간을 늘리고 싶다면 Thanos나 VictoriaMetrics를 검토한다. 애플리케이션 자체의 커스텀 지표(예: 주문 처리 건수)를 수집하려면 각 언어별 Prometheus 클라이언트 라이브러리(Python, Java, Node.js 등)로 <code>/metrics</code> 엔드포인트를 직접 노출시키면 된다.</p>
<h1 id="참고-자료">참고 자료</h1>
<ul>
<li><a href="https://prometheus.io/docs/introduction/overview/">Prometheus 공식 문서</a></li>
<li><a href="https://grafana.com/docs/grafana/latest/setup-grafana/installation/docker/">Grafana 공식 문서 - Docker 설치</a></li>
<li><a href="https://github.com/docker/awesome-compose/blob/master/prometheus-grafana/README.md">docker/awesome-compose - prometheus-grafana 예제</a></li>
<li><a href="https://grafana.com/docs/grafana-cloud/send-data/metrics/metrics-prometheus/prometheus-config-examples/docker-compose-linux/">Grafana Cloud - Docker Compose로 리눅스 호스트 모니터링</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[시험 대비 교재 — 쿠버네티스 Pod 기반 서비스 & Podman 기반 서비스]]></title>
            <link>https://velog.io/@cse23_ewha/%EC%8B%9C%ED%97%98-%EB%8C%80%EB%B9%84-%EA%B5%90%EC%9E%AC-%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-Pod-%EA%B8%B0%EB%B0%98-%EC%84%9C%EB%B9%84%EC%8A%A4-Podman-%EA%B8%B0%EB%B0%98-%EC%84%9C%EB%B9%84%EC%8A%A4</link>
            <guid>https://velog.io/@cse23_ewha/%EC%8B%9C%ED%97%98-%EB%8C%80%EB%B9%84-%EA%B5%90%EC%9E%AC-%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-Pod-%EA%B8%B0%EB%B0%98-%EC%84%9C%EB%B9%84%EC%8A%A4-Podman-%EA%B8%B0%EB%B0%98-%EC%84%9C%EB%B9%84%EC%8A%A4</guid>
            <pubDate>Wed, 08 Jul 2026 00:05:29 GMT</pubDate>
            <description><![CDATA[<h2 id="1-pod란-무엇인가-핵심-개념">1. Pod란 무엇인가 (핵심 개념)</h2>
<ul>
<li>인프라 엔지니어 관점에서 Pod = &quot;인프라 컨테이너&quot;라고도 부름.</li>
<li>Pod가 생성되면 눈에 보이지 않는 <strong>infra 컨테이너(<code>/pause</code>)</strong>가 먼저 뜨고, 이 컨테이너가 <strong>PID 1</strong>로 동작.</li>
<li>이 infra 컨테이너가 Pod에 속한 모든 컨테이너를 대신해서 커널 자원을 관리함:<ul>
<li><strong>namespace</strong> (커널)</li>
<li><strong>c-group</strong> (커널)</li>
<li><strong>network</strong>: LinuxBridge + TUN/TAP</li>
</ul>
</li>
<li>web, db 컨테이너도 자체 IPC는 갖고 있지만, 가상머신처럼 시스템콜이 Pod(infra 컨테이너)를 거쳐서 호스트와 연결됨.</li>
<li><strong>핵심 포인트</strong>: Pod 안의 web이나 api 컨테이너가 죽어서 재시작되더라도, pause 컨테이너가 살아있는 한 <strong>Pod의 IP는 바뀌지 않음</strong>.</li>
</ul>
<blockquote>
<p>시험 포인트: &quot;Pod 안의 컨테이너가 재시작돼도 바뀌지 않는 것은?&quot; → <strong>Pod의 IP 주소</strong> (pause 컨테이너가 네트워크 네임스페이스를 유지하기 때문)</p>
</blockquote>
<hr>
<h2 id="2-service-외부-노출">2. Service (외부 노출)</h2>
<ul>
<li><code>kind: Service</code>는 <strong>외부에 노출</strong>하는 역할.</li>
<li>예제의 <code>wordpress-mysql</code> 서비스는 <code>clusterIP: None</code>으로 설정됨 → <strong>헤드리스 서비스(Headless Service)</strong>. DB처럼 고정된 개별 Pod에 직접 붙어야 하는 경우 사용.</li>
<li><code>selector</code>로 어떤 라벨을 가진 Pod에 연결할지 지정 (<code>app: wordpress</code>, <code>tier: mysql</code>처럼).</li>
</ul>
<hr>
<h2 id="3-스토리지-체계-storageclass-→-pv-→-pvc">3. 스토리지 체계: StorageClass → PV → PVC</h2>
<ul>
<li><strong>DB는 데이터를 저장</strong>해야 하고, <strong>Web은 보통 저장하지 않음</strong> (상태 없는 stateless 컨테이너).</li>
<li><strong>PV (PersistentVolume)</strong>: 어떤 스토리지 유형/백엔드에 저장할지 결정하는 실체. 실제 디스크 자원 그 자체.</li>
<li><strong>PVC (PersistentVolumeClaim)</strong>: &quot;이 정도 용량, 이런 접근 모드로 스토리지 주세요&quot;라고 요청하는 자원. <strong>실질적으로 데이터는 PVC를 통해 저장됨</strong>.</li>
<li><strong>StorageClass</strong>: 최신 실무 방식. 여러 스토리지 유형을 미리 정의해두고, PVC가 새로 생성될 때 알맞은 PV를 <strong>자동으로 생성(Dynamic Provisioning)</strong>해서 바인딩해줌.</li>
</ul>
<p><strong>흐름 정리</strong></p>
<ul>
<li>(전통적 방식) 관리자가 PV를 수동으로 미리 만듦 → PVC가 그 PV에 바인딩 → Pod가 PVC를 마운트</li>
<li>(실무/최신 방식) StorageClass 정의 → PVC 생성 요청 → StorageClass가 자동으로 PV 생성 → PVC와 바인딩 → Pod가 마운트</li>
</ul>
<blockquote>
<p>시험 포인트: &quot;PV와 PVC 중 실제로 Pod가 마운트해서 쓰는 것은?&quot; → <strong>PVC</strong>. PV는 그 뒤에서 실제 저장소가 무엇인지 결정하는 역할.</p>
</blockquote>
<hr>
<h2 id="4-secret">4. Secret</h2>
<ul>
<li>비밀번호 등 민감 정보를 저장하는 쿠버네티스 오브젝트.</li>
<li>예제에서는 <code>mysql-pass</code>라는 Secret 안에 <code>password</code> 키로 저장됨.</li>
<li>yaml에서는 <code>valueFrom.secretKeyRef</code>로 컨테이너 환경변수에 주입:</li>
</ul>
<pre><code class="language-yaml">env:
- name: MYSQL_ROOT_PASSWORD
  valueFrom:
    secretKeyRef:
      name: mysql-pass
      key: password</code></pre>
<ul>
<li>Secret을 실제로 만드는 명령어 (yaml에 없지만 시험에 나올 수 있음):</li>
</ul>
<pre><code class="language-bash">kubectl create secret generic mysql-pass --from-literal=password=&lt;비밀번호&gt;</code></pre>
<hr>
<h2 id="5-kubectl-핵심-명령어-정리">5. kubectl 핵심 명령어 정리</h2>
<table>
<thead>
<tr>
<th>명령어</th>
<th>용도</th>
<th>방식</th>
</tr>
</thead>
<tbody><tr>
<td><code>kubectl run</code></td>
<td>Pod 하나 즉석 실행</td>
<td>명령형(imperative)</td>
</tr>
<tr>
<td><code>kubectl create</code></td>
<td>리소스 즉석 생성</td>
<td>명령형(imperative)</td>
</tr>
<tr>
<td><code>kubectl apply -f &lt;파일&gt;.yaml</code></td>
<td>yaml 파일 기반으로 리소스 생성/갱신</td>
<td>선언형(declarative), 재실행해도 안전</td>
</tr>
<tr>
<td><code>kubectl get pod,svc,pv,pvc,ns</code></td>
<td>여러 리소스 상태를 한 번에 조회</td>
<td>조회</td>
</tr>
<tr>
<td><code>kubectl describe &lt;리소스&gt; &lt;이름&gt;</code></td>
<td>리소스 상세 정보/이벤트 확인</td>
<td>조회</td>
</tr>
<tr>
<td><code>kubectl delete pod &lt;이름&gt;</code></td>
<td>Pod 삭제</td>
<td>삭제</td>
</tr>
</tbody></table>
<blockquote>
<p>시험 포인트: WordPress+MySQL을 배포할 때 사용하는 명령은 <code>kubectl apply -f mysql.yaml</code>, <code>kubectl apply -f wordpress.yaml</code> — <strong>yaml 파일이 있으므로 선언형 명령인 <code>apply</code>를 씀</strong> (<code>run</code>/<code>create</code>가 아님).</p>
</blockquote>
<hr>
<h2 id="6-wordpress--mysql-예제-매니페스트">6. WordPress + MySQL 예제 매니페스트</h2>
<blockquote>
<p>⚠️ 노트에 <code>wordpress.yaml</code>이라고 적힌 코드가 <code>mysql.yaml</code>과 내용이 완전히 같았음 (복사 실수로 보임). 아래에 <strong>mysql-deployment</strong>와 <strong>wordpress-deployment</strong>를 구분해서 정리함.</p>
</blockquote>
<h3 id="6-1-mysql-deploymentyaml">6-1. mysql-deployment.yaml</h3>
<pre><code class="language-yaml">apiVersion: v1
kind: Service
metadata:
  name: wordpress-mysql
  labels:
    app: wordpress
spec:
  ports:
    - port: 3306
  selector:
    app: wordpress
    tier: mysql
  clusterIP: None      # 헤드리스 서비스
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-pv-claim
  labels:
    app: wordpress
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: wordpress-mysql
  labels:
    app: wordpress
spec:
  selector:
    matchLabels:
      app: wordpress
      tier: mysql
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: wordpress
        tier: mysql
    spec:
      containers:
      - image: mysql:8.0
        name: mysql
        env:
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-pass
              key: password
        - name: MYSQL_DATABASE
          value: wordpress
        - name: MYSQL_USER
          value: wordpress
        - name: MYSQL_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-pass
              key: password
        ports:
        - containerPort: 3306
          name: mysql
        volumeMounts:
        - name: mysql-persistent-storage
          mountPath: /var/lib/mysql
      volumes:
      - name: mysql-persistent-storage
        persistentVolumeClaim:
          claimName: mysql-pv-claim</code></pre>
<h3 id="6-2-wordpress-deploymentyaml-web-쪽--mysql과-구조는-같은-패턴-대상만-다름">6-2. wordpress-deployment.yaml (web 쪽 — mysql과 구조는 같은 패턴, 대상만 다름)</h3>
<pre><code class="language-yaml">apiVersion: v1
kind: Service
metadata:
  name: wordpress
  labels:
    app: wordpress
spec:
  ports:
    - port: 80
  selector:
    app: wordpress
    tier: frontend
  type: LoadBalancer
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: wp-pv-claim
  labels:
    app: wordpress
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: wordpress
  labels:
    app: wordpress
spec:
  selector:
    matchLabels:
      app: wordpress
      tier: frontend
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: wordpress
        tier: frontend
    spec:
      containers:
      - image: wordpress:latest
        name: wordpress
        env:
        - name: WORDPRESS_DB_HOST
          value: wordpress-mysql       # mysql Service 이름으로 접속
        - name: WORDPRESS_DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-pass
              key: password
        ports:
        - containerPort: 80
          name: wordpress
        volumeMounts:
        - name: wordpress-persistent-storage
          mountPath: /var/www/html
      volumes:
      - name: wordpress-persistent-storage
        persistentVolumeClaim:
          claimName: wp-pv-claim</code></pre>
<h3 id="6-3-배포-순서">6-3. 배포 순서</h3>
<pre><code class="language-bash">kubectl create secret generic mysql-pass --from-literal=password=&lt;비밀번호&gt;
kubectl apply -f mysql.yaml
kubectl apply -f wordpress.yaml

kubectl get pod,svc,pv,pvc,ns</code></pre>
<blockquote>
<p>시험 포인트: <strong>mysql을 먼저 apply하고 wordpress를 나중에 apply</strong>하는 게 일반적 (wordpress가 <code>WORDPRESS_DB_HOST=wordpress-mysql</code>로 mysql Service를 참조하기 때문). Secret은 두 매니페스트보다 먼저 만들어져 있어야 함.</p>
</blockquote>
<hr>
<h2 id="7-deployment--replicaset--pod-관계">7. Deployment / ReplicaSet / Pod 관계</h2>
<pre><code>Deployment  →  ReplicaSet  →  Pod (1개 이상)</code></pre><ul>
<li><strong>Deployment</strong>: 원하는 상태(이미지 버전, replica 수)를 선언. 롤링 업데이트/롤백 관리.</li>
<li><strong>ReplicaSet</strong>: Deployment가 자동으로 만듦. 지정된 개수만큼 Pod가 항상 떠 있도록 유지.</li>
<li><strong>Pod</strong>: 실제 컨테이너가 도는 최소 단위.</li>
</ul>
<p>위 예제에서 <code>strategy: type: Recreate</code>는 업데이트 시 기존 Pod를 먼저 삭제하고 새 Pod를 만드는 방식 (DB처럼 동시에 여러 개가 뜨면 안 되는 경우 사용).</p>
<hr>
<h2 id="8-podman-기반-dev-서비스-구성">8. Podman 기반 dev 서비스 구성</h2>
<p>개발 단계(dev)에서는 쿠버네티스 대신 <strong>Podman Pod</strong>로 가볍게 웹+DB(또는 LLM/AI 서비스)를 구성.</p>
<h3 id="8-1-설치">8-1. 설치</h3>
<pre><code class="language-bash">dnf install podman -y</code></pre>
<h3 id="8-2-pod-생성-외부-포트-게시">8-2. Pod 생성 (외부 포트 게시)</h3>
<pre><code class="language-bash">podman pod create --publish 80:80 --name &lt;pod이름&gt;
podman pod ls</code></pre>
<h3 id="8-3-pod-안에-컨테이너-붙이기">8-3. Pod 안에 컨테이너 붙이기</h3>
<pre><code class="language-bash">podman container run -d --image &lt;이미지&gt; --name app-web --pod &lt;pod이름&gt;
podman container run -d --image &lt;이미지&gt; --name app-db  --pod &lt;pod이름&gt;</code></pre>
<h3 id="8-4-확인">8-4. 확인</h3>
<pre><code class="language-bash">podman pod ls
podman container ls</code></pre>
<blockquote>
<p>시험 포인트: Podman에서 여러 컨테이너를 하나로 묶으려면 <strong>먼저 <code>podman pod create</code>로 Pod(+포트 게시)를 만들고</strong>, 그 다음 <code>podman container run --pod &lt;pod이름&gt;</code> 옵션으로 컨테이너를 그 Pod에 붙임. <strong>외부 포트:내부 포트</strong>는 Pod 생성 시 <code>--publish</code>로 지정.</p>
</blockquote>
<hr>
<h2 id="9-헷갈리기-쉬운-것들-정리-오답-노트용">9. 헷갈리기 쉬운 것들 정리 (오답 노트용)</h2>
<table>
<thead>
<tr>
<th>질문</th>
<th>정답</th>
</tr>
</thead>
<tbody><tr>
<td>Pod 안 컨테이너가 재시작돼도 안 바뀌는 것은?</td>
<td>Pod의 IP (pause 컨테이너가 유지)</td>
</tr>
<tr>
<td>Pod를 인프라 엔지니어는 뭐라고 부르나?</td>
<td>인프라 컨테이너</td>
</tr>
<tr>
<td>실제로 Pod가 마운트해서 쓰는 스토리지 자원은?</td>
<td>PVC (PV는 그 뒤에서 실체 결정)</td>
</tr>
<tr>
<td>여러 스토리지 유형 중 하나를 골라 PVC 요청 시 자동으로 PV를 만들어주는 것은?</td>
<td>StorageClass</td>
</tr>
<tr>
<td>yaml 파일로 리소스를 생성/갱신할 때 쓰는 명령은?</td>
<td><code>kubectl apply -f</code></td>
</tr>
<tr>
<td>비밀번호 등 민감정보를 담는 쿠버네티스 오브젝트는?</td>
<td>Secret</td>
</tr>
<tr>
<td>Podman에서 컨테이너 여러 개를 하나의 네트워크로 묶는 단위는?</td>
<td>Pod (<code>podman pod create</code>)</td>
</tr>
<tr>
<td>Podman Pod에 컨테이너를 넣을 때 쓰는 옵션은?</td>
<td><code>--pod &lt;pod이름&gt;</code></td>
</tr>
<tr>
<td>DB Deployment에서 <code>strategy: type: Recreate</code>를 쓰는 이유는?</td>
<td>동시에 여러 DB 인스턴스가 뜨는 것을 방지하기 위해</td>
</tr>
</tbody></table>
<hr>
<h2 id="10-연습-문제">10. 연습 문제</h2>
<p><strong>Q1.</strong> WordPress + MySQL을 쿠버네티스에 배포할 때, mysql.yaml과 wordpress.yaml을 적용하는 명령으로 올바른 것은?</p>
<ol>
<li><code>kubectl run -f mysql.yaml</code></li>
<li><code>kubectl create pod mysql.yaml</code></li>
<li><code>kubectl apply -f mysql.yaml</code></li>
<li><code>kubectl start -f mysql.yaml</code></li>
</ol>
<p>3번. yaml 파일 기반 선언형 배포는 apply를 씀.</details></p>
<p><strong>Q2.</strong> Pod 내부에서 PID 1로 동작하며 네트워크 네임스페이스를 유지시켜주는 컨테이너는?</p>
<ol>
<li>sidecar 컨테이너</li>
<li>init 컨테이너</li>
<li>infra 컨테이너 (<code>/pause</code>)</li>
<li>main 컨테이너</li>
</ol>
<details><summary>정답</summary>3번.</details>

<p><strong>Q3.</strong> 다음 중 실제로 애플리케이션 Pod가 마운트해서 데이터를 저장하는 쿠버네티스 오브젝트는?</p>
<ol>
<li>StorageClass</li>
<li>PersistentVolume (PV)</li>
<li>PersistentVolumeClaim (PVC)</li>
<li>ConfigMap</li>
</ol>
<details><summary>정답</summary>3번. PV는 그 뒤에서 실체를 결정하고, Pod가 직접 쓰는 건 PVC.</details>

<p><strong>Q4.</strong> <code>wordpress-mysql</code> Service의 <code>clusterIP: None</code> 설정이 의미하는 것은?</p>
<ol>
<li>서비스가 비활성화됨</li>
<li>외부에서 접근 불가능한 헤드리스 서비스</li>
<li>랜덤 IP 할당</li>
<li>로드밸런서 타입 서비스</li>
</ol>
<details><summary>정답</summary>2번.</details>

<p><strong>Q5.</strong> Podman에서 web, db 컨테이너를 하나의 네트워크로 묶으려면 가장 먼저 해야 할 명령은?</p>
<ol>
<li><code>podman container run --pod</code></li>
<li><code>podman network create</code></li>
<li><code>podman pod create --publish &lt;포트&gt;</code></li>
<li><code>podman build</code></li>
</ol>
<details><summary>정답</summary>3번. Pod를 먼저 만들고 그 다음 컨테이너를 붙임.</details>

<p><strong>Q6. (빈칸 채우기)</strong> 아래 명령어의 빈칸을 채우세요.</p>
<pre><code class="language-bash">podman pod create --publish ____:____ --name svc-blog
podman container run -d --pod svc-blog --name app_web ____</code></pre>
<details><summary>정답</summary>외부포트:내부포트 (예: 10080:80), 그리고 두 번째 빈칸엔 사용할 이미지 이름 (예: harbor주소/library/blog/web:v2)</details>

<p><strong>Q7.</strong> MySQL 컨테이너의 root 비밀번호를 안전하게 주입하기 위해 사용하는 오브젝트와 필드는?</p>
<ol>
<li>ConfigMap / data</li>
<li>Secret / secretKeyRef</li>
<li>Env 파일 / env_file</li>
<li>Volume / hostPath</li>
</ol>
<details><summary>정답</summary>2번.</details>

<p><strong>Q8.</strong> Deployment - ReplicaSet - Pod의 관계를 올바르게 나열한 것은?</p>
<ol>
<li>Pod가 ReplicaSet을 만들고, ReplicaSet이 Deployment를 만든다</li>
<li>Deployment가 ReplicaSet을 만들고, ReplicaSet이 Pod를 관리한다</li>
<li>셋은 서로 독립적이며 관계가 없다</li>
<li>ReplicaSet은 Pod의 하위 개념이다</li>
</ol>
<details><summary>정답</summary>2번.</details>
---
### 1. 주요 개념 정리

<ul>
<li><p><strong>Docker와 Podman의 차이점</strong></p>
<p>  <strong>Docker</strong>: 중앙 집중형 관리 방식으로 하나의 백그라운드 데몬(<code>dockerd</code>)이 컨테이너 생성, 실행, 이미지 관리, 네트워크, 볼륨 등의 라이프사이클을 통합 제어합니다. 데몬이 주로 root 권한으로 동작하여 보안 측면의 고려가 필요하며, 데몬 장애 시 컨테이너 제어에 영향을 받을 수 있습니다.</p>
<p>  <strong>Podman</strong>: 쿠버네티스(Kubernetes)와의 호환성을 고려한 설계로 데몬리스(Daemonless)로 동작하며, 단독 컨테이너뿐만 아니라 POD 기반으로 컨테이너를 생성하고 관리할 수 있는 환경을 제공합니다. Docker에서는 직접 다루기 어려운 POD 자원 생성과 Infra-Container 변경도 자유롭게 수행할 수 있습니다.</p>
</li>
<li><p><strong>컨테이너 생성자 (런타임) 종류</strong></p>
<p>  <strong>runc</strong>: 표준 런타임의 교과서로, Docker 및 Containerd 기반에서 주로 사용됩니다.</p>
<p>  <strong>crun</strong>: 현대 Linux 환경에 최적화된 실전 런타임으로, Podman + cgroup v2 조합 및 대다수 오픈소스 엔터프라이즈 시스템에서 사용됩니다.</p>
</li>
<li><p><strong>ROOTLESS</strong></p>
<ul>
<li>포드만에서 생성하는 컨테이너는 기본적으로 ROOTLESS 형태로 생성 및 구성됩니다 . root 권한 대신 일반 사용자로 콘솔에 직접 로그인하여 사용할 것이 권장됩니다.</li>
<li>OCI 사양의 런타임 구성 파일인 <code>/etc/subgid</code> 등을 통해 사용자 GID를 subgid에 명시된 값으로 매핑하여 사용합니다.</li>
</ul>
</li>
<li><p><strong>POD 및 인프라 컨테이너 (Infra Container)</strong></p>
<ul>
<li>쿠버네티스에서 제시한 개념으로, 포드만에서도 단독 또는 연동 컨테이너 관리를 위해 이를 구성할 수 있습니다.</li>
<li>Pod 내부의 구현을 위해 <code>pause</code>라는 애플리케이션 컨테이너를 사용하는데, 이를 &#39;인프라 컨테이너(infra-container)&#39; 혹은 &#39;pause container&#39;라고 부릅니다.</li>
<li>Pod는 애플리케이션 컨테이너가 연결되지 않으면 시작하지 않으며 , 볼륨/네트워크 포트/네임스페이스를 <code>pause</code> 애플리케이션에 전달 및 공유하는 방식으로 동작합니다. 구성 방식에 따라 인프라, 이닛(init), 프라이머리(primary), 사이드카(sidecar) 컨테이너 구조로 나뉩니다.</li>
</ul>
</li>
<li><p><strong>컨테이너 볼륨 마운트 옵션</strong></p>
<p>  <code>rw|ro</code>: 컨테이너에 쓰기 및 읽기로 볼륨 디스크 설정.</p>
<p>  <code>z|Z</code>: SELinux 컨텍스트 적용 및 허용.</p>
<p>  <code>[O]</code>: 오버레이 모듈을 통해 컨테이너에 마운트.</p>
<p>  <code>[U]</code>: 호스트 기반의 UID/GID를 변경 (컨테이너가 많을수록 생성 속도가 느려짐).</p>
<p>  <code>[no]copy</code>: 컨테이너 내부 볼륨에 내용을 복사하거나 복사하지 않음.</p>
<p>  <code>[no]dev</code>: 볼륨 내용에 프로세스 접근 가능 여부 설정 (기본값 nodev).</p>
<p>  <code>[no]exec</code>: 마운트된 볼륨 내 프로세스 실행 허용 여부 설정.</p>
<p>  <code>[no]suid</code>: nosuid 권한 변경 제한 설정.</p>
</li>
</ul>
<p><code>[r]bind</code>: 하위 디렉터리까지 재귀적으로 연결(rbind) 처리.</p>
<ul>
<li><strong>Podman 가상머신 (Podman Machine)</strong><ul>
<li>macOS나 Windows 환경은 Linux kernel namespace, cgroups, overlayfs 등의 네이티브 컨테이너 동작 조건을 갖추고 있지 않습니다. 따라서 Mac/Windows에서 Podman을 쓰기 위해 내부적으로 자동으로 생성되는 리눅스 VM 기술을 의미합니다 . macOS에서는 QEMU 및 HVF 기술을, Windows에서는 WSL2 기반 기술을 사용합니다.</li>
</ul>
</li>
</ul>
<h3 id="2-핵심-명령어-정리">2. 핵심 명령어 정리</h3>
<h4 id="1-일반-컨테이너-및-시스템-관리-명령어">1) 일반 컨테이너 및 시스템 관리 명령어</h4>
<ul>
<li><p>  <strong><code>podman run</code></strong>: 컨테이너를 생성하고 실행하는 명령어입니다. 백그라운드(<code>-d</code>) 또는 TTY를 통한 대화형 모드(<code>-it</code>) 형식을 지원하며, <code>--rm</code> 옵션을 주면 컨테이너 중지 시 자동으로 제거됩니다.</p>
</li>
<li><p>  <strong><code>podman container create</code></strong>: 컨테이너 자원만 생성하고 실행은 수행하지 않는 명령어입니다.</p>
</li>
<li><p>  <strong><code>podman port</code></strong>: 컨테이너에서 사용하는 포트가 호스트로 바인딩된 정보를 확인합니다. <code>-l</code> 옵션으로 마지막 컨테이너의 포트를 보거나 특정 ID/이름을 지정할 수 있습니다.</p>
</li>
<li><p>  <strong><code>podman ps</code> / <code>podman container ps</code> / <code>podman container ls</code></strong>: 컨테이너화되어 동작 중인 프로세스 목록을 확인합니다.</p>
<ul>
<li>주요 옵션: <code>-size</code>(사용 중인 컨테이너 크기 확인), <code>-sort</code>(정렬 기준 필드 선택), <code>-noheading</code>(제목 필드 출력 제외), <code>-latest</code> 또는 <code>l</code>(마지막 생성 컨테이너 출력), <code>-watch</code> 또는 <code>w</code>(실시간으로 명시된 초마다 프로세스 목록 갱신).</li>
</ul>
</li>
</ul>
<p><strong><code>podman container rm</code></strong>: 프로세스가 중지되어 있는 컨테이너를 제거합니다. <code>--all</code> 옵션으로 전체를 지우거나, <code>--force</code> 옵션으로 강제 제거할 수 있습니다.</p>
<p><strong><code>podman rmi</code></strong>: 런타임 엔진에서 관리하는 이미지를 제거합니다. <code>--all</code> 옵션으로 모든 이미지를 한 번에 지울 수 있습니다.</p>
<p><strong><code>podman container logs</code></strong>: 컨테이너가 시작되면서 출력하는 표준 출력/오류 로그 기록을 확인합니다. (리눅스 시스템 명령어인 <code>journalctl PODMAN_ID=&lt;ID&gt;</code> 형식으로도 동일한 런타임 정보 확인이 가능합니다.)</p>
<ul>
<li><p><strong><code>podman container mount</code></strong>: 컨테이너에 마운트되거나 바인딩(mount --bind)된 파일시스템 자원 경로를 확인합니다.</p>
</li>
<li><p><strong><code>podman container unmount</code></strong>: 바인딩되어 있던 루트 파일시스템 디렉터리 연결을 네임스페이스 공간을 통해 컨테이너에서 해제합니다.</p>
</li>
<li><p>  <strong><code>podman container wait</code></strong>: 컨테이너의 실행 상태 리턴 코드를 화면에 숫자로 출력합니다 (예: 0, 1, 125).</p>
</li>
<li><p>  <strong><code>podman info</code></strong>: 사용 중인 파일시스템 블록 디바이스 정보(<code>Backing Filesystem</code> 등)를 포함한 포드만 엔진의 전반적인 상태를 출력합니다.</p>
</li>
</ul>
<h4 id="2-볼륨volume-관리-명령어">2) 볼륨(Volume) 관리 명령어</h4>
<ul>
<li><p>  <strong><code>podman volume ls</code></strong>: 런타임 엔진 기반으로 관리되고 있는 볼륨 목록을 조회합니다. 생성된 로컬 볼륨 디렉터리는 기본적으로 <code>/var/lib/containers/storage/volumes/</code> 하위에 위치합니다.</p>
</li>
<li><p>  <strong><code>podman volume create</code></strong>: 데이터를 영구적으로 관리하고 백업/복구하기 위한 전용 볼륨 공간을 생성합니다.</p>
</li>
</ul>
<p>3) 포드(Pod) 관리 명령어 (<code>podman pod ...</code>) </p>
<p>포드만 내부에서 포드 자원을 관리할 때 사용하는 하위 명령어 구조입니다.</p>
<ul>
<li><p>  <strong><code>create</code></strong>: 새로운 Pod를 생성하며, 필요에 따라 포트 매핑(<code>-p</code>)이나 볼륨 지정을 하위 컨테이너들이 공유하도록 설정할 수 있습니다.</p>
</li>
<li><p>  <strong><code>exists</code></strong>: 특정 Pod가 시스템에 존재하는지 확인합니다.</p>
</li>
<li><p>  <strong><code>inspect</code></strong>: Pod의 상태 및 구성 세부 정보를 JSON 형태의 런타임 정보로 출력합니다.</p>
</li>
<li><p>  <strong><code>kill</code></strong>: Pod 내에서 동작 중인 인프라 프로세스(<code>pause</code>)를 강제 종료합니다 (기본 시그널 15번).</p>
</li>
<li><p>  <strong><code>list</code> (또는 <code>ls</code>)</strong>: 현재 생성되어 있는 Pod 목록을 출력합니다.</p>
</li>
<li><p>  <strong><code>logs</code></strong>: Pod 단위 내부에서 발생한 표준 로그 기록을 확인합니다.</p>
</li>
<li><p>  <strong><code>pause</code> / <code>unpause</code></strong>: 동작 중인 Pod를 잠시 일시 정지하거나, 정지된 Pod를 다시 시작합니다.</p>
</li>
<li><p>  <strong><code>prune</code></strong>: 시스템 내부에서 중지 상태로 방치된 Pod 및 연결된 컨테이너를 일괄 제거합니다.</p>
</li>
<li><p>  <strong><code>start</code> / <code>stop</code> / <code>restart</code></strong>: 해당 Pod의 시작, 중지, 그리고 재시작 처리를 수행합니다.</p>
</li>
<li><p>  <strong><code>rm</code></strong>: 지정한 Pod 자원을 시스템에서 완전히 제거합니다.</p>
</li>
<li><p>  <strong><code>state</code></strong>: 사용자가 상태를 보기 편하도록 내부 Pod 정보 상태를 렌더링하여 화면에 출력합니다.</p>
</li>
<li><p>  <strong><code>top</code></strong>: Pod 내부 자원들의 실시간 사용 상태 및 프로세스를 조회합니다.</p>
</li>
</ul>
<h4 id="4-쿠버네티스-마이그레이션-명령어">4) 쿠버네티스 마이그레이션 명령어</h4>
<ul>
<li><p>  <strong><code>podman generate kube</code></strong>: 포드만에서 사용한 컨테이너 및 시스템 자원을 쿠버네티스 배포 표준 형식인 <strong>쿠버네티스 YAML 파일</strong>로 전환하여 추출해 주는 명령어입니다.</p>
<ul>
<li>주요 옵션: <code>-filename</code>(생성할 파일 이름 지정), <code>-replicas</code>(복제할 개수 지정), <code>-service</code>(쿠버네티스 서비스 리소스 함께 구성), <code>-type</code>(변환 시 API 형식을 Pod 또는 Deployment 중 선택 명시, 기본값은 Pod).</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[리눅스 시험대비]]></title>
            <link>https://velog.io/@cse23_ewha/%EB%A6%AC%EB%88%85%EC%8A%A4-%EC%8B%9C%ED%97%98%EB%8C%80</link>
            <guid>https://velog.io/@cse23_ewha/%EB%A6%AC%EB%88%85%EC%8A%A4-%EC%8B%9C%ED%97%98%EB%8C%80</guid>
            <pubDate>Mon, 29 Jun 2026 12:04:14 GMT</pubDate>
            <description><![CDATA[<h1 id="1-리눅스-기본-명령어--파일-관리--편집기-사용-정리">1. 리눅스 기본 명령어 / 파일 관리 / 편집기 사용 정리</h1>
<hr>
<h2 id="1-운영체제os와-리눅스">1. 운영체제(OS)와 리눅스</h2>
<h3 id="운영체제란">운영체제란?</h3>
<ul>
<li>하드웨어(CPU, 메모리, 하드디스크) 위에서 소프트웨어(프로세스)를 실행할 수 있게 해주는 기반 소프트웨어</li>
<li>하드웨어를 먼저 구현하고, 그 위에서 소프트웨어를 처리할 수 있는 시스템을 만드는 것</li>
<li>운영체제 자체도 소프트웨어</li>
</ul>
<h3 id="리눅스의-특징">리눅스의 특징</h3>
<ul>
<li>서버용 운영체제로 많이 사용</li>
<li>오픈소스 / 무료 (Free SW = 라이선스 자체가 무료, Open Source = Free SW + Charge SW 포함)</li>
<li>Apache, MySQL 등 고품질 소프트웨어 지원</li>
<li>멀티유저(multi-user), 멀티태스킹(multi-tasking) 지원</li>
<li>적은 리소스에서도 동작, 강력한 네트워크 환경 지원</li>
<li>Unix 프로그램과 이식성 높음</li>
<li>스크립트를 통한 자동화 용이</li>
</ul>
<h3 id="리눅스-커널-vs-배포판">리눅스 커널 vs 배포판</h3>
<ul>
<li><strong>커널</strong>: OS의 핵심. 하드웨어를 직접 제어하는 소프트웨어. 사용자 도구/앱은 포함하지 않음</li>
<li><strong>리눅스 배포판</strong>: 커널 + 기본 명령어 + 애플리케이션을 묶어서 패키징한 것 = 우리가 실제로 사용하는 &quot;리눅스&quot;</li>
<li><strong>GNU/Linux</strong>: GNU 프로젝트 안에 여러 프로젝트들이 포함된 형태</li>
</ul>
<h3 id="주요-배포판">주요 배포판</h3>
<table>
<thead>
<tr>
<th>계열</th>
<th>배포판</th>
</tr>
</thead>
<tbody><tr>
<td>레드햇 (실무에서 많이 사용)</td>
<td>Red Hat Enterprise Linux, CentOS, Fedora, <strong>Rocky Linux</strong></td>
</tr>
<tr>
<td>데비안</td>
<td>Debian GNU/Linux, Ubuntu</td>
</tr>
</tbody></table>
<blockquote>
<p>이번 과정에서는 Rocky Linux 사용</p>
</blockquote>
<hr>
<h2 id="2-리눅스-실행-환경">2. 리눅스 실행 환경</h2>
<h3 id="cli-vs-gui">CLI vs GUI</h3>
<table>
<thead>
<tr>
<th>구분</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>CLI (Command Line Interface)</td>
<td>텍스트 기반 명령줄 인터페이스. 리눅스에서 주로 사용</td>
</tr>
<tr>
<td>GUI (Graphical User Interface)</td>
<td>그래픽 기반. Windows처럼 아이콘/메뉴 방식</td>
</tr>
</tbody></table>
<h3 id="실행-환경-전환-명령어">실행 환경 전환 명령어</h3>
<pre><code class="language-bash"># GUI로 변경
systemctl isolate graphical.target

# CLI로 변경
systemctl isolate multi-user.target

# 기본 실행 환경 확인/변경
systemctl get-default
systemctl set-default graphical.target
systemctl set-default multi-user.target</code></pre>
<h3 id="gui-패키지-설치-minimal-설치-시">GUI 패키지 설치 (minimal 설치 시)</h3>
<pre><code class="language-bash">dnf grouplist
dnf groupinstall &quot;Server with GUI&quot;</code></pre>
<hr>
<h2 id="3-셸shell과-명령어">3. 셸(Shell)과 명령어</h2>
<h3 id="셸이란">셸이란?</h3>
<ul>
<li>사용자가 입력한 명령어를 기계어(이진 데이터)로 번역해주는 프로그램</li>
<li>커널은 기계어만 이해하므로 번역기(인터프리터) 역할 필요</li>
</ul>
<pre><code>키보드 입력 → 터미널 → Shell → Kernel</code></pre><ul>
<li><strong>터미널</strong>: 입출력을 담당하는 프로그램 (Shell과 다름)</li>
<li>프롬프트 상태:<ul>
<li><code>#</code> → root 사용자</li>
<li><code>$</code> → 일반 사용자</li>
<li>아무 표시 없음 → 현재 명령어 입력 불가 상태</li>
</ul>
</li>
</ul>
<h3 id="기본-셸-확인-및-변경">기본 셸 확인 및 변경</h3>
<pre><code class="language-bash">echo $SHELL          # 현재 셸 확인 (기본: bash)

/bin/sh              # 본셸로 변경
/bin/bash            # bash로 변경
exit                 # 현재 셸 종료 (상위 셸로 돌아감)</code></pre>
<blockquote>
<p>본셸(sh)이 리눅스 기본 셸이었고, 이를 업데이트한 것이 bash<br>macOS는 zsh, Windows는 PowerShell 사용<br>셸 변경 시 실제로는 하위 셸을 중첩 실행하는 것 (pstree 명령어로 확인 가능)</p>
</blockquote>
<h3 id="명령어-이력-history">명령어 이력 (history)</h3>
<ul>
<li><code>↑</code> / <code>↓</code> 방향키: 이전/다음 명령어 탐색</li>
<li><code>ctrl + r</code>: 증분 검색 모드 (문자 입력 시 자동 검색)</li>
</ul>
<table>
<thead>
<tr>
<th>단축키</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>문자 입력</td>
<td>입력할 때마다 이력 검색</td>
</tr>
<tr>
<td><code>ctrl + r</code></td>
<td>이전 검색 결과로 이동</td>
</tr>
<tr>
<td><code>Enter</code></td>
<td>현재 검색 결과 실행</td>
</tr>
<tr>
<td><code>ESC</code></td>
<td>검색 결과 유지하되 실행 안 하고 프롬프트로 복귀</td>
</tr>
<tr>
<td><code>ctrl + g</code></td>
<td>검색 결과 지우고 프롬프트로 복귀</td>
</tr>
</tbody></table>
<blockquote>
<p>메모리가 flush되기 전까지 검색 가능. 파일로 저장도 가능.</p>
</blockquote>
<hr>
<h2 id="4-리눅스-디렉토리-구조">4. 리눅스 디렉토리 구조</h2>
<h3 id="핵심-개념">핵심 개념</h3>
<ul>
<li>리눅스는 <strong>모든 것을 파일로 다룸</strong> (하드디스크, 키보드, 프린터 등 장치도 파일)</li>
<li>전체 구조는 <strong>트리 형태</strong> (FHS: Filesystem Hierarchy Standard)</li>
</ul>
<h3 id="주요-디렉토리">주요 디렉토리</h3>
<table>
<thead>
<tr>
<th>디렉토리</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>/</code></td>
<td>루트 디렉토리. 모든 파일 위치의 시작점</td>
</tr>
<tr>
<td><code>/root</code></td>
<td>root 사용자의 홈 디렉토리 (루트 디렉토리와 다름!)</td>
</tr>
<tr>
<td><code>/home</code></td>
<td>일반 사용자 계정 홈 디렉토리 (<code>/home/user2</code> 등)</td>
</tr>
<tr>
<td><code>/bin</code></td>
<td>일반 사용자용 기본 실행 명령어</td>
</tr>
<tr>
<td><code>/sbin</code></td>
<td>관리자용 명령어</td>
</tr>
<tr>
<td><code>/etc</code></td>
<td>환경 설정 파일, 서비스 구성 파일</td>
</tr>
<tr>
<td><code>/boot</code></td>
<td>리눅스 커널 + 부트로더 위치</td>
</tr>
<tr>
<td><code>/usr</code></td>
<td>사용자 공유 유틸리티/애플리케이션</td>
</tr>
<tr>
<td><code>/lib</code></td>
<td><code>/bin</code>의 바이너리에 필요한 라이브러리</td>
</tr>
<tr>
<td><code>/dev</code></td>
<td>시스템 장치(device) 목록</td>
</tr>
<tr>
<td><code>/proc</code></td>
<td>커널 및 프로세스 상태 정보</td>
</tr>
<tr>
<td><code>/var</code></td>
<td>가변 파일 (로그, 스풀, 캐시 등)</td>
</tr>
<tr>
<td><code>/tmp</code></td>
<td>임시 파일</td>
</tr>
<tr>
<td><code>/mnt</code></td>
<td>임시 마운트 포인트</td>
</tr>
</tbody></table>
<h3 id="주요-설정-파일">주요 설정 파일</h3>
<pre><code class="language-bash">/etc/passwd         # 사용자 UID, 쉘 등 관리
/etc/shadow         # 사용자 패스워드 (암호화 저장)
/etc/hosts          # 호스트명 → IP 매핑 (DNS보다 우선 동작)
                    # resolving: 도메인→IP 변환 순서: 캐시 → /etc/hosts → DNS
/etc/fstab          # 파일시스템 마운트 정보
/etc/resolv.conf    # DNS 서버 지정
/var/log/messages   # 시스템 로그
/var/run/sshd.pid   # sshd 데몬 PID</code></pre>
<hr>
<h2 id="5-경로path">5. 경로(Path)</h2>
<h3 id="절대-경로-vs-상대-경로">절대 경로 vs 상대 경로</h3>
<table>
<thead>
<tr>
<th>구분</th>
<th>설명</th>
<th>예시</th>
</tr>
</thead>
<tbody><tr>
<td><strong>절대 경로</strong></td>
<td><code>/</code>(루트)로 시작. 전체 경로 정보 표시</td>
<td><code>/etc/ssh/sshd_config</code></td>
</tr>
<tr>
<td><strong>상대 경로</strong></td>
<td>현재 위치(워킹 디렉토리) 기준 경로</td>
<td><code>etc/ssh</code></td>
</tr>
</tbody></table>
<blockquote>
<p>차근차근 들어갈 때는 상대경로, 정확한 위치를 알면 절대경로로 바로 이동</p>
</blockquote>
<h3 id="특수-경로-표기">특수 경로 표기</h3>
<table>
<thead>
<tr>
<th>표기</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td><code>.</code></td>
<td>현재 디렉토리</td>
</tr>
<tr>
<td><code>..</code></td>
<td>상위(부모) 디렉토리</td>
</tr>
<tr>
<td><code>~</code></td>
<td>현재 사용자의 홈 디렉토리</td>
</tr>
<tr>
<td><code>/</code></td>
<td>루트 디렉토리</td>
</tr>
</tbody></table>
<h3 id="cd-명령어">cd 명령어</h3>
<pre><code class="language-bash">cd /home              # 절대경로로 이동
cd ~                  # 홈 디렉토리로 이동
cd ..                 # 상위 디렉토리로 이동
cd ../testdir2        # 상위 디렉토리의 testdir2로 이동
pwd                   # 현재 경로 출력</code></pre>
<hr>
<h2 id="6-파일-관리-명령어">6. 파일 관리 명령어</h2>
<h3 id="ls---목록-확인">ls - 목록 확인</h3>
<pre><code class="language-bash">ls /                  # 루트 디렉토리 내용
ls -l                 # 상세 정보 출력
ls -a                 # 숨김 파일 포함 출력
ls -al                # 상세 + 숨김 파일
ls / /usr             # 여러 경로 동시 출력</code></pre>
<h3 id="mkdir---디렉토리-생성">mkdir - 디렉토리 생성</h3>
<pre><code class="language-bash">mkdir work
mkdir ./testdir1
mkdir ./testdir2</code></pre>
<h3 id="touch---파일-생성">touch - 파일 생성</h3>
<pre><code class="language-bash">touch testfile1
touch testfile1 testfile2 testfile3   # 여러 파일 동시 생성</code></pre>
<h3 id="rm---삭제">rm - 삭제</h3>
<pre><code class="language-bash">rm testfile1              # 파일 삭제
rm testfile*              # 와일드카드 사용 일괄 삭제
rm -r testdir1            # 디렉토리 삭제 (-r 옵션 필수)
rm -i testfile1           # 삭제 전 확인 메시지 출력</code></pre>
<blockquote>
<p>⚠️ 리눅스는 삭제 시 휴지통 없음. 바로 삭제됨!</p>
</blockquote>
<h3 id="cat---파일-내용-출력">cat - 파일 내용 출력</h3>
<pre><code class="language-bash">cat newfile
cat /etc/crontab</code></pre>
<h3 id="less---페이지-단위로-파일-보기">less - 페이지 단위로 파일 보기</h3>
<pre><code class="language-bash">less /etc/ssh/sshd_config</code></pre>
<table>
<thead>
<tr>
<th>단축키</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td><code>Space</code> / <code>f</code></td>
<td>한 화면 아래로</td>
</tr>
<tr>
<td><code>b</code></td>
<td>한 화면 위로</td>
</tr>
<tr>
<td><code>j</code> / <code>Enter</code></td>
<td>한 행 아래로</td>
</tr>
<tr>
<td><code>k</code></td>
<td>한 행 위로</td>
</tr>
<tr>
<td><code>q</code></td>
<td>종료</td>
</tr>
<tr>
<td><code>/문자열</code></td>
<td>아래 방향으로 검색</td>
</tr>
<tr>
<td><code>?문자열</code></td>
<td>위 방향으로 검색</td>
</tr>
<tr>
<td><code>n</code></td>
<td>다음 검색 결과</td>
</tr>
<tr>
<td><code>N</code></td>
<td>이전 검색 결과</td>
</tr>
</tbody></table>
<h3 id="cp---복사">cp - 복사</h3>
<pre><code class="language-bash">cp /etc/ssh/sshd_config ./testfile      # 파일 복사
cp testfile ./testdir1                   # 디렉토리로 복사
cp testfile1 testfile2 testfile3 ./testdir1   # 여러 파일 복사
cp testfile* ./testdir2                  # 와일드카드 복사
cp -r testdir1 testdir3                  # 디렉토리 복사 (-r 필수)</code></pre>
<h3 id="mv---이동이름-변경">mv - 이동/이름 변경</h3>
<pre><code class="language-bash">mv file1 file2           # 이름 변경
mv file1 dir1            # 디렉토리로 이동
mv file1 file2 file3 dir1   # 여러 파일 이동
mv -i file1 file2        # 덮어쓰기 전 확인
mv dir1 dir2             # 디렉토리 이동 (-r 옵션 불필요)</code></pre>
<hr>
<h2 id="7-vim-편집기">7. vim 편집기</h2>
<h3 id="기본-개념">기본 개념</h3>
<ul>
<li>리눅스 기본 텍스트 편집기 (vi 입력해도 vim 실행됨)</li>
<li>텍스트 파일만 편집 가능 (이미지, 동영상, 컴파일된 바이너리 불가)</li>
</ul>
<pre><code class="language-bash">vim --version         # vim 버전 확인
dnf install vim -y    # 설치 (없을 경우)</code></pre>
<h3 id="vim-실행-방식">vim 실행 방식</h3>
<pre><code class="language-bash">vi file1                  # 새 파일 또는 기존 파일 편집 (normal mode)
vi -R file1               # 읽기 전용으로 열기
vi -L file1               # 작업 중인 .swp 파일 확인
vi +30 file1              # 30번째 줄부터 편집 시작
vi +/&quot;hello&quot; file1        # &quot;hello&quot; 단어가 있는 줄부터 시작</code></pre>
<blockquote>
<p><code>vi</code> 실행 시 현재 디렉토리에 <code>.swp</code> 파일 생성됨. 이미 있으면 경고 출력.</p>
</blockquote>
<h3 id="3가지-핵심-모드">3가지 핵심 모드</h3>
<pre><code>Normal Mode ←→ (i/a/o 등) →→ Insert Mode
Normal Mode ←→ (:) →→ Command-line Mode
Insert/Cmdline → ESC → Normal Mode</code></pre><h3 id="normal-mode-기본-모드">Normal Mode (기본 모드)</h3>
<ul>
<li>vim 실행 시 처음 진입하는 모드</li>
<li>파일 내용 탐색, 복사/붙여넣기, 되돌리기 등</li>
</ul>
<p><strong>이동 키:</strong></p>
<table>
<thead>
<tr>
<th>키</th>
<th>동작</th>
</tr>
</thead>
<tbody><tr>
<td><code>h</code></td>
<td>왼쪽</td>
</tr>
<tr>
<td><code>j</code></td>
<td>아래</td>
</tr>
<tr>
<td><code>k</code></td>
<td>위</td>
</tr>
<tr>
<td><code>l</code></td>
<td>오른쪽</td>
</tr>
<tr>
<td><code>Page Up/Down</code></td>
<td>한 페이지 이동</td>
</tr>
</tbody></table>
<h3 id="insert-mode-편집-모드">Insert Mode (편집 모드)</h3>
<p>Normal Mode에서 다음 키로 진입:</p>
<table>
<thead>
<tr>
<th>키</th>
<th>동작</th>
</tr>
</thead>
<tbody><tr>
<td><code>i</code></td>
<td>현재 위치에서 입력 시작</td>
</tr>
<tr>
<td><code>I</code></td>
<td>라인 첫 번째 위치에서 입력</td>
</tr>
<tr>
<td><code>a</code></td>
<td>다음 문자부터 입력</td>
</tr>
<tr>
<td><code>A</code></td>
<td>라인 마지막에서 입력</td>
</tr>
<tr>
<td><code>o</code></td>
<td>다음 라인으로 입력</td>
</tr>
<tr>
<td><code>O</code></td>
<td>이전 라인으로 입력</td>
</tr>
<tr>
<td><code>s</code></td>
<td>현재 단어 삭제 후 입력</td>
</tr>
<tr>
<td><code>cw</code></td>
<td>다음 단어까지 삭제 후 입력</td>
</tr>
<tr>
<td><code>C</code></td>
<td>현재 위치부터 라인 끝 삭제 후 입력</td>
</tr>
<tr>
<td><code>cc</code> / <code>S</code></td>
<td>현재 라인 전체 삭제 후 입력</td>
</tr>
<tr>
<td><code>r</code></td>
<td>현재 문자 replace</td>
</tr>
<tr>
<td><code>R</code></td>
<td>여러 문자 replace</td>
</tr>
</tbody></table>
<h3 id="command-line-mode">Command-line Mode</h3>
<p><code>:</code> 입력으로 진입. 파일 저장, 종료, 검색 등.</p>
<table>
<thead>
<tr>
<th>명령어</th>
<th>동작</th>
</tr>
</thead>
<tbody><tr>
<td><code>:q</code></td>
<td>종료</td>
</tr>
<tr>
<td><code>:w</code></td>
<td>저장</td>
</tr>
<tr>
<td><code>:wq</code></td>
<td>저장 후 종료</td>
</tr>
<tr>
<td><code>:q!</code></td>
<td>저장하지 않고 강제 종료</td>
</tr>
<tr>
<td><code>:wq!</code></td>
<td>강제 저장 후 종료 (root 권한 시)</td>
</tr>
<tr>
<td><code>/문자열</code></td>
<td>검색 (아래 방향)</td>
</tr>
<tr>
<td><code>?문자열</code></td>
<td>검색 (위 방향)</td>
</tr>
</tbody></table>
<blockquote>
<p>정규 문자: <code>^</code> 시작, <code>$</code> 끝, <code>.</code> 문자 일치, <code>*</code> 모든 문자</p>
</blockquote>
<h4 id="이동">이동</h4>
<table>
<thead>
<tr>
<th>Key</th>
<th>동작</th>
</tr>
</thead>
<tbody><tr>
<td>w</td>
<td>한단어 다음 이동</td>
</tr>
<tr>
<td>b</td>
<td>한단어 이전 이동</td>
</tr>
<tr>
<td>(</td>
<td>한문단 다음으로 이동</td>
</tr>
<tr>
<td>)</td>
<td>한문단 이전으로 이동</td>
</tr>
<tr>
<td>CTRL + f</td>
<td>한 페이지 다음으로 이동</td>
</tr>
<tr>
<td>CTRL + b</td>
<td>한 페이지 이전으로 이동</td>
</tr>
<tr>
<td>$</td>
<td>라인의 마지막 문자로 이동</td>
</tr>
<tr>
<td>^ 혹은 0</td>
<td>라인의 첫번째 문자로 이동</td>
</tr>
<tr>
<td>10G</td>
<td>파일의 10번 라인으로 이동</td>
</tr>
<tr>
<td>G</td>
<td>파일의 마지막 라인으로 이동</td>
</tr>
</tbody></table>
<table>
<thead>
<tr>
<th>Key</th>
<th>동작</th>
</tr>
</thead>
<tbody><tr>
<td>x</td>
<td>한 글자 삭제</td>
</tr>
<tr>
<td>dw</td>
<td>현재 위치에서 다음 한단어 삭제</td>
</tr>
<tr>
<td>d0</td>
<td>현재 위치에서 라인의 처음까지 삭제</td>
</tr>
<tr>
<td>D  혹은 d%</td>
<td>현재 위치에서 라인의 마지막까지 삭제</td>
</tr>
<tr>
<td>dd</td>
<td>현재 라인 모두 삭제</td>
</tr>
<tr>
<td>3dd</td>
<td>현재라인 포함 총 3개의 라인을 삭제</td>
</tr>
<tr>
<td>dG</td>
<td>현재 위치에서 부터 마지막 라인까지 모두 삭제</td>
</tr>
<tr>
<td>d1G</td>
<td>현재 위치에서 부터 첫번째 라인까지 삭제</td>
</tr>
<tr>
<td>yy 혹은 Y</td>
<td>라인 yank (복사)</td>
</tr>
<tr>
<td>p</td>
<td>yank된 내용 붙여 넣기</td>
</tr>
<tr>
<td>J</td>
<td>아래 라인을 현재 라인의 마지막에 이어 붙임</td>
</tr>
<tr>
<td>u</td>
<td>실행 취소</td>
</tr>
<tr>
<td>CTRL + r</td>
<td>취소한 명령 다시 실행</td>
</tr>
<tr>
<td>K</td>
<td>현재 단어의 메뉴얼 페이지로 이동</td>
</tr>
<tr>
<td>.</td>
<td>이전 명령을 재실행</td>
</tr>
<tr>
<td>### vimrc 환경 파일</td>
<td></td>
</tr>
</tbody></table>
<pre><code class="language-bash">vi ~/.vimrc</code></pre>
<pre><code>set nu           # 행 번호 표시
set ai sw=2 ts=2 et   # 자동 들여쓰기, 탭 크기 설정</code></pre><hr>
<h2 id="8-추가-편집기-ide">8. 추가 편집기 (IDE)</h2>
<h3 id="jupyter-lab">Jupyter Lab</h3>
<pre><code class="language-bash"># 설치
yum install -y python3-pip
pip install jupyterlab
jupyter lab --generate-config
jupyter lab password

# 실행
jupyter lab --allow-root --ip 0.0.0.0 --port 8989 &amp;</code></pre>
<p>부팅 시 자동 실행: <code>/etc/systemd/system/jupyter.service</code> 작성 후</p>
<pre><code class="language-bash">systemctl daemon-reload
systemctl enable jupyter</code></pre>
<h3 id="vscode-code-server">VSCode (code-server)</h3>
<pre><code class="language-bash"># 설치
curl -L https://code-server.dev/install.sh | sh

# 설정 파일: ~/.config/code-server/config.yaml
bind-addr: 0.0.0.0:9999
auth: password
password: &lt;패스워드&gt;

# 실행
systemctl start code-server@$USER
systemctl enable code-server@$USER</code></pre>
<hr>
<h2 id="핵심-명령어-한눈에-보기">핵심 명령어 한눈에 보기</h2>
<table>
<thead>
<tr>
<th>명령어</th>
<th>기능</th>
</tr>
</thead>
<tbody><tr>
<td><code>pwd</code></td>
<td>현재 경로 출력</td>
</tr>
<tr>
<td><code>cd</code></td>
<td>디렉토리 이동</td>
</tr>
<tr>
<td><code>ls -al</code></td>
<td>숨김 파일 포함 상세 목록</td>
</tr>
<tr>
<td><code>mkdir</code></td>
<td>디렉토리 생성</td>
</tr>
<tr>
<td><code>touch</code></td>
<td>빈 파일 생성</td>
</tr>
<tr>
<td><code>rm -r</code></td>
<td>디렉토리 삭제</td>
</tr>
<tr>
<td><code>cat</code></td>
<td>파일 내용 출력</td>
</tr>
<tr>
<td><code>less</code></td>
<td>페이지 단위 파일 보기</td>
</tr>
<tr>
<td><code>cp -r</code></td>
<td>디렉토리 복사</td>
</tr>
<tr>
<td><code>mv</code></td>
<td>이동 / 이름 변경</td>
</tr>
<tr>
<td><code>vi</code></td>
<td>vim 편집기 실행</td>
</tr>
</tbody></table>
<h1 id="2-사용자-및-권한-관리--3-filesystem-및-disk-관리--4-프로세스-관리">2. 사용자 및 권한 관리 / 3. Filesystem 및 disk 관리 / 4. 프로세스 관리</h1>
<hr>
<h2 id="2-사용자-및-권한-관리">2. 사용자 및 권한 관리</h2>
<h3 id="사용자-관리-파일">사용자 관리 파일</h3>
<ul>
<li>시스템에서는 여러 작업을 수행하는 사용자별로 User와 Group을 통해 역할 구분</li>
</ul>
<p><strong><code>/etc/passwd</code></strong> - 사용자 관리 (콜론 <code>:</code> 기준으로 필드 구분)</p>
<pre><code class="language-bash">cat /etc/passwd | grep root
root:x:0:0:root:/root:/bin/bash</code></pre>
<table>
<thead>
<tr>
<th>필드</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>root</td>
<td>사용자 이름</td>
</tr>
<tr>
<td>x</td>
<td>사용자의 암호 (보안상 표시 안 함, 무조건 x)</td>
</tr>
<tr>
<td>0</td>
<td>사용자 아이디 (UID)</td>
</tr>
<tr>
<td>0</td>
<td>그룹 아이디 (GID)</td>
</tr>
<tr>
<td>root</td>
<td>설명 (comment)</td>
</tr>
<tr>
<td>/root</td>
<td>사용자의 홈 디렉토리</td>
</tr>
<tr>
<td>/bin/bash</td>
<td>사용자 로그인 시 실행하는 쉘</td>
</tr>
</tbody></table>
<blockquote>
<p><code>nologin</code> → 쉘을 사용하지 않고 내부적으로만 동작함</p>
</blockquote>
<p><strong><code>/etc/shadow</code></strong> - 암호 관리 (암호화되어 저장)</p>
<pre><code class="language-bash">cat /etc/shadow | grep root</code></pre>
<table>
<thead>
<tr>
<th>필드</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>root</td>
<td>사용자 이름</td>
</tr>
<tr>
<td>$6$0VhZ...</td>
<td>사용자의 암호 (암호화)</td>
</tr>
<tr>
<td>비어있음</td>
<td>암호를 변경한 날짜 (epoch 타임 기준, 초 아님)</td>
</tr>
<tr>
<td>0</td>
<td>암호를 변경할 수 없는 최소 날짜 (3이면 3일 동안 변경 불가)</td>
</tr>
<tr>
<td>99999</td>
<td>암호를 사용할 수 있는 최대 날짜 (무제한 의미, 30이면 30일 후 변경 필요)</td>
</tr>
<tr>
<td>7</td>
<td>암호 만료 되기 전 경고 메시지 발생 기간</td>
</tr>
<tr>
<td>비어있음</td>
<td>암호 만료 기간 (만료 전에는 복구 가능, 이후에는 사용 불가)</td>
</tr>
</tbody></table>
<hr>
<h3 id="사용자-생성수정삭제">사용자 생성/수정/삭제</h3>
<pre><code class="language-bash"># 사용자 생성
useradd -c test demouser

# 암호 설정
passwd demouser

# 확인
cat /etc/passwd | grep demouser
cat /etc/shadow | grep demouser
cat /etc/group | grep demouser
ls -al /home/</code></pre>
<blockquote>
<p>새로운 사용자는 기본적으로 홈 디렉토리가 자동으로 만들어지며 <strong>1000 이후의 UID와 GID</strong>가 지정됨</p>
</blockquote>
<pre><code class="language-bash"># 사용자 수정
usermod -u 2000 demouser
usermod -s /bin/sh demouser    # bash 쉘이 아닌 본쉘로 변경

# 그룹 추가/수정
groupadd testuser
groupdel demouser
usermod -l testuser -g testuser demouser
usermod -d /home/testuser -m testuser

# 사용자 삭제 (-r: 홈 디렉토리와 mail spool도 함께 삭제)
userdel -r testuser</code></pre>
<p><strong>usermod 옵션</strong></p>
<table>
<thead>
<tr>
<th>옵션</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>-a, --append</code></td>
<td>보조 그룹 추가. <code>-G</code> 옵션과 함께 사용</td>
</tr>
<tr>
<td><code>-c, --comment</code></td>
<td>주석 필드에 텍스트 추가</td>
</tr>
<tr>
<td><code>-d, --home</code></td>
<td>홈 디렉토리 지정</td>
</tr>
<tr>
<td><code>-g, --gid</code></td>
<td>기본 그룹 지정</td>
</tr>
<tr>
<td><code>-G, --groups</code></td>
<td>쉼표로 구분된 보조 그룹 목록 지정</td>
</tr>
<tr>
<td><code>-L, --lock</code></td>
<td>사용자 계정 잠금</td>
</tr>
<tr>
<td><code>-m, --move-home</code></td>
<td>홈 디렉토리를 새 위치로 이동. <code>-d</code> 옵션과 함께 사용</td>
</tr>
<tr>
<td><code>-s, --shell</code></td>
<td>로그인 쉘 지정</td>
</tr>
<tr>
<td><code>-U, --unlock</code></td>
<td>사용자 계정 잠금 해제</td>
</tr>
</tbody></table>
<blockquote>
<p>그룹 관련 주의사항:</p>
<ul>
<li>그룹에는 반드시 사용자가 한 명 이상 포함되어야 함</li>
<li>사용자는 어떤 그룹이든 반드시 그룹에 속해야 함</li>
<li><strong>기본 그룹 (<code>-g</code>)</strong>: append 개념 없음. 무조건 대체</li>
<li><strong>보조 그룹 (<code>-G</code>)</strong>: <code>-a</code> 옵션 없으면 싹 replace. <code>-a</code>와 함께 써야 추가됨</li>
</ul>
</blockquote>
<p><strong>암호 만기일 설정 예시</strong></p>
<pre><code class="language-bash">useradd -e 2026-05-30 expuser
passwd expuser
cat /etc/shadow | grep expuser

# 날짜 변경 후 접속 테스트
date -s 220601
ssh expuser@localhost
# 실패 확인</code></pre>
<hr>
<h3 id="uid-범위">UID 범위</h3>
<table>
<thead>
<tr>
<th>범위</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>UID 0</td>
<td>슈퍼유저(root)</td>
</tr>
<tr>
<td>UID 1~200</td>
<td>시스템 프로세스에 정적으로 할당되는 시스템 계정</td>
</tr>
<tr>
<td>UID 201~999</td>
<td>이 시스템의 파일을 소유하지 않은 시스템 프로세스에 동적으로 할당</td>
</tr>
<tr>
<td>UID 1000 이상</td>
<td>일반 사용자 (시스템 권한 없음)</td>
</tr>
</tbody></table>
<hr>
<h3 id="파일-소유자와-소유-그룹">파일 소유자와 소유 그룹</h3>
<ul>
<li>파일을 만든 사람 = 파일의 소유자 (변경 가능)</li>
<li>한 사용자는 여러 그룹에 소속 가능</li>
<li>그룹을 특별히 지정하지 않으면 사용자 이름과 동일한 그룹에 소속</li>
</ul>
<pre><code class="language-bash"># 파일 소유자/그룹 확인
ls -l testfile1

# 소유자 변경
chown testuser:testuser testfile1

# 현재 내가 속한 그룹 확인
groups</code></pre>
<hr>
<h3 id="퍼미션permission">퍼미션(Permission)</h3>
<ul>
<li>각 파일에 &#39;누구에게 어떤 권한을 허가할지&#39; 설정된 정보</li>
</ul>
<p><strong>파일 타입</strong></p>
<table>
<thead>
<tr>
<th>기호</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td><code>-</code></td>
<td>일반 파일</td>
</tr>
<tr>
<td><code>d</code></td>
<td>디렉토리</td>
</tr>
<tr>
<td><code>l</code></td>
<td>심볼릭 링크</td>
</tr>
</tbody></table>
<p><strong>권한 구조</strong> (9글자, 3자리씩 소유자 / 소유그룹 / 기타사용자)</p>
<pre><code>rwx rwx rwx
소유자 소유그룹 기타사용자</code></pre><table>
<thead>
<tr>
<th>기호</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td><code>r</code></td>
<td>읽기 (read) - cat 등 가능</td>
</tr>
<tr>
<td><code>w</code></td>
<td>쓰기 (write) - vi로 편집 가능</td>
</tr>
<tr>
<td><code>x</code></td>
<td>실행 (execute) - 바이너리/스크립트 실행 가능</td>
</tr>
<tr>
<td><code>-</code></td>
<td>해당 권한 없음</td>
</tr>
</tbody></table>
<blockquote>
<p>만들어지는 모든 파일은 보통 소유자 빼고 W가 빠지게 구성되어 있음</p>
</blockquote>
<p><strong>디렉토리 퍼미션 (파일과 의미가 다름!)</strong></p>
<table>
<thead>
<tr>
<th>기호</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td><code>r</code></td>
<td>디렉토리에 포함된 파일 리스트 취득 가능 (ls)</td>
</tr>
<tr>
<td><code>w</code></td>
<td>디렉토리 하위에 파일 및 디렉토리 작성/삭제 가능</td>
</tr>
<tr>
<td><code>x</code></td>
<td>디렉토리로 이동 가능 (cd)</td>
</tr>
</tbody></table>
<blockquote>
<p>파일을 지울 수 있는지 여부는 파일이 아니라 <strong>디렉토리의 퍼미션</strong>에 의해 결정됨<br><code>r--</code>이면 cd로 들어가진 못하는데 ls로 파일 리스트 확인은 가능</p>
</blockquote>
<hr>
<h3 id="퍼미션-변경-chmod">퍼미션 변경 (chmod)</h3>
<p>파일 모드 변경은 <strong>해당 파일의 소유자 혹은 슈퍼 사용자</strong>만 가능</p>
<p><strong>1. 기호 모드</strong></p>
<pre><code class="language-bash">chmod [ugoa] [+-=][rwx] &lt;파일 이름&gt;</code></pre>
<table>
<thead>
<tr>
<th>기호</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td><code>u</code></td>
<td>소유자</td>
</tr>
<tr>
<td><code>g</code></td>
<td>소유 그룹</td>
</tr>
<tr>
<td><code>o</code></td>
<td>기타 사용자</td>
</tr>
<tr>
<td><code>a</code></td>
<td>u, g, o 모두 (지정 안 하면 a로 간주)</td>
</tr>
</tbody></table>
<table>
<thead>
<tr>
<th>기호</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td><code>+</code></td>
<td>퍼미션 추가</td>
</tr>
<tr>
<td><code>-</code></td>
<td>퍼미션 금지</td>
</tr>
<tr>
<td><code>=</code></td>
<td>지정한 퍼미션과 같게 함</td>
</tr>
</tbody></table>
<pre><code class="language-bash">chmod u+w file.txt       # 소유자에게 쓰기 권한 추가
chmod g-w file.txt       # 그룹의 쓰기 권한 제거
chmod go=r file.txt      # 그룹과 기타사용자를 읽기만 가능하게
chmod a-r testdir2       # 모든 사용자 읽기 권한 제거
chmod a-x testdir3       # 모든 사용자 실행 권한 제거</code></pre>
<blockquote>
<p>기호 모드는 <strong>상대적</strong> 퍼미션 지정 (지정한 것 외에는 변화 없음)</p>
</blockquote>
<p><strong>2. 수치 모드 (Octal mode)</strong></p>
<pre><code class="language-bash">chmod &lt;8진수 수치&gt; &lt;파일 이름&gt;</code></pre>
<table>
<thead>
<tr>
<th>의미</th>
<th>숫자</th>
</tr>
</thead>
<tbody><tr>
<td>읽기 (r)</td>
<td>4</td>
</tr>
<tr>
<td>쓰기 (w)</td>
<td>2</td>
</tr>
<tr>
<td>실행 (x)</td>
<td>1</td>
</tr>
<tr>
<td>권한 없음</td>
<td>0</td>
</tr>
</tbody></table>
<p>조합 예시:</p>
<table>
<thead>
<tr>
<th>숫자</th>
<th>권한</th>
</tr>
</thead>
<tbody><tr>
<td>0</td>
<td>---</td>
</tr>
<tr>
<td>1</td>
<td>--x</td>
</tr>
<tr>
<td>2</td>
<td>-w-</td>
</tr>
<tr>
<td>3</td>
<td>-wx (1+2)</td>
</tr>
<tr>
<td>4</td>
<td>r--</td>
</tr>
<tr>
<td>5</td>
<td>r-x (4+1)</td>
</tr>
<tr>
<td>6</td>
<td>rw- (4+2)</td>
</tr>
<tr>
<td>7</td>
<td>rwx (4+2+1)</td>
</tr>
</tbody></table>
<pre><code class="language-bash">chmod 755 file.txt    # rwxr-xr-x  (다른 사용자가 봐도 되는 실행 파일에 보통 사용)
chmod 644 file.txt    # rw-r--r--  (다른 사용자가 봐도 되는 일반 파일에 보통 사용)</code></pre>
<blockquote>
<p>수치 모드는 기존 퍼미션을 <strong>새로운 퍼미션으로 덮어씀</strong></p>
</blockquote>
<hr>
<h3 id="권한-상승-escalation">권한 상승 (Escalation)</h3>
<ul>
<li><strong>슈퍼유저(root)</strong>: 시스템 설정 변경, 새로운 애플리케이션 설치, 퍼미션 무시하고 모든 파일 읽기/수정/삭제 가능</li>
<li>리눅스 설치하자마자 자동으로 만들어짐</li>
<li>평소에는 일반 사용자로 조작하다가 반드시 필요한 상황에서만 슈퍼 사용자로 조작하는 것이 좋음</li>
</ul>
<p><strong>su 명령어 - 사용자 전환</strong></p>
<ul>
<li>로그아웃하지 않고 다른 사용자로 전환 (권한 상승 아님, 사용자 전환)</li>
<li>root에서 일반 사용자로 전환 시 비번 안 물어봄, 반대는 물어봄</li>
</ul>
<pre><code class="language-bash"># 새로운 사용자 만들고 전환
useradd sudouser
passwd sudouser
su - sudouser

# 슈퍼 사용자로 전환 (인자 없으면 슈퍼 사용자로 전환 의미)
su
# 암호 입력 → root 암호

# 슈퍼 사용자 환경으로 초기화하면서 전환 (권장)
su -

# 원래 사용자로 돌아가기
exit</code></pre>
<blockquote>
<p><code>su</code> vs <code>su -</code> 차이:</p>
<ul>
<li><code>su</code>: 환경 변수/현재 디렉토리가 일반 사용자 상태로 유지됨</li>
<li><code>su -</code>: 슈퍼 사용자의 환경으로 초기화됨 (권장)</li>
</ul>
</blockquote>
<p><strong>sudo 명령어 - 명령어를 다른 사용자가 되어 실행</strong></p>
<ul>
<li>한 명령어만 슈퍼 사용자로 실행. 명령어가 끝나면 일반 사용자로 돌아옴</li>
<li>입력하는 암호는 <strong>현재 로그인한 사용자의 암호</strong> (su는 슈퍼 사용자 암호)</li>
</ul>
<pre><code class="language-bash">sudo cat /etc/shadow
# [sudo] sudouser의 암호: 입력</code></pre>
<blockquote>
<p><code>su</code>: 사용자 자체가 슈퍼 사용자로 전환 (exit 전까지 유지)<br><code>sudo</code>: 명령어 하나만 슈퍼 사용자로 실행</p>
</blockquote>
<p><strong>sudo 설정 - <code>/etc/sudoers</code></strong></p>
<pre><code class="language-bash">cat /etc/sudoers   # root로 실행</code></pre>
<pre><code>%wheel ALL=(ALL) ALL    # wheel 그룹에 속한 사용자는 모든 명령어 실행 가능</code></pre><p>특정 사용자에게 sudo 권한 부여:</p>
<pre><code class="language-bash">sudouser ALL=(ALL) ALL</code></pre>
<blockquote>
<p>sudoers 파일에 등록되지 않은 사용자는 <code>sudo</code> 사용 불가<br>sudo 권한을 준다는 것은 슈퍼 사용자의 권한을 주는 것과 마찬가지</p>
</blockquote>
<hr>
<h2 id="3-filesystem-및-disk-관리">3. Filesystem 및 disk 관리</h2>
<h3 id="파일시스템이란">파일시스템이란</h3>
<ul>
<li>OS에서 파일을 관리하도록 여러 가지 규칙과 방법을 정의하고 쉽게 사용할 수 있도록 기능을 제공해 주는 시스템</li>
<li>파일을 저장하고 삭제하는 등 파일을 구성하는 여러 보조 정보들을 활용하여 통합적으로 관리하게 만들어 주는 체계</li>
<li>디스크를 포맷한다 = 파일 시스템을 생성한다</li>
</ul>
<hr>
<h3 id="주요-파일시스템">주요 파일시스템</h3>
<p><strong>XFS</strong></p>
<ul>
<li>64비트 저널링 파일 시스템</li>
<li>RHEL7 버전부터 기본 파일 시스템</li>
<li>b-trees를 사용하여 우수한 I/O 확장성을 제공하고 모든 사용자 데이터 및 메타데이터를 인덱스</li>
<li>빠른 복구를 용이하게 하는 메타데이터 저널링 지원</li>
<li>마운트되어 활성화된 상태로 조각 모음 및 확장 가능</li>
<li>extend 기반 할당 사용 (지연 할당, 사전 할당 지원)</li>
</ul>
<blockquote>
<p>디스크 조각화: 시간이 지나면서 파일의 수많은 조각이 디스크 전체에 흩어지게 되는 현상. 파일에 액세스하는 데 시간이 더 오래 걸리고 속도가 느려짐</p>
</blockquote>
<p><strong>BtrFS</strong></p>
<ul>
<li>&quot;Butter&quot; 또는 &quot;Better&quot; FS로 발음</li>
<li>&quot;B-Tree File System&quot;의 약자</li>
<li>드라이브 풀링, 즉석 스냅샷, 투명한 압축 및 온라인 조각 모음 지원</li>
<li>최근 개발된 차세대 리눅스 파일 시스템이 될 수도 있음</li>
</ul>
<p><strong>EXT4</strong></p>
<ul>
<li>기존에 많이 사용되어 온 파일 시스템</li>
</ul>
<hr>
<h3 id="파일시스템-구성">파일시스템 구성</h3>
<p><strong>디스크 추가 (VirtualBox 기준)</strong></p>
<pre><code class="language-bash"># 가상머신 전원 종료 후 디스크 추가
poweroff</code></pre>
<p>VirtualBox 관리자 → 가상머신 설정 → 저장소 → SATA 컨트롤러 우클릭 → 하드디스크 추가 → 만들기 → VDI 포맷 선택 → 이름 작성 → 추가 → 확인 → 가상머신 시작</p>
<p><strong>디스크 확인</strong></p>
<pre><code class="language-bash">lsblk
lsblk -o NAME,SIZE,TYPE,FSTYPE</code></pre>
<pre><code>NAME            MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
sda               8:0    0   20G  0 disk
├─sda1            8:1    0    1G  0 part /boot
└─sda2            8:2    0   19G  0 part
sdb               8:16   0    2G  0 disk    ← 새로 추가한 디스크 (아직 파티션 없음)</code></pre><hr>
<p><strong>파티셔닝 - fdisk</strong></p>
<p>파티션으로 나누는 이유: 서로 다른 파티션에 다른 파일 시스템을 구현할 수 있기 때문</p>
<pre><code class="language-bash">fdisk /dev/sdb</code></pre>
<p>주요 명령어:</p>
<table>
<thead>
<tr>
<th>명령</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>m</code></td>
<td>도움말 출력</td>
</tr>
<tr>
<td><code>p</code></td>
<td>현재 파티션 테이블 출력</td>
</tr>
<tr>
<td><code>n</code></td>
<td>새로운 파티션 생성</td>
</tr>
<tr>
<td><code>d</code></td>
<td>파티션 삭제</td>
</tr>
<tr>
<td><code>l</code></td>
<td>파티션 타입 목록 출력</td>
</tr>
<tr>
<td><code>w</code></td>
<td>파티션 테이블에 저장하고 종료</td>
</tr>
<tr>
<td><code>q</code></td>
<td>저장하지 않고 종료</td>
</tr>
</tbody></table>
<p>파티션 생성 과정:</p>
<pre><code>n → p (primary, 최대 4개 / 이후는 extended) → 번호 입력 → 시작 섹터 (기본값 2048) → 크기 지정 (+512M 등)
w (저장)</code></pre><blockquote>
<p>first sector: 1부터 있지만 2048부터 사용 가능 (그 전은 예약)<br>GPT 파티션은 <code>g</code> 입력 후 타입 변경 후 생성</p>
</blockquote>
<hr>
<p><strong>파일시스템 생성 - mkfs</strong></p>
<pre><code class="language-bash">mkfs -t xfs /dev/sdb1</code></pre>
<hr>
<p><strong>파일시스템 마운트</strong></p>
<ul>
<li>파일시스템을 사용하여 파일을 저장하기 위해서는 특정한 디렉토리 경로로 연결해야 함</li>
<li>linux의 모든 파일은 <code>/</code> (루트 디렉토리) 하위의 경로를 가짐</li>
</ul>
<pre><code class="language-bash"># 마운트 포인트 생성
mkdir /data

# 마운트
mount -t xfs /dev/sdb1 /data

# 파일시스템 상태 확인
df -Th

# 마운트 해제
umount /dev/sdb1</code></pre>
<p><strong>부팅 시 자동 마운트 - <code>/etc/fstab</code></strong></p>
<pre><code class="language-bash"># /etc/fstab 마지막 행에 추가
/dev/sdb1       /data   xfs     defaults        0 0</code></pre>
<pre><code class="language-bash"># fstab 기반으로 마운트
mount -a
df -Th</code></pre>
<blockquote>
<p><code>/etc/fstab</code>에 정보 추가하면 부팅 시 마운트 자동으로 해줌</p>
</blockquote>
<hr>
<h2 id="4-프로세스-관리">4. 프로세스 관리</h2>
<h3 id="프로세스란">프로세스란</h3>
<ul>
<li>디스크에 있던 특정 파일들을 메모리에 적재하면서 처리하기 위해 CPU 쪽에서 스레드를 통해 연산 처리하는 것</li>
<li>특정한 사용자로 명령어를 통해 프로그램을 실행했을 때 프로세스가 생성되며 고유한 ID를 할당받음 → <strong>PID (Process ID)</strong></li>
<li>실행시킨 프로세스 = 부모 프로세스 / 하위 프로세스 = 자식 프로세스 (PPID: Parent Process ID 정보 가짐)</li>
<li>각 프로세스는 고유한 권한을 가지고 자체 가상 주소 공간에서 실행됨</li>
<li>IPC(Interactive Process Communication) 등 안전한 커널 관리 메커니즘을 통하지 않고는 다른 프로세스와 상호 작용할 수 없음 → 하나의 프로세스가 에러가 나도 다른 프로세스는 정상 작동</li>
</ul>
<blockquote>
<p>좀비 프로세스: 죽지 않고 계속 남아 있는 프로세스. 단순히 있는 건 문제 없으나, 자식이 죽고 좀비로 되어 있는데 부모가 안 죽이면 메모리 부족 문제가 발생할 수 있음</p>
</blockquote>
<hr>
<h3 id="rpm-redhat-package-manager">RPM (Redhat Package Manager)</h3>
<ul>
<li>Redhat에서 패키지를 쉽게 설치하고 관리하기 위해 만든 패키지 관리 프로그램</li>
<li>복잡한 컴파일 단계를 자동으로 처리</li>
<li><code>.rpm</code> 파일을 갖고 패키지 관리</li>
</ul>
<table>
<thead>
<tr>
<th>옵션</th>
<th>long 옵션</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>-q</code></td>
<td><code>--query</code></td>
<td>패키지 정보 질의</td>
</tr>
<tr>
<td><code>-i</code></td>
<td><code>--install</code></td>
<td>패키지 설치</td>
</tr>
<tr>
<td><code>-U</code></td>
<td><code>--upgrade</code></td>
<td>패키지 업그레이드</td>
</tr>
<tr>
<td><code>-e</code></td>
<td><code>--erase</code></td>
<td>패키지 삭제</td>
</tr>
<tr>
<td><code>-h</code></td>
<td><code>--hash</code></td>
<td>설치/업그레이드 프로그레스 상태 표시 (<code>-v</code>와 함께 사용)</td>
</tr>
<tr>
<td><code>-p</code></td>
<td><code>--package</code></td>
<td>패키지를 선택하여 query하거나 verify</td>
</tr>
<tr>
<td><code>-l</code></td>
<td><code>--list</code></td>
<td>패키지 안의 파일 정보 출력</td>
</tr>
<tr>
<td><code>-a</code></td>
<td><code>--all</code></td>
<td>모든 패키지 정보 출력</td>
</tr>
</tbody></table>
<pre><code class="language-bash">rpm -qa
rpm -qa | grep ssh
rpm -q openssh
rpm -ql openssh
rpm -qip vsftpd-3.0.2-29.el7_9.x86_64.rpm
rpm -ivh vsftpd-3.0.2-29.el7_9.x86_64.rpm
rpm -evh vsftpd</code></pre>
<hr>
<h3 id="파이프라인-및-리다이렉션">파이프라인 및 리다이렉션</h3>
<p><strong>파이프라인 (<code>|</code>)</strong></p>
<pre><code>CMD1 | CMD2</code></pre><ul>
<li>두 개의 명령어를 파이프라인으로 이어주는 역할</li>
<li>CMD1의 출력을 CMD2의 입력으로 넣어줌</li>
<li>CMD2에는 <code>grep</code> 많이 사용 → 해당 내용 있으면 그것만 출력</li>
</ul>
<p><strong>3채널 (stdin/stdout/stderr)</strong></p>
<table>
<thead>
<tr>
<th>채널</th>
<th>번호</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>stdin</td>
<td>0</td>
<td>터미널 통해 입력받음</td>
</tr>
<tr>
<td>stdout</td>
<td>1</td>
<td>터미널 통해 출력</td>
</tr>
<tr>
<td>stderr</td>
<td>2</td>
<td>에러 출력</td>
</tr>
</tbody></table>
<p><strong>리다이렉션</strong></p>
<pre><code class="language-bash">cmd &gt; result.txt        # stdout을 파일로 저장 (화면에는 출력 안 됨)
cmd &lt; result.txt        # 파일을 표준 입력으로 사용
cmd 2&gt; error.log        # stderr를 파일로 저장</code></pre>
<blockquote>
<p>스트림으로 처리한다 → 파일로 만들지 않고 흘려보낸다 (실시간 처리 가능)<br>리다이렉션은 재사용 가능하지만 지연시간 발생 가능 (단점)</p>
</blockquote>
<hr>
<h3 id="yum--dnf">Yum / DNF</h3>
<ul>
<li>사용 가능한 소프트웨어 패키지의 rpm 파일이 저장되어 있는 온라인 저장소인 <strong>리포지토리</strong>와 함께 작동하도록 설계</li>
<li>패키지 종속성을 자동으로 해결 (rpm은 수동으로 처리해야 했음)</li>
<li>최근 RHEL 계열은 yum의 업그레이드 버전인 <strong>dnf</strong> 사용 (명령어 체계 거의 동일)</li>
</ul>
<pre><code class="language-bash">ls /etc/yum.repos.d/
cat /etc/yum.repos.d/CentOS-Base.repo

yum clean all            # 레포지토리 캐시 제거
yum install docker-ce    # 설치
yum list docker-ce       # 설치된 항목 확인
yum info docker-ce       # 상세 확인
yum remove docker-ce     # 삭제</code></pre>
<hr>
<h3 id="linux-프로세스-명령어">Linux 프로세스 명령어</h3>
<pre><code class="language-bash">ps                          # 현재 사용자로 실행한 프로세스 확인
ps -e                       # 현재 시스템의 모든 프로세스 확인
ps -f                       # full-format으로 표시
ps -ef                      # grep으로 piping 해서 많이 사용
ps a                        # 다른 사용자의 프로세스 상태도 표시
ps x                        # 화면에 보이지 않는 프로세스까지 표시
ps u                        # 1분간 CPU, 메모리 점유율 포함 정보 표시
ps -ef | grep sshd          # 전체 프로세스 중 sshd 키워드로 검색
ps -ef | less
ps aux | less
ps -eo pid,ppid,%mem,%cpu,cmd    # 프로세스별 CPU, 메모리 사용률 확인

sleep 60 &amp;                  # 백그라운드로 sleep 프로세스 생성 (&amp; 없으면 foreground)
ps

ls /proc                    # 프로세스별 디렉토리 (커널 메모리에 마운트된 정보)</code></pre>
<blockquote>
<p><code>sleep</code>: 아무것도 처리하지 않는 멍때리는 프로세스<br><code>&amp;</code>: 백그라운드로 실행 (출력 스트림 기다리는 작업이 필요 없음)<br>데드락: 서로 기다리는 상태</p>
</blockquote>
<hr>
<h3 id="foreground--background">Foreground &amp; Background</h3>
<ul>
<li><strong>foreground</strong>: 프로세스가 실행 중일 때 사용자가 터미널을 사용해서 stdin을 사용하지 못함. 프로세스가 종료되어야 Shell의 prompt가 떨어짐</li>
<li><strong>background</strong>: 프로세스가 실행 중이더라도 터미널을 사용해 stdin으로 명령어를 수행할 수 있음. 종료되면 출력이 뜨지 않음. 명령행 끝에 <code>&amp;</code> 붙임</li>
</ul>
<pre><code class="language-bash"># foreground 실행 및 제어
sleep 60
&lt;Ctrl + c&gt;    # 즉시 종료
&lt;Ctrl + z&gt;    # 일시 중지

# background 실행
sleep 90 &amp;

# 상태 확인 및 전환
jobs          # 프로세스 상태 확인
bg 1          # 중지된 프로세스를 background로 실행 (숫자 = job ID)
fg            # 바로 직전에 중지된 프로세스를 foreground로 실행</code></pre>
<hr>
<h3 id="service-daemon">Service (Daemon)</h3>
<ul>
<li>사용자가 직접 제어하지 않고 백그라운드에 실행시킨 후 여러 작업들을 수행하는 프로세스</li>
<li>일반 백그라운드 프로세스와 달리 <strong>사용자와 상호작용하지 않음</strong></li>
<li>사용자 Input을 받기 위한 터미널이 없음</li>
<li>사용자별 프로세스가 아닌 시스템 전체적으로 동작</li>
<li><strong>systemd 프로세스</strong>로 (systemctl 명령어로) 관리. 높은 권한(root) 필요</li>
</ul>
<pre><code class="language-bash"># 서비스 목록 조회
systemctl list-units --type=service

systemctl status firewalld      # 상태 확인
systemctl stop firewalld        # 중지
systemctl disable firewalld     # 비활성화 (부팅 시 자동으로 올라오지 않음)
systemctl start firewalld       # 시작
systemctl enable firewalld      # 활성화</code></pre>
<hr>
<h3 id="systemctl과-container">systemctl과 Container</h3>
<p><strong>systemctl 방식</strong></p>
<ul>
<li>리눅스 부팅 시 가장 먼저 실행되는 systemd(PID 1)를 제어</li>
<li>호스트 OS의 자원을 직접 공유</li>
<li>항상 백그라운드에서 실행되어야 하는 핵심 서비스(네트워크, SSH, 로깅 등) 관리에 최적화</li>
</ul>
<pre><code class="language-bash">ps -ef | head

# nginx를 systemctl 방식으로 실행
dnf install nginx -y
systemctl start nginx</code></pre>
<p><strong>Container 방식</strong></p>
<ul>
<li>호스트 OS의 커널을 공유하지만 네임스페이스(Namespace)와 cgroups라는 리눅스 커널 기능으로 프로세스를 격리된 공간에서 실행</li>
<li>호스트 입장에서는 &#39;하나의 격리된 프로세스&#39;일 뿐</li>
</ul>
<pre><code class="language-bash">docker ps

# nginx를 컨테이너 방식으로 실행
docker run -d --name webserver -p 80:80 quay.io/uvelyster/nginx</code></pre>
<p>docker 옵션:</p>
<table>
<thead>
<tr>
<th>옵션</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>-d</code></td>
<td>백그라운드로 실행</td>
</tr>
<tr>
<td><code>--name</code></td>
<td>컨테이너 이름</td>
</tr>
<tr>
<td><code>-p 80:80</code></td>
<td>호스트 80 포트 → 컨테이너 80 포트 연결</td>
</tr>
<tr>
<td><code>quay.io/uvelyster/nginx</code></td>
<td>컨테이너 이미지</td>
</tr>
</tbody></table>
<hr>
<h3 id="프로세스-signal">프로세스 Signal</h3>
<p>background 프로세스를 종료하기 위해 특별한 signal을 프로세스로 전송</p>
<pre><code class="language-bash">kill -l           # signal 목록 확인
man 7 signal      # signal 상세 설명</code></pre>
<p>주요 signal:</p>
<table>
<thead>
<tr>
<th>Signal</th>
<th>번호</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>SIGHUP</td>
<td>1</td>
<td>터미널 끊김 감지</td>
</tr>
<tr>
<td>SIGINT</td>
<td>2</td>
<td>키보드 인터럽트 (Ctrl+c)</td>
</tr>
<tr>
<td>SIGKILL</td>
<td>9</td>
<td>강제 종료</td>
</tr>
<tr>
<td>SIGTERM</td>
<td>15</td>
<td>정상 종료 요청 (기본값, 권장)</td>
</tr>
<tr>
<td>SIGSTOP</td>
<td>19</td>
<td>프로세스 중지</td>
</tr>
<tr>
<td>SIGCONT</td>
<td>18</td>
<td>중지된 프로세스 재개</td>
</tr>
</tbody></table>
<pre><code class="language-bash">ps -ef | grep postfix         # postfix 프로세스의 PID 확인
kill -9 4249                  # 강제 종료 (-9 = SIGKILL)
systemctl restart postfix     # 재기동</code></pre>
<blockquote>
<p>foreground 종료: <code>Ctrl + c</code><br>background 강제 종료: <code>kill -9 PID</code><br>정상이라면 signal 안 던져도 자동으로 종료됨 (SIGTERM 권장)</p>
</blockquote>
<hr>
<h3 id="프로세스-모니터링">프로세스 모니터링</h3>
<pre><code class="language-bash">ps -eo pid,ppid,%mem,%cpu,cmd    # 프로세스별 CPU, 메모리 사용률

top          # 실시간 모니터링
h            # top 실행 중 help
q            # top 종료

uptime       # 1분, 5분, 15분 부하 평균</code></pre>
<blockquote>
<p>1분 사용량이 15분보다 높으면 → 점점 더 사용량이 높아지고 있음 (부하되고 있다)</p>
</blockquote>
<p><strong>top 상단 요약 정보</strong></p>
<pre><code>top - 21:05:46 up 6:07, 1 user, load average: 0.00, 0.01, 0.05
# 현재 시각, 부팅 이후 경과 시간, 사용자 수, 부하 평균(1분/5분/15분)

Tasks: 113 total, 1 running, 112 sleeping, 0 stopped, 0 zombie
# 전체 프로세스 및 각 상태별 개수

%Cpu(s): 0.0 us, 6.2 sy, 0.0 ni, 93.8 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
# CPU 사용률

KiB Mem: 1882072 total, 1388248 free, 274156 used, 219668 buff/cache
# 전체 메모리, 여유 메모리, 사용 중인 메모리, 커널 버퍼 + 페이지 캐시

KiB Swap: 2097148 total, 2097148 free, 0 used. 1440600 avail Mem
# 전체 swap, 여유 swap, 사용 중인 swap, 여유 메모리 + 페이지 캐시</code></pre><p>☁️ 클라우드 및 AWS 인프라 구성 마스터 가이드</p>
<ol start="5">
<li>SDx 가상화와 클라우드, 클라우드의 특성</li>
</ol>
<p>1) 가상화 (Virtualization)</p>
<p>과거의 IT 환경은 하드웨어 중심이었으며, 비효율성(하드웨어 성능의 10~20%만 사용, 유지비는 100% 지출)이 컸습니다. 물리적 제약을 깨고 소프트웨어가 하드웨어를 제어하기 시작하면서 등장한 개념이 가상화입니다.
가상화의 주요 목표는 더 나은 성능, 확장성, 안정성/가용성, 민첩성 획득 및 통합 보안/관리 도메인 생성입니다. 단일 컴퓨터를 여러 개별 컴퓨터로 만들거나, 논리적 네트워크 영역 구성, 여러 저장소를 하나로 묶거나 큰 저장소를 작게 나누는 등의 작업이 가능합니다.</p>
<p>2) SDx (Software Defined everything)</p>
<p>하드웨어의 제어권(Control Plane)을 물리적 장비에서 분리해 소프트웨어로 옮겨, 가상화된 리소스를 생성하고 물리적 리소스에 매핑시키는 기술입니다.</p>
<p>SDN (Software Defined Network): 가상의 네트워크</p>
<p>SDS (Software Defined Storage): 가상의 스토리지</p>
<p>SDDC (Software Defined Data Center): 가상의 데이터 센터</p>
<p>3) 가상화의 역사 (History)</p>
<p>1972년: 메인프레임 System/370 모델에서 시작 (당시 VM을 pseudo machine이라 명명). 리소스 할당 및 격리 제어 프로그램이 훗날 Hypervisor라는 이름을 얻음.</p>
<p>2003년 10월: x86용 최초 오픈소스 하이퍼바이저 Xen 도입 (Citrix XenServer, Oracle VM 등에 채택).</p>
<p>2007년 / 2010년: Redhat이 RHEL5에 Xen을 포함했으나, RHEL6(2010)에서 KVM으로 전환하며 오픈소스 하이퍼바이저의 전환점을 맞이함.</p>
<p>4) 가상화의 이점</p>
<p>짧은 평균 복원 시간 (MTTR): 실패한 서비스 복원에 전체 VM 스냅샷/백업을 활용하여 시간을 크게 단축.</p>
<p>노후화된 인프라 대안: 물리적 하드웨어(기대수명 3~4년)가 노후화되어도 VM은 영향을 받지 않고 하드웨어 업그레이드 후에도 지속 사용 가능.</p>
<p>Scale up이 쉬운 인프라: 호스트 용량이 남는 한 물리적 제한 없이 VM 용량 추가 가능.</p>
<p>리소스 활용: 적은 리소스로도 필요한 만큼 여러 VM을 동작시켜 가용성 추가 확보 및 효율적 관리.</p>
<p>운영 비용 절감: HA(고가용성) 구성을 가상 서버로 쉽게 변환 및 추가 가능하여 하드웨어 비용 절감.</p>
<p>5) 가상화의 종류 (목표 대상 기준)</p>
<p>데스크탑 가상화 (VDI): 장치에 의존하지 않고 어디서나 업무 환경 연결. 중앙 집중 관리/모니터링, 단순화된 배포 및 보안 설정 가능.</p>
<p>서버 가상화: 어플리케이션 환경을 구성. 백업 용이, 리소스 효율적 사용, 빠른 장애 대응.</p>
<p>어플리케이션 가상화: 원격 프로토콜/스트리밍 서비스를 패키징해 VM에 마운트 (예: Microsoft App-V, VMware App Volumes).</p>
<p>네트워크 가상화 (SDN): 물리적 스위치 없이 논리적인 가상 네트워크를 구성하여 안전하고 편하게 관리.</p>
<p>스토리지 가상화 (SDS): 물리적 공간을 가상화하여 할당. (file, block, object 유형). 공유 및 재사용성 향상.</p>
<p>6) 하이퍼바이저 (Hypervisor)</p>
<p>가상화의 본질인 추상화(Abstraction)와 격리(Isolation)를 달성하기 위해 하드웨어를 에뮬레이션(Emulation)하고 자원을 할당(Resource Allocation)하는 제어 프로그램입니다.</p>
<p>Type-1 (Native/Bare-metal): 하드웨어 위에 직접 설치. 오버헤드가 적고 보안이 강력 (엔터프라이즈 서버용).</p>
<p>예시: VMware ESXi, Xen, Microsoft Hyper-V, KVM (AWS EC2의 근간)</p>
<p>Type-2 (Hosted): 기존 운영체제(Host OS) 위에 애플리케이션처럼 설치. 설치가 쉽고 PC 테스트용으로 적합.</p>
<p>예시: Oracle VirtualBox, VMware Workstation</p>
<p>7) Virtualbox 기반 실습 환경 구성</p>
<p>설치: Oracle VirtualBox 설치 후 C++ 재배포 가능 패키지 설치 필요. 실습용 Rocky Linux ISO 파일 다운로드.</p>
<p>가상머신 생성: 무인설치 건너뛰기, 리소스 설정(기본 1vCPU, 2048MB RAM, 20GB 디스크).</p>
<p>네트워크 구성: 네트워크 관리자에서 호스트 전용(Host-only) 네트워크 설정 (실습 IP 대역: 172.16.0.1로 변경).</p>
<p>8) 클라우드 서비스 소개</p>
<p>가상화로 만든 추상화 계층을 통해 IT 리소스를 온디맨드 방식으로 대량 제공하며, 사용한 만큼 비용을 지불하는 모델입니다.</p>
<p>NIST의 클라우드 컴퓨팅 5대 필수 특성:</p>
<p>온디맨드 셀프 서비스: 버튼/API 호출로 임시 프로비저닝 가능.</p>
<p>광범위한 네트워크 액세스: 네트워크를 통한 기능 사용.</p>
<p>리소스 풀: 다중 테넌트 모델 기반 리소스 공유 할당.</p>
<p>신속함과 탄력성: 용량의 빠른 확장 및 축소.</p>
<p>측정된 서비스: 소비 통찰력을 위한 메트릭 제공.</p>
<p>클라우드 오퍼링 3가지 유형: 퍼블릭(대중 사용), 프라이빗(단일 조직 내), 하이브리드(퍼블릭+프라이빗 혼합).</p>
<p>CSP(Cloud Service Provider) 서비스 3대 분류:</p>
<p>IaaS: 가상머신, 스토리지, 네트워킹 등 기본 리소스 제공 (AWS EC2 등)</p>
<p>PaaS: 맞춤형 앱 배포 플랫폼 제공 (AWS Elastic Beanstalk 등)</p>
<p>SaaS: 인프라와 소프트웨어가 결합된 최종 어플리케이션 제공 (Microsoft 365 등)</p>
<p>9) Amazon Web Service (AWS) 개요</p>
<p>컴퓨팅(EC2), 스토리지(S3), 네트워킹 등의 솔루션을 REST API 및 HTTP 프로토콜을 통해 종량제(Pay-as-you-go)로 제공.</p>
<p>글로벌 인프라: 전 세계에 데이터 센터 분산. 리전(Region)과 여러 개의 가용 영역(AZ)으로 구성 (예: 한국 리전 ap-northeast-2는 4개의 가용영역 보유).</p>
<p>관리 콘솔: 루트 사용자는 계정 생성 시 사용하며, 평소에는 IAM 사용자로 로그인하여 작업.</p>
<ol start="6">
<li>IAM 인증 및 AWS CLI</li>
</ol>
<p>1) IAM 서비스 및 MFA 설정</p>
<p>AWS 계정은 리소스의 바구니이며, 모든 권한을 가진 &#39;루트 사용자&#39; 대신 접근을 제한한 IAM 사용자를 생성하여 사용해야 합니다.</p>
<p>MFA(멀티 팩터 인증): 아이디/비밀번호 외에 스마트폰 앱이나 물리 키를 통한 일회용 인증 코드를 추가로 요구하는 강력한 보안 장치.</p>
<p>가상 MFA 앱: Google Authenticator, Microsoft Authenticator, Duo Mobile 등.</p>
<p>IAM 사용자 생성: Management Console 접근 활성화 여부 선택, 권한 정책 할당(실습용 AdministratorAccess이나 실무에서는 권장 안 함).</p>
<p>2) AWS CLI 설정</p>
<p>API 진입점을 호출하여 터미널 내에서 인프라 생성 및 반복 작업을 자동화(Jenkins 등 연동)할 수 있는 도구입니다.</p>
<p>설치: curl로 다운로드 후 unzip, sudo ./aws/install 수행. aws --version으로 확인.</p>
<p>자격 증명 (Credentials): 콘솔 패스워드 대신 액세스 키(Access Key)와 시크릿 키(Secret Key) 사용. 생성 후 안전한 곳에 보관.</p>
<p>초기화: aws configure (키 정보, 리전, 출력 포맷 입력). aws configure list로 확인.</p>
<p>3) IAM 정책 (Policy) 및 역할 (Role) 구성</p>
<p>IAM 정책: 누가(Principal), 어떤 조건(Condition)에서, 어떤 서비스(Resource)의, 어떤 기능(Action)을, 허용(Allow)/거부(Deny)할지 명시한 JSON 문서.</p>
<p>예시: S3 버킷(s3://tmddnr-wave)의 목록 조회(s3:ListBucket) 권한 허용 JSON 작성.</p>
<p>aws iam create-policy로 정책 생성 후 aws iam attach-group-policy로 그룹에 바인딩.</p>
<p>IAM 역할: 리소스(예: EC2)에게 사용자 대신 작업을 수행할 수 있는 권한을 위임하는 객체.</p>
<p>Trust Policy(ec2.amazonaws.com)가 명시된 ec2trust.json으로 aws iam create-role 생성.</p>
<p>이후 aws iam attach-role-policy로 앞서 만든 정책을 역할에 바인딩.</p>
<p>4) 인스턴스 프로파일을 통한 EC2 권한 부여 (CLI 실습)</p>
<p>키페어 생성: aws ec2 create-key-pair 결과값을 .pem 파일로 저장 후 chmod 400 적용.</p>
<p>EC2 생성: aws ec2 run-instances (이미지, 인스턴스 타입, 키, 보안그룹, 서브넷 ID 지정).</p>
<p>인스턴스 프로파일: aws iam create-instance-profile 생성 -&gt; 역할 추가 -&gt; aws ec2 associate-iam-instance-profile로 가상머신에 프로파일 연결.</p>
<p>결과 테스트: ssh로 접속하여 내부에서 aws s3 ls가 에러 없이 실행되는지 확인.</p>
<ol start="7">
<li>EC2 서버 구성 및 EBS Block Storage 연결</li>
</ol>
<p>1) EC2 (Elastic Compute Cloud) 가상머신 구성</p>
<p>물리적 머신의 일부에 액세스하는 가상 머신 서비스입니다.</p>
<p>OS 이미지 (AMI): 사전 설치된 소프트웨어 번들. AWS는 맞춤형 하드웨어와 KVM을 결합한 Nitro 가상화(HVM)를 사용하여 베어메탈 수준의 성능을 제공합니다.</p>
<p>인스턴스 유형(크기 및 최적화):</p>
<p>T: 짧은 시간 높은 성능 발휘 (기본 성능형, 실습용 t3.micro)</p>
<p>M: CPU와 메모리 균형 (범용)</p>
<p>C: 컴퓨팅 최적화 (높은 CPU)</p>
<p>R: 메모리 최적화</p>
<p>D/I: 스토리지 최적화 (HDD/SSD)</p>
<p>X: 방대한 메모리 최적화</p>
<p>F: FPGA 기반 가속</p>
<p>P/G/CG: GPU 가속</p>
<p>키페어 구성: 비대칭 키 사용. Public Key는 클라우드에, Private Key는 로컬 유저가 보관.</p>
<p>2) 네트워크 및 보안그룹 (Firewall) 방화벽</p>
<p>보안 그룹: 인스턴스에 들어오고 나가는 인바운드(Ingress)/아웃바운드(Egress) 트래픽 제어.</p>
<p>규칙 정의: 방향, IP 프로토콜, 포트, 원본/대상을 기반으로 허용 여부 결정.</p>
<p>모범 사례: 꼭 필요한 포트(웹서버의 경우 HTTP 80, HTTPS 443)만 열어 가장 제한적인 규칙 적용(사람의 실수 방지).</p>
<p>3) 가상머신 연결 방법</p>
<p>Public IP 활용: 브라우저에서 직접 연결 기능 사용.</p>
<p>SSM Session Manager 활용: SSH 포트를 열지 않아도 연결 가능하여 보안 수준이 매우 높음.</p>
<p>EC2에 AmazonSSMManagedInstanceCore 정책이 부여된 IAM 역할을 생성 및 업데이트하고 재부팅하면 세션 매니저를 통한 연결 활성화.</p>
<p>4) EBS (Elastic Block Store) 볼륨 추가</p>
<p>파일 시스템 용량 확보를 위해 가상머신에 연결하는 블록 장치입니다.</p>
<p>볼륨 생성 시 가상머신과 동일한 가용영역(AZ)으로 설정 필수.</p>
<p>생성 후 EC2 인스턴스에 연결하고 서버 내에서 lsblk로 디스크 장착 여부 확인 (이후 파일시스템 생성 및 마운트 진행).</p>
<ol start="8">
<li>VPC 네트워크 연결</li>
</ol>
<p>1) VPC (Virtual Private Cloud) 구성</p>
<p>공용 클라우드 환경 안에 다른 사람과 격리된 &#39;나만의 독립적인 사설 클라우드 환경&#39;을 구성해 줍니다. IP 대역, 서브넷, 라우팅 테이블, 게이트웨이 등을 마음대로 제어할 수 있습니다.</p>
<p>사설 IP 대역: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16</p>
<p>VPC 생성 시 기본 네트워크 ACL, DHCP 옵션 세트, 기본 라우팅 테이블이 자동 구성됩니다. (세부 조정 시 새로 만드는 것 권장).</p>
<p>2) 서브넷 (Subnet) 구성</p>
<p>VPC CIDR 범위를 넘지 않는 선에서 용도별로 네트워크를 분할합니다.</p>
<p>외부 통신용 Public Subnet / 클라우드 내부 통신용 Private Subnet. (기본 생성 시 Private 상태).</p>
<p>서브넷 설정 편집에서 &#39;퍼블릭 IPv4 주소 자동 할당 활성화&#39;를 켜면 배치되는 VM이 공인 IP를 받음.</p>
<p>3) 인터넷 게이트웨이 (IGW) 및 라우팅 테이블</p>
<p>서브넷을 Public으로 만들기 위한 과정입니다.</p>
<p>인터넷 게이트웨이: VPC 내부 트래픽을 외부 인터넷으로 내보내는 문. 생성 후 VPC에 연결 (하나의 VPC당 1개의 IGW만 가능).</p>
<p>라우팅 테이블: 트래픽의 목적지 경로를 지정. 새로운 라우팅 테이블 생성 후 0.0.0.0/0 (모든 트래픽)의 대상을 &#39;인터넷 게이트웨이&#39;로 향하게 편집하고, 해당 서브넷에 연결 명시.</p>
<p>4) NAT 게이트웨이</p>
<p>외부에서 들어오는 요청은 차단하고, 내부에서 외부로 나가는 요청(소프트웨어 패치 다운로드 등)만 허용하는 게이트웨이입니다.</p>
<p>전송 중인 패킷의 IP 헤더를 수정(다시 매핑).</p>
<p>프라이빗 서브넷의 인터넷 통신을 위해 사용 (NAT 인스턴스 방식도 존재).</p>
<p>5) VPC 엔드포인트</p>
<p>VPC 내부가 아닌 AWS에서 직접 관리하는 서비스(S3, DynamoDB 등)에 인터넷 게이트웨이나 NAT 없이 비공개(Private)로 연결하게 해주는 가상 장치입니다. (보안 강화, 비용 절감 이점).</p>
<p>유형:</p>
<p>인터페이스 엔드포인트: 다양한 서비스 지원.</p>
<p>게이트웨이 엔드포인트: S3와 DynamoDB 전용.</p>
<p>생성 시 VPC와 라우팅 테이블을 선택하면 해당 경로를 통해 직접 비공개 통신이 가능해집니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[잉프라 회고록]]></title>
            <link>https://velog.io/@cse23_ewha/%EC%9E%89%ED%94%84%EB%9D%BC-%ED%9A%8C%EA%B3%A0%EB%A1%9D</link>
            <guid>https://velog.io/@cse23_ewha/%EC%9E%89%ED%94%84%EB%9D%BC-%ED%9A%8C%EA%B3%A0%EB%A1%9D</guid>
            <pubDate>Tue, 19 May 2026 09:58:58 GMT</pubDate>
            <description><![CDATA[<h2 id="1-aws-app-runner가-서울에-없다">1. AWS App Runner가 서울에 없다.</h2>
<p>프로젝트 목적이 &#39;트래픽 스파이크 처리&#39;, &#39;서버리스 지향&#39;, &#39;운영 부담 최소화&#39;
그래서 당연히 EC2보다 관리가 편한 <strong>AWS App Runner</strong>를 쓰기로 결정했고, Role 권한 부여하고 인프라 설계를 어느정도 한상태였는데... 막상 APP runner를 사용해서 배포하려니까 서울 리전(ap-northeast-2)에는 App Runner가 출시조차 안 되어 있었음.........</p>
<h3 id="ai말을-너무-신뢰하지말자">AI말을 너무 신뢰하지말자</h3>
<p>를 다시금 새기게됌</p>
<p>제미나이 응답</p>
<pre><code>1. 도쿄 리전(ap-northeast-1) 사용하기 (적극 추천) ⭐
아시아에서 App Runner를 지원하는장 가까운 리전이 바로 도쿄입니다.

한국(서울)과 도쿄는 지리적으로 가까워서 네트워크 지연 시간(Latency) 차이가 거의 느껴지지 않습니다. 동아리 프로젝트나 테스트용 API 서버로는 전혀 지장이 없습니다.

2. ECS Fargate
프로젝트 목적(트래픽 스파이크 처리, 서버리스 지향, 운영 부담 최소화)에 제일 부합
App Runner가 내부적으로 ECS Fargate 기반이라 사실상 같은 기술을 직접 쓰는 것</code></pre><p>우리의 선택 -&gt; 2번</p>
<h3 id="왜-ecs-fargate">왜 ECS Fargate?</h3>
<p>사실 App Runner도 내부적으로는 ECS Fargate 기반으로 돌아감</p>
<ul>
<li><strong>서버 관리 전무</strong>: EC2처럼 OS 패치나 인프라 관리에 리소스를 쏟을 필요 없이 컨테이너만 관리하면 됌</li>
<li><strong>Auto Scaling &amp; VPC 연동</strong>: CPU/메모리 기반 확장이 자유롭고 VPC 연동이 기본이라 ElastiCache 연동이 자연스러움</li>
<li><strong>기존 자산 유지</strong>: App Runner를 위해 빌드해 둔 <code>Dockerfile</code> → <code>ECR</code> → <code>Task Definition</code> → <code>Service</code> 파이프라인과 GitHub Actions CI/CD 구조를 거의 그대로 가져갈 수 있었음</li>
</ul>
<p>다시 한번 생각해야할것</p>
<h2 id="ai를-너무-신뢰하지는-말자">AI를 너무 신뢰하지는 말자</h2>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Troubleshooting] NIEDU 인프라 이슈 해결 회고]]></title>
            <link>https://velog.io/@cse23_ewha/Troubleshooting-NIEDU-%EC%9D%B8%ED%94%84%EB%9D%BC-%EC%9D%B4%EC%8A%88-%ED%95%B4%EA%B2%B0-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@cse23_ewha/Troubleshooting-NIEDU-%EC%9D%B8%ED%94%84%EB%9D%BC-%EC%9D%B4%EC%8A%88-%ED%95%B4%EA%B2%B0-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Mon, 11 May 2026 04:10:13 GMT</pubDate>
            <description><![CDATA[<h2 id="issue-1-ec2-스토리지-부족으로-인한-배포-중단-및-인스턴스-업그레이드">Issue 1. EC2 스토리지 부족으로 인한 배포 중단 및 인스턴스 업그레이드</h2>
<h3 id="문제-상황-symptom">문제 상황 (Symptom)</h3>
<ul>
<li>새로운 기능을 구현하고 ECR(Elastic Container Registry)에서 최신 도커 이미지를 받아 배포하려 했으나, <strong>&quot;No space left on device&quot;</strong> 에러와 함께 배포가 무한 실패함.</li>
<li>EC2 쉘(Shell)에 접속해 <code>df -h</code>와 <code>docker images</code>를 확인한 결과, AI 모델이 포함된 도커 이미지 용량이 너무 커서 남은 용량이 거의 없었음.</li>
</ul>
<h3 id="원인-분석-cause">원인 분석 (Cause)</h3>
<ul>
<li><strong>AI 이미지의 비대화</strong>: Python AI 서버 이미지가 딥러닝 라이브러리와 모델 파일을 포함하면서 수 GB 단위로 커짐.</li>
<li><strong>인스턴스 자원 한계</strong>: 기존 <code>t3.micro</code> 인스턴스는 디스크 용량뿐만 아니라 메모리(1GB)도 부족하여, 무거운 도커 이미지를 압축 해제하고 실행하는 과정을 버티지 못함.</li>
</ul>
<h3 id="해결-방법-solution">해결 방법 (Solution)</h3>
<ol>
<li><strong>인스턴스 타입 업그레이드</strong>: CPU와 메모리 사양을 높이기 위해 인스턴스를 <code>t3.micro</code>에서 <code>t3.small</code>로 스케일 업(Scale-up)함.</li>
<li><strong>도커 환경 정리</strong>: <code>docker system prune -a</code> 명령어를 통해 사용하지 않는 오래된 이미지와 컨테이너를 삭제하여 가용 공간을 확보함.</li>
<li><strong>EBS 볼륨 최적화</strong>: 인스턴스 타입 변경과 함께 루트 볼륨(EBS) 크기를 증설하여 향후 이미지 업데이트에 대비함.</li>
</ol>
<hr>
<h2 id="issue-2-ai-퀴즈-생성-데이터의-db-저장-정합성-오류">Issue 2. AI 퀴즈 생성 데이터의 DB 저장 정합성 오류</h2>
<h3 id="문제-상황-symptom-1">문제 상황 (Symptom)</h3>
<ul>
<li>Python AI 서버에서 퀴즈 생성 로직은 정상적으로 동작하여 응답을 보내주지만, Spring Boot 서버에서 이를 받아 <strong>DB(<code>Content</code>, <code>Question</code> 테이블 등)에 저장할 때 데이터가 누락되거나 저장되지 않는 현상</strong> 발생.</li>
</ul>
<h3 id="원인-분석-cause-1">원인 분석 (Cause)</h3>
<ol>
<li><strong>API 데이터 정합성 불일치</strong>: Python 서버가 보내주는 JSON 필드 구조와 Spring Boot의 DTO 구조가 미세하게 달러 역직렬화(Deserialization) 과정에서 데이터가 유실됨.</li>
<li><strong>트랜잭션 관리 실패</strong>: 퀴즈 생성 요청은 성공했으나, 연관된 여러 테이블(Step -&gt; Content -&gt; Choice)에 데이터를 넣는 과정에서 하나라도 실패할 경우 전체가 롤백(Rollback)되어야 하는데, 이 처리가 미흡하여 데이터가 꼬임.</li>
<li><strong>영속성 컨텍스트 이슈</strong>: 부모 엔티티가 저장되기 전에 자식 엔티티를 저장하려다 외래 키(FK) 제약 조건 위반이 발생함.</li>
</ol>
<h3 id="해결-방법-solution-1">해결 방법 (Solution)</h3>
<ol>
<li><strong>DTO 및 검증 로직 강화</strong>: Python 서버와의 규약을 재정의하고, <code>@Valid</code>를 통해 들어오는 데이터의 정합성을 최우선으로 검증함.</li>
<li><strong>Cascade 옵션 및 연관관계 편의 메서드 활용</strong>: <code>JPA CascadeType.ALL</code> 설정을 통해 부모 엔티티(<code>Step</code>) 저장 시 자식 엔티티들이 한 번에 안전하게 저장되도록 구조를 개선함.</li>
<li><strong>트랜잭션 원자성 확보</strong>: 퀴즈 저장 서비스 로직에 <code>@Transactional</code>을 적용하여 전체 프로세스가 &quot;All or Nothing&quot;으로 동작하게 하여 데이터 무결성을 보장함.</li>
</ol>
<hr>
<h3 id="회고를-마치며">회고를 마치며</h3>
<blockquote>
<p>&quot;인프라는 한 번 구축하면 끝나는 것이 아니라, 서비스 규모와 데이터 크기에 따라 지속적으로 모니터링하고 스케일링해야 한다는 것을 배웠습니다. 또한, 서로 다른 언어(Spring-Python)로 구성된 서버 간 통신에서는 데이터 정합성을 맞추는 것이 시스템 안정성의 핵심임을 깨달았습니다...어렵다 ㅠㅠㅠ&quot;</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[PROMATE 백엔드 트러블슈팅]]></title>
            <link>https://velog.io/@cse23_ewha/PROMATE-%EB%B0%B1%EC%97%94%EB%93%9C-%ED%8A%B8%EB%9F%AC%EB%B8%94%EC%8A%88%ED%8C%85</link>
            <guid>https://velog.io/@cse23_ewha/PROMATE-%EB%B0%B1%EC%97%94%EB%93%9C-%ED%8A%B8%EB%9F%AC%EB%B8%94%EC%8A%88%ED%8C%85</guid>
            <pubDate>Mon, 04 May 2026 08:52:58 GMT</pubDate>
            <description><![CDATA[<h4 id="1-다중-사용자-환경에서의-데이터-경합race-condition-해결">1. 다중 사용자 환경에서의 데이터 경합(Race Condition) 해결</h4>
<ul>
<li><p><strong>문제 상황:</strong> 여러 명의 팀원이 채팅방에서 동시에 AI 요약(<code>@mates</code>)을 호출하거나 상충하는 데이터를 입력할 때, AI 서버로 중복 요청이 가거나 DB의 요약 데이터가 덮어씌워지는(Lost Update) 데이터 정합성 파괴 현상 발생.</p>
</li>
<li><p><strong>해결 과정:</strong></p>
<ol>
<li><strong>입구 통제 (Redis 분산 락):</strong> AI 호출 시 <code>projectId</code>를 Key로 Redis 분산 락(Distributed Lock)을 걸어, 누군가 요약을 진행 중일 때는 다른 팀원의 중복 호출을 차단함.</li>
<li><strong>출구 방어 (JPA 낙관적 락):</strong> AI 요약본을 DB에 최종 업데이트할 때 <code>@Version</code>을 활용한 낙관적 락(Optimistic Lock)을 적용. 읽은 시점과 쓰는 시점의 버전이 다르면 예외를 발생시키고 안전하게 롤백하여 데이터 오염 방지.</li>
</ol>
</li>
<li><p><strong>결과:</strong> 100%의 데이터 정합성을 보장하는 안전한 실시간 협업 환경 구축 완료.</p>
<pre><code class="language-java">// 1. 엔티티에 낙관적 락 적용 (출구 방어)
@Entity
public class ProjectSummary {
  @Id @GeneratedValue
  private Long id;
  private String content;

  @Version // JPA Optimistic Lock
  private Long version; 
}
</code></pre>
</li>
</ul>
<p>// 2. 분산 락을 활용한 AI 호출 로직 (입구 방어)
@Service
@RequiredArgsConstructor
public class AISummaryService {
    private final RedissonClient redissonClient;
    private final SummaryRepository summaryRepository;</p>
<pre><code>public void requestAiSummary(Long projectId) {
    RLock lock = redissonClient.getLock(&quot;AI_LOCK:&quot; + projectId);
    try {
        // 락 획득 시도 (대기 시간 0초, 락 유지 10초)
        boolean isLocked = lock.tryLock(0, 10, TimeUnit.SECONDS);
        if (!isLocked) {
            throw new CustomException(&quot;이미 AI가 요약을 진행 중입니다.&quot;);
        }

        // AI 호출 및 DB 업데이트 로직 (낙관적 락 충돌 시 ObjectOptimisticLockingFailureException 발생)
        updateSummaryWithAI(projectId);

    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    } finally {
        if (lock.isLocked() &amp;&amp; lock.isHeldByCurrentThread()) {
            lock.unlock(); // 작업 완료 후 락 해제
        }
    }
}</code></pre><p>}</p>
<pre><code>
#### 2. 다중 서버(Scale-out) 환경에서의 WebSocket 세션 동기화 문제
* **문제 상황:** 단일 서버에서는 STOMP 기반 채팅이 잘 동작했으나, 트래픽 분산을 위해 백엔드 서버를 여러 대(Scale-out)로 늘렸을 때 서로 다른 서버에 접속한 유저 간에 메시지가 전달되지 않는 브로드캐스팅 단절 문제 발생.
* **해결 과정:** * **Redis Pub/Sub 도입:** 기존에 세션 관리용으로 쓰던 Redis의 Pub/Sub(발행/구독) 기능을 메세지 브로커로 확장 활용.
    * A서버의 유저가 메시지를 보내면 Redis로 Publish하고, Redis가 연결된 모든 서버(B, C 등)로 메시지를 브로드캐스팅하여, 어떤 서버에 붙어있든 실시간 통신이 끊기지 않도록 분산 웹소켓 아키텍처 재설계.
* **결과:** 대규모 접속 시에도 안정적인 채팅이 가능한 확장성(Scalability) 확보.

```java
// 1. 메시지 발행 (Publish)
@MessageMapping(&quot;/chat/message&quot;)
public void sendMessage(ChatMessage message) {
    // DB에 메시지 선 저장 (단일 진실 공급원)
    chatService.saveMessage(message);
    // Redis의 채팅 채널로 메시지 발행
    redisTemplate.convertAndSend(&quot;CHAT_ROOM:&quot; + message.getProjectId(), message);
}

// 2. 메시지 구독 (Subscribe) - RedisMessageListenerContainer 설정
@Service
public class RedisSubscriber implements MessageListener {
    private final SimpMessageSendingOperations messagingTemplate;

    @Override
    public void onMessage(Message message, byte[] pattern) {
        // Redis에서 메시지를 수신하면, 자신(서버)에게 연결된 웹소켓 클라이언트들에게 브로드캐스팅
        ChatMessage chatMessage = objectMapper.readValue(message.getBody(), ChatMessage.class);
        messagingTemplate.convertAndSend(&quot;/sub/chat/room/&quot; + chatMessage.getProjectId(), chatMessage);
    }
}
</code></pre><h4 id="3-외부-apinotion-rate-limit호출-제한-및-트랜잭션-병목-대응">3. 외부 API(Notion) Rate Limit(호출 제한) 및 트랜잭션 병목 대응</h4>
<ul>
<li><strong>문제 상황:</strong> 프로젝트 완료 후 Notion으로 템플릿을 내보낼 때, 다수의 팀이 동시에 요청할 경우 Notion API의 엄격한 호출 횟수 제한(429 Too Many Requests)에 걸려 실패. 게다가 동기식으로 API를 기다리다가 우리 DB 커넥션 풀까지 고갈될 위험 감지.</li>
<li><strong>해결 과정:</strong><ol>
<li><strong>트랜잭션 분리:</strong> 우리 DB에 데이터를 안전하게 커밋(Commit)하여 완전히 저장한 <strong>이후에만</strong> 외부 API(Notion)를 호출하도록 설계하여, 외부 장애가 내부 시스템으로 번지지 않도록 격리.</li>
<li><strong>비동기 큐 및 백오프:</strong> Redis 작업 큐(Task Queue)를 도입해 쏟아지는 노션 생성 요청을 담아두고, 워커(Worker)가 속도를 조절하며 순차 처리. API 에러 발생 시 대기 시간을 점진적으로 늘리는 &#39;지수 백오프(Exponential Backoff)&#39; 적용 및 실패 시 사용자 재시도(Retry) 로직 구현.</li>
</ol>
</li>
<li><strong>결과:</strong> 외부 API 장애로부터 내결함성(Fault Tolerance)을 갖춘 견고한 시스템 완성.<pre><code class="language-java">// 1. DB 트랜잭션과 외부 API 분리 (@TransactionalEventListener 활용)
@Service
public class ProjectService {
  @Transactional
  public void finishProject(Long projectId) {
      projectRepository.updateStatus(projectId, &quot;COMPLETED&quot;);
      // DB 커밋이 완료된 후 이벤트 발행
      eventPublisher.publishEvent(new ProjectCompletedEvent(projectId));
  }
}
</code></pre>
</li>
</ul>
<p>// 2. 이벤트 리스너에서 비동기로 Notion API 호출 및 재시도 로직 적용
@Component
public class NotionApiEventListener {</p>
<pre><code>// 트랜잭션이 성공적으로 커밋된 후에만 실행됨
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
@Async // 비동기 워커로 넘김 (API 병목 방지)
// 429 에러 발생 시 1초, 2초, 4초 대기하며 최대 3번 재시도
@Retryable(value = {RateLimitException.class}, maxAttempts = 3, backoff = @Backoff(delay = 1000, multiplier = 2))
public void handleProjectCompletedEvent(ProjectCompletedEvent event) {
    notionApiClient.exportToNotion(event.getProjectId());
}

// 최종 실패 시 처리 (Fallback)
@Recover
public void recoverNotionExport(RateLimitException e, ProjectCompletedEvent event) {
    log.error(&quot;Notion API 최대 재시도 횟수 초과. 프로젝트 ID: {}&quot;, event.getProjectId());
    // DB에 상태를 &#39;FAILED&#39;로 기록하고 사용자에게 &#39;재시도 버튼&#39; 노출 유도
    projectRepository.updateExportStatus(event.getProjectId(), &quot;FAILED&quot;);
}</code></pre><p>}</p>
<pre><code>#### 4. AI 환각(Hallucination) 및 비정형 JSON 응답에 대한 서버 생존성 확보
* **문제 상황:** 프롬프트로 JSON 형식을 강제했음에도, AI가 가끔 규격에 맞지 않는 엉뚱한 응답을 보내거나 정의되지 않은 Key를 던질 때 백엔드 서버에서 파싱 에러(Mapping Exception)가 발생하며 프로세스가 중단됨.
* **해결 과정:**
    * **방어적 프로그래밍과 Fallback:** Spring Boot 단에서 철저한 객체 검증(DTO Validation) 및 Try-Catch 예외 처리 적용. 에러 포착 시 서버를 멈추지 않고(Graceful Degradation), 사용자에게 알림을 보낸 뒤 **&#39;가장 최근에 검증된 성공 데이터(Last Known Good State)&#39;를 DB에 유지(Fallback)**하여 데이터 유실 방지.
* **결과:** AI의 실수에도 시스템이 무너지지 않고 사용자 데이터를 100% 보호하는 안전장치 마련.

```java
@Service
public class AiResponseHandler {

    // JSON에 정의되지 않은 필드가 들어와도 무시하도록 ObjectMapper 설정
    private final ObjectMapper objectMapper = new ObjectMapper()
            .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

    @Transactional
    public void processAiResponse(Long projectId, String aiRawResponse) {
        ProjectSummary currentSummary = summaryRepository.findByProjectId(projectId);

        try {
            // AI 응답 파싱 시도
            AiSummaryDto dto = objectMapper.readValue(aiRawResponse, AiSummaryDto.class);

            // 검증 성공 시에만 데이터 업데이트
            currentSummary.updateContent(dto.getContent());

        } catch (JsonProcessingException e) {
            // 파싱 실패 시 서버를 죽이지 않고 로그만 남김 (Graceful Degradation)
            log.error(&quot;AI 응답 JSON 파싱 실패. Raw Data: {}&quot;, aiRawResponse, e);

            // TODO: 슬랙 등 모니터링 채널로 알림 발송

            // 기존 데이터(currentSummary)는 롤백하지 않고 그대로 유지됨 (안전한 Fallback)
        }
    }
}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[ACC 핸즈온세션3]]></title>
            <link>https://velog.io/@cse23_ewha/ACC-%ED%95%B8%EC%A6%88%EC%98%A8%EC%84%B8%EC%85%983</link>
            <guid>https://velog.io/@cse23_ewha/ACC-%ED%95%B8%EC%A6%88%EC%98%A8%EC%84%B8%EC%85%983</guid>
            <pubDate>Tue, 21 Apr 2026 12:13:08 GMT</pubDate>
            <description><![CDATA[<h3 id="데이터베이스db-및-dbms">데이터베이스(DB) 및 DBMS</h3>
<ul>
<li><strong>데이터베이스(DB)</strong> 
   구조화된 정보 또는 데이터의 조직화된 모음이며, 일반적으로 DBMS에 의해 제어됨</li>
<li><strong>관계형 데이터베이스(Relational DB)</strong> 
   구조화된 데이터를 테이블 형태로 저장
   엄격한 스키마를 따르며 SQL(Structured Query Language)을 사용하여 데이터를 조작</li>
<li><strong>비관계형 데이터베이스(Non-Relational DB)</strong>
   비정형 데이터를 저장하며 고정된 스키마가 없음
   대량의 분산 데이터 저장 및 다양한 형태의 데이터를 빠르게 처리하는 데 적합함</li>
</ul>
<h3 id="rds의-배포-방식-비교">RDS의 배포 방식 비교</h3>
<ul>
<li><strong>On-Premise:</strong> 사용자가 직접 물리적 서버를 구축하고 데이터베이스를 설치 및 관리</li>
<li><strong>AWS EC2:</strong> AWS 가상 서버(EC2) 위에 사용자가 직접 DB 소프트웨어를 설치하고 운영 체제부터 DB까지 관리</li>
<li><strong>AWS RDS:</strong> AWS에서 제공하는 <strong>완전 관리형</strong> 관계형 데이터베이스 서비스로, 관리 부담이 가장 낮음</li>
</ul>
<h3 id="aws-rds-relational-database-service">AWS RDS (Relational Database Service)</h3>
<ul>
<li><strong>개요:</strong> AWS 클라우드에서 관계형 데이터베이스를 쉽게 설치, 운영 및 확장할 수 있도록 지원하는 웹 서비스</li>
<li><strong>주요 장점:</strong><ul>
<li><strong>간편한 관리:</strong> 패치, 백업 등 운영 자동화</li>
<li><strong>가용성 및 안정성:</strong> 자동 백업 및 스냅샷 기능을 제공하며 Multi-AZ를 통한 장애 복구 지원</li>
<li><strong>보안성:</strong> AWS 네트워크 보안 기능(VPC) 및 암호화 적용 가능</li>
<li><strong>확장성:</strong> Read Replica를 통한 읽기 성능 확장 및 스토리지 유연성 확보</li>
<li><strong>비용 효율성:</strong> 사용한 만큼만 비용을 지불하는 구조</li>
</ul>
</li>
</ul>
<h3 id="가용성-및-성능-확장-기술">가용성 및 성능 확장 기술</h3>
<ul>
<li>Multi-AZ (다중 가용 영역):<ul>
<li>다른 가용 영역(AZ)에 데이터베이스 복사본(Standby)을 생성하고 데이터를 동기화함</li>
<li>장애 감지 시 자동으로 대기 인스턴스로 대체(Failover)되어 서비스 연속성을 보장함</li>
</ul>
</li>
<li>Read Replica (읽기 전용 복제본):<ul>
<li>읽기(Read) 쿼리의 성능 향상과 부하 분산을 목적으로 생성함</li>
<li>주 인스턴스로부터 비동기 방식으로 데이터를 복제함</li>
<li>읽기 중심 워크로드에서 Scale-Out을 통해 처리량을 향상시킴</li>
</ul>
</li>
</ul>
<p>SAA (Solutions Architect Associate) 실무 사례</p>
<ul>
<li>문제 상황: RDS 사용 중 통계용 데이터를 읽어올 때 성능 저하가 발생하는 경우</li>
<li>해결책: RDS Read Replica를 생성하여 운영 DB의 부하를 줄이고, 읽기 전용 쿼리를 복제본에서 처리하도록 구성!!</li>
</ul>
<h3 id="sql-및-관계형-데이터베이스rdbms">SQL 및 관계형 데이터베이스(RDBMS)</h3>
<ul>
<li>관계형 데이터베이스에서 데이터를 저장, 조회, 수정, 삭제하기 위해 사용하는 언어</li>
<li><strong>특징:</strong><ul>
<li>정해진 구조(Schema)에 따라 테이블에 데이터를 저장</li>
<li>행(Row)과 열(Column)이 있는 표 형태의 구조를 가짐</li>
</ul>
</li>
<li>Oracle, MySQL, PostgreSQL 등</li>
</ul>
<h3 id="nosql-및-비관계형-데이터베이스">NoSQL 및 비관계형 데이터베이스</h3>
<ul>
<li>비관계형 데이터베이스 환경에서 데이터를 관리하는 방식임</li>
<li>특징:<ul>
<li>정형화되지 않은 유연한 구조를 사용하여 가용성과 확장성이 높음</li>
<li>고성능 처리에 최적화되어 있으며 SNS(Instagram, Facebook) 등에서 주로 사용됨</li>
</ul>
</li>
<li>MongoDB, AWS DynamoDB 등</li>
</ul>
<h3 id="aws-dynamodb-개요">AWS DynamoDB 개요</h3>
<ul>
<li>AWS에서 제공하는 서버리스 기반의 완전관리형 NoSQL 데이터베이스 서비스</li>
<li>주요 특성:<ul>
<li><strong>완전관리형:</strong> 장비 운영부터 솔루션 설치 및 운영까지 모든 인프라를 AWS에서 담당</li>
<li><strong>고성능:</strong> 10ms 미만의 지연 시간으로 데이터를 처리할 만큼 매우 빠름</li>
<li><strong>내구성:</strong> SSD에 저장되며 리전 내 여러 가용 영역(AZ)에 걸쳐 자동 복제</li>
<li><strong>Auto-Scaling:</strong> 요청량에 따라 테이블의 읽기/쓰기 용량을 자동으로 조절</li>
</ul>
</li>
</ul>
<h3 id="dynamodb-핵심-구성-요소">DynamoDB 핵심 구성 요소</h3>
<ul>
<li><strong>테이블(Tables)</strong>
데이터의 집합체 (예: People 테이블, Cars 테이블)</li>
<li><strong>항목(Items)</strong> 
테이블을 구성하는 개별 데이터 단위 (RDBMS의 행에 해당)</li>
<li><strong>속성(Attributes)</strong>
항목을 구성하는 세부 데이터 요소 (예: ID, 이름, 주소 등)</li>
<li><strong>유연한 스키마</strong> 
기본 키(PK)를 제외하고는 속성이나 데이터 형식을 미리 정의할 필요가 없으며, 각 항목은 고유하거나 중첩된 속성을 가질 수 있음</li>
</ul>
<h3 id="dynamodb의-키key-구조">DynamoDB의 키(Key) 구조</h3>
<ul>
<li><strong>Partition Key (파티션 키):</strong><ul>
<li>RDBMS의 Primary Key와 유사한 역할로 테이블 내 유일한 값이어야 함</li>
<li>데이터가 저장될 물리적 공간(Partition)을 결정하며 일치 검색(Equal)만 지원</li>
</ul>
</li>
<li><strong>Sort Key (정렬 키):</strong><ul>
<li>같은 파티션 내에서 데이터를 정렬하는 기준</li>
<li>일치, 부등호, 포함 등 범위 지정 검색을 지원하여 정교한 쿼리를 가능하게 함</li>
</ul>
</li>
</ul>
<h3 id="lambda를-활용한-dynamodb-최적화">Lambda를 활용한 DynamoDB 최적화</h3>
<ul>
<li><strong>Batch 작성 지원</strong>
<code>BatchWriteItem API</code>를 통해 한 번에 최대 25개의 항목을 삽입할 수 있어 데이터 추가 효율이 높음</li>
<li><strong>자동화 운용</strong> 
CloudWatch Events와 연동하여 특정 주기마다 Lambda 함수를 실행, 정기적인 데이터 처리가 가능</li>
<li><strong>Join 기능 구현</strong> 
DynamoDB는 자체적인 Join을 지원하지 않으므로, 복잡한 데이터 결합이 필요한 경우 Lambda를 통해 로직으로 구현</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[ACC 핸즈온세션2]]></title>
            <link>https://velog.io/@cse23_ewha/ACC-%ED%95%B8%EC%A6%88%EC%98%A8%EC%84%B8%EC%85%982</link>
            <guid>https://velog.io/@cse23_ewha/ACC-%ED%95%B8%EC%A6%88%EC%98%A8%EC%84%B8%EC%85%982</guid>
            <pubDate>Tue, 07 Apr 2026 13:11:31 GMT</pubDate>
            <description><![CDATA[<h3 id="1-클라우드-스토리지-cloud-storage-개요"><strong>1. 클라우드 스토리지 (Cloud Storage) 개요</strong></h3>
<ul>
<li><strong>정의:</strong> 클라우드 컴퓨팅 제공업체를 통해 데이터와 파일을 인터넷에 저장하는 모델. 사용자는 퍼블릭 인터넷 또는 전용 프라이빗 네트워크 연결을 통해 스토리지에 액세스 가능.</li>
<li><strong>클라우드 사용 시 이점:</strong> 비용 효율성, 민첩성 향상, 더 빠른 배포, 효율적인 데이터 관리, 확장성</li>
<li><strong>스토리지 종류:</strong> File Storage, Block Storage, Object Storage (사용자가 데이터를 어떻게 읽고 쓰는지 인터페이스 관점에서 구분)</li>
</ul>
<h3 id="2-스토리지-유형별-상세-분석"><strong>2. 스토리지 유형별 상세 분석</strong></h3>
<h4 id="①-block-storage-예-amazon-ebs"><strong>① Block Storage (예: Amazon EBS)</strong></h4>
<ul>
<li><strong>역할:</strong> 물리적 하드웨어(HDD, SSD)와 비슷한 역할을 수행하며 이를 흉내 냄. 가상머신(EC2)은 이를 자신의 하드디스크라고 생각함</li>
<li><strong>특징:</strong> 스토리지 어디에 저장되어 있는지 등의 세부 작업은 직접 하지 못함(물리적 디바이스 관리는 OS가 담당)</li>
<li><strong>데이터 처리:</strong> 데이터를 <strong>Block 형태</strong>로 Read/Write 함. 빠른 속도를 위해 각 블록에 <strong>고유 식별자(ID#)</strong>를 부여</li>
<li><strong>기타:</strong> 백업 기능을 제공하기도 하며 SAN(Storage Area Network) 역할을 수행</li>
</ul>
<h4 id="②-file-storage-예-amazon-efs"><strong>② File Storage (예: Amazon EFS)</strong></h4>
<ul>
<li><strong>역할:</strong> 데이터를 파일 단위의 계층 구조로 저장함. 애플리케이션에 가장 널리 사용되는 유형</li>
<li><strong>특징:</strong> 데이터를 파일 단위로 다루며 직접 데이터를 관리함(서버 기능도 수행). <strong>NFS, NAS 기술</strong> 사용 가능함. 로컬 하드 드라이브와 유사한 접근 방식을 가짐</li>
<li><strong>사용 시나리오:</strong> 블록 스토리지에 &#39;서버&#39;가 추가된 느낌으로, 여러 서버나 사용자가 동일 파일 시스템에 동시 접근하거나 네트워크를 통한 파일 공유가 필요할 때 사용</li>
</ul>
<h4 id="③-object-storage-예-amazon-s3"><strong>③ Object Storage (예: Amazon S3)</strong></h4>
<ul>
<li><strong>역할:</strong> 대용량 미디어 파일, 이미지, 백업 등 <strong>비정형 데이터</strong> 저장을 위한 스토리지</li>
<li><strong>특징:</strong> 데이터를 <strong>오브젝트(파일+메타데이터)</strong> 단위로 다룸. 전송된 형식 그대로 객체 데이터로 저장하며, 사용자가 직접 메타데이터 지정 가능</li>
<li><strong>접근 방식:</strong> 객체는 &#39;보안 버킷&#39;이라는 공간에 저장되며, <strong>HTTP 프로토콜 기반 REST API 호출</strong>을 통해 접근<ul>
<li><em>REST API란? HTTP의 장점을 살려 자원을 이름으로 구분하고 상태를 주고받는 API 방식.</em></li>
</ul>
</li>
<li><strong>사용 시나리오:</strong> 정적 웹 콘텐츠 호스팅, 전 세계 데이터센터 분산, 저렴한 비용으로 장기 보관 시 유리</li>
</ul>
<h3 id="3-amazon-s3-simple-storage-service-심화"><strong>3. Amazon S3 (Simple Storage Service) 심화</strong></h3>
<ul>
<li><strong>주요 특징:</strong> 확장성이 뛰어나 저장 용량을 신경 쓸 필요가 없으며, 버전 관리/암호화/복제 기능으로 데이터를 보호. 사용한 만큼만 비용을 지불</li>
<li><strong>구성 요소:</strong><ul>
<li><strong>버킷(Bucket):</strong> 최상위 디렉토리(컨테이너). 폴더와 유사함. 무제한 객체 저장 가능. 계정당 최대 100개 생성 가능. 전 세계에서 유일한 이름을 가져야 함</li>
<li><strong>객체(Object):</strong> 버킷 안의 파일. 크기는 최대 5TB. <strong>Key(이름)와 버전 ID</strong>로 식별됨.</li>
</ul>
</li>
<li><strong>객체의 상세 구성:</strong> Key(이름), Value(데이터), Version ID, Metadata(수정일, 타입, 소유자, 사이즈), 태그, 액세스 제어 정보.</li>
<li><strong>태그 활용 보안:</strong> * <code>s3:ExistingObjectTag</code>: 기존 객체에 특정 태그가 있는지 확인.<ul>
<li><code>s3:RequestObjectTagKeys</code>: 허용할 태그 키 제한</li>
<li><code>s3:RequestObjectTag</code>: 허용할 태그 키 및 값 제한</li>
<li><em>예시: environment: production 태그가 있는 객체만 읽기 허용 가능.</em></li>
</ul>
</li>
<li><strong>URL 형식 예시:</strong> <code>https://awsinaction.s3.ap-southeast-2.amazonaws.com/img/cat.png</code><ul>
<li>버킷명: <code>awsinaction</code> / 리전: <code>ap-southeast-2</code> / 키네임: <code>img/cat.png</code></li>
</ul>
</li>
</ul>
<h3 id="4-s3-보안-및-관리-기능"><strong>4. S3 보안 및 관리 기능</strong></h3>
<h4 id="데이터-암호화"><strong>데이터 암호화</strong></h4>
<ul>
<li><strong>서버 측 암호화 (Server-Side Encryption):</strong> AWS가 자동으로 보호. 업로드 시 암호화, 다운로드 시 복호화<ul>
<li>종류: SSE-S3(기본), SSE-KMS, DSSE-KMS, SSE-C. </li>
<li>요청 헤더 예시: <code>&quot;x-amz-server-side-encryption&quot;: &quot;AES256&quot;</code></li>
</ul>
</li>
<li><strong>클라이언트 측 암호화:</strong> 내 로컬 컴퓨터에서 암호화 후 업로드. 제3자에게 노출을 원천 방지하나 관리 복잡성이 높음</li>
</ul>
<h4 id="액세스-제어-access-control"><strong>액세스 제어 (Access Control)</strong></h4>
<ul>
<li>기본값은 <strong>Private</strong>임 (통째로 퍼블릭 설정 안 됨)</li>
<li><strong>IAM:</strong> 사용자/역할 기준. 내 계정 내 유저가 무엇을 할 수 있는지 제어</li>
<li><strong>ACL:</strong> 계정 단위 기준. 다른 AWS 계정에 대해 읽기/쓰기 권한 부여</li>
<li><strong>버킷 정책:</strong> 버킷/객체 기준. 단일 버킷 내 모든 객체 권한을 세부 구성(IP나 도메인 제한 가능).</li>
<li><strong>CORS:</strong> 출처가 다른 서버 간의 리소스 공유를 허용(Access-Control-Allow-Origin 헤더 사용).</li>
<li><strong>Pre-signed URL:</strong> 임시 URL 발급. 특정 시간 동안만 권한 없는 사용자에게 액세스 부여. 생성자가 유효한 권한을 보유해야 함.</li>
</ul>
<h4 id="스토리지-관리"><strong>스토리지 관리</strong></h4>
<p><img src="https://velog.velcdn.com/images/cse23_ewha/post/787e9672-7c31-49de-9585-d34369b2a16d/image.png" alt=""></p>
<ul>
<li><strong>버전 관리:</strong> 업데이트 시 버전이 추가됨. 실수로 삭제/덮어쓰기 시 복원 가능. 한 번 활성화 시 비활성화 불가.</li>
<li><strong>객체 복제:</strong> 동일 리전(SRR) 또는 타 리전(CRR)에 비동기 자동 복제. 배치 복제는 기존 객체 복제 시 사용.</li>
<li><strong>스토리지 클래스:</strong> 요구사항에 맞는 클래스 선택 가능.</li>
<li><strong>수명 주기(Lifecycle):</strong><ul>
<li><strong>전환 작업:</strong> 일정 기간 후 저렴한 클래스로 이동 (30일 뒤 IA, 60일 뒤 Glacier 등).</li>
<li><strong>만료 작업:</strong> 일정 기간 후 객체 자동 삭제.</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/cse23_ewha/post/26e13486-8fcc-4b4c-98d8-e37623ce0745/image.png" alt=""></p>
<h3 id="5-amazon-cloudfront-cdn"><strong>5. Amazon CloudFront (CDN)</strong></h3>
<ul>
<li><strong>정의:</strong> 글로벌 콘텐츠 전송 네트워크 서비스. 짧은 지연 시간과 빠른 속도로 데이터 전송. S3 앞단에서 보안 기능 제공</li>
<li><strong>CDN 원리:</strong> 여러 서버에 데이터를 분산 캐싱하여 사용자에게 가장 가까운 서버에서 배포</li>
<li><strong>주요 인프라:</strong><ul>
<li><strong>Edge Location:</strong> 원본 서버의 데이터를 캐싱하여 사용자에게 제공하는 지리적 거점</li>
<li><strong>Regional Edge Caches(REC):</strong> 오리진과 엣지 사이의 캐시 계층. 엣지에 없으면 REC를 먼저 확인하여 오리진 부하를 줄임.</li>
</ul>
</li>
<li><strong>동작 방식:</strong> 요청 → 엣지 확인 → 캐시 있으면 응답 / 없으면 오리진 포워딩 → 엣지 캐싱 후 응답.</li>
<li><strong>Origin Server:</strong> S3(정적) 및 EC2/ELB(동적) 연결 가능. 동적 콘텐츠는 TTL 동안 수정사항이 안 보일 수 있으므로 주의 필요.</li>
<li><strong>보안 및 전송 최적화:</strong><ul>
<li><strong>HTTPS 지원:</strong> 오리진이 지원 안 해도 CloudFront에서 설정 가능.</li>
<li><strong>지리적 제한:</strong> 특정 지역 접근 제한 가능.</li>
<li><strong>Signed URL:</strong> 특정 파일 하나에 대해 허용된 사용자만 접근 허용 (유료 결제 등)</li>
<li><strong>Signed Cookie:</strong> 다수의 파일에 대한 액세스 제공 (로그인한 유료 회원 등)</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[ACC 과제2 - AWS Storage 실습 가이드]]></title>
            <link>https://velog.io/@cse23_ewha/ACC-%EA%B3%BC%EC%A0%9C2-AWS-Storage-%EC%8B%A4%EC%8A%B5-%EA%B0%80%EC%9D%B4%EB%93%9C</link>
            <guid>https://velog.io/@cse23_ewha/ACC-%EA%B3%BC%EC%A0%9C2-AWS-Storage-%EC%8B%A4%EC%8A%B5-%EA%B0%80%EC%9D%B4%EB%93%9C</guid>
            <pubDate>Tue, 07 Apr 2026 12:58:05 GMT</pubDate>
            <description><![CDATA[<h3 id="1-사전-준비"><strong>1. 사전 준비</strong></h3>
<ul>
<li><strong>빌드 파일 준비:</strong> 배포할 웹 프로젝트의 빌드 파일을 다운로드하고 압축을 해제</li>
</ul>
<h3 id="2-amazon-s3-버킷-생성-및-설정"><strong>2. Amazon S3 버킷 생성 및 설정</strong></h3>
<ol>
<li><strong>S3 콘솔 접속:</strong> [버킷 만들기] 버튼을 클릭</li>
<li><strong>리전 및 이름 설정:</strong><ul>
<li><strong>리전:</strong> 사용자 데이터 지연 시간 단축과 비용 절감을 위해 지리적으로 가까운 리전(예: 서울)을 선택</li>
<li><strong>이름:</strong> 전역적으로 고유한 이름을 지정 (중복 불가)</li>
</ul>
</li>
<li><strong>생성 완료:</strong> 그 외 설정은 기본값을 유지하고 [버킷 만들기]를 완료</li>
</ol>
<h3 id="3-정적-파일-업로드"><strong>3. 정적 파일 업로드</strong></h3>
<ol>
<li><strong>파일 업로드:</strong> [업로드] 버튼을 클릭</li>
<li><strong>주의사항:</strong> 빌드 폴더 자체를 올리는 것이 아니라, <strong>빌드 폴더 내부에 있는 개별 파일 및 폴더들</strong>을 선택하여 업로드 (예: <code>index.html</code>, <code>static/</code> 등)</li>
</ol>
<h3 id="4-s3-정적-웹사이트-호스팅-활성화"><strong>4. S3 정적 웹사이트 호스팅 활성화</strong></h3>
<ol>
<li><strong>호스팅 설정:</strong> 버킷 상단 <strong>[속성]</strong> 탭 → 최하단 <strong>[정적 웹사이트 호스팅]</strong> 편집 클릭</li>
<li><strong>활성화:</strong> * <strong>인덱스 문서:</strong> <code>index.html</code> 입력<ul>
<li><strong>오류 문서:</strong> <code>index.html</code> 입력 (SPA 라우팅 대응)</li>
</ul>
</li>
<li>저장 후 생성된 <strong>엔드포인트</strong>로 접속<ul>
<li><strong>403 Forbidden</strong> 에러 발생 (정상) </li>
<li>보안을 위해 외부 접근이 막혀 있으므로 <strong>CloudFront</strong>를 사용해야함</li>
</ul>
</li>
</ol>
<h3 id="5-cloudfront-배포-생성-cdn-설정"><strong>5. CloudFront 배포 생성 (CDN 설정)</strong></h3>
<ol>
<li><strong>CloudFront 콘솔 접속:</strong> [배포 생성] 버튼을 클릭</li>
<li><strong>원본(Origin) 설정:</strong><ul>
<li><strong>오리진 도메인:</strong> 앞서 생성한 S3 버킷을 선택</li>
<li><strong>원본 액세스:</strong> <strong>원본 액세스 제어 설정(OAC, 권장)</strong>을 선택</li>
<li><strong>OAC 생성:</strong> [Create new OAC] 버튼 클릭 → 기본 설정 유지 후 [Create] 클릭</li>
</ul>
</li>
<li><strong>보안 및 기타 설정:</strong><ul>
<li><strong>WAF:</strong> 보안 보호 비활성화 선택 (실습용이니)</li>
<li><strong>기본값 루트 객체:</strong> <code>index.html</code> 입력</li>
</ul>
</li>
<li><strong>배포 완료:</strong> [배포 생성]을 누르면 도메인이 생성됌<ul>
<li>하지만 여전히 접속 안 됨! CloudFront가 S3에 접근할 권한이 아직 없기 때문..</li>
</ul>
</li>
</ol>
<h3 id="6-s3-버킷-정책-수정-권한-부여"><strong>6. S3 버킷 정책 수정 (권한 부여)</strong></h3>
<ol>
<li><strong>정책 복사:</strong> * CloudFront 콘솔 → 생성한 배포 선택 → <strong>[원본]</strong> 탭 → 원본 선택 후 <strong>[편집]</strong><ul>
<li><strong>[정책 복사]</strong> 버튼을 클릭하여 CloudFront가 생성해준 정책 문구(JSON)를 복사</li>
</ul>
</li>
<li><strong>S3에 정책 적용:</strong><ul>
<li>S3 콘솔 접속 → 해당 버킷 선택 → <strong>[권한]</strong> 탭</li>
<li><strong>[버킷 정책]</strong> 편집 버튼 클릭 → 복사한 내용을 붙여넣고 저장</li>
</ul>
</li>
<li>이제 CloudFront 배포 도메인으로 접속하면 웹사이트가 정상적으로 나타남</li>
</ol>
<hr>
<h4 id="왜-이렇게-복잡하게-하니"><strong>왜 이렇게 복잡하게 하니?</strong></h4>
<ul>
<li><strong>S3 단독 호스팅:</strong> 보안에 취약하며 속도가 느릴 수 있음</li>
<li><strong>CloudFront 병행:</strong> </li>
</ul>
<ol>
<li>보안 - S3를 외부에 직접 공개하지 않고 CloudFront만 통하게 하여 데이터를 보호 (OAC 설정의 이유)</li>
<li>속도 - 전 세계 Edge Location에 콘텐츠를 캐싱하여 사용자에게 더 빠르게 전달</li>
<li>비용 - 캐싱을 통해 S3 직접 요청 횟수를 줄여 비용을 최적화</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[ACC 과제1 - AWS VPC 기본 실습 가이드]]></title>
            <link>https://velog.io/@cse23_ewha/ACC-%EA%B3%BC%EC%A0%9C1-AWS-VPC-%EA%B8%B0%EB%B3%B8-%EC%8B%A4%EC%8A%B5-%EA%B0%80%EC%9D%B4%EB%93%9C</link>
            <guid>https://velog.io/@cse23_ewha/ACC-%EA%B3%BC%EC%A0%9C1-AWS-VPC-%EA%B8%B0%EB%B3%B8-%EC%8B%A4%EC%8A%B5-%EA%B0%80%EC%9D%B4%EB%93%9C</guid>
            <pubDate>Wed, 01 Apr 2026 13:19:03 GMT</pubDate>
            <description><![CDATA[<h4 id="step-1-vpc-생성-및-설정"><strong>Step 1. VPC 생성 및 설정</strong></h4>
<ul>
<li><strong>작업:</strong> 리전 선택(서울) → VPC 생성 (이름 태그 설정)</li>
<li><strong>설정:</strong> DNS 확인 및 DNS 호스트이름 활성화 체크</li>
<li>❓ IPv4 CIDR를 왜 수동으로 입력?<ul>
<li>VPC는 나만의 독립적인 가상 네트워크 영토를 선언하는 것. 수동으로 입력하는 이유는 내가 관리할 네트워크의 크기와 주소 범위를 직접 결정하기 위해서!!</li>
<li>특히 기업 환경에서는 다른 VPC나 회사 내부망(On-premise)과 연결할 때 주소가 겹치면 통신이 불가능함. 따라서 나중에 확장성이나 연결성을 고려하여 겹치지 않는 적절한 크기(예: <code>/16</code>)를 직접 지정해야하기 때문에 수동으로 입력</li>
</ul>
</li>
</ul>
<h4 id="step-2-public-및-private-서브넷-생성"><strong>Step 2. Public 및 Private 서브넷 생성</strong></h4>
<ul>
<li><strong>VPC 범위:</strong> <code>10.0.0.0/16</code> </li>
<li><strong>서브넷 구성:</strong> 2a, 2c 가용 영역에 각각 퍼블릭/프라이빗 배치</li>
</ul>
<ol>
<li>public subnet : 10.0.1.0/24 -&gt; ap-northeast-2a</li>
<li>private subnet : 10.0.2.0/25-&gt; ap-northeast-2a</li>
<li>public subnet : 10.0.3.0/24  -&gt; ap-northeast-2c</li>
<li>private subnet : 10.0.4.0/25 -&gt; ap-northeast-2c</li>
</ol>
<ul>
<li>❓ 왜 Public이 Private보다 더 많은 주소를 할당?<ul>
<li>실무에서는 사실 반대인 경우가 더 많긴함. 보통 DB나 내부 로직이 돌아가는 Private 영역에 서버가 훨씬 많이 들어가기 때문 , 하지만 학습용 실습이나 특정 아키텍처에서는 외부와 소통하는 서비스(웹 서버, 로드밸런서, NAT 게이트웨이 등)가 위치하는 Public 영역을 넉넉하게 잡음</li>
</ul>
</li>
</ul>
<ul>
<li>주소 할당량은 정답이 있는 게 아니라, 해당 서브넷에 얼마나 많은 리소스를 배치할 것인가에 따른 설계자의 의도.. 실습에서는 Public 통신 테스트를 위해 좀 더 넉넉하게 할당한 것</li>
</ul>
<h4 id="step-3-public-서브넷-자동-할당-ip-설정"><strong>Step 3. Public 서브넷 자동 할당 IP 설정</strong></h4>
<ul>
<li>Public 서브넷 2개에 대해 &#39;퍼블릭 IPv4 주소 자동 할당&#39; 활성화</li>
<li>이 설정을 켜야만 이 서브넷에 생성되는 EC2들이 부팅될 때 자동으로 인터넷 주소(Public IP)를 부여받아 우리가 밖에서 접속할 수 있게 됌..</li>
</ul>
<h4 id="step-4-ec2-인스턴스-생성"><strong>Step 4. EC2 인스턴스 생성</strong></h4>
<ul>
<li>AMI: ubuntu</li>
<li>인스턴스 유형: t2.micro</li>
<li>키페어: 생성 또는 있는 거 사용</li>
<li>Public EC2: 보안그룹(SSH, HTTP), 퍼블릭 IP 활성화 (외부 접속용)</li>
<li>Private EC2: 보안그룹(SSH, HTTP), 퍼블릭 IP 비활성화 (내부망 전용)</li>
</ul>
<h4 id="step-5-인터넷-게이트웨이igw-생성-및-연결"><strong>Step 5. 인터넷 게이트웨이(IGW) 생성 및 연결</strong></h4>
<ul>
<li>IGW 생성 후 내가 만든 VPC에 연결</li>
<li>VPC라는 닫힌 성벽에 바깥세상으로 나가는 문을 하나 다는 것과 같음. 이 문이 없으면 아무리 Public IP가 있어도 인터넷 통신이 불가능.</li>
</ul>
<h4 id="step-6-라우팅-테이블route-table-설정"><strong>Step 6. 라우팅 테이블(Route Table) 설정</strong></h4>
<ul>
<li><strong>작업:</strong> Public용 라우팅 테이블 생성 → Public 서브넷 삽입 → 인터넷 게이트웨이 추가</li>
</ul>
<ul>
<li><p><strong>❓라우팅 테이블에 Public 서브넷을 왜 연결?</strong></p>
<ul>
<li>라우팅 테이블은 이정표임. 서브넷을 이 테이블에 연결하는 이유는 이 서브넷 안에 있는 모든 리소스는 이 이정표를 보고 길을 찾으라고 명령하는것.</li>
<li>연결하지 않으면 서브넷은 기본(Default) 라우팅 테이블을 따르는데, 그러면 외부로 나가는 길을 모를수도 있음. 따라서 너희(Public Subnet)는 인터넷 게이트웨이로 가는 길이 적힌 이 전용 이정표를 봐!라고 명시해 주는 것</li>
</ul>
</li>
<li><p><strong>❓인터넷 게이트웨이 왜 추가</strong></p>
<ul>
<li>이정표에 바깥세상(<code>0.0.0.0/0</code>)으로 가고 싶으면 아까 만든 그 성문(IGW)으로 가라는 내용을 적어 넣는 작업. 이 규칙이 있어야 비로소 진짜 퍼블릭 서브넷이 됌.</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[ACC_핸즈온세션1]]></title>
            <link>https://velog.io/@cse23_ewha/ACC%ED%95%B8%EC%A6%88%EC%98%A8%EC%84%B8%EC%85%981</link>
            <guid>https://velog.io/@cse23_ewha/ACC%ED%95%B8%EC%A6%88%EC%98%A8%EC%84%B8%EC%85%981</guid>
            <pubDate>Tue, 31 Mar 2026 10:14:17 GMT</pubDate>
            <description><![CDATA[<h2 id="ip-및-dns-기초">IP 및 DNS 기초</h2>
<h4 id="ip-internet-protocol">IP (Internet Protocol)</h4>
<p>인터넷에 연결된 장치를 식별하는 고유 주소</p>
<ul>
<li>공인 IP: 인터넷에서 사용하는 주소</li>
<li>사설 IP: 내부 네트워크에서 사용하는 주소</li>
<li>고정 IP: 변하지 않는 주소</li>
<li>유동 IP: 바뀔 수 있는 주소</li>
</ul>
<h4 id="dns-domain-name-system">DNS (Domain Name System)</h4>
<p>도메인 이름과 IP 주소를 서로 변환해주는 분산형 데이터베이스</p>
<ul>
<li>도메인 → IP 변환: Forward DNS</li>
<li>IP → 도메인 변환: Reverse DNS</li>
<li>계층 구조: Root DNS → TLD DNS → Authoritative DNS 순으로 질의</li>
</ul>
<p>DNS는 하나의 서버가 모든 정보를 가지는 구조가 아니라, 각 서버가 일부 정보를 나누어 가지는 분산 구조</p>
<ul>
<li><strong>DNS 레코드 타입:</strong><ul>
<li><strong>A:</strong> 도메인을 IPv4 주소에 매핑.</li>
<li><strong>CNAME:</strong> 도메인을 다른 도메인에 매핑.</li>
<li><strong>NS:</strong> 도메인 관리 권한이 있는 네임 서버 지정.</li>
<li><strong>Alias:</strong> Route 53 전용, 도메인을 AWS 리소스에 직접 연결.</li>
</ul>
</li>
</ul>
<h3 id="네트워크-통신-흐름">네트워크 통신 흐름</h3>
<ul>
<li>DHCP(Dynamic Host Configuration Protocol) - 내 컴퓨터에 IP 주소를 자동으로 할당해주는 과정</li>
<li>ARP(Address Resolution Protocol) - IP 주소를 물리적 주소(MAC 주소)로 변환하는 과정</li>
<li>DNS</li>
<li>TCP(Transmission Control Protocol) - 데이터를 안전하게 주고받기 위해 연결하는 과정</li>
<li>HTTP/HTTPS </li>
</ul>
<p>전화기를 설치하고(DHCP), 상대방의 주소를 알아내고(ARP/DNS), 연결 라인을 확보한 뒤(TCP), 대화를 나누는(HTTP) 과정,,</p>
<h2 id="route-53">Route 53</h2>
<p><em>AWS에서 제공하는 DNS 서비스</em></p>
<p>단순히 도메인과 IP를 연결하는 것만이 아니라, 트래픽을 어떤 대상으로 보낼지 결정하는 기능도 함께 제공</p>
<ul>
<li><strong>역할:</strong> DNS + 헬스체크 + 장애 조치(Failover) + 라우팅 정책(GSLB)</li>
<li>주요 라우팅 정책:<ul>
<li><strong>단순:</strong> 표준 DNS 기능.</li>
<li><strong>가중치 기반:</strong> 설정한 비율로 트래픽 분산</li>
<li><strong>지리적 위치/근접:</strong> 사용자 또는 리소스 위치 기반 라우팅</li>
<li><strong>지연 시간:</strong> 응답 속도가 가장 빠른 리전으로 연결</li>
<li><strong>장애 조치:</strong> 대상이 정상일 때만 트래픽 전송</li>
<li><strong>다중 응답:</strong> 무작위로 여러 레코드 응답</li>
</ul>
</li>
</ul>
<p>로드 밸런서와 비슷해 보일 수 있지만 완전히 같지는 않음</p>
<ul>
<li>로드 밸런서: 들어온 트래픽을 여러 서버에 분산</li>
<li>Route 53: DNS 단계에서 어느 대상으로 보낼지 결정 </li>
</ul>
<h2 id="dns-레코드">DNS 레코드</h2>
<p><em>DNS 서버가 요청에 어떻게 응답할지 정의한 정보</em></p>
<p>기본 형식 : <strong>RR(Name, Value, Type, TTL)</strong></p>
<h3 id="주요-레코드">주요 레코드</h3>
<ul>
<li><p><strong>A 레코드</strong>
도메인 이름을 IPv4 주소에 매핑</p>
</li>
<li><p><strong>CNAME</strong>
하나의 도메인 이름을 다른 도메인 이름에 매핑</p>
</li>
<li><p><strong>NS</strong>
해당 도메인을 관리하는 네임서버 정보</p>
</li>
<li><p><strong>Alias</strong>
Route 53 전용 특수 레코드
AWS 리소스와 도메인을 연결할 때 사용</p>
</li>
<li><p><strong>TTL</strong>: DNS 응답을 캐시에 얼마나 오래 저장할지 정하는 값</p>
</li>
<li><p>테스트 시: 짧게 설정 / 운영 시: 보통 300초 이상 권장 </p>
</li>
</ul>
<h2 id="route-53-라우팅-정책">Route 53 라우팅 정책</h2>
<ul>
<li>단순 라우팅 -&gt; 기본 DNS 응답 방식</li>
<li>가중치 기반 라우팅 -&gt; 트래픽 비율 다르게 가능 [ 예) A 서버 80% /  B 서버 20% ]</li>
<li>지리적 위치 기반 라우팅 -&gt; 사용자 위치 기준으로 다른 대상으로 연결</li>
<li>지리적 근접 라우팅 -&gt; 리소스 위치 기준으로 가까운 곳에 연결</li>
<li>지연 시간 기반 라우팅 -&gt; 지연 시간이 가장 낮은 리전으로 연결</li>
<li>장애 조치 라우팅 -&gt; 정상인 대상에만 연결 / 헬스 체크와 함께 사용</li>
<li>다중 응답 라우팅 -&gt; 여러 레코드 중 일부를 무작위로 응답 </li>
</ul>
<h2 id="호스팅-영역">호스팅 영역</h2>
<p>DNS 레코드들을 저장해 두는 공간 -&gt; 특정 도메인에 대해 어떤 레코드를 둘지 관리하는 장소</p>
<ul>
<li>Public Hosted Zone: 인터넷에서 접근 가능</li>
<li>Private Hosted Zone: 같은 VPC 내부에서만 접근 가능 </li>
</ul>
<h2 id="vpc-virtual-private-cloud">VPC (Virtual Private Cloud)</h2>
<p>AWS 안에서 만드는 가상 네트워크
하나의 독립된 네트워크처럼 동작해서 보안, 관리, 확장 측면에서 유리</p>
<ul>
<li>리전 단위로 생성</li>
<li>리전당 기본 5개까지 생성 가능</li>
<li>VPC당 최대 5개의 CIDR 블록 설정 가능</li>
<li>사설 IPv4 범위만 할당 가능</li>
<li>다른 VPC나 외부 네트워크와 CIDR 대역이 겹치면 안 됨 </li>
</ul>
<h2 id="subnet">Subnet</h2>
<p>VPC를 더 작은 네트워크 단위로 나눈 것
VPC 안에서 리소스를 배치하는 IP 범위</p>
<ul>
<li>Public Subnet / Private Subnet</li>
</ul>
<p>특징</p>
<ul>
<li>서브넷은 하나의 AZ에만 속할 수 있음</li>
<li>서브넷의 IP 범위는 반드시 VPC의 CIDR 범위 안에 있어야 함</li>
</ul>
<h4 id="서브넷을-나누는-이유">서브넷을 나누는 이유</h4>
<ul>
<li>보안 격리</li>
<li>IP 주소 효율적 사용</li>
<li>관리 편의성 </li>
</ul>
<h2 id="cidr">CIDR</h2>
<p>IP 주소 범위를 표현하는 표기법</p>
<p>예시 ; <code>192.168.0.0/24</code></p>
<p>의미</p>
<ul>
<li>앞부분: 네트워크 ID (192.168.0.0)</li>
<li>뒷부분: 호스트 ID 범위 - 나머지 8비트(32비트 - 24비트)</li>
<li>서브넷 마스크 (/24): 앞의 24비트를 고정</li>
</ul>
<p>슬래시 뒤의 서브넷 마스크가 클수록 네트워크 ID 부분이 길어지고, 내가 쓸 수 있는 IP(호스트 ID 부분)는 적어짐</p>
<p>IP 주소 = <strong>네트워크 ID + 호스트 ID</strong> </p>
<p>AWS에서도 VPC와 서브넷의 주소 범위를 정할 때 CIDR 사용 </p>
<h2 id="igw-internet-gateway">IGW (Internet Gateway)</h2>
<p>VPC가 인터넷과 통신할 수 있도록 연결하는 게이트웨이</p>
<p>VPC는 기본적으로 외부와 격리된 네트워크라서 인터넷 연결을 위해 IGW가 필요
하지만 IGW만 붙인다고 바로 인터넷이 되는 것은 아님 -&gt; 인터넷 통신이 되려면 같이 필요</p>
<ul>
<li>VPC에 IGW 연결</li>
<li>라우팅 테이블에 인터넷 방향 경로 추가</li>
<li>공인 IP 또는 퍼블릭 접근 조건 충족 </li>
</ul>
<h2 id="route-table">Route Table</h2>
<p>트래픽을 어디로 보낼지 정하는 규칙표?</p>
<p>예를 들어 특정 목적지로 가는 요청을 로컬로 보낼지, IGW로 보낼지, NAT Gateway로 보낼지 결정</p>
<ul>
<li><code>local</code> → 같은 VPC 내부 통신</li>
<li><code>0.0.0.0/0 → IGW</code> → 인터넷 통신</li>
<li><code>0.0.0.0/0 → NAT Gateway</code> → 프라이빗 서브넷의 외부 통신</li>
</ul>
<p>하나의 라우팅 테이블을 여러 서브넷에 연결할 수도 있음 </p>
<h2 id="public-subnet--private-subnet">Public Subnet / Private Subnet</h2>
<h3 id="public-subnet">Public Subnet</h3>
<p>인터넷과 직접 연결 가능한 서브넷</p>
<ul>
<li>IGW 연결</li>
<li>라우팅 테이블에 인터넷 경로 존재</li>
<li>공인 IP 부여 가능</li>
<li>Bastion Host</li>
<li>NAT Gateway</li>
<li>Public EC2</li>
</ul>
<h3 id="private-subnet">Private Subnet</h3>
<p>인터넷에서 직접 접근 불가능한 서브넷</p>
<ul>
<li>DB</li>
<li>내부 서버</li>
<li>외부에 노출되면 안 되는 애플리케이션 서버 </li>
</ul>
<h2 id="nat-gateway">NAT Gateway</h2>
<p>Private Subnet의 리소스가 외부 인터넷으로 나갈 수 있게 해주는 장치</p>
<p><strong>내부 → 외부 가능</strong>
<strong>외부 → 내부 직접 접근 불가</strong></p>
<p>즉, 프라이빗 서브넷 안의 인스턴스가 패키지 다운로드, 외부 API 호출, 
업데이트 같은 작업은 가능하게 하고, 외부에서 직접 들어오는 연결은 막는 구조</p>
<p>동작 흐름 : Private Subnet 리소스 → NAT Gateway → IGW → 인터넷</p>
<ul>
<li>Public Subnet에 생성</li>
<li>Elastic IP 사용</li>
<li>특정 AZ에 생성</li>
<li>여러 AZ에 각각 만들면 가용성 향상</li>
<li>시간당 비용 + 데이터 처리 비용 발생 </li>
</ul>
<h2 id="bastion-host">Bastion Host</h2>
<p>외부에서 Private Subnet 내부 서버로 안전하게 접속하기 위한 중간 서버</p>
<p>보통 Public Subnet에 두고, 운영자는 먼저 Bastion Host에 접속한 뒤
그 내부에서 Private EC2로 SSH 접속</p>
<p>즉, 외부 사용자가 Private EC2에 바로 들어가는 것이 아니라
중간 관문을 하나 두는 방식</p>
<ul>
<li>보안 그룹 설정 예시
   Bastion Host: 특정 외부 IP만 SSH 허용
  Private EC2: Bastion Host에서 오는 SSH만 허용 </li>
</ul>
<h2 id="nacl">NACL</h2>
<p>Subnet 단위에서 트래픽을 제어하는 보안 장치</p>
<p>보안 그룹이 인스턴스 단위라면, NACL은 서브넷 단위?</p>
<ul>
<li>서브넷을 오가는 트래픽 제어</li>
<li>허용과 거부 규칙 모두 설정 가능</li>
<li>서브넷 생성 시 기본 NACL에 자동 연결</li>
<li>기본 NACL은 비교적 넓게 허용</li>
<li>사용자 정의 NACL은 처음에 허용 규칙 없이 시작</li>
<li>규칙 번호가 낮을수록 우선순위 높음 </li>
</ul>
<h2 id="보안-그룹-vs-nacl">보안 그룹 vs NACL</h2>
<table>
<thead>
<tr>
<th align="left">구분</th>
<th align="left">보안 그룹 (Security Group)</th>
<th align="left">네트워크 ACL (NACL)</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>적용 단위</strong></td>
<td align="left">인스턴스(EC2) 단위</td>
<td align="left">서브넷 단위</td>
</tr>
<tr>
<td align="left"><strong>상태 관리</strong></td>
<td align="left">Stateful (응답 트래픽 자동 허용)</td>
<td align="left">Stateless (응답 규칙 별도 필요)</td>
</tr>
<tr>
<td align="left"><strong>규칙</strong></td>
<td align="left">허용만 가능</td>
<td align="left">허용 및 거부 가능</td>
</tr>
<tr>
<td align="left"><strong>보안범위</strong></td>
<td align="left">EC2 인스턴스 하나에만 적용되는 보안</td>
<td align="left">Subnet 안에 있는 모든 EC2 인스턴스에 적용</td>
</tr>
<tr>
<td align="left"><strong>우선순위</strong></td>
<td align="left">모든 규칙 평가 후 허용/거부 여부 결정</td>
<td align="left">번호순 평가 (낮은 번호 우선) , 첫번째 비교로 허용/거부 여부 평가</td>
</tr>
</tbody></table>
<ul>
<li>더 세밀하게 인스턴스를 보호 → 보안 그룹</li>
<li>서브넷 단위로 큰 흐름 제어 → NACL </li>
</ul>
<hr>
<h2 id="aws-네트워크-구조">AWS 네트워크 구조</h2>
<ul>
<li>Region : AWS 데이터 센터가 모여 있는 지리적 영역</li>
<li>AZ (Availability Zone) : 하나 이상의 데이터 센터 묶음 / 여러 AZ가 모여 하나의 Region 구성</li>
<li>VPC : Region 안에 만드는 독립 네트워크</li>
<li>Subnet : VPC를 더 작게 나눈 네트워크 단위 ( 각 서브넷은 하나의 AZ에 속함 )</li>
</ul>
<h2 id="vpc-peering">VPC Peering</h2>
<p>서로 다른 VPC끼리 연결하는 방식</p>
<p>원래 VPC는 서로 독립적이지만, 피어링을 설정하면 마치 같은 네트워크처럼 통신 가능</p>
<ul>
<li>AWS 내부 네트워크로 연결</li>
<li>private IPv4 또는 IPv6 사용</li>
<li>서로 다른 계정 간 가능</li>
<li>서로 다른 리전 간 가능</li>
<li>VPC CIDR 대역이 겹치면 안 됨</li>
<li>피어링 관계는 전이되지 않음</li>
<li>라우팅 테이블도 함께 수정 필요 </li>
</ul>
<p>전이되지 않는다는 말 ;  A-B 연결, B-C 연결이 있어도 A와 C가 자동으로 연결X</p>
<h2 id="vpc-endpoint">VPC Endpoint</h2>
<p>VPC 내부에서 AWS 서비스로 직접 연결하는 방식</p>
<p>예를 들어 Private Subnet의 인스턴스가 S3, SNS 같은 AWS 서비스에 접근해야 할 때, 
굳이 NAT Gateway → IGW → 인터넷 경로를 거치지 않도록 해줌</p>
<ul>
<li>AWS 내부 네트워크 이용</li>
<li>더 안전</li>
<li>더 효율적</li>
<li>인터넷 우회 불필요</li>
</ul>
<h2 id="nat-gateway와-vpc-endpoint-차이">NAT Gateway와 VPC Endpoint 차이</h2>
<h4 id="nat-gateway-1">NAT Gateway</h4>
<p>인터넷을 통해 외부로 나가는 통로
경로 : Private Subnet → NAT Gateway → IGW → 외부 인터넷/AWS 서비스</p>
<h4 id="vpc-endpoint-1">VPC Endpoint</h4>
<p>AWS 내부망으로 바로 연결
경로 : Private Subnet → VPC Endpoint → AWS 서비스
AWS 서비스 접근만 필요하다면 VPC Endpoint가 더 적절한 경우가 많음 
장점: 보안성 향상, 속도개선, 비용 최적화</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[대상그룹 헬스체크 502 해결과정]]></title>
            <link>https://velog.io/@cse23_ewha/%EB%8C%80%EC%83%81%EA%B7%B8%EB%A3%B9-%ED%97%AC%EC%8A%A4%EC%B2%B4%ED%81%AC-502-%ED%95%B4%EA%B2%B0%EA%B3%BC%EC%A0%95</link>
            <guid>https://velog.io/@cse23_ewha/%EB%8C%80%EC%83%81%EA%B7%B8%EB%A3%B9-%ED%97%AC%EC%8A%A4%EC%B2%B4%ED%81%AC-502-%ED%95%B4%EA%B2%B0%EA%B3%BC%EC%A0%95</guid>
            <pubDate>Sat, 14 Mar 2026 11:51:37 GMT</pubDate>
            <description><![CDATA[<h2 id="aws-alb-502-에러-원인-애플리케이션-문제인-줄-알았는데-대상-그룹-protocol-version이-http2">AWS ALB 502 에러 원인: 애플리케이션 문제인 줄 알았는데, 대상 그룹 Protocol Version이 HTTP2..</h2>
<p>Spring Boot 애플리케이션을 AWS EC2에 배포하고, ALB(Application Load Balancer) 뒤에 연결해 두었는데 계속 <strong>502 Bad Gateway</strong> 가 발생했다. 처음에는 애플리케이션 자체 문제, 보안 그룹, 헬스체크 경로 문제를 의심했다. 그런데 결론적으로는 <strong>대상 그룹(Target Group)의 Protocol Version이 HTTP2로 설정되어 있었던 것</strong>이 핵심 원인이었다.</p>
<p>정리하면, 내 서비스는 일반적인 Spring Boot 백엔드였고 <code>8080</code> 포트에서 HTTP로 정상 동작하고 있었다. 직접 EC2 내부에서 확인해 보면 <code>/health</code> 엔드포인트는 <code>200 OK</code>를 반환했다. 그런데도 ALB 대상 그룹에서는 계속 <code>Unhealthy</code>가 떴고, 결국 502가 발생했다.</p>
<hr>
<h4 id="먼저-확인했던-것들">먼저 확인했던 것들</h4>
<p>처음에는 가장 흔한 원인부터 점검했다.</p>
<ul>
<li>애플리케이션이 실제로 <code>8080</code> 포트에서 실행 중인지</li>
<li><code>/health</code> 엔드포인트가 <code>200</code>을 반환하는지</li>
<li>대상 그룹 상태 검사 경로가 <code>/health</code>로 되어 있는지</li>
<li>성공 코드가 <code>200</code>인지</li>
<li>보안 그룹에서 포트 <code>8080</code>이 열려 있는지</li>
</ul>
<p>직접 EC2에서 확인했을 때는 <code>/health</code>가 정상 응답을 주고 있었다. 그래서 애플리케이션이 죽은 것은 아니었고, 헬스체크 경로도 틀리지 않았다.</p>
<p>또 AWS 문서상 ALB 대상 그룹의 상태 검사에서는 HTTP/1.1 또는 HTTP/2를 사용하는 경우 유효한 URI 경로를 지정할 수 있고, 성공 코드는 기본적으로 <code>200</code>이다. 따라서 <code>/health</code>, <code>200</code> 조합 자체는 문제 없는 설정이었다.</p>
<hr>
<h4 id="이상했던-점">이상했던 점</h4>
<p>문제는 대상 그룹 상세 설정을 자세히 보다가 발견했다.</p>
<ul>
<li>Protocol: <code>HTTP</code></li>
<li>Port: <code>8080</code></li>
<li><strong>Protocol version: <code>HTTP2</code></strong></li>
<li>Health check path: <code>/health</code></li>
<li>Success code: <code>200</code></li>
</ul>
<p>겉보기에는 크게 이상해 보이지 않을 수 있다. 하지만 AWS 공식 문서에 따르면, <strong>Application Load Balancer는 기본적으로 타깃에 HTTP/1.1로 요청을 보낸다.</strong> 다만 대상 그룹의 <strong>Protocol version</strong> 설정을 통해 타깃으로 <strong>HTTP/2 또는 gRPC</strong>를 사용하도록 바꿀 수 있다. 
즉, 나는 평범한 Spring Boot 백엔드를 올려 두었는데, 대상 그룹이 백엔드와 통신할 때 <strong>HTTP2를 쓰도록 설정되어 있었던 것</strong>이다.</p>
<hr>
<h4 id="왜-이게-문제가-되었나">왜 이게 문제가 되었나</h4>
<p>내 애플리케이션은 브라우저나 <code>curl</code>로 접근했을 때는 잘 응답했다. 이 테스트들은 사실상 일반적인 HTTP 요청 확인에 해당한다. 그런데 ALB는 대상 그룹 설정에 따라 타깃과 통신하는 방식이 달라질 수 있다.</p>
<p>AWS 문서에는 대상 그룹의 프로토콜 버전에 따라 요청 프로토콜과의 조합 결과가 달라진다고 명시되어 있다. 특히 <strong>HTTP/1.1 요청을 HTTP/2 대상 그룹으로 보내는 조합은 오류</strong>가 될 수 있다. 또한 프로토콜 버전이 맞지 않으면 ALB에서 프로토콜 불일치 문제가 발생할 수 있다고 설명한다. ([AWS Docs][2])</p>
<p>결국 내 경우는 다음과 같이 이해할 수 있었다.</p>
<ul>
<li>애플리케이션 자체는 정상</li>
<li><code>/health</code>도 정상 응답</li>
<li>하지만 ALB 대상 그룹이 백엔드와 통신할 때 HTTP2를 기대</li>
<li>실제 백엔드는 그 환경에 맞지 않음</li>
<li>상태 검사 실패</li>
<li>타깃이 <code>Unhealthy</code></li>
<li>최종적으로 502 발생</li>
</ul>
<hr>
<h4 id="해결-방법">해결 방법</h4>
<p>해결은 단순했다. 대상 그룹의 Protocol Version을 다음과 같이 바꾸면 되었다.</p>
<ul>
<li>Protocol: <code>HTTP</code></li>
<li>Port: <code>8080</code></li>
<li><strong>Protocol version: <code>HTTP1</code></strong></li>
<li>Health check path: <code>/health</code></li>
<li>Success code: <code>200</code></li>
</ul>
<p>AWS 공식 문서상 ALB는 기본적으로 타깃에 HTTP/1.1로 요청을 보내므로, 일반적인 Spring Boot 백엔드라면 이 설정이 가장 자연스럽다.
설정을 바꾼 뒤에는 대상 그룹 상태가 정상으로 바뀌고, 502도 해결되었다.
안되면 대상그룹 - 로드밸런서를 다시 만들면 HTTP1로 잘 적용된다!!!</p>
<hr>
<h2 id="왜-http2로-설정되어-있었는지는-아직-모르겠다">왜 HTTP2로 설정되어 있었는지는 아직 모르겠다</h2>
<p>솔직히 말하면, <strong>왜 대상 그룹 Protocol Version이 HTTP2로 바뀌어 있었는지는 나도 정확히 모르겠다.</strong>
내가 의도적으로 설정한 기억은 없다.</p>
<p>가능한 추측은 몇 가지 있다.</p>
<ul>
<li>대상 그룹 생성 시 콘솔에서 잘못 선택했을 가능성</li>
<li>기존 설정을 복사하거나 수정하는 과정에서 바뀌었을 가능성</li>
<li>다른 문서를 참고하면서 HTTP2가 더 “최신” 설정처럼 보여 무심코 선택했을 가능성</li>
</ul>
<p>하지만 중요한 것은 원인을 특정하는 것보다, <strong>백엔드 서버가 어떤 프로토콜을 기대하는지와 대상 그룹 설정이 일치해야 한다</strong>는 점이다.</p>
<hr>
<h2 id="이번-이슈에서-배운-점">이번 이슈에서 배운 점</h2>
<p>이번 문제를 겪으면서 느낀 것은, ALB 502가 난다고 해서 무조건 애플리케이션 코드 문제라고 보면 안 된다는 점이다.</p>
<p>내가 실제로 확인했던 순서는 이랬다.</p>
<ol>
<li>애플리케이션 프로세스/컨테이너가 살아 있는지 확인</li>
<li>EC2 내부에서 <code>/health</code>가 <code>200</code>인지 확인</li>
<li>대상 그룹의 헬스체크 경로와 성공 코드 확인</li>
<li>보안 그룹 확인</li>
<li>마지막으로 대상 그룹의 <strong>Protocol Version</strong> 확인</li>
</ol>
<p>특히 앞의 것들이 모두 정상인데도 타깃이 계속 <code>Unhealthy</code>라면, <strong>Protocol Version까지 반드시 확인해야 한다.</strong></p>
<hr>
<h2 id="한-줄-결론">한 줄 결론</h2>
<p><strong>Spring Boot 백엔드는 정상인데 ALB에서만 헬스체크가 실패하고 502가 난다면, 대상 그룹의 Protocol Version이 HTTP2로 되어 있지 않은지 꼭 확인하자. 일반적인 백엔드라면 HTTP1 설정이 맞을 가능성이 크다.</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[코코베이] 아이디어창업프로젝트 회고록]]></title>
            <link>https://velog.io/@cse23_ewha/%EC%BD%94%EC%BD%94%EB%B2%A0%EC%9D%B4-%EC%95%84%EC%9D%B4%EB%94%94%EC%96%B4%EC%B0%BD%EC%97%85%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0%EB%A1%9D</link>
            <guid>https://velog.io/@cse23_ewha/%EC%BD%94%EC%BD%94%EB%B2%A0%EC%9D%B4-%EC%95%84%EC%9D%B4%EB%94%94%EC%96%B4%EC%B0%BD%EC%97%85%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0%EB%A1%9D</guid>
            <pubDate>Wed, 11 Mar 2026 01:40:52 GMT</pubDate>
            <description><![CDATA[<h3 id="코코베이-아이디어창업프로젝트-회고록"><strong>[코코베이] 아이디어창업프로젝트 회고록</strong></h3>
<hr>
<h3 id="아이템-요약">아이템 요약</h3>
<p>육아 초보 부모들을 위한 개인 맞춤형 육아용품 큐레이션 앱 코코베이(Cocobay)를 구상</p>
<p>과잉 정보 속에서 제품을 고르기 어려운 부모들을 위해 월령별 가이드, AI 추천, 예산 계산기, 할인 알림 등 기능을 제공</p>
<h3 id="팀-구성-및-역할">팀 구성 및 역할</h3>
<ul>
<li>기획: 육아용품 시장 리서치, 사용자 인터뷰, 페르소나 설정</li>
<li>디자인: UI/UX</li>
<li>프론트엔드 개발: 추천 홈화면, 예산 계산기, 할인 알림 기능 구현</li>
<li>AI 모델 개발: AI 렌즈 기반 유사 상품 탐색 기능 초기 모델 구현</li>
</ul>
<h3 id="프로젝트-진행-과정">프로젝트 진행 과정</h3>
<ul>
<li>육아 부모 대상 설문 및 심층 인터뷰 진행</li>
<li>맘카페 크롤링을 통한 정보 탐색 피로 분석</li>
<li>기획 → 디자인 → 프론트 구현 → AI 모델 → 발표자료 제작까지 진행</li>
<li><strong>Notion + Figma + GitHub</strong>로 협업</li>
</ul>
<h3 id="잘했다고-생각하는-점">잘했다고 생각하는 점</h3>
<ul>
<li>사용자 중심 문제 정의가 명확했고, 인터뷰 기반 페르소나가 구체적이었음</li>
<li>실사용자의 고민을 직접 반영한 기능 기획</li>
<li>디자인과 개발 간 협업이 매끄럽고 속도감 있게 진행됨</li>
<li>실제 프론트엔드 구현물 + AI 모델까지 일정 내에 완성</li>
</ul>
<h3 id="아쉬운-점">아쉬운 점</h3>
<ul>
<li>데이터 기반 기능의 정확도와 검증 부분은 시간이 부족해 MVP 수준에 머물렀음</li>
<li>백엔드 연동하지 못하고 프론트에서 그친것이 아쉬움</li>
<li>체험단 확보 및 초기 홍보 전략을 실제 실행해보진 못하여 유저 피드백 수집 및 반복 개선을 하지 못한 점이 아쉬움</li>
</ul>
<h3 id="향후-발전-방향">향후 발전 방향</h3>
<ul>
<li>AI 렌즈 정확도 개선 및 자체 데이터셋 구축</li>
<li>제휴몰 연동을 통한 수익화 실험</li>
</ul>
<h3 id="개인적으로-얻은-것">개인적으로 얻은 것</h3>
<ul>
<li>사용자 인터뷰부터 실 프로토타입 개발까지, 스타트업의 문제 정의 – 솔루션 설계 – MVP 개발 – 피칭 전 과정을 경험</li>
<li>팀 내 빠른 의사결정과 다른 파트 팀원들과의 협업의 중요성을 체감</li>
<li>피봇되는게 당연하고 이 과정을 단순히 힘들다고 회피하지 않기</li>
<li>아이디어만 있다면 충분히 공부해서라도 구체화 가능하다. 너무 불가능하다고 생각하지 말기!</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[클라우드컴퓨팅]]></title>
            <link>https://velog.io/@cse23_ewha/%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C%EC%BB%B4%ED%93%A8%ED%8C%85</link>
            <guid>https://velog.io/@cse23_ewha/%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C%EC%BB%B4%ED%93%A8%ED%8C%85</guid>
            <pubDate>Mon, 02 Mar 2026 06:38:56 GMT</pubDate>
            <description><![CDATA[<h2 id="ch2">ch2</h2>
<p><img src="https://velog.velcdn.com/images/cse23_ewha/post/094a8a0d-b03f-4869-a08b-eb7e1eb53b32/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/cse23_ewha/post/d6a24342-8d3c-49de-95ff-ba004b53d698/image.jpg" alt=""></p>
<h2 id="ch3">ch3</h2>
<p><img src="https://velog.velcdn.com/images/cse23_ewha/post/6bdcb85a-0ee9-433f-bd00-106d5d4b984c/image.jpg" alt=""></p>
<h2 id="ch4">ch4</h2>
<p><img src="https://velog.velcdn.com/images/cse23_ewha/post/4da410b7-39ab-4eb3-9069-3bacdcc7f2f9/image.jpg" alt=""></p>
<h2 id="ch5">ch5</h2>
<p><img src="https://velog.velcdn.com/images/cse23_ewha/post/d8220b4b-df65-4152-a8b4-a51c1d9e26ca/image.jpg" alt=""></p>
<h2 id="ch6">ch6</h2>
<p><img src="https://velog.velcdn.com/images/cse23_ewha/post/f1380915-f669-41e0-bb6c-4d7f1e6c881c/image.jpg" alt=""></p>
<h2 id="ch7">ch7</h2>
<p><img src="https://velog.velcdn.com/images/cse23_ewha/post/b493091e-d3a7-4986-905d-aba9f7c3a6d7/image.jpg" alt=""></p>
<h2 id="ch8">ch8</h2>
<p><img src="https://velog.velcdn.com/images/cse23_ewha/post/223c247c-72e6-4fb6-bd57-84331a256d96/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/cse23_ewha/post/6b9c4bdf-0b94-4861-a076-637958e52297/image.jpg" alt=""></p>
<h2 id="ch9">ch9</h2>
<p><img src="https://velog.velcdn.com/images/cse23_ewha/post/ba7511ba-96d1-46a7-b9cb-0c75c52f2657/image.jpg" alt=""></p>
<h2 id="ch10">ch10</h2>
<p><img src="https://velog.velcdn.com/images/cse23_ewha/post/c9be376e-9801-4db4-b858-034fdbba6dd9/image.jpg" alt=""></p>
<h2 id="ch11">ch11</h2>
<p><img src="https://velog.velcdn.com/images/cse23_ewha/post/922f041f-99f1-40e1-be25-1470a8dd0d2a/image.jpg" alt=""></p>
<h2 id="ch12">ch12</h2>
<p><img src="https://velog.velcdn.com/images/cse23_ewha/post/21344081-60f2-4ef9-9a9f-cbe94cf2417f/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/cse23_ewha/post/44d306c7-f2a0-47de-9813-83b582b31248/image.jpg" alt=""></p>
<h2 id="ch13">ch13</h2>
<p><img src="https://velog.velcdn.com/images/cse23_ewha/post/738ae1cc-4013-4ffc-a212-10abe2c8361d/image.jpg" alt=""></p>
<h2 id="ch14">ch14</h2>
<p><img src="https://velog.velcdn.com/images/cse23_ewha/post/47aa0d67-f0cb-4a8c-8a66-7defbd8de4a2/image.jpg" alt=""></p>
<h2 id="ch15">ch15</h2>
<p><img src="https://velog.velcdn.com/images/cse23_ewha/post/59c589fe-9bae-4a87-950b-526bc588a62f/image.jpg" alt=""></p>
<h2 id="ch16">ch16</h2>
<p><img src="https://velog.velcdn.com/images/cse23_ewha/post/135224f0-7055-4e6e-bbc6-160ce169a9d3/image.jpg" alt=""></p>
<h2 id="ch17">ch17</h2>
<p><img src="https://velog.velcdn.com/images/cse23_ewha/post/9e515b08-3a39-4513-bf7c-1df4cf54005b/image.jpg" alt=""><img src="https://velog.velcdn.com/images/cse23_ewha/post/4e8cd1f5-16da-4274-9379-64ff8b129b32/image.jpg" alt=""></p>
<h2 id="ch18">ch18</h2>
<p><img src="https://velog.velcdn.com/images/cse23_ewha/post/96b4e123-ad33-439b-83c5-c0fec7e3d6ab/image.jpg" alt=""><img src="https://velog.velcdn.com/images/cse23_ewha/post/7b50afad-7519-43e0-b386-ed44a03abfe8/image.jpg" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[소프트웨어공학]]></title>
            <link>https://velog.io/@cse23_ewha/%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4%EA%B3%B5%ED%95%99</link>
            <guid>https://velog.io/@cse23_ewha/%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4%EA%B3%B5%ED%95%99</guid>
            <pubDate>Mon, 02 Mar 2026 06:03:16 GMT</pubDate>
            <description><![CDATA[<h2 id="개요">개요</h2>
<p><img src="https://velog.velcdn.com/images/cse23_ewha/post/b52734e2-9b2c-4735-a979-de9ef43290d8/image.jpg" alt=""></p>
<h2 id="소프트웨어-프로세스-1">소프트웨어 프로세스 1</h2>
<p><img src="https://velog.velcdn.com/images/cse23_ewha/post/48332305-beb6-4783-bafd-20172bdf9b88/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/cse23_ewha/post/fca8166d-cee3-4a87-8785-98592af81c15/image.jpg" alt=""></p>
<h2 id="소프트웨어-프로세스-2">소프트웨어 프로세스 2</h2>
<p><img src="https://velog.velcdn.com/images/cse23_ewha/post/76cc8280-8156-4cf0-8688-07d17da62f75/image.jpg" alt=""></p>
<h2 id="sw-모델링-및-표현도구">sw 모델링 및 표현도구</h2>
<p><img src="https://velog.velcdn.com/images/cse23_ewha/post/9e8bc194-4cbf-421e-b5c5-e11fc034c941/image.jpg" alt=""></p>
<h2 id="요구공학">요구공학</h2>
<p><img src="https://velog.velcdn.com/images/cse23_ewha/post/2015a765-01bc-41fb-80d5-c0c2f6cf70e5/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/cse23_ewha/post/75b72dbc-2f92-4ca6-a45e-38679415f9ce/image.jpg" alt=""></p>
<h2 id="시스템-모델링">시스템 모델링</h2>
<p><img src="https://velog.velcdn.com/images/cse23_ewha/post/dfa4e4d1-891f-4d7c-aceb-f95aaa6a9e01/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/cse23_ewha/post/80a68664-50fd-49dd-bae5-ebc3356ead06/image.jpg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/cse23_ewha/post/57b916a6-9e6d-4c66-8f06-0a22fdb488bc/image.jpg" alt=""></p>
<h2 id="아키텍쳐설계">아키텍쳐설계</h2>
<p><img src="https://velog.velcdn.com/images/cse23_ewha/post/65b84ca3-1a8a-4f94-ab30-2282914179f8/image.jpg" alt=""></p>
<h2 id="객체지향소프트웨어">객체지향소프트웨어</h2>
<p><img src="https://velog.velcdn.com/images/cse23_ewha/post/d7512f4b-66d9-4fc8-964f-bf59580b5225/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/cse23_ewha/post/b93e69b0-b105-4886-a8f4-d4db8bf7a830/image.jpg" alt=""></p>
<h2 id="ch8">ch8</h2>
<p><img src="https://velog.velcdn.com/images/cse23_ewha/post/746b3e6c-7821-417b-aa9e-80ad5afc814b/image.jpg" alt=""></p>
<h2 id="ch9">ch9</h2>
<p><img src="https://velog.velcdn.com/images/cse23_ewha/post/1720e28c-f07a-4aac-8882-16ae1073957d/image.jpg" alt=""></p>
<h2 id="ch10">ch10</h2>
<p><img src="https://velog.velcdn.com/images/cse23_ewha/post/56d7407e-a357-426d-ae7d-c304251d28fe/image.jpg" alt=""></p>
<h2 id="ch11">ch11</h2>
<p><img src="https://velog.velcdn.com/images/cse23_ewha/post/0b75f9b5-8a67-44ab-9c78-fd72cbe31bfe/image.jpg" alt=""></p>
<h2 id="ch12">ch12</h2>
<p><img src="https://velog.velcdn.com/images/cse23_ewha/post/1967f1c5-851a-4766-b62e-b93092baa01f/image.jpg" alt=""></p>
<h2 id="ch13">ch13</h2>
<p><img src="https://velog.velcdn.com/images/cse23_ewha/post/d7bdbe87-318e-4b56-8c7f-d76b198e1aa9/image.jpg" alt=""></p>
<h2 id="ch14">ch14</h2>
<p><img src="https://velog.velcdn.com/images/cse23_ewha/post/585ef8d1-d77d-49db-8d35-1ff9820d0897/image.jpg" alt=""></p>
<h2 id="ch15">ch15</h2>
<p><img src="https://velog.velcdn.com/images/cse23_ewha/post/789a530b-d9e8-4968-bcec-7f69c77accf6/image.jpg" alt=""></p>
<h2 id="ch16">ch16</h2>
<p><img src="https://velog.velcdn.com/images/cse23_ewha/post/c40a0d34-3591-43a2-89bf-2b95cdc0467a/image.jpg" alt=""></p>
<h2 id="ch17">ch17</h2>
<p><img src="https://velog.velcdn.com/images/cse23_ewha/post/c99b72f4-f471-48e4-93d5-eb1c42f6c667/image.jpg" alt=""></p>
<h2 id="ch18">ch18</h2>
<p><img src="https://velog.velcdn.com/images/cse23_ewha/post/eb862c6e-1e58-49f9-a7a0-a8eaceebdc5d/image.jpg" alt=""></p>
<h2 id="ch19">ch19</h2>
<p><img src="https://velog.velcdn.com/images/cse23_ewha/post/ae39ee1d-df66-4bbf-ba94-13457c3dd736/image.jpg" alt=""></p>
<h2 id="ch20">ch20</h2>
<p><img src="https://velog.velcdn.com/images/cse23_ewha/post/02a4d827-8c8d-47db-91aa-cda634d7b6f5/image.jpg" alt=""></p>
<h2 id="ch21">ch21</h2>
<p><img src="https://velog.velcdn.com/images/cse23_ewha/post/e88abc7f-d9c0-4f4a-9d59-551418f86eb3/image.jpg" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[빅 데이터 응용]]></title>
            <link>https://velog.io/@cse23_ewha/%EB%B9%85-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%9D%91%EC%9A%A9-hvla3lyl</link>
            <guid>https://velog.io/@cse23_ewha/%EB%B9%85-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%9D%91%EC%9A%A9-hvla3lyl</guid>
            <pubDate>Mon, 02 Mar 2026 05:41:31 GMT</pubDate>
            <description><![CDATA[<p>ch1
<img src="https://velog.velcdn.com/images/cse23_ewha/post/d456578a-b76d-4d16-8b3c-d4cf31b4783c/image.jpg" alt=""></p>
<p>ch2
<img src="https://velog.velcdn.com/images/cse23_ewha/post/6327f22f-1475-4f07-8216-b61a390e4647/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/cse23_ewha/post/beed9222-909b-4870-a5eb-e3d9ea25c7aa/image.jpg" alt=""></p>
<p>ch3</p>
<p><img src="https://velog.velcdn.com/images/cse23_ewha/post/a2c04615-77f7-4e26-8911-29ab2398f951/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/cse23_ewha/post/d2ceff28-b65e-4a11-88aa-4fb72bf34434/image.jpg" alt=""></p>
<p>ch4
<img src="https://velog.velcdn.com/images/cse23_ewha/post/2ba67c3b-f061-4f34-b975-1d8bcfe7d68a/image.jpg" alt="">
ch5
<img src="https://velog.velcdn.com/images/cse23_ewha/post/8f6301c8-0961-4a6b-8b7f-2169b56e5b8c/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/cse23_ewha/post/3182c5eb-1f17-4c4d-a70a-e8f6150c144d/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/cse23_ewha/post/b9375d67-a5e0-4535-adaa-30560354b28e/image.jpg" alt=""></p>
<p>ch6
<img src="https://velog.velcdn.com/images/cse23_ewha/post/7128fa0d-5a43-42c3-9e2a-55723e6ad6eb/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/cse23_ewha/post/0e675293-8a2f-42bd-af22-194abea6e1bd/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/cse23_ewha/post/946b4472-36ec-4c0f-b3fc-760a0dec07c7/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/cse23_ewha/post/4dd51b8c-1b82-4cd5-8fa7-1d7d89936710/image.jpg" alt=""></p>
<p>ch7
<img src="https://velog.velcdn.com/images/cse23_ewha/post/3f57a091-44f8-4e37-a379-9b0ae36d500e/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/cse23_ewha/post/0ddbc689-cdf9-4166-a9ee-e2a0889fa5ae/image.jpg" alt="">
ch8
<img src="https://velog.velcdn.com/images/cse23_ewha/post/a338b011-3b3a-4a9e-8a39-d39382cdb06e/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/cse23_ewha/post/370914a1-06e6-4a2c-a56b-5395f6bf151a/image.jpg" alt="">
ch9
<img src="https://velog.velcdn.com/images/cse23_ewha/post/e7fe060f-944b-44f3-b57d-735e929d6b2b/image.jpg" alt=""></p>
<h2 id="족보-풀이">족보 풀이</h2>
<p><img src="https://velog.velcdn.com/images/cse23_ewha/post/db2693a2-dcdc-4a98-a7b5-f0a5c6f8313a/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/cse23_ewha/post/de08e405-7483-4047-9f06-6a236a3e3ae4/image.jpg" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[FastAPI 기초 문법]]></title>
            <link>https://velog.io/@cse23_ewha/FastAPI-%EA%B8%B0%EC%B4%88-%EB%AC%B8%EB%B2%95</link>
            <guid>https://velog.io/@cse23_ewha/FastAPI-%EA%B8%B0%EC%B4%88-%EB%AC%B8%EB%B2%95</guid>
            <pubDate>Sat, 24 Jan 2026 03:57:47 GMT</pubDate>
            <description><![CDATA[<p>몰입캠프 3주차를 위한 FastAPI 문법 속성으로 공부하기</p>
<hr>
<h3 id="1-기본-골격">1. 기본 골격</h3>
<p>가장 기초적인 형태
<code>@app.get(&quot;/&quot;)</code> 같은 데코레이터가 이 주소로 오면 아래 함수를 실행해라고 알려줌</p>
<pre><code class="language-python">from fastapi import FastAPI

app = FastAPI()

# GET 요청을 받음
@app.get(&quot;/&quot;)
async def read_root():
    return {&quot;message&quot;: &quot;Hello World&quot;}
</code></pre>
<ul>
<li><strong><code>async def</code></strong>: 비동기 함수로 정의 <ul>
<li>데이터베이스나 AI 요청처럼 오래 걸리는 작업에 필수 -&gt; ai가 오래걸려도 그걸 기다지리않고 다른걸 할수있음</li>
</ul>
</li>
<li><strong>Return</strong>: 딕셔너리(<code>{}</code>)를 리턴하면 자동으로 JSON으로 변환되어 나감</li>
</ul>
<hr>
<h3 id="2-경로-변수-path-parameter">2. 경로 변수 (Path Parameter)</h3>
<p>URL 경로 자체에 들어있는 변수를 받음
예: <code>/items/5</code> -&gt; <code>item_id</code>는 5</p>
<pre><code class="language-python">@app.get(&quot;/items/{item_id}&quot;)
async def read_item(item_id: int):  # 타입 힌트(int) 필수!
    return {&quot;item_id&quot;: item_id}
</code></pre>
<ul>
<li><strong><code>item_id: int</code></strong>: 이렇게 타입을 적어두면, 사용자가 <code>/items/foo</code>라고 문자를 넣었을 때 FastAPI가 알아서 숫자만 넣으라고 에러를 냄</li>
</ul>
<hr>
<h3 id="3-쿼리-파라미터-query-parameter">3. 쿼리 파라미터 (Query Parameter)</h3>
<p>URL 뒤에 <code>?</code>로 붙는 변수
경로(<code>{}</code>)에 없는 변수를 함수 인자로 넣으면 자동으로 쿼리 파라미터가 됌
예: <code>/items/?skip=0&amp;limit=10</code></p>
<pre><code class="language-python">@app.get(&quot;/items/&quot;)
async def read_items(skip: int = 0, limit: int = 10):
    return {&quot;skip&quot;: skip, &quot;limit&quot;: limit}
</code></pre>
<ul>
<li><strong><code>skip: int = 0</code></strong>: 값이 안 들어오면 기본값 0 사용</li>
</ul>
<hr>
<h3 id="4-데이터-받기">4. 데이터 받기</h3>
<p>POST 요청으로 복잡한 데이터를 받을 때 사용
<code>Pydantic</code>이라는 라이브러리로 데이터의 모양(스키마)을 정의</p>
<pre><code class="language-python">from pydantic import BaseModel

# 1. 데이터 모양 정의 (붕어빵 틀)
class Item(BaseModel):
    name: str
    price: float
    is_offer: bool = None  # 선택 사항

# 2. 함수에서 사용
@app.post(&quot;/items/&quot;)
async def create_item(item: Item): # item 변수는 Item 클래스 모양이어야 함
    return {&quot;name&quot;: item.name, &quot;price&quot;: item.price}
</code></pre>
<ul>
<li>사용자가 JSON 데이터를 보내면, FastAPI가 <code>Item</code> 클래스 모양과 맞는지 검사하고, 자동으로 변수(<code>item</code>)에 넣어줌</li>
<li><code>item.name</code>처럼 점(<code>.</code>)을 찍어서 데이터에 접근할 수 있음</li>
</ul>
<hr>
<h3 id="5-라우터-나누기-apirouter">5. 라우터 나누기 (APIRouter)</h3>
<p>기능별로 파일을 쪼갤 때 사용 </p>
<p><strong>파일: `users.py</strong>`</p>
<pre><code class="language-python">from fastapi import APIRouter

router = APIRouter()

@router.get(&quot;/users/&quot;)
async def read_users():
    return [{&quot;username&quot;: &quot;Rick&quot;}, {&quot;username&quot;: &quot;Morty&quot;}]
</code></pre>
<p><strong>파일: <code>main.py</code> (메인)</strong></p>
<pre><code class="language-python">from fastapi import FastAPI
from . import users # users.py 불러오기

app = FastAPI()

# users의 라우터를 메인에 합체!
app.include_router(users.router)
</code></pre>
<hr>
<h3 id="자동-문서화-swagger-ui">자동 문서화 (Swagger UI)</h3>
<p>FastAPI의 가장 큰 장점
코드를 짜고 서버를 켜면, 자동으로 API 설명서 사이트를 만들어줌</p>
<ul>
<li>서버 실행 후 주소창에: <code>http://localhost:8000/docs</code></li>
<li>API를 직접 테스트 가능 (Postman 없어도 됨)</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[웹소켓(WebSocket) 성공 기록]]></title>
            <link>https://velog.io/@cse23_ewha/%EC%9B%B9%EC%86%8C%EC%BC%93WebSocket-%EC%84%B1%EA%B3%B5-%EA%B8%B0%EB%A1%9D</link>
            <guid>https://velog.io/@cse23_ewha/%EC%9B%B9%EC%86%8C%EC%BC%93WebSocket-%EC%84%B1%EA%B3%B5-%EA%B8%B0%EB%A1%9D</guid>
            <pubDate>Thu, 22 Jan 2026 04:22:14 GMT</pubDate>
            <description><![CDATA[<p>카이스트 몰입캠프 2주차 프로젝트 결과물인 몰 봐(MV) 서비스의 핵심인 <strong>&quot;댓글 기반 팟 모집 -&gt; 채팅방 생성 -&gt; 실시간 채팅&quot;</strong> 로직을 바탕으로 웹소켓(WebSocket) 구현 성공 기록기...</p>
<hr>
<h4 id="1-웹소켓과-stomp-기초-개념">1. 웹소켓과 STOMP 기초 개념</h4>
<p>단순 웹소켓만 쓰면 로우 레벨이라 메시지 형식을 일일이 다 정해야 함
그래서 우리는 <strong>STOMP</strong> 규격 사용했음</p>
<ul>
<li><p><strong>구조</strong>: 메일함처럼 특정 주소를 &#39;구독(Sub)&#39;하고, 그 주소로 메시지를 &#39;발행(Pub)&#39;하는 방식임.</p>
</li>
<li><p><strong>장점</strong>: 메시지 브로커를 통해 채팅방별로 메시지를 꽂아주기가 훨씬 편함.</p>
</li>
<li><p><strong>Publisher(발행자)</strong>: 채팅 메시지를 보내는 사람 (클라이언트)</p>
</li>
<li><p><strong>Subscriber(구독자)</strong>: 채팅방에 들어와서 메시지를 받는 사람 (클라이언트)</p>
</li>
<li><p><strong>Broker(중개자)</strong>: 메시지를 받아서 구독 중인 사람들에게 뿌려주는 서버</p>
</li>
</ul>
<hr>
<h4 id="2-백엔드-설정-spring-boot">2. 백엔드 설정 (Spring Boot)</h4>
<p>먼저 의존성에 <code>spring-boot-starter-websocket</code> 추가되어 있어야 함.</p>
<p><strong>[WebSocketConfig.java]</strong></p>
<pre><code class="language-java">@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        // 메시지 받을 때: /sub/chat/room/1 이런 식으로 구독함
        config.enableSimpleBroker(&quot;/sub&quot;); 
        // 메시지 보낼 때: /pub/chat/message 이런 식으로 보냄
        config.setApplicationDestinationPrefixes(&quot;/pub&quot;);
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint(&quot;/ws&quot;) // 프론트가 접속할 엔드포인트
                .setAllowedOrigins(&quot;https://www.example.com&quot;) // 프론트 도메인 필수
                .withSockJS(); // 낮은 버전 브라우저 대응
    }
}
</code></pre>
<hr>
<h4 id="3-채팅-로직-흐름-우리-서비스-특화">3. 채팅 로직 흐름 (우리 서비스 특화)</h4>
<p> <strong>글쓴이가 게시글(Post) 댓글에서 선택해서 팟 참여 확정 -&gt; 채팅방 생성</strong> 구조로 기획을 했음..</p>
<ol>
<li><strong>팟 확정</strong>: 게시글 작성자가 참여자를 선택하고 &#39;확정&#39; 누름.</li>
<li><strong>채팅방 생성</strong>: 서버에서 <code>ChatRoom</code> 만들고 선택된 인원들을 <code>ChatMember</code>로 등록함.</li>
<li><strong>알림 발송</strong>: 참여자들에게 &quot;채팅방에 초대되었습니다&quot;라고 알림 쏨.</li>
<li><strong>입장 및 구독</strong>: 프론트에서 <code>roomId</code>를 받아 해당 경로(<code>/sub/chat/room/{id}</code>)를 구독함.</li>
<li><strong>메시지 전송(Publish)</strong>: 유저가 채팅 치면 프론트에서 <code>/pub/chat/message</code>로 JSON 데이터를 전송</li>
</ol>
<p><strong>[ChatController.java]</strong></p>
<pre><code class="language-java">@Controller
@RequiredArgsConstructor
public class ChatController {
    private final SimpMessageSendingOperations messagingTemplate;

    @MessageMapping(&quot;/chat/message&quot;) // 프론트가 /pub/chat/message로 보낼 때
    public void message(ChatMessageDto message) {
        // DB에 채팅 내역 저장하는 로직 추가 가능
        // 구독 중인 사람들에게 메시지 전달
        messagingTemplate.convertAndSend(&quot;/sub/chat/room/&quot; + message.getRoomId(), message);
    }
}
</code></pre>
<hr>
<h4 id="4-프론트엔드와-협업할-때-주의사항">4. 프론트엔드와 협업할 때 주의사항</h4>
<p><strong>① 보안 및 인증 (가장 많이 막히는 부분)</strong></p>
<ul>
<li>웹소켓은 일반 HTTP 요청이랑 다르게 Header에 토큰 담기가 까다로움....</li>
<li><strong>해결</strong>: 연결 시점(<code>connect</code>)에 쿼리 파라미터로 토큰을 보내거나, STOMP의 <code>connectCallback</code> 헤더에 JWT 토큰을 실어 보내야 함. 백엔드에서 <code>ChannelInterceptor</code>를 구현해서 토큰 검증 로직 따로 짰음</li>
</ul>
<p><strong>② CORS 에러</strong></p>
<ul>
<li>웹소켓 엔드포인트(<code>.addEndpoint(&quot;/ws&quot;)</code>)에도 <code>.setAllowedOrigins</code> 설정을 정확히 해줘야 함. 안 그러면 프론트에서 접속 못 함......</li>
</ul>
<p><strong>③ 도메인 연결 (HTTPS/WSS)</strong></p>
<ul>
<li>서비스 HTTPS 적용했다면 프론트도 <code>ws://</code>가 아니라 <code>wss://</code>로 접속해야 함.</li>
<li>AWS ALB(로드밸런서) 쓰고 있으면 웹소켓 프로토콜도 지원하도록 설정 확인해야 함 (기본적으로 지원하지만 간혹 타임아웃 이슈 있음).</li>
</ul>
<hr>
<h4 id="5-오류-해결-및-팁">5. 오류 해결 및 팁</h4>
<ul>
<li><strong>채팅방 자동 생성 이슈</strong>: 댓글에서 선택해서 채팅방 만들 때, 선택된 인원들의 <code>Member</code> ID 리스트를 받아서 <code>ChatMember</code>(중간 테이블)에 저장해야 했음. 그래야 나중에 &quot;내 채팅방 목록&quot; 조회할 때 내가 속한 방만 가져올 수 있었음</li>
<li><strong>무한 루프 방지</strong>: 프론트에서 메시지 받고 바로 다시 메시지 쏘는 로직 들어가면 서버 터짐. 이벤트 리스너 관리 잘하라고 프론트한테 말해줘서 해야함!</li>
</ul>
<hr>
<h4 id="사소하지만-디테일-구현하고자-한-포인트">사소하지만 디테일 구현하고자 한 포인트</h4>
<p>유저마다 <code>last_read_at</code>(마지막 읽은 시간)을 필드로 둠. 채팅방 목록 불러올 때 이 시간 이후로 쌓인 메시지 개수를 쿼리로 날려서 &quot;안 읽은 메시지 N개&quot; 기능을 구현했음..</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[가비아 도메인으로 Full-Stack 서비스 배포 및 HTTPS 보안 적용하기]]></title>
            <link>https://velog.io/@cse23_ewha/%EA%B0%80%EB%B9%84%EC%95%84-%EB%8F%84%EB%A9%94%EC%9D%B8%EC%9C%BC%EB%A1%9C-Full-Stack-%EC%84%9C%EB%B9%84%EC%8A%A4-%EB%B0%B0%ED%8F%AC-%EB%B0%8F-HTTPS-%EB%B3%B4%EC%95%88-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@cse23_ewha/%EA%B0%80%EB%B9%84%EC%95%84-%EB%8F%84%EB%A9%94%EC%9D%B8%EC%9C%BC%EB%A1%9C-Full-Stack-%EC%84%9C%EB%B9%84%EC%8A%A4-%EB%B0%B0%ED%8F%AC-%EB%B0%8F-HTTPS-%EB%B3%B4%EC%95%88-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 22 Jan 2026 04:04:10 GMT</pubDate>
            <description><![CDATA[<p>협업 프로젝트를 하며 프론트와 협업하며 경험한 HTTPS하는법 정리본!
딱 한번 해보면 어렵지 않음.</p>
<hr>
<h3 id="full-stack-https-인프라-구축-완벽-정리">Full-Stack HTTPS 인프라 구축 완벽 정리</h3>
<h4 id="1-인프라-구조-설계">1. 인프라 구조 설계</h4>
<ul>
<li><strong>Front-end</strong>: Vercel (자동 HTTPS 적용됨) → <code>www.example.com</code></li>
<li><strong>Back-end</strong>: AWS EC2 + ALB (수동 설정 필요) → <code>api.example.com</code></li>
<li><strong>핵심</strong>: 하나의 도메인을 사서 서브도메인으로 앞뒤를 나눠서 관리함.</li>
</ul>
<h4 id="2-도메인-구매-및-route-53-연결">2. 도메인 구매 및 Route 53 연결</h4>
<ol>
<li><strong>도메인 구매</strong>: 가비아 등에서 맘에 드는 거 삼.</li>
<li><strong>호스팅 영역 생성</strong>: AWS Route 53 가서 구매한 도메인으로 &#39;퍼블릭 호스팅 영역&#39; 만듦.</li>
<li><strong>네임서버(NS) 교체</strong>:</li>
</ol>
<ul>
<li>Route 53에서 생성된 NS 레코드 값 4개 복사함.</li>
<li>가비아 관리 페이지 가서 기존 네임서버 지우고 복사한 값 넣음. (맨 끝 마침표 <code>.</code> 빼고)
<em>전파되는데 시간 좀 걸림</em></li>
</ul>
<h4 id="3-ssl-인증서-발급-acm">3. SSL 인증서 발급 (ACM)</h4>
<ol>
<li><strong>인증서 요청</strong>: AWS ACM 가서 <code>*.example.com</code> , <code>example.com</code>  형태로 2개 포함해 신청</li>
<li><strong>DNS 검증</strong>: 검증 방식은 &#39;DNS 검증&#39; 선택</li>
<li><strong>레코드 생성</strong>: &quot;Route 53에서 레코드 생성&quot; 버튼 누르면 알아서 CNAME 생성</li>
<li><strong>상태 확인</strong>: &#39;발급됨&#39; 뜰 때까지 대기 ( 시간소요)</li>
</ol>
<h4 id="4-로드밸런서alb-및-타겟-그룹-세팅">4. 로드밸런서(ALB) 및 타겟 그룹 세팅</h4>
<ol>
<li><strong>대상 그룹(Target Group) 생성</strong>:</li>
</ol>
<ul>
<li>유형은 &#39;인스턴스&#39;, 포트는 스프링 부트 포트(8080)로 설정</li>
<li>내 EC2 인스턴스 체크해서 타겟에 포함시키기</li>
</ul>
<ol start="2">
<li><strong>ALB 생성</strong>:</li>
</ol>
<ul>
<li>Application Load Balancer 선택</li>
<li>가용 영역(AZ)은 최소 2개 이상의 퍼블릭 서브넷 선택해야함 (나는 a,c선택함)</li>
<li><strong>보안 그룹</strong>: 80(HTTP), 443(HTTPS) 둘 다 열려 있어야 함.</li>
</ul>
<ol start="3">
<li><strong>리스너 설정</strong>:</li>
</ol>
<ul>
<li><strong>HTTPS:443</strong>: 아까 ACM에서 만든 인증서 걸어주고, 대상 그룹으로 넘기기</li>
<li><strong>HTTP:80</strong>: 호스트 헤드 =  도메인 , URL로 리디랙션 <pre><code>      -&gt; 모든 요청을 HTTPS로 리디렉션 (상태 코드 301) -&gt; 1순위</code></pre></li>
</ul>
<h4 id="5-route-53---a-레코드-연결">5. Route 53 - A 레코드 연결</h4>
<ol>
<li>Route 53 호스팅 영역 다시 들어감.
1-1. 서브도메인(백엔드용)
1-2. <strong>레코드 생성</strong>: <code>www</code> 혹은 <code>api</code> 등 서브도메인 입력
1-3. <strong>별칭(Alias)</strong>: 스위치 켜고, &#39;ALB에 대한 별칭&#39; 선택해서 만든 로드밸런서 주소랑 연결
1-1. 도메인(프론트용)
1-2. <strong>레코드 생성</strong>: 아무런 이름 입력 X
1-3. <strong>별칭(Alias) 끄기</strong>: ALB에 대한 별칭 해제하고 프론트의 vercel 배포 주소를 넣기</li>
</ol>
<h4 id="6-cors-및-헬스-체크-이슈-해결-중요함">6. CORS 및 헬스 체크 이슈 해결 (중요함!)</h4>
<ul>
<li><strong>CORS 에러</strong>: 프론트(<code>www</code>)랑 백(<code>api</code>) 도메인이 다르기 때문에 스프링 부트에서 허용해줘야 함. <code>allowedOrigins</code>에 프론트 주소 정확히 작성하기</li>
<li><strong>Health Check</strong>: ALB는 주기적으로 서버가 살아있는지 체크해줌 (<code>/</code> 경로가 404 뜨면 서버 죽은 줄 알고 트래픽 끊음)<ul>
<li>스프링 부트에 간단하게 <code>200 OK</code> 뱉는 <code>/health</code> 컨트롤러 만들고, 대상 그룹 설정에서 헬스 체크 경로를 <code>/health</code>로 바꿔서 테스트하기</li>
</ul>
</li>
</ul>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[Amazon Linux에서 Nginx 설치 및 설정]]></title>
            <link>https://velog.io/@cse23_ewha/Amazon-Linux%EC%97%90%EC%84%9C-Nginx-%EC%84%A4%EC%B9%98-%EB%B0%8F-%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@cse23_ewha/Amazon-Linux%EC%97%90%EC%84%9C-Nginx-%EC%84%A4%EC%B9%98-%EB%B0%8F-%EC%84%A4%EC%A0%95</guid>
            <pubDate>Sat, 17 Jan 2026 03:03:13 GMT</pubDate>
            <description><![CDATA[<h3 id="🛠️-amazon-linux에서-nginx-설치-및-설정">🛠️ Amazon Linux에서 Nginx 설치 및 설정</h3>
<h4 id="1-nginx-설치">1. Nginx 설치</h4>
<pre><code class="language-bash"># 패키지 매니저 업데이트
sudo yum update -y

# Nginx 설치
sudo yum install nginx -y

# Nginx 실행 및 서버 부팅 시 자동 실행 설정
sudo systemctl start nginx
sudo systemctl enable nginx
</code></pre>
<h4 id="2-설정-파일-수정">2. 설정 파일 수정</h4>
<pre><code class="language-bash"># 기본 설정 파일 백업 
sudo cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak

# 설정 파일 열기
sudo nano /etc/nginx/nginx.conf
</code></pre>
<h4 id="3-nginxconf-내용-수정">3. <code>nginx.conf</code> 내용 수정</h4>
<pre><code class="language-nginx">server {
    listen       80;
    listen       [::]:80;
    server_name  도메인 www.도메인;

    location / {
        proxy_pass http://localhost:8080;
        proxy_http_version 1.1;

        # SSE 및 실시간 통신 설정
        proxy_set_header Connection &quot;&quot;;
        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_cache_off;
        proxy_buffering off;
        proxy_read_timeout 3600s;
        chunked_transfer_encoding off;
    }

    # 기존에 있던 다른 설정들은 일단 그대로 두기
}
</code></pre>
<h4 id="4-적용-및-확인">4. 적용 및 확인</h4>
<pre><code class="language-bash"># 문법 체크 
sudo nginx -t

# Nginx 재시작
sudo systemctl restart nginx
</code></pre>
<hr>
<h4 id="주의사항-aws-보안-그룹">주의사항 (AWS 보안 그룹)</h4>
<p><strong>AWS 보안 그룹</strong>에서 <strong>80 포트</strong>를 여는 것이 필수</p>
]]></description>
        </item>
    </channel>
</rss>