<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>its-jihyeon.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Sun, 24 May 2026 07:23:07 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>its-jihyeon.log</title>
            <url>https://velog.velcdn.com/images/its-jihyeon/profile/5a9c78d6-d575-442f-b1a7-ac613981c232/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. its-jihyeon.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/its-jihyeon" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[멋쟁이사자처럼부트캠프 클라우드 엔지니어링 5기 [17주차]]]></title>
            <link>https://velog.io/@its-jihyeon/%EB%A9%8B%EC%9F%81%EC%9D%B4%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A7%81-5%EA%B8%B0-17%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@its-jihyeon/%EB%A9%8B%EC%9F%81%EC%9D%B4%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A7%81-5%EA%B8%B0-17%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Sun, 24 May 2026 07:23:07 GMT</pubDate>
            <description><![CDATA[<h3 id="gitops--argocd-원론-개념">GitOps &amp; ArgoCD 원론 개념</h3>
<h3 id="01-기존-배포-방식의-문제--push-방식"><strong>01. 기존 배포 방식의 문제 — Push 방식</strong></h3>
<ul>
<li>CI가 완료되면 Runner가 kubectl apply를 직접 실행해서 클러스터에 배포했음 (<strong>Push 방식)</strong></li>
</ul>
<pre><code>[Push 방식]

  개발자 코드 Push
        |
        v
  GitHub Actions Runner
        |
        |  kubeconfig 필요 (외부에서 클러스터 접근)
        |  kubectl apply -f deployment.yaml
        v
  k8s 클러스터

  문제점:
    1. Runner 가 클러스터 외부에서 접근 -&gt; kubeconfig 시크릿 노출 위험
    2. 배포 후 누군가 kubectl 로 직접 변경해도 감지 불가
    3. 롤백하려면 워크플로우를 다시 실행해야 함
    4. &quot;지금 실제로 뭐가 배포되어 있지?&quot; 를 Git 만으로 알 수 없음</code></pre><br>

<h3 id="02-gitops-란-무엇인가--pull-방식"><strong>02. GitOps 란 무엇인가 — Pull 방식</strong></h3>
<ul>
<li>GitOps는 배포 방식을 근본적으로 바꿈</li>
<li>클러스터가 스스로 Git 을 감시하다가 &quot;Git 에 변경이 생기면 내가 알아서 맞춰나간다&quot; 는 방식</li>
</ul>
<pre><code>[Pull 방식 — GitOps / ArgoCD]

  개발자 코드 Push
        |
        v
  App Repo    GitHub Actions CI 만 담당
        |     (빌드, 테스트, 이미지 Push)
        |
        v  이미지 태그만 업데이트
  Config Repo  (k8s 매니페스트만 있는 저장소)
        |
        |  ArgoCD 가 주기적으로 감시 (Pull)
        |  &quot;Config Repo 가 바뀌었다 -&gt; 클러스터도 맞춰야 한다&quot;
        v
  k8s 클러스터
  ArgoCD Controller 가 내부에서 동기화
  (외부에서 접근 불필요 -&gt; 보안 강화)</code></pre><br>

<h4 id="push-vs-pull-비교">Push vs Pull 비교</h4>
<table>
<thead>
<tr>
<th>항목</th>
<th>Push 방식 (final3)</th>
<th>Pull 방식 (GitOps)</th>
</tr>
</thead>
<tbody><tr>
<td>배포 주체</td>
<td>GitHub Actions Runner</td>
<td>클러스터 내부 ArgoCD</td>
</tr>
<tr>
<td>클러스터 접근</td>
<td>외부에서 직접 접근</td>
<td>내부에서 자체 감지</td>
</tr>
<tr>
<td>보안</td>
<td>kubeconfig 시크릿 필요</td>
<td>외부 접근 권한 불필요</td>
</tr>
<tr>
<td>변경 감지</td>
<td>불가</td>
<td>자동 감지 및 복구</td>
</tr>
<tr>
<td>감사 로그</td>
<td>Actions 로그</td>
<td>Git 커밋 이력</td>
</tr>
<tr>
<td>롤백</td>
<td>워크플로우 재실행</td>
<td>git revert 한 줄</td>
</tr>
</tbody></table>
<br>

<h3 id="03-핵심-용어-정리">03. 핵심 용어 정리</h3>
<table>
<thead>
<tr>
<th>용어</th>
<th>한 줄 설명</th>
</tr>
</thead>
<tbody><tr>
<td>Desired State</td>
<td>Git 에 선언된 원하는 상태</td>
</tr>
<tr>
<td>Live State</td>
<td>클러스터에서 실제 동작 중인 상태</td>
</tr>
<tr>
<td>Drift</td>
<td>Desired 와 Live 의 불일치</td>
</tr>
<tr>
<td>Sync</td>
<td>Desired 를 Live 에 적용</td>
</tr>
<tr>
<td>Self-Heal</td>
<td>Drift 발생 시 자동 복구</td>
</tr>
<tr>
<td>Prune</td>
<td>Git 삭제 리소스를 클러스터에서도 삭제</td>
</tr>
<tr>
<td>Reconciliation</td>
<td>주기적 상태 비교 및 조정 루프</td>
</tr>
</tbody></table>
<br>

<h3 id="06-이중-레포지토리-전략"><strong>06. 이중 레포지토리 전략</strong></h3>
<ul>
<li>GitOps 에서 가장 중요한 패턴 중 하나</li>
<li>소스 코드와 배포 설정을 <strong>반드시 분리</strong>해서 관리</li>
</ul>
<br>

<p>왜 분리해야 하는가?</p>
<ul>
<li>개발자는 소스 코드만 변경해야 함 (배포 설정 실수 방지)</li>
<li>배포 이력을 소스 커밋과 별도로 추적</li>
<li>운영팀이 배포 설정만 별도 검토/승인 가능</li>
</ul>
<pre><code>┌─────────────────────────────────────────────────────────────┐
│                  이중 레포지토리 전략                     │
├───────────────────────────┬─────────────────────────────────┤
│  App Repository         │  Config Repository           │
│  (소스 코드)             │   (k8s 매니페스트)             │
│                         │                              │
│  src/                   │  apps/spring-boot/           │
│  Dockerfile             │    base/                     │
│  pom.xml                │      deployment.yaml         │
│  .github/workflows/     │      service.yaml            │
│    ci.yml  &lt;- CI 전담    │    overlays/                 │
│                         │      dev/                    │
│  변경 주체: 개발자        │      staging/                │
│                         │      prod/                   │
│                         │  argocd/                     │
│                         │    applicationset.yaml       │
│                         │                              │
│                         │  변경 주체: CI 봇 + 운영팀      │
└───────────────────────────┴─────────────────────────────────┘
          |                              |
          | 이미지 빌드 완료                | ArgoCD 가 감시
          | 태그 업데이트 커밋 --------&gt;    |
          v                              v
      ECR / DockerHub              k8s 클러스터</code></pre><br>

<h3 id="01-argocd-컴포넌트-아키텍처"><strong>01. ArgoCD 컴포넌트 아키텍처</strong></h3>
<ul>
<li>ArgoCD 는 단일 프로세스가 아니라 여러 컴포넌트가 역할을 나눠서 동작</li>
<li>각 컴포넌트는 독립된 Pod 로 배포되며 Redis 를 통해 상태를 공유</li>
</ul>
<pre><code>┌─────────────────────────────────────────────────────────────┐
│           ArgoCD 컴포넌트 (argocd Namespace)            │
├─────────────────────────────────────────────────────────────┤
│                                                        │
│  ┌─────────────────────┐   ┌─────────────────────────────┐  │
│  │     API Server    │   │   Application Controller  │  │
│  │  UI / CLI / API   │   │  Reconciliation Loop      │  │
│  │  RBAC 적용         │   │  Desired vs Live 비교     │  │
│  │  SSO 연동          │   │  Sync 실행                │  │
│  └──────────┬──────────┘   └──────────────┬──────────────┘  │
│            │                           │                │
│  ┌──────────┴──────────┐   ┌──────────────┴──────────────┐  │
│  │     Repo Server   │   │           Redis           │  │
│  │  Git clone        │   │  App 상태 캐시             │  │
│  │  매니페스트 렌더링   │   │  세션 관리                 │  │
│  └─────────────────────┘   └─────────────────────────────┘  │
│                                                        │
│  ┌─────────────────────┐                                 │
│  │        Dex        │  SSO / OIDC Provider            │
│  └─────────────────────┘                                 │
└─────────────────────────────────────────────────────────────┘</code></pre><br>

<h4 id="동작-흐름">동작 흐름</h4>
<pre><code>사용자가 App 을 등록하면

  1. API Server
     요청 수신, RBAC 검사
         |
         v
  2. Repo Server
     Config Repo 에서 YAML 가져와서 렌더링
         |
         v
  3. Application Controller
     Git YAML 과 클러스터 상태 비교
     차이 있으면 Sync 실행
         |
         v
  4. Redis
     결과 상태 캐시 저장</code></pre><br>

<h3 id="매니페스트-관리--kustomize">매니페스트 관리 — Kustomize</h3>
<h3 id="01-kustomize란"><strong>01. Kustomize란</strong></h3>
<ul>
<li>Kustomize는 <strong>템플릿 없이 YAML을 오버레이 방식으로 커스터마이징</strong>하는 도구</li>
<li>kubectl에 내장되어 있고 ArgoCD가 기본 지원</li>
</ul>
<br>

<h3 id="02-kustomize-핵심-문법"><strong>02. Kustomize 핵심 문법</strong></h3>
<ul>
<li>kustomization.yaml이 핵심 파일</li>
<li>resources, images, patches, namespace 4가지 필드를 중심으로 구성</li>
</ul>
<br>

<h3 id="03-applicationset--멀티-환경-자동화"><strong>03. ApplicationSet — 멀티 환경 자동화</strong></h3>
<ul>
<li>ApplicationSet은 하나의 템플릿으로 여러 Application을 자동 생성</li>
<li>Config Repo 디렉토리 구조만으로 dev/staging/prod 환경을 자동 감지</li>
</ul>
<br>

<h3 id="매니페스트-관리--helm-서드파티-패키지">매니페스트 관리 — Helm (서드파티 패키지)</h3>
<h3 id="01-helm이란"><strong>01. Helm이란</strong></h3>
<ul>
<li>Helm은 Kubernetes의 <strong>패키지 매니저</strong></li>
<li>Prometheus, Grafana, ArgoCD 자체 설치 등 서드파티 패키지에 주로 사용</li>
</ul>
<br>

<h3 id="02-prometheus--grafana-설치-kube-prometheus-stack"><strong>02. Prometheus + Grafana 설치 (kube-prometheus-stack)</strong></h3>
<ul>
<li>kube-prometheus-stack은 Prometheus Operator + Grafana + AlertManager + node-exporter를 묶은 Helm Chart</li>
</ul>
<hr>
<h3 id="github-actions-ci--argocd-cd-연동">GitHub Actions CI + ArgoCD CD 연동</h3>
<h3 id="01-cicd-역할-분리"><strong>01. CI/CD 역할 분리</strong></h3>
<ul>
<li>GitHub Actions 는 CI(빌드, 테스트, 이미지 Push) 만 담당하고 CD(배포) 는 ArgoCD 가 담당</li>
<li>CI 가 Config Repo 의 이미지 태그만 업데이트하면 ArgoCD 가 감지하고 자동으로 배포</li>
</ul>
<pre><code>gitops-app (App Repo)
STS: C:\CE\99.practice\argocd-lab\gitops-app\

  개발자 코드 Push
       |
       v
  GitHub Actions CI
    테스트 실행
    Docker 이미지 빌드
    DockerHub Push (sha-xxxxxxx 태그)
    gitops-config 이미지 태그 업데이트 [skip ci]
       |
       v
gitops-config (Config Repo)
/home/k8s/argocd-lab/gitops-config/

  apps/gitops-app/overlays/dev/kustomization.yaml
    images:
      newTag: sha-xxxxxxx  &lt;- 업데이트됨
       |
       v
  ArgoCD 3분 폴링 감지
       |
       v
  k8s 클러스터 자동 배포</code></pre><br>

<h4 id="argocd-접속">ArgoCD 접속</h4>
<pre><code class="language-bash"># port-forward 실행
kubectl port-forward svc/argocd-server \
  -n argocd 8080:443 --address=0.0.0.0 &amp;

# 호스트 PC 브라우저에서 접속: https://192.168.56.10:8080

# CLI 비밀번호 확인
ARGOCD_PASS=$(argocd admin initial-password -n argocd | head -1)
echo &quot;초기 비밀번호: $ARGOCD_PASS&quot;</code></pre>
<br>

<h3 id="spring-boot-멀티-앱-ci--분산-호출-gitops-완성">Spring Boot 멀티 앱 CI — 분산 호출 GitOps 완성</h3>
<h3 id="01-spring-boot-소스-대체"><strong>01. Spring Boot 소스 대체</strong></h3>
<ul>
<li>매니페스트 골격은 완성</li>
<li>그 위에 <strong>Spring Boot 소스 코드</strong>를 얹어서 두 앱 간 분산 호출이 동작하는 시스템 구현</li>
</ul>
<br>

<h4 id="동작-흐름-1"><strong>동작 흐름</strong></h4>
<pre><code>[개발자]
  ↓ git push (gitops-app, ops-app1/ 변경)
[GitHub Actions]
  ↓ ci-ops-app1.yml만 트리거 (paths filter)
  ↓ Maven 빌드·테스트·Docker 빌드
  ↓ DockerHub Push (sha-xxxxxxx 태그)
  ↓ gitops-config의 ops-app1/overlays/dev/kustomization.yaml 업데이트
[ArgoCD]
  ↓ Config Repo 변경 감지 (또는 Refresh)
  ↓ ops-app1-dev Application 자동 Sync
[ops-app1 Pod]
  ↓ 새 이미지로 롤링 배포
  ↓ ops-app1 → ops-app2 분산 호출 가능
[사용자]
  → kubectl port-forward로 외부 접근 → 응답 확인</code></pre><br>

<h3 id="02-app-repo-디렉토리-구조--모노리포"><strong>02. App Repo 디렉토리 구조 — 모노리포</strong></h3>
<ul>
<li>gitops-app 저장소를 <strong>모노리포</strong>로 구성</li>
<li>두 앱 소스가 한 저장소에 공존하지만 각 앱의 변경만 해당 워크플로우 트리거</li>
</ul>
<br>

<h4 id="모노리포-장점">모노리포 장점</h4>
<pre><code>1. 한 저장소에서 두 앱 동시 작업 가능
   - 두 앱이 함께 변경되는 기능 개발 (예: API 스펙 변경)
   - 한 PR로 두 앱 변경 묶음 관리

2. CI/CD 자동화 패턴 통일
   - 같은 워크플로우 패턴을 두 앱에 일관 적용
   - paths filter로 변경된 앱만 CI 실행

3. 의존성 버전 통일
   - 두 앱 Spring Boot·JDK 버전 자동 일치

4. 코드 공유 용이
   - 공통 DTO·예외 클래스 등을 한 저장소에서 관리 (향후 확장)</code></pre><hr>
<h3 id="eks-gitops">EKS GitOps</h3>
<h3 id="01-eks에서의-gitops-확인"><strong>01. EKS에서의 GitOps 확인</strong></h3>
<ul>
<li>로컬 kubeadm 환경에서 완성한 GitOps 환경(ArgoCD + Spring Boot 두 앱)을 AWS EKS 운영 환경 이식</li>
<li><strong>GitOps 구조는 동일, 인프라만 교체</strong>. 외부 노출은 K8s 표준 <strong>Gateway API</strong>(Ingress 미사용)</li>
</ul>
<br>

<h4 id="로컬-환경-vs-eks-환경-차이점">로컬 환경 vs EKS 환경 차이점</h4>
<pre><code>                       로컬 환경             EKS 환경
─────────────────────────────────────────────────────────────
인프라 구축             VirtualBox·UTM        Terraform (Bastion+VPC+EKS+IRSA)
K8s 설치                kubeadm               EKS 1.35 + Managed Node
이미지 저장소            DockerHub             ECR (Private, IMMUTABLE)
CI 인증                 Token (Access Key)    GitHub OIDC (Access Key 제거)
ArgoCD UI 접근          port-forward          Gateway API HTTPRoute
앱 외부 노출            port-forward           Gateway API (ALB)
외부 노출 방식         Ingress (수동 NodePort)  Gateway API CRD 5종
Secret 관리             매니페스트 평문          SSM Parameter + ESO
IAM·IRSA                 없음                 Terraform 자동 Role 생성
EBS Storage             hostPath              EBS CSI Driver + gp3

핵심: 매니페스트·CI·ArgoCD 구조는 동일, 인프라만 교체</code></pre><br>

<h3 id="external-secrets-operator"><strong>External Secrets Operator</strong></h3>
<ul>
<li>쿠버네티스 외부의 시크릿 저장소(AWS Secrets Manager, SSM Parameter Store, HashiCorp Vault, GCP Secret Manager 등)에 보관된 값을 클러스터 내부 Secret 리소스로 동기화하는 오퍼레이터</li>
</ul>
<pre><code>┌──────────────────────────────────────────────────────────────┐
│                      AWS Secrets Manager                │
│         secret name: prod/order-service/db              │
│         { &quot;username&quot;: &quot;admin&quot;, &quot;password&quot;: &quot;***&quot; }      │
└───────────────────────────────┬──────────────────────────────┘
                             │ (1) IRSA로 인증 후 GetSecretValue
                             ▼
┌──────────────────────────────────────────────────────────────┐
│                  External Secrets Operator              │
│                                                         │
│ ClusterSecretStore ──── 인증/엔드포인트 정의 (클러스터 공용) │
│         ▲                                               │
│         │ 참조                                           │
│  ExternalSecret ─────── 어떤 외부 키 → 어떤 K8s Secret으로  │
│         │                                               │
│         ▼ (2) refreshInterval 마다 동기화                 │
│   Secret (Opaque)                                       │
└───────────────────────────────┬──────────────────────────────┘
                             │ (3) volume / envFrom
                             ▼
                         Application Pod</code></pre><table>
<thead>
<tr>
<th>리소스</th>
<th>스코프</th>
<th>역할</th>
</tr>
</thead>
<tbody><tr>
<td><code>SecretStore</code></td>
<td>Namespace</td>
<td>해당 네임스페이스 전용 외부 저장소 연결 정의</td>
</tr>
<tr>
<td><code>ClusterSecretStore</code></td>
<td>Cluster</td>
<td>모든 네임스페이스에서 공용으로 참조 가능한 연결 정의</td>
</tr>
<tr>
<td><code>ExternalSecret</code></td>
<td>Namespace</td>
<td>외부 키 ↔ K8s Secret 매핑 및 동기화 정책 정의</td>
</tr>
<tr>
<td><code>Secret</code></td>
<td>Namespace</td>
<td>ESO가 생성·갱신하는 결과물(Opaque 타입)</td>
</tr>
</tbody></table>
<br>

<pre><code>운영 환경 이식의 핵심 통찰

  1. GitOps 자산의 80%는 그대로 이식
     - Spring Boot 소스 100% 동일
     - Kustomize base 100% 동일
     - ApplicationSet 구조 동일

  2. 변경 영역
     - Image URL (DockerHub → ECR)
     - Namespace 이름 (env 접두사)
     - 외부 노출 방식 (port-forward → Gateway API)
     - Secret 관리 (평문 → ESO + SSM)
     - CI 인증 (Access Key → OIDC)

  3. 인프라 영역
     - 로컬: VirtualBox·UTM 수동 관리
     - 운영: Terraform IaC 자동 관리
       (ALB Controller·EBS CSI·ESO IRSA + ECR + OIDC)</code></pre><br>

<h3 id="eks-prometheus--grafana--servicemonitor·골든-시그널·분산-호출-관찰">EKS Prometheus + Grafana — ServiceMonitor·골든 시그널·분산 호출 관찰</h3>
<h3 id="01-eks-환경의-멀티-앱-모니터링-풀스택"><strong>01. EKS 환경의 멀티 앱 모니터링 풀스택</strong></h3>
<ul>
<li>EKS 위에 <strong>kube-prometheus-stack을 신규 설치</strong>하고 ops-app1·ops-app2의 메트릭을 통합 수집하여 멀티 앱 분산 환경의 골든 시그널 관찰</li>
<li>부하 시뮬레이션 → 4가지 핵심 지표 변화 → 분산 호출 흐름 추적까지 완성</li>
</ul>
<br>

<h4 id="동작-흐름-2">동작 흐름</h4>
<pre><code>[ops-app1 Pod / ops-app2 Pod]
  ↓ Actuator + Micrometer가 /actuator/prometheus 엔드포인트 노출
  ↓ http_server_requests_seconds, jvm_memory_used_bytes 등 자동 수집

[Prometheus]
  ↑ ServiceMonitor를 통해 15초마다 스크랩
  ↑ Service 라벨 selector로 두 앱 Pod 자동 발견

[Grafana]
  ↑ Prometheus를 데이터소스로 시각화
  ↑ JVM 대시보드·Spring Boot 대시보드 자동 Import
  ↑ 골든 시그널 4가지 패널 사용자 정의

[부하 시뮬레이션]
  curl /load?type=slow  → Latency 폭증 관찰
  curl /load?type=error → 5xx 비율 폭증 관찰
  curl /load?type=cpu   → CPU 사용률 폭증 관찰</code></pre><br>

<h3 id="02-kube-prometheus-stack-설치"><strong>02. kube-prometheus-stack 설치</strong></h3>
<ul>
<li>EKS 클러스터(monitoring 네임스페이스)에 kube-prometheus-stack을 Helm으로 신규 설치</li>
</ul>
<pre><code>kube-prometheus-stack (단일 Helm Chart로 모든 포함)
├── Prometheus Operator   CRD 관리 (ServiceMonitor·PrometheusRule·Alertmanager)
├── Prometheus            메트릭 수집·저장 (15초 스크랩, 7일 보관)
├── Grafana               시각화 대시보드
├── AlertManager          알림 관리 (학습 환경 비활성)
├── node-exporter         EKS 노드 메트릭
└── kube-state-metrics    K8s 오브젝트 메트릭 (Pod·Deployment 등)</code></pre><br>

<h3 id="03-spring-boot-actuator--micrometer-prometheus"><strong>03. Spring Boot Actuator + Micrometer Prometheus</strong></h3>
<ul>
<li>Spring Boot Actuator의 기본 메트릭(HTTP 요청·JVM 메모리·GC 등)을 Prometheus 형식으로 노출하려면 micrometer-registry-prometheus 의존성 1개 추가</li>
<li>추가 코드 작성 없이 /actuator/prometheus 엔드포인트 자동 생성</li>
</ul>
<br>

<h4 id="자동-노출되는-핵심-메트릭">자동 노출되는 핵심 메트릭</h4>
<pre><code>[HTTP 요청 메트릭 — 골든 시그널의 핵심]
  http_server_requests_seconds_count
    {application=&quot;ops-app1&quot;, method=&quot;GET&quot;, uri=&quot;/api/orders&quot;, status=&quot;200&quot;} 42

  http_server_requests_seconds_sum
    {application=&quot;ops-app1&quot;, ...} 1.234

  http_server_requests_seconds_bucket    (Histogram)
    {application=&quot;ops-app1&quot;, le=&quot;0.5&quot;} 38
    {application=&quot;ops-app1&quot;, le=&quot;1.0&quot;} 41
    {application=&quot;ops-app1&quot;, le=&quot;2.0&quot;} 42

[JVM 메모리 메트릭]
  jvm_memory_used_bytes{area=&quot;heap&quot;, id=&quot;...&quot;}
  jvm_memory_max_bytes{area=&quot;heap&quot;, id=&quot;...&quot;}
  jvm_gc_pause_seconds_count
  jvm_threads_live_threads

[시스템 메트릭]
  system_cpu_usage
  process_cpu_usage
  system_load_average_1m
  process_uptime_seconds

[Tomcat 메트릭]
  tomcat_sessions_active_current_sessions
  tomcat_threads_busy_threads
  tomcat_threads_current_threads</code></pre><br>

<h3 id="04-servicemonitor-crd--prometheus-자동-스크랩"><strong>04. ServiceMonitor CRD — Prometheus 자동 스크랩</strong></h3>
<ul>
<li>ServiceMonitor는 Prometheus Operator의 CRD. K8s Service의 어떤 포트·경로에서 메트릭을 가져올지 선언</li>
<li>Service 라벨 selector로 자동 발견 — 새 Pod 추가/삭제 시 즉시 반영</li>
</ul>
<br>

<h4 id="servicemonitor-동작-흐름">ServiceMonitor 동작 흐름</h4>
<pre><code>[ServiceMonitor 생성]
  apiVersion: monitoring.coreos.com/v1
  selector.matchLabels: app.kubernetes.io/name=ops-app1
       ↓
[Prometheus Operator]
  - ServiceMonitor CRD 감시
  - Service selector 매칭
  - Prometheus 설정에 추가
       ↓
[Prometheus]
  - 15초마다 매칭된 Service의 모든 Pod의 /actuator/prometheus 스크랩
  - Pod 추가/삭제 자동 반영
       ↓
[메트릭 수집]
  http_server_requests_seconds_count{
    application=&quot;ops-app1&quot;,
    namespace=&quot;ops-app1-dev&quot;,
    pod=&quot;ops-app1-xxx-yyy&quot;
  }</code></pre><br>

<h3 id="05-sre-golden-signals-4가지"><strong>05. SRE Golden Signals 4가지</strong></h3>
<ul>
<li>Google SRE Book이 정의한 운영의 핵심 4가지 지표</li>
</ul>
<pre><code>SRE 4 Golden Signals

  1. Latency (지연시간)
     - p50 / p95 / p99 응답 시간
     - 사용자 경험의 핵심
     - 본 강의 측정: /load?type=slow → 폭증

  2. Traffic (트래픽)
     - 초당 요청 수 (RPS)
     - 시스템 부하 측정
     - 본 강의 측정: 반복 요청 → 증가

  3. Errors (에러)
     - 4xx, 5xx 비율
     - 정상성 측정
     - 본 강의 측정: /load?type=error → 5xx 폭증

  4. Saturation (포화도)
     - CPU·메모리·디스크·스레드풀 등 리소스 사용률
     - 한계 도달 임박 지표
     - 본 강의 측정: /load?type=cpu → CPU 폭증</code></pre><br>

<h4 id="이상-패턴-식별">이상 패턴 식별</h4>
<table>
<thead>
<tr>
<th>패턴</th>
<th>의심 원인</th>
<th>조사 방향</th>
</tr>
</thead>
<tbody><tr>
<td>Latency 만 증가</td>
<td>DB 쿼리 느려짐, GC 증가</td>
<td>DB·GC 로그 확인</td>
</tr>
<tr>
<td>Latency + Errors 증가</td>
<td>다운스트림 장애</td>
<td>의존 서비스 확인</td>
</tr>
<tr>
<td>Traffic 급증 + Saturation 증가</td>
<td>트래픽 폭증</td>
<td>스케일 아웃 검토</td>
</tr>
<tr>
<td>Errors만 증가</td>
<td>특정 API 버그</td>
<td>최근 배포 롤백 검토</td>
</tr>
<tr>
<td>Saturation만 증가</td>
<td>메모리 누수</td>
<td>Heap dump 분석</td>
</tr>
<tr>
<td>ops-app1 5xx + ops-app2 정상</td>
<td>ops-app1 자체 문제</td>
<td>ops-app1 로그</td>
</tr>
<tr>
<td>ops-app1 5xx + ops-app2 5xx</td>
<td>ops-app2 장애 → ops-app1 영향</td>
<td>ops-app2 우선 조치</td>
</tr>
</tbody></table>
<br>

<h3 id="cloudwatch-기초·메트릭·로그">CloudWatch 기초·메트릭·로그</h3>
<h3 id="01-cloudwatch"><strong>01. CloudWatch</strong></h3>
<ul>
<li>AWS의 통합 모니터링·관측 가능성 서비스</li>
<li>3대 데이터(Metric·Log·Event)와 부가 기능(Alarm·Dashboard·Insights·EventBridge)으로 구성</li>
<li>AWS 모든 서비스가 기본 메트릭을 자동 전송하며 사용자 정의 메트릭·로그도 수집 가능</li>
</ul>
<pre><code>CloudWatch 전체 구조

┌──────────────────────────────────────────────────────────────────┐
│                     CloudWatch 핵심 데이터                   │
│                                                            │
│  Metric (메트릭)         Log (로그)         Event (이벤트)    │
│  - 시계열 숫자 데이터     - 텍스트 로그       - 상태 변경 이벤트  │
│  - 네임스페이스·차원      - Log Group 단위   - EventBridge 통합│
│  - 통계 (Avg/Sum/p99)   - Logs Insights    - 자동 대응 가능   │
│                                                            │
└──────────────────────────────────────────────────────────────────┘
                          │
                          ▼
┌─────────────────────────────────────────────────────────────────┐
│                     CloudWatch 부가 기능                    │
│                                                            │
│   Alarm                   Dashboard            Insights    │
│   - 임계치 기반 알림        - 시각화·공유         - 로그 쿼리   │
│   - SNS → Slack 등        - 위젯 조합           - 패턴 분석   │
│                                                            │
│  EventBridge           Container Insights   Logs Insights  │
│  - 자동 대응 규칙        - EKS 통합 모니터링    - SQL 유사 쿼리 │
│  - Lambda·SNS 트리거                                        │
└─────────────────────────────────────────────────────────────────┘</code></pre><br>

<h4 id="모니터링-vs-알람-vs-감사-구분">모니터링 vs 알람 vs 감사 구분</h4>
<pre><code>**모니터링 (Monitoring)**
  - 현재·과거 상태를 시각화
  - 대시보드·메트릭 차트
  - 트렌드 분석·용량 계획

**알람 (Alerting)**
  - 임계치 기반 자동 알림
  - 운영자에게 행동 요구
  - SNS·Slack·이메일 전송

**감사 (Auditing)**
  - 누가·언제·무엇을 했는가
  - CloudTrail (API 호출)
  - 컴플라이언스·포렌식</code></pre><br>

<h3 id="02-cloudwatch-metric--시계열-수집·저장"><strong>02. CloudWatch Metric — 시계열 수집·저장</strong></h3>
<ul>
<li>CloudWatch Metric은 시계열 숫자 데이터</li>
<li>모든 AWS 서비스가 기본 메트릭을 자동 전송. 네임스페이스(AWS/EC2 등)·차원(InstanceId 등)·통계(Avg/Sum/Max/p99)로 구조화</li>
</ul>
<br>

<h4 id="메트릭-구조">메트릭 구조</h4>
<pre><code>네임스페이스 (Namespace)
  - AWS 서비스 단위 (AWS/EC2, AWS/RDS, AWS/EKS)
  - 사용자 정의 (Custom/MyApp)

메트릭 이름 (Metric Name)
  - CPUUtilization, NetworkIn 등

차원 (Dimension)
  - 메트릭을 구분하는 키-값 쌍
  - 예: InstanceId=i-xxx, AutoScalingGroupName=asg-1
  - 최대 30개까지

통계 (Statistic)
  - Average, Sum, Maximum, Minimum, SampleCount
  - 백분위수: p50, p90, p95, p99</code></pre><br>

<h4 id="표준-메트릭-vs-상세-모니터링">표준 메트릭 vs 상세 모니터링</h4>
<pre><code>EC2 기본 모니터링 (Basic, 5분 주기)
  - 무료
  - 5분 데이터 포인트

EC2 상세 모니터링 (Detailed, 1분 주기)
  - 인스턴스당 약 $2.10/월
  - 1분 데이터 포인트
  - 빠른 이상 감지·HPA·CloudWatch Alarm 정밀도 ↑

EKS·ECS·Lambda 등은 기본적으로 1분 주기 메트릭</code></pre><br>

<h3 id="03-cloudwatch-logs--로그-수집·저장·조회"><strong>03. CloudWatch Logs — 로그 수집·저장·조회</strong></h3>
<ul>
<li>CloudWatch Logs는 텍스트 로그를 Log Group·Log Stream 구조로 수집·저장</li>
</ul>
<br>

<h4 id="log-group--log-stream-구조">Log Group / Log Stream 구조</h4>
<pre><code>Log Group (로그 그룹)
  - 보존 기간·암호화·구독 등 설정 단위
  - 예: /aws/eks/ops-platform-eks/cluster
        /aws/lambda/my-function
        /aws/containerinsights/&lt;cluster&gt;/application

Log Stream (로그 스트림)
  - 단일 소스의 시간 순 로그
  - 예: ec2/i-xxx
        pod/ops-app1/&lt;container-id&gt;</code></pre><br>

<h3 id="04-dashboard--시각화·공유"><strong>04. Dashboard — 시각화·공유</strong></h3>
<ul>
<li>메트릭·로그를 한 화면에서 시각화</li>
</ul>
<br>

<h4 id="위젯-종류">위젯 종류</h4>
<table>
<thead>
<tr>
<th>위젯</th>
<th>용도</th>
</tr>
</thead>
<tbody><tr>
<td>Line Graph</td>
<td>시계열 추세 (가장 일반)</td>
</tr>
<tr>
<td>Stacked Area</td>
<td>누적 트렌드</td>
</tr>
<tr>
<td>Number</td>
<td>단일 숫자 값 (KPI)</td>
</tr>
<tr>
<td>Gauge</td>
<td>백분율 표시 (한도 대비)</td>
</tr>
<tr>
<td>Bar Graph</td>
<td>비교 분석</td>
</tr>
<tr>
<td>Pie Chart</td>
<td>비율 분석</td>
</tr>
<tr>
<td>Logs Insights</td>
<td>쿼리 결과 임베드</td>
</tr>
<tr>
<td>Text (Markdown)</td>
<td>설명·문서</td>
</tr>
</tbody></table>
<br>

<h3 id="container-insights"><strong>Container Insights</strong></h3>
<ul>
<li>EKS 클러스터의 노드/Pod/컨테이너/네임스페이스 단위 메트릭과 로그를 자동 수집하여 CloudWatch로 통합 전송하는 관리형 옵저버빌리티 서비스</li>
<li>Add-on 한 번 설치로 80여 개 지표가 자동 활성화</li>
</ul>
<br>

<h4 id="control-plane-metrics와의-차이">Control Plane Metrics와의 차이</h4>
<pre><code>┌──────────────────────────────────────────────────────────────┐
│  EKS CloudWatch 지표 체계                                │
├──────────────────────────────────────────────────────────────┤
│  [A] Control Plane Metrics  ── EKS 클러스터 옵션에서 활성화 │
│      ├─ Namespace: AWS/EKS                              │
│      ├─ apiserver_*, scheduler_*, etcd_*                │
│      └─ &quot;클러스터 자체가 건강한가&quot; 진단                     │
│                                                         │
│  [B] Container Insights     ── Add-on 별도 설치          │
│      ├─ Namespace: ContainerInsights                    │
│      ├─ cluster_*, node_*, pod_*, container_*, service_*│
│      └─ &quot;워크로드와 노드가 건강한가&quot; 진단                    │
└──────────────────────────────────────────────────────────────┘</code></pre><br>

<h3 id="cloudwatch-alarms--logs-insights--알람·자동화·로그-분석">CloudWatch Alarms + Logs Insights — 알람·자동화·로그 분석</h3>
<h3 id="01-cloudwatch-alarm-동작-원리"><strong>01. CloudWatch Alarm 동작 원리</strong></h3>
<ul>
<li>Alarm은 메트릭이 임계치를 초과하면 자동으로 알림을 발송하거나 AWS 동작을 트리거하는 기능</li>
<li>운영 자동화의 핵심. SNS·EventBridge·Auto Scaling·EC2 Action 등 다양한 대상에 연결 가능</li>
</ul>
<br>

<h4 id="alarm-상태-3종">Alarm 상태 3종</h4>
<pre><code>OK
  - 임계치 안에 있음 (정상)
  - 알림 발송 없음

ALARM
  - 임계치 초과 (이상)
  - SNS·EventBridge로 알림 발송
  - 상태 변경 시 1회 발송 (지속 발송 아님)

INSUFFICIENT_DATA
  - 데이터 부족 (메트릭 수집 안 됨)
  - 운영 환경에서 알림 발송 정책 결정 필요</code></pre><br>

<h3 id="02-sns--slack-알림-체계-콘솔-필수"><strong>02. SNS + Slack 알림 체계 (콘솔 필수)</strong></h3>
<ul>
<li>CloudWatch Alarm은 직접 Slack 발송 안 함</li>
<li>SNS(Simple Notification Service)를 거쳐서 알림 라우팅</li>
</ul>
<br>

<h4 id="알림-흐름">알림 흐름</h4>
<pre><code>[CloudWatch Alarm]
  ↓ 상태 변경 시 발행
[SNS Topic]
  ↓ 구독자에게 fan-out
  ├──→ Email (직접 구독)
  ├──→ SMS
  ├──→ Lambda (자동 대응)
  ├──→ SQS (지연 처리)
  └──→ AWS Chatbot
       ↓
       └──→ Slack 채널</code></pre><br>

<h3 id="03-eventbridge-자동화"><strong>03. EventBridge 자동화</strong></h3>
<ul>
<li>EventBridge는 AWS의 이벤트 라우팅 서비스</li>
<li>자가 치유(Self Healing) 패턴의 핵심</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[멋쟁이사자처럼부트캠프 클라우드 엔지니어링 5기 [16주차]]]></title>
            <link>https://velog.io/@its-jihyeon/%EB%A9%8B%EC%9F%81%EC%9D%B4%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A7%81-5%EA%B8%B0-16%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@its-jihyeon/%EB%A9%8B%EC%9F%81%EC%9D%B4%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A7%81-5%EA%B8%B0-16%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Sat, 16 May 2026 06:30:03 GMT</pubDate>
            <description><![CDATA[<h3 id="kubernetes-스케줄링-scheduling"><strong>Kubernetes 스케줄링 (Scheduling)</strong></h3>
<h3 id="01-스케줄링-개요"><strong>01. 스케줄링 개요</strong></h3>
<ul>
<li><strong>스케줄링(Scheduling)</strong> 은 새로 생성된 Pod을 어느 노드에 실행할지 결정하는 과정</li>
</ul>
<pre><code>                         ┌─────────────────────────────────┐
   사용자 요청             │  kube-scheduler              │
   (Pod 생성)             │                              │
        │                │  1. 미할당 Pod 감지            │
        │                │ 2. Filtering (가능한 노드 선별) │
        ▼                │  3. Scoring (점수로 순위 매김)  │
   ┌─────────┐            │  4. Binding (최종 배정)        │
   │ K8s API│  ───────►   │                              │
   └─────────┘            └────────────────┬────────────────┘
                                         │
                          ┌───────────────┼───────────────┐
                          ▼              ▼              ▼
                     ┌────────┐     ┌────────┐     ┌────────┐
                     │node-1 │     │ node-2 │     │node-3 │
                     │ (선택) │     │        │    │       │
                     └────────┘     └────────┘     └────────┘</code></pre><br>

<h4 id="스케줄링의-3단계">스케줄링의 3단계</h4>
<pre><code>  1단계 Filtering         2단계 Scoring         3단계 Binding
 ─────────────────       ────────────────       ───────────────
  실행 가능한 노드        각 노드에 점수         최고 점수 노드에
  목록 추출 (큐)          부여 후 순위 결정      Binding 이벤트 발행</code></pre><br>

<h3 id="02-label과-selector"><strong>02. Label과 Selector</strong></h3>
<ul>
<li>스케줄링 동작의 대부분은 <strong>Label</strong>(키-값 메타정보)과 <strong>Selector</strong>(라벨 기반 질의)로 이루어짐</li>
</ul>
<table>
<thead>
<tr>
<th>구분</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>Label</td>
<td>리소스를 식별·그룹화하기 위한 키-값 한 쌍 메타정보</td>
</tr>
<tr>
<td>Selector</td>
<td>라벨 기반 필터링 질의 (key=value, in, notin, exists)</td>
</tr>
</tbody></table>
<br>

<h3 id="03-스케줄링-방법-5가지-비교"><strong>03. 스케줄링 방법 5가지 비교</strong></h3>
<ul>
<li>Kubernetes는 Pod을 특정 노드에 배치하는 5가지 메커니즘을 제공</li>
<li>각각 사용 시점과 강점이 다르므로 상황에 맞게 선택</li>
</ul>
<table>
<thead>
<tr>
<th>방법</th>
<th>특징</th>
<th>장점</th>
<th>단점</th>
</tr>
</thead>
<tbody><tr>
<td><strong>nodeName</strong></td>
<td>노드 이름을 직접 명시 (스케줄러 우회)</td>
<td>가장 단순, 즉시 적용</td>
<td>유연성 없음, 노드 장애 시 복구 불가</td>
</tr>
<tr>
<td><strong>nodeSelector</strong></td>
<td>노드 라벨 기반 필수 매칭</td>
<td>설정 간단</td>
<td>AND 조건만, 표현력 부족</td>
</tr>
<tr>
<td><strong>nodeAffinity</strong></td>
<td>노드 라벨 + 다양한 연산자 + 가중치</td>
<td>required/preferred 구분, In/NotIn/Exists</td>
<td>설정 복잡</td>
</tr>
<tr>
<td><strong>podAffinity / podAntiAffinity</strong></td>
<td>다른 Pod 위치 기반 (같이 / 멀리)</td>
<td>네트워크 최적화, 장애 격리</td>
<td>조건 미충족 시 Pending 위험</td>
</tr>
<tr>
<td><strong>taints + tolerations</strong></td>
<td>노드가 Pod을 거부, Pod이 거부 허용</td>
<td>전용 노드 운영, 노드 격리</td>
<td>양쪽 설정 필요, 디버깅 복잡</td>
</tr>
</tbody></table>
<br>

<h3 id="04-5가지-방법-최종-정리"><strong>04. 5가지 방법 최종 정리</strong></h3>
<pre><code>[Pod을 어느 노드에 둘까?]
        │
        ▼
┌─────────────────────────────────────────────────────┐
│ Q1. 정확히 한 노드에 강제 배치?                    │
│      Yes → nodeName (디버깅 외 비권장)            │
│      No  ↓                                      │
│                                                 │
│ Q2. 단순 라벨 매칭으로 충분?                       │
│      Yes → nodeSelector                         │
│      No  ↓                                      │
│                                                 │
│ Q3. 다양한 연산자/가중치 필요?                      │
│      Yes → nodeAffinity                         │
│      No  ↓                                      │
│                                                 │
│ Q4. 다른 Pod의 위치 기준?                         │
│      Yes → podAffinity / podAntiAffinity        │
│      No  ↓                                      │
│                                                 │
│ Q5. 노드가 특정 Pod만 받게 하려는가?                │
│      Yes → taints + tolerations                 │
└─────────────────────────────────────────────────────┘</code></pre><br>

<h3 id="kubernetes-오토스케일링-autoscaling"><strong>Kubernetes 오토스케일링 (Autoscaling)</strong></h3>
<h3 id="01-오토스케일링-개요"><strong>01. 오토스케일링 개요</strong></h3>
<ul>
<li>워크로드 부하에 따라 클러스터 또는 워크로드 용량을 자동으로 조정하는 기능</li>
</ul>
<br>

<h4 id="오토스케일링-3종-비교">오토스케일링 3종 비교</h4>
<table>
<thead>
<tr>
<th>종류</th>
<th>약어</th>
<th>조정 대상</th>
<th>사용 시점</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Horizontal Pod Autoscaler</strong></td>
<td>HPA</td>
<td>Pod 개수 (replicas)</td>
<td>가장 흔함, 트래픽 변동</td>
</tr>
<tr>
<td><strong>Vertical Pod Autoscaler</strong></td>
<td>VPA</td>
<td>Pod 리소스 (cpu/memory request)</td>
<td>단일 Pod 워크로드, 적정 사이징</td>
</tr>
<tr>
<td><strong>Cluster Autoscaler</strong></td>
<td>CA</td>
<td>노드 개수</td>
<td>클라우드 환경, 자동 증설</td>
</tr>
</tbody></table>
<br>

<h3 id="02-metrics-server"><strong>02. Metrics Server</strong></h3>
<ul>
<li>오토스케일링의 <strong>모든 출발점</strong>은 Metrics Server</li>
<li>Pod·Node의 CPU·Memory 사용량을 수집하여 K8s API 표준 형식으로 노출하며, HPA·VPA 모두 이 데이터를 참조</li>
</ul>
<br>

<h3 id="03-hpa-horizontal-pod-autoscaler"><strong>03. HPA (Horizontal Pod Autoscaler)</strong></h3>
<ul>
<li><strong>HPA</strong>는 Pod의 <strong>개수(replicas)</strong> 를 부하에 따라 자동 조정</li>
</ul>
<br>

<h4 id="hpa-동작-흐름">HPA 동작 흐름</h4>
<pre><code>   ┌─────────────────────────────────────────────────────────┐
   │  1. 사용자가 HPA 리소스 생성                          │
   │     - target: Deployment / StatefulSet / ReplicaSet│
   │     - min/max replicas                             │
   │     - 메트릭과 임계값                                │
   └─────────────────┬───────────────────────────────────────┘
                     │
                     ▼ 15초 주기로 반복
   ┌────────────────────────────────────────────────────────────┐
   │  2. HPA Controller                                    │
   │     - Metrics Server에서 현재 메트릭 조회                │
   │     - 목표 replicas 계산                               │
   │       desiredReplicas = ceil(currentReplicas ×        │
   │                         currentMetric / targetMetric) │
   └─────────────────┬──────────────────────────────────────────┘
                     │
                     ▼
   ┌────────────────────────────────────────────────────────┐
   │  3. Deployment.spec.replicas 변경                  │
   │     → ReplicaSet이 Pod 추가/제거                    │
   └────────────────────────────────────────────────────────┘</code></pre><br>

<h4 id="대상-deployment-작성"><strong>대상 Deployment 작성</strong></h4>
<pre><code class="language-yaml">apiVersion: apps/v1
kind: Deployment
metadata:
  name: scalable-web
  labels:
    app.kubernetes.io/name: scalable-web
spec:
  replicas: 2
  selector:
    matchLabels:
      app.kubernetes.io/name: scalable-web
  template:
    metadata:
      labels:
        app.kubernetes.io/name: scalable-web
    spec:
      containers:
      - name: nginx
        image: nginx:1.25
        resources:
          requests:
            cpu: 50m                        # HPA 계산 기준 (필수)
            memory: 50Mi
          limits:
            cpu: 200m
            memory: 100Mi
        ports:
        - containerPort: 80</code></pre>
<br>

<h4 id="hpa-리소스-작성">HPA 리소스 작성</h4>
<pre><code class="language-yaml">apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: scalable-web-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: scalable-web
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 50              # 목표 CPU 사용률 50%</code></pre>
<br>

<h3 id="04-vpa-vertical-pod-autoscaler"><strong>04. VPA (Vertical Pod Autoscaler)</strong></h3>
<ul>
<li><strong>VPA</strong>는 Pod의 <strong>리소스 요청량(requests/limits)</strong> 을 자동 조정</li>
<li>HPA가 &quot;Pod 개수를 바꾼다&quot;면 VPA는 &quot;Pod 한 개의 크기를 바꾼다&quot;는 차이가 핵심</li>
</ul>
<pre><code class="language-yaml">   ┌─────────────────────────────────────────────┐
   │  VPA Recommender                        │
   │   - Metrics Server 데이터 수집            │
   │   - 사용량 히스토리 분석                   │
   │   - 적정 cpu/memory 추천 계산             │
   └────────────────────┬────────────────────────┘
                        │ 추천값 저장
                        ▼
   ┌─────────────────────────────────────────────┐
   │  VPA Updater                            │
   │   - 1분 주기로 실행 중 Pod 검사            │
   │   - 추천값 범위 벗어나면 Pod 축출(eviction) │
   └────────────────────┬────────────────────────┘
                        │
                        ▼ Pod 재생성 시점
   ┌─────────────────────────────────────────────┐
   │  VPA Admission Webhook (Mutating)       │
   │   - 새 Pod 스펙 가로채기                   │
   │   - resources.requests/limits 수정       │
   │   - 수정된 스펙으로 Pod 생성               │
   └─────────────────────────────────────────────┘</code></pre>
<br>

<h3 id="05-cluster-autoscaler-개념">05. Cluster Autoscaler 개념</h3>
<h4 id="동작-흐름">동작 흐름</h4>
<pre><code>   1. HPA가 Pod 추가 요청 → replicas 증가
            ↓
   2. 노드 리소스 부족 → 새 Pod이 Pending
            ↓
   3. Cluster Autoscaler가 Pending Pod 감지
            ↓
   4. 클라우드 API 호출 (AWS Auto Scaling Group, GCP MIG 등)
            ↓
   5. 새 노드 생성 (1~3분) → Pod이 새 노드에 배치
            ↓
   6. 부하 감소 시 빈 노드 감지 → 노드 제거</code></pre><br>

<h3 id="ubernetes-인증-authentication"><strong>ubernetes 인증 (Authentication)</strong></h3>
<h3 id="01-인증-개요"><strong>01. 인증 개요</strong></h3>
<ul>
<li>클러스터에 접근하려는 주체가 &quot;누구인지&quot;를 증명하는 과정</li>
<li>Kubernetes API Server는 모든 요청에 대해 다음 두 단계를 거침</li>
</ul>
<br>

<h3 id="02-인증-방식-종류"><strong>02. 인증 방식 종류</strong></h3>
<ul>
<li>Kubernetes는 여러 인증 방식을 동시에 지원</li>
<li>API Server는 설정된 인증 모듈을 순서대로 시도하며, 하나라도 성공하면 통과</li>
</ul>
<br>

<h4 id="방식별-비교">방식별 비교</h4>
<table>
<thead>
<tr>
<th>방식</th>
<th>사용 대상</th>
<th>갱신 방법</th>
<th>실무 사용</th>
</tr>
</thead>
<tbody><tr>
<td>x509 인증서</td>
<td>사람(관리자)</td>
<td>인증서 재발급</td>
<td>매우 흔함, kubeadm 기본</td>
</tr>
<tr>
<td>Static Token</td>
<td>사람(테스트용)</td>
<td>파일 수동 변경</td>
<td>비권장 (보안 취약)</td>
</tr>
<tr>
<td>Bootstrap Token</td>
<td>노드(kubeadm join)</td>
<td>TTL 만료 후 재생성</td>
<td>kubeadm 내부 자동</td>
</tr>
<tr>
<td><strong>ServiceAccount Token</strong></td>
<td><strong>Pod(앱)</strong></td>
<td><strong>자동 회전(Bound Token)</strong></td>
<td><strong>가장 흔함</strong></td>
</tr>
<tr>
<td>OIDC</td>
<td>사람(기업)</td>
<td>IDP가 자동 갱신</td>
<td>대규모 조직 표준</td>
</tr>
<tr>
<td>Webhook</td>
<td>사람·앱</td>
<td>외부 시스템 위임</td>
<td>커스텀 인증 시스템 연동</td>
</tr>
</tbody></table>
<br>

<h3 id="03-kubeconfig-구조"><strong>03. kubeconfig 구조</strong></h3>
<ul>
<li>kubeconfig는 kubectl이 어떤 클러스터에, 어떤 사용자로 접속할지 정의한 YAML 파일</li>
</ul>
<pre><code class="language-yaml">apiVersion: v1
kind: Config
clusters:
- name: k8s-cluster                      # 클러스터 별칭
  cluster:
    server: https://172.16.0.10:6443     # API Server 주소
    certificate-authority-data: &lt;BASE64&gt;  # CA 인증서 (서명 검증용)

users:
- name: kubernetes-admin                  # 사용자 별칭
  user:
    client-certificate-data: &lt;BASE64&gt;    # 클라이언트 인증서
    client-key-data: &lt;BASE64&gt;            # 클라이언트 개인키

contexts:
- name: admin@k8s-cluster                 # 컨텍스트 별칭
  context:
    cluster: k8s-cluster                  # 위 clusters 참조
    user: kubernetes-admin                # 위 users 참조
    namespace: default                    # 기본 네임스페이스

current-context: admin@k8s-cluster        # 현재 활성 컨텍스트</code></pre>
<br>

<h3 id="kubernetes-인가--rbac"><strong>Kubernetes 인가 — RBAC</strong></h3>
<h3 id="01-인가authorization-개요"><strong>01. 인가(Authorization) 개요</strong></h3>
<ul>
<li>인증된 사용자가 &quot;어떤 리소스에 어떤 동작을 할 수 있는지&quot;를 결정하는 과정</li>
</ul>
<br>

<h4 id="인가-모듈-종류">인가 모듈 종류</h4>
<table>
<thead>
<tr>
<th>모듈</th>
<th>용도</th>
<th>사용 빈도</th>
</tr>
</thead>
<tbody><tr>
<td><strong>RBAC</strong></td>
<td><strong>역할 기반 권한 부여</strong></td>
<td><strong>표준 (거의 항상)</strong></td>
</tr>
<tr>
<td>Node</td>
<td>kubelet → API 통신 권한</td>
<td>kubeadm 자동 설정</td>
</tr>
<tr>
<td>ABAC</td>
<td>속성 기반 (정책 파일)</td>
<td>거의 안 씀 (Deprecated 추세)</td>
</tr>
<tr>
<td>Webhook</td>
<td>외부 시스템 위임</td>
<td>커스텀 정책 엔진 연동</td>
</tr>
<tr>
<td>AlwaysAllow</td>
<td>모든 요청 허용</td>
<td>테스트 전용 (절대 운영 금지)</td>
</tr>
<tr>
<td>AlwaysDeny</td>
<td>모든 요청 거부</td>
<td>테스트 전용</td>
</tr>
</tbody></table>
<br>

<h3 id="02-rbac-4대-리소스">02. RBAC 4대 리소스</h3>
<table>
<thead>
<tr>
<th>리소스</th>
<th>스코프</th>
<th>정의 대상</th>
<th>바인딩 가능</th>
</tr>
</thead>
<tbody><tr>
<td>Role</td>
<td>Namespace</td>
<td>해당 NS의 리소스 권한</td>
<td>RoleBinding</td>
</tr>
<tr>
<td>ClusterRole</td>
<td>Cluster</td>
<td>전역 권한 + 클러스터 스코프 리소스</td>
<td>RoleBinding 또는 ClusterRoleBinding</td>
</tr>
<tr>
<td>RoleBinding</td>
<td>Namespace</td>
<td>사용자/그룹/SA에 권한 부여</td>
<td>Role 또는 ClusterRole 참조</td>
</tr>
<tr>
<td>ClusterRoleBinding</td>
<td>Cluster</td>
<td>사용자/그룹/SA에 전역 권한 부여</td>
<td>ClusterRole만 참조</td>
</tr>
</tbody></table>
<br>

<h3 id="03-role--clusterrole-정의"><strong>03. Role / ClusterRole 정의</strong></h3>
<ul>
<li>Role과 ClusterRole은 &quot;어떤 리소스(resources)에 대해 어떤 동작(verbs)을 허용할지&quot;를 명시</li>
</ul>
<br>

<h4 id="role-예시">Role 예시</h4>
<pre><code class="language-yaml">apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: dev                # ← Role은 반드시 네임스페이스 지정
  name: pod-reader
rules:
- apiGroups: [&quot;&quot;]               # ← &quot;&quot; = core API group (Pod, Service 등)
  resources: [&quot;pods&quot;]           # ← 대상 리소스
  verbs: [&quot;get&quot;, &quot;list&quot;, &quot;watch&quot;]  # ← 허용 동작
- apiGroups: [&quot;&quot;]
  resources: [&quot;pods/log&quot;]       # ← 서브리소스 (Pod 로그 조회)
  verbs: [&quot;get&quot;]</code></pre>
<br>

<h4 id="clusterrole-예시">ClusterRole 예시</h4>
<pre><code class="language-yaml">apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: node-reader              # ← 클러스터 스코프 (namespace 필드 없음)
rules:
- apiGroups: [&quot;&quot;]
  resources: [&quot;nodes&quot;]           # ← Node는 클러스터 스코프 리소스
  verbs: [&quot;get&quot;, &quot;list&quot;, &quot;watch&quot;]
- apiGroups: [&quot;metrics.k8s.io&quot;]
  resources: [&quot;nodes&quot;]
  verbs: [&quot;get&quot;, &quot;list&quot;]</code></pre>
<br>

<h3 id="04-rolebinding--clusterrolebinding"><strong>04. RoleBinding / ClusterRoleBinding</strong></h3>
<ul>
<li>RoleBinding은 &quot;누구(subjects)에게 어떤 Role(roleRef)을 부여하는지&quot;를 정의</li>
</ul>
<br>

<h4 id="rolebinding-예시">RoleBinding 예시</h4>
<pre><code class="language-yaml">apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  namespace: dev                  # ← 어느 네임스페이스에서 효력 발생
  name: alice-pod-reader-binding
subjects:
- kind: User                       # ← User / Group / ServiceAccount
  name: alice                      # ← 인증서 CN과 동일
  apiGroup: rbac.authorization.k8s.io
- kind: Group
  name: dev-team                   # ← 인증서 O와 동일 (그룹 단위 부여)
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role                       # ← Role 또는 ClusterRole
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io</code></pre>
<hr>
<h3 id="eks-개념-이해"><strong>EKS 개념 이해</strong></h3>
<h3 id="01-왜-eks가-필요한가"><strong>01. 왜 EKS가 필요한가</strong></h3>
<ul>
<li>로컬 클러스터(kubeadm)는 Control Plane을 직접 설치하고 운영</li>
<li>EKS는 Control Plane을 AWS가 완전 관리하므로 운영 부담이 없음</li>
</ul>
<pre><code>[ 로컬 클러스터 vs EKS 책임 범위 비교 ]

  로컬 (kubeadm)                     EKS
  ┌───────────────────────┐           ┌───────────────────────────────────┐
  │  사용자 직접 관리     │           │  AWS 관리 영역                   │
  │  ┌──────────────────┐ │           │  ┌──────────────────────────────┐ │
  │  │ Control Plane  │ │           │  │ EKS Control Plane          │ │
  │  │ kube-apiserver │ │  ──────▶  │  │ kube-apiserver (Multi-AZ)  │ │
  │  │ etcd           │ │           │  │ etcd (자동 백업)            │ │
  │  │ scheduler      │ │           │  │ scheduler / controller-mgr │ │
  │  │ controller-mgr │ │           │  └──────────────────────────────┘ │
  │  └──────────────────┘ │           │                                │
  │  ┌──────────────────┐ │           │  사용자 관리 영역                │
  │  │  Worker Node   │ │           │  ┌──────────────────────────────┐ │
  │  │  kubelet       │ │           │  │ Data Plane (EC2 / Fargate) │ │
  │  │  kube-proxy    │ │           │  │ kubelet / kube-proxy       │ │
  │  │  containerd    │ │           │  │ [Pods]                     │ │
  │  └──────────────────┘ │           │  └──────────────────────────────┘ │
  └───────────────────────┘           └───────────────────────────────────┘</code></pre><br>

<h3 id="02-eks-아키텍처"><strong>02. EKS 아키텍처</strong></h3>
<ul>
<li>EKS Control Plane은 AWS가 관리하는 별도 VPC에서 실행</li>
<li>Worker Node(Data Plane)는 사용자 VPC에 위치하며 ENI(Elastic Network Interface)를 통해 Control Plane과 통신</li>
</ul>
<pre><code>[ EKS 전체 아키텍처 ]

  외부 접근
  ┌──────────┐   HTTPS
  │ kubectl │──────────────┐
  └──────────┘             │
  ┌──────────┐   AWS API   │
  │ AWS CLI │──────────────┤
  └──────────┘             │
  ┌──────────┐   HTTPS     ▼
  │ Console │────────── 사용자 VPC
  └──────────┘  │
               │   ┌─────────────────────────────────────────────┐
               │   │  EKS Control Plane (AWS 관리 VPC)        │
               │   │  ┌────────────┐  ┌────────┐  ┌───────────┐  │
               └─▶│  │kube-      │  │ etcd   │  │scheduler │  │
                   │  │apiserver  │  │Multi-AZ│  │controller│ │
                   │  └────────────┘  └────────┘  └───────────┘  │
                   └──────────────────────┬──────────────────────┘
                                          │ ENI 연결
                   ┌──────────────────────▼───────────────────────┐
                   │  Data Plane (사용자 VPC)                  │
                   │  ┌──────────────────┐  ┌──────────────────┐  │
                   │  │  Node Group     │  │  Node Group    │  │
                   │  │  AZ: 2a         │  │  AZ: 2c        │  │
                   │  │  EC2 [Pod][Pod] │  │  EC2 [Pod][Pod]│  │
                   │  └──────────────────┘  └──────────────────┘  │
                   │                                           │
                   │  ┌──────────┐  ┌──────────┐  ┌───────────┐   │
                   │  │   ECR   │  │   IAM   │  │CloudWatch│   │
                   │  └──────────┘  └──────────┘  └───────────┘   │
                   └──────────────────────────────────────────────┘</code></pre><br>

<h4 id="로컬-k8s-vs-eks-비교">로컬 k8s vs EKS 비교</h4>
<table>
<thead>
<tr>
<th>구분</th>
<th>로컬 (kubeadm)</th>
<th>Amazon EKS</th>
</tr>
</thead>
<tbody><tr>
<td>Control Plane 관리</td>
<td>직접 설치/운영</td>
<td>AWS 완전 관리</td>
</tr>
<tr>
<td>etcd 관리</td>
<td>직접 백업/복구</td>
<td>Multi-AZ 자동 백업</td>
</tr>
<tr>
<td>고가용성</td>
<td>직접 구성 필요</td>
<td>기본 제공</td>
</tr>
<tr>
<td>업그레이드</td>
<td>직접 수행</td>
<td>콘솔/CLI 지원</td>
</tr>
<tr>
<td>인증 (kubectl)</td>
<td>인증서 기반</td>
<td>AWS IAM 토큰</td>
</tr>
<tr>
<td>네트워킹(CNI)</td>
<td>Calico 직접 설치 (BGP 라우팅)</td>
<td>VPC CNI 기본 탑재</td>
</tr>
<tr>
<td>노드 추가</td>
<td><code>kubeadm join</code></td>
<td>Node Group 스케일</td>
</tr>
<tr>
<td>Control Plane 비용</td>
<td>VM 비용만</td>
<td>$0.10/hr 추가</td>
</tr>
</tbody></table>
<br>

<h3 id="05-eks-핵심-구성-요소"><strong>05. EKS 핵심 구성 요소</strong></h3>
<p><strong>Data Plane 옵션 비교</strong></p>
<table>
<thead>
<tr>
<th>항목</th>
<th>Managed Node Group</th>
<th>Self-Managed</th>
<th>Fargate</th>
</tr>
</thead>
<tbody><tr>
<td>EC2 제어</td>
<td>제한적</td>
<td>완전 제어</td>
<td>없음</td>
</tr>
<tr>
<td>AMI 업데이트</td>
<td>자동</td>
<td>수동</td>
<td>자동</td>
</tr>
<tr>
<td>SSH 접근</td>
<td>가능</td>
<td>가능</td>
<td>불가</td>
</tr>
<tr>
<td>GPU 지원</td>
<td>가능</td>
<td>가능</td>
<td>불가</td>
</tr>
<tr>
<td>추천 상황</td>
<td>일반 워크로드</td>
<td>특수 설정</td>
<td>배치 작업</td>
</tr>
</tbody></table>
<br>

<h3 id="vpc-cni--eks-네트워킹의-핵심"><strong>VPC CNI — EKS 네트워킹의 핵심</strong></h3>
<ul>
<li>Calico CNI를 사용하여 BGP 기반으로 Pod 간 통신</li>
<li>EKS VPC CNI는 Pod IP = VPC의 실제 IP를 사용하는 방식으로 구조가 다름</li>
</ul>
<pre><code>[ Calico(k8s1 로컬) vs VPC CNI(EKS) 비교 ]

  Calico (BGP 라우팅)               VPC CNI (네이티브)

  Node: 10.0.1.10                   Node: 192.168.1.10
  Pod:  172.16.0.x                  Pod:  192.168.1.x  ← VPC IP 직접 사용
        │                                  │
  BGP 피어링 / IPIP 터널              ENI 통한 VPC 네이티브 라우팅
  Pod CIDR ≠ Node IP 대역             Pod IP = VPC IP 대역
  별도 CNI 설치 필요                   aws-node DaemonSet 기본 탑재</code></pre><br>

<h3 id="eksctl-클러스터-생성--yaml-기반-eks-클러스터-구성"><strong>eksctl 클러스터 생성 — YAML 기반 EKS 클러스터 구성</strong></h3>
<h3 id="01-eksctl-클러스터-생성-개요"><strong>01. eksctl 클러스터 생성 개요</strong></h3>
<ul>
<li>AML 파일 하나로 VPC, IAM Role, EKS 클러스터, Node Group을 한 번에 생성</li>
<li>내부적으로 CloudFormation 스택 2개를 생성하여 전체 인프라를 관리</li>
</ul>
<br>

<h3 id="02-clusteryaml-주요-프로퍼티"><strong>02. cluster.yaml 주요 프로퍼티</strong></h3>
<p><strong>YAML</strong></p>
<pre><code class="language-yaml">apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

metadata:
  name: eks-lab-cluster        # 클러스터 이름
  region: ap-northeast-2       # 서울 리전
  version: &quot;1.35&quot;              # Kubernetes 버전

vpc:
  nat:
    gateway: Single            # NAT Gateway 개수
    # gateway: HighlyAvailable  # AZ별 NAT GW — 비용 높음, HA 보장
    # gateway: Disable          # NAT GW 없음 — Private 서브넷 외부 통신 불가

  clusterEndpoints:
    publicAccess: true    # 인터넷에서 kubectl 접근 허용
    privateAccess: true   # 클러스터 VPC 내부에서 접근 허용

addons:
  - name: vpc-cni
    version: latest

iam:
  withOIDC: true               # IRSA 사용을 위한 OIDC Provider 자동 생성

managedNodeGroups:
  - name: ng-general
    instanceType: t3.medium
    desiredCapacity: 2
    minSize: 1
    maxSize: 3
    amiFamily: AmazonLinux2023
    volumeSize: 30               # EBS 볼륨 크기(GB)
    volumeType: gp3              # 최신 EBS 타입, gp2 대비 성능 향상
    privateNetworking: true      # Private Subnet 배치 (보안 권장)
    labels:
      role: worker
      env: lab</code></pre>
<br>

<h4 id="최상위-필드">최상위 필드</h4>
<table>
<thead>
<tr>
<th>프로퍼티</th>
<th>타입</th>
<th>필수</th>
<th>의미</th>
<th>기본값</th>
</tr>
</thead>
<tbody><tr>
<td><code>metadata.name</code></td>
<td>string</td>
<td>O</td>
<td>클러스터 이름</td>
<td>—</td>
</tr>
<tr>
<td><code>metadata.region</code></td>
<td>string</td>
<td>O</td>
<td>AWS 리전</td>
<td>—</td>
</tr>
<tr>
<td><code>metadata.version</code></td>
<td>string</td>
<td>O</td>
<td>Kubernetes 버전</td>
<td>—</td>
</tr>
<tr>
<td><code>iam.withOIDC</code></td>
<td>bool</td>
<td>권장</td>
<td>OIDC Provider 자동 생성</td>
<td>false</td>
</tr>
<tr>
<td><code>vpc.nat.gateway</code></td>
<td>string</td>
<td>—</td>
<td>NAT Gateway 구성</td>
<td>Single</td>
</tr>
</tbody></table>
<br>

<h3 id="03-주요-kubectl-명령어"><strong>03. 주요 kubectl 명령어</strong></h3>
<p><strong>클러스터 생성/삭제</strong></p>
<pre><code class="language-bash"># 클러스터 생성 (dry-run으로 사전 검증)
eksctl create cluster \
  --config-file cluster.yaml \
  --dry-run

# 클러스터 생성 (실제 실행, 약 15~20분)
eksctl create cluster \
  --config-file cluster.yaml

# 클러스터 삭제 (모든 리소스 함께 삭제)
eksctl delete cluster \
  --name $CLUSTER_NAME \
  --region $AWS_REGION \
  --wait</code></pre>
<br>

<h3 id="eks-네트워킹--irsa--vpc-cni-원리와-pod-단위-aws-권한-부여"><strong>EKS 네트워킹 &amp; IRSA — VPC CNI 원리와 Pod 단위 AWS 권한 부여</strong></h3>
<h3 id="01-vpc-cni-동작-원리"><strong>01. VPC CNI 동작 원리</strong></h3>
<ul>
<li>EKS에서 Pod IP는 VPC의 실제 IP를 직접 사용</li>
<li>노드에 ENI(Elastic Network Interface)를 추가로 연결하고, 각 ENI의 Secondary IP를 Pod에 할당</li>
</ul>
<pre><code>[ Calico(로컬 k8s1) vs VPC CNI(EKS) 비교 ]

  Calico — BGP 기반 라우팅 방식 (k8s 로컬 환경)
  Node A: 10.0.1.10                        Node B: 10.0.1.20
  ┌──────────────────────┐                 ┌──────────────────────┐
  │  Pod (172.16.0.2)  │                 │  Pod (172.16.1.2)  │
  │       │            │                 │       ▲            │
  │     veth           │                 │     veth           │
  │  cali* (veth pair) │ ──BGP 라우팅───▶ │  cali* (veth pair) │
  │  eth0 (10.0.1.10)  │  or IPIP 터널    │  eth0 (10.0.1.20)  │
  └──────────────────────┘                 └──────────────────────┘
  Pod IP ≠ Node IP (별도 Pod CIDR 사용)
  BGP 피어링 시 오버헤드 최소 / IPIP 모드 시 캡슐화 발생

---

  VPC CNI — 네이티브 방식 (EKS 환경)
  Node A: 192.168.1.10 (ENI 3개)          Node B: 192.168.2.10 (ENI 3개)
  ┌──────────────────────┐                ┌──────────────────────┐
  │ Pod (192.168.1.21) │                │ Pod (192.168.2.21) │
  │      │             │                │       ▲            │
  │    veth            │                │     veth           │
  │ ENI (VPC IP 직접)   │──VPC 라우팅───▶│ ENI (VPC IP 직접)   │
  └──────────────────────┘  터널 없음      └──────────────────────┘
  Pod IP = VPC IP → 별도 CNI 설치 불필요, VPC 라우팅 테이블 직접 활용</code></pre><br>

<h3 id="02-aws-node-daemonset-확인"><strong>02. aws-node DaemonSet 확인</strong></h3>
<ul>
<li>VPC CNI는 aws-node DaemonSet으로 모든 노드에 1개씩 배포</li>
</ul>
<pre><code class="language-bash"># aws-node DaemonSet 확인
kubectl get daemonset aws-node -n kube-system</code></pre>
<br>

<h3 id="03-coredns-동작-확인"><strong>03. CoreDNS 동작 확인</strong></h3>
<ul>
<li>CoreDNS는 클러스터 내부 DNS 서버</li>
<li>Pod 간 통신 시 서비스명.네임스페이스.svc.cluster.local 형태로 DNS 조회</li>
</ul>
<br>

<h3 id="04-oidc-provider와-irsa-개념"><strong>04. OIDC Provider와 IRSA 개념</strong></h3>
<ul>
<li>Pod가 AWS 리소스(S3, DynamoDB 등)에 접근하는 방법은 3가지</li>
<li>IRSA(IAM Roles for Service Accounts)가 유일하게 권장되는 방식</li>
</ul>
<pre><code>[ Pod의 AWS 리소스 접근 방식 비교 ]

  방법 1 — Access Key 하드코딩 (금지)
  ──────────────────────────────────────────────────
  Pod 환경변수에 AWS_ACCESS_KEY_ID, SECRET 직접 입력
  → 키 유출 시 전체 계정 위협, 로테이션 어려움

  방법 2 — Node IAM Role 과다 부여 (비권장)
  ──────────────────────────────────────────────────
  Worker Node EC2에 강력한 IAM Role 연결
  → 같은 노드의 모든 Pod가 동일한 강력한 권한 획득
  → 최소 권한 원칙 위반

  방법 3 — IRSA (권장)
  ──────────────────────────────────────────────────
  ServiceAccount ↔ IAM Role 1:1 연결
  → Pod 단위 최소 권한 적용
  → Access Key 없음, 자동 갱신
  → CloudTrail에 Pod 단위로 기록됨</code></pre><br>

<h4 id="irsa-동작-흐름">IRSA 동작 흐름</h4>
<pre><code>[ IRSA 전체 동작 흐름 ]

  사전 설정:
  ┌─────────────────────────────────────────────────────────┐
  │ 1. EKS OIDC Endpoint → AWS IAM OIDC Provider로 등록 │
  │ 2. IAM Role 신뢰 정책: 이 SA에서 온 토큰만 Assume 허용  │
  │ 3. ServiceAccount에 IAM Role ARN 어노테이션 추가      │
  └─────────────────────────────────────────────────────────┘

  런타임 동작:
  Pod 시작
      │
      ▼
  k8s가 OIDC JWT 토큰 자동 마운트
  /var/run/secrets/eks.amazonaws.com/serviceaccount/token
      │
      ▼
  AWS SDK → STS AssumeRoleWithWebIdentity 요청
      │
      ▼
  STS가 OIDC Provider를 통해 토큰 검증
      │
      ▼
  임시 자격증명 발급 (자동 갱신)
      │
      ▼
  S3 / DynamoDB 등 AWS 서비스 호출 성공</code></pre><br>

<h3 id="워크로드-배포-및-외부-노출--deployment--gateway-api--alb-controller"><strong>워크로드 배포 및 외부 노출 — Deployment / Gateway API / ALB Controller</strong></h3>
<h3 id="01-워크로드-배포-전체-흐름"><strong>01. 워크로드 배포 전체 흐름</strong></h3>
<ul>
<li>EKS에서 애플리케이션 배포는 ECR 이미지 푸시 → Deployment 생성 → Service 생성 → Gateway API로 외부 노출 순서로 진행</li>
</ul>
<pre><code>[ EKS 워크로드 배포 흐름 ]

     로컬 빌드               ECR                  EKS 클러스터
  ┌──────────────┐       ┌──────────┐         ┌────────────────────────────────┐
  │ Dockerfile  │─────▶ │  Image  │───────▶ │  Deployment (Pods)          │
  └──────────────┘ push  │ Registry│  pull   │  ┌──────────┐  ┌──────────┐   │
                        └──────────┘         │  │  Pod 1  │  │  Pod 2  │   │
                                            │  └────┬─────┘  └────┬─────┘   │
                                            │      └──────┬───────┘        │
                                            │        ClusterIP Service    │
                                            │              │              │
                                            │         HTTPRoute           │
                                            │              │              │
                                            │          Gateway            │
                                            └──────────────┼─────────────────┘
                                                          │
                                            ┌──────────────▼─────────────────┐
                                            │AWS Application Load Balancer│
                                            └────────────────────────────────┘</code></pre><br>

<h3 id="02-ecr-이미지-관리"><strong>02. ECR 이미지 관리</strong></h3>
<ul>
<li>ECR(Elastic Container Registry)은 AWS 완전 관리형 컨테이너 이미지 저장소</li>
</ul>
<pre><code class="language-bash"># ECR 리포지토리 생성
export APP_NAME=&quot;spring-demo&quot;
export ECR_URI=&quot;${ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${APP_NAME}&quot;

aws ecr create-repository \
  --repository-name $APP_NAME \
  --region $AWS_REGION \
  --image-tag-mutability IMMUTABLE    # 같은 태그 덮어쓰기 방지</code></pre>
<br>

<h3 id="node-group-스케일링--add-on-업데이트--버전-업그레이드"><strong>Node Group 스케일링 / Add-on 업데이트 / 버전 업그레이드</strong></h3>
<h3 id="01-node-group-스케일링"><strong>01. Node Group 스케일링</strong></h3>
<ul>
<li>EKS Managed Node Group은 Auto Scaling Group 기반으로 동작</li>
<li>노드 수는 콘솔, CLI, eksctl 모두로 조정 가능</li>
</ul>
<pre><code class="language-bash"># 스케일 아웃 (노드 증가)
NODEGROUP_NAME=$(aws eks list-nodegroups \
  --cluster-name $CLUSTER_NAME \
  --region $AWS_REGION \
  --query &#39;nodegroups[0]&#39; \
  --output text)

aws eks update-nodegroup-config \
  --cluster-name $CLUSTER_NAME \
  --nodegroup-name $NODEGROUP_NAME \
  --region $AWS_REGION \
  --scaling-config desiredSize=3,minSize=1,maxSize=5

# 노드 추가 확인 (watch 모드)
kubectl get nodes -w

# 스케일 인 (노드 감소)
aws eks update-nodegroup-config \
  --cluster-name $CLUSTER_NAME \
  --nodegroup-name $NODEGROUP_NAME \
  --region $AWS_REGION \
  --scaling-config desiredSize=2,minSize=1,maxSize=5</code></pre>
<br>

<h3 id="02-노드-유지보수--cordon--drain"><strong>02. 노드 유지보수 — Cordon &amp; Drain</strong></h3>
<ul>
<li>노드 유지보수나 업그레이드 전, 해당 노드의 Pod를 안전하게 다른 노드로 이동</li>
</ul>
<pre><code>[ Cordon &amp; Drain 동작 흐름 ]

  kubectl cordon node-A
        │
        ▼
  node-A에 NoSchedule Taint 추가
  (새 Pod 할당 차단)
        │
  kubectl drain node-A
        │
        ▼
  기존 Pod에 Eviction API 요청 (PDB 존중)
        │
  ┌─────┴───────────────────────────┐
  │                              │
  ▼                              ▼
DaemonSet Pod                 일반 Pod
(--ignore-daemonsets로 유지)   다른 노드에 재스케줄됨
        │
        ▼
  node-A 유지보수 / 업그레이드 진행
        │
  kubectl uncordon node-A
        ▼
  node-A 다시 스케줄 허용</code></pre><hr>
<h3 id="iac-왜-코드로-인프라를-관리하는가"><strong>IaC 왜 코드로 인프라를 관리하는가</strong></h3>
<h3 id="01-iac란"><strong>01. IaC란</strong></h3>
<p><strong>Infrastructure as Code</strong></p>
<ul>
<li>인프라를 콘솔 UI로 관리하는 것이 아니라 코드로 관리하는 방법론</li>
<li>DevOps의 효율성과 클라우드 인프라 자동화 구축을 위해 반드시 필요한 기술</li>
</ul>
<pre><code>전통적 방식                          IaC 방식

개발자 A -&gt; 콘솔 클릭 -&gt; EC2 생성     개발자 A -&gt; 코드 작성 -&gt; apply -&gt; EC2 생성
개발자 B -&gt; 콘솔 클릭 -&gt; 다른 EC2     개발자 B -&gt; 동일 코드 -&gt; apply -&gt; 동일 EC2

결과: 환경 불일치                     결과: 환경 일관성 보장
재현: 불가능                          재현: Git clone 후 apply
추적: 개인 기억에 의존                추적: Git 커밋 이력
협업: 구두 전달                       협업: PR 리뷰</code></pre><br>

<h3 id="02-핵심-개념"><strong>02. 핵심 개념</strong></h3>
<h4 id="선언형-vs-명령형"><strong>선언형 vs 명령형</strong></h4>
<ul>
<li><strong>명령형(Imperative)</strong> 은 &quot;어떻게 만들지&quot;를 순서대로 기술하는 방식</li>
<li><strong>선언형(Declarative)</strong> 은 &quot;무엇이 있어야 하는지&quot;를 기술하는 방식</li>
</ul>
<table>
<thead>
<tr>
<th>구분</th>
<th>방식</th>
<th>도구 예시</th>
<th>특징</th>
</tr>
</thead>
<tbody><tr>
<td><strong>명령형</strong></td>
<td>&quot;어떻게&quot; 기술</td>
<td>Bash, Ansible</td>
<td>순서 중요, 멱등성 없음</td>
</tr>
<tr>
<td><strong>선언형</strong></td>
<td>&quot;무엇을&quot; 기술</td>
<td>Terraform, CloudFormation</td>
<td>현재-&gt;목표 수렴, 멱등성 보장</td>
</tr>
</tbody></table>
<br>

<h4 id="state-개념"><strong>State 개념</strong></h4>
<ul>
<li>IaC 도구는 현재 실제 인프라 상태를 추적하기 위해 State를 사용</li>
<li>State가 없으면 &quot;현재 AWS에 어떤 VPC가 있는지, 없는지&quot;를 도구가 알 방법이 없음</li>
</ul>
<table>
<thead>
<tr>
<th>구분</th>
<th>CloudFormation</th>
<th>Terraform</th>
</tr>
</thead>
<tbody><tr>
<td>State 저장소</td>
<td>AWS 내부 관리</td>
<td>직접 선택 (local / S3)</td>
</tr>
<tr>
<td>팀 협업</td>
<td>Stack 단위 권한</td>
<td>S3 + DynamoDB locking 필요</td>
</tr>
<tr>
<td>충돌 방지</td>
<td>AWS 자동 처리</td>
<td>DynamoDB로 락 구현</td>
</tr>
<tr>
<td>민감 정보</td>
<td>암호화 지원</td>
<td>tfstate에 평문 저장 주의</td>
</tr>
</tbody></table>
<br>

<h3 id="03-iac-도구-생태계"><strong>03. IaC 도구 생태계</strong></h3>
<ul>
<li>현재 IaC 도구는 AWS 네이티브와 멀티클라우드 계열로 구분</li>
</ul>
<pre><code>AWS 네이티브                           멀티클라우드

CloudFormation   &lt;- AWS 전용 표준       Terraform    &lt;- 업계 표준 (HCL)
AWS SAM          &lt;- 서버리스 특화       OpenTofu     &lt;- Terraform 오픈소스 포크
AWS CDK          &lt;- 코드로 CFN 생성     Pulumi       &lt;- TS/Python/Go 코드형</code></pre><br>

<h3 id="cloudformation-기초--aws-네이티브-iac"><strong>CloudFormation 기초 — AWS 네이티브 IaC</strong></h3>
<h3 id="01-왜-cloudformation인가"><strong>01. 왜 CloudFormation인가</strong></h3>
<ul>
<li>AWS가 공식 제공하는 IaC 서비스</li>
<li>YAML 또는 JSON 형식의 템플릿 파일을 작성하면 AWS가 해석해 스택(Stack) 단위로 리소스를 생성·관리</li>
<li>CloudFormation의 가장 큰 장점은 <strong>별도 State 파일이 필요 없다</strong>는 것</li>
</ul>
<br>

<h3 id="02-핵심-개념-1"><strong>02. 핵심 개념</strong></h3>
<h4 id="템플릿-구조"><strong>템플릿 구조</strong></h4>
<ul>
<li>CloudFormation 템플릿은 최대 7개 최상위 섹션으로 구성</li>
</ul>
<pre><code class="language-yaml">AWSTemplateFormatVersion: &#39;2010-09-09&#39;   # 버전 — 항상 이 값 사용
Description: &#39;템플릿 설명&#39;               # 스택 설명 (선택)

Metadata:     # 콘솔 UI 표시 설정 (선택)
Parameters:   # 배포 시 입력받는 변수 (선택) — 환경별로 달라지는 값을 여기서 받음
Mappings:     # 조건별 값 매핑 테이블 (선택) — 리전별 AMI ID 등
Conditions:   # 조건문 정의 (선택) — dev/prod 환경 분기
Resources:    # 필수 — 실제로 만들 AWS 리소스를 정의하는 곳
Outputs:      # 스택 출력값 (선택) — 다른 스택이나 콘솔에서 참조</code></pre>
<br>

<h3 id="03-yaml-프로퍼티-상세"><strong>03. YAML 프로퍼티 상세</strong></h3>
<p><strong>기본 YAML</strong></p>
<pre><code class="language-yaml">AWSTemplateFormatVersion: &#39;2010-09-09&#39;
Description: &#39;EKS Study - VPC Stack&#39;

Parameters:
  EnvironmentName:
    Type: String
    Default: eks-study
    AllowedValues: [dev, staging, prod]   # 이 세 값만 허용
  VpcCidr:
    Type: String
    Default: &#39;10.0.0.0/16&#39;
    AllowedPattern: &#39;^(\d{1,3}\.){3}\d{1,3}/\d{1,2}$&#39;   # CIDR 형식만 허용

Resources:
  MyVPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VpcCidr            # Parameters에서 입력받은 값 참조
      EnableDnsHostnames: true           # EKS 필수 옵션
      EnableDnsSupport: true             # EKS 필수 옵션
      Tags:
        - Key: Name
          Value: !Sub &#39;${EnvironmentName}-vpc&#39;   # 변수 치환으로 이름 생성

Outputs:
  VpcId:
    Description: &#39;VPC ID&#39;
    Value: !Ref MyVPC
    Export:
      Name: !Sub &#39;${AWS::StackName}-VpcId&#39;   # 다른 스택에서 !ImportValue로 참조</code></pre>
<br>

<h3 id="04-주요-명령어"><strong>04. 주요 명령어</strong></h3>
<h4 id="스택-관리"><strong>스택 관리</strong></h4>
<pre><code class="language-bash"># 템플릿 문법 검증 (배포 전 필수 — 문법 오류를 배포 전에 잡을 수 있음)
aws cloudformation validate-template \
  --template-body file://vpc-stack.yaml

# 스택 생성
aws cloudformation create-stack \
  --stack-name eks-study-vpc \
  --template-body file://vpc-stack.yaml \
  --parameters ParameterKey=EnvironmentName,ParameterValue=eks-study \
  --capabilities CAPABILITY_IAM

# 생성 완료까지 대기 (완료되면 프롬프트가 돌아옴)
aws cloudformation wait stack-create-complete \
  --stack-name eks-study-vpc

# Output 값 조회 — VPC ID, Subnet ID 등 확인
aws cloudformation describe-stacks \
  --stack-name eks-study-vpc \
  --query &#39;Stacks[0].Outputs[*].{Key:OutputKey,Value:OutputValue}&#39; \
  --output table

# 스택 삭제
aws cloudformation delete-stack --stack-name eks-study-vpc
aws cloudformation wait stack-delete-complete --stack-name eks-study-vpc</code></pre>
<br>

<h4 id="changeset-명령어">ChangeSet 명령어</h4>
<pre><code class="language-bash"># ChangeSet 생성 — 실제 변경 없이 무엇이 바뀔지만 계산
aws cloudformation create-change-set \
  --stack-name eks-study-vpc \
  --change-set-name my-changes \
  --change-set-type UPDATE \
  --template-body file://vpc-stack.yaml

# ChangeSet 완료 대기
aws cloudformation wait change-set-create-complete \
  --stack-name eks-study-vpc \
  --change-set-name my-changes

# 변경 내용 확인 — Replacement 컬럼 반드시 확인
aws cloudformation describe-change-set \
  --stack-name eks-study-vpc \
  --change-set-name my-changes \
  --query &#39;Changes[*].{Action:ResourceChange.Action,Type:ResourceChange.ResourceType,Replace:ResourceChange.Replacement}&#39; \
  --output table

# 확인 후 실행
aws cloudformation execute-change-set \
  --stack-name eks-study-vpc \
  --change-set-name my-changes</code></pre>
<br>

<h3 id="aws-sam--서버리스-특화-iac"><strong>AWS SAM — 서버리스 특화 IaC</strong></h3>
<h3 id="01-왜-sam인가"><strong>01. 왜 SAM인가</strong></h3>
<ul>
<li>AWS SAM(Serverless Application Model)은 CloudFormation 위에서 동작하는 서버리스 전용 IaC 프레임워크</li>
</ul>
<table>
<thead>
<tr>
<th>항목</th>
<th>CloudFormation</th>
<th>AWS SAM</th>
</tr>
</thead>
<tbody><tr>
<td>대상</td>
<td>모든 AWS 리소스</td>
<td>서버리스 특화</td>
</tr>
<tr>
<td>Lambda 정의</td>
<td>50줄 이상</td>
<td>10줄 이하</td>
</tr>
<tr>
<td>IAM Role</td>
<td>직접 작성</td>
<td>자동 생성</td>
</tr>
<tr>
<td>API Gateway</td>
<td>복잡한 설정 필요</td>
<td><code>Events: Http</code>로 간결</td>
</tr>
<tr>
<td>로컬 테스트</td>
<td>불가</td>
<td><code>sam local invoke</code> 지원</td>
</tr>
</tbody></table>
<br>

<h3 id="02-yaml-프로퍼티-상세"><strong>02. YAML 프로퍼티 상세</strong></h3>
<p><strong>기본 YAML</strong></p>
<pre><code class="language-yaml">AWSTemplateFormatVersion: &#39;2010-09-09&#39;
Transform: AWS::Serverless-2016-10-31   # SAM Transform 선언 필수
Description: &#39;EKS Health Check Lambda (SAM)&#39;

Globals:
  Function:
    Runtime: python3.12
    Timeout: 30
    MemorySize: 128
    Environment:
      Variables:
        ENV: !Ref Environment
        CLUSTER_NAME: !Ref ClusterName

Parameters:
  Environment:
    Type: String
    Default: dev
    AllowedValues: [dev, staging, prod]
  ClusterName:
    Type: String
    Default: eks-study-cluster

Resources:
  EksHealthCheckFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: !Sub &#39;eks-health-check-${Environment}&#39;
      CodeUri: src/health_check/        # sam build 시 패키징할 소스 경로
      Handler: app.lambda_handler       # 파일명.함수명
      Policies:
        - AWSLambdaBasicExecutionRole   # 관리형 정책 (CloudWatch Logs 권한)
        - Statement:
            - Effect: Allow
              Action:
                - &#39;eks:DescribeCluster&#39;
                - &#39;eks:ListNodegroups&#39;
              Resource: !Sub &#39;arn:aws:eks:${AWS::Region}:${AWS::AccountId}:cluster/${ClusterName}&#39;
      Events:
        HealthApi:
          Type: Api
          Properties:
            Path: /health
            Method: get
            RestApiId: !Ref HealthApi

  HealthApi:
    Type: AWS::Serverless::Api
    Properties:
      Name: !Sub &#39;eks-health-api-${Environment}&#39;
      StageName: !Ref Environment
      Cors:
        AllowMethods: &quot;&#39;GET,OPTIONS&#39;&quot;
        AllowHeaders: &quot;&#39;Content-Type,Authorization&#39;&quot;
        AllowOrigin: &quot;&#39;*&#39;&quot;

Outputs:
  ApiEndpoint:
    Description: &#39;Health Check API 엔드포인트&#39;
    Value: !Sub &#39;https://${HealthApi}.execute-api.${AWS::Region}.amazonaws.com/${Environment}/health&#39;
  FunctionArn:
    Value: !GetAtt EksHealthCheckFunction.Arn</code></pre>
<hr>
<h3 id="terraform-아키텍처--hcl-문법">Terraform 아키텍처 &amp; HCL 문법</h3>
<h3 id="01-왜-terraform인가"><strong>01. 왜 Terraform인가</strong></h3>
<ul>
<li>HCL(HashiCorp Configuration Language)로 작성된 코드를 Provider 플러그인을 통해 실제 클라우드 API 호출로 변환하는 선언형 IaC 도구</li>
</ul>
<pre><code>Terraform 전체 아키텍처

+------------------------------------------------+
|              Terraform Core                    |
|  HCL 파싱 -&gt; Dependency Graph -&gt; Plan -&gt; Apply  |
+--------------------+---------------------------+
                     | Plugin Protocol (gRPC)
          +----------+-----------------+
          |                            |
+---------v--------+       +-----------v-----------+
|   AWS Provider   |       |   기타 Provider        |
|   aws_vpc        |       |   (GCP, Azure, K8s..) |
|   aws_eks_*      |       +-----------------------+
|   aws_rds_*      |
+-----+------------+
      | AWS API 호출
+-----v------------+
|   실제 AWS 리소스  |
+------------------+</code></pre><pre><code>Terraform 실행 흐름

terraform init
    |
    v Provider 다운로드 (.terraform/) + Lock 파일 생성
terraform plan
    |
    v State + 실제 AWS 비교 -&gt; diff 계산 (실제 변경 없음)
terraform apply
    |
    v 계획 적용 -&gt; State 파일 업데이트 (terraform.tfstate)
terraform destroy
    |
    v State 기반으로 모든 리소스 삭제</code></pre><br>

<h3 id="02-hcl-기본-문법-실습">02. HCL 기본 문법 실습</h3>
<h4 id="terraform--provider-블록"><strong>terraform &amp; provider 블록</strong></h4>
<ul>
<li>terraform 블록은 Terraform CLI 자체와 사용할 Provider의 버전을 선언</li>
<li>provider 블록은 해당 Provider의 동작 방식(리전, 인증, 공통 태그 등)을 설정</li>
</ul>
<pre><code>[ 학습자 PC ]
      │
      │  terraform init
      ▼
┌─────────────────────────────────────────────┐
│ terraform 블록 읽기                      │
│   └─ required_providers 확인             │
│      └─ registry.terraform.io에서 다운로드│
│            └─ .terraform/ 디렉토리에 저장  │
└─────────────────────────────────────────────┘
      │
      │  terraform apply
      ▼
┌──────────────────────────────────────────┐
│  provider 블록 읽기                    │
│    └─ 인증·리전 설정 적용               │
│         └─ resource 블록 실행          │
└──────────────────────────────────────────┘</code></pre><br>

<h4 id="버전-표기법">버전 표기법</h4>
<table>
<thead>
<tr>
<th>연산자</th>
<th>의미</th>
<th>예시</th>
</tr>
</thead>
<tbody><tr>
<td><code>= 1.9.0</code></td>
<td>정확히 그 버전만</td>
<td><code>= 1.9.0</code></td>
</tr>
<tr>
<td><code>!= 1.9.0</code></td>
<td>그 버전 제외</td>
<td><code>!= 1.9.0</code></td>
</tr>
<tr>
<td><code>&gt;= 1.9.0</code></td>
<td>이상</td>
<td><code>&gt;= 1.9.0</code></td>
</tr>
<tr>
<td><code>&lt; 2.0.0</code></td>
<td>미만</td>
<td><code>&lt; 2.0.0</code></td>
</tr>
<tr>
<td><code>~&gt; 1.9</code></td>
<td>마지막 자리만 증가 허용 (1.10 OK, 2.0 NO)</td>
<td><code>~&gt; 1.9</code></td>
</tr>
<tr>
<td><code>~&gt; 1.9.0</code></td>
<td>패치만 증가 허용 (1.9.5 OK, 1.10 NO)</td>
<td><code>~&gt; 1.9.0</code></td>
</tr>
</tbody></table>
<br>

<h3 id="variable-블록"><strong>variable 블록</strong></h3>
<ul>
<li>variable은 외부에서 주입받는 입력값</li>
<li>같은 코드를 dev/staging/prod 환경에서 다른 값으로 재사용할 수 있게 해줌</li>
</ul>
<pre><code>값 주입 우선순위 (높은 순)
┌────────────────────────────────────────┐
│ 1. -var / -var-file CLI 옵션        │
│ 2. *.auto.tfvars 파일               │
│ 3. terraform.tfvars 파일            │
│ 4. TF_VAR_&lt;이름&gt; 환경 변수            │ 
│ 5. variable 블록의 default 값        │
└────────────────────────────────────────┘</code></pre><br>

<h4 id="hcl-타입-시스템">HCL 타입 시스템</h4>
<table>
<thead>
<tr>
<th>타입</th>
<th>선언 예시</th>
<th>사용 예</th>
</tr>
</thead>
<tbody><tr>
<td>string</td>
<td><code>type = string</code></td>
<td>리전명, 클러스터명</td>
</tr>
<tr>
<td>number</td>
<td><code>type = number</code></td>
<td>노드 수, 포트 번호</td>
</tr>
<tr>
<td>bool</td>
<td><code>type = bool</code></td>
<td>기능 활성화 여부</td>
</tr>
<tr>
<td>list(string)</td>
<td><code>type = list(string)</code></td>
<td>CIDR 목록</td>
</tr>
<tr>
<td>map(string)</td>
<td><code>type = map(string)</code></td>
<td>태그 맵</td>
</tr>
<tr>
<td>object({...})</td>
<td><code>type = object({...})</code></td>
<td>복합 설정 객체</td>
</tr>
</tbody></table>
<br>

<h3 id="locals-블록"><strong>locals 블록</strong></h3>
<ul>
<li>locals는 모듈 내부에서만 사용하는 계산값</li>
<li>variable과 달리 외부에서 주입할 수 없고, 반복되는 표현식이나 계산 결과를 한 곳에서 관리할 때 사용</li>
</ul>
<table>
<thead>
<tr>
<th>구분</th>
<th>variable</th>
<th>locals</th>
</tr>
</thead>
<tbody><tr>
<td>외부 주입</td>
<td>가능</td>
<td>불가능</td>
</tr>
<tr>
<td>참조</td>
<td><code>var.이름</code></td>
<td><code>local.이름</code></td>
</tr>
<tr>
<td>용도</td>
<td>사용자 입력</td>
<td>내부 상수/계산값</td>
</tr>
</tbody></table>
<br>

<h3 id="03-hcl-블록-상세-실습">03. HCL 블록 상세 실습</h3>
<h3 id="data-블록"><strong>data 블록</strong></h3>
<ul>
<li>data 블록은 <strong>이미 존재하는 리소스의 정보를 읽기만 함</strong></li>
<li>새로 생성하지 않으므로 destroy 대상이 아님</li>
</ul>
<pre><code>resource 블록          data 블록
    │                      │
    │  생성·관리            │  읽기만
    ▼                      ▼
┌────────┐            ┌────────┐
│CREATE │            │  READ  │
│ ESTROY│            │  ONLY  │
└────────┘            └────────┘
    │                      │
    ▼                      ▼
 .tfstate              .tfstate
 (등록됨)              (참조 캐시)</code></pre><br>

<h4 id="aws에서-자주-쓰는-data-블록">AWS에서 자주 쓰는 data 블록</h4>
<ul>
<li>aws_caller_identity (계정 ID)</li>
<li>aws_region (현재 리전)</li>
<li>aws_ami (최신 AMI)</li>
<li>aws_availability_zones (AZ 목록)</li>
</ul>
<br>

<h3 id="output-블록"><strong>output 블록</strong></h3>
<ul>
<li>output은 apply 완료 후 콘솔에 노출되는 값</li>
<li>다른 모듈에서 호출 시 반환값 역할 수행</li>
</ul>
<br>

<h3 id="lifecycle-메타-인자"><strong>lifecycle 메타 인자</strong></h3>
<ul>
<li>lifecycle은 리소스의 생성·수정·삭제 동작을 변경</li>
</ul>
<table>
<thead>
<tr>
<th>옵션</th>
<th>동작</th>
<th>사용처</th>
</tr>
</thead>
<tbody><tr>
<td><code>create_before_destroy = true</code></td>
<td>새 리소스 생성 후 기존 삭제</td>
<td>무중단 교체가 필요한 리소스</td>
</tr>
<tr>
<td><code>prevent_destroy = true</code></td>
<td>destroy 명령 거부</td>
<td>운영 DB, 중요 데이터</td>
</tr>
<tr>
<td><code>ignore_changes = [속성]</code></td>
<td>해당 속성 변경 무시</td>
<td>외부에서 변경되는 태그 등</td>
</tr>
</tbody></table>
<br>

<h3 id="조건-표현식과-조건부-리소스-생성"><strong>조건 표현식과 조건부 리소스 생성</strong></h3>
<ul>
<li>삼항 연산자 조건 ? 참 : 거짓으로 환경에 따라 다른 값을 적용할 수 있음</li>
<li>count = 조건 ? 1 : 0 패턴으로 리소스 자체의 생성 여부를 조건부로 결정</li>
</ul>
<pre><code>조건부 값                          조건부 리소스
───────────────                    ────────────────
instance_type =                    count = var.enable ? 1 : 0
  var.env == &quot;prod&quot;                  │
    ? &quot;t3.large&quot;                     ├─ true → 1개 생성
    : &quot;t3.micro&quot;                     └─ false → 0개 (생성 안 함)</code></pre><br>

<h3 id="04loop-반복문">04.Loop (반복문)</h3>
<h3 id="count--정수-기반-반복"><strong>count — 정수 기반 반복</strong></h3>
<ul>
<li>count는 리소스를 정수 횟수만큼 반복 생성</li>
<li>count.index는 0부터 시작하며 각 인덱스에 다른 값을 주입할 수 있음</li>
</ul>
<pre><code>count = 3
   │
   ├─ count.index = 0 ──→ 리소스 [0]
   ├─ count.index = 1 ──→ 리소스 [1]
   └─ count.index = 2 ──→ 리소스 [2]

참조: local_file.users[0], local_file.users[1] ...
state: local_file.users[0], local_file.users[1] ...</code></pre><br>

<h3 id="for_each--mapset-기반-반복"><strong>for_each — map/set 기반 반복</strong></h3>
<ul>
<li>for_each는 map 또는 set의 키 개수만큼 반복함</li>
<li><strong>키 기반</strong>이므로 중간 항목이 빠져도 다른 리소스에 영향을 주지 않음</li>
</ul>
<pre><code>for_each = {
  dev     = &quot;debug&quot;
  staging = &quot;info&quot;
  prod    = &quot;error&quot;
}
   │
   ├─ each.key = &quot;dev&quot;,     each.value = &quot;debug&quot;
   ├─ each.key = &quot;staging&quot;, each.value = &quot;info&quot;
   └─ each.key = &quot;prod&quot;,    each.value = &quot;error&quot;

참조: local_file.envs[&quot;dev&quot;], local_file.envs[&quot;prod&quot;]
state: local_file.envs[&quot;dev&quot;], local_file.envs[&quot;prod&quot;]   ← 키로 저장</code></pre><table>
<thead>
<tr>
<th>구분</th>
<th>count</th>
<th>for_each</th>
</tr>
</thead>
<tbody><tr>
<td>입력 타입</td>
<td>정수</td>
<td>map 또는 set</td>
</tr>
<tr>
<td>식별자</td>
<td>인덱스 [0], [1]</td>
<td>키 [&quot;dev&quot;], [&quot;prod&quot;]</td>
</tr>
<tr>
<td>중간 항목 제거</td>
<td>인덱스 밀림 → 재생성</td>
<td>해당 키만 destroy</td>
</tr>
<tr>
<td>권장 용도</td>
<td>조건부 1개 토글</td>
<td>일반 반복</td>
</tr>
</tbody></table>
<br>

<h3 id="for-표현식"><strong>for 표현식</strong></h3>
<ul>
<li>for 표현식은 list/map을 변환해 새 list/map을 만듦</li>
<li>데이터 가공이 필요할 때 사용</li>
</ul>
<pre><code>list → list      [for x in list : 가공(x)]
list → map       {for x in list : 키 =&gt; 값}
map  → list      [for k, v in map : 가공(k, v)]
map  → map       {for k, v in map : 키 =&gt; 값}
필터링            [for x in list : x if 조건]</code></pre><br>

<h3 id="dynamic-블록"><strong>dynamic 블록</strong></h3>
<ul>
<li>리소스 안에 같은 구조의 nested block이 여러 개 필요할 때 사용</li>
</ul>
<pre><code>일반 블록 (정적)              dynamic 블록 (동적)
─────────────────             ────────────────────
ingress {                     dynamic &quot;ingress&quot; {
  from_port = 22                for_each = var.ports
}                               content {
ingress {                         from_port = ingress.value
  from_port = 80                }
}                             }
ingress {
  from_port = 443             → 입력 변수만 바꾸면
}                               규칙 개수가 자동 조절됨</code></pre><br>

<h3 id="05remote-backend-실습-s3--dynamodb">05.Remote Backend 실습 (S3 + DynamoDB)</h3>
<ul>
<li>State 파일을 어디에 저장할지 결정하는 설정</li>
<li>기본은 로컬 terraform.tfstate이지만, 팀 협업·CI/CD 환경에서는 반드시 원격 Backend가 필요</li>
</ul>
<pre><code>[ State Lock 동작 흐름 ]

학습자 A: terraform apply
   │
   ├─ 1) DynamoDB에 LockID 기록 (획득)
   ├─ 2) S3에서 State 읽기
   ├─ 3) AWS API 호출 → 리소스 생성
   ├─ 4) S3에 새 State 쓰기
   └─ 5) DynamoDB에서 LockID 제거 (해제)

학습자 B: terraform apply (A가 작업 중)
   │
   └─ Lock 시도 → DynamoDB에 이미 LockID 있음
       └─ 대기 또는 에러 (충돌 방지)</code></pre><br>

<h4 id="아키텍처-전체-흐름">아키텍처 전체 흐름</h4>
<pre><code>┌──────────────────────────────────────────────────────────┐
│     학습자 PC (Windows + Git Bash / macOS + zsh)     │
│                                                     │
│  ~/terraform-lab/13-backend-bootstrap/              │
│   ├─ main.tf       ← 1단계: 로컬 State로 S3/DDB 생성   │
│   └─ tfstate       ← 처음엔 로컬에 저장                │
│                                                     │
│  ~/terraform-lab/14-backend-use/                    │
│   ├─ backend.tf    ← 2단계: 원격 Backend 선언         │
│   └─ main.tf       ← State는 S3로 저장됨              │
└──────────────────────┬───────────────────────────────────┘
                     │ AWS CLI 인증
                     │ (~/.aws/credentials)
                     ▼
┌──────────────────────────────────────────────────────────┐
│  AWS (ap-northeast-2)                               │
│                                                     │
│   ┌────────────────────┐      ┌──────────────────────┐  │
│   │  S3 Bucket       │      │  DynamoDB Table    │  │
│   │  tf-backend-     │      │  terraform-lock    │  │
│   │   &lt;account-id&gt;   │      │                    │  │
│   │                  │      │  PK: LockID (S)    │  │
│   │ - Versioning ON  │      │  Billing: On-Demand│  │
│   │ - Public Block ON│      │                    │  │
│   │ - Encryption ON  │      │                    │  │
│   └────────────────────┘      └──────────────────────┘  │
│           ▲                          ▲              │
│           │ State R/W                │ Lock 획득/해제│
│           └──────────┬─────────────────┘              │
│                     │                               │
│              Terraform CLI                          │
└──────────────────────────────────────────────────────────┘</code></pre><br>

<h3 id="06-tfvars-환경-분리-실습">06. tfvars 환경 분리 실습</h3>
<ul>
<li>동일한 인프라 코드를 dev / staging / prod 환경에서 다른 값으로 재사용하기 위한 패턴</li>
<li>변수 선언(<a href="http://variables.tf/">variables.tf</a>)과 변수 값(*.tfvars)을 분리해 관리</li>
</ul>
<pre><code>┌──────────────────────────────────────────────────┐
│  공통 코드 (모든 환경 공유)                     │
│   ├─ main.tf                                 │
│   ├─ variables.tf      ← 변수 선언만 (값 없음)  │
│   └─ outputs.tf                              │
│                                              │
│  환경별 값 (분리 보관)                          │
│   └─ environments/                           │
│       ├─ dev.tfvars     ← 개발 환경 값         │
│       ├─ staging.tfvars ← 스테이징 값          │
│       └─ prod.tfvars    ← 운영 환경 값         │
└──────────────────────────────────────────────────┘

배포 시:
terraform apply -var-file=&quot;environments/dev.tfvars&quot;
terraform apply -var-file=&quot;environments/prod.tfvars&quot;</code></pre><br>

<h3 id="07-terraform-import">07. terraform import</h3>
<ul>
<li>콘솔로 이미 만든 AWS 리소스를 Terraform State에 등록해서 코드로 관리하는 기능</li>
<li>&quot;콘솔로 만들었지만 이제 IaC로 전환하고 싶다&quot; 상황의 핵심 도구</li>
</ul>
<br>

<h4 id="두-가지-import-방식">두 가지 import 방식</h4>
<table>
<thead>
<tr>
<th>방식</th>
<th>Terraform 버전</th>
<th>특징</th>
</tr>
</thead>
<tbody><tr>
<td>CLI <code>terraform import</code></td>
<td>1.0+</td>
<td>구버전 호환, 한 번에 1개씩, 코드 수동 작성</td>
</tr>
<tr>
<td><code>import</code> 블록</td>
<td>1.5+</td>
<td>권장, 코드 자동 생성 가능, plan으로 미리 확인</td>
</tr>
</tbody></table>
<br>

<h3 id="terraform-모듈--cicd"><strong>Terraform 모듈 &amp; CI/CD</strong></h3>
<h3 id="01-왜-모듈인가"><strong>01. 왜 모듈인가</strong></h3>
<ul>
<li>인프라 관리 규모가 커질수록 코드 중복, 유지보수 어려움, 종속성 파악 어려움이 발생</li>
<li>모듈(Module)은 이를 해결하는 재사용 가능한 인프라 코드의 집합</li>
</ul>
<table>
<thead>
<tr>
<th>특징</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>코드 재사용</td>
<td>같은 인프라 구성을 매번 작성할 필요 없음</td>
</tr>
<tr>
<td>관리 용이성</td>
<td>코드를 구조화해 팀원 간 협업과 유지보수 향상</td>
</tr>
<tr>
<td>표준화</td>
<td>특정 리소스 구성 방식을 일관되게 유지</td>
</tr>
<tr>
<td>버전 관리</td>
<td>모듈별 독립적인 버전 관리 가능</td>
</tr>
</tbody></table>
<br>

<h3 id="02-핵심-개념-2"><strong>02. 핵심 개념</strong></h3>
<h4 id="모듈-구조"><strong>모듈 구조</strong></h4>
<p>기본 구조:</p>
<pre><code>project/
 +-- main.tf       # 실제 리소스 정의
 +-- variables.tf  # 입력 변수 정의
 +-- outputs.tf    # 출력 값 정의
 +-- README.md     # 모듈의 사용 방법과 목적 설명</code></pre><p>표준 구조 (루트 모듈 + 자식 모듈):</p>
<pre><code>Terraform 프로젝트 (루트 모듈)
+-- main.tf
+-- provider.tf
+-- variables.tf
+-- outputs.tf
+-- modules/
    +-- vpc (자식 모듈 1)
    |   +-- main.tf
    |   +-- variables.tf
    |   +-- outputs.tf
    +-- ec2 (자식 모듈 2)
        +-- main.tf
        +-- variables.tf
        +-- outputs.tf</code></pre><hr>
<h3 id="eks-클러스터-구축--실무형-완전-구성"><strong>EKS 클러스터 구축 — 실무형 완전 구성</strong></h3>
<h3 id="01-전체-아키텍처-개요"><strong>01. 전체 아키텍처 개요</strong></h3>
<ul>
<li>실무에서 사용하는 방식과 동일하게 EKS 클러스터를 구성</li>
</ul>
<pre><code>전체 아키텍처

로컬 PC (VSCode)
    |  SSH (Termius / 터미널)
    v
tf-bastion (퍼블릭 서브넷, t3.micro)
    |  kubectl / aws cli (VPC 내부 통신)
    v
tf-cluster API Server (프라이빗, 외부 접근 차단)
    |
    +-- Node Group (프라이빗 서브넷 2a, 2c)
    |     t3.medium x 2 (AL2023)
    |
    +-- EBS CSI Driver  (IRSA: EBS 볼륨 관리)
    +-- ALB Controller  (IRSA: ALB 생성/관리)
    +-- Gateway API     (HTTPRoute -&gt; ALB)</code></pre><br>

<h4 id="terraform-모듈-구조">Terraform 모듈 구조</h4>
<pre><code>06.eks-terraform/
+-- main.tf              루트 모듈 (모듈 조합)
+-- variables.tf         루트 변수 선언
+-- outputs.tf           루트 출력값
+-- backend.tf           S3 Remote Backend
+-- terraform.tfvars     실습용 변수값
+-- modules/
|   +-- vpc/             VPC + 서브넷 + IGW + NAT GW + Route Table
|   |   +-- main.tf
|   |   +-- variables.tf
|   |   +-- outputs.tf
|   +-- bastion/         Bastion EC2 + 보안그룹
|   |   +-- main.tf
|   |   +-- variables.tf
|   |   +-- outputs.tf
|   +-- eks/             EKS 클러스터 + 노드그룹 + IRSA + 애드온
|   |   +-- main.tf
|   |   +-- variables.tf
|   |   +-- outputs.tf
|   +-- irsa-test/       IRSA 검증용 S3 버킷 + IAM Role
|       +-- main.tf
|       +-- outputs.tf
+-- manifests/           kubectl apply용 YAML 파일
    +-- 01-nginx-test.yaml     기본 Pod 동작 확인
    +-- 02-irsa-test.yaml      IRSA 권한 격리 검증
    +-- 03-ebs-test.yaml       EBS CSI Driver 검증
    +-- 04-gateway-test.yaml   Gateway API + ALB 검증</code></pre><br>

<h3 id="02-핵심-개념-3"><strong>02. 핵심 개념</strong></h3>
<h4 id="irsa-iam-roles-for-service-accounts"><strong>IRSA (IAM Roles for Service Accounts)</strong></h4>
<ul>
<li>Pod 단위로 최소 권한 IAM Role을 부여하는 패턴</li>
</ul>
<pre><code>IRSA 없는 경우 (위험)

노드 EC2 IAM Role에 모든 권한 부여
  -&gt; 이 노드의 모든 Pod이 동일한 권한 사용
  -&gt; Pod 탈취 시 노드 전체 권한 노출

---
IRSA 있는 경우 (안전)

각 Pod의 ServiceAccount에 Role ARN annotation
  -&gt; 해당 Pod만 특정 AWS 서비스 접근 가능
  -&gt; 다른 Pod은 권한 없음 (AccessDenied)</code></pre><br>

<h3 id="terraform-cicd--github-actions--oidc"><strong>Terraform CI/CD — GitHub Actions + OIDC</strong></h3>
<h3 id="01-왜-cicd가-필요한가"><strong>01. 왜 CI/CD가 필요한가</strong></h3>
<ul>
<li>Terraform 코드를 로컬에서만 실행하면 팀 협업 시 여러 문제가 발생</li>
</ul>
<pre><code>로컬 실행의 문제점

팀원 A                        팀원 B
terraform apply               terraform apply
    |                             |
    v                             v
AWS 리소스 변경               AWS 리소스 변경 (충돌!)

누가 언제 무엇을 바꿨는지 추적 불가
plan 없이 apply -&gt; 의도치 않은 변경
Access Key가 각 개발자 PC에 분산 -&gt; 보안 위험</code></pre><p>CI/CD 파이프라인으로 해결:</p>
<pre><code>모든 변경은 Git PR을 통해서만 반영

팀원 A -&gt; PR 오픈 -&gt; GitHub Actions: plan 자동 실행
팀원 B -&gt; 코드 리뷰 + plan 결과 확인
리뷰 승인 -&gt; main 머지 -&gt; GitHub Actions: apply 자동 실행
    |
    v
AWS 리소스 변경 (이력 = Git 커밋)</code></pre><br>

<h3 id="03-워크플로우-yaml-상세"><strong>03. 워크플로우 YAML 상세</strong></h3>
<p><strong>3-Job 파이프라인 구조</strong></p>
<pre><code class="language-yaml">PR 오픈 시:
  Job 1: validate   fmt-check, init, validate
  Job 2: plan       plan 실행 -&gt; artifact 저장 -&gt; PR 코멘트

main 머지 시:
  Job 1: validate
  Job 2: plan
  Job 3: apply      저장된 plan artifact로 apply 실행</code></pre>
<br>

<h4 id="전체-워크플로우-yaml">전체 워크플로우 YAML</h4>
<pre><code># .github/workflows/terraform.yml
#
# 역할: Terraform CI/CD 파이프라인
#   - PR 오픈 시: validate -&gt; plan -&gt; PR 코멘트 게시
#   - main 머지 시: validate -&gt; plan -&gt; apply (production 승인 후)
#
name: Terraform CI/CD

on:
  pull_request:
    branches: [main]
    paths:
      - &#39;terraform/**&#39;
      - &#39;.github/workflows/terraform.yml&#39;
  push:
    branches: [main]
    paths:
      - &#39;terraform/**&#39;

env:
  TF_VERSION: &#39;1.15.2&#39;
  AWS_REGION: &#39;ap-northeast-2&#39;
  TF_WORKING_DIR: &#39;./terraform/eks-infra&#39;

permissions:
  id-token: write       # OIDC 토큰 발급 필수
  contents: read
  pull-requests: write  # PR 코멘트 작성

jobs:
  validate:
    name: Validate
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: ${{ env.TF_WORKING_DIR }}

    steps:
      - name: 코드 체크아웃
        uses: actions/checkout@v4

      - name: Terraform 설치
        uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: ${{ env.TF_VERSION }}

      - name: AWS OIDC 인증
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/github-actions-terraform-role
          aws-region: ${{ env.AWS_REGION }}

      - name: Terraform Init
        run: terraform init

      - name: 포맷 검사
        run: terraform fmt -check -recursive

      - name: 문법 검증
        run: terraform validate

  plan:
    name: Plan
    needs: validate
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: ${{ env.TF_WORKING_DIR }}

    steps:
      - name: 코드 체크아웃
        uses: actions/checkout@v4

      - name: Terraform 설치
        uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: ${{ env.TF_VERSION }}

      - name: AWS OIDC 인증
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/github-actions-terraform-role
          aws-region: ${{ env.AWS_REGION }}

      - name: Terraform Init
        run: terraform init

      - name: Terraform Plan
        id: plan
        run: |
          terraform plan -no-color -out=tfplan 2&gt;&amp;1 | tee plan-output.txt
          echo &quot;exit_code=${PIPESTATUS[0]}&quot; &gt;&gt; $GITHUB_OUTPUT

      - name: Plan 결과 PR 코멘트 게시
        if: github.event_name == &#39;pull_request&#39;
        uses: actions/github-script@v7
        with:
          script: |
            const fs = require(&#39;fs&#39;);
            const plan = fs.readFileSync(&#39;${{ env.TF_WORKING_DIR }}/plan-output.txt&#39;, &#39;utf8&#39;);
            const maxLength = 65000;
            const truncated = plan.length &gt; maxLength
              ? plan.substring(0, maxLength) + &#39;\n... (출력 생략)&#39;
              : plan;

            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: `## Terraform Plan 결과\n\`\`\`hcl\n${truncated}\n\`\`\``
            });

      - name: Plan Artifact 저장
        uses: actions/upload-artifact@v4
        with:
          name: tfplan
          path: ${{ env.TF_WORKING_DIR }}/tfplan
          retention-days: 1

  apply:
    name: Apply
    needs: plan
    runs-on: ubuntu-latest
    if: github.event_name == &#39;push&#39; &amp;&amp; github.ref == &#39;refs/heads/main&#39;
    environment: production    # GitHub Environment 보호 규칙 적용
    defaults:
      run:
        working-directory: ${{ env.TF_WORKING_DIR }}

    steps:
      - name: 코드 체크아웃
        uses: actions/checkout@v4

      - name: Terraform 설치
        uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: ${{ env.TF_VERSION }}

      - name: AWS OIDC 인증
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/github-actions-terraform-role
          aws-region: ${{ env.AWS_REGION }}

      - name: Terraform Init
        run: terraform init

      - name: Plan Artifact 다운로드
        uses: actions/download-artifact@v4
        with:
          name: tfplan
          path: ${{ env.TF_WORKING_DIR }}

      - name: Terraform Apply
        run: terraform apply -auto-approve tfplan</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[멋쟁이사자처럼부트캠프 클라우드 엔지니어링 5기 [15주차]]]></title>
            <link>https://velog.io/@its-jihyeon/%EB%A9%8B%EC%9F%81%EC%9D%B4%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A7%81-5%EA%B8%B0-15%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@its-jihyeon/%EB%A9%8B%EC%9F%81%EC%9D%B4%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A7%81-5%EA%B8%B0-15%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Sat, 09 May 2026 09:26:09 GMT</pubDate>
            <description><![CDATA[<h3 id="kubernetes-아키텍쳐"><strong>Kubernetes 아키텍쳐</strong></h3>
<h3 id="01-왜-kubernetes인가"><strong>01. 왜 Kubernetes인가</strong></h3>
<p>Docker로 컨테이너 하나를 실행하는 것은 쉬움
하지만 실제 서비스를 운영하다 보면 금방 한계에 부딪힘</p>
<br>

<ul>
<li>Docker는 <strong>단일 호스트</strong> 위에서 컨테이너를 실행하는 도구</li>
<li>Kubernetes(이하 K8s)는 <strong>여러 서버(노드)에 걸쳐</strong> 컨테이너를 자동으로 배포·관리·복구하는 <strong>컨테이너 오케스트레이션(orchestration)</strong> 플랫폼</li>
</ul>
<br>

<p><strong>오케스트레이션(Orchestration)</strong></p>
<ul>
<li>여러 컨테이너의 배포, 확장, 복구, 네트워킹을 자동으로 조율하는 것</li>
</ul>
<br>

<p><strong>클러스터(Cluster)</strong></p>
<ul>
<li>여러 서버(노드)를 하나의 시스템처럼 묶어 관리하는 단위</li>
<li>K8s에서는 Control Plane 노드 1개 이상과 Worker 노드 여러 개가 하나의 클러스터를 이룸</li>
</ul>
<br>

<table>
<thead>
<tr>
<th>항목</th>
<th>Docker (단독)</th>
<th>Kubernetes</th>
</tr>
</thead>
<tbody><tr>
<td>실행 범위</td>
<td>단일 호스트</td>
<td>다중 서버(클러스터)</td>
</tr>
<tr>
<td>자동 복구</td>
<td>없음 (수동 재시작)</td>
<td>Pod 자동 재시작</td>
</tr>
<tr>
<td>자동 스케일링</td>
<td>없음 (수동)</td>
<td>HPA로 자동 확장·축소</td>
</tr>
<tr>
<td>로드밸런싱</td>
<td>별도 설정 필요</td>
<td>Service로 기본 제공</td>
</tr>
<tr>
<td>무중단 배포</td>
<td>별도 전략 필요</td>
<td>롤링 업데이트 기본 제공</td>
</tr>
<tr>
<td>설정 관리</td>
<td>.env 파일 직접 관리</td>
<td>ConfigMap / Secret</td>
</tr>
<tr>
<td>서비스 디스커버리</td>
<td>수동 IP/포트 관리</td>
<td>DNS 기반 자동 연결</td>
</tr>
</tbody></table>
<br>

<h3 id="02-kubernetes-전체-아키텍처"><strong>02. Kubernetes 전체 아키텍처</strong></h3>
<p>Kubernetes 클러스터는 크게 두 역할로 나뉨</p>
<ul>
<li><strong>Control Plane</strong>: 클러스터 전체를 관리하고 명령하는 두뇌</li>
<li><strong>Worker Node</strong>: 실제 컨테이너(Pod)가 실행되는 서버</li>
</ul>
<pre><code>┌──────────────────────────────────────────────────────────────┐
│                     Kubernetes Cluster                  │
│                                                         │
│      ┌───────────────────────────────────────────┐          │
│      │              Control Plane             │         │
│      │  ┌──────────────┐  ┌────────────────────┐ │         │
│      │  │ kube-       │  │       etcd       │ │         │
│      │  │ apiserver   │  │  (상태 저장소)     │ │         │
│      │  └──────────────┘  └────────────────────┘ │         │
│      │  ┌──────────────┐  ┌────────────────────┐ │         │
│      │  │ kube-       │  │ kube-controller- │ │         │
│      │  │ scheduler   │  │ manager          │ │         │
│      │  └──────────────┘  └────────────────────┘ │         │
│      └───────────────────────────────────────────┘         │
│         │                 │                │           │
│  ┌──────┴──────┐   ┌────────┴────┐   ┌────────┴────┐       │
│  │  Worker 1  │   │  Worker 2  │   │  Worker 3  │       │
│  │  kubelet   │   │  kubelet   │   │  kubelet   │       │
│  │  kube-proxy│   │  kube-proxy│   │  kube-proxy│       │
│  │  containerd│   │  containerd│   │  containerd│       │
│  │  [Pod][Pod]│   │  [Pod]     │   │  [Pod][Pod]│       │
│  └─────────────┘   └─────────────┘   └─────────────┘        │
│                                                         │
│  kubectl  &lt;──── 사용자가 클러스터에 명령을 내리는 CLI 도구    │
└──────────────────────────────────────────────────────────────┘</code></pre><br>

<h3 id="03-control-plane-컴포넌트"><strong>03. Control Plane 컴포넌트</strong></h3>
<ul>
<li>Control Plane은 4개의 핵심 컴포넌트로 구성</li>
<li>각 컴포넌트는 kube-system 네임스페이스의 <strong>Static Pod</strong>로 실행</li>
</ul>
<br>

<h4 id="①-kube-apiserver--모든-통신의-단일-진입점"><strong>① kube-apiserver — 모든 통신의 단일 진입점</strong></h4>
<ul>
<li>kubectl, 내부 컴포넌트, 외부 시스템 등 <strong>모든 요청은 반드시 API Server를 거침</strong></li>
<li>REST API 형태로 동작하며 인증(Authentication)·인가(Authorization)를 처리</li>
<li>etcd에 쓰고 읽는 유일한 컴포넌트 — 다른 컴포넌트는 API Server를 통해서만 etcd에 접근</li>
</ul>
<br>

<h4 id="②-etcd--클러스터의-유일한-상태-저장소"><strong>② etcd — 클러스터의 유일한 상태 저장소</strong></h4>
<ul>
<li>클러스터의 <strong>모든 상태 데이터</strong>를 저장하는 분산 Key-Value 스토어</li>
<li>Pod 목록, 설정, 시크릿, 노드 상태 등 오브젝트 전체가 여기에 저장됨</li>
<li>etcd가 손상되면 클러스터 전체가 동작 불능이 되므로 <strong>정기 백업이 필수</strong></li>
</ul>
<br>

<h4 id="③-kube-scheduler--pod-배치-결정자"><strong>③ kube-scheduler — Pod 배치 결정자</strong></h4>
<ul>
<li>새로 생성된 Pod를 <strong>어떤 Worker 노드에 배치할지 결정</strong>하는 컴포넌트</li>
<li>노드의 CPU·메모리 여유량, 레이블, 제약 조건(Taint/Affinity) 등을 종합 판단</li>
<li>결정만 하고 실제 실행은 kubelet이 담당</li>
</ul>
<br>

<h4 id="④-kube-controller-manager--상태-유지-자동화"><strong>④ kube-controller-manager — 상태 유지 자동화</strong></h4>
<ul>
<li>Desired State(원하는 상태)와 Current State(현재 상태)를 지속적으로 비교하고 차이를 자동으로 맞춤</li>
<li>내부에 여러 Controller가 하나의 프로세스로 실행</li>
</ul>
<br>

<h3 id="04-worker-node-컴포넌트"><strong>04. Worker Node 컴포넌트</strong></h3>
<ul>
<li>Worker Node에는 3가지 컴포넌트가 항상 실행</li>
</ul>
<br>

<h4 id="①-kubelet--노드의-현장-관리자"><strong>① kubelet — 노드의 현장 관리자</strong></h4>
<ul>
<li>Control Plane으로부터 &quot;이 Pod를 실행해라&quot;는 명령을 받아 Container Runtime에 전달</li>
<li>Pod의 실행 상태를 주기적으로 API Server에 보고</li>
<li>노드의 헬스체크(Liveness/Readiness Probe) 실행도 담당</li>
</ul>
<br>

<h4 id="②-container-runtime--컨테이너-실행-엔진"><strong>② Container Runtime — 컨테이너 실행 엔진</strong></h4>
<ul>
<li>컨테이너 이미지를 레지스트리에서 pull하고 컨테이너를 실제로 실행</li>
<li>K8s는 <strong>CRI(Container Runtime Interface)</strong> 규격을 지원하는 런타임만 사용 가능</li>
</ul>
<br>

<h4 id="③-kube-proxy--네트워크-규칙-관리자"><strong>③ kube-proxy — 네트워크 규칙 관리자</strong></h4>
<ul>
<li>각 노드에서 iptables(또는 ipvs) 규칙을 설정·관리</li>
<li>Service 오브젝트가 생성되면 해당 Service로 오는 트래픽이 올바른 Pod로 전달되도록 규칙을 업데이트</li>
</ul>
<br>

<h3 id="05-kubernetes-핵심-오브젝트-개요"><strong>05. Kubernetes 핵심 오브젝트 개요</strong></h3>
<ul>
<li>Kubernetes에서 모든 것은 <strong>오브젝트(Object)</strong> 로 표현</li>
</ul>
<br>

<p><strong>선언형(Declarative) vs 명령형(Imperative)</strong></p>
<ul>
<li>명령형: &quot;컨테이너를 실행해라&quot; (docker run처럼 직접 동작 지시)</li>
<li>선언형: &quot;nginx Pod가 3개 있는 상태가 되기를 원한다&quot;고 선언만 함 <strong>(K8s가 사용하는 방식)</strong></li>
</ul>
<br>

<p><strong>핵심 오브젝트</strong></p>
<pre><code>Pod             → 컨테이너를 담는 최소 실행 단위
Deployment      → Pod를 N개 유지하고 롤링 업데이트를 관리
StatefulSet     → 순서와 고정 ID가 필요한 Pod 집합 (DB 등)
DaemonSet       → 모든 노드에 1개씩 배치되는 Pod (모니터링 에이전트 등)
Service         → Pod에 고정 주소(ClusterIP)와 DNS를 부여하는 로드밸런서
ConfigMap       → 비민감 설정 값을 컨테이너에 주입
Secret          → 패스워드·토큰 등 민감 정보를 컨테이너에 주입
Namespace       → 클러스터를 논리적으로 분리하는 경계 (dev / prod 등)
PersistentVolume→ 컨테이너가 삭제되어도 유지되는 영속 스토리지</code></pre><br>

<h3 id="06-kubectl-기본-사용법"><strong>06. kubectl 기본 사용법</strong></h3>
<ul>
<li>kubectl은 Kubernetes API Server와 통신하는 CLI 도구</li>
</ul>
<pre><code>사용자
  ↓ kubectl 명령어 입력
kubectl (CLI 도구)
  ↓ HTTPS로 REST API 요청
kube-apiserver
  ↓
etcd / Scheduler / Controller</code></pre><br>

<h3 id="07-kubectl-apply-실행-시-내부-흐름"><strong>07. kubectl apply 실행 시 내부 흐름</strong></h3>
<h3 id="전체-흐름-다이어그램"><strong>전체 흐름 다이어그램</strong></h3>
<ul>
<li>kubectl apply -f deployment.yaml 한 줄을 실행했을 때 내부에서 일어나는 일</li>
</ul>
<pre><code>① kubectl apply -f deployment.yaml
        │ HTTPS POST 요청
        ↓
② kube-apiserver
        │ 인증 → 인가 → YAML 유효성 검사
        │ etcd에 Deployment 오브젝트 저장
        ↓
③ kube-controller-manager (Deployment Controller)
        │ &quot;Deployment 생성됨&quot; 이벤트 감지
        │ ReplicaSet 생성 → etcd 저장
        ↓
④ kube-controller-manager (ReplicaSet Controller)
        │ &quot;replicas: 3, 현재 0개&quot; 감지
        │ Pod 3개 생성 요청 → etcd 저장
        ↓
⑤ kube-scheduler
        │ &quot;nodeName 없는 Pod 3개&quot; 감지
        │ 각 Pod를 어느 노드에 배치할지 결정
        │ Pod.spec.nodeName 업데이트 → etcd 저장
        ↓
⑥ 해당 Worker 노드의 kubelet
        │ &quot;내 노드에 배치된 Pod 있음&quot; 감지
        │ containerd에 이미지 pull + 컨테이너 실행 요청
        ↓
⑦ containerd
        │ 이미지 다운로드 → 컨테이너 실행
        ↓
⑧ kubelet → API Server에 &quot;Pod Running&quot; 상태 보고
        ↓
⑨ kubectl get pods → STATUS: Running 확인</code></pre><br>

<h3 id="클러스터-구축">클러스터 구축</h3>
<h3 id="cnicontainer-network-interface">CNI(Container Network Interface)</h3>
<ul>
<li>Pod 간 통신을 가능하게 하는 네트워크 플러그인</li>
<li>CNI 없이는 Pod가 서로 통신할 수 없고 CoreDNS도 동작하지 않음</li>
</ul>
<br>

<h3 id="kubernetes-pod"><strong>Kubernetes Pod</strong></h3>
<h3 id="01-pod란"><strong>01. Pod란</strong></h3>
<ul>
<li>Pod는 Kubernetes에서 <strong>배포할 수 있는 가장 작은 단위</strong></li>
<li>Kubernetes 환경에서는 컨테이너를 직접 배포하지 않고, <strong>Pod라는 껍데기 안에 컨테이너를 넣어서</strong> 배포</li>
</ul>
<pre><code>[ Docker 단독 환경 ]                 [ Kubernetes 환경 ]

    컨테이너 (최소 단위)                  Pod (최소 단위)
  ┌──────────────────┐                  ┌──────────────────────┐
  │   nginx:1.27    │                 │  ┌────────────────┐  │
  │  IP: 172.17.0.2 │     →           │  │   nginx:1.27  │  │
  │   :80           │                 │  │   :80         │  │
  └──────────────────┘                  │  └────────────────┘ │
                                      │ Pod IP: 10.244.1.5 │
                                      └──────────────────────┘
                                              Node</code></pre><br>

<h4 id="핵심-특성-4가지">핵심 특성 4가지</h4>
<table>
<thead>
<tr>
<th>공유 자원</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td>네트워크 네임스페이스</td>
<td>같은 IP 주소, 같은 포트 공간 사용. <code>localhost</code>로 서로 통신 가능</td>
</tr>
<tr>
<td>IPC 네임스페이스</td>
<td>컨테이너 간 프로세스 통신(공유 메모리, 세마포어) 가능</td>
</tr>
<tr>
<td>UTS 네임스페이스</td>
<td>같은 호스트명(hostname) 공유</td>
</tr>
<tr>
<td>볼륨(Volume)</td>
<td>같은 디스크 공간을 마운트하여 파일 공유 가능</td>
</tr>
</tbody></table>
<br>

<p><strong>네임스페이스(Linux Namespace) :</strong> Linux 커널이 제공하는 격리 기술</p>
<br>

<h3 id="02-docker-vs-kubernetes-pod-비교">02. Docker vs Kubernetes Pod 비교</h3>
<table>
<thead>
<tr>
<th>비교 항목</th>
<th>Docker 컨테이너</th>
<th>Kubernetes Pod</th>
</tr>
</thead>
<tbody><tr>
<td>배포 단위</td>
<td>컨테이너 1개</td>
<td>1개 이상 컨테이너 묶음</td>
</tr>
<tr>
<td>네트워크</td>
<td>컨테이너마다 다른 IP</td>
<td>Pod 내부에서 IP 공유</td>
</tr>
<tr>
<td>스토리지</td>
<td>컨테이너마다 별도 볼륨</td>
<td>Pod 내부에서 볼륨 공유</td>
</tr>
<tr>
<td>실행 위치</td>
<td>사람이 직접 서버 지정</td>
<td>Scheduler가 자동 배치</td>
</tr>
<tr>
<td>자가 치유</td>
<td>없음</td>
<td>워크로드 리소스와 연동 시 자동 복구</td>
</tr>
<tr>
<td>선언적 관리</td>
<td><code>docker run</code> 명령형</td>
<td>YAML 선언형</td>
</tr>
</tbody></table>
<br>

<h4 id="동작-흐름-비교">동작 흐름 비교</h4>
<pre><code>[ Docker 환경에서 컨테이너 실행 ]

사용자
  │
  │ docker run nginx:1.27
  ▼
docker daemon
  │
  ▼
컨테이너 시작 → 죽으면 그냥 죽음 (재시작 없음)

---

[ Kubernetes 환경에서 Pod 생성 ]

사용자
  │
  │ kubectl apply -f pod.yaml
  ▼
kube-apiserver (요청 접수)
  │
  ▼
etcd (원하는 상태 저장)
  │
  ▼
kube-scheduler (어느 노드에 배치할지 결정)
  │
  ▼
kubelet (해당 노드에서 실행)
  │
  ▼
containerd (실제 컨테이너 생성)
  │
  ▼
Pod 시작 → 죽어도 워크로드 리소스가 자동 복구</code></pre><br>

<h3 id="03-pod-내부-구조"><strong>03. Pod 내부 구조</strong></h3>
<ul>
<li>사용자가 정의한 <strong>앱 컨테이너</strong> 외에도 보이지 않는 <strong>pause 컨테이너</strong>가 함께 존재</li>
</ul>
<pre><code>                    Node (Ubuntu Server)
┌──────────────────────────────────────────────────────────┐
│                                                     │
│   Pod: my-app-pod                                   │
│  ┌────────────────────────────────────────────────────┐  │
│  │  Pod IP: 10.244.1.5  (Pod 내부 컨테이너가 공유)   │  │
│  │                                                │  │
│  │  ┌─────────────────┐                             │  │
│  │  │pause container │ ← 네트워크 네임스페이스 보유   │  │
│  │  │ (보이지 않음)   │                             │  │
│  │  └─────────────────┘                             │  │
│  │                                                │  │
│  │  ┌─────────────────┐    ┌────────────────┐        │  │
│  │  │  app container │    │ Volume (공유) │        │  │
│  │  │  Spring Boot   │    │  /data       │         │  │
│  │  │  :8080         │    │              │         │  │
│  │  └─────────────────┘    └────────────────┘         │  │
│  └─────────────────────────────────────────────────────┘  │
│                                                      │
└───────────────────────────────────────────────────────────┘</code></pre><br>

<h3 id="04-pod-yaml-기본-구조">04. Pod YAML 기본 구조</h3>
<pre><code class="language-yaml"># my-pod.yaml
apiVersion: v1               # API 버전
kind: Pod                    # 리소스 종류
metadata:                    # 식별 정보
  name: my-app-pod
  labels:
    app: my-app
spec:                        # 원하는 상태(Desired State)
  containers:
  - name: app
    image: nginx:1.27
    ports:
    - containerPort: 80</code></pre>
<br>

<h3 id="apiversion-상세">apiVersion 상세</h3>
<table>
<thead>
<tr>
<th>리소스</th>
<th>apiVersion 값</th>
</tr>
</thead>
<tbody><tr>
<td>Pod, Service, ConfigMap, Secret</td>
<td><code>v1</code></td>
</tr>
<tr>
<td>Deployment, ReplicaSet, StatefulSet, DaemonSet</td>
<td><code>apps/v1</code></td>
</tr>
<tr>
<td>Job, CronJob</td>
<td><code>batch/v1</code></td>
</tr>
<tr>
<td>Ingress</td>
<td><code>networking.k8s.io/v1</code></td>
</tr>
<tr>
<td>HorizontalPodAutoscaler</td>
<td><code>autoscaling/v2</code></td>
</tr>
</tbody></table>
<br>

<h3 id="04-멀티-컨테이너-패턴"><strong>04. 멀티 컨테이너 패턴</strong></h3>
<ul>
<li>대부분의 Pod는 컨테이너 1개로 구성하지만, 명확한 이유가 있을 때 여러 컨테이너를 한 Pod에 넣음</li>
</ul>
<table>
<thead>
<tr>
<th>패턴</th>
<th>역할</th>
<th>대표 사례</th>
</tr>
</thead>
<tbody><tr>
<td>Sidecar</td>
<td>메인 앱 보조 (로그, 모니터링)</td>
<td>Fluent Bit, Envoy proxy</td>
</tr>
<tr>
<td>Ambassador</td>
<td>외부 통신 프록시</td>
<td>DB 커넥션 프록시</td>
</tr>
<tr>
<td>Adapter</td>
<td>출력 포맷 변환</td>
<td>Prometheus exporter</td>
</tr>
</tbody></table>
<br>

<h3 id="05-주요-kubectl-명령어">05. 주요 kubectl 명령어</h3>
<pre><code># Pod 생성
kubectl apply -f my-pod.yaml

# Pod 목록
kubectl get pods
kubectl get pods -o wide                # IP, 노드 포함
kubectl get pods --show-labels          # 라벨 포함
kubectl get pods -A                     # 모든 네임스페이스

# Pod 상세 정보
kubectl describe pod my-app-pod

# Pod 로그
kubectl logs my-app-pod
kubectl logs my-app-pod --previous      # 재시작 직전 로그
kubectl logs my-app-pod -c app          # 멀티 컨테이너에서 특정 컨테이너

# Pod 진입
kubectl exec -it my-app-pod -- /bin/bash

# Pod 삭제
kubectl delete pod my-app-pod

# 라벨 기반 검색
kubectl get pods -l app=spring-app
kubectl get pods -l &#39;tier in (backend, frontend)&#39;

# YAML 초안 빠르게 생성
kubectl run my-app-pod --image=nginx:1.27 --dry-run=client -o yaml &gt; my-pod.yaml

# 실시간 상태 감시
kubectl get pods -w</code></pre><br>

<h3 id="kubernetes-pod-라이프사이클">Kubernetes Pod 라이프사이클</h3>
<h3 id="probe"><strong>Probe</strong></h3>
<ul>
<li>Kubernetes가 <strong>컨테이너의 건강 상태를 주기적으로 검사</strong>하는 메커니즘</li>
</ul>
<table>
<thead>
<tr>
<th>Probe</th>
<th>실패 시 동작</th>
<th>주요 용도</th>
</tr>
</thead>
<tbody><tr>
<td><code>startupProbe</code></td>
<td>컨테이너 재시작</td>
<td>느린 앱 기동 시간 확보</td>
</tr>
<tr>
<td><code>livenessProbe</code></td>
<td>컨테이너 재시작</td>
<td>데드락, 무한루프 등 비정상 상태 감지</td>
</tr>
<tr>
<td><code>readinessProbe</code></td>
<td>Service 엔드포인트에서 제거 (트래픽 차단)</td>
<td>트래픽 수신 가능 여부</td>
</tr>
</tbody></table>
<br>

<h2 id="kubernetes-service"><strong>Kubernetes Service</strong></h2>
<h3 id="01-service란"><strong>01. Service란</strong></h3>
<ul>
<li><strong>고정된 가상 IP(ClusterIP)</strong> 와 <strong>DNS 이름</strong> 을 제공하고, 그 뒤에 있는 Pod들로 트래픽을 자동 분산</li>
</ul>
<pre><code>[ Service 없이 직접 Pod 접근 — 문제 ]

클라이언트
   │  http://10.244.1.5:8080
   ▼
Pod A (10.244.1.5)  ← 재시작 → IP 변경 (10.244.1.9)
                                 │
                             연결 끊김!

---
[ Service 사용 — 해결 ]

클라이언트
   │  http://my-svc:80   (고정 주소)
   ▼
Service (ClusterIP: 10.96.100.1, 영구 고정)
   │
   ├──► Pod A (10.244.1.5)  ← 재시작되어도
   ├──► Pod B (10.244.1.6)  ← IP가 바뀌어도
   └──► Pod C (10.244.1.7)  ← Service가 알아서 연결</code></pre><br>

<h4 id="service가-제공하는-3가지-기능"><strong>Service가 제공하는 3가지 기능</strong></h4>
<ul>
<li><strong>서비스 디스커버리(Service Discovery)</strong>: Pod의 IP가 변해도 Service 이름으로 접근 가능</li>
<li><strong>로드 밸런싱(Load Balancing)</strong>: 여러 Pod로 트래픽 자동 분산</li>
<li><strong>외부 노출(External Exposure)</strong>: NodePort, LoadBalancer 타입으로 클러스터 외부에서 접근 가능</li>
</ul>
<br>

<h3 id="02-selector와-label-연결-메커니즘"><strong>02. Selector와 Label 연결 메커니즘</strong></h3>
<ul>
<li>어떤 Pod로 트래픽을 보낼지 어떻게 알까? <strong>Label Selector</strong> 라는 메커니즘을 사용</li>
</ul>
<br>

<h3 id="03-clusterip-기본-타입"><strong>03. ClusterIP (기본 타입)</strong></h3>
<ul>
<li>Service의 기본 타입</li>
<li><strong>클러스터 내부에서만 접근</strong> 가능한 가상 IP를 할당받음</li>
</ul>
<pre><code>┌─────────────────── Kubernetes 클러스터 ───────────────────┐
│                                                      │
│   Frontend Pod ──► Service (ClusterIP: 10.96.100.1)  │
│                       │                              │
│                       ├──► Backend Pod 1             │
│                       ├──► Backend Pod 2             │
│                       └──► Backend Pod 3             │
│                                                      │
│   ※ 외부에서는 10.96.100.1 직접 접근 불가               │
└───────────────────────────────────────────────────────────┘</code></pre><br>

<h4 id="yaml-예시">YAML 예시</h4>
<pre><code class="language-yaml">apiVersion: v1
kind: Service
metadata:
  name: my-app-service
spec:
  type: ClusterIP                # 생략 시 기본값
  selector:
    app: my-app
  ports:
  - port: 80                     # Service 포트 (클라이언트가 접근하는 포트)
    targetPort: 8080             # Pod의 실제 포트
    protocol: TCP                # TCP / UDP / SCTP</code></pre>
<br>

<h3 id="04-nodeport"><strong>04. NodePort</strong></h3>
<ul>
<li>NodePort는 클러스터 외부에서 접근 가능하게 만드는 가장 단순한 타입</li>
<li><strong>모든 노드의 특정 포트</strong>를 열어두고, 그 포트로 들어온 트래픽을 Service로 전달</li>
</ul>
<pre><code>외부 브라우저
    │  http://192.168.56.11:30080
    │       또는
    │  http://192.168.56.12:30080
    ▼
┌─── 모든 노드의 30080 포트 동일하게 열림 ──────┐
│                                          │
│   Node 1 (192.168.56.11) :30080          │
│        │                                 │
│        ▼                                 │
│   Service (ClusterIP) :80                │
│        │                                 │
│        └──► Pod (위치는 어느 노드든 가능)    │
│                                          │
└──────────────────────────────────────────────┘</code></pre><br>

<h4 id="yaml-예시-1">YAML 예시</h4>
<pre><code class="language-yaml">apiVersion: v1
kind: Service
metadata:
  name: my-app-nodeport
spec:
  type: NodePort
  selector:
    app: my-app
  ports:
  - port: 80                     # Service 포트 (클러스터 내부)
    targetPort: 8080             # Pod 포트
    nodePort: 30080              # 노드에서 열리는 포트</code></pre>
<br>

<h3 id="05-loadbalancer"><strong>05. LoadBalancer</strong></h3>
<ul>
<li>LoadBalancer는 클라우드 환경(AWS, GCP, Azure)에서 사용하는 타입</li>
<li>클라우드의 로드 밸런서(AWS의 NLB/ALB 등)를 자동으로 프로비저닝하여 외부에서 접근 가능한 IP/DNS를 제공</li>
</ul>
<pre><code>인터넷
   │
   ▼
AWS NLB (자동 프로비저닝)
   │  EXTERNAL-IP: a1b2c3.ap-northeast-2.elb.amazonaws.com
   │  Port: 80
   ▼
NodePort (내부 자동 생성)
   │
   ▼
ClusterIP
   │
   ▼
  Pod</code></pre><br>

<h4 id="yaml-예시-aws-eks">YAML 예시 (AWS EKS)</h4>
<pre><code class="language-yaml">apiVersion: v1
kind: Service
metadata:
  name: my-app-lb
  annotations:
    # AWS Load Balancer Controller 어노테이션
    service.beta.kubernetes.io/aws-load-balancer-type: &quot;nlb&quot;
    service.beta.kubernetes.io/aws-load-balancer-scheme: &quot;internet-facing&quot;
spec:
  type: LoadBalancer
  selector:
    app: my-app
  ports:
  - port: 80
    targetPort: 8080</code></pre>
<br>

<h3 id="06-externalname"><strong>06. ExternalName</strong></h3>
<ul>
<li>ExternalName은 클러스터 내부에서 외부 서비스를 마치 내부 Service인 것처럼 사용할 수 있게 해주는 타입 (<strong>DNS CNAME 레코드</strong>로 매핑)</li>
</ul>
<pre><code>앱 코드:
  jdbc:mysql://external-db:3306/orderdb
            ↓ (클러스터 DNS가 CNAME 변환)
  jdbc:mysql://my-rds.xxxxxx.ap-northeast-2.rds.amazonaws.com:3306/orderdb</code></pre><br>

<h4 id="yaml-예시-2">YAML 예시</h4>
<pre><code class="language-yaml">apiVersion: v1
kind: Service
metadata:
  name: external-db
spec:
  type: ExternalName
  externalName: my-rds.xxxxxx.ap-northeast-2.rds.amazonaws.com</code></pre>
<h3 id="kubernetes-ingress"><strong>Kubernetes Ingress</strong></h3>
<h3 id="01-ingress란"><strong>01. Ingress란</strong></h3>
<pre><code>[ Ingress 사용 시 ]

인터넷
   │  http://example.com/...
   ▼
Ingress (1개 진입점)
   │
   ├── /web      → frontend Service
   ├── /api      → api Service
   ├── /admin    → admin Service
   ├── api.example.com → api Service
   └── shop.example.com → shop Service

장점:
- 단일 진입점 (LB 1개로 충분, 비용 절감)
- 도메인 기반 라우팅 (host)
- 경로 기반 라우팅 (path)
- HTTPS 종료 (TLS 인증서 통합 관리)
- HTTP 헤더 조작, 리다이렉트, rewrite</code></pre><br>

<h3 id="02-ingress의-핵심--controller가-필요"><strong>02. Ingress의 핵심 — Controller가 필요</strong></h3>
<ul>
<li><strong>Ingress 리소스 자체는 단순한 &quot;규칙 정의&quot;</strong></li>
<li>실제 트래픽을 처리하려면 <strong>Ingress Controller</strong> 라는 별도의 프로그램이 필요</li>
</ul>
<pre><code>[ Ingress의 두 구성 요소 ]

Ingress 리소스 (YAML)              Ingress Controller (Pod)
       ↓                                  ↓
&quot;규칙 선언&quot;                         &quot;실제 트래픽 처리&quot;
&quot;/web → web-svc&quot;                    NGINX, HAProxy, Envoy 등으로 동작
&quot;/api → api-svc&quot;                    Ingress 리소스를 읽고 자기 설정 변경

규칙만 있다고 동작하지 않음.
Controller가 그 규칙대로 트래픽을 처리해야 비로소 동작.</code></pre><br>

<h4 id="주요-ingress-controller-종류">주요 Ingress Controller 종류</h4>
<table>
<thead>
<tr>
<th>Controller</th>
<th>특징</th>
<th>용도</th>
</tr>
</thead>
<tbody><tr>
<td><strong>ingress-nginx</strong></td>
<td>NGINX 기반, 가장 널리 사용</td>
<td>일반적인 베어메탈/로컬 환경</td>
</tr>
<tr>
<td>AWS Load Balancer Controller</td>
<td>ALB 자동 프로비저닝</td>
<td>EKS 환경</td>
</tr>
<tr>
<td>Traefik</td>
<td>동적 설정, 컨테이너 친화적</td>
<td>Docker Swarm 환경에서 시작, K8s에서도 사용</td>
</tr>
<tr>
<td>HAProxy Ingress</td>
<td>HAProxy 기반</td>
<td>고성능 요구 환경</td>
</tr>
<tr>
<td>Contour</td>
<td>Envoy 기반</td>
<td>Service Mesh 친화적</td>
</tr>
<tr>
<td>Cilium</td>
<td>eBPF 기반</td>
<td>네트워크 정책 통합</td>
</tr>
</tbody></table>
<br>

<h3 id="03-ingress-동작-흐름"><strong>03. Ingress 동작 흐름</strong></h3>
<pre><code>[ Ingress 동작 흐름 — 6단계 ]

1. 사용자: curl http://demo.local/web
              │
              ▼
2. DNS: demo.local → 노드 IP (192.168.56.11) 변환
   (호스트 PC의 /etc/hosts 또는 실제 DNS 서버)
              │
              ▼
3. 노드 IP의 NodePort (예: 31234) 도달
   (ingress-nginx-controller Service의 NodePort)
              │
              ▼
4. ingress-nginx-controller Pod (NGINX)
   Ingress 리소스의 규칙 평가:
   - host: demo.local → 매칭
   - path: /web (Prefix) → 매칭
              │
              ▼
5. 백엔드 Service로 트래픽 전달 (web-svc)
              │
              ▼
6. Service의 Endpoints 중 한 Pod로 라우팅 (web-app Pod)
              │
              ▼
   응답 반환 → 사용자</code></pre><br>

<h3 id="04-ingress-yaml-기본-구조">04. Ingress YAML 기본 구조</h3>
<pre><code class="language-yaml">apiVersion: networking.k8s.io/v1     # Ingress의 API 그룹
kind: Ingress
metadata:
  name: demo-ingress
  namespace: lab-ingress
spec:
  ingressClassName: nginx            # 어떤 Controller가 처리할지
  rules:
  - host: demo.local                 # 도메인 (선택)
    http:
      paths:
      - path: /web                   # 경로
        pathType: Prefix             # 매칭 방식
        backend:
          service:
            name: web-svc            # 대상 Service
            port:
              number: 80             # Service 포트</code></pre>
<hr>
<h3 id="kubernetes-워크로드-리소스--replicaset"><strong>Kubernetes 워크로드 리소스 &amp; ReplicaSet</strong></h3>
<h3 id="01-왜-pod를-직접-관리하면-안-되는가"><strong>01. 왜 Pod를 직접 관리하면 안 되는가</strong></h3>
<ul>
<li>실제 운영에서는 <strong>Pod를 직접 만들지 않음</strong>. (<strong>직접 생성한 Pod는 죽으면 다시 살아나지 않음)</strong></li>
</ul>
<br>

<h3 id="02-워크로드-리소스-종류"><strong>02. 워크로드 리소스 종류</strong></h3>
<ul>
<li>Kubernetes는 사용 목적에 따라 다양한 워크로드 리소스를 제공</li>
</ul>
<pre><code>Kubernetes 워크로드 리소스
│
├── Deployment      ← 무상태(Stateless) 앱 배포 (가장 많이 사용)
│      └── ReplicaSet (Deployment가 자동 관리)
│
├── StatefulSet     ← 유상태(Stateful) 앱 배포 (DB 등)
│
├── DaemonSet       ← 모든 노드에 1개씩 배포 (로그, 모니터링)
│
├── Job             ← 한 번 실행 후 완료 (배치 작업)
│
└── CronJob         ← 주기적 실행 (스케줄 배치)</code></pre><br>

<h3 id="03-replicaset-개념"><strong>03. ReplicaSet 개념</strong></h3>
<ul>
<li><strong>항상 지정한 수의 Pod 복제본(replica)을 유지</strong>하는 가장 기본적인 워크로드 리소스</li>
</ul>
<br>

<h4 id="pod-복제-vs-재시작의-차이">Pod 복제 vs 재시작의 차이</h4>
<table>
<thead>
<tr>
<th>항목</th>
<th>restartPolicy (Pod 자체)</th>
<th>ReplicaSet (외부에서)</th>
</tr>
</thead>
<tbody><tr>
<td>복구 범위</td>
<td>같은 Pod의 컨테이너 재시작</td>
<td>새 Pod 자체를 생성</td>
</tr>
<tr>
<td>노드 장애 대응</td>
<td>노드가 죽으면 Pod도 죽음</td>
<td>다른 노드에 새 Pod 생성</td>
</tr>
<tr>
<td>스케일링</td>
<td>1개로 고정</td>
<td>replicas 수 변경 가능</td>
</tr>
</tbody></table>
<br>

<h3 id="04-replicaset-yaml-프로퍼티-상세"><strong>04. ReplicaSet YAML 프로퍼티 상세</strong></h3>
<pre><code class="language-yaml">apiVersion: apps/v1                  #  Pod와 다름! apps/v1
kind: ReplicaSet
metadata:
  name: my-app-rs
  labels:
    app: my-app
spec:
  replicas: 3                        # 유지할 Pod 개수
  selector:
    matchLabels:
      app: my-app                    # 이 라벨의 Pod를 관리
  template:                          # 새 Pod를 만들 때 사용할 설계도
    metadata:
      labels:
        app: my-app                  #  selector와 반드시 일치
    spec:
      containers:
      - name: app
        image: nginx:1.27
        ports:
        - containerPort: 80</code></pre>
<br>

<h3 id="kubernetes-deployment"><strong>Kubernetes Deployment</strong></h3>
<h3 id="01-deployment란"><strong>01. Deployment란</strong></h3>
<ul>
<li>Deployment는 Kubernetes에서 <strong>가장 많이 사용하는 워크로드 리소스</strong></li>
<li>무상태(Stateless) 앱 배포에 사용하며, 내부적으로 ReplicaSet을 자동 관리</li>
</ul>
<br>

<h4 id="deployment가-추가로-제공하는-기능">Deployment가 추가로 제공하는 기능</h4>
<table>
<thead>
<tr>
<th>기능</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>롤링 업데이트</strong></td>
<td>서비스 중단 없이 새 버전으로 점진 교체</td>
</tr>
<tr>
<td><strong>롤백</strong></td>
<td>이전 버전으로 즉시 되돌리기</td>
</tr>
<tr>
<td><strong>배포 이력</strong></td>
<td>모든 배포 버전(revision)을 추적</td>
</tr>
<tr>
<td><strong>일시정지</strong></td>
<td>배포 중간에 멈춰서 검증 가능 (카나리 기초)</td>
</tr>
<tr>
<td><strong>자동 재배포</strong></td>
<td>template 변경 시 모든 Pod 자동 교체</td>
</tr>
</tbody></table>
<br>

<h3 id="02-deployment-계층-구조-상세"><strong>02. Deployment 계층 구조 상세</strong></h3>
<ul>
<li>Deployment는 직접 Pod를 만들지 않고, ReplicaSet을 통해 간접적으로 Pod를 관리</li>
<li>3계층 구조가 롤링 업데이트의 기반</li>
</ul>
<br>

<h3 id="03-deployment-yaml-프로퍼티-상세">03. Deployment YAML 프로퍼티 상세</h3>
<pre><code class="language-yaml">apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  annotations:
    kubernetes.io/change-cause: &quot;nginx 1.28 보안 패치 CVE-2025-xxxx&quot;
spec:
  replicas: 4
  revisionHistoryLimit: 5
  progressDeadlineSeconds: 300
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0           # 무중단 배포
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
        version: &quot;1.28&quot;
    spec:
      containers:
      - name: app
        image: nginx:1.28
        ports:
        - containerPort: 80
          name: http
        resources:
          requests:
            cpu: &quot;100m&quot;
            memory: &quot;128Mi&quot;
          limits:
            cpu: &quot;300m&quot;
            memory: &quot;256Mi&quot;
        readinessProbe:
          httpGet:
            path: /
            port: http
          initialDelaySeconds: 5
          periodSeconds: 5
        livenessProbe:
          httpGet:
            path: /
            port: http
          initialDelaySeconds: 10
          periodSeconds: 10
        lifecycle:
          preStop:
            exec:
              command: [&quot;sleep&quot;, &quot;5&quot;]
      terminationGracePeriodSeconds: 30</code></pre>
<br>

<h3 id="04-롤링-업데이트-동작-원리"><strong>04. 롤링 업데이트 동작 원리</strong></h3>
<ul>
<li>롤링 업데이트는 새 버전으로 한 번에 모두 바꾸지 않고, <strong>하나씩 또는 일부씩 점진적으로</strong> 교체하는 방식</li>
</ul>
<br>

<h4 id="흐름-replicas4-maxsurge1-maxunavailable0">흐름 (replicas=4, maxSurge=1, maxUnavailable=0)</h4>
<pre><code>초기 상태: v1 Pod × 4
[v1] [v1] [v1] [v1]                      replicas=4 (정상)

──────────────────────────────────────────────────
Step 1: v2 Pod 1개 추가 (maxSurge=1로 +1 허용)
[v1] [v1] [v1] [v1] [v2]                  총 5개 (4+1)
                          ↑ Ready 대기

──────────────────────────────────────────────────
Step 2: v2 Ready 확인 → v1 Pod 1개 제거
[v1] [v1] [v1] [v2]                       총 4개

──────────────────────────────────────────────────
Step 3: v2 Pod 1개 추가
[v1] [v1] [v1] [v2] [v2]                  총 5개

──────────────────────────────────────────────────
Step 4: v2 Ready → v1 1개 제거
[v1] [v1] [v2] [v2]                       총 4개

... 계속 반복 ...

──────────────────────────────────────────────────
완료: v2 × 4
[v2] [v2] [v2] [v2]                       업데이트 완료</code></pre><br>

<h3 id="kubernetes-statefulset"><strong>Kubernetes StatefulSet</strong></h3>
<h3 id="01-statefulset이란"><strong>01. StatefulSet이란</strong></h3>
<ul>
<li>Deployment가 만드는 Pod는 모두 <strong>교체 가능한 일회용</strong></li>
<li>이름도 랜덤(my-app-5d4b7f-xk9p2), 어느 Pod에 트래픽이 가도 같은 결과를 반환하므로 IP가 바뀌어도 문제 없음</li>
</ul>
<br>

<h4 id="deployment-vs-statefulset-비교">Deployment vs StatefulSet 비교</h4>
<table>
<thead>
<tr>
<th>비교 항목</th>
<th>Deployment</th>
<th>StatefulSet</th>
</tr>
</thead>
<tbody><tr>
<td>Pod 이름</td>
<td>랜덤 해시 (<code>my-app-5d4b7f-xk9p2</code>)</td>
<td>순서 번호 (<code>mysql-0</code>, <code>mysql-1</code>, ...)</td>
</tr>
<tr>
<td>재시작 후 이름</td>
<td>변경됨</td>
<td>동일하게 유지</td>
</tr>
<tr>
<td>기동 순서</td>
<td>동시 기동</td>
<td>순서대로 (0 → 1 → 2)</td>
</tr>
<tr>
<td>종료 순서</td>
<td>동시 종료</td>
<td>역순 (2 → 1 → 0)</td>
</tr>
<tr>
<td>Pod IP</td>
<td>변경됨</td>
<td>변경됨 (DNS는 고정)</td>
</tr>
<tr>
<td>안정적 DNS</td>
<td>없음 (Service만)</td>
<td>각 Pod마다 고유 DNS</td>
</tr>
<tr>
<td>스토리지</td>
<td>공유 또는 없음</td>
<td>Pod마다 독립 PVC 자동 생성</td>
</tr>
<tr>
<td>스케일 다운 시 PVC</td>
<td>함께 삭제</td>
<td><strong>PVC 보존</strong> (데이터 보호)</td>
</tr>
<tr>
<td>Service 요구사항</td>
<td>일반 Service</td>
<td><strong>Headless Service 필수</strong></td>
</tr>
<tr>
<td>주 용도</td>
<td>웹 서버, API 서버</td>
<td>DB, 메시지 큐, 캐시</td>
</tr>
</tbody></table>
<br>

<h3 id="02-statefulset-yaml-프로퍼티-상세">02. StatefulSet YAML 프로퍼티 상세</h3>
<pre><code class="language-yaml">apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  serviceName: &quot;mysql-headless&quot;        #  Headless Service 이름 필수
  replicas: 3
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: mysql
        image: mysql:8.4
        env:
        - name: MYSQL_ROOT_PASSWORD
          value: &quot;mypassword&quot;
        ports:
        - containerPort: 3306
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
  volumeClaimTemplates:                #  Pod마다 PVC 자동 생성
  - metadata:
      name: data
    spec:
      accessModes: [&quot;ReadWriteOnce&quot;]
      resources:
        requests:
          storage: 10Gi</code></pre>
<br>

<h3 id="03-실무-패턴--mysql-primary-replica"><strong>03. 실무 패턴 — MySQL Primary-Replica</strong></h3>
<ul>
<li>MySQL을 StatefulSet으로 배포할 때의 패턴</li>
<li>Init Container로 Pod 순서에 따라 Primary/Replica를 자동 설정</li>
</ul>
<pre><code class="language-yaml">apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  serviceName: &quot;mysql-headless&quot;
  replicas: 3
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      initContainers:
      - name: init-mysql
        image: mysql:8.4
        command:
        - bash
        - -c
        - |
          # Pod 이름 끝의 숫자 추출
          [[ $HOSTNAME =~ -([0-9]+)$ ]] || exit 1
          ordinal=${BASH_REMATCH[1]}

          # 0번 Pod는 Primary, 나머지는 Replica
          if [[ $ordinal -eq 0 ]]; then
            cp /mnt/config-map/primary.cnf /mnt/conf.d/
          else
            cp /mnt/config-map/replica.cnf /mnt/conf.d/
          fi
        volumeMounts:
        - name: conf
          mountPath: /mnt/conf.d
        - name: config-map
          mountPath: /mnt/config-map
      containers:
      - name: mysql
        image: mysql:8.4
        env:
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-secret
              key: password
        ports:
        - containerPort: 3306
          name: mysql
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
        - name: conf
          mountPath: /etc/mysql/conf.d
        livenessProbe:
          exec:
            command: [&quot;mysqladmin&quot;, &quot;ping&quot;, &quot;-uroot&quot;, &quot;-p$MYSQL_ROOT_PASSWORD&quot;]
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          exec:
            command: [&quot;mysql&quot;, &quot;-uroot&quot;, &quot;-p$MYSQL_ROOT_PASSWORD&quot;, &quot;-e&quot;, &quot;SELECT 1&quot;]
          initialDelaySeconds: 5
          periodSeconds: 2
      volumes:
      - name: conf
        emptyDir: {}
      - name: config-map
        configMap:
          name: mysql-config
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: [&quot;ReadWriteOnce&quot;]
      storageClassName: &quot;standard&quot;
      resources:
        requests:
          storage: 20Gi</code></pre>
<br>

<h3 id="daemonset-·-job-·-cronjob"><strong>DaemonSet · Job · CronJob</strong></h3>
<h3 id="01-daemonset이란"><strong>01. DaemonSet이란</strong></h3>
<ul>
<li>DaemonSet은 <strong>클러스터의 모든 노드에 Pod를 1개씩</strong> 자동 배포하는 워크로드 리소스</li>
<li>새 노드가 추가되면 자동 생성, 노드가 제거되면 자동 삭제</li>
</ul>
<br>

<h3 id="02-daemonset-yaml-프로퍼티">02. DaemonSet YAML 프로퍼티</h3>
<pre><code class="language-yaml">apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: log-agent
spec:
  selector:
    matchLabels:
      app: log-agent
  template:
    metadata:
      labels:
        app: log-agent
    spec:
      containers:
      - name: fluent-bit
        image: fluent/fluent-bit:3.2
        volumeMounts:
        - name: varlog
          mountPath: /var/log
      volumes:
      - name: varlog
        hostPath:                  #  노드의 실제 디렉토리 마운트
          path: /var/log</code></pre>
<br>

<h3 id="03-job이란"><strong>03. Job이란</strong></h3>
<ul>
<li>Job은 <strong>정해진 작업을 완료할 때까지 실행</strong> 하는 워크로드 리소스</li>
<li>작업이 끝나면 Pod는 Completed 상태로 유지 (자동 삭제 X)</li>
</ul>
<br>

<h3 id="04-job-yaml-프로퍼티-상세">04. Job YAML 프로퍼티 상세</h3>
<pre><code class="language-yaml">apiVersion: batch/v1                #  Pod와 다름! batch/v1
kind: Job
metadata:
  name: db-migration
spec:
  template:                          # Pod 템플릿 (selector는 자동)
    spec:
      restartPolicy: OnFailure       #  Job은 Always 불가
      containers:
      - name: migration
        image: my-migration:1.0
        command: [&quot;./migrate&quot;, &quot;--up&quot;]</code></pre>
<br>

<h3 id="05-cronjob이란"><strong>05. CronJob이란</strong></h3>
<ul>
<li>CronJob은 <strong>주기적으로 Job을 자동 실행</strong> 하는 워크로드 리소스</li>
<li>Linux의 cron과 동일한 문법을 사용</li>
</ul>
<br>

<h3 id="06-cronjob-yaml-프로퍼티">06. CronJob YAML 프로퍼티</h3>
<pre><code class="language-yaml">apiVersion: batch/v1
kind: CronJob
metadata:
  name: daily-backup
spec:
  schedule: &quot;0 2 * * *&quot;            #  cron 표현식
  timeZone: &quot;Asia/Seoul&quot;            # v1.27+ Stable
  jobTemplate:                      # 실행할 Job 정의
    spec:
      template:
        spec:
          restartPolicy: OnFailure
          containers:
          - name: backup
            image: my-backup:1.0
            command: [&quot;/backup.sh&quot;]</code></pre>
<hr>
<h3 id="configmap"><strong>ConfigMap</strong></h3>
<h3 id="01-왜-configmap이-필요한가"><strong>01. 왜 ConfigMap이 필요한가</strong></h3>
<ul>
<li>ConfigMap은 <strong>설정 데이터를 이미지에서 분리</strong>하여 Kubernetes 오브젝트로 관리하는 방법</li>
</ul>
<pre><code>ConfigMap                        Pod
┌───────────────────────────┐    ┌──────────────────────┐
│ APP_ENV: development    │───&gt;│ 컨테이너             │
│ DB_HOST: mysql-svc      │    │ (설정값이 주입됨)     │
│ LOG_LEVEL: DEBUG        │    └──────────────────────┘
└───────────────────────────┘</code></pre><br>

<h3 id="02-configmap-개념"><strong>02. ConfigMap 개념</strong></h3>
<ul>
<li><strong>key-value 형태로 설정 데이터를 저장</strong>하는 오브젝트</li>
<li>텍스트 데이터(문자열, 설정 파일 내용 등) 저장 가능</li>
<li>Pod에서 환경변수 또는 파일(볼륨) 형태로 주입 가능</li>
<li>데이터는 <strong>암호화되지 않음</strong> → 민감 정보 저장 금지</li>
</ul>
<br>

<h3 id="03-configmap-생성-방법">03. ConfigMap 생성 방법</h3>
<table>
<thead>
<tr>
<th>방법</th>
<th>명령어 옵션</th>
<th>특징</th>
</tr>
</thead>
<tbody><tr>
<td>리터럴 직접 입력</td>
<td><code>--from-literal</code></td>
<td>간단한 key-value에 적합</td>
</tr>
<tr>
<td>파일로부터 생성</td>
<td><code>--from-file</code></td>
<td>설정 파일 통째로 저장</td>
</tr>
<tr>
<td>YAML 파일 작성</td>
<td><code>kubectl apply -f</code></td>
<td>GitOps·버전관리에 적합, 현업 표준</td>
</tr>
</tbody></table>
<br>

<h4 id="방법-3--yaml-파일로-생성-권장">방법 3 — YAML 파일로 생성 (권장)</h4>
<pre><code class="language-yaml"># configmap-app.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: default
data:
  APP_ENV: &quot;development&quot;
  DB_HOST: &quot;mysql-svc&quot;
  DB_PORT: &quot;3306&quot;
  LOG_LEVEL: &quot;DEBUG&quot;</code></pre>
<br>

<h3 id="04-pod에서-configmap-사용하는-방법">04. Pod에서 ConfigMap 사용하는 방법</h3>
<pre><code>ConfigMap
┌──────────────────┐
│ APP_ENV=dev     │
│ DB_HOST=mysql   │
│ LOG_LEVEL=DEBUG │
└────────┬─────────┘
        │
    ┌────┴──────────────────────┐─────────────────────────┐
    ▼                         ▼                       ▼
방법 1: envFrom       방법 2: env.valueFrom    방법 3: 볼륨 마운트
(전체 key → 환경변수)  (특정 key → 환경변수)      (key → 파일)</code></pre><br>

<h4 id="3가지-방법-비교">3가지 방법 비교</h4>
<table>
<thead>
<tr>
<th>방법</th>
<th>구문</th>
<th>장점</th>
<th>단점</th>
<th>주요 사용 상황</th>
</tr>
</thead>
<tbody><tr>
<td><code>envFrom</code></td>
<td><code>configMapRef</code></td>
<td>한 줄로 전체 주입</td>
<td>key 이름 변경 불가</td>
<td>설정이 많을 때</td>
</tr>
<tr>
<td><code>env.valueFrom</code></td>
<td><code>configMapKeyRef</code></td>
<td>key 선택·이름 변경 가능</td>
<td>key마다 작성 필요</td>
<td>특정 값만 필요할 때</td>
</tr>
<tr>
<td>볼륨 마운트</td>
<td><code>volumes.configMap</code></td>
<td>파일 제공, 실시간 업데이트</td>
<td>환경변수로 직접 못 씀</td>
<td>nginx.conf, app.yml 등</td>
</tr>
</tbody></table>
<br>

<h3 id="secret"><strong>Secret</strong></h3>
<h3 id="01-왜-secret이-필요한가"><strong>01. 왜 Secret이 필요한가</strong></h3>
<ul>
<li>ConfigMap은 데이터가 평문으로 저장되어 민감 정보 저장에 부적합</li>
<li>Secret은 ConfigMap과 구조가 거의 동일하지만 민감 정보 처리에 특화된 오브젝트</li>
</ul>
<br>

<h3 id="02-secret-개념"><strong>02. Secret 개념</strong></h3>
<ul>
<li>민감 데이터를 <strong>Base64 인코딩</strong>하여 저장하는 오브젝트</li>
<li>ConfigMap과 사용 방법이 거의 동일 (envFrom, env.valueFrom, 볼륨 마운트)</li>
<li>Pod에서 참조할 때 자동으로 <strong>Base64 디코딩</strong>되어 원본값으로 주입됨</li>
<li>볼륨으로 마운트 시 <strong>tmpfs(메모리 기반)</strong> 에 저장 → 디스크에 기록되지 않음</li>
</ul>
<br>

<h3 id="03-secret-생성-방법">03. Secret 생성 방법</h3>
<ul>
<li><strong>방법 1 — kubectl 명령어 (값 자동 인코딩)</strong><ul>
<li>kubectl create secret 명령어는 Base64 인코딩을 자동으로 처리</li>
</ul>
</li>
<li><strong>방법 2 — YAML + data 필드 (Base64 직접 입력)</strong></li>
<li><strong>방법 3 — YAML + stringData 필드 (평문 입력)</strong><ul>
<li>apply 시 자동으로 data 필드(Base64)로 변환·저장</li>
</ul>
</li>
</ul>
<br>

<h3 id="04-pod에서-secret-사용하는-방법"><strong>04. Pod에서 Secret 사용하는 방법</strong></h3>
<ul>
<li>ConfigMap과 동일한 구조</li>
<li>configMapRef → secretRef, configMapKeyRef → secretKeyRef 로 변경</li>
</ul>
<br>

<h4 id="configmap-vs-secret-비교">ConfigMap vs Secret 비교</h4>
<table>
<thead>
<tr>
<th>항목</th>
<th>ConfigMap</th>
<th>Secret</th>
</tr>
</thead>
<tbody><tr>
<td>목적</td>
<td>일반 설정 데이터</td>
<td>민감 정보</td>
</tr>
<tr>
<td>저장 형식</td>
<td>평문</td>
<td>Base64 인코딩</td>
</tr>
<tr>
<td><code>kubectl describe</code></td>
<td>값 노출</td>
<td>값 숨김 (크기만 표시)</td>
</tr>
<tr>
<td>볼륨 저장 위치</td>
<td>노드 디스크</td>
<td>tmpfs (메모리)</td>
</tr>
<tr>
<td>볼륨 기본 권한</td>
<td>readOnly 선택</td>
<td>readOnly + 0400 권장</td>
</tr>
<tr>
<td>명령어 키워드</td>
<td><code>configmap</code>, <code>configMapRef</code></td>
<td><code>secret</code>, <code>secretRef</code></td>
</tr>
<tr>
<td>YAML 값 입력</td>
<td>평문</td>
<td>Base64 (또는 stringData)</td>
</tr>
</tbody></table>
<br>

<h3 id="볼륨-기초--emptydir-·-projected-·-configmap-볼륨"><strong>볼륨 기초 — emptyDir · projected · configMap 볼륨</strong></h3>
<h3 id="01-왜-볼륨이-필요한가"><strong>01. 왜 볼륨이 필요한가</strong></h3>
<pre><code>볼륨 없음
컨테이너 실행 → /data/log.txt 생성
       │
       ▼ 컨테이너 재시작
/data/log.txt 없어짐 (데이터 소실)

볼륨 있음
컨테이너 실행 → /data/log.txt 생성 (볼륨에 저장)
       │
       ▼ 컨테이너 재시작
/data/log.txt 유지 (볼륨에서 읽어옴)</code></pre><br>

<h3 id="02-emptydir--임시-공유-볼륨"><strong>02. emptyDir — 임시 공유 볼륨</strong></h3>
<ul>
<li>Pod가 노드에 할당될 때 빈 디렉토리로 생성되는 볼륨</li>
<li>같은 Pod 안의 여러 컨테이너가 공유 가능. Pod 삭제 시 데이터도 삭제</li>
</ul>
<pre><code class="language-yaml"># pod-emptydir.yaml
apiVersion: v1
kind: Pod
metadata:
  name: emptydir-pod
  namespace: lab-volume
spec:
  containers:
  - name: writer
    image: busybox:1.36
    command:
    - sh
    - -c
    - |
      while true; do
        echo &quot;$(date) - Hello from writer&quot; &gt;&gt; /shared/log.txt
        sleep 5
      done
    volumeMounts:
    - name: shared-data
      mountPath: /shared              # 쓰기 경로

  - name: reader
    image: busybox:1.36
    command: [&quot;sh&quot;, &quot;-c&quot;, &quot;tail -f /data/log.txt&quot;]
    volumeMounts:
    - name: shared-data
      mountPath: /data                # 같은 볼륨, 다른 경로로 마운트

  volumes:
  - name: shared-data
    emptyDir: {}                      # 기본 emptyDir</code></pre>
<br>

<h3 id="03-configmap--secret-볼륨">03. configMap / secret 볼륨</h3>
<pre><code class="language-yaml">volumes:
- name: config-vol
  configMap:
    name: app-config
    defaultMode: 0644         # 파일 권한

- name: secret-vol
  secret:
    secretName: app-secret
    defaultMode: 0400         # Secret은 0400 권장 (owner 읽기만 가능)</code></pre>
<br>

<h3 id="04-projected-볼륨--여러-소스를-하나의-경로로"><strong>04. projected 볼륨 — 여러 소스를 하나의 경로로</strong></h3>
<ul>
<li>ConfigMap, Secret, Downward API, ServiceAccountToken을 <strong>하나의 디렉토리</strong>에 합쳐서 마운트</li>
<li>별개의 volumeMounts를 여러 개 쓰는 대신 하나로 통합 가능</li>
</ul>
<pre><code class="language-yaml">volumes:
- name: all-in-one
  projected:
    sources:
    - configMap:
        name: app-config
        items:
        - key: APP_ENV
          path: app-env           # /etc/app/app-env 파일 생성
    - secret:
        name: app-secret
        items:
        - key: DB_PASSWORD
          path: db-password       # /etc/app/db-password 파일 생성
        mode: 0400                # Secret 파일만 0400 적용
    - downwardAPI:
        items:
        - path: pod-name
          fieldRef:
            fieldPath: metadata.name</code></pre>
<br>

<h3 id="persistentvolume--pvc--영구-데이터-저장"><strong>PersistentVolume / PVC — 영구 데이터 저장</strong></h3>
<h3 id="01-영구-볼륨이-필요한-이유"><strong>01. 영구 볼륨이 필요한 이유</strong></h3>
<ul>
<li>emptyDir은 Pod 삭제 시 데이터가 사라짐</li>
<li>DB나 파일 업로드 서버처럼 Pod가 교체되어도 데이터가 남아야 하는 경우 PersistentVolume을 사용</li>
</ul>
<pre><code>emptyDir (임시)
Pod 삭제 → 데이터 소실

PersistentVolume (영구)
Pod 삭제 → 외부 스토리지에 데이터 보존
새 Pod 생성 → 동일 데이터 다시 마운트</code></pre><br>

<h4 id="pv--pvc--storageclass-역할">PV / PVC / StorageClass 역할</h4>
<pre><code>관리자      PersistentVolume (PV)      실제 스토리지 자원 준비
개발자      PersistentVolumeClaim(PVC) 스토리지 요청서 작성
Kubernetes  조건이 맞는 PV와 PVC를 자동 바인딩
Pod         PVC를 참조하여 스토리지 사용</code></pre><br>

<h3 id="02-접근-모드-accessmode">02. 접근 모드 (AccessMode)</h3>
<p>볼륨이 어떻게 마운트될 수 있는지 정의</p>
<table>
<thead>
<tr>
<th>모드</th>
<th>약자</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td><code>ReadWriteOnce</code></td>
<td>RWO</td>
<td>하나의 노드에서 읽기/쓰기</td>
</tr>
<tr>
<td><code>ReadOnlyMany</code></td>
<td>ROX</td>
<td>여러 노드에서 읽기 전용</td>
</tr>
<tr>
<td><code>ReadWriteMany</code></td>
<td>RWX</td>
<td>여러 노드에서 읽기/쓰기</td>
</tr>
<tr>
<td><code>ReadWriteOncePod</code></td>
<td>RWOP</td>
<td>하나의 Pod에서만 읽기/쓰기 (1.29 GA)</td>
</tr>
</tbody></table>
<br>

<h3 id="03-반환-정책-reclaim-policy">03. 반환 정책 (Reclaim Policy)</h3>
<p>PVC가 삭제된 후 PV를 어떻게 처리할지 결정</p>
<table>
<thead>
<tr>
<th>정책</th>
<th>동작</th>
<th>사용 상황</th>
</tr>
</thead>
<tbody><tr>
<td><code>Retain</code></td>
<td>PV와 데이터 유지 (수동 정리 필요)</td>
<td>운영 DB 데이터 보호</td>
</tr>
<tr>
<td><code>Delete</code></td>
<td>PV와 외부 스토리지 자동 삭제</td>
<td>동적 프로비저닝 기본값</td>
</tr>
</tbody></table>
<br>

<h3 id="04-pv--pvc-생성-및-사용"><strong>04. PV / PVC 생성 및 사용</strong></h3>
<p><strong>정적 프로비저닝 (수동 PV 생성)</strong></p>
<pre><code class="language-yaml"># pv-hostpath.yaml (실습용 — 실제 운영에서는 EBS, NFS 등 사용)
apiVersion: v1
kind: PersistentVolume
metadata:
  name: mysql-pv
spec:
  capacity:
    storage: 5Gi
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  hostPath:
    path: /mnt/mysql-data
    type: DirectoryOrCreate         # 없으면 디렉토리 자동 생성</code></pre>
<pre><code class="language-yaml"># pvc-mysql.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-pvc
  namespace: lab-pvc
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 3Gi
  storageClassName: &quot;&quot;              # 빈 문자열: 정적 PV 바인딩 강제</code></pre>
<pre><code class="language-yaml"># Pod에서 PVC 사용
spec:
  volumes:
  - name: mysql-storage
    persistentVolumeClaim:
      claimName: mysql-pvc          # PVC 이름 참조
  containers:
  - name: mysql
    volumeMounts:
    - name: mysql-storage
      mountPath: /var/lib/mysql</code></pre>
<br>

<h3 id="storageclass--동적-프로비저닝"><strong>StorageClass — 동적 프로비저닝</strong></h3>
<h3 id="01-storageclass가-왜-필요한가"><strong>01. StorageClass가 왜 필요한가</strong></h3>
<ul>
<li>정적 프로비저닝에서는 관리자가 PV를 미리 만들어야 함</li>
<li>StorageClass를 사용하면 PVC 생성 시 PV가 자동으로 만들어짐 — <strong>동적 프로비저닝</strong></li>
</ul>
<pre><code>정적 프로비저닝의 문제

관리자가 PV를 미리 생성 → 용량이 딱 맞지 않아도 PV 하나 전체 배정 (낭비)
개발자가 PVC 요청할 때마다 관리자가 대응해야 함

---
동적 프로비저닝 흐름

개발자: PVC 생성 (storageClassName: gp3)
              │
              ▼
StorageClass(gp3) → 프로비저너에게 PV 생성 요청
              │
              ▼
CSI 드라이버 → 외부 스토리지(EBS 등)에 볼륨 자동 생성
              │
              ▼
PV 자동 생성 + PVC와 자동 바인딩</code></pre><br>

<h3 id="02-storageclass-주요-설정"><strong>02. StorageClass 주요 설정</strong></h3>
<pre><code class="language-yaml">apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: gp3
  annotations:
    storageclass.kubernetes.io/is-default-class: &quot;true&quot;   # 기본 SC 지정
provisioner: ebs.csi.aws.com          # 사용할 CSI 드라이버
reclaimPolicy: Delete                 # Delete(기본) / Retain
allowVolumeExpansion: true            # PVC 확장 허용
volumeBindingMode: WaitForFirstConsumer  # Pod 스케줄링 후 PV 생성 (권장)
parameters:
  type: gp3
  iops: &quot;3000&quot;
  throughput: &quot;125&quot;
  encrypted: &quot;true&quot;</code></pre>
<br>

<h4 id="주요-필드">주요 필드</h4>
<table>
<thead>
<tr>
<th>프로퍼티</th>
<th>의미</th>
<th>기본값</th>
</tr>
</thead>
<tbody><tr>
<td><code>provisioner</code></td>
<td>PV를 생성할 CSI 드라이버</td>
<td>— (필수)</td>
</tr>
<tr>
<td><code>reclaimPolicy</code></td>
<td>PVC 삭제 후 PV 처리 방법</td>
<td><code>Delete</code></td>
</tr>
<tr>
<td><code>allowVolumeExpansion</code></td>
<td>PVC 용량 확장 허용</td>
<td><code>false</code></td>
</tr>
<tr>
<td><code>volumeBindingMode</code></td>
<td>PV 바인딩 시점</td>
<td><code>Immediate</code></td>
</tr>
<tr>
<td><code>parameters</code></td>
<td>드라이버별 추가 설정</td>
<td>—</td>
</tr>
</tbody></table>
<br>

<h3 id="종합-실습">종합 실습</h3>
<h4 id="mysql-statefulset--spring-boot-통합-배포">MySQL StatefulSet + Spring Boot 통합 배포</h4>
<br>

<h4 id="step-1-configmap-생성">Step 1. ConfigMap 생성</h4>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/a6f12f0d-67de-4622-98e9-ceb9e224c54b/image.png" alt=""></p>
<br>

<h4 id="step-2-secret-생성">Step 2. Secret 생성</h4>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/28f2709f-77a0-45b5-be44-0b2d850d37af/image.png" alt=""></p>
<br>

<h4 id="step-3-mysql-headless-service--statefulset">Step 3. MySQL Headless Service + StatefulSet</h4>
<pre><code class="language-yaml">apiVersion: v1
kind: Service
metadata:
  name: mysql
  namespace: lab-final
spec:
  clusterIP: None           # Headless Service 선언 값
  selector:
    app: mysql
  ports:
  - port: 3306
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
  namespace: lab-final
spec:
  serviceName: mysql         # 위에서 생성한 Service 이름
  replicas: 1
  selector:
    matchLabels:
      app: mysql
  persistentVolumeClaimRetentionPolicy:
    whenDeleted: Delete       # StatefulSet 삭제 시 PVC도 함께 삭제
    whenScaled: Retain        # 스케일 다운 시 PVC 유지
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: mysql
        image: mysql:8.0
        env:
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:                        # Secret에서 값 참조하는 방식
              name: app-secret
              key: DB_ROOT_PASSWORD
        - name: MYSQL_DATABASE
          valueFrom:
            configMapKeyRef:                        # ConfigMap에서 값 참조하는 방식
              name: app-config
              key: DB_NAME
        volumeMounts:
        - name: mysql-data
          mountPath: /var/lib/mysql
        resources:
          requests:
            memory: &quot;256Mi&quot;
            cpu: &quot;250m&quot;
          limits:
            memory: &quot;512Mi&quot;
            cpu: &quot;500m&quot;
        readinessProbe:
          exec:
            command: [&quot;mysqladmin&quot;, &quot;ping&quot;, &quot;-h&quot;, &quot;localhost&quot;]
          initialDelaySeconds: 30
          periodSeconds: 10
  volumeClaimTemplates:                                  # Pod별 PVC를 자동 생성하는 StatefulSet 전용 필드
  - metadata:
      name: mysql-data
    spec:
      accessModes: [&quot;ReadWriteOnce&quot;]
      resources:
        requests:
          storage: 2Gi</code></pre>
<br>

<h4 id="step-4-초기-데이터-생성">Step 4. 초기 데이터 생성</h4>
<pre><code># 방법 1 : 직접 들어가서 생성
kubectl exec -it mysql-0 -n lab-final — bash
    mysql -uroot -prootpass123 shopdb
    USE shopdb;</code></pre><p><img src="https://velog.velcdn.com/images/its-jihyeon/post/b61e1962-7d09-4b78-90ed-400be8f05bbd/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/85755703-dc9d-4789-acea-708c8750c8af/image.png" alt=""></p>
<br>

<pre><code># 방법 2
kubectl exec mysql-0 -n lab-final -- \
mysql -uroot -prootpass123 shopdb -e \
&quot;CREATE TABLE IF NOT EXISTS products (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    price INT NOT NULL
);
INSERT INTO products (name, price) VALUES (&#39;Apple&#39;, 1500), (&#39;Banana&#39;, 800);
SELECT * FROM products;&quot;</code></pre><br>

<h4 id="step-5-spring-boot-deployment--service">Step 5. Spring Boot Deployment + Service</h4>
<pre><code class="language-yaml">apiVersion: v1
kind: Service
metadata:
  name: spring-app-svc
  namespace: lab-final
spec:
  type: NodePort                # 외부에서 노드 IP로 접근 가능한 타입
  selector:
    app: spring-app
  ports:
  - port: 80
    targetPort: 80
    nodePort: 30080
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: spring-app
  namespace: lab-final
  annotations:
    kubernetes.io/change-cause: &quot;초기 배포&quot;
spec:
  replicas: 2
  selector:
    matchLabels:
      app: spring-app
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0               # 배포 중 사용 불가 Pod 수 (무중단 보장)
  template:
    metadata:
      labels:
        app: spring-app
    spec:
      initContainers:
      - name: wait-for-mysql
        image: busybox:1.36
        command:
        - sh
        - -c
        - |
          until nc -z mysql 3306; do
            echo &quot;MySQL 준비 대기 중...&quot;
            sleep 3
          done

      containers:
      - name: spring-app
        image: nginx:1.25-alpine
        ports:
        - containerPort: 80
        envFrom:                            # ConfigMap 전체를 환경변수로 주입
        - configMapRef:
            name: app-config
        - secretRef:
            name: app-secret
        env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name        # Downward API 필드 경로 지정 키
        - name: POD_IP
          valueFrom:
            fieldRef:
              fieldPath: status.podIP
        volumeMounts:
        - name: log-vol
          mountPath: /logs
        readinessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 5
          periodSeconds: 5
        livenessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 10
          periodSeconds: 10
          failureThreshold: 3
        resources:
          requests:
            cpu: &quot;100m&quot;
            memory: &quot;128Mi&quot;
          limits:
            cpu: &quot;300m&quot;
            memory: &quot;256Mi&quot;
        lifecycle:
          preStop:
            exec:
              command: [&quot;sleep&quot;, &quot;5&quot;]

      - name: log-sidecar
        image: busybox:1.36
        restartPolicy: Always                     # 네이티브 사이드카 선언 필드
        command: [&quot;sh&quot;, &quot;-c&quot;, &quot;tail -f /logs/app.log 2&gt;/dev/null || sleep 3600&quot;]
        volumeMounts:
        - name: log-vol
          mountPath: /logs

      volumes:
      - name: log-vol
        emptyDir: {}                         # Pod 수명과 동일한 임시 공유 볼륨 타입

      terminationGracePeriodSeconds: 30</code></pre>
<br>

<h4 id="step-6-전체-리소스-상태-확인"><strong>Step 6. 전체 리소스 상태 확인</strong></h4>
<p>조회 대상: Deployment, StatefulSet, Pod, Service, PVC, ConfigMap, Secret</p>
<br>

<h4 id="step-7-환경변수-주입-검증"><strong>Step 7. 환경변수 주입 검증</strong></h4>
<p>spring-app Pod 이름을 변수에 저장</p>
<pre><code class="language-bash">SPRING_POD=$(kubectl get pod -n lab-final -l app=spring-app -o jsonpath=&#39;{.items[0].metadata.name}&#39;)</code></pre>
<br>

<h4 id="step-8-mysql-데이터-영속성-검증">Step 8. MySQL 데이터 영속성 검증</h4>
<pre><code class="language-bash"># 1. 삭제 전 데이터 조회
kubectl exec mysql-0 -n lab-final -- \
&gt; mysql -uroot -prootpass123 shopdb -e &quot;SELECT * FROM products;&quot;

# 2. Pod 강제 삭제
kubectl delete pod mysql-0 -n lab-final

# 3. 재생성 대기
kubectl get pod -n lab-final

# 4. 재생성 후 데이터 조회 (영속성 검증)
kubectl exec mysql-0 -n lab-final -- \
&gt; mysql -uroot -prootpass123 shopdb -e &quot;SELECT * FROM products;&quot;</code></pre>
<br>

<h4 id="step-09-롤링업데이트--롤백--완전-작성형">Step 09. 롤링업데이트 + 롤백 — 완전 작성형</h4>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/cdcac011-7abb-4e7f-956c-a29a79ab667d/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[멋쟁이사자처럼부트캠프 클라우드 엔지니어링 5기 [14주차]]]></title>
            <link>https://velog.io/@its-jihyeon/%EB%A9%8B%EC%9F%81%EC%9D%B4%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A7%81-5%EA%B8%B0-14%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@its-jihyeon/%EB%A9%8B%EC%9F%81%EC%9D%B4%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A7%81-5%EA%B8%B0-14%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Sun, 03 May 2026 03:15:19 GMT</pubDate>
            <description><![CDATA[<h3 id="01-devops란"><strong>01. DevOps란</strong></h3>
<h3 id="개념"><strong>개념</strong></h3>
<ul>
<li>소프트웨어 개발(Development)과 운영(Operations)의 합성어</li>
<li>두 조직 사이의 협업·자동화·지속적 개선을 통해 소프트웨어를 빠르고 안정적으로 제공하는
문화·철학·방법론의 집합</li>
</ul>
<pre><code>[전통적 개발 방식 — 사일로(Silo) 구조]

  개발팀(Dev)           운영팀(Ops)
  ┌──────────┐          ┌──────────┐
  │ 코드 작성 │ ──────►  │ 배포·운영 │
  │ 기능 개발 │         │ 장애 대응 │
  └──────────┘          └──────────┘
       ↑                     │
       └─────────────────────┘
           장애 발생 시 책임 전가

---
[DevOps 구조 — 단일 팀이 전체 수명주기 담당]

  ┌──────────────────────────────────────────┐
  │              DevOps 팀                │
  │  계획 → 코드 → 빌드 → 테스트 → 배포 →     │
  │  운영 → 모니터링 → (피드백) → 계획 →      │
  └──────────────────────────────────────────┘
              무한 루프(Infinite Loop)</code></pre><br>

<h3 id="02-devops-루프--8단계-무한-사이클"><strong>02. DevOps 루프 — 8단계 무한 사이클</strong></h3>
<h3 id="개념-1"><strong>개념</strong></h3>
<ul>
<li>단방향 프로세스가 아니라 <strong>무한 루프(Infinite Loop)</strong> 구조</li>
</ul>
<pre><code>        [DevOps 8단계 무한 루프]

             ┌──────────┐
        ┌───►│  PLAN    │◄───────────────────────────┐
        │    │  계획    │ 백로그, 이슈 트래킹         │
        │    └────┬─────┘ (Jira, GitHub Issues)    │
        │         ▼                               │
        │    ┌──────────┐                          │
        │    │  CODE   │ 버전 관리, 코드 리뷰        │
        │    │  코드    │ (Git, GitHub, GitLab)    │
        │    └────┬─────┘                          │
        │         ▼                               │
        │    ┌──────────┐                          │
        │    │  BUILD  │ 컴파일, 의존성 해결         │
        │    │  빌드    │ (Maven, Gradle, Docker)  │
        │    └────┬─────┘                          │
        │         ▼                               │
        │    ┌──────────┐                          │
        │    │  TEST   │ 단위·통합·보안 테스트       │
        │    │  테스트  │ (JUnit, Selenium, Trivy) │
        │    └────┬─────┘                          │
        │         ▼                               │
        │    ┌──────────┐                          │
        │    │ RELEASE │ 아티팩트 생성·버전 태깅     │
        │    │  릴리즈  │ (Docker Image, ECR)      │
        │    └────┬─────┘                          │
        │         ▼                               │
        │    ┌──────────┐                          │
        │    │  DEPLOY │ 환경에 배포                │
        │    │  배포    │ (ArgoCD, ECS, EKS)       │
        │    └────┬─────┘                          │
        │         ▼                               │
        │    ┌──────────┐                          │
        │    │ OPERATE │ 인프라 운영·스케일링        │
        │    │  운영    │ (Kubernetes, ASG)        │
        │    └────┬─────┘                          │
        │         ▼                               │
        │    ┌──────────┐                          │
        └────│ MONITOR │ 로그·메트릭·알람            │
             │ 모니터링 │ (Prometheus, Grafana, CloudWatch)
             └──────────┘
                  │ 피드백 → 다음 PLAN에 반영</code></pre><br>

<h3 id="03-devops-아키텍처-구조"><strong>03. DevOps 아키텍처 구조</strong></h3>
<h3 id="상황"><strong>상황</strong></h3>
<ul>
<li>실제 한국 핀테크 기업 수준의 DevOps 아키텍처를 구조적으로 이해</li>
</ul>
<pre><code>[핀테크 서비스 DevOps 아키텍처 — 전체 흐름]

  개발자 PC
  ┌──────────────────┐
  │  IntelliJ IDEA  │
  │ Spring Boot 3.x │
  │ Git commit/push │
  └────────┬─────────┘
           │ git push (feature 브랜치)
           ▼
  ┌──────────────────────────────────────────────────┐
  │                   GitHub                     │
  │  ┌─────────────────────────────────────────────┐ │
  │  │Pull Request → Code Review → Merge to main│ │
  │  └──────────────────────┬──────────────────────┘ │
  └─────────────────────────┼────────────────────────┘
                            │ Push event (webhook)
                            ▼
  ┌──────────────────────────────────────────────────┐
  │           GitHub Actions (CI Pipeline)       │
  │                                              │
  │  [1] 단위 테스트 (JUnit 5)                     │
  │  [2] 코드 품질 검사 (SonarQube)                 │
  │  [3] 보안 취약점 스캔 (Trivy)                   │
  │  [4] Docker 이미지 빌드                        │
  │  [5] ECR(Elastic Container Registry)에 Push   │
  │  [6] GitOps 저장소 image tag 업데이트           │
  └───────────────────────────────────────────────────┘
                            │ GitOps repo 변경 감지
                            ▼
  ┌──────────────────────────────────────────────────┐
  │              ArgoCD (CD — GitOps)            │
  │                                              │
  │  GitOps Repo 변경 감지 → K8s 클러스터 동기화     │
  │  Staging 자동 배포 → Production 수동 승인       │
  └──────────────────────────────────────────────────┘
                            │
                            ▼
  ┌──────────────────────────────────────────────────┐
  │              AWS EKS (Production)            │
  │                                              │
  │  ap-northeast-2 (서울 리전)                   │
  │  AZ-a: Pod 그룹 A     AZ-c: Pod 그룹 B        │
  │                                              │
  │  ALB → Service → Deployment(Pods)            │
  │  RDS MySQL 8.x (Multi-AZ)                    │
  │  ElastiCache Redis (Multi-AZ)                │
  └──────────────────────────────────────────────────┘
                            │
                            ▼
  ┌──────────────────────────────────────────────────┐
  │          모니터링 스택 (Observability)         │
  │                                              │
  │  Prometheus → 메트릭 수집                      │
  │  Grafana    → 대시보드 시각화                  │
  │  Loki       → 로그 수집·조회                   │
  └──────────────────────────────────────────────────┘</code></pre><br>

<h3 id="github-actions"><strong>GitHub Actions</strong></h3>
<h3 id="01-ci--cd-개념"><strong>01. CI / CD 개념</strong></h3>
<h3 id="개념-2"><strong>개념</strong></h3>
<ul>
<li>CI/CD는 코드 변경부터 배포까지 전 과정을 자동화하는 방법론</li>
</ul>
<br>

<p><strong>CI (Continuous Integration, 지속적 통합)</strong></p>
<ul>
<li>다수의 개발자가 협업할 때 발생하는 충돌을 조기에 탐지하고, 코드 변경 사항을 자동으로 빌드·테스트하여 통합하는 프로세스</li>
</ul>
<p><strong>CD (Continuous Deployment / Delivery, 지속적 배포)</strong></p>
<ul>
<li>CI를 통과한 코드를 자동으로 스테이징 또는 프로덕션 환경에 배포하는 프로세스</li>
</ul>
<pre><code>[CI/CD 전체 흐름]

  개발자 코드 작성
       |
       v
  git push / PR
       |
       v
  ┌────────────────────────────────┐
  │           CI 단계            │
  │  빌드 → 단위테스트 → 통합테스트 │
  └────────────────────────────────┘
       |
       v
  ┌─────────────────────────────────┐
  │           CD 단계             │
  │ 스테이징 배포 → 검증 → 운영 배포 │
  └─────────────────────────────────┘
       |
       v
   서비스 운영 중</code></pre><br>

<h3 id="02-github-actions-개요"><strong>02. GitHub Actions 개요</strong></h3>
<h3 id="개념-3"><strong>개념</strong></h3>
<ul>
<li>GitHub Actions는 GitHub 리포지토리 이벤트(push, PR 등)를 트리거로 워크플로우를 자동화하는 플랫폼</li>
<li>YAML 파일로 워크플로우를 정의하며, 반드시 .github/workflows/ 디렉토리에 저장</li>
</ul>
<br>

<h4 id="구성-요소">구성 요소</h4>
<table>
<thead>
<tr>
<th>구성 요소</th>
<th>역할</th>
<th>비고</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Workflow</strong></td>
<td>자동화 전체 절차</td>
<td><code>.github/workflows/*.yml</code></td>
</tr>
<tr>
<td><strong>Event</strong></td>
<td>Workflow 실행 트리거</td>
<td>push, pull_request, schedule 등</td>
</tr>
<tr>
<td><strong>Job</strong></td>
<td>Runner에서 실행되는 Step의 집합</td>
<td>기본 병렬, needs로 순서 제어</td>
</tr>
<tr>
<td><strong>Step</strong></td>
<td>Job 내부의 개별 실행 단위</td>
<td>run(shell) 또는 uses(action)</td>
</tr>
<tr>
<td><strong>Action</strong></td>
<td>재사용 가능한 독립 명령어</td>
<td>GitHub Marketplace</td>
</tr>
<tr>
<td><strong>Runner</strong></td>
<td>Job이 실행되는 서버 환경</td>
<td>GitHub-hosted / Self-hosted</td>
</tr>
</tbody></table>
<br>

<h3 id="03-워크플로우-파일-기본-구조"><strong>03. 워크플로우 파일 기본 구조</strong></h3>
<h3 id="개념-4"><strong>개념</strong></h3>
<ul>
<li>워크플로우 파일은 YAML 형식이며 다음 키워드들로 구성</li>
</ul>
<pre><code class="language-yaml">[워크플로우 파일 구조]

  name:        ← Workflow 이름 (Actions 탭에 표시)
  run-name:    ← 실행 이름 (실행 목록에 표시, 표현식 사용 가능)
  on:          ← 트리거 이벤트 정의
    ...
  jobs:        ← Job 목록
    job-이름:
      runs-on: ← Runner 환경
      steps:   ← Step 목록
        - name: Step 이름
          run:  ← shell 명령어
          uses: ← 외부 Action 참조</code></pre>
<br>

<h3 id="github-actions-yaml-문법"><strong>GitHub Actions YAML 문법</strong></h3>
<h3 id="01-워크플로우-최상위-키워드"><strong>01. 워크플로우 최상위 키워드</strong></h3>
<table>
<thead>
<tr>
<th>키워드</th>
<th>필수</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>name</code></td>
<td>선택</td>
<td>Actions 탭에 표시되는 워크플로우 이름</td>
</tr>
<tr>
<td><code>run-name</code></td>
<td>선택</td>
<td>실행 목록에 표시되는 이름, 표현식 사용 가능</td>
</tr>
<tr>
<td><code>on</code></td>
<td><strong>필수</strong></td>
<td>워크플로우를 실행할 이벤트 정의</td>
</tr>
<tr>
<td><code>env</code></td>
<td>선택</td>
<td>모든 Job/Step에서 사용할 전역 환경변수</td>
</tr>
<tr>
<td><code>defaults</code></td>
<td>선택</td>
<td>Job/Step의 기본값 설정</td>
</tr>
<tr>
<td><code>jobs</code></td>
<td><strong>필수</strong></td>
<td>실행할 Job 목록 정의</td>
</tr>
</tbody></table>
<br>

<ul>
<li><strong>on — 트리거 이벤트 :</strong> 워크플로우를 실행할 이벤트를 정의</li>
<li><strong>jobs — Job 정의 :</strong> jobs 키워드 하위에 실행할 Job을 정의</li>
<li><strong>steps — Step 정의 :</strong> Job 내부에서 순서대로 실행되는 작업 단위 , 각 Step은 run(shell) 또는 uses(외부 Action) 중 하나를 반드시 포함</li>
</ul>
<br>

<h3 id="02-env--환경변수"><strong>02. env — 환경변수</strong></h3>
<ul>
<li>환경변수는 워크플로우 레벨, Job 레벨, Step 레벨 세 곳에서 정의 가능하며, 하위 레벨이 상위 레벨을 덮어씀</li>
</ul>
<pre><code>[환경변수 우선순위]

  워크플로우 레벨 env (가장 낮음)
       |
       ▼ override
  Job 레벨 env
       |
       ▼ override
  Step 레벨 env (가장 높음)</code></pre><br>

<h3 id="03-uses--with--외부-action"><strong>03. uses &amp; with — 외부 Action</strong></h3>
<h3 id="개념-5"><strong>개념</strong></h3>
<ul>
<li>uses는 GitHub Marketplace의 Action을 참조</li>
<li>with는 Action에 입력값을 전달</li>
</ul>
<pre><code class="language-yaml"># actions/checkout — 리포지토리 코드를 Runner에 내려받음
- name: 소스코드 체크아웃
  uses: actions/checkout@v4
  with:
    ref: main               # 특정 브랜치/태그/SHA 체크아웃
    path: ./app             # 체크아웃 경로 (기본: 워크스페이스 루트)
    fetch-depth: 0          # 전체 히스토리 (기본 1, 얕은 복제)

# 다른 리포지토리 체크아웃
- name: 외부 리포지토리 체크아웃
  uses: actions/checkout@v4
  with:
    repository: actions/checkout
    path: ./other-repo</code></pre>
<br>

<h3 id="github-actions-context--expressions--variables--secrets"><strong>GitHub Actions Context &amp; Expressions &amp; Variables &amp; Secrets</strong></h3>
<h3 id="01-context"><strong>01. Context</strong></h3>
<ul>
<li>워크플로우 실행 시점에 GitHub Actions가 자동으로 주입하는 정보 객체</li>
</ul>
<br>

<h3 id="02-github-context"><strong>02. github Context</strong></h3>
<ul>
<li>github Context는 워크플로우 실행을 유발한 이벤트와 리포지토리 관련 정보를 담음</li>
</ul>
<table>
<thead>
<tr>
<th>속성</th>
<th>설명</th>
<th>예시 값</th>
</tr>
</thead>
<tbody><tr>
<td><code>github.actor</code></td>
<td>워크플로우 실행자 계정명</td>
<td><code>octocat</code></td>
</tr>
<tr>
<td><code>github.repository</code></td>
<td>리포지토리 전체 이름</td>
<td><code>owner/repo</code></td>
</tr>
<tr>
<td><code>github.ref</code></td>
<td>트리거된 브랜치/태그 전체 참조</td>
<td><code>refs/heads/main</code></td>
</tr>
<tr>
<td><code>github.ref_name</code></td>
<td>브랜치 또는 태그 이름만</td>
<td><code>main</code></td>
</tr>
<tr>
<td><code>github.sha</code></td>
<td>트리거된 커밋의 전체 SHA</td>
<td><code>a1b2c3d...</code></td>
</tr>
<tr>
<td><code>github.event_name</code></td>
<td>트리거 이벤트 이름</td>
<td><code>push</code>, <code>pull_request</code></td>
</tr>
<tr>
<td><code>github.run_id</code></td>
<td>워크플로우 실행 고유 ID</td>
<td><code>1658821493</code></td>
</tr>
<tr>
<td><code>github.run_number</code></td>
<td>워크플로우 실행 번호</td>
<td><code>42</code></td>
</tr>
<tr>
<td><code>github.workspace</code></td>
<td>체크아웃 작업 디렉토리</td>
<td><code>/home/runner/work/...</code></td>
</tr>
</tbody></table>
<pre><code class="language-yaml">steps:
  - name: github context 주요 값
    run: |
      echo &quot;actor: ${{ github.actor }}&quot;
      echo &quot;repository: ${{ github.repository }}&quot;
      echo &quot;ref_name: ${{ github.ref_name }}&quot;
      echo &quot;sha: ${{ github.sha }}&quot;
      echo &quot;event_name: ${{ github.event_name }}&quot;
      echo &quot;run_number: ${{ github.run_number }}&quot;

  - name: github context 전체 (JSON)
    env:
      GITHUB_CONTEXT: ${{ toJson(github) }}
    run: echo &quot;$GITHUB_CONTEXT&quot;</code></pre>
<br>

<h3 id="03-runner-context"><strong>03. runner Context</strong></h3>
<ul>
<li>runner Context는 현재 Job이 실행되는 Runner 환경 정보를 제공</li>
</ul>
<br>

<h3 id="04-steps-context--github_output"><strong>04. steps Context &amp; GITHUB_OUTPUT</strong></h3>
<ul>
<li>steps Context는 현재 Job 내의 이전 Step들의 실행 결과를 참조</li>
</ul>
<pre><code class="language-yaml">[Step 간 데이터 전달 흐름]

  Step 1 (id: generate)
    └─ run: echo &quot;result=Hello&quot; &gt;&gt; $GITHUB_OUTPUT
                                   │
                                   │ GITHUB_OUTPUT 파일에 기록
                                   ▼
  Step 2
    └─ run: echo &quot;${{ steps.generate.outputs.result }}&quot;
                                   ↑
                          steps.{id}.outputs.{키} 로 참조</code></pre>
<br>

<h3 id="05-vars--secrets-context"><strong>05. vars &amp; secrets Context</strong></h3>
<ul>
<li>vars는 일반 구성 변수를 참조 (로그에 평문 노출)</li>
<li>secrets는 암호화된 민감한 정보를 참조하며 로그에 ***로 마스킹</li>
</ul>
<pre><code class="language-yaml">jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - name: vars 출력 (평문)
        run: echo &quot;환경: ${{ vars.APP_ENV }}&quot;

      - name: secrets 출력 (마스킹)
        run: echo &quot;토큰: ${{ secrets.API_TOKEN }}&quot;
        # 로그: 토큰: ***</code></pre>
<br>

<h3 id="06-expressions--표현식"><strong>06. Expressions — 표현식</strong></h3>
<ul>
<li>표현식은 ${{ }} 안에 작성하며, Context 값 참조, 연산, 함수 호출을 지원</li>
<li>if 조건, run-name, env 값, with 입력값 등에서 사용 가능</li>
</ul>
<br>

<h3 id="07-inputs-context--workflow_dispatch-입력값"><strong>07. inputs Context — workflow_dispatch 입력값</strong></h3>
<ul>
<li>workflow_dispatch 이벤트에 inputs를 정의하면 Actions 탭의 &quot;Run workflow&quot; 버튼 클릭 시 사용자가 직접 값을 입력할 수 있음</li>
</ul>
<pre><code class="language-yaml">on:
  workflow_dispatch:
    inputs:
      name:
        description: &#39;이름 (영소문자)&#39;
        required: true
        default: &#39;user&#39;
        type: string
      environment:
        description: &#39;배포 환경&#39;
        required: true
        default: &#39;staging&#39;
        type: choice
        options:
          - staging
          - production
      debug:
        description: &#39;디버그 모드&#39;
        required: false
        default: false
        type: boolean

jobs:
  use-inputs:
    runs-on: ubuntu-latest
    steps:
      - name: 입력값 확인
        run: |
          echo &quot;이름: ${{ inputs.name }}&quot;
          echo &quot;환경: ${{ inputs.environment }}&quot;
          echo &quot;디버그: ${{ inputs.debug }}&quot;</code></pre>
<h3 id="github-actions-job-제어"><strong>GitHub Actions Job 제어</strong></h3>
<h3 id="01-needs--job-의존성-및-순차-실행"><strong>01. needs — Job 의존성 및 순차 실행</strong></h3>
<ul>
<li>모든 Job은 병렬 실행</li>
<li>needs 키워드로 선행 Job을 지정하면 해당 Job 완료 후 현재 Job이 실행</li>
</ul>
<pre><code>[needs를 활용한 Job 실행 순서 설계]

  병렬 실행 (기본)        needs 활용 순차/혼합 실행

  ┌──────┐ ┌──────┐      ┌──────┐
  │ job1│ │ job2 │      │ job1 │ ── lint
  └──────┘ └──────┘      └──────┘
                              │
                    ┌─────────┼──────────┐
                    ▼         ▼          ▼
                 ┌──────┐ ┌──────┐   ┌──────┐
                 │ job2│ │ job3 │   │ job4 │  ← 동시 실행 (모두 job1 needs)
                 └──────┘ └──────┘   └──────┘
                    │         │
                    └────┬────┘
                         ▼
                      ┌──────┐
                      │ job5│ (needs: [job2, job3])
                      └──────┘</code></pre><br>

<h3 id="02-strategymatrix--다중-환경-병렬-빌드"><strong>02. strategy.matrix — 다중 환경 병렬 빌드</strong></h3>
<ul>
<li>strategy.matrix는 하나의 Job 정의로 여러 변수 조합을 생성하여 각각 독립된 Runner에서 병렬 실행</li>
</ul>
<br>

<h3 id="03-if--조건부-실행"><strong>03. if — 조건부 실행</strong></h3>
<ul>
<li>Job 또는 Step 레벨에서 사용하며, 조건이 true일 때만 실행</li>
</ul>
<pre><code class="language-yaml">jobs:
  # Job 레벨 if
  deploy:
    runs-on: ubuntu-latest
    if: ${{ github.ref_name == &#39;main&#39; }}
    steps:
      - run: echo &quot;main 배포&quot;

  # Step 레벨 if
  conditional-steps:
    runs-on: ubuntu-latest
    steps:
      - id: random
        run: echo &quot;num=$(($RANDOM % 2))&quot; &gt;&gt; $GITHUB_OUTPUT

      - name: 0일 때 실행
        if: ${{ steps.random.outputs.num == &#39;0&#39; }}
        run: echo &quot;0 출력&quot;

      - name: 실패 시에만 실행
        if: ${{ failure() }}
        run: echo &quot;이전 Step 실패&quot;

      - name: 항상 실행 (실패해도)
        if: ${{ always() }}
        run: echo &quot;항상 실행됨&quot;</code></pre>
<table>
<thead>
<tr>
<th>패턴</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>github.ref_name == &#39;main&#39;</code></td>
<td>main 브랜치에서만 실행</td>
</tr>
<tr>
<td><code>github.event_name == &#39;pull_request&#39;</code></td>
<td>PR에서만 실행</td>
</tr>
<tr>
<td><code>startsWith(github.ref, &#39;refs/tags/v&#39;)</code></td>
<td>v로 시작하는 태그에서만 실행</td>
</tr>
<tr>
<td><code>contains(github.event.head_commit.message, &#39;skip ci&#39;)</code></td>
<td>커밋 메시지 포함 여부</td>
</tr>
<tr>
<td><code>needs.build.result == &#39;success&#39;</code></td>
<td>선행 Job 성공 시에만 실행</td>
</tr>
<tr>
<td><code>failure()</code></td>
<td>이전 Step/Job 실패 시 실행</td>
</tr>
<tr>
<td><code>always()</code></td>
<td>항상 실행</td>
</tr>
</tbody></table>
<br>

<h3 id="04-concurrency--동시-실행-제어"><strong>04. concurrency — 동시 실행 제어</strong></h3>
<ul>
<li>동일 그룹에서 동시에 실행되는 워크플로우/Job 수를 제한</li>
<li>연속 push 시 이전 배포가 완료되기 전 새 배포가 시작되는 문제를 방지</li>
</ul>
<pre><code>[concurrency 동작]

  Push 1 → 배포 A 실행 중 ────────────────▶ 완료
  Push 2 →       대기 (A가 실행 중이므로)
  Push 3 →             A 취소 후 C가 실행    (cancel-in-progress: true)
           → Push 2는 취소됨 (가장 최신만 실행)</code></pre><pre><code class="language-yaml"># 워크플로우 레벨 적용 (가장 일반적)
name: 배포 워크플로우
on: push

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}    # 브랜치별 독립 그룹
  cancel-in-progress: true                            # 진행 중 취소

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - run: echo &quot;배포&quot;</code></pre>
<br>

<h3 id="github-actions-artifacts--caching"><strong>GitHub Actions Artifacts &amp; Caching</strong></h3>
<h3 id="01-artifacts--빌드-결과물-저장-및-공유"><strong>01. Artifacts — 빌드 결과물 저장 및 공유</strong></h3>
<ul>
<li>워크플로우 실행 중 생성된 파일을 GitHub에 업로드하여 Job 간 공유하거나 실행 후 다운로드할 수 있도록 하는 기능</li>
<li>CI에서 빌드한 JAR, 테스트 리포트, 로그 파일 등을 저장하고 CD Job에서 사용하는 패턴이 가장 일반적</li>
</ul>
<pre><code>[Artifact 흐름]

  Job 1 (Runner A)
  ├── 파일 생성 (예: app.jar)
  └── actions/upload-artifact ──▶ GitHub Artifact 저장소
                                          │
                                          │ (Job 경계 초월)
                                          ▼
  Job 2 (Runner B)              GitHub Artifact 저장소
  ├── actions/download-artifact ◀──────────┘
  └── 파일 사용</code></pre><pre><code class="language-yaml">jobs:
  produce:
    runs-on: ubuntu-latest
    steps:
      - name: 파일 생성
        run: |
          mkdir output
          echo &quot;빌드 결과&quot; &gt; output/result.txt

      - name: Artifact 업로드
        uses: actions/upload-artifact@v4
        with:
          name: my-artifact              # Artifact 이름 (다운로드 시 참조)
          path: output/                  # 업로드할 파일 또는 디렉토리
          retention-days: 7              # 보관 기간 (기본 90일)
          if-no-files-found: error       # 파일 없을 때 처리: error|warn|ignore

  consume:
    needs: produce
    runs-on: ubuntu-latest
    steps:
      - name: Artifact 다운로드
        uses: actions/download-artifact@v4
        with:
          name: my-artifact              # 업로드 시 지정한 이름
          path: ./received               # 다운로드 경로

      - name: 다운로드 확인
        run: |
          ls -al ./received
          cat ./received/result.txt</code></pre>
<br>

<h3 id="02-actionscache--의존성-캐싱"><strong>02. actions/cache — 의존성 캐싱</strong></h3>
<ul>
<li>의존성 파일(node_modules, ~/.m2, ~/.gradle 등)을 캐싱하여 워크플로우 실행 시간을 단축</li>
<li>캐시 키가 일치하면 이전에 저장한 캐시를 복원하고, 불일치 시 새로 다운로드 후 저장</li>
</ul>
<br>

<h3 id="github-actions-reusable-workflows--composite-actions"><strong>GitHub Actions Reusable Workflows &amp; Composite Actions</strong></h3>
<h3 id="01-재사용의-필요성"><strong>01. 재사용의 필요성</strong></h3>
<ul>
<li>여러 리포지토리 또는 여러 워크플로우에서 동일한 작업(JDK 설정, 빌드, 배포 등)을 반복 정의하는 문제를 해결하기 위해 GitHub Actions는 두 가지 재사용 메커니즘을 제공</li>
</ul>
<table>
<thead>
<tr>
<th>구분</th>
<th>Reusable Workflow</th>
<th>Composite Action</th>
</tr>
</thead>
<tbody><tr>
<td>재사용 단위</td>
<td>Job (전체)</td>
<td>Step (일부)</td>
</tr>
<tr>
<td>파일 위치</td>
<td><code>.github/workflows/*.yml</code></td>
<td><code>.github/actions/{name}/action.yml</code></td>
</tr>
<tr>
<td>호출 방법</td>
<td><code>uses: ./.github/workflows/파일.yml</code></td>
<td><code>uses: ./.github/actions/폴더명</code></td>
</tr>
<tr>
<td>Runner 설정</td>
<td>재사용 워크플로우 내부에서 정의</td>
<td>호출 Job의 Runner 사용</td>
</tr>
<tr>
<td>inputs 전달</td>
<td><code>with</code>로 inputs 전달</td>
<td><code>with</code>로 inputs 전달</td>
</tr>
<tr>
<td>secrets 전달</td>
<td><code>secrets: inherit</code> 또는 명시</td>
<td>호출 환경 자동 공유</td>
</tr>
</tbody></table>
<br>

<h3 id="02-reusable-workflow--workflow_call"><strong>02. Reusable Workflow — workflow_call</strong></h3>
<ul>
<li>on: workflow_call로 정의된 워크플로우는 다른 워크플로우에서 uses 키워드로 호출 가능</li>
<li>inputs로 파라미터를 받고, secrets로 시크릿을 전달받으며, outputs로 결과를 반환</li>
</ul>
<pre><code class="language-yaml"># .github/workflows/reusable-deploy.yml (재사용 가능한 워크플로우)

name: Reusable Deploy
on:
  workflow_call:               # 이 키워드로 재사용 가능하게 정의
    inputs:
      environment:
        required: true
        type: string
      version:
        required: true
        type: string
    outputs:
      result:                  # 호출한 워크플로우로 반환할 값
        description: &quot;배포 결과&quot;
        value: ${{ jobs.deploy.outputs.result }}

jobs:
  deploy:
    runs-on: ubuntu-latest
    outputs:
      result: ${{ steps.run.outputs.result }}
    steps:
      - id: run
        run: |
          echo &quot;환경: ${{ inputs.environment }}&quot;
          echo &quot;버전: ${{ inputs.version }}&quot;
          echo &quot;result=success-${{ inputs.environment }}&quot; &gt;&gt; $GITHUB_OUTPUT</code></pre>
<pre><code class="language-yaml"># .github/workflows/main-pipeline.yml (호출하는 워크플로우)

name: Main Pipeline
on: workflow_dispatch

jobs:
  call-deploy:
    uses: ./.github/workflows/reusable-deploy.yml    # 로컬 호출
    with:
      environment: staging
      version: &quot;1.0.0&quot;

  use-result:
    needs: call-deploy
    runs-on: ubuntu-latest
    steps:
      - run: echo &quot;결과: ${{ needs.call-deploy.outputs.result }}&quot;</code></pre>
<br>

<h3 id="github-actions-cicd-실습"><strong>GitHub Actions CI/CD 실습</strong></h3>
<ul>
<li>간단한 REST API를 가진 Spring Boot 프로젝트를 GitHub Actions를 통해 mylab-bastion EC2에 자동 배포하는 흐름을 단계별로 구축</li>
</ul>
<pre><code class="language-yaml">[전체 흐름]

  단계 1. SSH + Private Key 직접 실행
       JAR을 SCP로 EC2에 직접 전송, SSH로 java -jar 실행
       장점 : 가장 단순, Docker 불필요
       한계 : 컨테이너 격리 없음, 환경 의존성 노출

       전환 동기 : 컨테이너로 환경 격리 필요
              ↓

  단계 2. Docker Hub + 컨테이너 실행
       JAR을 Docker 이미지로 빌드, Docker Hub에 push
       EC2가 Docker Hub에서 pull하여 컨테이너 실행
       장점 : 환경 격리, 이미지 버전 관리 용이
       한계 : Private Key, Docker Hub Token 모두 GitHub Secrets에 저장

       전환 동기 : 장기 자격증명을 모두 제거하고 싶음
              ↓

  단계 3. OIDC 인증으로 전환
       장기 자격증명 제거, AWS IAM Role을 임시 토큰으로 Assume
       장점 : Secret 노출 위험 최소화, 자동 회전
       실무 표준 : AWS와 GitHub Actions의 권장 패턴</code></pre>
<br>

<p><strong>사용할 Action 참고 URL</strong></p>
<table>
<thead>
<tr>
<th>Action</th>
<th>URL</th>
<th>역할</th>
</tr>
</thead>
<tbody><tr>
<td>actions/checkout</td>
<td><a href="https://github.com/actions/checkout">https://github.com/actions/checkout</a></td>
<td>리포지토리 코드 체크아웃</td>
</tr>
<tr>
<td>actions/setup-java</td>
<td><a href="https://github.com/actions/setup-java">https://github.com/actions/setup-java</a></td>
<td>JDK 설정 + Maven 캐시</td>
</tr>
<tr>
<td>actions/upload-artifact</td>
<td><a href="https://github.com/actions/upload-artifact">https://github.com/actions/upload-artifact</a></td>
<td>빌드 결과물 업로드</td>
</tr>
<tr>
<td>actions/download-artifact</td>
<td><a href="https://github.com/actions/download-artifact">https://github.com/actions/download-artifact</a></td>
<td>Job 간 결과물 공유</td>
</tr>
<tr>
<td>appleboy/scp-action</td>
<td><a href="https://github.com/marketplace/actions/scp-command-to-transfer-files">https://github.com/marketplace/actions/scp-command-to-transfer-files</a></td>
<td>SSH로 파일 전송</td>
</tr>
<tr>
<td>appleboy/ssh-action</td>
<td><a href="https://github.com/marketplace/actions/ssh-remote-commands">https://github.com/marketplace/actions/ssh-remote-commands</a></td>
<td>SSH로 원격 명령 실행</td>
</tr>
</tbody></table>
<br>

<h4 id="cicdyml-작성"><strong>cicd.yml 작성</strong></h4>
<ul>
<li>ci Job(빌드 + Artifact 업로드)과 cd Job(다운로드 + SCP + SSH 실행)을 하나의 워크플로우 파일에 분리해서 작성</li>
<li>needs로 cd가 ci 완료 후 자동 실행되도록 연결.</li>
</ul>
<pre><code class="language-yaml">name: CI/CD - cicd-basic-lab to mylab-bastion

# 빈칸 1: main 브랜치 push와 수동 실행 두 가지 트리거 작성
on:
  push:
    branches: [main]
  workflow_dispatch:

# 빈칸 2: 같은 워크플로우/브랜치 동시 실행 시 이전 실행 취소되도록 설정
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true
jobs:
  ci:
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: ./cicd-basic-lab

    # 빈칸 3: cd Job에서 참조할 Artifact 이름을 outputs로 노출
    outputs:
      artifact_name: ${{ steps.set-name.outputs.value }}

    steps:
      - name: 소스코드 체크아웃
        uses: actions/checkout@v4

      - name: JDK 17 설정
        uses: actions/setup-java@v4
        with:
          java-version: &#39;17&#39;
          distribution: &#39;temurin&#39;
          # 빈칸 4: Maven 의존성 캐시 키워드
          cache: maven

      - name: Maven 빌드
        run: mvn -B package -DskipTests 

      - name: JAR 파일명 정리
        run: mv ./target/*.jar ./target/app.jar

      - name: Artifact 이름 설정
        id: set-name
        # 빈칸 5: GITHUB_OUTPUT에 value=app-{run_number} 형식으로 저장
        run: echo &quot;value=app-${{ github.run_number }}&quot; &gt;&gt; $GITHUB_OUTPUT

      - name: JAR Artifact 업로드
        uses: actions/upload-artifact@v4
        with:
          name: ${{ steps.set-name.outputs.value }}
          # 빈칸 6: 업로드할 파일 경로
          path: ./cicd-basic-lab/target/app.jar
          retention-days: 1

  cd:
    # 빈칸 7: ci Job 완료 후 실행되도록 의존성 선언
    needs: ci
    runs-on: ubuntu-latest

    steps:
      - name: JAR Artifact 다운로드
        uses: actions/download-artifact@v4
        with:
          # 빈칸 8: ci Job의 outputs에서 artifact_name 값 참조
          name: ${{ needs.ci.outputs.artifact_name }}
          path: .

      - name: SCP로 EC2에 JAR 전송
        # 빈칸 9: SCP 액션 (소유자/저장소 형식)
        uses: appleboy/scp-action@v1
        with:
          # 빈칸 10: 등록한 secrets 3개를 각각 참조
          host: ${{ secrets.EC2_HOST }}
          username: ${{ secrets.EC2_USERNAME }}
          key: ${{ secrets.EC2_PRIVATE_KEY }}
          source: &quot;app.jar&quot;
          target: &quot;/home/ubuntu/app&quot;

      - name: SSH로 EC2에서 앱 실행
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.EC2_HOST }}
          username: ${{ secrets.EC2_USERNAME }}
          key: ${{ secrets.EC2_PRIVATE_KEY }}
          # 빈칸 11: 스크립트 중 실패 시 즉시 중단되도록 설정
          script_stop: true
          script: |
            mkdir -p /home/ubuntu/app/deploy
            mv /home/ubuntu/app/app.jar /home/ubuntu/app/deploy/app.jar
            sudo fuser -k -n tcp 8080 || true
            cd /home/ubuntu/app/deploy
            nohup java -jar app.jar &gt; ./output.log 2&gt;&amp;1 &amp;
            sleep 3
            echo &quot;앱 실행 완료&quot;</code></pre>
<br>

<h4 id="단계-1의-한계점">단계 1의 한계점</h4>
<table>
<thead>
<tr>
<th>한계</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>환경 격리 없음</td>
<td>EC2에 직접 java -jar 실행. JDK 버전, 시스템 라이브러리에 종속</td>
</tr>
<tr>
<td>의존성 충돌 위험</td>
<td>여러 앱 배포 시 충돌 가능</td>
</tr>
<tr>
<td>배포 롤백 어려움</td>
<td>이전 JAR 백업/복원 로직 별도 필요</td>
</tr>
</tbody></table>
<br>

<h3 id="이-한계를-해결하기-위해-docker-컨테이너로-전환">이 한계를 해결하기 위해 Docker 컨테이너로 전환</h3>
<br>

<h4 id="github-variables-추가">GitHub Variables 추가</h4>
<p>리포지토리 → Settings → Secrets and variables → Actions → Variables 탭 → New repository variable</p>
<table>
<thead>
<tr>
<th>Variable 이름</th>
<th>값</th>
</tr>
</thead>
<tbody><tr>
<td>DOCKERHUB_USERNAME</td>
<td>Docker Hub 계정명 (예: myname)</td>
</tr>
</tbody></table>
<h4 id="github-secrets-추가">GitHub Secrets 추가</h4>
<p>Secrets 탭 → New repository secret</p>
<table>
<thead>
<tr>
<th>Secret 이름</th>
<th>값</th>
</tr>
</thead>
<tbody><tr>
<td>DOCKERHUB_TOKEN</td>
<td>발급한 PAT</td>
</tr>
</tbody></table>
<br>

<h4 id="사용할-action-참고-url">사용할 Action 참고 URL</h4>
<table>
<thead>
<tr>
<th>Action</th>
<th>URL</th>
<th>역할</th>
</tr>
</thead>
<tbody><tr>
<td>docker/login-action</td>
<td><a href="https://github.com/marketplace/actions/docker-login">https://github.com/marketplace/actions/docker-login</a></td>
<td>Docker 레지스트리 로그인</td>
</tr>
<tr>
<td>docker/setup-buildx-action</td>
<td><a href="https://github.com/marketplace/actions/docker-setup-buildx">https://github.com/marketplace/actions/docker-setup-buildx</a></td>
<td>BuildKit 빌더 설정</td>
</tr>
<tr>
<td>docker/build-push-action</td>
<td><a href="https://github.com/marketplace/actions/build-and-push-docker-images">https://github.com/marketplace/actions/build-and-push-docker-images</a></td>
<td>이미지 빌드 및 push</td>
</tr>
</tbody></table>
<br>

<h4 id="cicdyml-작성-1"><strong>cicd.yml 작성</strong></h4>
<pre><code class="language-yaml">name: CI/CD - cicd-basic-lab to mylab-bastion (Docker)

on:
  push:
    branches: [main]
  workflow_dispatch:

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  ci:
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: ./cicd-basic-lab
    # 빈칸 1: cd Job에서 참조할 이미지 태그를 outputs로 노출
    outputs:
      image_tag: ${{ steps.tag.outputs.value }}
    steps:
      - uses: actions/checkout@v4

      - name: JDK 17 설정
        uses: actions/setup-java@v4
        with:
          java-version: &#39;17&#39;
          distribution: &#39;temurin&#39;
          cache: maven

      - name: Maven 빌드
        run: mvn -B package -DskipTests

      - name: JAR 파일명 정리
        run: mv ./target/*.jar ./target/app.jar

      - name: 이미지 태그 생성
        id: tag
        # 빈칸 2: {DOCKERHUB_USERNAME}/cicd-basic-lab:{run_number}-{sha 앞 7자리} 형식 생성
        # 힌트: SHORT_SHA로 SHA 7자리 추출 → TAG 변수에 조합 → $GITHUB_OUTPUT에 저장
        run: |
          SHORT_SHA=$(echo &quot;${{ github.sha }}&quot; | cut -c1-7)
          TAG=&quot;${{ vars.DOCKERHUB_USERNAME }}/cicd-basic-lab:${{ github.run_number }}-$SHORT_SHA&quot;
          echo &quot;value=$TAG&quot; &gt;&gt; $GITHUB_OUTPUT

      - name: Docker Hub 로그인
        # 빈칸 3: Docker 로그인 액션 (소유자/저장소@v3)
        uses: docker/login-action@v3
        with:
          # 빈칸 4: vars로 username, secrets로 token 참조
          username: ${{ vars.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Docker Buildx 설정
        # 빈칸 5: Buildx 설정 액션
        uses: docker/setup-buildx-action@v3

      - name: 이미지 빌드 및 Push
        uses: docker/build-push-action@v6
        with:
          # 빌드 컨텍스트는 Dockerfile이 있는 프로젝트 폴더
          context: ./cicd-basic-lab
          # 빈칸 6: Push를 위한 boolean 값
          push: true
          tags: |
            ${{ steps.tag.outputs.value }}
            ${{ vars.DOCKERHUB_USERNAME }}/cicd-basic-lab:latest
          # 빈칸 7: GitHub Actions 캐시를 Docker 레이어 캐시로 활용
          cache-from: type=gha
          cache-to: type=gha,mode=max # mode=max : 중간 빌드 단계의 레이어까지 모두 저장 / mode=min : 최종 레이어만 저장

  cd:
    needs: ci
    runs-on: ubuntu-latest
    steps:
      - name: 배포할 이미지 출력
        run: |
          echo &quot;배포 이미지: ${{ needs.ci.outputs.image_tag }}&quot;

      - name: SSH로 EC2에서 docker pull &amp; run
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.EC2_HOST }}
          username: ${{ secrets.EC2_USERNAME }}
          key: ${{ secrets.EC2_PRIVATE_KEY }}
          script_stop: true
          # 빈칸 8: pull → 기존 컨테이너 stop/rm → run -d 순서로 작성
          # 힌트:
          #   docker pull {image_tag}
          #   docker stop cicd-basic-lab || true
          #   docker rm cicd-basic-lab || true
          #   docker run -d --name cicd-basic-lab -p 8080:8080 -e TZ=Asia/Seoul --restart unless-stopped {image_tag}
          #   docker image prune -f
          script: |
            docker pull ${{ needs.ci.outputs.image_tag }}
            docker stop cicd-basic-lab || true
            docker rm cicd-basic-lab || true
            docker run -d \
              --name cicd-basic-lab \
              -p 8080:8080 \
              -e TZ=Asia/Seoul \
              --restart unless-stopped \
              ${{ needs.ci.outputs.image_tag }}
            docker image prune -f
            docker ps
            echo &quot;배포 완료&quot;</code></pre>
<br>

<p><strong>실습 아키텍처</strong></p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/99757c98-6f5a-4132-8f15-33cd59911a67/image.png" alt=""></p>
<br>

<h4 id="단계-2의-한계점">단계 2의 한계점</h4>
<table>
<thead>
<tr>
<th>한계</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>Private Key가 여전히 GitHub에 저장</td>
<td>EC2_PRIVATE_KEY는 장기 자격증명</td>
</tr>
<tr>
<td>Docker Hub Token도 장기 자격증명</td>
<td>DOCKERHUB_TOKEN 노출 시 누구나 이미지 push 가능</td>
</tr>
<tr>
<td>자격증명 회전이 수동</td>
<td>키/토큰 변경 시 GitHub Secrets도 매번 갱신</td>
</tr>
<tr>
<td>감사 로그 부족</td>
<td>누가 언제 어떤 행위를 했는지 추적 어려움</td>
</tr>
</tbody></table>
<br>

<h3 id="aws-iam-oidc-provider-및-role-생성">AWS IAM OIDC Provider 및 Role 생성</h3>
<ul>
<li>Docker Hub 부분은 다음 과정에서 ECR로 전환</li>
</ul>
<pre><code class="language-yaml">[단계 2 → 단계 3 전환]

[단계 2: SSH + Docker Hub]
  GitHub Actions
       │
       │ Docker Hub Token (장기)
       ├─▶ Docker Hub Push
       │
       │ EC2_PRIVATE_KEY (장기, 가장 위험)
       └─▶ SSH/SCP ─▶ EC2

[단계 3: OIDC + Docker Hub + SSM]
  GitHub Actions
       │
       │ Docker Hub Token (단계 2 그대로 유지)
       ├─▶ Docker Hub Push
       │
       │ OIDC 임시 자격증명 (15분~1시간)
       └─▶ AWS STS ─▶ SSM Send-Command ─▶ EC2
                                          (SSH Agent 없이 명령 실행)</code></pre>
<br>

<h4 id="github-variables-추가-1">GitHub Variables 추가</h4>
<table>
<thead>
<tr>
<th>Variable 이름</th>
<th>값</th>
</tr>
</thead>
<tbody><tr>
<td>AWS_ROLE_ARN</td>
<td>arn:aws:iam::{계정ID}:role/github-actions-cicd-basic-lab-role</td>
</tr>
<tr>
<td>AWS_REGION</td>
<td>ap-northeast-2</td>
</tr>
<tr>
<td>EC2_INSTANCE_ID</td>
<td>mylab-bastion 인스턴스 ID (예: i-0abc1234def567890)</td>
</tr>
</tbody></table>
<br>

<h3 id="사용할-action-참고-url-1">사용할 Action 참고 URL</h3>
<table>
<thead>
<tr>
<th>Action</th>
<th>URL</th>
<th>역할</th>
</tr>
</thead>
<tbody><tr>
<td>aws-actions/configure-aws-credentials</td>
<td><a href="https://github.com/aws-actions/configure-aws-credentials">https://github.com/aws-actions/configure-aws-credentials</a></td>
<td>OIDC 토큰으로 AWS 자격증명 설정</td>
</tr>
</tbody></table>
<br>

<h4 id="cicdyml-작성-2"><strong>cicd.yml 작성</strong></h4>
<pre><code class="language-yaml">name: CI/CD - cicd-basic-lab to mylab-bastion (OIDC + SSM)
on:
  push:
    branches: [main]
  workflow_dispatch:

# 빈칸 1: OIDC 토큰 발급을 위한 권한
permissions:
  id-token: write
  contents: read

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  ci:
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: ./cicd-basic-lab
    outputs:
      image_tag: ${{ steps.tag.outputs.value }}
    steps:
      - uses: actions/checkout@v4

      - name: JDK 17 설정
        uses: actions/setup-java@v4
        with:
          java-version: &#39;17&#39;
          distribution: &#39;temurin&#39;
          cache: maven

      - name: Maven 빌드
        run: mvn -B package -DskipTests

      - name: JAR 파일명 정리
        run: mv ./target/*.jar ./target/app.jar

      - name: 이미지 태그 생성
        id: tag
        run: |
          SHORT_SHA=$(echo &quot;${{ github.sha }}&quot; | cut -c1-7)
          TAG=&quot;${{ vars.DOCKERHUB_USERNAME }}/cicd-basic-lab:${{ github.run_number }}-$SHORT_SHA&quot;
          echo &quot;value=$TAG&quot; &gt;&gt; $GITHUB_OUTPUT

      - name: Docker Hub 로그인
        uses: docker/login-action@v3
        with:
          username: ${{ vars.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Docker Buildx 설정
        uses: docker/setup-buildx-action@v3

      - name: 이미지 빌드 및 Push
        uses: docker/build-push-action@v6
        with:
          # 빌드 컨텍스트는 Dockerfile이 있는 프로젝트 폴더
          context: ./cicd-basic-lab
          push: true
          tags: |
            ${{ steps.tag.outputs.value }}
            ${{ vars.DOCKERHUB_USERNAME }}/cicd-basic-lab:latest
          cache-from: type=gha
          cache-to: type=gha,mode=max

  cd:
    needs: ci
    runs-on: ubuntu-latest
    steps:
      - name: 배포할 이미지 출력
        run: |
          echo &quot;배포 이미지: ${{ needs.ci.outputs.image_tag }}&quot;

      - name: AWS 자격증명 설정 (OIDC)
        # 빈칸 2: configure-aws-credentials 액션 (소유자/저장소@v4)
        uses: aws-actions/configure-aws-credentials@v4
        with:
          # 빈칸 3: vars로 등록한 Role ARN 참조
          role-to-assume: ${{ vars.AWS_ROLE_ARN }}
          aws-region: ${{ vars.AWS_REGION }}

      - name: 자격증명 검증
        # 현재 어떤 Role로 인증되었는지 확인 (디버깅 목적)
        run: aws sts get-caller-identity

      - name: SSM Send-Command로 EC2 배포
        id: ssm
        # 빈칸 4: SSM SendCommand 호출 (AWS-RunShellScript 사용)
        # 힌트: aws ssm send-command --instance-ids {EC2_INSTANCE_ID} --document-name &quot;AWS-RunShellScript&quot; --parameters commands=...
        run: |
          COMMAND_ID=$(aws ssm send-command \
            --instance-ids ${{ vars.EC2_INSTANCE_ID }} \
            --document-name &quot;AWS-RunShellScript&quot; \
            --comment &quot;Deploy cicd-basic-lab run #${{ github.run_number }}&quot; \
            --parameters &#39;{&quot;commands&quot;:[
              &quot;docker pull ${{ needs.ci.outputs.image_tag }}&quot;,
              &quot;docker stop cicd-basic-lab || true&quot;,
              &quot;docker rm cicd-basic-lab || true&quot;,
              &quot;docker run -d --name cicd-basic-lab -p 8080:8080 -e TZ=Asia/Seoul --restart unless-stopped ${{ needs.ci.outputs.image_tag }}&quot;,
              &quot;docker image prune -f&quot;,
              &quot;docker ps&quot;
            ]}&#39; \
            --query &quot;Command.CommandId&quot; \
            --output text)
          echo &quot;command_id=$COMMAND_ID&quot; &gt;&gt; $GITHUB_OUTPUT
          echo &quot;SSM Command ID: $COMMAND_ID&quot;

      - name: SSM 명령 결과 대기 및 확인
        # 빈칸 5: 명령 실행 완료까지 대기 후 결과 출력
        # 힌트:
        #   sleep 10  (또는 폴링 로직)
        #   aws ssm get-command-invocation --command-id {command_id} --instance-id {EC2_INSTANCE_ID}
        run: |
          sleep 10
          aws ssm get-command-invocation \
            --command-id &quot;${{ steps.ssm.outputs.command_id }}&quot; \
            --instance-id ${{ vars.EC2_INSTANCE_ID }} \
            --query &quot;{Status:Status, Output:StandardOutputContent, Error:StandardErrorContent}&quot; \
            --output table</code></pre>
<br>

<p><strong>실습 아키텍처</strong></p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/3db3fe06-aacd-40b3-83c8-d553c0e15cc9/image.png" alt=""></p>
<hr>
<h3 id="컨테이너-서비스-개요--아키텍처"><strong>컨테이너 서비스 개요 &amp; 아키텍처</strong></h3>
<h3 id="01-컨테이너란"><strong>01. 컨테이너란</strong></h3>
<ul>
<li>애플리케이션과 그 실행에 필요한 모든 의존성(라이브러리, 런타임, 환경변수, 설정 파일 등)을 하나의 패키지로 묶어 격리된 환경에서 실행하는 기술</li>
</ul>
<table>
<thead>
<tr>
<th>항목</th>
<th>가상 머신 (VM)</th>
<th>컨테이너</th>
</tr>
</thead>
<tbody><tr>
<td>격리 수준</td>
<td>OS 수준 완전 격리</td>
<td>프로세스 수준 격리</td>
</tr>
<tr>
<td>기동 시간</td>
<td>수십 초 ~ 수 분</td>
<td>수 밀리초 ~ 수 초</td>
</tr>
<tr>
<td>이미지 크기</td>
<td>수 GB</td>
<td>수십 ~ 수백 MB</td>
</tr>
<tr>
<td>OS 공유</td>
<td>불가 (각각 Guest OS 포함)</td>
<td>가능 (Host OS 커널 공유)</td>
</tr>
<tr>
<td>이식성</td>
<td>낮음</td>
<td>높음 (어디서나 동일 실행)</td>
</tr>
</tbody></table>
<br>

<h3 id="02-docker-핵심-개념"><strong>02. Docker 핵심 개념</strong></h3>
<ul>
<li>컨테이너를 빌드·실행·배포하기 위한 플랫폼</li>
</ul>
<pre><code>[Docker 핵심 구성 요소]

  Dockerfile    docker build                 docker push
  (빌드 명세서) ──────────────► Docker Image ──────────────► Registry (읽기 전용)  
                                                                                                                      (ECR / Docker Hub)
                                                                                                                                  │
                                                                                                              docker run│
                                                                                                                                  ▼
                                                                                                                          Container
                                                                                                                 (Image + 쓰기 레이어)</code></pre><br>

<h3 id="03-aws-컨테이너-서비스-전체-맵"><strong>03. AWS 컨테이너 서비스 전체 맵</strong></h3>
<ul>
<li>AWS는 컨테이너 생명주기 전반을 지원하는 완성된 서비스 생태계를 제공</li>
</ul>
<pre><code>[AWS 컨테이너 서비스 전체 맵]

┌──────────────────────────────────────────────────────────────┐
│  빌드 (Build)                                            │
│  로컬 Docker CLI / GitHub Actions / AWS CodeBuild        │
│   → Dockerfile → Docker Image 생성                       │
└─────────────────────────────┬────────────────────────────────┘
                              │ docker push
                              ▼
┌──────────────────────────────────────────────────────────────┐
│  저장 (Registry) - ECR                                  │
│  Private Registry (계정·리전 격리)                        │
│   - mylab-backend:1.0.0                                 │
│   - mylab-backend:{git-sha}                             │
│ Tag Immutability / Enhanced Scanning / Lifecycle Policy │
└─────────────────────────────┬────────────────────────────────┘
                              │ docker pull (IAM Role 자동 인증)
                              ▼
┌──────────────────────────────────────────────────────────────┐
│  실행 (Orchestration)                                    │
│                                                         │
│   Amazon ECS                    Amazon EKS              │
│   (AWS 관리형 오케스트레이터)   (관리형 Kubernetes)         │
│   mylab-cluster                 Kubernetes Cluster      │
│  Fargate / EC2 선택            Fargate / EC2 / Karpenter │
└─────────────────────────────┬────────────────────────────────┘
                              │
                              ▼
┌──────────────────────────────────────────────────────────────┐
│  관찰 &amp; 보안 (Observability &amp; Security)                  │
│  CloudWatch (메트릭·로그)       CloudTrail (API 감사)     │
│  AWS X-Ray (분산 추적)          Amazon Inspector (취약점) │
│  IAM (접근 제어)                AWS KMS (이미지 암호화)    │
└──────────────────────────────────────────────────────────────┘</code></pre><table>
<thead>
<tr>
<th>서비스</th>
<th>역할</th>
<th>주요 특징</th>
</tr>
</thead>
<tbody><tr>
<td>Amazon ECR</td>
<td>컨테이너 이미지 저장소</td>
<td>완전 관리형, IAM 통합, 이미지 스캔</td>
</tr>
<tr>
<td>Amazon ECS</td>
<td>컨테이너 오케스트레이터</td>
<td>AWS 전용, 단순 설정, Fargate 지원</td>
</tr>
<tr>
<td>Amazon EKS</td>
<td>관리형 Kubernetes</td>
<td>쿠버네티스 표준, 유연한 워크로드</td>
</tr>
<tr>
<td>AWS Fargate</td>
<td>서버리스 컴퓨팅 엔진</td>
<td>인프라 관리 불필요, ECS/EKS 모두 사용 가능</td>
</tr>
</tbody></table>
<br> 

<h3 id="04-ecr--ecs-핵심-흐름-이해"><strong>04. ECR + ECS 핵심 흐름 이해</strong></h3>
<ul>
<li>ECR과 ECS는 독립적인 서비스이지만 항상 함께 사용</li>
</ul>
<table>
<thead>
<tr>
<th>단계</th>
<th>작업</th>
<th>관련 서비스</th>
</tr>
</thead>
<tbody><tr>
<td>1. Build</td>
<td>멀티 스테이지 Dockerfile 기반 이미지 빌드</td>
<td>Docker CLI / GitHub Actions</td>
</tr>
<tr>
<td>2. Push</td>
<td><code>mylab-backend</code> 레포지토리에 이미지 업로드</td>
<td>ECR</td>
</tr>
<tr>
<td>3. Pull</td>
<td>ECS Task 실행 시 ECR에서 이미지 다운로드</td>
<td>ECR + IAM Role</td>
</tr>
<tr>
<td>4. Run</td>
<td>Task를 <code>private-app-2a/2c</code>에서 Fargate로 실행</td>
<td>ECS + Fargate</td>
</tr>
<tr>
<td>5. Expose</td>
<td><code>mylab-alb</code>를 통해 외부 트래픽 수신</td>
<td>ALB + ECS Service</td>
</tr>
</tbody></table>
<br>

<h3 id="amazon-ecr---이미지-레지스트리"><strong>Amazon ECR - 이미지 레지스트리</strong></h3>
<h3 id="01-amazon-ecr-개요"><strong>01. Amazon ECR 개요</strong></h3>
<ul>
<li>Amazon ECR(Elastic Container Registry)은 AWS가 제공하는 완전 관리형 컨테이너 이미지 레지스트리 서비스. Docker 이미지, OCI 이미지, Helm Chart 등을 안전하게 저장·관리·배포</li>
<li>자체 레지스트리(Harbor, Nexus 등)를 운영할 필요 없이 AWS가 인프라 유지보수·확장·가용성을 완전히 관리</li>
</ul>
<pre><code>[ECR 서비스 개념 구조]

  AWS 계정 (123456789012)
  ┌───────────────────────────────────────────────────────────┐
  │  ECR Private Registry                                │
  │URI: 123456789012.dkr.ecr.ap-northeast-2.amazonaws.com│
  │                                                      │
  │   ┌─────────────────────────┐                          │
  │   │ Repository            │                          │
  │   │ (mylab-backend)       │                          │
  │   │                       │                          │
  │   │  mylab-backend:1.0.0  │                          │
  │   │  mylab-backend:1.1.0  │                          │
  │   │  mylab-backend:latest │  ← 각 이미지는             │
  │   │  mylab-backend:{sha}  │    Tag + Digest로 식별    │
  │   └─────────────────────────┘                          │
  │                                                      │
  └───────────────────────────────────────────────────────────┘

  이미지 URI 형식:
    {계정ID}.dkr.ecr.{리전}.amazonaws.com/{레포지토리명}:{태그}

  예시:
    123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/mylab-backend:1.0.0</code></pre><br>

<h3 id="02-ecr-인증-메커니즘"><strong>02. ECR 인증 메커니즘</strong></h3>
<ul>
<li>Docker 표준 인증(Username/Password)이 아닌, AWS IAM 기반 토큰 인증을 사용</li>
<li>인증 토큰은 12시간 유효하며, Push/Pull 전 반드시 획득해야 함</li>
</ul>
<pre><code>[ECR 인증 흐름]

  mylab-bastion (Docker CLI)
  │
  │ 1. aws ecr get-login-password
  ▼
  AWS IAM 서비스
  │ IAM 권한 검증
  │ 2. 임시 인증 토큰 발급 (유효기간: 12시간)
  ▼
  mylab-bastion
  │
  │ 3. docker login --username AWS --password-stdin
  │  {계정ID}.dkr.ecr.ap-northeast-2.amazonaws.com
  ▼
  ECR 엔드포인트
  │ 토큰 검증 완료
  │ 4. docker push / docker pull 허용
  ▼
  ECR Repository (mylab-backend)</code></pre><br>

<h3 id="03-pull-through-cache--cross-region-replication"><strong>03. Pull-through Cache &amp; Cross-Region Replication</strong></h3>
<ul>
<li><strong>Pull-through Cache</strong>는 Docker Hub 등 외부 레지스트리 이미지를 ECR Private Registry에 자동 캐싱하는 기능</li>
</ul>
<br>

<h3 id="ecr-보안--운영-관리"><strong>ECR 보안 &amp; 운영 관리</strong></h3>
<h3 id="01-ecr-보안-설계"><strong>01. ECR 보안 설계</strong></h3>
<ul>
<li>ECR 보안은 이미지의 생성·저장·배포 전 단계에 걸쳐 적용</li>
</ul>
<p>1순위: Tag Immutability 활성화 (운영 환경 필수)
2순위: Enhanced Scanning + Scan-on-Push 활성화
3순위: Lifecycle Policy 설정 (비용 최적화)
4순위: Secrets Manager 연동 (민감 정보 분리)
5순위: Image Signing 적용 (공급망 보안)</p>
<br>

<h3 id="02-lifecycle-policy-수명주기-정책"><strong>02. Lifecycle Policy (수명주기 정책)</strong></h3>
<ul>
<li>Lifecycle Policy는 레포지토리의 이미지를 규칙 기반으로 자동 삭제하는 기능</li>
<li>CI/CD 파이프라인에서 이미지가 지속적으로 Push되면 저장 비용이 증가하므로 필수 설정</li>
</ul>
<pre><code>┌─────────────────────────────────────────────────────────┐
│         ECR Lifecycle Policy 동작 흐름               │
└─────────────────────────────────────────────────────────┘

   Push 누적                Lifecycle Policy           정리 후
   ┌─────────┐              평가(매일 1회)             ┌─────────┐
   │ 이미지  │              ┌──────────────┐           │ 필요한  │
   │ 100개  │  ────────►    │ 규칙 매칭    │  ──────►   │ 30개만  │
   │ (난잡함)│              │ → expire    │           │ 보존    │
   └─────────┘              └──────────────┘           └─────────┘
                                                      스토리지 비용 절감</code></pre><h3 id="amazon-ecs"><strong>Amazon ECS</strong></h3>
<h3 id="01-개요"><strong>01. 개요</strong></h3>
<ul>
<li>Amazon ECS(Elastic Container Service) AWS가 제공하는 완전 관리형 컨테이너 오케스트레이션 서비스</li>
<li>컨테이너의 배포·스케일링·상태 관리를 자동화하며, AWS 생태계(ECR, ALB, IAM, CloudWatch 등)와 긴밀하게 통합</li>
</ul>
<pre><code>[ECS 핵심 구성 요소 계층 구조]

  mylab-cluster (ECS Cluster)
  ─ 컨테이너를 실행하는 논리적 그룹
  │
  └── mylab-backend-service (ECS Service)
              ─ 지정한 수의 Task를 항상 실행 유지
              ─ 실패한 Task 자동 재시작
                  │
                  ├── Task #1 (private-app-2a)
                  │    └── mylab-backend 컨테이너
                  │    (mylab-backend:1.0 이미지)
                  │
                  └── Task #2 (private-app-2c)
                           └── mylab-backend 컨테이너
                           (mylab-backend:1.0 이미지)

  Task Definition: mylab-backend
  ─ Task 실행 청사진 (이미지, CPU, 메모리, 환경변수, 시크릿 등)
  ─ Revision으로 버전 관리 (mylab-backend:1, mylab-backend:2 ...)</code></pre><table>
<thead>
<tr>
<th>구성 요소</th>
<th>역할</th>
</tr>
</thead>
<tbody><tr>
<td>Cluster</td>
<td>컴퓨팅 자원 묶음</td>
</tr>
<tr>
<td>Task Definition</td>
<td>컨테이너 실행 설계도</td>
</tr>
<tr>
<td>Task</td>
<td>실행 중인 컨테이너 인스턴스</td>
</tr>
<tr>
<td>Service</td>
<td>원하는 수의 Task를 유지하는 관리자</td>
</tr>
</tbody></table>
<br>

<h3 id="02-task-definition-구조"><strong>02. Task Definition 구조</strong></h3>
<ul>
<li>Task Definition은 ECS Task를 실행하기 위한 설계도</li>
</ul>
<pre><code>[mylab-backend Task Definition 구조]

  Task Definition: mylab-backend (Revision 1)
  ┌──────────────────────────────────────────────────────────────────┐
  │  Family:        mylab-backend                               │ 
  │  Launch Type:   FARGATE                                     │
  │  Network Mode:  awsvpc                                      │
  │  Task CPU:      512 (0.5 vCPU)                              │
  │  Task Memory:   1024 MB                                     │
  │                                                             │
  │  Execution Role: mylab-backend-execution-role               │
  │   ├─ ECR Pull 권한                                           │
  │   ├─ CloudWatch Logs 전송 권한                                │
  │   └─ Secrets Manager 조회 권한                                │
  │                                                             │
  │  Task Role: mylab-backend-task-role                         │
  │   ├─ S3 접근 (애플리케이션 요건)                               │
  │   └─ SQS 접근 (애플리케이션 요건)                              │
  │                                                             │
  │  Container: mylab-backend                                   │
  │  ┌────────────────────────────────────────────────────────────┐  │
  │  │  Image:                                               │  │
  │  │   {계정}.dkr.ecr.ap-northeast-2.amazonaws.com          │  │
  │  │   /mylab-backend:1.0                                  │  │
  │  │                                                       │  │
  │  │  Port: 8080                                           │  │
  │  │                                                       │  │
  │  │  Environment:                                         │  │
  │  │   SPRING_PROFILES_ACTIVE = prod                       │  │
  │  │   DB_HOST = mylab-rds.xxx.rds.amazonaws.com           │  │
  │  │                                                       │  │
  │  │  Secrets (Secrets Manager 참조):                       │  │
  │  │   DB_PASSWORD → mylab/db-password                     │  │
  │  │   JWT_SECRET  → mylab/jwt-secret                      │  │
  │  │                                                       │  │
  │  │  Log Driver: awslogs → /ecs/mylab-backend             │  │
  │  └────────────────────────────────────────────────────────────┘  │
  └──────────────────────────────────────────────────────────────────┘</code></pre><p><strong>Task Role vs Task Execution Role 구분</strong></p>
<pre><code>[Execution Role vs Task Role 비교]

  mylab-backend-execution-role          mylab-backend-task-role
  ──────────────────────────────        ──────────────────────────────
  ECS 인프라(Agent)가 사용               컨테이너 애플리케이션이 사용

  ECR            → 이미지 Pull           S3        → 파일 업로드/다운로드
  CloudWatch     → 로그 전송             DynamoDB  → 데이터 접근
  Secrets Manager → 시크릿 조회          SQS       → 메시지 처리

           ↑                                       ↑
   ECS가 자동 사용                        Spring Boot AWS SDK가 사용
   (컨테이너 시작 전)                     (컨테이너 실행 중)</code></pre><br>

<h3 id="03-ecs-보안-그룹-설계"><strong>03. ECS 보안 그룹 설계</strong></h3>
<ul>
<li>ECS Task는 private-app-2a/2c에 배치</li>
<li>보안 그룹은 최소 권한 원칙에 따라 mylab-alb-sg와 mylab-bastion-sg에서 오는 트래픽만 허용</li>
</ul>
<pre><code>[mylab-ecs-task-sg 설계]

                      인터넷
                        │
                        ▼
              mylab-alb (mylab-alb-sg)
                        │
                        │ 8080 허용
                        │ (mylab-alb-sg → mylab-ecs-task-sg)
                        ▼
         ┌──────────────────────────────────────┐
         │  ECS Task (mylab-ecs-task-sg)     │
         │                                   │
         │  인바운드 규칙:                     │
         │   ① 8080 ← mylab-alb-sg 참조       │  ← ALB에서만 허용
         │   ② 22   ← mylab-bastion-sg 참조   │  ← Bastion에서만 허용
         │                                   │
         │  아웃바운드 규칙:                   │
         │   All traffic → 0.0.0.0/0         │  ← ECR Pull, RDS 접근 등
         └──────────────────────────────────────┘
                        │
                        │ 3306
                        │ (mylab-ecs-task-sg → sg-db)
                        ▼
                 RDS MySQL (sg-db)</code></pre><br>

<h3 id="ecs-fargate-실전-배포"><strong>ECS Fargate 실전 배포</strong></h3>
<h3 id="01-실습-목표-아키텍처"><strong>01. 실습 목표 아키텍처</strong></h3>
<ul>
<li>shop-vpc 3계층 구조를 기반으로, ALB는 퍼블릭 서브넷에, ECS Task는 프라이빗 서브넷에 배치하는 실무 표준 구조</li>
</ul>
<br>

<h3 id="02-fargate-동작-원리"><strong>02. Fargate 동작 원리</strong></h3>
<ul>
<li>AWS Fargate는 ECS의 서버리스 컴퓨팅 엔진</li>
<li>Task 실행 시 AWS가 내부적으로 전용 마이크로 VM을 프로비저닝하여 컨테이너를 격리 실행</li>
</ul>
<pre><code>[사용자 관리 영역 vs Fargate 관리 영역]

  사용자가 설정하는 영역
  ┌──────────────────────────────────────────────────┐
  │  Task Definition (이미지, CPU, 메모리, 포트)   │
  │  ECS Service (desired count, 배포 전략)       │
  │ VPC 서브넷:    private-app-2a / private-app-2c│
  │  보안 그룹:     mylab-ecs-task-sg             │
  │  IAM Role:      Execution Role / Task Role   │
  └──────────────────────────────────────────────────┘
                          │
                          │ Task 실행 요청 (RunTask API)
                          ▼
  AWS Fargate가 자동 처리하는 영역
  ┌──────────────────────────────────────────────────┐
  │  마이크로 VM 프로비저닝 (Firecracker)           │
  │  컨테이너 런타임 설치 (containerd)              │
  │  ECR에서 mylab-backend:1.0 Pull               │
  │  private-app 서브넷에 ENI 연결 (awsvpc 모드)    │
  │  컨테이너 실행 및 상태 모니터링                  │
  │  OS 패치 및 보안 업데이트                       │
  │  EC2 인스턴스 관리 / 클러스터 스케일링            │
  └──────────────────────────────────────────────────┘</code></pre><hr>
<h3 id="ecr--ecs-통합-아키텍처--cicd"><strong>ECR + ECS 통합 아키텍처 &amp; CI/CD</strong></h3>
<h3 id="01--ecr--ecs-전환"><strong>01.  ECR + ECS 전환</strong></h3>
<pre><code>[단계별 발전 흐름]

단계 1. SSH + Private Key
  GitHub Actions → SCP → EC2 (JAR 직접 전송) → SSH 실행
  문제: 장기 자격증명(Private Key) 노출 위험, EC2 직접 관리 부담

단계 2. Docker Hub + 컨테이너
  GitHub Actions → Docker Hub Push → EC2 docker pull + run
  문제: Docker Hub Rate Limit, 민간 레지스트리 의존, EC2 여전히 존재

단계 3. OIDC + SSM
  GitHub Actions (OIDC) → SSM Send-Command → EC2 docker pull + run
  문제: EC2가 여전히 존재, 인프라 관리 필요

[ECR + ECS 전환]

단계 4. OIDC + ECR + ECS (목표)
  GitHub Actions (OIDC)
  → ECR Push (AWS 전용 프라이빗 레지스트리)
  → ECS Rolling Update (서버리스 컨테이너 실행)
  → EC2 없음 / 인프라 관리 없음 / 무중단 배포</code></pre><table>
<thead>
<tr>
<th>항목</th>
<th>CICD</th>
<th>ECR + ECS</th>
</tr>
</thead>
<tbody><tr>
<td>컨테이너 레지스트리</td>
<td>Docker Hub</td>
<td>Amazon ECR</td>
</tr>
<tr>
<td>실행 환경</td>
<td>EC2 (직접 관리)</td>
<td>ECS Fargate (서버리스)</td>
</tr>
<tr>
<td>배포 방식</td>
<td>SSM Send-Command</td>
<td>ECS Rolling Update</td>
</tr>
<tr>
<td>스케일링</td>
<td>수동</td>
<td>Auto Scaling 자동</td>
</tr>
<tr>
<td>인프라 관리</td>
<td>EC2 패치/관리 필요</td>
<td>AWS 완전 관리</td>
</tr>
<tr>
<td>비용 모델</td>
<td>EC2 상시 과금</td>
<td>Task 실행 시간 과금</td>
</tr>
</tbody></table>
<br>

<h3 id="02-전체-cicd-파이프라인-흐름">02. 전체 CI/CD 파이프라인 흐름</h3>
<pre><code>[cicd-basic-lab → mylab-backend ECR + ECS CI/CD 파이프라인]

  개발자
  │ git push origin main
  ▼
  GitHub Repository (cicd-basic-lab)
  │ main 브랜치 Push → cicd.yml 트리거
  ▼
  ┌─────────────────────────────────────────────────────────────┐
  │  Job 1: ci (빌드 + ECR Push)                            │
  │                                                       │
  │  [1] OIDC 인증                                          │
  │      mylab-github-actions-role 임시 자격증명 발급        │
  │                                                        │
  │  [2] Maven 빌드                                        │
  │      ./mvnw clean package -DskipTests                  │
  │                                                        │
  │  [3] Docker 멀티 스테이지 빌드                           │
  │      이미지 태그: {run_number}-{sha7}                   │
  │      예: mylab-backend:42-abc1234                      │
  │                                                        │
  │  [4] ECR 인증 + Push                                    │
  │      aws ecr get-login-password | docker login         │
  │      docker push → mylab-backend:{태그}                 │
  │                                                        │
  │  [5] ECR Enhanced Scanning 결과 확인                     │
  │      Critical 발견 시 → 파이프라인 중단 (배포 차단)         │
  │                                                        │
  │  outputs: image-uri → Job 2로 전달                      │
  └──────────────────────────┬──────────────────────────────────┘
                             │ needs: ci
                             ▼
  ┌─────────────────────────────────────────────────────────────┐
  │  Job 2: cd (ECS 배포)                                   │
  │                                                        │
  │  [6] OIDC 인증 (재인증)                                  │
  │                                                        │
  │  [7] Task Definition ACCOUNT_ID 동적 치환                │
  │                                                        │
  │  [8] 이미지 URI 교체                                     │
  │      amazon-ecs-render-task-definition                 │
  │      mylab-backend 컨테이너 이미지를 새 URI로 교체         │
  │                                                        │
  │  [9] ECS Rolling Update 배포                           │
  │      amazon-ecs-deploy-task-definition                 │
  │      mylab-cluster / mylab-backend-service             │
  │      배포 안정화까지 대기 (wait-for-service-stability)    │
  └─────────────────────────────────────────────────────────────┘
                             │
                             ▼
  ECS mylab-cluster
  └── mylab-backend-service
       ├── Task (private-app-2a): mylab-backend:{새 태그}
       └── Task (private-app-2c): mylab-backend:{새 태그}
             ↑ Rolling Update (무중단)</code></pre><br>

<h3 id="github-secrets--variables-설정"><strong>GitHub Secrets / Variables 설정</strong></h3>
<pre><code>경로: GitHub → cicd-basic-lab 레포지토리
→ Settings → Secrets and variables → Actions

[Secrets]
→ [New repository secret]
Name: AWS_ACCOUNT_ID
Value: 123456789012    ← 본인 AWS 계정 ID

OIDC 방식이므로 ACCESS_KEY 등 장기 자격증명은 저장하지 않음

[Variables]
→ [New repository variable]
Name: ECR_REPOSITORY  / Value: mylab-backend
Name: ECS_CLUSTER     / Value: mylab-cluster
Name: ECS_SERVICE     / Value: mylab-backend-service
Name: CONTAINER_NAME  / Value: mylab-backend</code></pre><br>

<h3 id="사용할-action-참고-url-2">사용할 Action 참고 URL</h3>
<table>
<thead>
<tr>
<th>Action</th>
<th>URL</th>
<th>역할</th>
</tr>
</thead>
<tbody><tr>
<td>actions/checkout</td>
<td><a href="https://github.com/actions/checkout">https://github.com/actions/checkout</a></td>
<td>리포지토리 코드 체크아웃</td>
</tr>
<tr>
<td>actions/setup-java</td>
<td><a href="https://github.com/actions/setup-java">https://github.com/actions/setup-java</a></td>
<td>JDK 설정 + Maven 캐시</td>
</tr>
<tr>
<td>aws-actions/configure-aws-credentials</td>
<td><a href="https://github.com/aws-actions/configure-aws-credentials">https://github.com/aws-actions/configure-aws-credentials</a></td>
<td>OIDC 인증 + AWS 임시 자격증명 발급</td>
</tr>
<tr>
<td>aws-actions/amazon-ecr-login</td>
<td><a href="https://github.com/aws-actions/amazon-ecr-login">https://github.com/aws-actions/amazon-ecr-login</a></td>
<td>ECR 로그인 (docker login 대체)</td>
</tr>
<tr>
<td>aws-actions/amazon-ecs-render-task-definition</td>
<td><a href="https://github.com/aws-actions/amazon-ecs-render-task-definition">https://github.com/aws-actions/amazon-ecs-render-task-definition</a></td>
<td>Task Definition 이미지 URI 교체</td>
</tr>
<tr>
<td>aws-actions/amazon-ecs-deploy-task-definition</td>
<td><a href="https://github.com/aws-actions/amazon-ecs-deploy-task-definition">https://github.com/aws-actions/amazon-ecs-deploy-task-definition</a></td>
<td>ECS Rolling Update 배포</td>
</tr>
</tbody></table>
<br>

<h3 id="cicdyml-작성-ecr--ecs-버전">cicd.yml 작성 (ECR + ECS 버전)</h3>
<pre><code class="language-yaml">name: CI/CD — ECR + ECS

on:
  push:
    branches: [ main ]
  workflow_dispatch:

permissions:
  id-token: write    # OIDC JWT 토큰 발급
  contents: read

env:
  AWS_REGION: ap-northeast-2
  ECR_REPOSITORY: ${{ vars.ECR_REPOSITORY }}
  ECS_CLUSTER:    ${{ vars.ECS_CLUSTER }}
  ECS_SERVICE:    ${{ vars.ECS_SERVICE }}
  CONTAINER_NAME: ${{ vars.CONTAINER_NAME }}
  TASK_DEFINITION: .aws/task-definition.json

jobs:

  ci:
    name: CI — Build &amp; Push to ECR
    runs-on: ubuntu-latest

    outputs:
      image-repo-tag: ${{ steps.build-push.outputs.image-repo-tag }}

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up JDK 17
        uses: actions/setup-java@v4
        with:
          java-version: &#39;17&#39;
          distribution: &#39;temurin&#39;
          cache: &#39;maven&#39;

      - name: Build with Maven
        working-directory: cicd-basic-lab
        run: mvn clean package -DskipTests -B

      - name: Configure AWS credentials (OIDC)
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/github-actions-cicd-basic-lab-role
          aws-region: ${{ env.AWS_REGION }}
          role-session-name: GitHubActions-CI-${{ github.run_id }}

      - name: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v2

      - name: Build, tag and push image to ECR
        working-directory: cicd-basic-lab
        env:
          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
        run: |
          SHORT_SHA=&quot;${{ github.sha }}&quot;
          SHORT_SHA=&quot;${SHORT_SHA:0:7}&quot;
          TAG=&quot;${{ github.run_number }}-${SHORT_SHA}&quot;
          IMAGE_URI=&quot;${ECR_REGISTRY}/${{ env.ECR_REPOSITORY }}:${TAG}&quot;

          docker build -t ${IMAGE_URI} .
          docker push ${IMAGE_URI}

          # working-directory 스텝에서 GITHUB_OUTPUT 직접 쓰면 cd Job 전달 불가
          # GITHUB_ENV로 다음 스텝에 전달
          echo &quot;IMAGE_TAG=${TAG}&quot; &gt;&gt; $GITHUB_ENV

      # working-directory 없이 실행해야 GITHUB_OUTPUT 정상 동작
      - name: Export image outputs
        id: build-push
        run: |
          echo &quot;image-repo-tag=${{ env.ECR_REPOSITORY }}:${IMAGE_TAG}&quot; &gt;&gt; $GITHUB_OUTPUT

  cd:
    name: CD — Deploy to ECS
    runs-on: ubuntu-latest
    needs: ci

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Configure AWS credentials (OIDC)
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/github-actions-cicd-basic-lab-role
          aws-region: ${{ env.AWS_REGION }}
          role-session-name: GitHubActions-CD-${{ github.run_id }}

      # ACCOUNT_ID 플레이스홀더 치환
      # find로 동적 탐색 (경로 중복 문제 회피)
      - name: Inject Account ID into task definition
        run: |
          ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
          TASK_DEF_PATH=$(find $GITHUB_WORKSPACE -name &quot;task-definition.json&quot; | head -1)
          sed -i &quot;s/ACCOUNT_ID/${ACCOUNT_ID}/g&quot; ${TASK_DEF_PATH}

      # . / 포함 값은 Job outputs 전달 불가
      # → image-repo-tag (mylab-backend:36-761c224) 만 전달받고
      # → cd Job에서 ECR registry 주소를 직접 조합
      - name: Render new task definition with updated image
        id: task-def
        uses: aws-actions/amazon-ecs-render-task-definition@v1
        with:
          task-definition: ${{ env.TASK_DEFINITION }}
          container-name:  ${{ env.CONTAINER_NAME }}
          image:           ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ env.AWS_REGION }}.amazonaws.com/${{ needs.ci.outputs.image-repo-tag }}

      - name: Deploy to ECS (Rolling Update)
        uses: aws-actions/amazon-ecs-deploy-task-definition@v1
        with:
          task-definition: ${{ steps.task-def.outputs.task-definition }}
          service:  ${{ env.ECS_SERVICE }}
          cluster:  ${{ env.ECS_CLUSTER }}
          wait-for-service-stability: true  # NEW TASK가 healthy + OLD TASK가 종료 여부 확</code></pre>
<br>

<p><strong>실습 아키텍처</strong></p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/8a72793b-d03e-4618-aa31-23e9b098d37a/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[AWS] RDS Proxy의 대상 그룹 에러]]></title>
            <link>https://velog.io/@its-jihyeon/AWS-RDS-Proxy%EC%9D%98-%EB%8C%80%EC%83%81-%EA%B7%B8%EB%A3%B9-%EC%97%90%EB%9F%AC</link>
            <guid>https://velog.io/@its-jihyeon/AWS-RDS-Proxy%EC%9D%98-%EB%8C%80%EC%83%81-%EA%B7%B8%EB%A3%B9-%EC%97%90%EB%9F%AC</guid>
            <pubDate>Sat, 25 Apr 2026 05:00:41 GMT</pubDate>
            <description><![CDATA[<h4 id="에러-상황">에러 상황</h4>
<p>RDS Proxy 설정 중 “사용할 수 없음” 에러 발생</p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/5dca7db6-a46d-4377-9937-7c2a6631fffb/image.png" alt=""></p>
<br>

<h4 id="원인">원인</h4>
<pre><code>[Proxy 도입 구조]
  EC2 Task × 50         RDS Proxy              RDS Primary
  ┌────────────┐        ┌────────────────┐    ┌─────────────┐
  │ HikariCP ×5│ ──────►│ 연결 풀 관리    │──► │ 실제 연결 30│
  │ 250 요청   │        │ (Multiplexing) │    │ 한도 이내    │
  └────────────┘        └────────────────┘    └─────────────┘
                        ↑ 여러 앱 연결을 소수 DB 연결로 묶음</code></pre><p>여기 구조에서 RDS Proxy와 RDS primary는 같은 보안그룹(mylab-rds-sg)을 공유 중</p>
<p>보안 그룹 인바운드 규칙에 EC2 Task만 허용된 상태인 걸 발견</p>
<ul>
<li>EC2 Task → RDS Proxy (EC2 주소 허용 규칙이 있어 통과)</li>
<li>RDS Proxy → RDS Primary <strong>(Proxy 주소 허용 규칙이 없어 차단 발생!)</strong></li>
</ul>
<br>

<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/228a2aa4-f48b-467f-95e3-edd7f10ae9a4/image.png" alt=""></p>
<br>

<h4 id="해결책">해결책</h4>
<p>보안 그룹 인바운드 규칙에 RDS Proxy자기 자신의 ID를 추가</p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/d172d6d9-09be-4db8-9ae3-b3638afd4310/image.png" alt=""></p>
<br>

<p>RDS Proxy의 상태가 Available로 변경된 것을 확인</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[멋쟁이사자처럼부트캠프 클라우드 엔지니어링 5기 [13주차]]]></title>
            <link>https://velog.io/@its-jihyeon/%EB%A9%8B%EC%9F%81%EC%9D%B4%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A7%81-5%EA%B8%B0-13%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@its-jihyeon/%EB%A9%8B%EC%9F%81%EC%9D%B4%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A7%81-5%EA%B8%B0-13%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Fri, 24 Apr 2026 15:26:26 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>13주차에는 Spring Boot와 AWS 서비스를 연결하여 사용하는 실습을 많이 했다. 
실습 코드는 따로 개인 깃허브에 저장해놓고 복습할 예정이다.</p>
</blockquote>
<br>

<h3 id="spring-boot-파일-서비스--ec2-배포--iam-role"><strong>Spring Boot 파일 서비스 — EC2 배포 + IAM Role</strong></h3>
<h3 id="01-ec2--s3-전체-아키텍처"><strong>01. EC2 + S3 전체 아키텍처</strong></h3>
<ul>
<li>로컬 환경에서 개발하고 테스트했다면, 제 AWS 환경으로 배포</li>
<li>JAR 파일을 직접 EC2에 전송하고, IAM Role을 사용해 S3 접근 권한을 안전하게 관리</li>
</ul>
<pre><code>[로컬 개발 → EC2 배포 전환]

 ─────────────── (로컬) ──────────────────

 개발자 PC (Windows / Mac)
 ┌──────────────────────────────────────────┐
 │STS → Spring Boot 실행 (localhost:8080)│
 │IAM User 자격증명 (~/.aws/credentials)  │
 └──────────────────────────────────────────┘
 │ HTTPS
 ▼
 AWS S3 버킷

 ─────────────── (EC2 배포) ────────────────

 개발자 PC                    AWS Cloud
 ┌──────────────┐         ┌──────────────────────────────┐
 │ mvn package │         │      EC2 (Ubuntu 24.04)   │
 │ -&gt; app.jar  │   scp   │           t3.micro        │
 │             │ ──────► │      Java 17 + app.jar 실행│
 └──────────────┘         │             :8080         │
                         │                           │
                         │IAM Role (Instance Profile)│
                         │    ↓ 자동 자격증명 획득      │
                         └──────────────┬───────────────┘
                                       │ HTTPS
                                       ▼
                                     AWS S3 버킷
                                  (ap-northeast-2)</code></pre><br>

<h4 id="로컬-vs-ec2-환경-비교">로컬 vs EC2 환경 비교</h4>
<table>
<thead>
<tr>
<th>항목</th>
<th>로컬</th>
<th>EC2</th>
</tr>
</thead>
<tbody><tr>
<td><strong>실행 방법</strong></td>
<td>STS Boot Dashboard</td>
<td>JAR 직접 실행 (<code>java -jar</code>)</td>
</tr>
<tr>
<td><strong>자격증명</strong></td>
<td><code>~/.aws/credentials</code> 파일</td>
<td>IAM Role (Instance Profile)</td>
</tr>
<tr>
<td><strong>파일 저장</strong></td>
<td>로컬 디스크</td>
<td>임시 저장 없이 S3 직접 스트리밍</td>
</tr>
<tr>
<td><strong>OS</strong></td>
<td>Windows / macOS</td>
<td>Ubuntu 24.04</td>
</tr>
<tr>
<td><strong>코드 변경</strong></td>
<td>없음</td>
<td>없음 (환경만 달라짐)</td>
</tr>
</tbody></table>
<br>

<h3 id="02-iam-user-vs-iam-role"><strong>02. IAM User vs IAM Role</strong></h3>
<p>로컬 개발과 EC2 배포 환경에서 AWS 인증 방식이 달라짐</p>
<pre><code>[IAM User — 로컬 개발]

 Spring Boot
 │
 ▼
 DefaultCredentialsProvider
 │
 ├── ~/.aws/credentials 파일 발견
 │   aws_access_key_id = AKIA...
 │   aws_secret_access_key = xxxx
 └── 해당 키로 S3 API 호출

 문제점:
 -&gt; 키를 EC2 서버에 복사하면 보안 위험
 -&gt; 키 노출 시 서버를 통해 S3 전체 접근 가능

---
[IAM Role — EC2 배포]

 Spring Boot (EC2에서 실행)
 │
 ▼
 DefaultCredentialsProvider
 │
 ├── ~/.aws/credentials 없음
 │
 ├── EC2 메타데이터 서비스 확인
 │   http://169.254.169.254/latest/meta-data/iam/security-credentials/
 │
 └── IAM Role에서 임시 자격증명 자동 발급
     AccessKeyId / SecretAccessKey / SessionToken
     → 만료 전 자동 갱신

 장점:
 -&gt; 키를 서버에 저장할 필요 없음
 -&gt; 권한을 Role로 중앙 관리
 -&gt; 임시 자격증명 → 유출되어도 만료 시 무효</code></pre><br>

<h4 id="iam-user-vs-iam-role-비교">IAM User vs IAM Role 비교</h4>
<table>
<thead>
<tr>
<th>항목</th>
<th>IAM User (Access Key)</th>
<th>IAM Role (Instance Profile)</th>
</tr>
</thead>
<tbody><tr>
<td><strong>자격증명 위치</strong></td>
<td>파일 / 환경변수</td>
<td>EC2 메타데이터 서비스 (자동)</td>
</tr>
<tr>
<td><strong>만료</strong></td>
<td>영구 (수동 삭제 전까지)</td>
<td>임시 (자동 갱신, 보통 1시간)</td>
</tr>
<tr>
<td><strong>보안 위험</strong></td>
<td>키 유출 시 영구 접근 가능</td>
<td>만료 시 자동 무효화</td>
</tr>
<tr>
<td><strong>코드 변경</strong></td>
<td>불필요</td>
<td>불필요 (SDK가 자동 전환)</td>
</tr>
<tr>
<td><strong>사용 환경</strong></td>
<td>로컬 개발</td>
<td>EC2, Lambda, ECS 등 AWS 서비스</td>
</tr>
<tr>
<td><strong>실무 권장</strong></td>
<td>로컬만</td>
<td>EC2 이상 모든 배포 환경</td>
</tr>
</tbody></table>
<br>

<h3 id="03-환경별-설정-분리"><strong>03. 환경별 설정 분리</strong></h3>
<ul>
<li>로컬 개발과 EC2 운영 환경에서 파일 경로, 로그 경로, 캐시 설정 등이 다름</li>
<li>하나의 application.properties에서 모두 관리하면 실수로 운영 설정이 로컬에 적용되거나, 로컬 설정이 EC2에 배포되는 문제 발생</li>
<li>Spring Profile을 사용해 환경별 설정을 분리하면 이를 방지 가능</li>
</ul>
<pre><code>[프로파일별 설정 파일 구조]

 src/main/resources/
 ├── application.properties          &lt;- 공통 설정 (모든 환경에서 항상 로드)
 ├── application-local.properties    &lt;- 로컬 전용 (local 프로파일 활성화 시)
 └── application-prod.properties     &lt;- EC2 운영 전용 (prod 프로파일 활성화 시)

 [로드 우선순위]
 application-{profile}.properties 값이
 application.properties 값을 덮어씀 (override)</code></pre><br>

<h3 id="04-spring-boot-actuator-헬스체크"><strong>04. Spring Boot Actuator 헬스체크</strong></h3>
<h3 id="개념"><strong>개념</strong></h3>
<ul>
<li>Actuator는 운영 중인 Spring Boot 애플리케이션의 <strong>상태·메트릭·정보를 HTTP 엔드포인트로 노출</strong>하는 공식 모듈</li>
<li>EC2 배포 후 앱이 정상 동작하는지 확인하거나, ALB의 헬스체크 Target으로 활용</li>
</ul>
<pre><code>[Actuator 동작 구조]

 Spring Boot 앱 (내부)
 ├── HealthIndicator 자동 집계
 │     ├── DiskSpaceHealthIndicator  : 디스크 여유 공간 확인
 │     ├── PingHealthIndicator       : 앱 자체 응답 확인
 │     └── (DB 연결 시) DataSourceHealthIndicator
 │
 └── /actuator/health 엔드포인트로 결과 노출
           │
           ▼
      { &quot;status&quot;: &quot;UP&quot; }   &lt;- HTTP 200 OK
      { &quot;status&quot;: &quot;DOWN&quot; } &lt;- HTTP 503 Service Unavailable

 ALB
 └── 주기적으로 /actuator/health 호출
       -&gt; 200 OK  : 인스턴스 정상 -&gt; 트래픽 전달
       -&gt; 그 외   : 인스턴스 비정상 -&gt; 트래픽 제외, 새 인스턴스 기동</code></pre><br>

<h4 id="actuator-응답-상태-종류">Actuator 응답 상태 종류</h4>
<table>
<thead>
<tr>
<th>status 값</th>
<th>HTTP 코드</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td><code>UP</code></td>
<td>200</td>
<td>모든 컴포넌트 정상</td>
</tr>
<tr>
<td><code>DOWN</code></td>
<td>503</td>
<td>하나 이상의 컴포넌트 비정상</td>
</tr>
<tr>
<td><code>OUT_OF_SERVICE</code></td>
<td>503</td>
<td>수동으로 서비스 중단 처리</td>
</tr>
<tr>
<td><code>UNKNOWN</code></td>
<td>200</td>
<td>상태 확인 불가 (경고)</td>
</tr>
</tbody></table>
<ul>
<li>운영 환경에서는 show-details=when-authorized로 설정하여 상세 정보를 인증된 요청에만 노출</li>
<li>ALB 헬스체크는 /actuator/health 경로만 호출하면 되며, status: UP = HTTP 200 응답으로 정상 판단</li>
</ul>
<br>

<h3 id="05-무중단-배포-패턴"><strong>05. 무중단 배포 패턴</strong></h3>
<h3 id="alb--asg-rolling-배포-다중-ec2"><strong>ALB + ASG Rolling 배포 (다중 EC2)</strong></h3>
<ul>
<li>ALB(Application Load Balancer)와 ASG(Auto Scaling Group)를 사용하면 EC2를 순차적으로 교체하는 Rolling 배포가 가능</li>
<li>인스턴스 하나씩 새 버전으로 교체하면서 전체 서비스 중단 없이 배포</li>
</ul>
<pre><code>[ALB + ASG Rolling 배포 흐름]

 배포 전:
 ALB -&gt; EC2 #1 (구버전) :8080
     -&gt; EC2 #2 (구버전) :8080

 Rolling 배포 중:
 Step 1: EC2 #1 을 ALB에서 제외 (In-service -&gt; Draining)
          EC2 #1 새 버전으로 교체 후 Actuator 헬스체크 통과
          EC2 #1 ALB에 재등록 (In-service)

 Step 2: EC2 #2 를 ALB에서 제외
          EC2 #2 새 버전으로 교체 후 Actuator 헬스체크 통과
          EC2 #2 ALB에 재등록

 배포 완료:
 ALB -&gt; EC2 #1 (신버전) :8080
     -&gt; EC2 #2 (신버전) :8080</code></pre><br>

<h3 id="spring-boot-파일-서비스--cloudwatch-모니터링"><strong>Spring Boot 파일 서비스 — CloudWatch 모니터링</strong></h3>
<h3 id="01-cloudwatch-로그-모니터링-개념"><strong>01. CloudWatch 로그 모니터링 개념</strong></h3>
<ul>
<li>Spring Boot 애플리케이션이 EC2에서 실행될 때, 로그는 기본적으로 EC2 내부 파일에만 기록됨</li>
<li><strong>AWS CloudWatch Logs</strong>는 애플리케이션 로그를 중앙에 수집·저장·검색할 수 있게 해주는 관리형 서비스</li>
</ul>
<pre><code>[CloudWatch 없을 때 — 로그 확인의 불편함]

 EC2 인스턴스 A EC2 인스턴스 B
 ┌─────────────┐ ┌────────────┐
 │ app.log    │ │   app.log │
 │ (로컬 파일) │ │ (로컬 파일) │
 └─────────────┘ └────────────┘
 ↑ ↑
 SSH 접속 후 확인 SSH 접속 후 확인
 → 인스턴스가 늘어날수록 로그 확인이 어려워짐
 → 인스턴스 종료 시 로그 소실

---
[CloudWatch 사용 후 — 중앙 집중 로그]

 EC2 인스턴스 A EC2 인스턴스 B
 ┌─────────────┐     ┌─────────────┐
 │    app.log │    │     app.log │
 └──────┬──────┘     └──────┬──────┘
        │ CloudWatch Agent  │ CloudWatch Agent
        └────────┬──────────┘
                                 ▼
                                 AWS CloudWatch Logs
                                 └── Log Group: /ec2/s3-file-service
                                 ├── Log Stream: i-0abc123 (인스턴스 A)
                                 └── Log Stream: i-0def456 (인스턴스 B)
                                 ↑
                                 브라우저·CLI에서 통합 조회·검색·알람 설정
</code></pre><hr>
<h3 id="amazon-rds"><strong>Amazon RDS</strong></h3>
<h3 id="01-amazon-rds-핵심-개념"><strong>01. Amazon RDS 핵심 개념</strong></h3>
<h3 id="개념-1"><strong>개념</strong></h3>
<ul>
<li>AWS가 제공하는 완전 관리형(Managed) 관계형 데이터베이스 서비스</li>
<li>DB 서버 프로비저닝, OS 패치, DB 엔진 설치, 백업, 모니터링 등 운영 부담을 AWS가 대신 처리</li>
</ul>
<pre><code>[RDS 핵심 구성 요소]

  ┌─────────────────────────────────────────────────┐
  │                  Amazon RDS                 │
  │                                             │
  │  ┌──────────────┐      ┌──────────────────────┐ │
  │  │ DB Instance │      │   Storage (EBS)    │ │
  │  │             │      │                    │ │
  │  │ - DB 엔진    │ ───► │ gp3 / io2 / 자동확장│ │
  │  │ - vCPU/RAM  │      │                    │ │
  │  └──────────────┘      └──────────────────────┘ │
  │                                              │
  │  ┌──────────────┐      ┌──────────────────────┐ │
  │  │  DB Subnet  │      │   Parameter Group  │ │
  │  │  Group      │      │   (DB 엔진 설정값)   │ │
  │  │  (VPC 배치)  │      │                    │ │
  │  └──────────────┘      └──────────────────────┘ │
  └──────────────────────────────────────────────────┘</code></pre><br>

<h4 id="주요-용어-정리">주요 용어 정리</h4>
<table>
<thead>
<tr>
<th>용어</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>DB Instance</strong></td>
<td>RDS의 기본 단위. DB 엔진이 실행되는 컴퓨팅+스토리지 환경</td>
</tr>
<tr>
<td><strong>DB Engine</strong></td>
<td>DB Instance에서 실행되는 RDBMS 소프트웨어 (MySQL, PostgreSQL 등)</td>
</tr>
<tr>
<td><strong>DB Subnet Group</strong></td>
<td>RDS가 배치될 VPC 내 서브넷 집합. 최소 2개 AZ의 서브넷 필요</td>
</tr>
<tr>
<td><strong>Parameter Group</strong></td>
<td>DB 엔진의 동작 설정값 모음 (max_connections, innodb_buffer_pool_size 등)</td>
</tr>
<tr>
<td><strong>Option Group</strong></td>
<td>일부 엔진(Oracle, SQL Server)의 추가 기능 활성화 설정</td>
</tr>
<tr>
<td><strong>Endpoint</strong></td>
<td>애플리케이션이 DB에 접속할 때 사용하는 DNS 주소</td>
</tr>
<tr>
<td><strong>Multi-AZ</strong></td>
<td>2개 AZ에 Primary + Standby를 두어 자동 Failover를 제공하는 HA 구성</td>
</tr>
<tr>
<td><strong>Read Replica</strong></td>
<td>읽기 전용 복제본. 읽기 트래픽 분산 목적 (HA 목적 아님)</td>
</tr>
<tr>
<td><strong>Snapshot</strong></td>
<td>DB의 특정 시점 상태를 S3에 저장한 백업본</td>
</tr>
<tr>
<td><strong>PITR</strong></td>
<td>Point-In-Time Recovery. 자동 백업을 이용해 특정 시각으로 복구</td>
</tr>
<tr>
<td><strong>Performance Insights</strong></td>
<td>쿼리 수준의 DB 부하를 시각화하는 모니터링 도구</td>
</tr>
</tbody></table>
<br>

<h3 id="02-rds-스토리지"><strong>02. RDS 스토리지</strong></h3>
<h3 id="개념-2"><strong>개념</strong></h3>
<ul>
<li>RDS는 EBS(Elastic Block Store) 기반 스토리지를 사용</li>
<li>CPU·메모리(DB Instance)와 스토리지를 독립적으로 확장 가능</li>
</ul>
<br>

<h4 id="스토리지-자동-확장-storage-auto-scaling"><strong>스토리지 자동 확장 (Storage Auto Scaling)</strong></h4>
<ul>
<li>남은 용량이 임계값 이하로 떨어지면 자동으로 스토리지 용량을 증가시키는 기능</li>
<li>활성화 시 최대 허용 용량(Maximum Storage Threshold) 을 반드시 설정</li>
</ul>
<pre><code>[Storage Auto Scaling 동작 원리]

  초기 설정: 100 GB gp3
  최대 허용: 500 GB

  100 GB ──────────────► 자동 확장 조건 충족 ──────────────► 125 GB
  (사용량 90% 초과,                                          (자동 증가)
   5분 이상 지속)
                          ↓ 조건
                   ① 여유 공간이 할당 용량의 10% 미만
                   ② 해당 상태가 5분 이상 지속
                   ③ 마지막 확장 후 6시간 이상 경과</code></pre><br>

<h4 id="주요-용어-정리-1">주요 용어 정리</h4>
<table>
<thead>
<tr>
<th>용어</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>IOPS</strong></td>
<td>Input/Output Operations Per Second. 초당 읽기/쓰기 작업 수. 높을수록 빠름</td>
</tr>
<tr>
<td><strong>처리량 (Throughput)</strong></td>
<td>초당 전송되는 데이터 양 (MB/s). 대용량 순차 읽기에 영향</td>
</tr>
<tr>
<td><strong>프로비저닝</strong></td>
<td>필요한 용량·성능을 미리 설정해두는 것. io1/io2에서 IOPS를 직접 지정</td>
</tr>
<tr>
<td><strong>버스트 (Burst)</strong></td>
<td>gp2에서 평소 기본 IOPS보다 높은 성능을 일시적으로 제공하는 기능</td>
</tr>
<tr>
<td><strong>Storage Auto Scaling</strong></td>
<td>여유 공간 부족 시 자동으로 스토리지 용량을 증가시키는 기능</td>
</tr>
<tr>
<td><strong>Maximum Storage Threshold</strong></td>
<td>Auto Scaling 시 증가 가능한 최대 스토리지 용량 한도</td>
</tr>
</tbody></table>
<h4 id="스토리지-타입-선택-기준">스토리지 타입 선택 기준</h4>
<table>
<thead>
<tr>
<th>상황</th>
<th>권장 타입</th>
</tr>
</thead>
<tbody><tr>
<td>개발·테스트 환경, 소규모 서비스</td>
<td>gp3 (기본)</td>
</tr>
<tr>
<td>일반 프로덕션 웹 서비스</td>
<td>gp3 (IOPS 조정)</td>
</tr>
<tr>
<td>고성능 OLTP, 금융 시스템</td>
<td>io2</td>
</tr>
<tr>
<td>분석·데이터 웨어하우스 (순차 읽기 많음)</td>
<td>io2 또는 gp3 (처리량 조정)</td>
</tr>
</tbody></table>
<br>

<h3 id="03-multi-az-vs-read-replica"><strong>03. Multi-AZ vs Read Replica</strong></h3>
<p>RDS의 두 가지 핵심 복제 방식</p>
<pre><code>[Multi-AZ — 고가용성(HA) 목적]

  AZ-a (ap-northeast-2a)        AZ-c (ap-northeast-2c)
  ┌──────────────────┐           ┌──────────────────┐
  │  Primary DB    │ ─ 동기     │  Standby DB     │
  │  (읽기·쓰기 모두)│  복제 ──►  │  (대기 상태)      │
  │                │           │  (트래픽 없음)    │
  └──────────────────┘           └──────────────────┘
           │                              │
           └──────── 하나의 Endpoint ─────┘
                  (DNS: mydb.xxx.rds.amazonaws.com)

  Primary 장애 발생 시:
  → DNS가 Standby를 가리키도록 자동 전환 (Failover)
  → RTO: 약 60~120초
  → 애플리케이션 코드 변경 불필요 (같은 Endpoint 사용)</code></pre><pre><code>[Read Replica — 읽기 분산(Scale-Out) 목적]

  Primary DB           Read Replica-1       Read Replica-2
  ┌────────────┐           ┌────────────┐       ┌────────────┐
  │ 읽기+쓰기  │ ─비동기─►   │ 읽기 전용  │  ···  │ 읽기 전용  │
  │           │  복제      │           │       │           │
  └────────────┘           └────────────┘       └────────────┘
  (쓰기 엔드포인트)        (읽기 엔드포인트)    (읽기 엔드포인트)

  ※ 최대 5개 생성 가능 (Aurora는 15개)
  ※ 동일 리전 또는 다른 리전(Cross-Region Read Replica) 가능
  ※ Replica는 Primary 장애 시 자동 Failover 대상이 아님
     → 수동으로 Promote(승격) 해야 Primary가 됨</code></pre><br>

<h4 id="multi-az-vs-read-replica-핵심-비교">Multi-AZ vs Read Replica 핵심 비교</h4>
<table>
<thead>
<tr>
<th>항목</th>
<th>Multi-AZ</th>
<th>Read Replica</th>
</tr>
</thead>
<tbody><tr>
<td><strong>목적</strong></td>
<td>고가용성 (HA), 자동 Failover</td>
<td>읽기 성능 향상 (Scale-Out)</td>
</tr>
<tr>
<td><strong>복제 방식</strong></td>
<td>동기(Synchronous)</td>
<td>비동기(Asynchronous)</td>
</tr>
<tr>
<td><strong>Standby/Replica 트래픽</strong></td>
<td>없음 (대기만)</td>
<td>읽기 트래픽 처리 가능</td>
</tr>
<tr>
<td><strong>Endpoint</strong></td>
<td>Primary와 동일 (1개)</td>
<td>별도 Endpoint (각각 존재)</td>
</tr>
<tr>
<td><strong>Failover</strong></td>
<td>자동 (DNS 전환)</td>
<td>수동 Promote 필요</td>
</tr>
<tr>
<td><strong>Failover RTO</strong></td>
<td>약 60~120초</td>
<td>N/A (자동 아님)</td>
</tr>
<tr>
<td><strong>데이터 일관성</strong></td>
<td>항상 동일 (동기 복제)</td>
<td>약간의 Lag 존재 가능</td>
</tr>
<tr>
<td><strong>Cross-Region</strong></td>
<td>불가</td>
<td>가능</td>
</tr>
<tr>
<td><strong>비용</strong></td>
<td>Primary 대비 약 2배</td>
<td>Replica 수만큼 추가 과금</td>
</tr>
</tbody></table>
<br>

<h3 id="04-rds-백업--복구"><strong>04. RDS 백업 &amp; 복구</strong></h3>
<h3 id="개념-3"><strong>개념</strong></h3>
<ul>
<li>RDS는 두 가지 방식의 백업을 제공</li>
<li><strong>자동 백업(Automated Backup)</strong> 과 <strong>수동 스냅샷(Manual Snapshot)</strong> — 목적과 보존 기간이 다름</li>
</ul>
<pre><code>[RDS 백업 방식 비교]

  ┌─────────────────────────────────────────────────────────────┐
  │  자동 백업 (Automated Backup)                           │
  │                                                       │
  │  - RDS 생성 시 기본 활성화                               │
  │  - 매일 지정된 백업 윈도우(Backup Window)에 전체 스냅샷 생성│
  │  - 트랜잭션 로그(Transaction Log)를 5분 간격으로 S3 저장   │
  │  - 보존 기간: 1~35일 설정 (기본 7일)                      │
  │  - DB Instance 삭제 시 자동 삭제 (설정에 따라 보존 가능)    │
  │  - PITR(특정 시점 복구) 지원                              │
  ├─────────────────────────────────────────────────────────────┤
  │  수동 스냅샷 (Manual Snapshot)                           │
  │                                                        │
  │  - 사용자가 직접 생성 (콘솔, CLI, API)                    │
  │  - 보존 기간 제한 없음 (명시적으로 삭제할 때까지 유지)        │ 
  │  - DB Instance 삭제해도 스냅샷은 유지됨                    │
  │  - 다른 AWS 계정·리전으로 공유·복사 가능                    │
  │  - PITR 불가 (특정 시점이 아닌 스냅샷 시점으로만 복구)        │
  └──────────────────────────────────────────────────────────────┘</code></pre><br>

<h4 id="pitr-point-in-time-recovery"><strong>PITR (Point-In-Time Recovery)</strong></h4>
<p>자동 백업 + 트랜잭션 로그를 조합해 <strong>특정 시각</strong>으로 DB를 복구하는 기능 (원하는 시점에 복구)</p>
<br>

<h4 id="백업·복구-전략-비교">백업·복구 전략 비교</h4>
<table>
<thead>
<tr>
<th>시나리오</th>
<th>사용 방법</th>
<th>특징</th>
</tr>
</thead>
<tbody><tr>
<td>실수로 데이터 삭제 (1시간 전)</td>
<td>PITR</td>
<td>분 단위 복구. 새 Instance 생성</td>
</tr>
<tr>
<td>주요 배포 전 안전망</td>
<td>Manual Snapshot</td>
<td>배포 직전 수동 생성. 삭제 전까지 유지</td>
</tr>
<tr>
<td>장기 보존 (법적 요건)</td>
<td>Manual Snapshot</td>
<td>기간 제한 없이 보관</td>
</tr>
<tr>
<td>다른 리전으로 복사</td>
<td>Snapshot Copy</td>
<td>Cross-Region 복사 후 복구</td>
</tr>
<tr>
<td>DB 삭제 후 복구</td>
<td>Final Snapshot</td>
<td>삭제 시 Final Snapshot 생성 옵션 활성화 필수</td>
</tr>
</tbody></table>
<br>

<h3 id="05-rds-보안"><strong>05. RDS 보안</strong></h3>
<p>RDS 보안은 <strong>네트워크 격리 → 접근 제어 → 자격증명 관리 → 암호화</strong> 4개 레이어로 구성</p>
<pre><code>[RDS 보안 4개 레이어]

  인터넷
    │
    ▼
  ┌─────────────────────────────────────────────────┐
  │  Layer 1. 네트워크 격리                       │
  │  VPC Private Subnet 배치                     │
  │  → 인터넷 게이트웨이(IGW)와 직접 연결 없음       │
  └─────────────────────┬───────────────────────────┘
                        │ (VPC 내부 트래픽만)
                        ▼
  ┌─────────────────────────────────────────────────┐
  │  Layer 2. 접근 제어                          │
  │  Security Group → 허용된 IP/포트만 통과       │
  │  예) EC2 Security Group → RDS 3306 포트만 허용│
  └─────────────────────┬───────────────────────────┘
                        │
                        ▼
  ┌──────────────────────────────────────────────────┐
  │  Layer 3. 자격증명 관리                        │
  │ Secrets Manager → DB 비밀번호 중앙 관리·자동 교체│
  │  IAM DB Authentication → IAM 토큰으로 DB 접속  │
  └─────────────────────┬────────────────────────────┘
                        │
                        ▼
  ┌─────────────────────────────────────────────────┐
  │  Layer 4. 암호화                             │
  │  저장 데이터: KMS 키로 EBS 볼륨 암호화          │
  │  전송 데이터: SSL/TLS로 전송 구간 암호화        │
  └─────────────────────────────────────────────────┘</code></pre><br>

<h3 id="06-rds-모니터링"><strong>06. RDS 모니터링</strong></h3>
<ul>
<li>RDS 모니터링은 3가지 도구를 계층적으로 활용</li>
<li>각 도구가 보여주는 <strong>관찰 수준(레벨)</strong> 이 다름</li>
</ul>
<br>

<h3 id="db-파라미터-그룹">DB 파라미터 그룹</h3>
<p><strong>수정 가능한 파라미터 그룹</strong></p>
<ul>
<li>character_set_client</li>
<li>character_set_server</li>
<li>collation_server</li>
<li>time_zone</li>
<li>long_query_time</li>
<li>slow_query_log</li>
<li>max_connections</li>
<li>require_secure_transport</li>
</ul>
<br>

<h3 id="dynamodb"><strong>DynamoDB</strong></h3>
<h3 id="01-dynamodb-핵심-개념--nosql"><strong>01. DynamoDB 핵심 개념 — NoSQL</strong></h3>
<ul>
<li>AWS의 완전 관리형(Serverless) NoSQL 데이터베이스</li>
<li>키-값(Key-Value) 및 문서(Document) 데이터 모델 지원</li>
</ul>
<pre><code>[RDB vs NoSQL — 언제 무엇을 선택하는가]

  RDB (RDS MySQL/PostgreSQL)          DynamoDB (NoSQL)
  ─────────────────────────────────────────────────────
  정형화된 스키마 (테이블·컬럼 고정)   스키마 유연 (항목마다 속성 다를 수 있음)
  JOIN 지원 → 복잡한 관계형 쿼리       JOIN 없음 → 단순 조회 최적화
  ACID 트랜잭션 (강한 일관성)          기본 최종 일관성 (강한 일관성 옵션 제공)
  수직 확장 (Scale-Up) 중심            수평 확장 (Scale-Out) 자동 무제한
  예측 가능한 쿼리 패턴에 최적          고트래픽·예측 불가 워크로드에 최적
  예) 주문·결제·회원 관리              예) 세션 저장·게임 리더보드·IoT·장바구니</code></pre><br>

<h3 id="02-dynamodb-핵심-개념--key-설계"><strong>02. DynamoDB 핵심 개념 — Key 설계</strong></h3>
<ul>
<li>DynamoDB 성능의 핵심은 <strong>Key 설계</strong></li>
<li>데이터가 어떤 파티션에 분산 저장되는지, 어떤 순서로 정렬되는지 결정</li>
</ul>
<pre><code>[파티션 키 (Partition Key, PK) 동작 원리]

  Partition Key 값 → 해시 함수 → 저장 파티션 결정

  PK: &quot;user#1001&quot; → 해시 → 파티션 A
  PK: &quot;user#1002&quot; → 해시 → 파티션 C
  PK: &quot;user#1003&quot; → 해시 → 파티션 B

  ※ PK 값이 다양할수록 데이터가 파티션에 골고루 분산됨
  ※ PK 값이 편중되면 특정 파티션에 트래픽 집중 → 핫 파티션(Hot Partition) 문제 발생</code></pre><br>

<h4 id="키-설계-원칙">키 설계 원칙</h4>
<pre><code>[DynamoDB 키 설계 철학]

  RDB 방식:                    DynamoDB 방식:
  ────────────────             ──────────────────────
  1. 엔티티 도출                1. 쿼리 패턴 먼저 나열
  2. 정규화                    2. Access Pattern 문서화
  3. 관계 정의                 3. PK/SK를 쿼리에 맞춰 설계
  4. 쿼리 작성                 4. 하나의 테이블에 모두 담기
     (쿼리는 나중에)              (Single-Table Design)

  ※ RDB처럼 &quot;먼저 만들고 나중에 쿼리한다&quot;가 불가능함
  ※ JOIN이 없으므로 조회에 필요한 모든 데이터를 한 Item에 포함하거나
    같은 PK로 묶어 한 번의 Query로 가져오도록 설계함
</code></pre><br>

<h3 id="03-dynamodb-용량-모드"><strong>03. DynamoDB 용량 모드</strong></h3>
<p>DynamoDB 읽기·쓰기 용량을 관리하는 두 가지 모드</p>
<pre><code>[On-Demand Mode — 트래픽 자동 대응]

  요청 발생 → DynamoDB가 자동으로 용량 처리
  → 사용한 만큼만 과금 (RCU/WCU 단위)
  → 트래픽 급증 시 자동 확장 (Throttle 없음)
  → 예측 불가 트래픽·신규 서비스에 적합

  비용: 읽기 $0.25/1M RRU, 쓰기 $1.25/1M WRU (ap-northeast-2)

[Provisioned Mode — 용량 사전 설정]

  읽기/쓰기 용량(RCU, WCU)을 직접 설정
  → 설정된 용량 초과 시 Throttle 발생 (ProvisionedThroughputExceededException)
  → Auto Scaling 설정으로 자동 조정 가능
  → 예측 가능한 안정적 트래픽에 적합 (Reserved Capacity로 비용 절감 가능)</code></pre><br>

<h4 id="rcu--wcu-계산">RCU / WCU 계산</h4>
<table>
<thead>
<tr>
<th>개념</th>
<th>정의</th>
</tr>
</thead>
<tbody><tr>
<td><strong>RCU (Read Capacity Unit)</strong></td>
<td>1초에 최대 4KB 항목을 강한 일관성 읽기 1회. 최종 일관성은 0.5 RCU</td>
</tr>
<tr>
<td><strong>WCU (Write Capacity Unit)</strong></td>
<td>1초에 최대 1KB 항목을 쓰기 1회</td>
</tr>
</tbody></table>
<pre><code>[RCU/WCU 계산 예시]

  시나리오: 항목 크기 8KB, 초당 100회 읽기 (강한 일관성)

  RCU = ceil(8KB / 4KB) × 100회 = 2 × 100 = 200 RCU 필요

  시나리오: 항목 크기 3KB, 초당 50회 쓰기

  WCU = ceil(3KB / 1KB) × 50회 = 3 × 50 = 150 WCU 필요</code></pre><br>

<h3 id="elasticache"><strong>ElastiCache</strong></h3>
<h3 id="01-캐시cache와-elasticache-개념"><strong>01. 캐시(Cache)와 ElastiCache 개념</strong></h3>
<p><strong>캐시(Cache)</strong></p>
<ul>
<li>자주 사용되는 데이터를 빠른 저장소(메모리)에 임시 저장해 DB 부하를 줄이고 응답 속도를 높이는 기술</li>
</ul>
<br>

<p><strong>Amazon ElastiCache</strong></p>
<ul>
<li>Valkey / Redis OSS / Memcached 엔진을 완전 관리형으로 제공하는 AWS 인메모리 캐시 서비스</li>
</ul>
<pre><code>[캐시 없는 구조 — DB 부하 문제]

  사용자 100명이 동시에 &quot;인기 상품 목록&quot; 요청
        │
        ▼
  애플리케이션 서버
        │ 100번 동일 쿼리 실행
        ▼
  RDS / DynamoDB ← 과부하 발생, 응답 느림, 비용 증가
  (디스크 기반 → 수십 ms 응답)

---
[캐시 적용 후 — DB 부하 해소]

  사용자 100명이 동시에 &quot;인기 상품 목록&quot; 요청
        │
        ▼
  애플리케이션 서버
        │
        ├── ElastiCache 확인 (캐시 HIT) → 즉시 반환 (&lt; 1ms)
        │                                  99번의 DB 쿼리 차단됨
        └── ElastiCache 없음 (캐시 MISS) → DB 조회 → 캐시 저장 → 반환
              (최초 1번만 DB 조회)</code></pre><br>

<h3 id="02-elasticache-운영-모드--serverless-vs-self-designed"><strong>02. ElastiCache 운영 모드 — Serverless vs Self-Designed</strong></h3>
<ul>
<li>ElastiCache는 두 가지 운영 모드를 지원</li>
<li>트래픽 예측 가능성과 운영 복잡도 허용 수준에 따라 선택</li>
</ul>
<pre><code>[Serverless Cache — 자동 확장, 운영 부담 제로]

  사용자 설정: 캐시 이름 입력 + 엔진 선택
                      │
                      ▼
  ElastiCache Serverless가 자동으로:
  ├── 용량 자동 조정 (메모리·CPU·네트워크)
  ├── Multi-AZ 자동 구성
  ├── 패치·업그레이드 자동 적용
  └── 단일 Endpoint 제공

  과금: 저장 데이터(GB-hour) + 처리 요청(ECPU)
  Valkey Serverless 최소: $6/월 (100MB부터)
  → 개발·스타트업·예측 불가 트래픽에 최적

[Self-Designed Cluster — 세밀한 제어 가능]

  사용자가 직접:
  ├── 노드 타입 선택 (cache.r7g.large 등)
  ├── 샤드(Shard) 수 설정
  ├── Replica 수 설정
  └── AZ 배치 설정

  과금: 노드 시간당 과금
  → 예측 가능한 고성능 워크로드, 비용 최적화 가능</code></pre><table>
<thead>
<tr>
<th>항목</th>
<th>Serverless</th>
<th>Self-Designed Cluster</th>
</tr>
</thead>
<tbody><tr>
<td>설정 복잡도</td>
<td>매우 낮음 (이름만 설정)</td>
<td>높음 (노드·샤드·Replica 설정)</td>
</tr>
<tr>
<td>용량 관리</td>
<td>자동</td>
<td>수동 (Auto Scaling 별도 설정)</td>
</tr>
<tr>
<td>최소 비용</td>
<td>$6/월 (Valkey)</td>
<td>노드 시간당</td>
</tr>
<tr>
<td>트래픽 대응</td>
<td>즉각 자동 확장</td>
<td>Auto Scaling (수분 지연)</td>
</tr>
<tr>
<td>권장 대상</td>
<td>신규 서비스, 가변 트래픽</td>
<td>대규모·예측 가능 워크로드</td>
</tr>
</tbody></table>
<br>

<h3 id="03-캐싱-전략"><strong>03. 캐싱 전략</strong></h3>
<p>ElastiCache 적용 시 어떤 패턴으로 캐시를 읽고 쓸지 결정하는 전략</p>
<pre><code>[Cache-Aside (Lazy Loading) — 가장 일반적]

  읽기 요청
      │
      ▼
  ElastiCache 조회
  ├── HIT → 데이터 반환 (DB 조회 없음)
  └── MISS
          │
          ▼
       DB 조회
          │
          ▼
       ElastiCache에 저장 (TTL 설정)
          │
          ▼
       데이터 반환

  장점: DB 부하 최소화, 필요한 데이터만 캐시
  단점: 최초 MISS 시 응답 지연, 캐시-DB 데이터 불일치 가능
  적용: 상품 목록, 사용자 프로필, API 응답 캐시

[Write-Through — 쓰기 시 즉시 캐시 갱신]

  쓰기 요청
      │
      ├──► DB 저장
      └──► ElastiCache 저장 (동시에)

  장점: 캐시 항상 최신 상태 유지
  단점: 쓰기 지연 증가, 캐시 공간 낭비 가능 (읽히지 않는 데이터도 캐시)
  적용: 읽기 빈도 높고 데이터 신선도 중요한 경우

[Write-Behind (Write-Back) — 캐시 먼저, DB는 나중에]

  쓰기 요청
      │
      ▼
  ElastiCache 저장 (즉시 응답)
      │
      ▼ (비동기, 나중에)
  DB 저장

  장점: 매우 빠른 쓰기 응답
  단점: 캐시 장애 시 데이터 손실 위험
  적용: 게임 점수, 좋아요 수 등 손실 허용 가능한 데이터</code></pre><br>

<h4 id="ttl-time-to-live-설정">TTL (Time To Live) 설정</h4>
<pre><code>[TTL 설계 원칙]

  TTL 너무 짧음:
  → 캐시 MISS 빈번 → DB 부하 증가 → 캐시 의미 없음

  TTL 너무 길음:
  → 오래된 데이터 제공 → 사용자에게 잘못된 정보 노출

  적정 TTL 기준:
  ├── 거의 변하지 않는 데이터 (카테고리, 설정): 1시간~24시간
  ├── 자주 조회되지만 가끔 변하는 데이터 (상품 상세): 5분~1시간
  ├── 실시간성 중요한 데이터 (재고, 가격): 30초~5분
  └── 세션 데이터: 세션 만료 시간과 동일하게</code></pre><br>

<h3 id="04-elasticache-접속-경로">04. ElastiCache 접속 경로</h3>
<pre><code>[왜 로컬 PC에서 직접 접속이 안 되는가]

  ElastiCache(Valkey/Redis)는 DynamoDB와 달리:
  ├── VPC 내부 Private 리소스 (Public Endpoint 없음)
  ├── 6379 포트는 VPC 내부에서만 열림
  └── 인터넷에서 직접 접근 불가 (AWS 보안 설계 원칙)

  따라서 접속 경로는 반드시 VPC 내부 EC2를 경유함:

  ┌────────────────────────────────────────────┐
  │  로컬 PC (Termius / Redis Insight)      │
  └──────────────────┬─────────────────────────┘
                     │ SSH (22) + Port Forwarding (6379)
                     ▼
  ┌────────────────────────────────────────────┐
  │  EC2 Bastion (mylab-bastion)           │
  │  └── valkey-cli (CLI 실습)              │
  └──────────────────┬─────────────────────────┘
                     │ TLS 6379 (VPC 내부 Private 통신)
                     ▼
  ┌────────────────────────────────────────────┐
  │  ElastiCache Valkey Serverless         │
  │  mylab-valkey-cache.serverless.apn2..  │
  └────────────────────────────────────────────┘</code></pre><hr>
<h3 id="01-rds"><strong>01. RDS</strong></h3>
<ul>
<li>Amazon RDS(Relational Database Service)는 AWS가 데이터베이스 인프라의 운영·관리를 대신 처리하는 완전 관리형(Managed) 관계형 데이터베이스 서비스</li>
</ul>
<br>

<h3 id="02-엔진">02. 엔진</h3>
<h4 id="주요-엔진-비교">주요 엔진 비교</h4>
<table>
<thead>
<tr>
<th>엔진</th>
<th>2026 권장 버전</th>
<th>주요 특징</th>
<th>선택 시나리오</th>
</tr>
</thead>
<tbody><tr>
<td>MySQL</td>
<td>8.4 (LTS)</td>
<td>가장 보편적, Spring Boot JPA 궁합 우수</td>
<td>일반 웹 서비스·API 백엔드</td>
</tr>
<tr>
<td>PostgreSQL</td>
<td>17</td>
<td>표준 SQL 준수, JSON 지원 강력</td>
<td>복잡한 쿼리·데이터 분석</td>
</tr>
<tr>
<td>Amazon Aurora MySQL</td>
<td>3.x (MySQL 8.0 호환)</td>
<td>MySQL 대비 최대 5배 성능, 자동 스토리지</td>
<td>고트래픽·엔터프라이즈</td>
</tr>
<tr>
<td>Amazon Aurora PostgreSQL</td>
<td>16 호환</td>
<td>PostgreSQL 호환, 서버리스 v2 지원</td>
<td>가변 트래픽·서버리스 아키텍처</td>
</tr>
<tr>
<td>MariaDB</td>
<td>10.11 (LTS)</td>
<td>MySQL 포크, 일부 추가 기능</td>
<td>MySQL 대체가 필요한 레거시 환경</td>
</tr>
<tr>
<td>Oracle / SQL Server</td>
<td>—</td>
<td>엔터프라이즈 라이선스 필요</td>
<td>기존 온프레미스 이전(Lift &amp; Shift)</td>
</tr>
</tbody></table>
<br>

<h3 id="03-multi-az--고가용성-구성-3가지-방식"><strong>03. Multi-AZ — 고가용성 구성 3가지 방식</strong></h3>
<p>RDS를 여러 가용 영역(AZ)에 걸쳐 배포하여 AZ 장애 시 자동 Failover를 제공하는 고가용성 구성</p>
<pre><code>[Single-AZ — SPOF 존재]

  ap-northeast-2a
  ┌─────────────┐
  │ RDS Primary│ ← 이 AZ 장애 시 전체 서비스 중단
  └─────────────┘

---
[Multi-AZ Instance — 전통적 Active-Standby]

  ap-northeast-2a               ap-northeast-2c
  ┌─────────────┐  동기 복제   ┌──────────────┐
  │  Primary   │ ──────────►  │  Standby    │
  │ (읽기/쓰기) │              │  (대기 전용) │
  └─────────────┘              └──────────────┘
        │
        │ AZ-a 장애 발생 시
        ▼
  DNS Failover (60~120초)
        │
        ▼
  Standby → Primary로 자동 승격

---
[Multi-AZ Cluster — 더 빠른 Failover]

  ap-northeast-2a     ap-northeast-2b     ap-northeast-2c
  ┌───────────┐       ┌───────────┐       ┌───────────┐
  │  Writer  │──────►│  Reader 1 │       │  Reader 2 │
  │(읽기/쓰기)│       │ (읽기 가능)│       │ (읽기 가능)│
  └───────────┘       └───────────┘       └───────────┘
  Writer 장애 시 → Reader 중 하나가 Writer로 승격 (수 초 내)</code></pre><br>

<h4 id="multi-az-방식-비교">Multi-AZ 방식 비교</h4>
<table>
<thead>
<tr>
<th>항목</th>
<th>Single-AZ</th>
<th>Multi-AZ Instance</th>
<th>Multi-AZ Cluster</th>
</tr>
</thead>
<tbody><tr>
<td>AZ 수</td>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
<tr>
<td>Standby 읽기 가능</td>
<td>—</td>
<td>불가</td>
<td>가능 (읽기 트래픽 분산)</td>
</tr>
<tr>
<td>Failover 시간</td>
<td>해당 없음</td>
<td>60~120초</td>
<td>수 초 (더 빠름)</td>
</tr>
<tr>
<td>비용</td>
<td>가장 저렴</td>
<td>약 2배</td>
<td>약 2배 이상</td>
</tr>
<tr>
<td>권장 환경</td>
<td>개발·테스트</td>
<td>일반 프로덕션</td>
<td>고가용성 필수 프로덕션</td>
</tr>
</tbody></table>
<br>

<h3 id="04-백업--복구--bluegreen-deployment"><strong>04. 백업 &amp; 복구 — Blue/Green Deployment</strong></h3>
<p><strong>Blue/Green Deployment</strong></p>
<pre><code>[Blue/Green Deployment — 무중단 메이저 버전 업그레이드]

  Blue 환경 (현재 프로덕션)        Green 환경 (신규 버전)
  ┌──────────────────┐             ┌──────────────────┐
  │ RDS MySQL 8.0.x │  ──복제──►  │ RDS MySQL 8.4    │
  │ (현재 서비스 중)  │            │ (검증 단계)       │
  └──────────────────┘             └──────────────────┘
           │                                │
           │  검증 완료 후 Switchover 실행    │
           ▼                                ▼
  Blue → Standby 전환            Green → 프로덕션 전환
  (문제 발생 시 수 초 내 롤백 가능)</code></pre><br>

<h3 id="spring-boot에서-단일-rds-연결"><strong>Spring Boot에서 단일 RDS 연결</strong></h3>
<h3 id="01-개념"><strong>01. 개념</strong></h3>
<ul>
<li>구축한 RDS에 Spring Boot 애플리케이션이 접속 및 연결 성공 검증</li>
<li>RDS가 Private Subnet에 있기 때문에 로컬 PC에서 직접 접근 불가</li>
</ul>
<pre><code>[로컬 PC Spring Boot]
        │
        ↓ localhost:3307
[SSH Tunnel Process]
        │
        ↓ 암호화된 SSH 세션
[Bastion EC2] (Public Subnet)
        │
        ↓ VPC 내부 통신 3306
[RDS] (Private Subnet)</code></pre><br>

<p><strong>실습</strong> </p>
<p>터널링을 이용하여 RDS에 접속하고 Postman을 이용하여 DB 테스트</p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/f64bc50c-48b1-4652-b45c-8eb30b95e7ea/image.png" alt=""></p>
<br>

<h3 id="read-replica-생성-및-콘솔-확인"><strong>Read Replica 생성 및 콘솔 확인</strong></h3>
<h3 id="01-개념-1"><strong>01. 개념</strong></h3>
<ul>
<li>읽기 트래픽이 증가하면 Primary에 부하가 집중되어 전체 성능이 저하</li>
<li>Read Replica는 Primary의 복제본으로, <strong>SELECT 쿼리를 분산 처리</strong>하여 Primary의 쓰기 성능을 보호</li>
</ul>
<pre><code>[기존 구조]
                   ┌──────────────────┐
   App ─ SELECT/ ─►│ Primary (Writer)│
          INSERT   └──────────────────┘

[Read Replica 추가 후]
                   ┌──────────────────┐
   App ─ INSERT ──►│ Primary (Writer)│──┐
                   └──────────────────┘  │ 비동기 binlog 복제
                                       ▼
                   ┌──────────────────┐
   App ─ SELECT ──►│ Replica (Reader)│
                   └──────────────────┘</code></pre><table>
<thead>
<tr>
<th>항목</th>
<th>Primary</th>
<th>Read Replica</th>
</tr>
</thead>
<tbody><tr>
<td>쓰기</td>
<td>O</td>
<td>X (읽기 전용)</td>
</tr>
<tr>
<td>읽기</td>
<td>O</td>
<td>O</td>
</tr>
<tr>
<td>복제 방식</td>
<td>—</td>
<td>MySQL binlog 비동기 복제</td>
</tr>
<tr>
<td>엔드포인트</td>
<td>별도</td>
<td>별도 (분리 사용 가능)</td>
</tr>
<tr>
<td>자격증명</td>
<td>동일</td>
<td>Primary와 공유</td>
</tr>
<tr>
<td>Failover</td>
<td>Multi-AZ Standby로</td>
<td>자동 승격 안 됨 (수동 Promote)</td>
</tr>
<tr>
<td>과금</td>
<td>Multi-AZ이면 2배</td>
<td>인스턴스 개수당 과금</td>
</tr>
</tbody></table>
<br>

<h3 id="spring-boot-writerreader-datasource-분리"><strong>Spring Boot Writer/Reader DataSource 분리</strong></h3>
<h3 id="01-개념-2"><strong>01. 개념</strong></h3>
<ul>
<li>단일 DataSource로 RDS 연결에 성공, Read Replica 복제 동작을 검증 완료</li>
<li><strong>쓰기는 Writer, 읽기는 Reader</strong>로 분리</li>
</ul>
<pre><code>[Spring Boot]
     │
     ├─ @Transactional              → WriterDataSource → Primary
     │  (INSERT/UPDATE/DELETE)
     │
     └─ @Transactional(readOnly=true) → ReaderDataSource → Read Replica
        (SELECT)</code></pre><br>

<h3 id="02-hikaricp-개념-및-핵심-파라미터"><strong>02. HikariCP 개념 및 핵심 파라미터</strong></h3>
<h3 id="hikaricp란"><strong>HikariCP란</strong></h3>
<ul>
<li>Spring Boot 2.x 이상의 기본 JDBC 커넥션 풀. 커넥션을 미리 생성해 풀에 보관하고, 요청 시 대여·반납하는 방식으로 <strong>매 요청마다 TCP handshake + MySQL 인증 비용(50~200ms)을 제거</strong></li>
</ul>
<pre><code>[커넥션 풀 없음]
  HTTP 요청 → 새 커넥션 생성(50~200ms) → 쿼리 → 연결 해제
             ↑ 매번 오버헤드

[HikariCP 풀링]
  App 시작 시 → 커넥션 N개 미리 생성 → Pool 보관
  HTTP 요청 → Pool에서 대여(수 ms) → 쿼리 → Pool 반납
             ↑ 오버헤드 없음</code></pre><br>

<h4 id="핵심-파라미터">핵심 파라미터</h4>
<table>
<thead>
<tr>
<th>파라미터</th>
<th>기본값</th>
<th>역할</th>
</tr>
</thead>
<tbody><tr>
<td><code>maximumPoolSize</code></td>
<td>10</td>
<td>풀 최대 커넥션 수. 초과 요청은 <code>connectionTimeout</code>만큼 대기</td>
</tr>
<tr>
<td><code>minimumIdle</code></td>
<td>max와 동일</td>
<td>최소 유휴 커넥션. 0이면 탄력적 풀</td>
</tr>
<tr>
<td><code>connectionTimeout</code></td>
<td>30,000ms</td>
<td>커넥션 대여 최대 대기. 초과 시 <code>SQLTimeoutException</code></td>
</tr>
<tr>
<td><code>idleTimeout</code></td>
<td>600,000ms</td>
<td>유휴 커넥션 제거 시점. <code>minimumIdle</code> 미만으로 줄이지 않음</td>
</tr>
<tr>
<td><code>maxLifetime</code></td>
<td>1,800,000ms</td>
<td>커넥션 최대 생존 시간. RDS <code>wait_timeout</code>보다 짧게</td>
</tr>
<tr>
<td><code>keepaliveTime</code></td>
<td>0 (비활성)</td>
<td>유휴 커넥션 ping 주기. idle 끊김 방지</td>
</tr>
<tr>
<td><code>poolName</code></td>
<td>auto</td>
<td>로그·메트릭 식별자</td>
</tr>
</tbody></table>
<br>

<h3 id="rds-proxy-도입--spring-boot-엔드포인트-전환"><strong>RDS Proxy 도입 + Spring Boot 엔드포인트 전환</strong></h3>
<ul>
<li>Lambda·ECS Fargate처럼 <strong>짧게 생성·소멸되는 컨테이너가 급증</strong>하는 환경에서는 여전히 문제</li>
<li>RDS Proxy는 이 문제를 해결하는 <strong>커넥션 풀 중앙 관리</strong> 계층</li>
</ul>
<pre><code>[Proxy 없는 구조]
  ECS Task × 50 (HikariCP ×5)        RDS Primary
  ┌─────────────────────┐             ┌────────────────────┐
  │ 각 태스크 × 5 커넥션│ ──────────►  │ 250개 요청         │
  │ 총 250개 요청      │             │ max_connections 66│
  └─────────────────────┘             │ → 초과 장애        │
                                    └────────────────────┘

[Proxy 도입 구조]
  ECS Task × 50         RDS Proxy              RDS Primary
  ┌────────────┐        ┌────────────────┐    ┌─────────────┐
  │HikariCP ×5│ ──────► │ 연결 풀 관리   │──► │ 실제 연결 30 │
  │ 250 요청   │        │(Multiplexing)│    │ 한도 이내    │
  └────────────┘        └────────────────┘    └─────────────┘
                        ↑ 여러 앱 연결을 소수 DB 연결로 묶음</code></pre><br>

<h4 id="rds-proxy-주요-기능">RDS Proxy 주요 기능</h4>
<table>
<thead>
<tr>
<th>기능</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>Connection Multiplexing</td>
<td>N개 앱 연결을 M개 DB 연결로 재사용 (N ≫ M)</td>
</tr>
<tr>
<td>Secrets Manager 통합</td>
<td>자격증명 로테이션 자동 반영</td>
</tr>
<tr>
<td>IAM 인증 지원</td>
<td>IAM 토큰으로 DB 접속 가능 (비밀번호 없이)</td>
</tr>
<tr>
<td>Failover 가속화</td>
<td>Multi-AZ Failover 시 앱 재연결 불필요, RTO 단축</td>
</tr>
<tr>
<td>Idle 연결 관리</td>
<td>유휴 커넥션 자동 정리</td>
</tr>
</tbody></table>
<hr>
<h3 id="amazon-dynamodb"><strong>Amazon DynamoDB</strong></h3>
<h3 id="01-dynamodb를-선택하는-기준"><strong>01. DynamoDB를 선택하는 기준</strong></h3>
<ul>
<li>DynamoDB는 키-값(Key-Value) 및 문서(Document) 방식의 완전 관리형 NoSQL 데이터베이스 서비스</li>
</ul>
<pre><code>[RDB(RDS MySQL) vs NoSQL(DynamoDB) 구조 차이]

  RDB — 정형 스키마, 다중 테이블 JOIN
  ┌──────────┐     JOIN    ┌──────────┐
  │ orders  │  ──────────► │ products │
  │ id  PK  │             │ id  PK   │
  │ user_id │             │ name     │
  │ prod_id │             │ price    │
  └──────────┘             └──────────┘
  → 스키마 변경 시 ALTER TABLE 필요
  → 수평 확장(Scale-Out)이 어려움
  → 복잡한 쿼리와 트랜잭션에 유리

---
  DynamoDB — 유연한 스키마, 단일 테이블 설계
  ┌────────────────────────────────────────────┐
  │                   Orders               │
  │ PK(pk)          SK(sk)      attributes │
  │ USER#u001       ORDER#o001  {상품, 수량}│
  │ USER#u001       ORDER#o002  {상품, 수량}│
  │ PRODUCT#p001    META        {이름, 가격}│
  │ ORDER#o001      STATUS      {CONFIRMED}│
  └────────────────────────────────────────────┘
  → 스키마 변경 없이 속성 추가 가능
  → 수평 확장 자동 처리 (파티션 자동 분산)
  → 단순 조회 패턴, 대규모 트래픽에 유리</code></pre><br>

<h4 id="선택-기준-비교">선택 기준 비교</h4>
<table>
<thead>
<tr>
<th>기준</th>
<th>RDS(MySQL) 선택</th>
<th>DynamoDB 선택</th>
</tr>
</thead>
<tbody><tr>
<td>데이터 구조</td>
<td>정형 데이터, 관계 복잡</td>
<td>비정형·반정형, 유연한 속성</td>
</tr>
<tr>
<td>쿼리 패턴</td>
<td>다양하고 복잡한 JOIN·집계</td>
<td>사전 정의된 단순 조회 패턴</td>
</tr>
<tr>
<td>트랜잭션</td>
<td>복잡한 다중 테이블 트랜잭션</td>
<td>단순 트랜잭션 (최대 100개 항목)</td>
</tr>
<tr>
<td>규모</td>
<td>중소규모, 수직 확장</td>
<td>수억 건 이상, 자동 수평 확장</td>
</tr>
<tr>
<td>지연 시간</td>
<td>수 ms ~ 수십 ms</td>
<td>한 자릿수 ms (일관된 성능)</td>
</tr>
<tr>
<td>대표 사용 사례</td>
<td>주문·회원·결제 핵심 도메인</td>
<td>세션·장바구니·게임 점수·IoT 이벤트</td>
</tr>
</tbody></table>
<br>

<h3 id="02-pk--sk--gsi--lsi--파티션"><strong>02. PK / SK / GSI / LSI / 파티션</strong></h3>
<p>DynamoDB의 모든 데이터 모델링은 기본 키(Primary Key) 설계에서 시작</p>
<pre><code>[DynamoDB 기본 키 2가지 유형]

  단순 기본 키 (Simple Primary Key)
  → 파티션 키(PK, Partition Key) 하나만 사용
  → 파티션 키 값이 고유해야 함
  → 예: 사용자 테이블에서 userId가 PK

  복합 기본 키 (Composite Primary Key)
  → 파티션 키(PK) + 정렬 키(SK, Sort Key) 조합
  → PK가 같아도 SK가 다르면 다른 항목으로 저장됨
  → 예: 주문 테이블에서 userId(PK) + orderId(SK)

  PK의 역할 — 파티션 결정
  ┌──────────────────────────────────────────────────┐
  │  PK 값 → 내부 해시 함수 → 저장 파티션 결정        │
  │                                              │
  │  Partition 1    Partition 2    Partition 3   │
  │  [USER#u001]    [USER#u002]    [USER#u003]   │
  │  ORDER#o001     ORDER#o003     ORDER#o005    │
  │  ORDER#o002     ORDER#o004     ORDER#o006    │
  └──────────────────────────────────────────────────┘
  → 같은 PK를 가진 항목들은 같은 파티션에 SK 순서로 정렬 저장됨
  → 핫 파티션(Hot Partition) 방지를 위해 PK 값을 분산시켜야 함</code></pre><br>

<h3 id="transactwriteitems---주문--재고-트랜잭션"><strong>TransactWriteItems - 주문 + 재고 트랜잭션</strong></h3>
<ul>
<li>주문 생성과 동시에 재고 차감을 하나의 트랜잭션으로 묶어서 둘 중 하나라도 실패하면 전체 롤백 확인</li>
</ul>
<br>

<h4 id="트랜잭션-비교">트랜잭션 비교</h4>
<pre><code>[일반 쓰기 - 위험]
  주문 생성 (성공) → 재고 차감 시도 (실패) → 데이터 불일치

[TransactWriteItems - 안전]
  주문 + 재고 차감을 하나의 트랜잭션으로 묶음
  조건 검증 통과 → 둘 다 적용
  조건 검증 실패 → 둘 다 롤백</code></pre><br>

<h3 id="dynamodb-streams--lambda-트리거"><strong>DynamoDB Streams + Lambda 트리거</strong></h3>
<ul>
<li>DynamoDB 항목이 변경될 때마다 Lambda가 자동 실행되어 CloudWatch Logs에 이벤트 이력을 기록하는 이벤트 드리븐 파이프라인을 구축</li>
</ul>
<hr>
<h3 id="amazon-elasticache-valkey"><strong>Amazon ElastiCache (Valkey)</strong></h3>
<h3 id="01-db-부하-문제와-인메모리-캐시의-역할"><strong>01. DB 부하 문제와 인메모리 캐시의 역할</strong></h3>
<h3 id="개념-4"><strong>개념</strong></h3>
<ul>
<li>웹 서비스에서 동일한 데이터를 DB에서 반복적으로 조회하면 DB 부하가 증가하고 응답 시간이 길어짐</li>
</ul>
<pre><code>[캐시 없는 구조 — DB 병목 발생]

  사용자 1000명이 동시에 상품 목록 조회 요청
        │
        ▼
  Spring Boot 애플리케이션
        │ 1000번 쿼리 발생
        ▼
  RDS MySQL ← CPU 100%, 응답 지연, 타임아웃 발생

---
[캐시 도입 후 — DB 부하 분산]

  사용자 1000명이 동시에 상품 목록 조회 요청
        │
        ▼
  Spring Boot 애플리케이션
        │
        ├── Cache HIT (900명): ElastiCache Valkey에서 즉시 응답 (&lt; 1ms)
        │
        └── Cache MISS (100명): RDS MySQL 조회 후 캐시 저장
                                 RDS: 100번 쿼리만 처리 → 부하 90% 감소

[성능 차이]

  RDS MySQL 조회:    1ms ~ 수십 ms (디스크 I/O 포함)
  ElastiCache 조회:  수십 μs ~ 1ms (메모리에서 즉시 반환)</code></pre><br>

<h3 id="02-serverless-vs-self-designed-cluster"><strong>02. Serverless vs Self-Designed Cluster</strong></h3>
<h3 id="개념-5"><strong>개념</strong></h3>
<ul>
<li>ElastiCache는 인프라 관리 방식에 따라 두 가지 배포 옵션을 제공</li>
<li>신규 서비스에서는 <strong>Serverless 방식이 기본 권장 옵션</strong></li>
</ul>
<pre><code>[Serverless 방식]

  장점:
  ├── 1분 이내 생성 완료
  ├── 용량 자동 조정 (트래픽 증감에 따라 자동 확장/축소)
  ├── 별도의 노드 타입 선택 불필요
  ├── 최소 비용: $6/월 (100MB 데이터 기준)
  └── Multi-AZ HA 자동 적용

  단점:
  ├── 노드 타입 직접 선택 불가
  └── 대규모 고정 트래픽에서는 Self-Designed 대비 비용이 높을 수 있음

[Self-Designed Cluster 방식]

  장점:
  ├── 노드 타입, 노드 수, 복제 그룹 구성을 직접 제어
  ├── 대규모 고정 트래픽에서 Reserved Nodes로 비용 최적화 가능
  └── 클러스터 모드(Cluster Mode) 활성화로 수평 확장 가능

  단점:
  ├── 노드 타입 선택, 클러스터 설계 필요
  └── 용량 부족 시 수동으로 Scale-Out 필요

[선택 기준]

  신규 서비스 / 트래픽 예측 불가     → Serverless 권장
  대규모 안정적 트래픽 / 비용 최적화  → Self-Designed + Reserved Nodes</code></pre><br>

<h3 id="03-캐싱-전략-1">03. 캐싱 전략</h3>
<p><strong>Cache-Aside / Write-Through / Write-Behind / Read-Through</strong></p>
<ul>
<li>캐시를 어떻게 채우고 갱신할지 결정하는 전략</li>
<li>서비스의 데이터 특성과 일관성 요구사항에 따라 적합한 전략을 선택</li>
</ul>
<pre><code>[1. Cache-Aside (Lazy Loading) — 가장 일반적인 패턴]

  읽기 요청
       │
       ▼
  캐시에 데이터 있음? (Cache HIT)
       ├── YES → 캐시에서 즉시 반환
       └── NO  → DB 조회 → 캐시에 저장 → 반환 (Cache MISS)

  특징:
  → 실제 읽히는 데이터만 캐시에 저장됨 (효율적)
  → DB 장애 시에도 캐시로 서비스 일부 유지 가능
  → 최초 요청(Cold Start) 시 Cache MISS 발생

---
[2. Write-Through — 쓰기 시 캐시 동시 갱신]

  쓰기 요청 → DB 저장 AND 캐시 갱신 (동시 수행)

  특징:
  → 캐시가 항상 최신 상태 유지됨
  → 쓰기 작업마다 캐시를 갱신하므로 쓰기 비용 증가
  → 거의 조회되지 않는 데이터도 캐시에 저장됨 (비효율)

---
[3. Write-Behind (Write-Back) — 비동기 DB 저장]

  쓰기 요청 → 캐시 먼저 저장 → 비동기로 나중에 DB에 저장

  특징:
  → 쓰기 응답이 매우 빠름
  → 캐시 장애 시 데이터 유실 위험 (내구성 낮음)
  → 쓰기가 매우 빈번한 서비스(IoT, 게임 점수)에 적합

---
[4. Read-Through — 캐시가 DB 조회를 대행]

  읽기 요청 → 캐시 미스 시 캐시가 직접 DB 조회 후 저장 → 반환

  특징:
  → 애플리케이션 코드에서 DB 직접 조회 로직 불필요
  → Cache-Aside와 유사하나, 캐시 라이브러리가 자동 처리
  → Spring의 @Cacheable 어노테이션이 이 패턴과 유사하게 동작</code></pre><br>

<h4 id="캐싱-전략-비교">캐싱 전략 비교</h4>
<table>
<thead>
<tr>
<th>전략</th>
<th>데이터 최신성</th>
<th>쓰기 성능</th>
<th>읽기 성능</th>
<th>구현 난이도</th>
<th>적합 사례</th>
</tr>
</thead>
<tbody><tr>
<td>Cache-Aside</td>
<td>약간 오래될 수 있음</td>
<td>영향 없음</td>
<td>첫 요청 느림</td>
<td>낮음</td>
<td>상품 목록, 공지사항</td>
</tr>
<tr>
<td>Write-Through</td>
<td>항상 최신</td>
<td>낮아짐</td>
<td>높음</td>
<td>중간</td>
<td>사용자 프로필</td>
</tr>
<tr>
<td>Write-Behind</td>
<td>약간 오래될 수 있음</td>
<td>매우 높음</td>
<td>높음</td>
<td>높음</td>
<td>IoT, 게임 점수</td>
</tr>
<tr>
<td>Read-Through</td>
<td>약간 오래될 수 있음</td>
<td>영향 없음</td>
<td>첫 요청 느림</td>
<td>낮음</td>
<td>일반 조회 API</td>
</tr>
</tbody></table>
<br>

<h3 id="04-ssh-터널링-설정-로컬-pc-→-elasticache"><strong>04. SSH 터널링 설정 (로컬 PC → ElastiCache)</strong></h3>
<ul>
<li>ElastiCache Serverless는 Private Subnet에만 배치되므로 로컬 PC에서 직접 접근 불가</li>
<li>Bastion EC2를 통한 SSH 터널로 로컬 6379 포트를 ElastiCache로 포워딩</li>
</ul>
<pre><code>[SSH 터널 구조]

로컬 PC (STS4)
   │
   │  localhost:6379로 접속
   ▼
SSH 터널 (로컬 터미널에서 유지)
   │
   │  Bastion EC2 경유
   ▼
ElastiCache Serverless (Private Subnet)
   mylab-valkey-cache-xxxxxx.serverless.apn2.cache.amazonaws.com:6379</code></pre><br>

<h3 id="실습-1-cache-aside-패턴--sorted-set-랭킹"><strong>실습 1. Cache-Aside 패턴 + Sorted Set 랭킹</strong></h3>
<ul>
<li>전자상거래 상품 조회 API에 ElastiCache Valkey를 도입하여 DB 부하를 줄이고, Sorted Set으로 실시간 조회 랭킹을 구현</li>
</ul>
<br>

<h3 id="실습-2-jwt-인증--elasticache-토큰-저장소"><strong>실습 2. JWT 인증 + ElastiCache 토큰 저장소</strong></h3>
<ul>
<li>Spring Security + JWT 기반 인증 API를 구현하고, ElastiCache Valkey를 Refresh Token 저장소와 Access Token 블랙리스트로 활용</li>
</ul>
<br>

<h4 id="전체-구조"><strong>전체 구조</strong></h4>
<pre><code>[클라이언트]
     │
     │  POST /auth/login
     ▼
[Spring Boot - JWT 인증 서버]
     │
     ├── JwtAuthenticationFilter      모든 요청에서 Access Token 검증
     │     └── TokenStoreService      블랙리스트 조회
     │
     ├── AuthController
     │   ├── POST /auth/register      회원가입
     │   ├── POST /auth/login         Access + Refresh 발급
     │   ├── POST /auth/refresh       토큰 갱신 (Rotation)
     │   └── POST /auth/logout        토큰 무효화
     │
     └── 보호된 API
         └── GET /api/me              인증된 사용자만 접근

[ElastiCache Valkey - 토큰 저장소]
     │
     ├── Refresh Token 저장
     │   키: &quot;refresh:{email}&quot;
     │   값: Refresh Token 문자열
     │   TTL: 7일
     │
     └── Access Token 블랙리스트
         키: &quot;blacklist:{jti}&quot;
         값: &quot;revoked&quot;
         TTL: Access Token 남은 유효 시간</code></pre><br>

<h3 id="실습-3-redisson-분산-락--elasticache"><strong>실습 3. Redisson 분산 락 + ElastiCache</strong></h3>
<ul>
<li>다중 서버 환경에서 동시 요청이 몰릴 때 발생하는 Race Condition을 Redisson 분산 락으로 해결</li>
</ul>
<br>

<h4 id="race-condition-경쟁-조건"><strong>Race Condition (경쟁 조건)</strong></h4>
<pre><code>[재고 1개 상품에 동시 주문 2건 발생]

  EC2 인스턴스 A                      EC2 인스턴스 B
  ─────────────────────────────────────────────────
  1. SELECT stock = 1 조회             1. SELECT stock = 1 조회
  2. (재고 있음 확인)                   2. (재고 있음 확인)
  3. UPDATE stock = 0                  3. UPDATE stock = 0
  4. 주문 완료 처리                     4. 주문 완료 처리

  → 두 인스턴스 모두 주문 완료
  → 재고가 1개인데 2개 판매 (데이터 정합성 파괴)</code></pre><br>

<h4 id="분산-락-구성-요소">분산 락 구성 요소</h4>
<table>
<thead>
<tr>
<th>요소</th>
<th>역할</th>
</tr>
</thead>
<tbody><tr>
<td>Mutual Exclusion</td>
<td>하나의 자원에 동시 하나의 프로세스만 접근</td>
</tr>
<tr>
<td>TTL (Time To Live)</td>
<td>락에 만료 시간 부여 → Deadlock 방지</td>
</tr>
<tr>
<td>Atomicity</td>
<td>락 획득 연산이 원자적 실행 (SETNX + EXPIRE)</td>
</tr>
<tr>
<td>Reentrant</td>
<td>같은 스레드의 중복 획득 허용</td>
</tr>
<tr>
<td>Pub/Sub</td>
<td>락 해제 시 대기자에게 이벤트 알림 (Polling 회피)</td>
</tr>
</tbody></table>
<br>

<h4 id="watchdog-패턴">Watchdog 패턴</h4>
<pre><code>[Watchdog 없이 TTL 30초로 락 획득 후 35초 작업 시]

  락 획득 (TTL 30초)
       │
       │── 30초 경과 → TTL 만료 → 락 자동 해제 (조기 해제)
       │── 다른 프로세스 락 획득 가능
       ▼
  35초 시점 작업 완료 → unlock 시도 → 예외 발생

[Watchdog 적용 시 (Redisson lock())]

  락 획득 (TTL 30초) + Watchdog 시작
       │
       │── 10초마다 Watchdog이 TTL을 30초로 갱신
       │── 20초 경과 → TTL 30초로 갱신
       │── 30초 경과 → TTL 30초로 갱신
       ▼
  35초 시점 작업 완료 → unlock → 정상 해제

[프로세스 장애 시]

  Watchdog도 함께 종료 → TTL 갱신 중단 → 30초 후 자동 해제
  → Deadlock 방지</code></pre><br>

<h3 id="실습-4-spring-session-클러스터링--elasticache"><strong>실습 4. Spring Session 클러스터링 + ElastiCache</strong></h3>
<ul>
<li>단일 서버 JVM 메모리에 저장되던 HTTP 세션을 ElastiCache Valkey로 외부화하여 다중 인스턴스 환경에서 세션을 공유</li>
</ul>
<br>

<h4 id="sticky-session-vs-clustering">Sticky Session vs Clustering</h4>
<pre><code>[Sticky Session - 서버 고정 방식]

  사용자 A ─→ ALB (쿠키 기반 고정) ─→ 항상 인스턴스 1로 라우팅
  사용자 B ─→ ALB (쿠키 기반 고정) ─→ 항상 인스턴스 2로 라우팅

  장점: 구현 간단 (ALB 설정만 변경)
  단점: 특정 서버 부하 집중, 서버 장애 시 세션 유실,
        Rolling Update 시 세션 유실, Auto Scaling 부적합

[Session Clustering - ElastiCache 공유 방식]

  사용자 A의 요청
       │
       ▼
  ALB (부하 분산)
  ├──→ 인스턴스 1 ─→ ElastiCache 세션 조회 ─→ Session
  ├──→ 인스턴스 2 ─→ ElastiCache 세션 조회 ─→ Session
  └──→ 인스턴스 3 ─→ ElastiCache 세션 조회 ─→ Session
                         ↑
                    모든 인스턴스가 동일 세션 접근

  장점: 서버 장애 시에도 세션 유지, Auto Scaling 즉시 적용,
        무중단 배포 가능, 확장성 우수
  단점: ElastiCache 의존성, 네트워크 조회 비용</code></pre><br>

<h4 id="spring-session-내부-동작">Spring Session 내부 동작</h4>
<pre><code>[@EnableRedisHttpSession 적용 시]

   HTTP 요청 (Cookie: JSESSIONID=abc123)
        │
        ▼
   SessionRepositoryFilter (자동 등록, 가장 앞에 위치)
        │
        ├── 쿠키에서 세션 ID 추출
        ├── Redis 조회: HGETALL spring:session:sessions:abc123
        ├── HttpSession으로 래핑
        └── lastAccessedTime 갱신 후 Redis 저장
        │
        ▼
   Spring Security FilterChain → 컨트롤러

[Redis 저장 키 구조 (기본 namespace: spring:session)]

   ① 세션 데이터 (Hash)
   spring:session:sessions:{sessionId}
      sessionAttr:SPRING_SECURITY_CONTEXT   {직렬화된 인증 객체}
      creationTime                           1713254400000
      lastAccessedTime                       1713254460000
      maxInactiveInterval                    1800

   ② 세션 만료 키 (String + TTL)
   spring:session:sessions:expires:{sessionId}
   → TTL 1800 (30분)

   ③ 만료 인덱스 (Set, 주기적 정리용)
   spring:session:expirations:{만료 버킷}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[멋쟁이사자처럼부트캠프 클라우드 엔지니어링 5기 [12주차]]]></title>
            <link>https://velog.io/@its-jihyeon/%EB%A9%8B%EC%9F%81%EC%9D%B4%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A7%81-5%EA%B8%B0-12%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@its-jihyeon/%EB%A9%8B%EC%9F%81%EC%9D%B4%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A7%81-5%EA%B8%B0-12%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Sat, 18 Apr 2026 07:09:01 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이번 주는 AWS의 고가용성과 S3 스토리지를 스트링 부트와 연결하여 활용하는 것 까지 배웠다.</p>
</blockquote>
<br>

<h3 id="route-53">Route 53</h3>
<h3 id="01-dns">01. DNS</h3>
<p>DNS(Domain Name System)는 <strong>사람이 읽을 수 있는 도메인 이름을 IP 주소로 변환</strong>하는 인터넷 전화번호부</p>
<br>

<h4 id="dns-쿼리-전체-흐름">DNS 쿼리 전체 흐름</h4>
<pre><code>사용자 브라우저: &quot;도메인&quot; 입력
      │
      ▼
① 로컬 캐시 확인
   브라우저 캐시 → OS 캐시 → /etc/hosts 파일
   → 캐시 없으면 다음 단계
      │
      ▼
② DNS Resolver (재귀 조회 시작)
   ISP 제공 또는 8.8.8.8, 1.1.1.1
   클라이언트를 대신해 DNS 조회 수행
      │
      ▼
③ Root DNS 서버 조회
   &quot;.kr을 담당하는 TLD NS 주소 응답&quot;
      │
      ▼
④ TLD DNS 서버 조회 (.kr)
   &quot;mylab.kr을 담당하는 Authoritative NS 응답&quot;
      │
      ▼
⑤ Authoritative DNS 서버 (Route 53)
   &quot;shop.mylab.kr의 IP는 &lt;CloudFront IP&gt;&quot; 응답
      │
      ▼
⑥ DNS Resolver → 클라이언트에 IP 전달
   TTL 시간 동안 캐시 후 전달
      │
      ▼
⑦ 브라우저가 IP로 HTTP/S 요청 전송</code></pre><br>

<h3 id="02-route-53">02. Route 53</h3>
<p>AWS의 완전 관리형 권위 있는 DNS 서비스</p>
<pre><code>Route 53의 4가지 핵심 기능

① 도메인 등록 (Domain Registration)
   .com / .kr 등 도메인 직접 구매·관리.
   가비아 등 외부 등록 기관에서 구매한 도메인도 연동 가능.

② DNS 라우팅 (DNS Routing)
   Hosted Zone에 레코드를 설정하여 도메인 → IP 매핑.

③ 상태 확인 (Health Check)
   엔드포인트 정상 여부를 주기적으로 모니터링.
   장애 감지 시 다른 엔드포인트로 자동 전환.

④ 프라이빗 DNS (Private DNS)
   VPC 내부에서만 해석되는 내부 DNS 이름 관리.</code></pre><p><strong>Hosted Zone</strong>
Route 53에서 하나의 도메인에 대한 DNS 레코드 모음</p>
<table>
<thead>
<tr>
<th>유형</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>Public Hosted Zone</td>
<td>인터넷에서 접근 가능. 공개 도메인 관리.</td>
</tr>
<tr>
<td>Private Hosted Zone</td>
<td>VPC 내부에서만 해석 가능. 내부 서비스 DNS 관리.</td>
</tr>
</tbody></table>
<br>

<h3 id="03-dns-레코드-타입-정리">03. DNS 레코드 타입 정리</h3>
<table>
<thead>
<tr>
<th>타입</th>
<th>설명</th>
<th>주요 사용처</th>
</tr>
</thead>
<tbody><tr>
<td><strong>A</strong></td>
<td>도메인 → IPv4 주소</td>
<td>EC2, ALB IP 연결</td>
</tr>
<tr>
<td><strong>AAAA</strong></td>
<td>도메인 → IPv6 주소</td>
<td>IPv6 리소스 연결</td>
</tr>
<tr>
<td><strong>CNAME</strong></td>
<td>도메인 → 다른 도메인</td>
<td>외부 서비스 연결</td>
</tr>
<tr>
<td><strong>Alias</strong></td>
<td>도메인 → AWS 리소스 (AWS 전용)</td>
<td>CloudFront, ALB, S3</td>
</tr>
<tr>
<td><strong>MX</strong></td>
<td>이메일 수신 서버 지정</td>
<td>메일 서비스</td>
</tr>
<tr>
<td><strong>TXT</strong></td>
<td>텍스트 데이터</td>
<td>도메인 소유권 확인, SPF</td>
</tr>
<tr>
<td><strong>NS</strong></td>
<td>권위 NS 서버 지정</td>
<td>Route 53 자동 관리</td>
</tr>
</tbody></table>
<br>

<h4 id="cname-vs-alias"><strong>CNAME vs Alias</strong></h4>
<p>CNAME의 한계: Zone Apex(루트 도메인)에 사용 불가 </p>
<table>
<thead>
<tr>
<th>항목</th>
<th>CNAME</th>
<th>Alias</th>
</tr>
</thead>
<tbody><tr>
<td>Zone Apex 사용</td>
<td>불가</td>
<td>가능</td>
</tr>
<tr>
<td>AWS 리소스 연결</td>
<td>도메인명 입력</td>
<td>리소스 직접 선택</td>
</tr>
<tr>
<td>IP 변경 자동 추적</td>
<td>불가</td>
<td>가능</td>
</tr>
<tr>
<td>쿼리 비용</td>
<td>발생</td>
<td>AWS 리소스 연결 시 무료</td>
</tr>
</tbody></table>
<br>

<p><strong>실습</strong></p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/2d525f50-f04f-4a00-9c50-d78f4517e2f0/image.png" alt=""></p>
<br>

<h3 id="cloudfront">CloudFront</h3>
<h3 id="01-cdn">01. CDN</h3>
<p>전 세계 분산된 서버(Edge)에 콘텐츠를 캐싱하여 사용자에게 가장 가까운 위치에서 빠르게 응답하는 분산 콘텐츠 전송 네트워크</p>
<pre><code>[ CDN 없는 경우 ]
한국 사용자 ───────────────────────────────▶ 서울 EC2 (20ms)
미국 사용자 ───────────────── 약 200ms ────▶ 서울 EC2 (Origin 직접 요청)

[ CloudFront 있는 경우 ]
한국 사용자 ──▶ 서울 Edge (캐시 있으면 5ms)
미국 사용자 ──▶ 미국 Edge (캐시 있으면 10ms)
                        Cache Miss 시에만 → 서울 EC2(Origin)에서 가져와 Edge에 저장</code></pre><br>

<h3 id="02-핵심-구성요소">02. 핵심 구성요소</h3>
<table>
<thead>
<tr>
<th>구성요소</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Distribution</strong></td>
<td>CloudFront의 최상위 설정 단위. 생성 시 고유 도메인 부여: <code>dXXXX.cloudfront.net</code></td>
</tr>
<tr>
<td><strong>Origin</strong></td>
<td>콘텐츠 원본 서버. S3, EC2, ALB, API GW 등. 하나의 Distribution에 여러 Origin 설정 가능.</td>
</tr>
<tr>
<td><strong>Behavior</strong></td>
<td>URL 경로 패턴별 캐시·Origin 동작 규칙. 가장 구체적인 패턴이 우선 적용.</td>
</tr>
<tr>
<td><strong>Cache Policy</strong></td>
<td>TTL, 캐시 Key(헤더·쿼리스트링) 설정.</td>
</tr>
<tr>
<td><strong>OAC</strong></td>
<td>S3 Origin 접근 제어. 버킷 퍼블릭 오픈 없이 CloudFront만 접근 허용.</td>
</tr>
</tbody></table>
<br>

<h3 id="03-acm">03. ACM</h3>
<p>AWS에서 SSL/TLS 인증서를 무료로 발급·갱신·관리해주는 서비스</p>
<br>

<p><strong>동작 흐름</strong></p>
<pre><code>도메인 소유자
    │
    ▼
ACM에서 인증서 요청
    │
    ├─ DNS 검증 (권장) : Route 53에 CNAME 레코드 자동 추가
    └─ 이메일 검증   : 도메인 등록 이메일로 확인 링크 전송
    │
    ▼
인증서 발급 완료
    │
    ▼
ALB / CloudFront 리스너에 연결
    │
    ▼
HTTPS 트래픽 처리</code></pre><br>

<h4 id="정적-파일-cloudfront-구성---아키텍처">정적 파일 CloudFront 구성 - 아키텍처</h4>
<pre><code>사용자
  │ https://도메인
  ▼
Route 53 (Alias → CloudFront)
  │
  ▼
CloudFront Distribution
  │ SSL/TLS 종료 (ACM 인증서)
  │ HTTP → HTTPS 리다이렉트
  │ Behavior: /* → S3 (캐시 ON)
  ▼
S3 Bucket (비공개 유지)
  ↑ OAC로만 접근 허용. 직접 URL 접근 → 403</code></pre><br>

<h3 id="정리">정리</h3>
<pre><code>내용 요약

① CDN 원리
   전 세계 Edge Location에 콘텐츠 캐싱.
   Cache Hit → Edge에서 즉시 응답. Origin 부하 없음.
   Cache Miss → Origin에서 가져와 Edge에 저장 후 응답.

② CloudFront 구성요소
   Distribution / Origin / Behavior / Cache Policy / OAC.

③ Behavior 핵심
   /api/* → CachingDisabled (동적 API)
   /*     → CachingOptimized (정적 파일)
   더 구체적인 패턴이 우선 적용.
   동적 API에 캐시 실수 적용 시 → 다른 사용자 데이터 노출 가능.

④ OAC (최신 방식)
   S3 버킷 퍼블릭 오픈 없이 CloudFront만 접근 허용.
   Bucket Policy에 CloudFront 서비스 주체 명시.

⑤ ACM + HTTPS
   CloudFront용 인증서는 us-east-1에서 발급.
   DNS 검증으로 자동 갱신.

⑥ Invalidation
   Edge 캐시 강제 삭제. 배포 시 /* 실행.
   CI/CD 파이프라인에서 자동화하는 것이 실무 표준.</code></pre><br>

<p><strong>실습</strong></p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/666215eb-05cd-4654-88b3-a31e39a30e7a/image.png" alt=""></p>
<br>

<h3 id="전체-구성요소-통합-아키텍처">전체 구성요소 통합 아키텍처</h3>
<pre><code>사용자 브라우저 (https://도메인)
      │
      ▼
① Route 53
   도메인 → Alias → CloudFront
   Failover: Primary(EC2) 장애 시 Secondary(S3 점검 페이지) 자동 전환
      │
      ▼
② CloudFront Distribution
   Edge Location에서 캐시 응답
   ACM 인증서 (us-east-1) → HTTPS 종료
   HTTP → HTTPS 자동 리다이렉트
      │
      ├── Behavior: /*      → S3 (정적, 캐시 ON)
      │         ▼
      │    ③ S3 Bucket (OAC 적용, 퍼블릭 차단)
      │
      └── Behavior: /api/*  → EC2 (동적, 캐시 OFF)
                ▼
┌─────────────────────────────────────────────────────────────────────┐
│  VPC: shop-vpc (10.10.0.0/16)                                       │
│                                                                     │
│  ④ Internet Gateway: shop-igw                                       │
│      │                                                              │
│  AZ: ap-northeast-2a             AZ: ap-northeast-2c                │
│  ┌────────────────────────┐  ┌──────────────────────────────────┐   │
│  │  Public 10.10.1.0/24   │  │  Public 10.10.2.0/24             │   │
│  │  [NAT GW-2a + EIP]     │  │  [NAT GW-2c + EIP]               │   │
│  └────────────────────────┘  └──────────────────────────────────┘   │
│                                                                     │
│  ⑤ NACL — 서브넷 경계 방화벽 (Stateless)                             │  
│                                                                     │
│  ┌────────────────────────┐  ┌──────────────────────────────────┐   │
│  │  Private 10.10.3.0/24  │  │  Private 10.10.4.0/24            │   │
│  │  ⑥ EC2: App (sg-app)   │  │  ⑥ EC2: App (sg-app) [이중화]    │   │
│  └──────────┬─────────────┘  └────────────────┬─────────────────┘   │
│             │ sg-db: 3306                     │                     │
│  ┌──────────▼─────────────────────────────────▼──────────────────┐  │
│  │  Isolated 10.10.5.0/24   /   Isolated 10.10.6.0/24            │  │
│  │  ⑦ RDS MySQL (Primary)       RDS MySQL (Standby)              │  │
│  │     db.internal.mylab.kr → Private Hosted Zone ㅍ             │  │
│  └───────────────────────────────────────────────────────────────┘  │
│                                                                     │
│  ⑧ VPC Flow Logs → CloudWatch (CH03)                                │
└─────────────────────────────────────────────────────────────────────┘

Security Group 체계 :
  sg-alb     : 80/443  ← 0.0.0.0/0
  sg-app     : 8080    ← sg-alb 참조 / SSH 22 ← sg-bastion 참조
  sg-db      : 3306    ← sg-app 참조
  sg-bastion : 22      ← 관리자 IP/32

Route Table 체계 :
  public-rt      → 0.0.0.0/0 → IGW        (AZ-a, AZ-c 공통)
  private-rt-2a  → 0.0.0.0/0 → nat-gw-2a  (AZ-a 전용)
  private-rt-2c  → 0.0.0.0/0 → nat-gw-2c  (AZ-c 전용)
  isolated-db-rt → local만                 (AZ-a, AZ-c 공통)</code></pre><br>

<h3 id="vpc-peering">VPC Peering</h3>
<ul>
<li>두 VPC 사이를 <strong>AWS 내부 백본 네트워크로 직접 연결</strong>하는 기능</li>
<li>인터넷을 경유하지 않으므로 트래픽이 외부에 노출되지 않음</li>
</ul>
<pre><code>[ 인터넷 경유 방식 — Peering 없음 ]

VPC-A (10.10.0.0/16)                   VPC-B (10.20.0.0/16)
  EC2-A ──── 공인 IP ────▶ 인터넷 ────▶ EC2-B
             (보안 위험, 데이터 전송 비용 발생)

[ VPC Peering 사용 ]

VPC-A (10.10.0.0/16)                   VPC-B (10.20.0.0/16)
  EC2-A ◀──────── AWS 내부 백본 ────────▶ EC2-B
                (인터넷 미경유, 사설 IP 통신)</code></pre><br>

<p><strong>AWS 내부 백본 네트워크</strong></p>
<ul>
<li>AWS가 전 세계 리전과 데이터센터를 연결하기 위해 구축한 전용 고속 사설 네트워크</li>
<li>공용 인터넷과 물리적으로 분리된 전용선으로 구성됨</li>
</ul>
<br>

<h3 id="vpc-endpoint-privatelink">VPC Endpoint (PrivateLink)</h3>
<ul>
<li>Private Subnet의 리소스가 <strong>인터넷을 경유하지 않고 AWS 서비스에 직접 접근</strong>하는 기능</li>
<li>NAT Gateway나 Internet Gateway 없이 AWS 내부 네트워크로만 트래픽 처리</li>
</ul>
<pre><code>[ 기존 방식 — NAT Gateway 경유 ]

Private EC2 ──▶ NAT GW ──▶ Internet ──▶ S3
                 (비용 발생: $0.059/GB)  (인터넷 노출)

[ VPC Endpoint 사용 ]

Private EC2 ──▶ VPC Endpoint ──▶ S3
                (AWS 내부망, 인터넷 미경유, 비용 절감)</code></pre><br>

<h4 id="endpoint-유형-비교">Endpoint 유형 비교</h4>
<table>
<thead>
<tr>
<th>항목</th>
<th>Gateway Endpoint</th>
<th>Interface Endpoint</th>
</tr>
</thead>
<tbody><tr>
<td><strong>동작 방식</strong></td>
<td>Route Table에 prefix list 경로 추가</td>
<td>VPC 내 ENI 생성 (프라이빗 IP 부여)</td>
</tr>
<tr>
<td><strong>지원 서비스</strong></td>
<td>S3, DynamoDB</td>
<td>대부분의 AWS 서비스</td>
</tr>
<tr>
<td><strong>비용</strong></td>
<td>무료</td>
<td>$0.01/시간 + $0.01/GB</td>
</tr>
<tr>
<td><strong>DNS 설정</strong></td>
<td>불필요</td>
<td>Private DNS 활성화 권장</td>
</tr>
<tr>
<td><strong>SG 적용</strong></td>
<td>불가</td>
<td>가능 (ENI에 SG 연결)</td>
</tr>
<tr>
<td><strong>온프레미스 접근</strong></td>
<td>불가</td>
<td>가능 (VPN/DX 경유)</td>
</tr>
<tr>
<td><strong>멀티 AZ</strong></td>
<td>자동</td>
<td>AZ별 ENI 각각 생성</td>
</tr>
</tbody></table>
<hr>
<h3 id="01-고가용성ha-핵심-개념">01. 고가용성(HA) 핵심 개념</h3>
<h3 id="개념">개념</h3>
<ul>
<li>장애가 발생해도 서비스가 계속 동작하도록 설계하는 것</li>
<li>핵심은 장애를 막는 것이 아니라, <strong>장애를 가정하고 자동으로 복구되도록 설계하는 것</strong></li>
</ul>
<br>

<h3 id="02-rto--rpo--복구-목표-지표">02. RTO / RPO — 복구 목표 지표</h3>
<p>장애 대응 설계의 기준이 되는 두 가지 핵심 지표</p>
<pre><code>[ RPO — Recovery Point Objective, 복구 시점 목표 ]

마지막 백업              장애 발생
    │                        │
────┼────────────────────────┼──→ 시간
    │◄─────── RPO ──────────►│
       데이터 손실 허용 최대치

[ RTO — Recovery Time Objective, 복구 시간 목표 ]

            장애 발생                   서비스 복구
                │                           │
────────────────┼───────────────────────────┼──→ 시간
                │◄─────── RTO ─────────────►│
                      서비스 중단 허용 최대치</code></pre><table>
<thead>
<tr>
<th>DR 전략</th>
<th>RTO</th>
<th>RPO</th>
<th>비용</th>
<th>특징</th>
</tr>
</thead>
<tbody><tr>
<td>Backup &amp; Restore</td>
<td>수 시간</td>
<td>수 시간~1일</td>
<td>가장 저렴</td>
<td>S3에 정기 백업, 복구 시 시간 소요</td>
</tr>
<tr>
<td>Pilot Light</td>
<td>수십 분</td>
<td>수 분</td>
<td>저렴</td>
<td>DB만 복제, 나머지는 꺼놓음</td>
</tr>
<tr>
<td>Warm Standby</td>
<td>수 분</td>
<td>수 초~수 분</td>
<td>중간</td>
<td>축소 사양으로 항상 운영</td>
</tr>
<tr>
<td>Multi-Site Active/Active</td>
<td>~0</td>
<td>~0</td>
<td>가장 비쌈</td>
<td>두 리전이 동시에 트래픽 처리</td>
</tr>
</tbody></table>
<br>

<h3 id="03-reliability-pillar--5가지-설계-원칙">03. Reliability Pillar — 5가지 설계 원칙</h3>
<p>장애를 막는 것이 아니라, 장애를 가정하고 자동 복구되도록 설계하는 것이 핵심</p>
<pre><code>1. 장애 복구 자동화    
── CloudWatch Alarm → Auto Scaling
KPI 임계값 초과 시 자동 교정

2. 복구 절차 테스트    
── RDS Failover 강제 실행
AWS FIS(Fault Injection Simulator)로 Chaos Engineering

3. 수평 확장           
── ALB + ASG Multi-AZ
큰 인스턴스 1개 → 작은 인스턴스 여러 개로 분산

4. 용량 추측 금지      
── Target Tracking Scaling Policy
실측 기반으로 인스턴스 수 자동 조정

5. 자동화로 변경 관리  
── IaC(CloudFormation / Terraform)
수동 콘솔 변경 금지, 코드 기반 변경만 허용</code></pre><br>

<h3 id="aws-고가용성ha-아키텍처">AWS 고가용성(HA) 아키텍처</h3>
<h3 id="01-application-load-balancer-alb">01. Application Load Balancer (ALB)</h3>
<p><strong>Layer 7(HTTP/HTTPS)</strong> 에서 동작하는 완전 관리형 로드밸런서</p>
<pre><code>                       인터넷
                         │
              [ALB — 퍼블릭 엔드포인트]
        my-alb-xxx.ap-northeast-2.elb.amazonaws.com
                         │
        ┌────────────────┼────────────────┐
        │  Listener:80   │                │
        ▼                ▼
  [Target Group]   (경로/호스트 기반 추가 라우팅 가능)
  Health Check 통과 인스턴스에만 트래픽 전달
        │
   ┌────┴────┐
   EC2-A     EC2-B
  (AZ-a)   (AZ-b)
  Private   Private
  Subnet    Subnet</code></pre><br>

<h4 id="alb-핵심-구성-요소">ALB 핵심 구성 요소</h4>
<table>
<thead>
<tr>
<th>구성 요소</th>
<th>역할</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Listener</strong></td>
<td>지정된 포트(80, 443)로 들어오는 요청 수신</td>
</tr>
<tr>
<td><strong>Rules</strong></td>
<td>경로(/api/*)·호스트 기반 라우팅 규칙</td>
</tr>
<tr>
<td><strong>Target Group</strong></td>
<td>실제 트래픽을 받는 서버 그룹</td>
</tr>
<tr>
<td><strong>Health Check</strong></td>
<td>주기적으로 대상 서버 상태 확인, 실패 시 트래픽 제외</td>
</tr>
</tbody></table>
<br>

<h4 id="alb-vs-nlb-vs-clb">ALB vs. NLB vs. CLB</h4>
<table>
<thead>
<tr>
<th>항목</th>
<th>ALB</th>
<th>NLB</th>
<th>CLB</th>
</tr>
</thead>
<tbody><tr>
<td>OSI 계층</td>
<td>L7 (HTTP/HTTPS)</td>
<td>L4 (TCP/UDP)</td>
<td>L4/L7 혼합</td>
</tr>
<tr>
<td>라우팅</td>
<td>경로·호스트 기반</td>
<td>포트 기반</td>
<td>기본</td>
</tr>
<tr>
<td>고정 IP</td>
<td>불가 (DNS 기반)</td>
<td>가능 (EIP)</td>
<td>불가</td>
</tr>
<tr>
<td>WebSocket</td>
<td>지원</td>
<td>지원</td>
<td>미지원</td>
</tr>
<tr>
<td>권장 용도</td>
<td>웹 애플리케이션</td>
<td>고성능·저지연</td>
<td>레거시 (신규 사용 자제)</td>
</tr>
</tbody></table>
<br>

<h3 id="02-auto-scaling-group-asg">02. Auto Scaling Group (ASG)</h3>
<p>Launch Template을 기반으로 EC2 인스턴스 수를 자동으로 조절하는 서비스</p>
<br>

<h4 id="3가지-스케일링-정책">3가지 스케일링 정책</h4>
<table>
<thead>
<tr>
<th>정책 유형</th>
<th>동작 방식</th>
<th>사용 상황</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Target Tracking</strong></td>
<td>CPU 60% 유지처럼 목표값 자동 추적</td>
<td>일반적 사용 (권장)</td>
</tr>
<tr>
<td><strong>Step Scaling</strong></td>
<td>CloudWatch 알람 단계별로 다른 증감</td>
<td>부하 패턴이 단계적일 때</td>
</tr>
<tr>
<td><strong>Scheduled</strong></td>
<td>시간 기반 사전 스케일링</td>
<td>점심·퇴근 트래픽처럼 예측 가능 시</td>
</tr>
</tbody></table>
<br>

<h3 id="03-alb--asg-연동-흐름">03. ALB + ASG 연동 흐름</h3>
<pre><code>사용자 요청
    │
    ▼
[ALB Listener :80]
    │  헬스체크 통과한 대상에만 트래픽 전달
    ▼
[Target Group: ha-app-tg]
    ├── EC2-A (InService) (o)  8080
    └── EC2-B (InService) (o)  8080
         ↑
         │ ASG가 Target Group에 자동 등록·해제
         │
    [Auto Scaling Group: ha-app-asg]
    ├── Launch Template: ha-app-lt
    ├── Min:2 / Max:6 / Desired:2
    ├── Subnet: Private-2a, Private-2b
    └── Health Check: ELB  ← 중요</code></pre><hr>
<h3 id="aws-well-architected-framework">AWS Well-Architected Framework</h3>
<h3 id="01-waf란-무엇인가">01. WAF란 무엇인가</h3>
<ul>
<li>AWS가 수천 개 고객 아키텍처 리뷰 경험을 체계화한 모범 사례 프레임워크</li>
<li>&quot;잘 설계된 시스템인가?&quot;를 일관된 기준으로 평가하고 개선 방향을 제시하는 도구</li>
</ul>
<pre><code>기존 학습 내용과 WAF 6 Pillars 연결

계정 보안 및 관리
┌────────────────────────┐
│ IAM / MFA / KMS        │ ──→  Security Pillar
│ CloudTrail / Config    │ ──→  Operational Excellence
└────────────────────────┘

컴퓨팅
┌────────────────────────┐
│ EC2 / Auto Scaling     │ ──→  Reliability + Performance Efficiency
│ Lambda / ECS / Fargate │ ──→  Cost Optimization + Performance
│ Graviton (ARM64)       │ ──→  Sustainability
└────────────────────────┘

스토리지
┌────────────────────────┐
│ S3 / EBS / EFS         │ ──→  Reliability + Cost Optimization
│ S3 Lifecycle / IT      │ ──→  Cost Optimization + Sustainability
│ KMS 암호화             │ ──→  Security
└────────────────────────┘

네트워크
┌────────────────────────┐
│ VPC / SG / NACL        │ ──→  Security + Reliability
│ CloudFront / Route 53  │ ──→  Performance Efficiency
│ Multi-AZ 설계          │ ──→  Reliability
└────────────────────────┘

┌────────────────────────┐
│ CloudWatch / Alarm     │ ──→  Operational Excellence
│ ElastiCache            │ ──→  Performance Efficiency
│ Reserved / Spot        │ ──→  Cost Optimization
│ Carbon Footprint Tool  │ ──→  Sustainability
└────────────────────────┘</code></pre><br>

<h3 id="02-6개-pillar-전체-구조">02. 6개 Pillar 전체 구조</h3>
<table>
<thead>
<tr>
<th>Pillar</th>
<th>핵심 질문</th>
<th>주요 서비스</th>
</tr>
</thead>
<tbody><tr>
<td>Operational Excellence</td>
<td>운영을 자동화하고 지속 개선하는가?</td>
<td>CloudWatch, CloudTrail, Config</td>
</tr>
<tr>
<td>Security</td>
<td>데이터와 시스템을 안전하게 보호하는가?</td>
<td>IAM, KMS, GuardDuty, Security Hub</td>
</tr>
<tr>
<td>Reliability</td>
<td>장애에서 빠르게 복구하도록 설계되었는가?</td>
<td>ALB, ASG, RDS Multi-AZ, Route 53</td>
</tr>
<tr>
<td>Performance Efficiency</td>
<td>리소스를 효율적으로 사용하는가?</td>
<td>CloudFront, ElastiCache, Graviton</td>
</tr>
<tr>
<td>Cost Optimization</td>
<td>불필요한 비용 없이 최적화되었는가?</td>
<td>Cost Explorer, Budgets, S3 Lifecycle</td>
</tr>
<tr>
<td>Sustainability</td>
<td>환경적 영향을 최소화하는가?</td>
<td>Graviton, S3 IT, Carbon Tool</td>
</tr>
</tbody></table>
<br>

<h3 id="03-핵심-용어-정의">03. 핵심 용어 정의</h3>
<table>
<thead>
<tr>
<th>용어</th>
<th>정의</th>
</tr>
</thead>
<tbody><tr>
<td>Component</td>
<td>하나의 요구사항을 충족하는 코드 + 설정 + AWS 리소스 단위</td>
</tr>
<tr>
<td>Workload</td>
<td>비즈니스 가치를 전달하는 Component들의 집합</td>
</tr>
<tr>
<td>Architecture</td>
<td>Component들이 Workload 내에서 상호작용하는 방식</td>
</tr>
<tr>
<td>Milestone</td>
<td>아키텍처 발전 과정의 주요 변곡점을 저장한 스냅샷</td>
</tr>
<tr>
<td>HRI</td>
<td>High Risk Issue — 즉시 개선이 필요한 위험 항목</td>
</tr>
<tr>
<td>MRI</td>
<td>Medium Risk Issue — 계획적 개선이 필요한 위험 항목</td>
</tr>
<tr>
<td>WAFR</td>
<td>Well-Architected Framework Review — 아키텍처 검토 프로세스</td>
</tr>
<tr>
<td>Lens</td>
<td>특정 도메인에 특화된 WAF 확장 가이드 (Serverless, ML, GenAI 등)</td>
</tr>
</tbody></table>
<br>

<h3 id="security-pillar">Security Pillar</h3>
<h3 id="01-security-pillar-7대-설계-원칙">01. Security Pillar 7대 설계 원칙</h3>
<ul>
<li>AWS1(계정 보안 및 관리)에서 학습한 내용을 WAF Security Pillar 관점으로 재구성</li>
<li>Security는 다른 Pillar와 절대 트레이드오프하지 않는 유일한 영역</li>
</ul>
<pre><code>1. 강력한 자격증명 기반     
── IAM / MFA / 최소 권한 원칙

2. 추적 가능성 확보         
── CloudTrail / Config / VPC Flow Logs

3. 모든 레이어 보안 적용    
── VPC SG / NACL / WAF / Shield

4. 보안 모범 사례 자동화    
── Config Rules / Security Hub
자동 규정 준수 평가

5. 전송·저장 데이터 보호    
── KMS / TLS / S3 암호화

6. 사람의 데이터 접근 최소화 
── 프로세스 기반 접근, 감사 추적
IAM Role 기반 서비스 간 접근

7. 보안 이벤트 대비        
── GuardDuty / Incident Playbook
위협 자동 탐지 및 대응</code></pre><br>

<h3 id="02-iam-최소-권한-및-access-analyzer">02. IAM 최소 권한 및 Access Analyzer</h3>
<ul>
<li>IAM 정책 평가는 명시적 Deny → SCP Deny → 명시적 Allow 순으로 적용</li>
<li>어디에도 명시되지 않은 요청은 암묵적으로 거부됨(묵시적 Deny)</li>
</ul>
<br>

<h3 id="03-kms--s3-암호화">03. KMS + S3 암호화</h3>
<p>S3 암호화 옵션 비교 및 KMS CMK(Customer Managed Key) 기반 감사 가능한 암호화 체계 구성</p>
<br>

<p><strong>키 유형 비교</strong></p>
<table>
<thead>
<tr>
<th>구분</th>
<th>AWS Owned Key</th>
<th>AWS Managed Key</th>
<th>Customer Managed Key (CMK)</th>
</tr>
</thead>
<tbody><tr>
<td>관리 주체</td>
<td>AWS</td>
<td>AWS</td>
<td><strong>고객</strong></td>
</tr>
<tr>
<td>키 정책 변경</td>
<td>x</td>
<td>x</td>
<td>o</td>
</tr>
<tr>
<td>직접 암호화 호출</td>
<td>x</td>
<td>x</td>
<td>o</td>
</tr>
<tr>
<td>CloudTrail 감사</td>
<td>불가</td>
<td>제한적</td>
<td><strong>완전 감사</strong></td>
</tr>
<tr>
<td>키 로테이션 제어</td>
<td>x</td>
<td>자동(3년)</td>
<td>직접 설정</td>
</tr>
<tr>
<td>비용</td>
<td>무료</td>
<td>사용량 과금</td>
<td>월정액 + 사용량</td>
</tr>
</tbody></table>
<br>

<h3 id="operational-excellence-pillar">Operational Excellence Pillar</h3>
<h3 id="01-oe-pillar-5대-설계-원칙">01. OE Pillar 5대 설계 원칙</h3>
<p>비즈니스 가치를 창출하기 위해 시스템을 운영하고 모니터링하며 지속적으로 개선하는 능력</p>
<pre><code>1. Operations as Code        
── CloudFormation / Terraform
인프라와 운영 절차를 코드로 정의

2. 잦은 소규모 변경          
── CI/CD 파이프라인
큰 배포 대신 작고 자주 배포

3. 운영 절차 지속 개선       
── Runbook / Playbook 정기 업데이트
장애 사후 분석(Blameless Postmortem)

4. 장애 예상                 
── AWS FIS (Fault Injection Simulator)
장애를 미리 시뮬레이션하여 대비

5. 모든 운영 실패에서 학습   
── CloudWatch + CloudTrail 통합 분석
장애를 비난 없이 학습 기회로 전환</code></pre><br>

<h3 id="02-aws-config--리소스-규정-준수">02. AWS Config — 리소스 규정 준수</h3>
<p>AWS 리소스 구성을 지속적으로 기록하고 규정 준수 여부를 자동 평가</p>
<pre><code>AWS 리소스 변경 발생
    │
    ▼
Config 변경 감지 → Configuration History (S3 저장)
    │
    ▼
Config Rules 평가
    │
    ├── Compliant (규정 준수) → 정상
    └── Non-Compliant (위반) → SNS 알림 → 자동 교정 가능</code></pre><br>

<h3 id="reliability-pillar">Reliability Pillar</h3>
<h3 id="reliability-pillar-5대-설계-원칙">Reliability Pillar 5대 설계 원칙</h3>
<ul>
<li>의도한 기능을 정확히 수행하고 장애에서 빠르게 복구하는 능력</li>
<li>장애를 막는 것이 아니라, 장애를 가정하고 설계하는 것이 핵심</li>
</ul>
<pre><code>1. 장애 복구 자동화      
── CloudWatch Alarm → Auto Scaling
정상 상태 정의 후 자동 교정

2. 복구 절차 테스트      
── RDS Failover 강제 실행
AWS FIS (Fault Injection Simulator) — 고급

3. 수평 확장             
── ALB + ASG Multi-AZ
단일 AZ 장애 → 다른 AZ로 자동 트래픽 분산

4. 용량 추측 금지        
── Target Tracking Scaling Policy
실측 기반으로 인스턴스 수 자동 조정

5. 자동화 관리 변경      
── IaC (CloudFormation / Terraform)
수동 변경 금지, 코드 기반 변경만 허용</code></pre><br>

<h3 id="performance-efficiency-pillar">Performance Efficiency Pillar</h3>
<h3 id="01-pe-pillar-5대-설계-원칙">01. PE Pillar 5대 설계 원칙</h3>
<ul>
<li>수요 변화와 기술 발전에 맞춰 컴퓨팅 리소스를 효율적으로 사용하는 능력</li>
<li>많은 리소스를 쓰는 것이 아니라, 적절한 리소스를 올바른 방식으로 사용하는 것이 핵심</li>
</ul>
<pre><code>1. 민주화된 고급 기술    
── ElastiCache / CloudFront 등 관리형 서비스 활용
복잡한 기술을 직접 구현하지 않고 즉시 활용

2. 글로벌 배포           
── CloudFront Edge Location (전 세계 400+ 거점)

3. 서버리스 아키텍처     
── Lambda / Fargate
서버 관리 비용 제거 + 자동 스케일 + 사용량 기반 과금

4. 빈번한 실험           
── Compute Optimizer
다양한 인스턴스 타입 테스트 및 권장사항 확인

5. 기계적 동감 (Mechanical Sympathy)        
── 워크로드 특성을 이해하고 그에 맞는 기술 선택
메모리 집약 → r 계열 / CPU 집약 → c 계열</code></pre><br>

<h3 id="02-compute-optimizer">02. Compute Optimizer</h3>
<p>머신러닝으로 EC2, RDS, Lambda 사용 패턴을 분석하여 최적의 인스턴스 타입과 크기를 추천</p>
<br>

<h3 id="cost-optimization-pillar">Cost Optimization Pillar</h3>
<h3 id="01-co-pillar-5대-설계-원칙">01. CO Pillar 5대 설계 원칙</h3>
<ul>
<li>비즈니스 가치를 최대화하면서 불필요한 지출을 제거하는 능력</li>
<li>가장 저렴한 것을 쓰는 것이 아니라, 필요한 것만큼만 쓰는 것이 핵심</li>
</ul>
<pre><code>1. 클라우드 재무 관리    
── 비용을 기술 팀의 책임으로 내재화
태그 전략으로 팀/프로젝트별 비용 추적

2. 소비 모델 적용        
── On-Demand / Reserved / Savings Plans / Spot
워크로드 특성에 맞는 구매 모델 선택

3. 전체 효율성 측정      
── Cost Explorer
사용자당 AWS 비용 등 비즈니스 지표와 연동

4. 무차별적 작업 제거    
── 관리형 서비스 (RDS / ElastiCache / Fargate)
서버 운영 오버헤드 제거

5. 비용 분석 및 귀속     
── 태그 기반 팀/프로젝트별 비용 분류
Cost Explorer 필터로 부서별 청구 가능</code></pre><br>

<h3 id="02-ec2-구매-모델-비교">02. EC2 구매 모델 비교</h3>
<pre><code>할인율 비교

On-Demand    ████████████████  100% (기준)
             언제든 시작/중지, 약정 없음

Reserved 1년 ████████          ~60% (40% 절감)
             1년 약정, 예측 가능한 워크로드

Reserved 3년 ██████            ~40% (60% 절감)
             3년 약정, 장기 안정 워크로드

Savings Plans████████          ~60% (40% 절감)
             인스턴스 타입 무관, 유연한 약정

Spot         ████              최대 10% (90% 절감)
             언제든 회수될 수 있음, 내결함성 필수</code></pre><hr>
<h3 id="로컬-파일-업로드다운로드spring-boot-파일-서비스">로컬 파일 업로드/다운로드(Spring Boot 파일 서비스)</h3>
<h3 id="01-파일-서비스-개념">01. 파일 서비스 개념</h3>
<p>웹 애플리케이션에서 파일을 처리하는 방법은 크게 세 가지</p>
<br>

<pre><code>[파일 저장 방식 비교]

  클라이언트
      │
      ▼ HTTP Multipart Request
  Spring Boot
      │
      ├──► 로컬 디스크    : 서버 내부 경로에 직접 저장
      │                    서버가 죽으면 파일도 사라짐
      │
      ├──► DB (Blob)      : 파일 바이너리를 DB에 저장
      │                    파일 크기 제한, 성능 저하
      │
      └──► 오브젝트 스토리지 : AWS S3 등 외부 스토리지 사용
                              무제한 확장, 고가용성</code></pre><br>

<h4 id="파일-저장-방식-비교">파일 저장 방식 비교</h4>
<table>
<thead>
<tr>
<th>방식</th>
<th>장점</th>
<th>단점</th>
<th>적합한 상황</th>
</tr>
</thead>
<tbody><tr>
<td>로컬 디스크</td>
<td>구현 단순, 빠른 I/O</td>
<td>서버 의존적, 확장 불가</td>
<td>개발/테스트 환경</td>
</tr>
<tr>
<td>DB Blob</td>
<td>트랜잭션 관리 편리</td>
<td>성능 저하, DB 용량 증가</td>
<td>소용량 파일, 강한 정합성 필요 시</td>
</tr>
<tr>
<td>S3 오브젝트 스토리지</td>
<td>무한 확장, 고가용성</td>
<td>네트워크 I/O 필요</td>
<td>프로덕션 환경 (표준)</td>
</tr>
</tbody></table>
<br>

<h4 id="multipartfile-처리-흐름">MultipartFile 처리 흐름</h4>
<pre><code>[클라이언트 → Spring Boot 내부 처리 흐름]

  브라우저 (HTML Form)
      │
      │  POST /file-upload
      │  Content-Type: multipart/form-data
      ▼
  DispatcherServlet
      │
      ▼
  MultipartResolver ──► 요청을 파싱하여 MultipartFile 객체 생성
      │
      ▼
  @Controller / @RestController
      │
      │  @RequestPart(&quot;file&quot;) MultipartFile file
      ▼
  Service Layer
      │
      ├──► file.getOriginalFilename()  : 원본 파일명
      ├──► file.getContentType()       : MIME 타입
      ├──► file.getSize()              : 파일 크기 (bytes)
      └──► file.transferTo(File)       : 디스크에 저장</code></pre><br>

<h3 id="02-주요-객체-및-문법">02. 주요 객체 및 문법</h3>
<h3 id="어노테이션">어노테이션</h3>
<h3 id="requestpart-vs-requestparam"><code>@RequestPart</code> vs <code>@RequestParam</code></h3>
<p>멀티파트 요청에서 파일 또는 데이터를 바인딩할 때 사용</p>
<table>
<thead>
<tr>
<th>어노테이션</th>
<th>처리 대상</th>
<th>주요 사용 상황</th>
</tr>
</thead>
<tbody><tr>
<td><code>@RequestPart</code></td>
<td>multipart/form-data의 각 파트</td>
<td><code>MultipartFile</code> 또는 JSON 파트 바인딩</td>
</tr>
<tr>
<td><code>@RequestParam</code></td>
<td>쿼리 파라미터 또는 단순 multipart 필드</td>
<td>문자열·숫자 파라미터, 단순 파일</td>
</tr>
</tbody></table>
<br>

<h3 id="03-로컬-파일-업로드-구현">03. 로컬 파일 업로드 구현</h3>
<p>Spring MVC에서 파일 업로드는 <code>@RequestPart</code> 또는 <code>@RequestParam</code>으로 <code>MultipartFile</code>을 받아 처리</p>
<pre><code>[업로드 처리 흐름]

  POST /file-upload (multipart/form-data)
          │
          ▼
  FileController.uploadFile()
          │
          ├── 1. 저장 경로 확인 및 생성 (mkdir)
          ├── 2. UUID + 원본파일명 조합
          ├── 3. file.transferTo() 로 디스크 저장
          └── 4. 결과 응답</code></pre><br>

<h3 id="04-로컬-파일-다운로드-구현">04. 로컬 파일 다운로드 구현</h3>
<p>파일 다운로드는 서버에서 파일을 읽어 HTTP 응답 스트림으로 전달하는 방식</p>
<pre><code>[다운로드 처리 흐름]

  GET /file-download?fileName=xxx
          │
          ▼
  FileController.downloadFile()
          │
          ├── 1. 파일 경로 조합 (savePath + fileName)
          ├── 2. Files.newInputStream() → InputStreamResource 변환
          ├── 3. HttpHeaders 설정
          │       Content-Type: application/octet-stream
          │       Content-Disposition: attachment; filename=&quot;원본파일명&quot;
          └── 4. ResponseEntity&lt;Resource&gt; 반환</code></pre><br>

<h3 id="05-로컬-파일-삭제-구현">05. 로컬 파일 삭제 구현</h3>
<p>파일 삭제는 <code>File.delete()</code> 또는 <code>Files.deleteIfExists()</code>로 처리</p>
<br>

<h3 id="spring-boot-파일-서비스-aws-s3-연동"><strong>Spring Boot 파일 서비스 AWS S3 연동</strong></h3>
<h3 id="01-aws-s3-핵심-개념"><strong>01. AWS S3 핵심 개념</strong></h3>
<ul>
<li>Amazon S3(Simple Storage Service)는 AWS에서 제공하는 오브젝트 스토리지 서비스</li>
<li>파일을 버킷(Bucket) 안에 오브젝트(Object) 형태로 저장하며, 각 오브젝트는 키(Key) 로 식별</li>
</ul>
<br>

<h4 id="s3-요청-흐름">S3 요청 흐름</h4>
<pre><code>[로컬 개발 환경 — IAM User 자격증명 사용]

 Spring Boot (로컬)
 │
 │ ~/.aws/credentials 또는 환경변수로 인증
 ▼
 AWS SDK v2 (S3Client)
 │
 │ HTTPS 요청
 ▼
 AWS S3 서울 리전 (ap-northeast-2)
 └── Bucket: my-app-bucket
 └── s3_data/uuid_file.txt

---
[EC2 배포 환경 — IAM Role 사용]

 EC2 (Spring Boot JAR 실행)
 │
 │ Instance Profile(IAM Role) 자동 인증 → 자격증명 파일 불필요
 ▼
 AWS SDK v2 (S3Client)
 │
 ▼
 AWS S3</code></pre><br>

<h3 id="02-주요-객체-및-문법-1"><strong>02. 주요 객체 및 문법</strong></h3>
<h3 id="s3client"><strong>S3Client</strong></h3>
<p>S3 버킷·오브젝트에 대한 모든 동기 API 요청을 처리하는 핵심 클라이언트</p>
<pre><code>// 생성 — 빌더 패턴 사용
S3Client s3Client = S3Client.builder()
 .region(Region.AP_NORTHEAST_2) // 서울 리전 지정
 // .credentialsProvider(...) // 생략 시 DefaultCredentialsProvider 자동 사용
 .build();

// DefaultCredentialsProvider 자동 탐색 순서:
// 1. 환경변수 (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)
// 2. ~/.aws/credentials 파일
// 3. EC2 IAM Role (Instance Profile)
// → 로컬과 EC2 모두 코드 변경 없이 동작하는 이유</code></pre><pre><code>// 주요 메서드
s3Client.putObject(request, requestBody); // 파일 업로드
s3Client.getObject(request); // 파일 다운로드 (스트림 반환)
s3Client.deleteObject(request); // 파일 삭제
s3Client.listObjectsV2(request); // 버킷 내 오브젝트 목록 조회
s3Client.headObject(request); // 오브젝트 메타데이터만 조회 (파일 존재 확인)
s3Client.copyObject(request); // 오브젝트 복사
s3Client.close(); // 리소스 해제 (Bean으로 관리 시 불필요)</code></pre><br>

<h3 id="s3presigner"><strong>S3Presigner</strong></h3>
<ul>
<li><strong>Presigned URL을 생성</strong>하는 전용 클라이언트</li>
<li>S3Client와 별도로 존재하며, Spring Cloud AWS를 사용해도 AutoConfiguration 대상이 아니므로 <strong>반드시 Bean으로 직접 등록</strong></li>
</ul>
<br>

<h3 id="요청-객체-request"><strong>요청 객체 (Request)</strong></h3>
<p>모든 S3 API 호출은 <strong>요청 객체를 빌더 패턴으로 생성</strong>하여 클라이언트에 전달</p>
<ul>
<li><strong>PutObjectRequest :</strong> S3에 <strong>파일을 업로드</strong>할 때 사용하는 요청 객체</li>
<li><strong>GetObjectRequest :</strong> S3에서 <strong>파일을 다운로드</strong>할 때 사용하는 요청 객체</li>
<li><strong>DeleteObjectRequest :</strong> S3에서 <strong>파일을 삭제</strong>할 때 사용하는 요청 객체</li>
<li><strong>ListObjectsV2Request :</strong> S3 버킷 내 <strong>오브젝트 목록을 조회</strong>할 때 사용하는 요청 객체</li>
</ul>
<br>

<h3 id="요청-바디-객체"><strong>요청 바디 객체</strong></h3>
<ul>
<li><strong>RequestBody :</strong> putObject() 호출 시 전송할 <strong>실제 파일 데이터를 감싸는 래퍼 클래스</strong></li>
</ul>
<br>

<h3 id="응답-객체-response"><strong>응답 객체 (Response)</strong></h3>
<ul>
<li><strong>ResponseInputStream<GetObjectResponse> :</strong> s3Client.getObject() 호출 시 반환되는 <strong>S3 오브젝트 스트림 응답</strong></li>
</ul>
<br>

<h3 id="03-s3service-구현"><strong>03. S3Service 구현</strong></h3>
<h3 id="개념-1"><strong>개념</strong></h3>
<p>DB 없이 파일 메타데이터를 메모리 Map으로 임시 관리</p>
<pre><code>[파일 처리 흐름 — DB 없이 메모리 Map 사용]

 업로드 요청 (MultipartFile)
 │
 ▼
 S3Service.uploadS3File()
 ├── UUID 생성 → savedFileName
 ├── S3 putObject (버킷/키/스트림)
 └── fileStore Map에 저장
 key : fileNo (1, 2, 3...)
 value : savedFileName (UUID_원본명)

 다운로드 요청 (fileNo)
 │
 ▼
 S3Service.downloadS3File()
 ├── fileStore.get(fileNo) → savedFileName 조회
 └── S3 getObject → ResponseInputStream → Resource

 ️ 메모리 Map은 서버 재시작 시 초기화됨
 → PART 3에서 RDS 연결 후 DB로 전환 예정</code></pre><br>

<h3 id="04-presigned-url"><strong>04. Presigned URL</strong></h3>
<ul>
<li>Presigned URL은 S3 버킷에 <strong>임시 접근 권한</strong>을 부여하는 서명된 URL</li>
<li>클라이언트가 AWS 자격증명 없이도 특정 시간 동안 파일을 업로드하거나 다운로드할 수 있음</li>
</ul>
<pre><code>[Presigned URL 동작 흐름]

 클라이언트 Spring Boot AWS S3
 │ │ │
 │ GET /api/s3/presign │ │
 │ ?fileName=test.jpg │ │
 │ ──────────────────────► │ │
 │ │ S3Presigner로 URL 생성│
 │ │ (서명 포함, 만료시간) │
 │ Presigned URL 반환 │ │
 │ ◄────────────────────── │ │
 │ │ │
 │ PUT [presigned URL] │ │
 │ (파일 직접 전송) │ │
 │ ──────────────────────────────────────────────► │
 │ │ 파일 저장 완료│
 │ ◄────────────────────────────────────────────── │

클라이언트 → S3 직접 업로드 (Spring Boot 서버 경유 없음)
서버 부하 감소, 대용량 파일에 적합
URL 만료 후에는 접근 불가 (최대 7일)</code></pre><br>

<h4 id="presigned-url-vs-서버-경유-업로드-비교">Presigned URL vs 서버 경유 업로드 비교</h4>
<table>
<thead>
<tr>
<th>방식</th>
<th>흐름</th>
<th>장점</th>
<th>단점</th>
</tr>
</thead>
<tbody><tr>
<td>서버 경유 업로드</td>
<td>클라이언트 → Spring Boot → S3</td>
<td>서버에서 검증·가공 가능</td>
<td>서버 메모리·대역폭 사용</td>
</tr>
<tr>
<td>Presigned URL 업로드</td>
<td>클라이언트 → S3 직접</td>
<td>서버 부하 없음, 대용량 적합</td>
<td>서버 사이드 검증 어려움</td>
</tr>
</tbody></table>
<br>

<h3 id="s3-cors-설정"><strong>S3 CORS 설정</strong></h3>
<h3 id="개념-2"><strong>개념</strong></h3>
<ul>
<li>Presigned URL을 사용해 <strong>브라우저에서 S3로 직접 업로드</strong>할 때, S3 버킷에 CORS 정책이 없으면 브라우저가 요청을 차단</li>
</ul>
<pre><code>[CORS가 없을 때 — 브라우저 차단]

 브라우저 (localhost:8080)
 │ PUT https://my-bucket.s3.amazonaws.com/...
 │ OPTIONS (Preflight 요청)
 ▼
 AWS S3
 │ 응답: CORS 헤더 없음
 ▼
 브라우저 → 차단! (Cross-Origin Request Blocked)

[CORS 설정 후 — 정상 동작]

 브라우저 (localhost:8080)
 │ OPTIONS (Preflight 요청)
 ▼
 AWS S3
 │ 응답: Access-Control-Allow-Origin: *
 ▼
 브라우저 → 허용
 │ PUT (실제 파일 업로드)
 ▼
 AWS S3 → 저장 완료</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[멋쟁이사자처럼부트캠프 클라우드 엔지니어링 5기 [11주차]]]></title>
            <link>https://velog.io/@its-jihyeon/%EB%A9%8B%EC%9F%81%EC%9D%B4%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A7%81-5%EA%B8%B0-11%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@its-jihyeon/%EB%A9%8B%EC%9F%81%EC%9D%B4%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A7%81-5%EA%B8%B0-11%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Sat, 11 Apr 2026 07:48:44 GMT</pubDate>
            <description><![CDATA[<h3 id="aws-계정-구조와-보안">AWS 계정 구조와 보안</h3>
<h3 id="01-aws-계정-구조">01. AWS 계정 구조</h3>
<ul>
<li>모든 리소스, 결제, 권한의 <strong>최상위 경계(Boundary)</strong></li>
<li>내부에는 여러 계층의 자격증명 주체(Identity)가 존재</li>
</ul>
<br>

<p><strong>AWS 계정 관리 비교</strong></p>
<ul>
<li>Root User : 최상위 관리자</li>
<li>IAM : 사용하고자 하는 계정을 만들어서 권한을 부여하고 내부 서비스를 이용할 수 있게 함</li>
<li>IAM Identity Center : 중앙 집중형 인간 사용자 관리</li>
</ul>
<br>

<h3 id="02-공동-책임-모델-shared-responsibility-model">02. 공동 책임 모델 (Shared Responsibility Model)</h3>
<ul>
<li><strong>“AWS와 사용자가 보안을 나눠 책임진다”</strong></li>
</ul>
<table>
<thead>
<tr>
<th>서비스 유형</th>
<th>AWS 책임 범위</th>
<th>고객 책임 범위</th>
</tr>
</thead>
<tbody><tr>
<td>EC2 (IaaS)</td>
<td>하드웨어, 하이퍼바이저</td>
<td>OS, 미들웨어, 앱, 데이터</td>
</tr>
<tr>
<td>RDS (PaaS)</td>
<td>하드웨어 + DB 엔진 패치</td>
<td>DB 설정, 접근 제어, 데이터</td>
</tr>
<tr>
<td>Lambda (SaaS형)</td>
<td>하드웨어 + 런타임 + OS</td>
<td>코드, IAM 권한, 환경 변수</td>
</tr>
<tr>
<td>S3</td>
<td>스토리지 인프라</td>
<td>버킷 정책, 퍼블릭 접근 차단, 암호화</td>
</tr>
</tbody></table>
<br>

<h3 id="03-최소-권한-원칙-principle-of-least-privilege">03. 최소 권한 원칙 (Principle of Least Privilege)</h3>
<ul>
<li>모든 IAM 정책 설계의 기본 원칙:</li>
<li>*&quot;필요한 작업을 수행하기 위한 최소한의 권한만 부여&quot;**</li>
</ul>
<br>

<h3 id="04-자격증명-유형-비교">04. 자격증명 유형 비교</h3>
<pre><code class="language-markdown">[장기 자격증명 — Long-term Credentials]
┌───────────────────────────────────────────────┐
│  IAM User Access Key                          │
│  ├─ Access Key ID:     AKIAIOSFODNN7EXAMPLE   │
│  └─ Secret Access Key: wJalrXUtnFEMI/...      │
│                                               │
│  특징: 만료 없음 → 유출 시 영구 피해            │
│  저장 위치: ~/.aws/credentials (로컬 파일)     │
│  위험: GitHub 실수 커밋 → 즉시 크립토 마이닝    │
└───────────────────────────────────────────────┘

[임시 자격증명 — Temporary Credentials (STS)]
┌───────────────────────────────────────────────┐
│  AWS STS (Security Token Service) 발급        │
│  ├─ Access Key ID                             │
│  ├─ Secret Access Key                         │
│  └─ Session Token  (필수 — 임시 증명)          │
│                                               │
│  특징: TTL 존재 (15분 ~ 36시간)                │
│  사용처: IAM Role, Identity Center, EC2 등    │
│  유출 시: TTL 만료 후 자동 무효화              │
└───────────────────────────────────────────────┘</code></pre>
<br>

<h3 id="root-계정-보안-설정">Root 계정 보안 설정</h3>
<h3 id="01-root-사용자">01. Root 사용자</h3>
<ul>
<li>AWS 계정을 처음 만들 때 생성되는 <strong>유일한 최고 권한 자격증명</strong></li>
<li>오직 Root 만 할 수 있는 작업이 존재</li>
</ul>
<pre><code class="language-markdown">[Root User 권한 범위]

         IAM 정책 적용 범위
┌─────────────────────────────────────┐
│                                     │
│   IAM User  ──→  IAM Policy 적용    │
│   IAM Role  ──→  IAM Policy 적용    │
│                                     │
│   Root User ──→  IAM Policy 무시    │ ← 모든 정책 우회
│             ──→  SCP 무시           │ ← 조직 정책도 우회
│             ──→  Permission         │
│                  Boundary 무시      │
└─────────────────────────────────────┘

[Root만 가능한 작업 목록]
- AWS 계정 해지
- IAM 활성화 (최초 1회)
- 결제 정보 / 지원 플랜 변경
- Route 53 도메인 이전
- S3 버킷 정책으로 잠긴 버킷 복구
- GovCloud 활성화</code></pre>
<br>

<h3 id="02-mfa-유형-비교">02. MFA 유형 비교</h3>
<pre><code class="language-markdown">┌─────────────────────────────────────────────────────────┐
│                  MFA 유형 비교                          │
├─────────────────┬───────────────┬───────────────────────┤
│  가상 MFA       │  하드웨어 MFA  │  Passkey / FIDO2      │
│  (Authenticator)│  (YubiKey 등) │  (보안 키 / Passkey)   │
├─────────────────┼───────────────┼───────────────────────┤
│ 스마트폰 앱      │ 물리적 장치   │ 물리 키 or 기기 내장   │
│ Google Auth     │ YubiKey       │ YubiKey (FIDO2)       │
│ MS Authenticator│ RSA Token     │ Touch ID, Face ID     │
│ Authy           │ Gemalto Token │ Windows Hello         │
├─────────────────┼───────────────┼───────────────────────┤
│ TOTP 기반       │ TOTP 기반      │ 공개키 암호화 기반     │
│ 피싱 취약       │ 피싱 취약      │  피싱 저항             │
├─────────────────┼───────────────┼───────────────────────┤
│ 무료            │ 유료 (장비)    │ 무료 (기기 내장) /     │
│                 │               │ 유료 (하드웨어 키)     │
└─────────────────┴───────────────┴───────────────────────┘</code></pre>
<br>

<p><strong>CloudShell</strong></p>
<pre><code class="language-markdown"># CloudShell
AWS 콘솔 안에 내장된 브라우저 기반 터미널.
AWS CLI, Git, Python, Node.js 등이 사전 설치되어 있고
콘솔 로그인 자격증명을 자동으로 사용

[비용 구조]
CloudShell 쉘 실행          →  무료 
1GB 홈 디렉토리 스토리지    →  무료 
aws iam / aws sts 명령 실행  →  무료  (조회 API는 무료)

CloudShell에서 EC2 생성     →  EC2 비용 발생 
CloudShell에서 S3 버킷 생성 →  S3 스토리지 비용 

# 접근 
[접근 경로]
AWS 콘솔 로그인
  → 상단 네비게이션 바
  → [&gt;_] 아이콘 클릭  (검색창 오른쪽)
  → 브라우저 하단에 터미널 창 열림

# 테스트 명령어
aws sts get-caller-identity
aws --version
aws configure get region

CloudShell은 비활성 상태 20~30분 후 세션이 자동 종료
홈 디렉토리(`~/`)에 저장한 파일은 세션 종료 후에도 유지되지만,
설치한 패키지나 환경 변수는 세션 재시작 시 초기화
긴 스크립트는 반드시 `~/` 경로에 파일로 저장</code></pre>
<table>
<thead>
<tr>
<th>항목</th>
<th>CloudShell</th>
<th>로컬 Ubuntu</th>
</tr>
</thead>
<tbody><tr>
<td>비용</td>
<td>완전 무료</td>
<td>완전 무료</td>
</tr>
<tr>
<td>설치</td>
<td>불필요</td>
<td>AWS CLI v2 설치 필요</td>
</tr>
<tr>
<td>자격증명</td>
<td>콘솔 자동 연동</td>
<td><code>aws configure</code> 수동 설정</td>
</tr>
<tr>
<td>Access Key 필요</td>
<td>불필요</td>
<td>필요</td>
</tr>
</tbody></table>
<br>

<pre><code class="language-markdown"># Root MFA 상태 점검
# Root Access Key 존재 여부 및 MFA 활성화 여부 확인

# 1단계: 자격증명 보고서 생성 요청
aws iam generate-credential-report

# 2단계: 보고서 다운로드 및 디코딩
aws iam get-credential-report \
  --query &#39;Content&#39; \
  --output text | base64 --decode &gt; credential-report.csv

# 3단계: Root 계정 행 확인 (첫 번째 데이터 행)
head -2 credential-report.csv

# 확인 항목:
# mfa_active          → true 
# access_key_1_active → false 
# access_key_2_active → false </code></pre>
<br>

<p><strong>결제 알림 &amp; 비용 이상 탐지 설정</strong></p>
<pre><code class="language-markdown">[비용 알림 구성 흐름]

AWS Budgets / Cost Anomaly Detection
        ↓ 임계값 초과 감지
    CloudWatch Alarm
        ↓
    SNS Topic
        ↓
  이메일 알림 발송</code></pre>
<br>

<h3 id="iam-사용자--역할--정책-관리">IAM 사용자 / 역할 / 정책 관리</h3>
<h3 id="01-iam-구성-요소-user--group--role--policy">01. IAM 구성 요소 (User / Group / Role / Policy)</h3>
<table>
<thead>
<tr>
<th>구성 요소</th>
<th>역할</th>
<th>주요 사용 사례</th>
</tr>
</thead>
<tbody><tr>
<td><strong>User</strong></td>
<td>장기 자격증명 보유 주체</td>
<td>CI/CD 서비스 계정 (비권장: 사람)</td>
</tr>
<tr>
<td><strong>Group</strong></td>
<td>User 묶음 — 정책 일괄 적용</td>
<td>팀별 권한 관리 (dev-team, ops-team)</td>
</tr>
<tr>
<td><strong>Role</strong></td>
<td>임시 자격증명 위임 주체</td>
<td>EC2, Lambda, 교차 계정 접근</td>
</tr>
<tr>
<td><strong>Policy</strong></td>
<td>권한 정의 JSON 문서</td>
<td>Allow/Deny 액션 및 리소스 정의</td>
</tr>
</tbody></table>
<br>

<p><strong>IAM 정책</strong> </p>
<p><strong>&quot;누가, 어떤 리소스에, 무엇을 할 수 있는가&quot;</strong> 를 정의하는 JSON 문서</p>
<ul>
<li>AWS의 모든 권한은 정책으로 제어</li>
<li>정책이 없으면 기본적으로 모든 접근은 <strong>거부</strong></li>
</ul>
<p><strong>우선순위: 명시적 Deny &gt; 명시적 Allow &gt; 묵시적 Deny</strong></p>
<br>

<h4 id="정책-json-문법-구조"><strong>정책 JSON 문법 구조</strong></h4>
<pre><code class="language-json">{
  &quot;Version&quot;: &quot;2012-10-17&quot;,       // 정책 언어 버전 — 항상 이 값 고정
  &quot;Statement&quot;: [                 // 권한 규칙 배열 — 여러 개 작성 가능
    {
      &quot;Sid&quot;: &quot;규칙ID&quot;,           // 선택값 — 규칙 식별용 이름
      &quot;Effect&quot;: &quot;Allow&quot;,         // Allow (허용) 또는 Deny (거부)
      &quot;Principal&quot;: &quot;*&quot;,          // 누가 — Resource 기반 정책에서만 사용
      &quot;Action&quot;: [                // 어떤 작업 — 서비스:작업 형태
        &quot;s3:GetObject&quot;,
        &quot;s3:PutObject&quot;
      ],
      &quot;Resource&quot;: [              // 어떤 리소스 — ARN 형식
        &quot;arn:aws:s3:::my-bucket/*&quot;
      ],
      &quot;Condition&quot;: {             // 추가 조건 — 선택값
        &quot;StringEquals&quot;: {
          &quot;aws:RequestedRegion&quot;: &quot;ap-northeast-2&quot;
        }
      }
    }
  ]
}</code></pre>
<br>

<h4 id="정책-설계-시-주의사항">정책 설계 시 주의사항</h4>
<table>
<thead>
<tr>
<th>#</th>
<th>잘못된 패턴</th>
<th>올바른 패턴</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td><code>&quot;Action&quot;: &quot;*&quot;</code> 전체 허용</td>
<td>필요한 Action만 명시</td>
</tr>
<tr>
<td>2</td>
<td><code>&quot;Resource&quot;: &quot;*&quot;</code> 전체 리소스</td>
<td>특정 ARN으로 제한</td>
</tr>
<tr>
<td>3</td>
<td>모든 사용자에게 AdministratorAccess</td>
<td>역할별 최소 권한 정책 분리</td>
</tr>
<tr>
<td>4</td>
<td>Inline Policy 남발</td>
<td>Customer Managed Policy 재사용</td>
</tr>
<tr>
<td>5</td>
<td>정책 검증 없이 바로 적용</td>
<td>Policy Simulator 검증 후 적용</td>
</tr>
<tr>
<td>6</td>
<td>Deny 없이 Allow만 작성</td>
<td>핵심 작업은 명시적 Deny 추가</td>
</tr>
</tbody></table>
<br>

<h3 id="02-iam-vs-iam-identity-center">02. IAM vs IAM Identity Center</h3>
<table>
<thead>
<tr>
<th>비교 항목</th>
<th>IAM User</th>
<th>IAM Identity Center</th>
</tr>
</thead>
<tbody><tr>
<td>자격증명 방식</td>
<td>장기 (Access Key)</td>
<td>임시 (STS Token)</td>
</tr>
<tr>
<td>멀티 계정 접근</td>
<td>계정마다 별도 User 생성</td>
<td>단일 포털에서 통합</td>
</tr>
<tr>
<td>사용자 관리 위치</td>
<td>각 계정 IAM</td>
<td>Identity Center 중앙 관리</td>
</tr>
<tr>
<td>퇴사자 처리</td>
<td>계정별 수동 삭제</td>
<td>Identity Center에서 1회 비활성화</td>
</tr>
<tr>
<td>MFA 관리</td>
<td>계정별 설정</td>
<td>Identity Center 통합 설정</td>
</tr>
<tr>
<td>학습 난이도</td>
<td>낮음 (입문)</td>
<td>중간 (Organizations 필요)</td>
</tr>
</tbody></table>
<br>

<h4 id="iam-access-analyzer-활성화">IAM Access Analyzer 활성화</h4>
<ul>
<li><strong>의도치 않게 외부에 노출된 리소스</strong>를 자동으로 탐지</li>
</ul>
<pre><code>[Access Analyzer가 탐지하는 항목]
- 퍼블릭 접근 허용된 S3 버킷
- 외부 계정에 접근 허용된 IAM Role
- 퍼블릭 접근 허용된 KMS 키
- 퍼블릭 접근 허용된 Lambda 함수
- 외부 노출된 SQS 큐 / SNS 토픽</code></pre><br>

<h3 id="계정-모니터링--실무-보안-설정">계정 모니터링 &amp; 실무 보안 설정</h3>
<h3 id="01-cloudtrail-개념-및-감사-로그">01. CloudTrail 개념 및 감사 로그</h3>
<ul>
<li>AWS 계정에서 발생하는 <strong>모든 API 호출을 기록</strong>하는 서비스
콘솔 클릭, CLI 명령, SDK 호출 — 모두 이벤트로 남음</li>
</ul>
<pre><code>[CloudTrail 동작 흐름]

  사용자 / 서비스
       │
       ▼
  API 호출 (Console / CLI / SDK)
       │
       ▼
  AWS CloudTrail ──────────────────▶  S3 버킷 (로그 저장)
       │                                    │
       │                              장기 보관 / 감사
       ▼
  CloudWatch Logs ─────────────────▶  알림 / 대시보드
       │
       ▼
  EventBridge ──────────────────────▶  자동화 (Lambda 등)</code></pre><br>

<h3 id="02-cloudwatch-보안-알림-설정--root-로그인-탐지">02. CloudWatch 보안 알림 설정 — Root 로그인 탐지</h3>
<ul>
<li>Root 계정 로그인은 비정상 이벤트이므로 즉시 알림</li>
</ul>
<pre><code>[Root 로그인 탐지 흐름]

CloudTrail 이벤트 (ConsoleLogin by Root)
        ↓
CloudWatch Logs → Metric Filter
        ↓
CloudWatch Alarm (임계값: 1회)
        ↓
SNS Topic → 이메일 알림</code></pre><hr>
<h3 id="클라우드-컴퓨팅">클라우드 컴퓨팅</h3>
<ul>
<li>인터넷을 통해 서버, 스토리지, 데이터베이스, 네트워킹 등 IT 자원을 <strong>필요한 만큼, 사용한 만큼</strong> 빌려 쓰는 방식</li>
</ul>
<br>

<h3 id="01-클라우드-서비스-모델-3가지">01. 클라우드 서비스 모델 3가지</h3>
<table>
<thead>
<tr>
<th>모델</th>
<th>풀네임</th>
<th>내가 관리</th>
<th>AWS가 관리</th>
<th>대표 예시</th>
</tr>
</thead>
<tbody><tr>
<td><strong>IaaS</strong></td>
<td>Infrastructure as a Service</td>
<td>OS, 미들웨어, 앱</td>
<td>하드웨어, 네트워크</td>
<td>EC2, S3</td>
</tr>
<tr>
<td><strong>PaaS</strong></td>
<td>Platform as a Service</td>
<td>앱 코드만</td>
<td>OS, 런타임, 인프라</td>
<td>Elastic Beanstalk, RDS</td>
</tr>
<tr>
<td><strong>SaaS</strong></td>
<td>Software as a Service</td>
<td>설정/사용만</td>
<td>모든 것</td>
<td>Gmail, Notion, Slack</td>
</tr>
</tbody></table>
<br>

<h3 id="02-nist-클라우드">02. NIST 클라우드</h3>
<ul>
<li>NIST (National Institute of Standards and Technology) 는 미국 국립표준기술연구소로, 클라우드 컴퓨팅의 국제 표준 정의 제공</li>
</ul>
<br>

<h4 id="5가지-필수-특성-essential-characteristics">5가지 필수 특성 (Essential Characteristics)</h4>
<table>
<thead>
<tr>
<th>#</th>
<th>특성</th>
<th>설명</th>
<th>실무 예시</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td><strong>온디맨드 셀프서비스</strong> (On-demand Self-service)</td>
<td>사람의 개입 없이 사용자가 직접 자원 provisioning</td>
<td>AWS 콘솔에서 EC2 즉시 생성</td>
</tr>
<tr>
<td>2</td>
<td><strong>광범위한 네트워크 접근</strong> (Broad Network Access)</td>
<td>다양한 기기(PC, 모바일)에서 네트워크를 통해 접근</td>
<td>어디서나 S3 버킷 접근</td>
</tr>
<tr>
<td>3</td>
<td><strong>자원 풀링</strong> (Resource Pooling)</td>
<td>여러 고객이 물리 자원을 공유 (멀티테넌시)</td>
<td>AWS 물리 서버를 여러 계정이 공유</td>
</tr>
<tr>
<td>4</td>
<td><strong>신속한 탄력성</strong> (Rapid Elasticity)</td>
<td>수요에 따라 자원을 빠르게 확장/축소</td>
<td>Auto Scaling, Karpenter</td>
</tr>
<tr>
<td>5</td>
<td><strong>측정 가능한 서비스</strong> (Measured Service)</td>
<td>사용량 자동 측정 → 사용한 만큼 과금</td>
<td>AWS 시간/GB 단위 청구</td>
</tr>
</tbody></table>
<br>

<h4 id="4가지-배포-모델-deployment-models">4가지 배포 모델 (Deployment Models)</h4>
<table>
<thead>
<tr>
<th>모델</th>
<th>설명</th>
<th>특징</th>
<th>사용 대상</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Public Cloud</strong></td>
<td>CSP가 인터넷으로 제공</td>
<td>비용 효율, 확장성 최고</td>
<td>스타트업, 일반 기업</td>
</tr>
<tr>
<td><strong>Private Cloud</strong></td>
<td>단일 조직 전용 인프라</td>
<td>보안/통제 강함</td>
<td>금융, 공공기관</td>
</tr>
<tr>
<td><strong>Community Cloud</strong></td>
<td>특정 커뮤니티 공유</td>
<td>공통 목적 조직</td>
<td>정부부처, 연구기관</td>
</tr>
<tr>
<td><strong>Hybrid Cloud</strong></td>
<td>Public + Private 혼합</td>
<td>유연성 + 보안 균형</td>
<td>대기업, 규제 산업</td>
</tr>
</tbody></table>
<br>

<h3 id="aws-글로벌-인프라--ec2-소개">AWS 글로벌 인프라 &amp; EC2 소개</h3>
<h3 id="01-aws-글로벌-인프라">01. AWS 글로벌 인프라</h3>
<pre><code>[AWS 글로벌 인프라 계층 구조]

  전 세계 (Global)
  │
  ├── 리전 (Region) — 지리적 독립 클러스터
  │     예) 서울, 도쿄, 버지니아, 프랑크푸르트
  │
  │     ├── 가용 영역 (AZ, Availability Zone)
  │     │     예) ap-northeast-2a / 2b / 2c / 2d
  │     │     └── 데이터센터 (1개 이상의 물리 건물)
  │     │           └── EC2 / RDS / EBS ...
  │     │
  │     └── 리전 내 글로벌 서비스 (VPC, S3, IAM ...)
  │
  └── 엣지 로케이션 (Edge Location)
        CloudFront CDN 캐시 노드 (400개+)
        → 사용자와 가장 가까운 위치에서 콘텐츠 전달</code></pre><br>

<h3 id="02-arn-amazon-resource-name-구조">02. ARN (Amazon Resource Name) 구조</h3>
<ul>
<li>AWS의 모든 리소스는 고유한 ARN으로 식별</li>
</ul>
<pre><code>ARN 형식:
arn:partition:service:region:account-id:resource

예시:
arn:aws:ec2:ap-northeast-2:123456789012:instance/i-0abcd1234efgh5678
│   │   │   │              │             │
│   │   │   │              │             └── 리소스 타입/ID
│   │   │   │              └── AWS 계정 ID
│   │   │   └── 리전 (서울)
│   │   └── 서비스 (EC2)
│   └── 파티션 (aws / aws-cn / aws-us-gov)
└── 고정값</code></pre><br>

<h3 id="03-ec2-elastic-compute-cloud">03. EC2 (Elastic Compute Cloud)</h3>
<ul>
<li>AWS의 가상 서버 서비스</li>
<li>몇 분 만에 생성하고 언제든 삭제 가능</li>
<li>다양한 운영체제(Linux, Windows 등) 선택 가능</li>
</ul>
<pre><code>[EC2 핵심 구성요소]

  EC2 인스턴스 (가상 서버)
  ├── AMI (Amazon Machine Image)
  │     → 운영체제 + 초기 설정 스냅샷
  │     → 예) Amazon Linux 2023, Ubuntu 24.04
  │
  ├── 인스턴스 타입 (하드웨어 사양)
  │     → 예) t2.micro (1vCPU, 1GB RAM)
  │
  ├── 스토리지 (EBS)
  │     → 예) 8GB gp3 SSD (루트 볼륨)
  │
  ├── 네트워크 (VPC + Subnet)
  │     → 어느 네트워크에 배치할지
  │
  └── 보안 (Security Group + Key Pair)
        → 어떤 포트를 열고, 누가 접근 가능한지</code></pre><table>
<thead>
<tr>
<th>용어</th>
<th>한 줄 설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>AMI</strong></td>
<td>인스턴스를 만들기 위한 OS 이미지 (Docker 이미지와 유사)</td>
</tr>
<tr>
<td><strong>인스턴스 타입</strong></td>
<td>CPU/메모리/네트워크 사양 조합</td>
</tr>
<tr>
<td><strong>Key Pair</strong></td>
<td>SSH 접속용 공개키/개인키 쌍</td>
</tr>
<tr>
<td><strong>Security Group</strong></td>
<td>인스턴스의 방화벽 (인바운드/아웃바운드 트래픽 제어)</td>
</tr>
<tr>
<td><strong>EBS</strong></td>
<td>EC2에 붙이는 가상 하드디스크</td>
</tr>
<tr>
<td><strong>Elastic IP</strong></td>
<td>인스턴스에 고정할 수 있는 공인 IP</td>
</tr>
<tr>
<td><strong>VPC</strong></td>
<td>EC2가 속하는 가상 사설 네트워크</td>
</tr>
</tbody></table>
<br>

<h3 id="ec2-인스턴스-타입">EC2 인스턴스 타입</h3>
<h4 id="인스턴스-타입-명명-규칙">인스턴스 타입 명명 규칙</h4>
<pre><code>m  8  g  .  2xlarge
│  │  │     │
│  │  │     └── 크기: nano &lt; micro &lt; small &lt; medium
│  │  │                    &lt; large &lt; xlarge &lt; 2xlarge ...
│  │  │
│  │  └── 추가 기능 코드
│  │        g = Graviton (ARM)    a = AMD
│  │        i = Intel             d = NVMe SSD 로컬 스토리지
│  │        n = 고성능 네트워크
│  │
│  └── 세대 (숫자가 클수록 최신 — 현재 7~8세대가 최신)
│
└── 패밀리
      m = General Purpose (범용)
      c = Compute Optimized (연산 집중)
      r = Memory Optimized (메모리 집중)
      t = Burstable (버스터블 — 프리 티어)
      p / g = GPU
      i = Storage Optimized</code></pre><br>

<h3 id="ami--키페어--security-group">AMI &amp; 키페어 &amp; Security Group</h3>
<h3 id="01-ami-amazon-machine-image">01. AMI (Amazon Machine Image)</h3>
<ul>
<li>EC2 인스턴스를 만들기 위한 <strong>운영체제 + 초기 설정의 스냅샷</strong></li>
</ul>
<pre><code>[Docker vs AMI 비교]

Docker 이미지                    AMI
─────────────────────────────────────────────
docker pull ubuntu:24.04    ←→   AMI 선택
docker run ubuntu:24.04     ←→   EC2 인스턴스 시작
컨테이너 (실행 중인 이미지)  ←→   EC2 인스턴스
Dockerfile                  ←→   AMI 빌드 스크립트
Docker Hub                  ←→   AWS Marketplace / AMI 카탈로그
─────────────────────────────────────────────</code></pre><pre><code>AMI 구성 요소

AMI (ami-xxxxxxxxxxxxxxxxx)
├── 루트 볼륨 스냅샷
│     → OS가 설치된 디스크 이미지 (기본 8~30GB)
│
├── 실행 권한
│     → 퍼블릭 (누구나 사용) / 프라이빗 (내 계정만)
│
├── 블록 디바이스 매핑
│     → /dev/xvda (루트), /dev/xvdb (추가 볼륨)
│
└── 가상화 타입
      → HVM (Hardware Virtual Machine) — 현재 표준</code></pre><br>

<h3 id="02-키페어-key-pair">02. 키페어 (Key Pair)</h3>
<ul>
<li>SSH로 EC2에 접속하기 위한 <strong>공개키/개인키 쌍</strong></li>
</ul>
<br>

<h3 id="03-security-group-보안-그룹">03. Security Group (보안 그룹)</h3>
<ul>
<li>EC2 인스턴스의 <strong>가상 방화벽</strong></li>
</ul>
<pre><code>[Security Group 동작 원리]

  인터넷           ┌──────────────┐    EC2 인스턴스
  ────────→        │Security Group│ →  ┌──────────┐
                   │              │    │ 앱 :8080  │
                   │ 인바운드 규칙 │    └──────────┘
                   │ SSH  22      │
                   │ HTTP 80      │
                   │ App  8080    │
                   │ DB   3306    │ ← 차단
                   │              │
                   │ 아웃바운드   │
                   │ 전체         │ ← 기본 전부 허용
                   └──────────────┘</code></pre><table>
<thead>
<tr>
<th>특성</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Stateful</strong></td>
<td>인바운드 허용 시 응답 트래픽은 자동 허용</td>
</tr>
<tr>
<td><strong>기본 아웃바운드</strong></td>
<td>모든 트래픽 허용 (0.0.0.0/0)</td>
</tr>
<tr>
<td><strong>화이트리스트</strong></td>
<td>명시된 것만 허용, 나머지 자동 차단</td>
</tr>
<tr>
<td><strong>공유 가능</strong></td>
<td>하나의 SG를 여러 인스턴스에 적용 가능</td>
</tr>
</tbody></table>
<br>

<h3 id="ebs--elastic-ip--네트워킹-기초">EBS &amp; Elastic IP &amp; 네트워킹 기초</h3>
<h3 id="01-ebs-elastic-block-store">01. EBS (Elastic Block Store)</h3>
<ul>
<li>EC2에 연결하는 <strong>가상 하드디스크</strong></li>
<li>EC2 인스턴스와 별개로 존재 → 인스턴스 종료 후에도 데이터 유지 (1:1 매핑)</li>
</ul>
<br>

<h3 id="02-elastic-ip-탄력적-ip">02. Elastic IP (탄력적 IP)</h3>
<ul>
<li>EC2 인스턴스에 <strong>고정 공인 IP</strong>를 연결해주는 서비스</li>
</ul>
<p>! 할당만 하고 연결하지 않으면 요금이 발생 - 사용하지 않을 때는 릴리즈(해제) 필수 !</p>
<br>

<h3 id="인스턴스-상태">인스턴스 상태</h3>
<p><strong>Stop vs Terminate vs Reboot 비교</strong>
| 항목 | Stop | Terminate | Reboot |
| --- | --- | --- | --- |
| 동작 | 인스턴스 중지 | 인스턴스 삭제 | OS 재시작 |
| EBS | 유지 (과금 지속) | 삭제* | 유지 |
| 퍼블릭 IP | 반환 (EIP 제외) | 반환 | 유지 (변경 없음) |
| 데이터 | RAM 데이터 소실 | 모든 데이터 소실 | 유지 |
| 복구 | 재시작 가능 | 복구 불가  | - |
| 인스턴스 ID | 유지 | 삭제 | 유지 |
| 과금 | 중지 후 없음 | 즉시 없음 | 계속 발생 |</p>
<br>

<h3 id="auto-scaling--alb">Auto Scaling &amp; ALB</h3>
<h3 id="auto-scaling-개념">Auto Scaling 개념</h3>
<pre><code>[Auto Scaling 동작 원리]

평상시 (CPU 낮음)         트래픽 급증               트래픽 감소
┌───┐ ┌───┐            ┌───┐ ┌───┐ ┌───┐ ┌───┐   ┌───┐ ┌───┐
│EC2│ │EC2│ Scale Out→ │EC2│ │EC2│ │EC2│ │EC2│→  │EC2│ │EC2│
└───┘ └───┘            └───┘ └───┘ └───┘ └───┘   └───┘ └───┘
  Min: 2개               Desired: 4개               Min: 2개</code></pre><table>
<thead>
<tr>
<th>구성 요소</th>
<th>역할</th>
<th>비유</th>
</tr>
</thead>
<tbody><tr>
<td><strong>시작 템플릿</strong></td>
<td>어떤 EC2를 만들지 정의</td>
<td>붕어빵 틀</td>
</tr>
<tr>
<td><strong>Auto Scaling 그룹(ASG)</strong></td>
<td>몇 대를 어디에 배치할지</td>
<td>붕어빵 가게</td>
</tr>
<tr>
<td><strong>조정 정책</strong></td>
<td>언제 늘리고 줄일지 기준</td>
<td>손님 많으면 틀 추가</td>
</tr>
<tr>
<td><strong>CloudWatch 경보</strong></td>
<td>조건 감지 (CPU 70% 초과 등)</td>
<td>손님 수 감지 센서</td>
</tr>
</tbody></table>
<br>

<p><strong>EC2 실습</strong></p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/b319939e-a380-4373-9051-d8107898a04b/image.png" alt=""></p>
<br>

<h3 id="aws-lambda">AWS Lambda</h3>
<ul>
<li><strong>서버를 프로비저닝하거나 관리하지 않고 코드를 실행</strong>하는 서버리스 컴퓨팅 서비스</li>
<li>이벤트가 발생할 때만 코드가 실행됨 → 유휴 시 비용 없음</li>
</ul>
<pre><code>[EC2 vs Lambda 실행 모델 비교]

EC2 방식
  서버 시작 (항상 실행 중)
      │
      ├── 요청 있을 때  → 처리
      ├── 요청 없을 때  → 대기 (비용 발생)
      └── 트래픽 급증   → 수동 또는 ASG로 확장 (시간 소요)

Lambda 방식
  이벤트 발생 ──▶ 실행 환경 자동 생성 ──▶ 코드 실행 ──▶ 종료
      │                                                    │
      └── 요청 없을 때: 실행 환경 없음 (비용 없음)          │
      └── 트래픽 급증: 수천 개 병렬 자동 실행              ─┘</code></pre><br>

<h3 id="01-lambda-핵심-구성-요소">01. Lambda 핵심 구성 요소</h3>
<pre><code>Lambda 함수 구성 요소

  Lambda Function
  ├── 코드 (Handler)
  │     → 진입점 함수: handler(event, context)
  │     → ZIP 파일 업로드 or 컨테이너 이미지
  │
  ├── 런타임 (Runtime)
  │     → Python 3.12 / Node.js 22 / Java 21
  │     → .NET 8 / Go / Ruby 3.3
  │     → 커스텀 런타임 (provided.al2023)
  │
  ├── 트리거 (Trigger / Event Source)
  │     → API Gateway, S3, DynamoDB Streams
  │     → SQS, SNS, EventBridge, CloudWatch Events
  │     → ALB, Kinesis, Cognito ...
  │
  ├── 권한 (IAM Role)
  │     → 실행 역할: CloudWatch 로그 기록, 다른 서비스 접근
  │
  └── 환경 설정
        → 메모리: 128MB ~ 10,240MB
        → 타임아웃: 1초 ~ 900초 (15분)
        → 환경 변수, VPC 설정, 레이어</code></pre><br>

<h3 id="02-lambda-호출-방식">02. Lambda 호출 방식</h3>
<pre><code>[Lambda 호출 방식 3가지]

1. 동기(Synchronous) 호출
   호출자 ──요청──▶ Lambda ──응답──▶ 호출자
   호출자가 응답을 기다림
   예) API Gateway, ALB, SDK 직접 호출
   실패 시: 호출자가 직접 오류 수신

2. 비동기(Asynchronous) 호출
   호출자 ──이벤트──▶ Lambda 내부 큐 ──▶ Lambda
   호출자는 수락 응답(202)만 받고 기다리지 않음
   예) S3 이벤트, SNS, EventBridge
   실패 시: 최대 2회 자동 재시도 → DLQ로 전송 가능

3. 스트림/폴링 기반 호출 (Event Source Mapping)
   Lambda ──polling──▶ SQS / Kinesis / DynamoDB Streams
   Lambda가 능동적으로 데이터 소스를 폴링
   예) SQS 큐 처리, Kinesis 스트림 처리</code></pre><br>

<h3 id="03-lambda-vs-ec2-vs-ecs-fargate-선택-기준">03. Lambda vs EC2 vs ECS Fargate 선택 기준</h3>
<table>
<thead>
<tr>
<th>비교 항목</th>
<th>Lambda</th>
<th>EC2</th>
<th>ECS Fargate</th>
</tr>
</thead>
<tbody><tr>
<td><strong>서버 관리</strong></td>
<td>불필요</td>
<td>직접 관리</td>
<td>불필요</td>
</tr>
<tr>
<td><strong>실행 시간</strong></td>
<td>최대 15분</td>
<td>무제한</td>
<td>무제한</td>
</tr>
<tr>
<td><strong>콜드 스타트</strong></td>
<td>있음</td>
<td>없음 (항상 실행)</td>
<td>있음 (컨테이너 시작)</td>
</tr>
<tr>
<td><strong>유휴 비용</strong></td>
<td>없음</td>
<td>있음</td>
<td>없음</td>
</tr>
<tr>
<td><strong>스케일링</strong></td>
<td>자동 (수천 병렬)</td>
<td>ASG 필요</td>
<td>자동</td>
</tr>
<tr>
<td><strong>적합한 워크로드</strong></td>
<td>이벤트 처리, 단기 작업</td>
<td>장시간 서버, 레거시</td>
<td>컨테이너 마이크로서비스</td>
</tr>
</tbody></table>
<hr>
<h3 id="aws-스토리지-개요-및-분류">AWS 스토리지 개요 및 분류</h3>
<h3 id="01-개념">01. 개념</h3>
<h3 id="스토리지의-3가지-계층">스토리지의 3가지 계층</h3>
<ul>
<li>온프레미스(물리 서버실)에서 쓰던 개념과 동일하지만, AWS에서는 이를 완전 관리형 서비스로 제공</li>
</ul>
<pre><code>[ 스토리지 3계층 개념도 ]

┌──────────────────────────────────────────────────────────────┐
│                   데이터 저장 방식                            │
├──────────────┬───────────────────┬───────────────────────────┤
│  Block       │  File             │  Object                   │
│  (블록)      │  (파일)           │  (객체)                    │
├──────────────┼───────────────────┼───────────────────────────┤
│  데이터를     │  구조로 저장      │  HTTP API로 접근           │
│  블록으로     │  NFS/SMB 프로토콜 │  무한 확장 가능            │
│  분할 저장    │  으로 접근        │                           │
├──────────────┼───────────────────┼───────────────────────────┤
│  AWS: EBS    │  AWS: EFS, FSx    │  AWS: S3                  │
│ (하드디스크)  │  (공유 네트워크   │  (인터넷 창고)             │
│              │   드라이브)       │                           │
└──────────────┴───────────────────┴───────────────────────────┘</code></pre><pre><code>[ AWS 스토리지 서비스 전체 구조 ]

AWS Storage Services
│
├── Object Storage (객체 스토리지)
│   └── Amazon S3
│       ├── General Purpose Bucket  ← 일반 사용 (기본)
│       ├── Directory Bucket        ← 저지연 (Express One Zone)
│       ├── Table Bucket            ← Apache Iceberg 분석 테이블
│       └── Vector Bucket           ← AI/RAG 벡터 저장
│
├── Block Storage (블록 스토리지)
│   └── Amazon EBS (Elastic Block Store)
│       ├── gp3  ← 범용 SSD (현재 기본 추천)
│       ├── io2  ← 고성능 IOPS (DB 전용)
│       ├── st1  ← 처리량 최적화 HDD
│       └── sc1  ← 콜드 HDD (저비용)
│
├── File Storage (파일 스토리지)
│   ├── Amazon EFS (Elastic File System)  ← Linux NFS, 자동 확장
│   └── Amazon FSx
│       ├── FSx for Windows File Server   ← Windows SMB
│       ├── FSx for Lustre                ← HPC/ML 고성능
│       ├── FSx for NetApp ONTAP          ← 엔터프라이즈 마이그레이션
│       └── FSx for OpenZFS               ← ZFS 마이그레이션
│
├── Hybrid/Edge (하이브리드)
│   ├── AWS Storage Gateway               ← 온프레미스 ↔ AWS 연결
│   └── AWS Snow Family                   ← 오프라인 대용량 데이터 이전
│       ├── Snowcone
│       ├── Snowball Edge
│       └── Snowmobile
│
└── Data Protection (데이터 보호)
    ├── AWS Backup                         ← 통합 백업 관리
    └── AWS DataSync                       ← 온라인 데이터 마이그레이션</code></pre><br>

<h3 id="02-주요-서비스별-핵심-특성">02. 주요 서비스별 핵심 특성</h3>
<table>
<thead>
<tr>
<th>구분</th>
<th>Amazon S3</th>
<th>Amazon EBS</th>
<th>Amazon EFS</th>
</tr>
</thead>
<tbody><tr>
<td><strong>스토리지 유형</strong></td>
<td>Object</td>
<td>Block</td>
<td>File (NFS)</td>
</tr>
<tr>
<td><strong>접근 방식</strong></td>
<td>HTTP REST API</td>
<td>OS 마운트 (직접 연결)</td>
<td>NFS 마운트 (네트워크)</td>
</tr>
<tr>
<td><strong>동시 접근</strong></td>
<td>무제한 클라이언트</td>
<td>기본 1개 EC2</td>
<td>수천 개 EC2 동시</td>
</tr>
<tr>
<td><strong>용량</strong></td>
<td>무제한</td>
<td>최대 64TB/볼륨</td>
<td>자동 확장 (페타바이트)</td>
</tr>
<tr>
<td><strong>내구성</strong></td>
<td>99.999999999% (11 nine)</td>
<td>99.8~99.9%</td>
<td>99.999999999%</td>
</tr>
<tr>
<td><strong>가용성</strong></td>
<td>99.99%</td>
<td>99.999%</td>
<td>99.99% (Regional)</td>
</tr>
<tr>
<td><strong>프리티어</strong></td>
<td>5GB/월</td>
<td>30GB/월</td>
<td>5GB/월</td>
</tr>
<tr>
<td><strong>주요 용도</strong></td>
<td>백업, 정적 파일, 데이터레이크</td>
<td>DB, OS 디스크</td>
<td>공유 파일시스템</td>
</tr>
</tbody></table>
<br>

<h3 id="03-심화">03. 심화</h3>
<h3 id="내구성durability-vs-가용성availability-차이">내구성(Durability) vs 가용성(Availability) 차이</h3>
<pre><code>[ 내구성 vs 가용성 ]

내구성 (Durability) = 데이터가 손실되지 않을 확률
  └── S3: 99.999999999% (11 nine)
      = 10,000,000개 객체 저장 시 1년에 0.000001개만 손실될 수 있음

가용성 (Availability) = 서비스에 접근 가능한 시간의 비율
  └── S3 Standard: 99.99%
      = 1년 365일 중 최대 약 52분만 접근 불가
      = 이 시간 동안 &quot;데이터는 있지만 접근이 안 될 수 있음&quot;

핵심: 내구성 ≠ 가용성
  - 내구성: 데이터 자체가 사라지지 않는 것
  - 가용성: 언제든 접근할 수 있는 것</code></pre><br>

<h3 id="amazon-s3-object-storage">Amazon S3 (Object Storage)</h3>
<h3 id="s3의-기본-구조">S3의 기본 구조</h3>
<ul>
<li>객체(Object) 단위로 데이터를 저장
파일시스템의 폴더/파일 구조와 달리, <strong>버킷(Bucket)</strong> 안에 <strong>객체(Object)</strong> 를 평탄한(flat) 구조로 저장</li>
</ul>
<pre><code>[ S3 구조 개념도 ]

AWS Account
└── Amazon S3 (리전 전체에 걸쳐 동작)
    │
    ├── Bucket: my-app-bucket-20260101      ← 전 세계 유일한 이름
    │   ├── Object: images/profile.jpg      ← Key + Value + Metadata
    │   ├── Object: logs/2026/03/app.log
    │   └── Object: backups/db-dump.sql
    │
    └── Bucket: my-static-website
        ├── Object: index.html
        └── Object: css/style.css

핵심 개념:
┌──────────────┬────────────────────────────────────────────┐
│ 구성 요소     │ 설명                                       │
├──────────────┼────────────────────────────────────────────┤
│ Bucket       │ 최상위 컨테이너. 전 세계에서 유일한 이름     │
│ Object       │ 저장 단위. 최대 크기: 50TB (2025 이후)      │
│ Key          │ 객체의 고유 식별자 (경로처럼 보이지만 문자열)│
│ Value        │ 실제 데이터 (바이너리)                      │
│ Metadata     │ 객체에 대한 정보 (Content-Type, 태그 등)    │
│ Version ID   │ 버전 관리 활성화 시 각 버전의 ID            │
└──────────────┴────────────────────────────────────────────┘</code></pre><br>

<p><strong>내부 동작 원리</strong></p>
<ul>
<li>S3는 내부적으로 각 객체를 최소 3개 AZ의 물리적으로 분리된 시설에 복제</li>
<li>Key 기반의 consistent hashing으로 저장 노드를 결정하며, 이것이 S3가 11 nine 내구성을 달성</li>
<li>객체 크기가 100MB 이상이면 Multipart Upload를 사용 권장</li>
<li>5GB 초과 객체는 반드시 Multipart Upload를 사용</li>
</ul>
<br>

<h3 id="s3-스토리지-클래스">S3 스토리지 클래스</h3>
<pre><code>[ S3 스토리지 클래스 — 접근 빈도에 따른 분류 ]

자주 접근 (Frequent Access)
│
├── S3 Standard
│   ├── 용도: 일반적인 웹 앱, 콘텐츠 배포
│   ├── 가용성: 99.99%
│   └── 비용: $0.025/GB/월 (서울)
│
├── S3 Intelligent-Tiering          접근 패턴 불명확
│   ├── 용도: 접근 패턴을 예측하기 어려운 데이터
│   └── 특징: AWS가 자동으로 최적 티어로 이동
│
가끔 접근 (Infrequent Access)
│
├── S3 Standard-IA         최소 보관: 30일
├── S3 One Zone-IA         단일 AZ, 재생성 가능 데이터
│
아카이빙 (Archiving)
│
├── S3 Glacier Instant Retrieval   검색: ms
├── S3 Glacier Flexible Retrieval  검색: 분~12시간
└── S3 Glacier Deep Archive        검색: 12~48시간, $0.002/GB/월</code></pre><br>

<p><strong>S3 데이터 일관성 보장</strong></p>
<p>중요: S3는 강한 일관성(strong consistency)을 제공하지만
     여러 클라이언트가 동시에 같은 객체를 수정하는
     &quot;동시성 제어(locking)&quot;는 지원하지 않음.
     → <strong>동시 수정이 필요하면 DynamoDB + S3 조합을 사용</strong></p>
<br>

<p><strong>버킷 생성</strong> </p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/b4851ad0-0daa-41bc-8863-89c1fb484883/image.png" alt=""></p>
<p><strong>파일 업로드</strong></p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/1cf7da1a-7c20-4dd1-a25c-8d234de57d49/image.png" alt=""></p>
<br>

<h3 id="object-lock-설정">Object Lock 설정</h3>
<pre><code>버킷 생성 시에만 활성화 가능! (나중에 변경 불가)

[Object Lock 활성화 버킷 생성]
경로: S3 → [버킷 만들기]

  기본 설정 진행 후 &quot;고급 설정&quot; 섹션에서:
  객체 잠금 → &quot;활성화&quot; 선택
  → &quot;확인했습니다&quot; 체크
  → [버킷 만들기]

[기본 보관 정책 설정]
  버킷 → [속성] 탭
  → 객체 잠금 → [편집]
  → 기본 보관: 활성화
  → 모드 선택:
    거버넌스(Governance) ← 실습용 (권한 있는 사용자 삭제 가능)
    규정 준수(Compliance) ← 실제 규제용 (누구도 삭제 불가!)
  → 기간: 30일
  → [변경 사항 저장]

[개별 객체 Legal Hold 적용]
  객체 클릭 → [객체 잠금] 탭
  → 법적 보존: 활성화
  → [저장]</code></pre><br>

<h3 id="s3-storage-lens">S3 Storage Lens</h3>
<pre><code>경로: S3 콘솔 → 좌측 [Storage Lens] → [대시보드]
      → [대시보드 생성]

설정:
  이름    : storage-lens-main
  홈 리전 : ap-northeast-2
  범위    : 계정 전체 포함 (기본값)

  지표:
    무료 지표: 기본 활성화 (비용 발생 없음)
    고급 지표: 생략 (유료)
  → [대시보드 생성]

24시간 후 데이터 수집되면 확인 가능:
  총 스토리지 / 객체 수 / 버킷별 사용량 분포
  스토리지 클래스별 분포 / 비용 최적화 권장 사항</code></pre><hr>
<h3 id="amazon-ebs-block-storage">Amazon EBS (Block Storage)</h3>
<h3 id="01-개념-1">01. 개념</h3>
<h3 id="ebs의-기본-구조">EBS의 기본 구조</h3>
<pre><code>[ EBS 기본 구조 ]

AWS Region: ap-northeast-2 (서울)
│
├── AZ-a (ap-northeast-2a)
│   ├── EC2 Instance (i-0abc123)
│   │   └── [연결됨] EBS Volume (vol-0001)  ← OS 디스크 (8GB, gp3)
│   │   └── [연결됨] EBS Volume (vol-0002)  ← 데이터 디스크 (100GB, gp3)
│   │
│   └── EBS Volume (vol-0003)  ← [미연결, available 상태 → 과금 중!]
│
└── AZ-b (ap-northeast-2b)
    └── EC2 Instance (i-0def456)
        └── vol-0004  ← AZ-a의 볼륨은 여기 연결 불가!

핵심 제약:
- EBS 볼륨은 같은 AZ의 EC2에만 연결 가능
- 기본적으로 EC2 1개에만 연결 (io2 Multi-Attach 예외)
- EC2 종료 시 Root Volume은 기본 삭제, 추가 볼륨은 유지</code></pre><p><strong>! EBS는 하나의 EC2만 가능 / EC2는 여러 개의 EBS가 가능</strong> </p>
<br>

<p><strong>EBS 내부 아키텍처</strong></p>
<ul>
<li>EBS는 AWS의 전용 스토리지 네트워크(EBS Network)를 통해 EC2와 연결</li>
<li>일반 인스턴스 네트워크와 분리된 전용 대역폭을 사용하므로 네트워크 트래픽과 간섭이 없음</li>
<li>Nitro 기반 EC2(최신 세대)는 NVMe 인터페이스를 사용하며 더 낮은 지연시간을 제공</li>
<li>볼륨은 내부적으로 동일 AZ 내 여러 서버에 복제되어 내구성을 유지</li>
</ul>
<br>

<h3 id="ebs-볼륨-타입">EBS 볼륨 타입</h3>
<table>
<thead>
<tr>
<th>타입</th>
<th>용도</th>
<th>최대 IOPS</th>
<th>비용</th>
</tr>
</thead>
<tbody><tr>
<td>gp3</td>
<td>범용 SSD (기본 추천)</td>
<td>16,000</td>
<td>$0.0912/GB/월</td>
</tr>
<tr>
<td>gp2</td>
<td>범용 SSD (레거시)</td>
<td>16,000</td>
<td>$0.114/GB/월</td>
</tr>
<tr>
<td>io2</td>
<td>고성능 DB</td>
<td>256,000</td>
<td>높음</td>
</tr>
<tr>
<td>st1</td>
<td>처리량 최적화 HDD</td>
<td>N/A</td>
<td>낮음</td>
</tr>
<tr>
<td>sc1</td>
<td>콜드 HDD</td>
<td>N/A</td>
<td>가장 낮음</td>
</tr>
</tbody></table>
<br>

<h3 id="ebs-성능">EBS 성능</h3>
<pre><code>**[ IOPS vs 처리량 차이 ]**

**IOPS (Input/Output Operations Per Second):**
  = 초당 수행 가능한 I/O 작업 횟수
  = 작은 블록(4KB~16KB) 랜덤 읽기/쓰기에 중요
  = 데이터베이스 트랜잭션에 핵심 지표

**처리량 (Throughput):**
  = 초당 전송 가능한 데이터 양 (MB/s)
  = 큰 블록 순차 읽기/쓰기에 중요
  = 로그 처리, 데이터 분석에 핵심 지표</code></pre><br>

<h3 id="ebs-multi-attach">EBS Multi-Attach</h3>
<p>io2 볼륨은 최대 16개의 Nitro EC2에 동시 연결 가능</p>
<pre><code>[ EBS Multi-Attach 아키텍처 ]

일반 EBS:
  EC2-A ──────── EBS Volume ── EC2-B (연결 불가)

Multi-Attach (io2만 지원):
  EC2-A ─┐
  EC2-B ─┼──── io2 EBS Volume (동일 AZ, 최대 16개)
  EC2-C ─┘

사용 사례:
- 고가용성 클러스터 파일시스템 (OCFS2, GFS2)
- Oracle RAC 데이터베이스

주의:
- 클러스터 인식 파일시스템 필수 (ext4/xfs 직접 사용 불가)
- 동시 쓰기 시 애플리케이션 레벨 잠금 필요
- 동일 AZ 내에서만 동작</code></pre><br>

<h3 id="ec2-인스턴스-스토어-vs-ebs-비교">EC2 인스턴스 스토어 vs EBS 비교</h3>
<pre><code>[ Instance Store vs EBS ]

EC2 Instance Store:
  ├── EC2 호스트 물리 서버에 직접 연결된 NVMe 스토리지
  ├── 극히 낮은 지연시간 (물리 연결)
  ├── EC2 중지/종료 시 데이터 영구 삭제 (임시 스토리지)
  └── 사용 사례: 캐시, 임시 처리 파일, 버퍼

EBS:
  ├── 네트워크 연결 스토리지 (약간의 지연)
  ├── EC2 종료 후에도 데이터 유지
  └── 사용 사례: OS 디스크, DB, 영구 데이터

자격증 시험:
  &quot;인스턴스 중지 시 데이터 삭제&quot; → Instance Store
  &quot;영구 보존 필요&quot; → EBS
  &quot;최고 성능 임시 캐시&quot; → Instance Store (NVMe SSD)

# Instance Store vs EBS 데이터 생명주기

Instance Store
EC2 stop -&gt; 삭제
EC2 terminate -&gt; 삭제
EC2 reboot -&gt; 유지
하드웨어 장애 -&gt; 삭제

EBS
EC2 stop -&gt; 유지
EC2 terminate -&gt; 기본값은 삭제, 설정으로 유지
EC2 reboot -&gt; 유지
EC2 Detach -&gt; 유지
다른 EC2 연결  -&gt; 유지</code></pre><br>

<h3 id="ami-vs-ebs-스냅샷-차이">AMI vs EBS 스냅샷 차이</h3>
<pre><code>EBS 스냅샷:
  └── 데이터 볼륨만 백업
      복원 → 새 EBS 볼륨 생성 후 EC2에 연결
      용도: DB 데이터, 업로드 파일 등 데이터 보호

EC2 AMI (Amazon Machine Image):
  └── OS + 설정 + 모든 볼륨 전체 백업
      복원 → AMI로 새 EC2 인스턴스를 그대로 복제
      용도: 서버 전체 복구, 동일 서버 여러 대 찍어내기</code></pre><table>
<thead>
<tr>
<th>상황</th>
<th>선택</th>
</tr>
</thead>
<tbody><tr>
<td>DB 데이터만 복구 필요</td>
<td>EBS 스냅샷</td>
</tr>
<tr>
<td>서버 전체 복구 필요</td>
<td>AMI</td>
</tr>
<tr>
<td>동일 서버를 여러 대 생성</td>
<td>AMI</td>
</tr>
<tr>
<td>다른 AZ로 볼륨만 이동</td>
<td>EBS 스냅샷</td>
</tr>
<tr>
<td>다른 리전에 서버 통째로 복제</td>
<td>AMI 복사</td>
</tr>
<tr>
<td>자동화된 정기 백업</td>
<td>AWS Backup (AMI + EBS 동시 지원)</td>
</tr>
</tbody></table>
<br>

<h3 id="amazon-efs--fsx-file-storage">Amazon EFS &amp; FSx (File Storage)</h3>
<h3 id="01-개념-2">01. 개념</h3>
<h3 id="efs의-기본-구조">EFS의 기본 구조</h3>
<pre><code>[ EFS 기본 구조 ]

AWS Region: ap-northeast-2 (서울)
│
└── EFS File System (fs-0abc1234)
    │  Regional: 3개 AZ에 데이터 자동 복제
    │
    ├── Mount Target in AZ-a (IP: 10.0.1.x)
    ├── Mount Target in AZ-b (IP: 10.0.2.x)
    └── Mount Target in AZ-c (IP: 10.0.3.x)
         │
         ├── EC2-1 ──NFS v4.1──► /mnt/efs  ← 동시 읽기/쓰기
         ├── EC2-2 ──NFS v4.1──► /mnt/efs
         ├── ECS Task ──────────► /mnt/efs
         └── Lambda ─────────────► /mnt/efs (EFS Lambda 마운트)</code></pre><br>

<p><strong>EFS 내부 아키텍처</strong></p>
<ul>
<li>EFS는 AWS의 분산 파일시스템으로, POSIX 호환 API를 제공</li>
<li>데이터는 AZ별 물리 스토리지에 분산 저장되며, 각 AZ의 Mount Target IP를 통해 접근</li>
<li>NFS v4.1의 parallel NFS(pNFS) 기능을 통해 여러 스토리지 서버에 동시 접근으로 처리량을 극대화</li>
<li><code>stunnel</code>을 통한 TLS 암호화 전송은 Mount Helper가 자동으로 처리</li>
</ul>
<br>

<h3 id="efs-파일시스템-타입-및-구성">EFS 파일시스템 타입 및 구성</h3>
<pre><code>[ EFS 구성 옵션 전체 ]

파일시스템 타입:
  Regional (권장): 3+ AZ 복제, 내구성 11 nine
  One Zone: 단일 AZ, ~47% 저렴, 재생성 가능 데이터

성능 모드:
  General Purpose (권장): 낮은 지연, 대부분의 워크로드
  Max I/O (레거시): 높은 동시성, 지연 증가

처리량 모드:
  Elastic (권장): 자동 조절, 최대 10GB/s 읽기, 3GB/s 쓰기
  Bursting: 크기 기반 기본 처리량 + 버스트 크레딧
  Provisioned: 고정 처리량 지정

스토리지 클래스:
  Standard: $0.36/GB/월
  Standard-IA: $0.0272/GB/월 (접근 시 $0.01/GB 추가)
  One Zone: $0.192/GB/월
  One Zone-IA: $0.0133/GB/월</code></pre><br>

<h3 id="efs-vs-ebs-선택">EFS vs EBS 선택</h3>
<pre><code>[ 선택 기준 ]

다중 EC2 동시 접근 필요? → EFS
단일 EC2, 고성능 DB?    → EBS io2
단일 EC2, 일반 워크로드? → EBS gp3
Linux NFS, 자동 확장?   → EFS
Windows SMB?            → FSx for Windows
HPC/ML 고성능?          → FSx for Lustre

실무 조합:
EC2 OS/앱           → EBS gp3
공유 업로드 파일     → EFS (또는 S3)
DB 데이터           → EBS gp3/io2
정적 콘텐츠         → S3 + CloudFront</code></pre><br>

<h3 id="efs-삭제-순서">EFS 삭제 순서</h3>
<pre><code>순서를 지키지 않으면 오류 발생!

[1단계] EC2에서 마운트 해제
  EC2 터미널: sudo umount /mnt/efs
  /etc/fstab에서 EFS 항목 삭제

[2단계] Mount Target 삭제
  EFS → 파일 시스템 → [네트워크] 탭
  → 각 AZ 마운트 대상 선택 → [삭제]
  → 상태가 사라질 때까지 약 2분 대기

[3단계] EFS 파일 시스템 삭제
  EFS → 파일 시스템 선택 → [삭제]
  → 파일 시스템 ID 입력 → [확인]

[4단계] 보안 그룹 삭제
  EC2 → 보안 그룹 → efs-security-group → [삭제]</code></pre><br>

<h3 id="통합--보안--백업--비용-관리">통합 — 보안 / 백업 / 비용 관리</h3>
<h3 id="보안-레이어-개념">보안 레이어 개념</h3>
<pre><code>[ AWS 스토리지 보안 5개 레이어 ]

요청(Request)
    │
    ▼
┌────────────────────────────────────┐
│  1. IAM (Identity &amp; Access Mgmt)   │
│  &quot;이 사용자/역할이 허가된 작업인가?&quot; │
└──────────────────┬─────────────────┘
                   ▼
┌────────────────────────────────────┐
│  2. 리소스 정책 (Resource Policy)  │
│  S3 버킷 정책, EFS 파일시스템 정책  │
└──────────────────┬─────────────────┘
                   ▼
┌────────────────────────────────────┐
│  3. 네트워크 제어                  │
│  VPC Endpoint, Security Group, IP  │
└──────────────────┬─────────────────┘
                   ▼
┌────────────────────────────────────┐
│  4. 전송 암호화 (TLS)              │
│  HTTPS, aws:SecureTransport 조건   │
└──────────────────┬─────────────────┘
                   ▼
┌────────────────────────────────────┐
│  5. 저장 암호화 (KMS/SSE)          │
│  SSE-S3, SSE-KMS, EBS 암호화       │
└────────────────────────────────────┘</code></pre><hr>
<h3 id="aws-인프라--네트워크">AWS 인프라 &amp; 네트워크</h3>
<h3 id="01-aws-인프라-계층-구조">01. AWS 인프라 계층 구조</h3>
<p>AWS 클라우드의 물리적·논리적 구성 단위를 계층으로 이해하는 것이 네트워크 설계의 출발점.</p>
<pre><code>전 세계 AWS 글로벌 인프라
│
├── Region (지역)
│     물리적으로 분리된 지리적 데이터센터 집합.
│     각 Region은 독립적으로 운영되며, 장애가 다른 Region에 전파되지 않음.
│     예) ap-northeast-2 (서울), us-east-1 (버지니아 북부)
│
│     ├── Availability Zone (AZ, 가용 영역)
│     │     하나의 Region 안에 있는 독립적인 데이터센터 클러스터.
│     │     AZ끼리는 물리적으로 수십 km 이상 떨어져 있어
│     │     화재·홍수·전력 장애 등이 동시에 영향을 미치지 않음.
│     │     예) ap-northeast-2a / 2b / 2c / 2d
│     │
│     └── Local Zone (선택적)
│           특정 도시에 컴퓨팅 리소스를 배치해 초저지연 제공.
│           예) 부산, 도쿄, 로스앤젤레스 등.
│
└── Edge Location (엣지 로케이션)
      CloudFront(CDN), Route 53(DNS)의 캐시·응답을 처리하는
      전 세계 550+ 분산 서버 거점.
      Region보다 훨씬 많고, 사용자 가까이에 위치.</code></pre><br>

<h3 id="02-온프레미스-네트워크-vs-aws-가상-네트워크">02. 온프레미스 네트워크 vs AWS 가상 네트워크</h3>
<p><strong>VLAN (Virtual Local Area Network)</strong></p>
<ul>
<li>물리적으로 같은 네트워크 장비에 연결되어 있어도 논리적으로 분리된 네트워크 구간</li>
<li>AWS에서는 Subnet이 이 역할을 수행</li>
</ul>
<br>

<p><strong>NAT (Network Address Translation)</strong></p>
<ul>
<li>사설 IP 주소를 공인 IP 주소로 변환하는 기술</li>
<li>Private Subnet의 EC2가 인터넷으로 나갈 때,
NAT Gateway가 사설 IP → 공인 IP로 변환해 인터넷 통신을 가능하게 함</li>
<li>반대 방향(인터넷 → 사설 IP)은 허용하지 않아 외부 침입을 원천 차단</li>
</ul>
<br>

<p><strong>IaC (Infrastructure as Code)</strong></p>
<ul>
<li>인프라 구성을 코드 파일로 정의하고 버전 관리하는 방식</li>
<li>콘솔에서 클릭하는 모든 설정을 코드로 표현해 반복 적용, 팀 공유, 이력 추적이 가능</li>
<li>대표 도구: Terraform (HashiCorp), AWS CloudFormation (AWS 네이티브)</li>
</ul>
<br>

<h3 id="03-cidr-표기법과-ip-주소-설계-원칙">03. CIDR 표기법과 IP 주소 설계 원칙</h3>
<p>AWS VPC와 Subnet의 IP 범위를 정의할 때 반드시 사용하는 표기법</p>
<pre><code>CIDR 표기 형식: X.X.X.X / prefix

예시: 10.0.0.0/16

10 . 0 . 0 . 0   /   16
└─────────────┘       └──
  네트워크 주소         prefix (앞에서부터 고정되는 비트 수)

→ 앞 16비트 고정 → 나머지 16비트가 호스트 영역
→ 사용 가능한 IP 수 = 2^16 = 65,536개</code></pre><p><strong>CIDR (Classless Inter-Domain Routing)</strong></p>
<ul>
<li>IP 주소를 클래스(A/B/C) 구분 없이 prefix 길이로 유연하게 범위를 지정하는 방식</li>
<li><code>/16</code>, <code>/24</code>, <code>/28</code> 등 숫자가 클수록 범위가 좁아지고 IP 수가 줄어듦</li>
</ul>
<br>

<p><strong>RFC 1918 (사설 IP 대역)</strong>
인터넷에서 라우팅되지 않는 내부 전용 IP 대역의 국제 표준 
AWS VPC 설계 시 반드시 이 대역 내에서 CIDR을 선택</p>
<table>
<thead>
<tr>
<th>대역</th>
<th>범위</th>
<th>주요 사용처</th>
</tr>
</thead>
<tbody><tr>
<td><code>10.0.0.0/8</code></td>
<td>10.0.0.0 ~ 10.255.255.255</td>
<td>AWS VPC (권장)</td>
</tr>
<tr>
<td><code>172.16.0.0/12</code></td>
<td>172.16.0.0 ~ 172.31.255.255</td>
<td>중간 규모</td>
</tr>
<tr>
<td><code>192.168.0.0/16</code></td>
<td>192.168.0.0 ~ 192.168.255.255</td>
<td>소규모 / 가정망</td>
</tr>
</tbody></table>
<br>

<h3 id="cidr-prefix별-ip-수-비교">CIDR prefix별 IP 수 비교</h3>
<table>
<thead>
<tr>
<th>CIDR</th>
<th>전체 IP 수</th>
<th>AWS 사용 가능 IP</th>
<th>주요 용도</th>
</tr>
</thead>
<tbody><tr>
<td><code>/16</code></td>
<td>65,536</td>
<td>65,531</td>
<td>VPC 전체 권장 크기</td>
</tr>
<tr>
<td><code>/24</code></td>
<td>256</td>
<td>251</td>
<td>서브넷 기본 단위</td>
</tr>
<tr>
<td><code>/28</code></td>
<td>16</td>
<td>11</td>
<td>소규모 서브넷</td>
</tr>
<tr>
<td><code>/32</code></td>
<td>1</td>
<td>—</td>
<td>단일 IP 지정 (SG 규칙 등)</td>
</tr>
<tr>
<td><code>/0</code></td>
<td>전체</td>
<td>—</td>
<td>모든 IP (0.0.0.0/0)</td>
</tr>
</tbody></table>
<p><strong>AWS 예약 IP 5개</strong>
AWS는 각 서브넷에서 아래 5개 IP를 시스템 용도로 예약하여 사용 불가
예) 서브넷 <code>10.0.1.0/24</code> 기준:</p>
<p><code>/24</code> 서브넷의 실제 사용 가능 IP = 256 - 5 = <strong>251개</strong>.</p>
<table>
<thead>
<tr>
<th>IP</th>
<th>용도</th>
</tr>
</thead>
<tbody><tr>
<td><code>10.0.1.0</code></td>
<td>네트워크 주소 (식별자)</td>
</tr>
<tr>
<td><code>10.0.1.1</code></td>
<td>VPC 라우터</td>
</tr>
<tr>
<td><code>10.0.1.2</code></td>
<td>AWS DNS 서버</td>
</tr>
<tr>
<td><code>10.0.1.3</code></td>
<td>향후 AWS 사용을 위한 예약</td>
</tr>
<tr>
<td><code>10.0.1.255</code></td>
<td>브로드캐스트 주소</td>
</tr>
</tbody></table>
<br>

<h3 id="04-패킷-흐름">04. 패킷 흐름</h3>
<pre><code>[인터넷 사용자]  예) 브라우저에서 https://shop.mylab.kr 요청
      │
      ▼
[Route 53]          DNS 쿼리 → 도메인을 IP 주소로 변환
      │
      ▼
[CloudFront]        Edge에서 캐시 확인. Cache Miss 시 Origin으로 전달.
      │
      ▼
[Internet Gateway]  VPC의 인터넷 출입문. 공인 IP ↔ 사설 IP 매핑.
      │
      ▼
[Route Table]       패킷의 목적지 판단.
                    0.0.0.0/0 → IGW (외부)
                    10.0.0.0/16 → local (VPC 내부)
      │
      ▼
[Network ACL]       서브넷 경계 방화벽 (Stateless).
                    인바운드 규칙 번호 순서대로 Allow / Deny 판단.
      │
      ▼
[Security Group]    인스턴스 레벨 방화벽 (Stateful).
                    허용된 포트·프로토콜만 통과.
      │
      ▼
[EC2 Instance]      목적지 도달. 애플리케이션이 요청 처리.
      │
      │  (DB 요청 발생 시)
      ▼
[Security Group]    DB용 SG. 앱 서버 SG에서만 허용.
      │
      ▼
[RDS / DB 서버]     인터넷에서 직접 접근 불가. 앱 서버만 접근 가능.</code></pre><p> <strong>Stateful vs Stateless</strong></p>
<p><strong>Stateful (상태 추적형)</strong></p>
<ul>
<li>연결 상태(Connection State)를 기억, 인바운드 요청을 허용하면 해당 연결의 응답은 자동 허용</li>
<li>AWS에서는 Security Group이 Stateful
예) 인바운드 80 허용 → 응답(아웃바운드)은 별도 규칙 없이 자동 통과.</li>
</ul>
<p><strong>Stateless (상태 비추적형)</strong></p>
<ul>
<li>연결 상태를 기억하지 않음, 요청과 응답 모두 별도 규칙 필요</li>
<li>AWS에서는 Network ACL이 Stateless.
예) 인바운드 80 허용 → 응답 아웃바운드도 반드시 별도 규칙 추가 필요.</li>
</ul>
<br>

<h3 id="vpc-핵심-구성요소">VPC 핵심 구성요소</h3>
<h3 id="01vpc-virtual-private-cloud">01.VPC (Virtual Private Cloud)</h3>
<ul>
<li>AWS 클라우드 안에 생성하는 <strong>논리적으로 격리된 전용 가상 네트워크</strong></li>
<li>모든 AWS 리소스(EC2, RDS, Lambda 등)는 VPC 안에서 동작</li>
</ul>
<br>

<h3 id="vpc-핵심-특성">VPC 핵심 특성</h3>
<table>
<thead>
<tr>
<th>특성</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>논리적 격리</strong></td>
<td>다른 AWS 계정·VPC와 완전 분리. 명시적 연결 없이는 통신 불가.</td>
</tr>
<tr>
<td><strong>리전 범위</strong></td>
<td>하나의 VPC는 하나의 리전 전체에 걸침. AZ 경계는 Subnet으로 구분.</td>
</tr>
<tr>
<td><strong>CIDR 고정</strong></td>
<td>생성 시 지정한 CIDR은 변경 불가. Secondary CIDR 추가만 가능.</td>
</tr>
<tr>
<td><strong>기본 무료</strong></td>
<td>VPC 자체는 요금 없음. NAT Gateway, Elastic IP 등 일부 구성요소는 유료.</td>
</tr>
<tr>
<td><strong>계정당 기본 5개</strong></td>
<td>리전당 기본 VPC 5개 제한 (Service Quota 증가 신청 가능).</td>
</tr>
</tbody></table>
<br>

<p><strong>ENI (Elastic Network Interface)</strong></p>
<p>EC2 인스턴스에 연결되는 가상 네트워크 카드</p>
<p><strong>DNS Hostname / DNS Resolution</strong></p>
<p>DNS Hostname: VPC 내 EC2에 자동으로 DNS 이름 부여 여부 설정
DNS Resolution: VPC 내부에서 AWS 제공 DNS 서버(VPC+2 주소)를 사용할지 여부</p>
<br>

<h3 id="02-서브넷-subnet">02. 서브넷 (Subnet)</h3>
<p>VPC CIDR을 용도·역할별로 분할한 하위 네트워크 구간
<strong>반드시 하나의 AZ에만 속하며, AZ 간 중복 불가</strong></p>
<pre><code>VPC: 10.10.0.0/16
│
├── [AZ-a]
│   ├── Public  Subnet  10.10.1.0/24  ← 인터넷 직접 연결 가능
│   ├── Private Subnet  10.10.3.0/24  ← NAT 통해 아웃바운드만 가능
│   └── Isolated Subnet 10.10.5.0/24  ← 인터넷 완전 차단 (DB 전용)
│
└── [AZ-c]
    ├── Public  Subnet  10.10.2.0/24
    ├── Private Subnet  10.10.4.0/24
    └── Isolated Subnet 10.10.6.0/24</code></pre><br>

<h4 id="서브넷-유형-비교">서브넷 유형 비교</h4>
<table>
<thead>
<tr>
<th>유형</th>
<th>Route Table</th>
<th>인터넷 인바운드</th>
<th>인터넷 아웃바운드</th>
<th>주요 리소스</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Public</strong></td>
<td>IGW 경로 있음</td>
<td>가능</td>
<td>가능</td>
<td>ALB, Bastion, NAT GW</td>
</tr>
<tr>
<td><strong>Private</strong></td>
<td>NAT GW 경로 있음</td>
<td>불가</td>
<td>가능 (NAT 통해)</td>
<td>App 서버, ECS, Lambda</td>
</tr>
<tr>
<td><strong>Isolated</strong></td>
<td>외부 경로 없음</td>
<td>불가</td>
<td>불가</td>
<td>RDS, ElastiCache</td>
</tr>
</tbody></table>
<p><strong>Bastion Host (배스천 호스트)</strong></p>
<ul>
<li>Private Subnet 내 리소스에 SSH로 안전하게 접근하기 위한 경유 서버</li>
<li>Public Subnet에 위치하며, 관리자 IP에서만 SSH 허용.</li>
</ul>
<br>

<h3 id="03-인터넷-게이트웨이-internet-gateway-igw">03. 인터넷 게이트웨이 (Internet Gateway, IGW)</h3>
<p>VPC와 인터넷 사이의 양방향 통신 출입문</p>
<br>

<h4 id="igw-동작의-3가지-필수-조건">IGW 동작의 3가지 필수 조건</h4>
<pre><code>조건 ①  VPC에 IGW가 연결(Attach)되어 있어야 함
조건 ②  서브넷의 Route Table에 0.0.0.0/0 → IGW 경로가 있어야 함
조건 ③  EC2 인스턴스에 Public IP 또는 Elastic IP가 할당되어 있어야 함

→ 3가지 중 하나라도 누락 시 인터넷 통신 불가.
   &quot;EC2에 접속이 안 될 때&quot; 이 세 가지를 순서대로 확인.</code></pre><br>

<h3 id="04-라우트-테이블-route-table">04. 라우트 테이블 (Route Table)</h3>
<p>패킷의 목적지 IP를 보고 어디로 보낼지 결정하는 규칙 테이블</p>
<br>

<h4 id="서브넷-유형별-route-table-설계">서브넷 유형별 Route Table 설계</h4>
<p><strong>Public RT</strong> (public-2a, public-2c 공통 연결)</p>
<table>
<thead>
<tr>
<th>Destination</th>
<th>Target</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td><code>10.10.0.0/16</code></td>
<td><code>local</code></td>
<td>VPC 내부 통신</td>
</tr>
<tr>
<td><code>0.0.0.0/0</code></td>
<td><code>igw-xxxxxxxx</code></td>
<td>인터넷 양방향</td>
</tr>
</tbody></table>
<p><strong>Private RT — AZ-a</strong> (private-app-2a 연결)</p>
<table>
<thead>
<tr>
<th>Destination</th>
<th>Target</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td><code>10.10.0.0/16</code></td>
<td><code>local</code></td>
<td>VPC 내부 통신</td>
</tr>
<tr>
<td><code>0.0.0.0/0</code></td>
<td><code>nat-gw-2a</code></td>
<td>AZ-a NAT GW 경유 아웃바운드</td>
</tr>
</tbody></table>
<p><strong>Private RT — AZ-c</strong> (private-app-2c 연결)</p>
<table>
<thead>
<tr>
<th>Destination</th>
<th>Target</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td><code>10.10.0.0/16</code></td>
<td><code>local</code></td>
<td>VPC 내부 통신</td>
</tr>
<tr>
<td><code>0.0.0.0/0</code></td>
<td><code>nat-gw-2c</code></td>
<td>AZ-c NAT GW 경유 아웃바운드</td>
</tr>
</tbody></table>
<p><strong>Isolated RT</strong> (isolated-db-2a, isolated-db-2c 공통 연결)</p>
<table>
<thead>
<tr>
<th>Destination</th>
<th>Target</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td><code>10.10.0.0/16</code></td>
<td><code>local</code></td>
<td>VPC 내부 통신만. 외부 경로 없음.</td>
</tr>
</tbody></table>
<br>

<h3 id="05-nat-gateway">05. NAT Gateway</h3>
<p>Private Subnet의 리소스가 인터넷으로 나가는 아웃바운드 통신만 허용하는 관리형 서비스</p>
<pre><code>[Private Subnet의 EC2]
       │  아웃바운드 요청 (예: apt update, S3 패키지 다운로드)
       ▼
[NAT Gateway]  ← 반드시 Public Subnet에 위치. Elastic IP 보유.
       │  사설 IP → 공인 IP(EIP)로 변환 (SNAT)
       ▼
[Internet Gateway] → 인터넷</code></pre><br>

<h3 id="06-elastic-ip-eip">06. Elastic IP (EIP)</h3>
<p>AWS 계정에 고정 할당되는 Public IPv4 주소
EC2를 재시작해도 IP가 변경되지 않음. </p>
<table>
<thead>
<tr>
<th>항목</th>
<th>일반 Public IP</th>
<th>Elastic IP</th>
</tr>
</thead>
<tbody><tr>
<td><strong>고정 여부</strong></td>
<td>EC2 재시작 시 변경됨</td>
<td>명시적 해제 전까지 고정</td>
</tr>
<tr>
<td><strong>비용</strong></td>
<td>인스턴스 실행 중 $0.005/시간</td>
<td><strong>미연결 상태에서도 과금</strong></td>
</tr>
<tr>
<td><strong>주요 사용처</strong></td>
<td>일반 웹 서버</td>
<td>Bastion, NAT GW</td>
</tr>
</tbody></table>
<br>

<p><strong>VPC 실습</strong></p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/db73c7a0-d7ba-48ea-803e-54f7ddc5c25e/image.png" alt=""></p>
<br>

<h3 id="보안-계층--security-group--nacl">보안 계층 — Security Group &amp; NACL</h3>
<h3 id="01-aws-2중-방화벽-구조-전체-흐름">01. AWS 2중 방화벽 구조 전체 흐름</h3>
<ul>
<li>AWS VPC는 <strong>서브넷 레벨(NACL)</strong>과 <strong>인스턴스 레벨(Security Group)</strong> 2중 방어 구조</li>
<li>두 계층이 모두 통과되어야 EC2 인스턴스에 트래픽이 도달</li>
</ul>
<pre><code>인터넷
  │
  ▼
[Internet Gateway]
  │
  ▼
[Route Table]               ← 목적지 판단. 경로가 없으면 여기서 차단.
  │
  ▼
┌─────────────────────────────────────────────────┐
│  서브넷 경계                                     │
│  ① Network ACL (NACL)                           │
│     → 서브넷 레벨, Stateless                     │
│     → Allow / Deny 모두 가능                     │
│     → 규칙 번호 순서대로 평가, 일치 즉시 적용     │
│  ┌─────────────────────────────────────────┐    │
│  │  인스턴스 경계                           │    │
│  │  ② Security Group (SG)                  │    │
│  │     → ENI(인스턴스) 레벨, Stateful       │    │
│  │     → Allow만 가능 (Deny 불가)           │    │
│  │     → 모든 규칙을 종합 평가              │    │
│  │  [ EC2 Instance ]                       │    │
│  └─────────────────────────────────────────┘    │
└─────────────────────────────────────────────────┘

트래픽 인바운드: IGW → RT → NACL 인바운드 → SG 인바운드 → EC2
트래픽 아웃바운드: EC2 → SG 아웃바운드 → NACL 아웃바운드 → IGW</code></pre><br>

<h3 id="02-security-group-sg">02. Security Group (SG)</h3>
<p>EC2 인스턴스(정확히는 ENI)에 적용되는 인스턴스 레벨 가상 방화벽</p>
<h4 id="sg-핵심-특성">SG 핵심 특성</h4>
<table>
<thead>
<tr>
<th>특성</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>적용 레벨</strong></td>
<td>ENI(인스턴스) 레벨</td>
</tr>
<tr>
<td><strong>Stateful</strong></td>
<td>인바운드 허용 → 응답 아웃바운드 자동 허용</td>
</tr>
<tr>
<td><strong>규칙 유형</strong></td>
<td>Allow만 가능</td>
</tr>
<tr>
<td><strong>기본 동작</strong></td>
<td>명시된 규칙 외 모든 트래픽 묵시적 거부</td>
</tr>
<tr>
<td><strong>다중 SG</strong></td>
<td>하나의 인스턴스에 여러 SG 할당 가능</td>
</tr>
<tr>
<td><strong>규칙 평가</strong></td>
<td>모든 규칙을 종합 평가 후 Allow 여부 결정</td>
</tr>
</tbody></table>
<br>

<h3 id="03-network-acl-nacl">03. Network ACL (NACL)</h3>
<p>서브넷에 연결되는 서브넷 레벨 방화벽</p>
<h4 id="nacl-핵심-특성">NACL 핵심 특성</h4>
<table>
<thead>
<tr>
<th>특성</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>적용 레벨</strong></td>
<td>서브넷 레벨</td>
</tr>
<tr>
<td><strong>Stateless</strong></td>
<td>요청과 응답 모두 별도 규칙 필요</td>
</tr>
<tr>
<td><strong>규칙 유형</strong></td>
<td>Allow + <strong>Deny 모두 가능</strong></td>
</tr>
<tr>
<td><strong>규칙 평가</strong></td>
<td>번호 낮은 것부터 순서대로. 일치 즉시 적용 후 나머지 미평가</td>
</tr>
<tr>
<td><strong>기본 NACL</strong></td>
<td>모든 인바운드/아웃바운드 허용</td>
</tr>
<tr>
<td><strong>연결 수</strong></td>
<td>1 서브넷 → 1 NACL. 1 NACL → 여러 서브넷 가능</td>
</tr>
</tbody></table>
<br>

<h4 id="nacl-규칙-번호-평가-방식">NACL 규칙 번호 평가 방식</h4>
<pre><code>인바운드 규칙 예시:

Rule # | Type   | Protocol | Port      | Source        | Action
-------|--------|----------|-----------|---------------|-------
  50   | Custom | TCP      | 80        | 1.2.3.4/32    | DENY    ← 특정 IP 긴급 차단
 100   | HTTP   | TCP      | 80        | 0.0.0.0/0     | ALLOW
 110   | HTTPS  | TCP      | 443       | 0.0.0.0/0     | ALLOW
 120   | SSH    | TCP      | 22        | 관리자IP/32   | ALLOW
 900   | Custom | TCP      | 1024-65535| 0.0.0.0/0     | ALLOW   ← Ephemeral 필수
   *   | All    | All      | All       | 0.0.0.0/0     | DENY    ← 기본 거부

→ 1.2.3.4에서 80 포트: Rule 50 DENY → 즉시 차단. Rule 100 미평가
→ 5.6.7.8에서 80 포트: Rule 50 불일치 → Rule 100 ALLOW → 통과

- 규칙 번호: 낮은 번호 -&gt; 높은 번호 (우선순위결정)
-- 1 ~ 32766 사용 가능
-- 32767(AWS 예약번호이므로 사용 불가능)

-- 1 ~ 99: 긴급 차단 용도
-- 100 ~ 200: 주요허용 규칙
-- 900 ~ 999: 임시 포트 등의 규칙</code></pre><br>

<h4 id="sg-vs-nacl-완전-비교표">SG vs NACL 완전 비교표</h4>
<table>
<thead>
<tr>
<th>항목</th>
<th>Security Group</th>
<th>Network ACL</th>
</tr>
</thead>
<tbody><tr>
<td><strong>적용 레벨</strong></td>
<td>인스턴스 (ENI)</td>
<td>서브넷</td>
</tr>
<tr>
<td><strong>상태 추적</strong></td>
<td>Stateful</td>
<td>Stateless</td>
</tr>
<tr>
<td><strong>Allow/Deny</strong></td>
<td>Allow만</td>
<td>Allow + Deny</td>
</tr>
<tr>
<td><strong>규칙 평가 방식</strong></td>
<td>모든 규칙 종합</td>
<td>번호 순서, 일치 즉시 적용</td>
</tr>
<tr>
<td><strong>기본 인바운드</strong></td>
<td>모두 차단</td>
<td>모두 허용 (기본 NACL)</td>
</tr>
<tr>
<td><strong>Ephemeral Port</strong></td>
<td>불필요 (Stateful)</td>
<td>아웃바운드 허용 규칙 필수</td>
</tr>
<tr>
<td><strong>적용 시점</strong></td>
<td>즉시 (기존 연결 유지)</td>
<td>즉시 (기존 연결도 영향)</td>
</tr>
<tr>
<td><strong>주 사용 목적</strong></td>
<td>역할별 접근 제어</td>
<td>IP 차단, 서브넷 경계 보안</td>
</tr>
</tbody></table>
<br>

<h3 id="04-vpc-flow-logs">04. VPC Flow Logs</h3>
<p>VPC 내 모든 네트워크 인터페이스(ENI)를 통과하는 IP 트래픽 메타데이터를 기록하는 기능</p>
<br>

<h3 id="정리">정리</h3>
<pre><code>내용 요약

① 2중 방화벽 구조
   인터넷 → IGW → RT → NACL → SG → EC2
   NACL이 외부 방어선, SG가 내부 방어선.

② Security Group
   인스턴스(ENI) 레벨, Stateful, Allow만.
   SG 간 참조 패턴이 실무 표준.
   아웃바운드 ALL 규칙 없어도 인바운드 허용된 연결의 응답은 자동 통과.

③ Network ACL
   서브넷 레벨, Stateless, Allow+Deny.
   Ephemeral Port(1024~65535) 아웃바운드 허용 필수.
   규칙 번호 낮은 것이 우선. DENY가 ALLOW보다 낮은 번호여야 차단됨.
   NACL 변경 → 기존 연결도 즉시 영향.

④ Stateless 체험 실험 핵심
   &quot;인바운드는 허용했는데 응답이 없다&quot; = NACL 아웃바운드 Ephemeral Port 누락.
   SG는 Stateful이라 이 현상 발생 안 함.

⑤ VPC Flow Logs
   REJECT 로그가 보이면 SG 또는 NACL 규칙 문제.
   ACCEPT인데 연결이 안 된다면 애플리케이션 레벨 문제.</code></pre><br>

<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/cc507403-68bc-4884-83ec-26952afadc03/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[멋쟁이사자처럼부트캠프 클라우드 엔지니어링 5기 [10주차]]]></title>
            <link>https://velog.io/@its-jihyeon/%EB%A9%8B%EC%9F%81%EC%9D%B4%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A7%81-5%EA%B8%B0-10%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@its-jihyeon/%EB%A9%8B%EC%9F%81%EC%9D%B4%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A7%81-5%EA%B8%B0-10%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Sun, 05 Apr 2026 05:09:46 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>클라우드 아키텍처 및 기획 (프로젝트)</p>
</blockquote>
<br>

<h3 id="1-프로젝트-개요">1. 프로젝트 개요</h3>
<ul>
<li><strong>주제</strong> : 학습노트 기반 quiz 생성하는 서비스 (서비스명 : Note2Quiz)</li>
<li><strong>개발 기간</strong> : 2025.03.31 ~ 2025.04.03 (4일)</li>
<li><strong>팀 구성</strong> : 6명 (Frontend 3명, Backend 3명)</li>
<li><strong>한 줄 요약</strong> : 사용자가 업로드한 학습 노트를 Google Gemini AI가 분석하여 맞춤형 복습 퀴즈를 자동으로 생성해 주는 서비스</li>
<li><strong>GitHub</strong> : <a href="https://github.com/CLD-05/team01_Note2Quiz">CLD-05/team01_Note2Quiz</a></li>
</ul>
<br>

<h3 id="2-기술-스택-tech-stack">2. 기술 스택 (Tech Stack)</h3>
<table>
<thead>
<tr>
<th align="left">영역</th>
<th align="left">기술</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>Frontend</strong></td>
<td align="left">React, Vite, Tailwind CSS, React Router</td>
</tr>
<tr>
<td align="left"><strong>Backend</strong></td>
<td align="left">Spring Boot, Spring AI, Spring Security, JPA</td>
</tr>
<tr>
<td align="left"><strong>Database</strong></td>
<td align="left">MySQL</td>
</tr>
<tr>
<td align="left"><strong>AI</strong></td>
<td align="left">Google Gemini 2.5 Flash</td>
</tr>
<tr>
<td align="left"><strong>Infra</strong></td>
<td align="left">Docker, Docker Compose, Nginx</td>
</tr>
</tbody></table>
<hr>
<p>이번 프로젝트에서 <strong>학습 노트(Note) 관련 핵심 비즈니스 로직 설계와 API 구현</strong>을 중점적으로 담당</p>
<ul>
<li><strong>노트 목록 조회 (<code>GET /api/notes</code>)</strong><ul>
<li><strong>보안 및 정렬</strong>: 로그인한 사용자의 노트만 필터링하여 반환하며, 사용 편의를 위해 <strong>최신순 정렬</strong>을 적용</li>
<li><strong>맞춤형 DTO 설계</strong>: 목록 조회 시 <code>wordCount</code>, <code>preview</code> 등 프론트엔드 메인 화면 구성에 꼭 필요한 데이터만 담아 응답 효율을 높임</li>
</ul>
</li>
<li><strong>노트 상세 조회 (<code>GET /api/notes/{noteId}</code>)</strong><ul>
<li><strong>권한 체크</strong>: 특정 노트에 접근 시 해당 노트의 소유주와 로그인 사용자가 일치하는지 검증하는 로직을 추가하여 데이터 보안을 강화</li>
</ul>
</li>
<li><strong>노트 삭제 (<code>DELETE /api/notes/{noteId}</code>)</strong><ul>
<li><strong>연쇄 삭제(CASCADE) 구현</strong>: 부모 엔티티(<code>notes</code>) 삭제 시 연관된 <code>quiz_sets</code>와 하위 <code>quizzes</code> 데이터가 함께 정리되도록 설계하여 데이터 무결성을 보장</li>
</ul>
</li>
</ul>
<br>

<h4 id="✅-api-응답-최적화-및-커스텀">✅ API 응답 최적화 및 커스텀</h4>
<ul>
<li><strong>Jackson 어노테이션 활용</strong>: <code>@JsonInclude(JsonInclude.Include.NON_NULL)</code>을 도입</li>
<li>이를 통해 <code>null</code> 값인 필드는 응답에서 자동으로 제외되도록 처리하여, <strong>네트워크 전송량을 최적화</strong>하고 프론트엔드에서 불필요한 데이터를 처리하는 비용을 줄임</li>
</ul>
<br>

<p><a href="https://velog.io/@its-jihyeon/Spring-Boot-STS-Maven-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-Import-%ED%99%9C%EC%84%B1%ED%99%94-%EC%98%A4%EB%A5%98">백엔드 서버 기초 세팅과정에서 생긴 문제</a></p>
<br>

<blockquote>
<p>개인적으로 준비해 보았던 클라우드 아키텍처 설계</p>
</blockquote>
<h3 id="클라우드-아키텍처-설계">클라우드 아키텍처 설계</h3>
<p>이번 프로젝트의 핵심 목표 중 하나인 <strong>고가용성(High Availability), 보안성(Security), 확장성(Scalability)</strong>을 확보하기 위해 AWS의 디자인 패턴을 적용하여 설계한 아키텍처</p>
<h4 id="아키텍처-구조-conceptual-diagram">아키텍처 구조 (Conceptual Diagram)</h4>
<pre><code class="language-text">                                 [ 사용자 (Client) ]
                                         |
                                         v
+---------------------------------------------------------------------------------------+
|               1. DNS &amp; 전용 최적화: Route 53 + CloudFront (Global Edge)                 |
|                   (사용자 요청 수신 및 프론트엔드 정적 리소스 초고속 배포)                    |
+---------------------------------------------------------------------------------------+
                                         |
                                         v
+---------------------------------------------------------------------------------------+
|                    2. 부하 분산: Application Load Balancer (ALB)                       |
|                     (L7 레이어 트래픽 분산 및 가용 영역별 상태 확인)                        |
+---------------------------------------------------------------------------------------+
                                         |
            +------+---------------------+--------------------------------+
            |                            |                                |
    [ 가용 영역 A (AZ-1) ]        [ 가용 영역 B (AZ-2) ]            [ 외부 서비스 연동 ]
|      (Seoul-1a)            |      (Seoul-1c)            |                              |
|                            |                            |                              |
|  +----------------------+  |  +----------------------+  |                              |
|  |  Public Subnet       |  |  |  Public Subnet       |  |                              |
|  |  [ NAT Gateway A ]   |  |  |  [ NAT Gateway B ]   |  |                              |
|  +----------|-----------+  |  +----------|-----------+  |                              |
|             v              |             v              |                              |
|  +----------------------+  |  +----------------------+  |   +-----------------------+  |
|  |  Private Subnet (App)|  |  |  Private Subnet (App)|  |   |  [ Google Cloud ]     |  |
|  |  [ EC2 Instance 1 ]  | &lt;|&gt; |  [ EC2 Instance 2 ]  | &lt;==&gt; |   **Gemini AI API**   |  |
|  |  (Spring Boot API)   |  |  |  (Spring Boot API)   |  |   |   (gemini-2.5-flash)  |  |
|  +----------|-----------+  |  +----------|-----------+  |   +-----------------------+  |
|             v              |             v              |             ^                |
|  +----------------------+  |  +----------------------+  |             |                |
|  |  Private Subnet (DB) |  |  |  Private Subnet (DB) |  |             |                |
|  |  [ RDS MySQL ]       | &lt;==&gt;|  [ RDS Standby ]     |  |             |                |
|  |  (Primary/Write)     |  |  |  (Read Replica)      | &lt;---   AI 생성 데이터 저장        |
|  +----------------------+  |  +----------------------+  |                              |
|                            |                            |                              |
+----------------------------+-----------------------------------------------------------+
                                          |
                                          v
+---------------------------------------------------------------------------------------+
|                3. 미디어 저장소: S3 Bucket (Cross-Region Replication)                   |
|                     (업로드 파일 및 AI 생성 콘텐츠의 내구적 저장)                           |
+---------------------------------------------------------------------------------------+</code></pre>
<br>

<h3 id="서비스-선정-이유">서비스 선정 이유</h3>
<table>
<thead>
<tr>
<th align="left">서비스 명칭</th>
<th align="left">핵심 선정 이유 (Key Points)</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>Route 53</strong></td>
<td align="left">지연 시간 기반 라우팅을 통해 최적의 접속 경로를 보장하고 안정적인 DNS 환경 제공</td>
</tr>
<tr>
<td align="left"><strong>CloudFront</strong></td>
<td align="left">글로벌 에지 로케이션 캐싱을 활용해 프론트엔드 리소스의 로딩 속도를 획기적으로 가속화</td>
</tr>
<tr>
<td align="left"><strong>ALB (Load Balancer)</strong></td>
<td align="left">다중 가용 영역(Multi-AZ) 트래픽 분산을 통해 특정 서버 장애 시에도 무중단 서비스 운영 가능</td>
</tr>
<tr>
<td align="left"><strong>EC2 Auto Scaling</strong></td>
<td align="left">실시간 트래픽 변화에 따라 인스턴스 수를 자동으로 조절하여 리소스 최적화 및 부하 분산</td>
</tr>
<tr>
<td align="left"><strong>Private Subnet</strong></td>
<td align="left">주요 애플리케이션 서버와 DB를 외부 인터넷으로부터 격리하여 네트워크 보안성 및 데이터 보호 강화</td>
</tr>
<tr>
<td align="left"><strong>NAT Gateway</strong></td>
<td align="left">보안 서브넷 내 인스턴스가 안전하게 외부 API(Gemini)를 호출하고 업데이트를 수행할 수 있는 통로 제공</td>
</tr>
<tr>
<td align="left"><strong>Google Gemini AI API</strong></td>
<td align="left"><code>gemini-2.5-flash</code> 모델을 활용하여 고성능 AI 기반 학습 콘텐츠(퀴즈)를 실시간으로 생성</td>
</tr>
<tr>
<td align="left"><strong>RDS Multi-AZ</strong></td>
<td align="left">DB 동기식 복제 및 자동 페일오버(Failover)를 통해 데이터 무결성과 시스템 가용성 확보</td>
</tr>
<tr>
<td align="left"><strong>S3 Bucket</strong></td>
<td align="left">높은 내구성을 바탕으로 대규모 비정형 데이터(업로드 파일 및 AI 생성 콘텐츠)를 안정적으로 저장</td>
</tr>
</tbody></table>
<br>

<h4 id="설계-과정에서의-아쉬운-점-self-review">설계 과정에서의 아쉬운 점 (Self-Review)</h4>
<p>AWS Overview를 통해 학습한 내용을 바탕으로 고가용성 아키텍처를 설계해 보았으나, 짧은 기간 내에 실제 서비스 수준의 정교한 기획을 담아내기에는 지식의 깊이 면에서 아쉬움이 남았습니다.</p>
<ol>
<li><strong>네트워크 레벨의 디테일 부족</strong>: 서브넷 마스크(CIDR) 설정이나 구체적인 라우팅 테이블 구성 등, VPC 내부의 세밀한 네트워크 설계를 기획서에 완벽히 녹여내지 못한 점이 아쉬웠습니다.</li>
<li><strong>시각화 및 표준화 미비</strong>: <code>draw.io</code>와 같은 전문 툴을 활용하여 클라우드 표준 다이어그램 형태로 시각화하지 못해 설계의 직관성이 다소 부족했습니다. 추후에는 더 정교한 아키텍처 맵을 작성해 볼 계획입니다.</li>
<li><strong>이론과 실제의 간극</strong>: 학습한 디자인 패턴을 이론적으로 배치하는 것에 그치지 않고, 각 리소스 간의 실제 통신 비용이나 성능 병목 지점까지 고려한 깊이 있는 설계에 도전해 보고 싶습니다.</li>
</ol>
<hr>
<h3 id="마치며">마치며...</h3>
<p>이번 미니 프로젝트는 단순히 API를 개발하는 것을 넘어, <strong>프론트엔드와 백엔드를 통합하는 과정</strong>에서 고려해야 할 상황이 예상보다 훨씬 많다는 것을 배운 소중한 시간이었습니다.</p>
<p>특히 <strong>의사소통이 프로젝트의 성패를 결정짓는다</strong>는 것을 뼈저리게 깨달았습니다. 원활한 협업을 위한 명확한 API 명세와 데이터 규격 설계가 얼마나 중요한지 체감할 수 있었습니다.</p>
<p>비록 짧은 기간이었지만 <strong>서비스 기획부터 구현, 그리고 클라우드 아키텍처 설계</strong>까지 전체 사이클을 직접 완주해 보았다는 점에서 큰 성취감을 느꼈습니다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring Boot] STS Maven 프로젝트 Import 활성화 오류]]></title>
            <link>https://velog.io/@its-jihyeon/Spring-Boot-STS-Maven-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-Import-%ED%99%9C%EC%84%B1%ED%99%94-%EC%98%A4%EB%A5%98</link>
            <guid>https://velog.io/@its-jihyeon/Spring-Boot-STS-Maven-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-Import-%ED%99%9C%EC%84%B1%ED%99%94-%EC%98%A4%EB%A5%98</guid>
            <pubDate>Sun, 05 Apr 2026 04:41:16 GMT</pubDate>
            <description><![CDATA[<h4 id="1-문제-상황-problem">1. 문제 상황 (Problem)</h4>
<ul>
<li><strong>현상</strong>: STS(Spring Tool Suite)에서 <code>Import Maven Projects</code>를 시도했으나, 리스트에 <code>pom.xml</code>은 뜨지만 <strong>체크박스가 비활성화</strong>되어 선택할 수 없고 <code>Finish</code> 버튼도 작동하지 않는 현상 발생.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/63f4e1f9-a2d4-4da9-a403-fb5e35be02ce/image.png" alt=""></p>
<br>

<h4 id="2-원인-분석-cause">2. 원인 분석 (Cause)</h4>
<p><strong>원인 1: Windows 경로 길이 제한 (MAX_PATH)</strong></p>
<ul>
<li>폴더 깊이가 너무 깊으면 인덱싱 및 파일 접근 에러가 발생할 수 도 있음</li>
</ul>
<br>

<p><strong>원인 2: 이미 워크스페이스에 동일한 이름의 프로젝트가 존재</strong></p>
<ul>
<li>기존 워크스페이스에 동일한 <code>artifactId</code>를 가진 프로젝트가 이미 등록되어 있을 경우 중복 임포트가 차단됨.</li>
</ul>
<br>

<p><strong>원인 3: 이전 설정 파일과의 충돌</strong></p>
<ul>
<li>이전에 다른 IDE나 환경에서 생성된 <code>.project</code>, <code>.settings</code> 등의 메타데이터가 현재 환경과 충돌할 수 있음.</li>
</ul>
<br>

<h4 id="3-해결-방법-solution">3. 해결 방법 (Solution)</h4>
<p><strong>방법 1: 경로 단순화</strong> </p>
<ul>
<li>프로젝트 폴더를 드라이브 루트에 가까운 경로로 이동시켜 경로 길이를 단축.</li>
</ul>
<br>

<p><strong>방법 2: 중복 프로젝트 확인 및 삭제</strong></p>
<ul>
<li>Package Explorer에서 이름이 겹치는 프로젝트를 삭제(Delete)하여 충돌 제거</li>
</ul>
<br>

<p><strong>방법 3: <code>.project</code> 및 <code>.settings</code> 파일 정리</strong></p>
<ul>
<li>폴더 내의 <code>.project</code>, <code>.classpath</code> 파일과 <code>.settings</code> 폴더를 삭제하여 설정 정보 초기화</li>
</ul>
<br>

<h4 id="4-최종-해결-action-taken">4. 최종 해결 (Action Taken)</h4>
<p><strong>&quot;경로 최적화 + 워크스페이스 신규 생성”</strong></p>
<ul>
<li>프로젝트 경로를 단순하게 변경함.</li>
<li>test 라는 이름의 새로운 워크스페이스(Workspace)를 생성하여 기존의 꼬여있던 메타데이터 간섭을 완전히 차단</li>
</ul>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/8733498b-8822-4b75-9ec9-ddcf9881b4b8/image.png" alt=""></p>
<br>

<h4 id="5-회고-retrospective">5. <strong>회고 (Retrospective)</strong></h4>
<ul>
<li>개발 환경에서 <strong>프로젝트 경로는 최대한 짧고 간결하게 관리</strong>하는 것이 예기치 못한 에러를 방지하는 지름길임을 깨달음</li>
<li>원인 파악이 힘들 때는 기존 설정을 고치려 하기보다, <strong>새로운 워크스페이스를 생성하여 클린한 상태에서 재시작</strong>하는 것이 훨씬 효율적인 해결책이 될 수 있음을 배움</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Docker] MySQL 컨테이너 3306 포트 충돌 해결 방법]]></title>
            <link>https://velog.io/@its-jihyeon/Docker-MySQL-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-3306-%ED%8F%AC%ED%8A%B8-%EC%B6%A9%EB%8F%8C-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@its-jihyeon/Docker-MySQL-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-3306-%ED%8F%AC%ED%8A%B8-%EC%B6%A9%EB%8F%8C-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Sun, 29 Mar 2026 01:26:11 GMT</pubDate>
            <description><![CDATA[<h3 id="docker-run-중-에러-발생">docker run 중 에러 발생</h3>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/01f8d409-3dc8-4143-82de-c416adcd5b4a/image.png" alt=""></p>
<p>원인 : STS 프로젝트가 로컬(우분투)에서 MySQL 서버를 실행 중이라 기본 포트인 3306을 점유하고 있었다.
이로 인해 포트 충돌이 발생하여 Docker 컨테이너가 시작되지 못했다.</p>
<br>

<p><strong>해결책 1 : 로컬 MySQL 서버 정지</strong> </p>
<p>가급적 MySQL 전용 포트(3306)를 그대로 사용하는 것이 관리 면에서 효율적</p>
<pre><code class="language-bash">sudo systemctl stop mysql</code></pre>
<br>

<p><strong>해결책 2: 포트번호를 변경하여 실행 (포트 포워딩 변경)</strong></p>
<pre><code class="language-bash">docker run -p 3307:3306 --name mysql-container -e MYSQL_ROOT_PASSWORD=password -d mysql</code></pre>
<br>

<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/6038f6ea-bc65-421a-9e58-3384ccad7f8c/image.png" alt=""></p>
<hr>
<h3 id="추가-이슈-이름-충돌">추가 이슈: 이름 충돌</h3>
<p>포트 문제를 해결한 뒤 다시 실행했을 때 에러 발생</p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/637a9840-08c2-45b0-b9ec-6e218f5da1da/image.png" alt=""></p>
<p>원인 : 이전에 실행에 실패한 컨테이너가 이름만 점유한 채 남아 있어 이름 충돌 발생</p>
<br>

<p><strong>해결책 : 재실행 필요 (or 기존 컨테이너 삭제하고 다시 생성)</strong></p>
<pre><code class="language-bash">docker rm -f [컨테이너_이름]</code></pre>
<br>

<p>성공 화면</p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/1b6d6a42-530f-43fc-90a8-273a5c15d798/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[멋쟁이사자처럼부트캠프 클라우드 엔지니어링 5기 [9주차]]]></title>
            <link>https://velog.io/@its-jihyeon/%EB%A9%8B%EC%9F%81%EC%9D%B4%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A7%81-5%EA%B8%B0-9%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@its-jihyeon/%EB%A9%8B%EC%9F%81%EC%9D%B4%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A7%81-5%EA%B8%B0-9%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Sun, 29 Mar 2026 01:16:01 GMT</pubDate>
            <description><![CDATA[<h3 id="셸-스크립트">셸 스크립트</h3>
<p>bash 명령어를 파일로 저장하여 반복 실행 가능하게 만든 자동화 스크립트 (배포 자동화, 로그 정리, 모니터링 알림 등 DevOps 업무의 핵심 도구)</p>
<br>

<p><strong>기본 구조</strong></p>
<pre><code class="language-bash">#!/bin/bash
# 스크립트 설명 주석

# 변수 선언
APP_NAME=&quot;myapp&quot;

# 명령어 실행
echo &quot;Starting $APP_NAME&quot;</code></pre>
<pre><code class="language-bash"># 실행 권한 부여 및 실행
chmod +x script.sh
./script.sh
bash script.sh      # 권한 없이 실행 가능</code></pre>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/016ed7a8-3e61-4171-9669-0e0307d78250/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/f58745f7-842d-48ea-956d-e7a52c65199c/image.png" alt=""></p>
<br>

<h3 id="조건문"><strong>조건문</strong></h3>
<pre><code class="language-bash"># if 기본 구조
# 대괄호 안에 공백 필수!
# fi : if문의 끝을 알림
if [ 조건 ]; then
    명령어
elif [ 조건 ]; then
    명령어
else
    명령어
fi    </code></pre>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/b2cea927-04ce-499d-a154-bd530d148903/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/1d727947-fc9d-422e-9597-dff1fa02e03b/image.png" alt=""></p>
<br>

<p><strong>비교 연산자</strong></p>
<table>
<thead>
<tr>
<th>정수 비교</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td><code>-eq</code></td>
<td>같음 (equal)</td>
</tr>
<tr>
<td><code>-ne</code></td>
<td>다름 (not equal)</td>
</tr>
<tr>
<td><code>-gt</code></td>
<td>초과 (greater than)</td>
</tr>
<tr>
<td><code>-ge</code></td>
<td>이상 (greater or equal)</td>
</tr>
<tr>
<td><code>-lt</code></td>
<td>미만 (less than)</td>
</tr>
<tr>
<td><code>-le</code></td>
<td>이하 (less or equal)</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>-z</code></td>
<td>빈 문자열</td>
</tr>
<tr>
<td><code>-n</code></td>
<td>비어있지 않음</td>
</tr>
</tbody></table>
<br>

<h3 id="반복문">반복문</h3>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/0240bea8-0be2-4a2a-a016-6421b6c1d3ff/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/d11a893e-32ab-4534-8c6f-b45f7865929d/image.png" alt=""></p>
<br>

<h3 id="함수">함수</h3>
<pre><code class="language-bash"># 함수 선언
function 함수명() {
    명령어
    return 반환값    # 0~255, 생략 시 마지막 명령 종료 코드
}

# 또는
함수명() {
    명령어
}

# 호출
함수명
함수명 인자1 인자2    # 인자 전달 시 함수 내에서 $1, $2로 참조</code></pre>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/402a4d78-0b21-4624-b267-e637b9832090/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/3d6b3fa5-a9bf-4670-b606-d3092a85340c/image.png" alt=""></p>
<br>

<h3 id="종료-코드--에러-처리">종료 코드 &amp; 에러 처리</h3>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/d9f0f214-6180-4fac-8931-fde54d577bd0/image.png" alt=""></p>
<br>

<h3 id="셸-스크립트로-배포-자동화">셸 스크립트로 배포 자동화</h3>
<pre><code class="language-bash">#!/bin/bash
# deploy.sh — Spring Boot 자동 배포 스크립트
# 사용법: ./deploy.sh [브랜치명]  (기본: main)
set -euo pipefail

# 설정 변수
# 깃허브 레파지토리 루트 경로
REPO_DIR=&quot;/home/ubuntu/infra-basic-deploy-lab&quot;
# 앱 루트경로
APP_DIR=&quot;/home/ubuntu/infra-basic-deploy-lab/infra-basic-deploy-lab&quot;
# 빌드 결과(아티팩트)
JAR_PATTERN=&quot;infra-basic-deploy-lab-*.jar&quot;
# 앱 실행 결과 로그
LOG_FILE=&quot;/home/ubuntu/infra-basic-deploy-lab/app.log&quot;
# 배포 과정 결과 로그
DEPLOY_LOG=&quot;/home/ubuntu/infra-basic-deploy-lab/deploy.log&quot;
# 인자 없다면 main 브랜치 기준
BRANCH=&quot;${1:-main}&quot;
PORT=8080
HEALTH_URL=&quot;http://localhost:${PORT}/api/backend&quot;
# 헬스체크 최대 대기(초)
MAX_WAIT=30

# 로그 함수
log() {
    local MSG=&quot;[$(date &#39;+%Y-%m-%d %H:%M:%S&#39;)] $1&quot;
    echo &quot;$MSG&quot; | tee -a &quot;$DEPLOY_LOG&quot;
}

# 1) Git Pull
pull_latest() {
    log &quot;=== 배포 시작: branch=$BRANCH ===&quot;

    # [힌트] git clone된 루트 폴더(.git)로 이동
    # [힌트] git fetch origin
    # [힌트] $BRANCH 로 checkout
    # [힌트] $BRANCH 를 pull
    # ?
    cd &quot;$REPO_DIR&quot;
    git fetch origin
    git checkout &quot;$BRANCH&quot;
    git pull origin &quot;$BRANCH&quot;

    log &quot;Git pull 완료&quot;
}

# 2) Maven 빌드
build_jar() {
    log &quot;Maven 빌드 시작...&quot;

    # [힌트] $APP_DIR 로 이동 (pom.xml 위치)
    # [힌트] Step 2에서 서버 빌드할 때 쓴 명령어 사용 (테스트 스킵, -q 옵션 추가)
    # ?
    cd &quot;$APP_DIR&quot;
    mvn clean package -DskipTests -q
    log &quot;빌드 완료: $(ls target/$JAR_PATTERN)&quot;
}

# 3) 기존 프로세스 종료
stop_app() {
    local PID
    PID=$(pgrep -f &quot;$JAR_PATTERN&quot; || true)

    # [힌트] $PID 가 비어있지 않으면 (-n) 종료 처리
    #         kill 후 최대 10초 대기하며 프로세스가 사라졌는지 확인
    #         비어있으면 &quot;실행 중인 프로세스 없음&quot; 로그 출력
    # ?
    if [ -n &quot;$PID&quot; ]; then
        log &quot;기존 프로세스 종료: PID=$PID&quot;
        kill &quot;$PID&quot;
        for i in {1..10}; do
            if ! pgrep -f &quot;$JAR_PATTERN&quot; &gt; /dev/null; then
                log &quot;프로세스 종료 확인&quot;
                break
            fi
        done
    else
        log &quot;실행 중인 프로세스 없음&quot;
    fi
}

# 4) 앱 시작
start_app() {
    local JAR
    JAR=$(ls &quot;$APP_DIR&quot;/target/$JAR_PATTERN | head -1)

    # [힌트] $APP_DIR 로 이동
    # [힌트] nohup 으로 $JAR 백그라운드 실행
    #         stdout/stderr 는 $LOG_FILE 로 저장
    # ?
    log &quot;앱 시작: $JAR&quot;
    cd &quot;$APP_DIR&quot;
    nohup java -jar &quot;$JAR&quot; &gt; &quot;$LOG_FILE&quot; 2&gt;&amp;1 &amp;
    log &quot;PID: $!&quot;
}

# 5) 헬스 체크
health_check() {
    log &quot;헬스 체크 대기 중 (최대 ${MAX_WAIT}초)...&quot;

    # [힌트] 1초 간격으로 $MAX_WAIT 회 반복
    #         curl -sf 로 $HEALTH_URL 에 요청
    #         성공하면 &quot;배포 성공&quot; 로그 출력 후 return 0
    #         반복이 끝날 때까지 응답 없으면 아래 return 1 실행
    # ?
    for i in $(seq 1 $MAX_WAIT); do
        if curl -sf &quot;$HEALTH_URL&quot; &gt; /dev/null 2&gt;&amp;1; then
            log &quot;배포 성공: $HEALTH_URL 응답 확인 (${i}초 소요)&quot;
            return 0
        fi
        sleep 1
    done

    log &quot;배포 실패: ${MAX_WAIT}초 내 응답 없음&quot;
    return 1
}

# 메인 실행
pull_latest
build_jar
stop_app
start_app
health_check

log &quot;=== 전체 배포 완료 ===&quot;</code></pre>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/4da8a918-5489-43ab-8bdb-2f3d3f014e4b/image.png" alt=""></p>
<hr>
<p><strong>Docker</strong> : 컨테이너 기술을 기반으로 한 오픈소스 가상화 플랫폼</p>
<ul>
<li>애플리케이션과 그 실행 환경을 <strong>컨테이너(Container)</strong> 단위로 패키징하여 어디서든 동일하게 실행 가능</li>
</ul>
<br>

<table>
<thead>
<tr>
<th>항목</th>
<th>호스트 가상화 (Type 2)</th>
<th>하이퍼바이저 가상화 (Type 1)</th>
<th>컨테이너 가상화</th>
</tr>
</thead>
<tbody><tr>
<td>동작 방식</td>
<td>Host OS 위에 소프트웨어로 하이퍼바이저 실행</td>
<td>하드웨어 위에 하이퍼바이저 직접 설치</td>
<td>Host OS 커널 공유, <code>namespace</code> + <code>cgroup</code>으로 격리</td>
</tr>
<tr>
<td>대표 기술</td>
<td>VirtualBox, VMware Workstation</td>
<td>KVM, VMware ESXi, Xen</td>
<td>Docker, containerd</td>
</tr>
<tr>
<td>사용 사례</td>
<td>개발자 로컬 환경</td>
<td>AWS EC2, Azure VM 등 클라우드 인프라 기반</td>
<td>MSA, CI/CD 파이프라인 (Kubernetes + Docker)</td>
</tr>
<tr>
<td>성능</td>
<td>Host OS → Hypervisor → Guest OS 레이어로 오버헤드 큼</td>
<td>하드웨어 직접 제어 → 우수</td>
<td>경량 프로세스 → 매우 빠름</td>
</tr>
<tr>
<td>격리 수준</td>
<td>중간</td>
<td>강함 (VM 단위 완전 격리)</td>
<td>낮음 (커널 공유)</td>
</tr>
<tr>
<td>이미지 크기</td>
<td>Guest OS 전체 포함 → 무거움</td>
<td>Guest OS 전체 포함 → 무거움</td>
<td>Guest OS 없음 → 수십 MB 수준</td>
</tr>
<tr>
<td>부팅 속도</td>
<td>느림</td>
<td>느림 (Guest OS 부팅 필요)</td>
<td>빠름 (수 초 이내)</td>
</tr>
<tr>
<td>OS 의존성</td>
<td>Host OS 필요</td>
<td>독립적 (각 VM이 자체 OS 보유)</td>
<td>Linux 커널 의존 (Windows/Mac은 내부적으로 경량 VM 사용)</td>
</tr>
<tr>
<td>설치 난이도</td>
<td>쉬움</td>
<td>복잡 (서버 직접 설정)</td>
<td>보통</td>
</tr>
</tbody></table>
<br>

<ul>
<li><strong>Client</strong><ul>
<li>명령어, API로 Host와 통신<ul>
<li>docker build</li>
<li>docker pull</li>
<li>docker push</li>
<li>docker run</li>
</ul>
</li>
<li>입력한 명령어는 터미널을 통해 docker daemon으로 전송</li>
</ul>
</li>
<li><strong>Host</strong><ul>
<li>환경 및 어플리케이션 실행</li>
<li>Daemon, Image, Container, Network, Storage, …</li>
</ul>
</li>
<li><strong>Registry</strong><ul>
<li>Image를 관리하고 저장<ul>
<li>Public Registry : Docker Hub</li>
<li>Private Registry : 개인, 기업</li>
</ul>
</li>
</ul>
</li>
</ul>
<br>

<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/94e4f1d4-419a-4019-9a40-ff5b9a6fd2ef/image.png" alt=""></p>
<br>

<p><strong>기본 동작</strong></p>
<pre><code class="language-bash"># 실행 중인 컨테이너 확인
docker ps

# nginx 컨테이너 실행 (이미지 자동 pull)
docker run -d -p 80:80 nginx

# 실행 중인 컨테이너 확인
docker ps

# 컨테이너 중지
docker stop &lt;컨테이너ID 또는 이름&gt;

# 전체 컨테이너 확인 (중지 포함)
docker ps -a

# Docker 시스템 전체 정보
docker system info

# 컨테이너 로그 확인
docker logs &lt;컨테이너ID&gt;</code></pre>
<hr>
<h3 id="docker-image">Docker Image</h3>
<ul>
<li>컨테이너 실행에 필요한 <strong>읽기 전용(Read-Only) 템플릿</strong></li>
<li>소스 코드, 라이브러리, 런타임, 설정 파일 등 애플리케이션 실행에 필요한 모든 것을 포함</li>
<li>한 번 빌드된 이미지는 <strong>변경 불가(Immutable)</strong> — 동일한 환경을 어디서나 재현 가능</li>
</ul>
<br>

<p><strong>이미지 레이어 구조</strong></p>
<pre><code class="language-bash">┌────────────────────────────────┐
│  Container Layer (R/W)         │  ← 컨테이너 실행 시 생성되는 쓰기 레이어
├────────────────────────────────┤
│  COPY index.html (Layer 4)     │  ← Dockerfile 각 명령어가 레이어 생성
├────────────────────────────────┤
│  RUN apt-get install (Layer 3) │
├────────────────────────────────┤
│  RUN apt-get update  (Layer 2) │
├────────────────────────────────┤
│  FROM ubuntu:22.04   (Layer 1) │  ← Base Image
└────────────────────────────────┘</code></pre>
<br>

<p><strong>레이어 특징</strong></p>
<table>
<thead>
<tr>
<th>특징</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>재사용</strong></td>
<td>동일한 레이어는 여러 이미지가 공유 — 저장소/다운로드 절감</td>
</tr>
<tr>
<td><strong>격리</strong></td>
<td>각 레이어는 독립적인 파일시스템 변경사항만 포함</td>
</tr>
<tr>
<td><strong>불변</strong></td>
<td>기존 레이어는 수정 불가 — 변경 시 새 레이어 추가</td>
</tr>
<tr>
<td><strong>캐시</strong></td>
<td>빌드 시 변경되지 않은 레이어는 캐시 활용 → 빌드 속도 향상</td>
</tr>
</tbody></table>
<br>

<pre><code class="language-bash"># 로컬 이미지 목록
docker images
docker image ls

# 이미지 검색 (Docker Hub)
docker search &lt;키워드&gt;

# 이미지 다운로드
docker pull &lt;이미지명&gt;:&lt;태그&gt;
docker pull nginx           # latest 태그 (기본값)
docker pull nginx:1.25
docker pull openjdk:17

# 이미지 상세 정보
docker image inspect &lt;이미지명&gt;

# 이미지 레이어 히스토리
docker history &lt;이미지명&gt;

# 이미지 삭제
docker rmi &lt;이미지명 또는 ID&gt;
docker rmi -f &lt;이미지명&gt;         # 강제 삭제 (실행 중 컨테이너 이미지 포함)

# 전체 이미지 삭제
docker rmi $(docker images -q)
docker rmi -f $(docker images -q)

# 이미지 태그 추가
docker tag &lt;기존이미지&gt; &lt;새이름&gt;:&lt;태그&gt;

# 이미지 tar 파일로 내보내기
docker save -o &lt;파일명&gt;.tar &lt;이미지명&gt;

# tar 파일에서 이미지 불러오기
docker load -i &lt;파일명&gt;.tar</code></pre>
<br>

<p><strong>docker image prune — 미사용 이미지 정리</strong></p>
<table>
<thead>
<tr>
<th>옵션</th>
<th>설명</th>
<th>예시</th>
</tr>
</thead>
<tbody><tr>
<td><em>(없음)</em></td>
<td>dangling 이미지(<code>&lt;none&gt;</code>)만 삭제</td>
<td><code>docker image prune</code></td>
</tr>
<tr>
<td><code>-a</code></td>
<td>컨테이너가 사용하지 않는 모든 이미지 삭제</td>
<td><code>docker image prune -a</code></td>
</tr>
<tr>
<td><code>-f</code></td>
<td>확인 없이 바로 삭제</td>
<td><code>docker image prune -f</code></td>
</tr>
</tbody></table>
<br>

<h3 id="docker-container">Docker Container</h3>
<ul>
<li>Docker Image를 실행한 <strong>인스턴스 — 실제 프로세스 동작 단위</strong></li>
<li>이미지는 불변(Read-Only), 컨테이너는 이미지 위에 <strong>쓰기 가능한 레이어</strong>를 추가하여 동작</li>
</ul>
<br>

<p><strong>실행</strong></p>
<pre><code class="language-bash"># 기본 실행
docker run &lt;이미지명&gt;

# 주요 옵션
docker run [옵션] &lt;이미지명&gt; [명령어]

-d              # 백그라운드(Detached) 실행
-it             # 대화형 터미널 연결 (-i: 표준입력 유지, -t: TTY 할당)
--name &lt;이름&gt;   # 컨테이너 이름 지정
-p &lt;호스트포트&gt;:&lt;컨테이너포트&gt;   # 포트 매핑
-v &lt;호스트경로&gt;:&lt;컨테이너경로&gt;   # 볼륨/바인드 마운트
--rm            # 컨테이너 종료 시 자동 삭제
--network &lt;네트워크명&gt;           # 네트워크 지정
-e &lt;KEY&gt;=&lt;VALUE&gt;                # 환경변수 설정
--restart &lt;policy&gt;              # 재시작 정책 (no/always/on-failure/unless-stopped)</code></pre>
<br>

<p><strong>삭제</strong></p>
<pre><code class="language-bash"># 컨테이너 삭제 (중지 상태여야 함)
docker rm &lt;컨테이너명&gt;

# 강제 삭제 (실행 중이어도)
docker rm -f &lt;컨테이너명&gt;

# 중지된 컨테이너 전체 삭제
docker container prune

# 전체 컨테이너 삭제
docker rm $(docker ps -aq)
docker stop $(docker ps -aq) &amp;&amp; docker rm $(docker ps -aq)</code></pre>
<br>

<p><strong>docker exec — 컨테이너 내부 명령 실행</strong></p>
<table>
<thead>
<tr>
<th>옵션</th>
<th>설명</th>
<th>예시</th>
</tr>
</thead>
<tbody><tr>
<td><em>(없음)</em></td>
<td>단발성 명령어 실행</td>
<td><code>docker exec web ls /usr/share/nginx/html</code></td>
</tr>
<tr>
<td><code>-it</code></td>
<td>대화형 터미널 접속</td>
<td><code>docker exec -it web bash</code></td>
</tr>
<tr>
<td><code>-it ... sh</code></td>
<td>bash 없는 경량 이미지(alpine)에서 접속</td>
<td><code>docker exec -it web sh</code></td>
</tr>
<tr>
<td><code>--user &lt;유저&gt;</code></td>
<td>특정 유저로 실행</td>
<td><code>docker exec --user root -it web bash</code></td>
</tr>
<tr>
<td><code>-e &lt;KEY&gt;=&lt;VALUE&gt;</code></td>
<td>명령 실행 시 환경변수 추가</td>
<td><code>docker exec -e DEBUG=true web env</code></td>
</tr>
</tbody></table>
<hr>
<h3 id="dockerfile"><strong>Dockerfile</strong></h3>
<ul>
<li>Docker 이미지를 생성하기 위한 명령어 스크립트 파일</li>
<li>확장자 없이 Dockerfile 이라는 이름으로 저장</li>
</ul>
<br>

<pre><code class="language-bash"># FROM — 베이스 이미지 지정 (항상 첫 줄)
FROM ubuntu:22.04
FROM openjdk:17-jdk-slim
FROM nginx:alpine

# RUN — 이미지 빌드 시 실행할 명령어 (새 레이어 생성)
RUN apt-get update &amp;&amp; \
    apt-get install -y curl git &amp;&amp; \
    rm -rf /var/lib/apt/lists/*

# COPY — 호스트 파일 → 이미지로 복사
COPY index.html /usr/share/nginx/html/index.html
COPY ./target/app.jar /app.jar

# ADD — COPY + URL 다운로드, tar 자동 압축해제 가능
ADD https://example.com/file.tar.gz /tmp/

# WORKDIR — 이후 명령어의 작업 디렉터리 지정 (없으면 자동 생성)
WORKDIR /app

# ENV — 환경변수 설정 (이미지 빌드 + 컨테이너 실행 시 모두 유효)
ENV JAVA_OPTS=&quot;-Xmx512m&quot;
ENV APP_PORT=8080

# EXPOSE — 컨테이너가 사용할 포트 명시 (실제 포트 오픈은 docker run -p 로)
EXPOSE 8080

# CMD — 컨테이너 시작 시 기본 실행 명령어 (docker run 인자로 덮어쓰기 가능)
CMD [&quot;nginx&quot;, &quot;-g&quot;, &quot;daemon off;&quot;]

# ENTRYPOINT — 컨테이너 시작 시 고정 실행 명령어 (덮어쓰기 불가)
ENTRYPOINT [&quot;java&quot;, &quot;-jar&quot;, &quot;/app.jar&quot;]

# ARG — 빌드 시에만 사용되는 변수 (docker build --build-arg 로 전달)
ARG APP_VERSION=1.0.0

# VOLUME — 마운트 포인트 지정
VOLUME [&quot;/var/log/app&quot;]

# USER — 명령어 실행 사용자 변경 (보안상 root 대신 일반 유저 사용)
USER appuser</code></pre>
<br>

<p>CMD vs ENTRYPOINT 비교</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>CMD</th>
<th>ENTRYPOINT</th>
</tr>
</thead>
<tbody><tr>
<td>역할</td>
<td>기본 실행 명령어</td>
<td>고정 실행 명령어</td>
</tr>
<tr>
<td><code>docker run</code> 인자로 덮어쓰기</td>
<td>가능</td>
<td>불가 (인자로 추가만 가능)</td>
</tr>
<tr>
<td>조합 사용</td>
<td><code>ENTRYPOINT</code> 의 기본 인자로 활용</td>
<td>CMD와 조합하여 기본값 제공</td>
</tr>
<tr>
<td>실무 활용</td>
<td>기본 커맨드 지정</td>
<td>실행 파일 고정 (java -jar 등)</td>
</tr>
</tbody></table>
<br>

<p><strong>docker build (이미지 생성)</strong></p>
<pre><code class="language-bash"># 기본 빌드
docker build -t &lt;이미지명&gt;:&lt;태그&gt; &lt;Dockerfile경로&gt;

# 현재 디렉터리의 Dockerfile로 빌드
docker build -t myapp:1.0 .

# 태그 없으면 latest 자동 부여
docker build -t myapp .

# 빌드 인자 전달
docker build --build-arg APP_VERSION=2.0 -t myapp:2.0 .

# 캐시 사용 안 함 (완전 새로 빌드)
docker build --no-cache -t myapp:1.0 .

# 멀티플랫폼 빌드 (ARM/AMD64 모두 지원)
docker buildx build --platform=linux/amd64,linux/arm64 -t myapp:1.0 .</code></pre>
<br>

<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/6ca72821-9aa3-432b-9692-f897528bdfb7/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/111b40ff-2232-44b8-a62b-43cd018c3029/image.png" alt=""></p>
<br>

<h3 id="dockerhub-이미지-pushpull">DockerHub 이미지 Push/Pull</h3>
<pre><code class="language-bash"># Docker Hub 로그인
docker login

# 이미지에 Hub 태그 추가
docker tag &lt;로컬이미지명&gt; &lt;DockerHub_ID&gt;/&lt;Repository명&gt;:&lt;태그&gt;
docker tag myapp:1.0 myid/myrepo:1.0

# 이미지 Push
docker push &lt;DockerHub_ID&gt;/&lt;Repository명&gt;:&lt;태그&gt;
docker push myid/myrepo:1.0

# 이미지 Pull
docker pull &lt;DockerHub_ID&gt;/&lt;Repository명&gt;:&lt;태그&gt;</code></pre>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/a375750c-c6f5-4b8c-a562-6949c1060268/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/6418c9af-5965-49f5-8509-4688ba02263b/image.png" alt=""></p>
<hr>
<h3 id="docker-volume">Docker Volume</h3>
<p>컨테이너 외부에 데이터를 저장하여 컨테이너 재생성 후에도 데이터를 유지하는 메커니즘</p>
<p>(컨테이너는 삭제되면 내부 데이터가 모두 소멸 - 데이터 영속화가 필요)</p>
<br>

<p><strong>방식별 비교</strong></p>
<table>
<thead>
<tr>
<th>종류</th>
<th>경로</th>
<th>특징</th>
<th>실무 활용</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Bind Mount</strong></td>
<td>호스트 특정 디렉터리</td>
<td>호스트 파일시스템 직접 접근, 성능 우수</td>
<td>개발 환경, 설정 파일 공유</td>
</tr>
<tr>
<td><strong>Volume</strong></td>
<td>Docker 관리 <code>/var/lib/docker/volumes/</code></td>
<td>Docker가 관리, 컨테이너 간 공유 가능</td>
<td>DB 데이터, 운영 데이터 영속화</td>
</tr>
<tr>
<td><strong>tmpfs</strong></td>
<td>메모리</td>
<td>디스크 기록 없음, 컨테이너 종료 시 소멸</td>
<td>임시 민감 데이터, 캐시</td>
</tr>
</tbody></table>
<br>

<h3 id="bind-mount">Bind Mount</h3>
<ul>
<li>호스트의 특정 디렉터리를 그대로 컨테이너에 마운트</li>
<li>양쪽에서 변경사항이 즉시 반영</li>
</ul>
<pre><code class="language-bash">docker run -v &lt;호스트절대경로&gt;:&lt;컨테이너경로&gt; &lt;이미지명&gt;
# 또는
docker run --mount type=bind,source=&lt;호스트경로&gt;,target=&lt;컨테이너경로&gt; &lt;이미지명&gt;

# 예시
# nginx html 디렉터리를 호스트 로컬 디렉터리로 마운트
docker run --name webserver -d -p 8080:80 \
  -v /home/ubuntu/webroot:/usr/share/nginx/html \
  nginx

# 호스트에서 파일 수정 → 컨테이너에 즉시 반영
echo &quot;&lt;h1&gt;Updated&lt;/h1&gt;&quot; &gt; /home/ubuntu/webroot/index.html</code></pre>
<br>

<h3 id="volume">Volume</h3>
<ul>
<li>Docker가 직접 관리하는 스토리지 — 호스트 파일시스템에 종속되지 않음</li>
<li>컨테이너 삭제 후에도 데이터 유지, 컨테이너 간 공유 가능</li>
</ul>
<br>

<p><strong>명령어</strong></p>
<pre><code class="language-bash"># Volume 생성
docker volume create &lt;볼륨명&gt;

# Volume 목록 확인
docker volume ls

# Volume 상세 정보
docker volume inspect &lt;볼륨명&gt;

# Volume 실제 데이터 경로 확인
sudo ls /var/lib/docker/volumes/&lt;볼륨명&gt;/_data

# Volume 삭제
docker volume rm &lt;볼륨명&gt;

# 사용하지 않는 Volume 전체 삭제
docker volume prune</code></pre>
<br>

<p><strong>컨테이너에 Volume 연결</strong></p>
<pre><code class="language-bash"># -v 옵션으로 Volume 연결
docker run --name mysql-server \
  -v mysql-data:/var/lib/mysql \
  -e MYSQL_ROOT_PASSWORD=root \
  -d mysql:8.0

# --mount 옵션으로 Volume 연결 (명시적)
docker run --name mysql-server \
  --mount type=volume,source=mysql-data,target=/var/lib/mysql \
  -e MYSQL_ROOT_PASSWORD=root \
  -d mysql:8.0</code></pre>
<hr>
<h3 id="docker-network">Docker Network</h3>
<ul>
<li>각 컨테이너는 <strong>격리된 네트워크 환경</strong>에서 독립적인 IP를 가짐</li>
<li>서로 다른 컨테이너가 통신하려면 <strong>Docker Network를 통해 연결</strong> 필요</li>
</ul>
<br>

<p><strong>네트워크 드라이버 종류</strong></p>
<table>
<thead>
<tr>
<th>드라이버</th>
<th>설명</th>
<th>실무 활용</th>
</tr>
</thead>
<tbody><tr>
<td><strong>bridge</strong></td>
<td>기본값. 가상 브리지로 컨테이너 연결</td>
<td>단일 호스트 다중 컨테이너 구성</td>
</tr>
<tr>
<td><strong>host</strong></td>
<td>호스트 네트워크 직접 사용 (포트 매핑 불필요)</td>
<td>성능 민감한 환경</td>
</tr>
<tr>
<td><strong>none</strong></td>
<td>네트워크 완전 차단</td>
<td>외부 통신이 불필요한 배치 작업</td>
</tr>
<tr>
<td><strong>overlay</strong></td>
<td>멀티 호스트 컨테이너 통신 (Docker Swarm)</td>
<td>클러스터 환경</td>
</tr>
<tr>
<td><strong>macvlan</strong></td>
<td>컨테이너에 MAC 주소 직접 할당</td>
<td>물리 네트워크 직접 연결</td>
</tr>
</tbody></table>
<br>

<p><strong>명령어</strong></p>
<pre><code class="language-bash"># 네트워크 목록 확인
docker network ls

# 네트워크 상세 정보
docker network inspect &lt;네트워크명&gt;

# 사용자 정의 네트워크 생성
docker network create &lt;네트워크명&gt;
docker network create --driver bridge &lt;네트워크명&gt;

# 실행 시 네트워크 지정
docker run --network &lt;네트워크명&gt; &lt;이미지명&gt;

# 실행 중인 컨테이너에 네트워크 연결
docker network connect &lt;네트워크명&gt; &lt;컨테이너명&gt;

# 컨테이너에서 네트워크 해제
docker network disconnect &lt;네트워크명&gt; &lt;컨테이너명&gt;

# 네트워크 삭제 (연결된 컨테이너 없어야 함)
docker network rm &lt;네트워크명&gt;

# 사용하지 않는 네트워크 전체 삭제
docker network prune</code></pre>
<hr>
<h3 id="docker-compose"><strong>Docker Compose</strong></h3>
<ul>
<li>여러 컨테이너를 하나의 YAML 파일(docker-compose.yml)로 정의하고 한 번에 실행·관리하는 도구</li>
<li>여러 docker run 명령어를 파일 하나로 통합 → 서비스 기동/중지/재시작 단순화</li>
</ul>
<br>

<h3 id="compose-파일">Compose 파일</h3>
<p>사람이 읽고 쓰기 아주 편하게 만들어진 데이터 직렬화(Data Serialization) 양식</p>
<br>

<p><strong>문법</strong></p>
<pre><code class="language-bash"># 주석 — # 사용

# key: value 형식
name: myapp
port: 8080

# 들여쓰기 — 2칸 또는 4칸 (탭 사용 금지)
database:
  host: localhost
  port: 3306

# 배열 / 리스트
ports:
  - &quot;8080:8080&quot;
  - &quot;3306:3306&quot;

# boolean
debug: true
production: false

# 멀티라인 (| : 줄바꿈 유지)
command: |
  echo &quot;hello&quot;
  echo &quot;world&quot;

# 멀티라인 (&gt; : 줄바꿈을 공백으로 변환)
description: &gt;
  This is a long
  description text.</code></pre>
<br>

<h3 id="env-파일로-민감-정보-분리">.env 파일로 민감 정보 분리</h3>
<ul>
<li>docker-compose.yml 에 패스워드, 시크릿 키 등을 하드코딩하면 Git에 노출되는 보안 위험</li>
<li>.env 파일에 민감 정보를 분리하고 Compose에서 변수로 참조</li>
</ul>
<br>

<p><strong>docker-compose.yml 에서 .env 파일 참조</strong></p>
<pre><code class="language-bash">services:
  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
    ports:
      - &quot;3306:3306&quot;

  backend:
    build: 
    ports:
      - &quot;${APP_PORT}:8080&quot;</code></pre>
<br>

<p><strong>.env 파일 예시</strong></p>
<pre><code class="language-bash"># .env
MYSQL_ROOT_PASSWORD=prod_root_secret
MYSQL_DATABASE=appdb
MYSQL_USER=appuser
MYSQL_PASSWORD=prod_user_secret
APP_PORT=8080</code></pre>
<br>

<h3 id="docker-compose-명령어"><strong>Docker Compose 명령어</strong></h3>
<pre><code class="language-bash"># 서비스 시작 (백그라운드)
docker-compose up -d

# Dockerfile 변경사항 반영하여 재빌드 후 시작
docker-compose up --build -d

# 실행 중인 서비스 목록
docker-compose ps

# 서비스 로그 확인
docker-compose logs
docker-compose logs -f backend      # 특정 서비스 실시간 로그

# 서비스 중지
docker-compose stop

# 서비스 시작
docker-compose start

# 서비스 재시작
docker-compose restart

# 서비스 종료 및 컨테이너 삭제 (볼륨은 유지)
docker-compose down

# 서비스 종료 및 볼륨까지 삭제
docker-compose down -v

# 특정 서비스만 실행
docker-compose up -d db

# .env 파일 변수 치환 결과 확인 (실제 실행 전 검증)
docker-compose config</code></pre>
<hr>
<h3 id="2-tier-client--server-환경">2-Tier (Client / Server) 환경</h3>
<p>인터넷이 보급되기 전, 기업들은 <strong>폐쇄된 내부 네트워크</strong>에서 GUI 클라이언트가 DB 서버에 직접 접속하는 방식으로 업무를 처리</p>
<ul>
<li>대부분의 <strong>비즈니스 로직이 클라이언트 측</strong>에 존재</li>
<li>DB는 데이터 저장/조회 역할만 담당</li>
</ul>
<br>

<h3 id="3-tier-환경">3-Tier 환경</h3>
<p>클라이언트와 DB 사이에 <strong>애플리케이션 계층</strong>을 추가한 구조</p>
<table>
<thead>
<tr>
<th><strong>계층</strong></th>
<th><strong>역할</strong></th>
<th><strong>예시</strong></th>
</tr>
</thead>
<tbody><tr>
<td>Presentation</td>
<td>사용자 입력·출력 담당</td>
<td>브라우저, 앱</td>
</tr>
<tr>
<td>Application</td>
<td>비즈니스 로직 처리</td>
<td>Spring Boot, Django</td>
</tr>
<tr>
<td>Database</td>
<td>데이터 저장·조회</td>
<td>MySQL, PostgreSQL</td>
</tr>
</tbody></table>
<br>

<h3 id="n-tier-환경">N-Tier 환경</h3>
<p>3-Tier에서 <strong>Application 계층 내부를 역할별로 분리</strong>한 구조
목적 : 재사용</p>
<pre><code class="language-bash">[curl / 브라우저]            ← Presentation 계층
       ▼
[backend-server]             ← Application 계층 (Spring Boot)
  @RestController               ┌─ @Controller  (요청/응답)
  @Service                      ├─ @Service     (비즈니스 로직)
  @Repository                   └─ @Repository  (DB 접근)
       ▼
[mysql-server]               ← Database 계층
  (MySQL 8.0)</code></pre>
<br>

<p><strong>Docker Compose가 N-Tier를 구현하는 방식</strong></p>
<table>
<thead>
<tr>
<th><strong>N-Tier 개념</strong></th>
<th><strong>Docker Compose 구현</strong></th>
</tr>
</thead>
<tbody><tr>
<td>계층 분리</td>
<td>서비스(services)를 별도 컨테이너로 분리</td>
</tr>
<tr>
<td>계층 간 통신</td>
<td>사용자 정의 네트워크(app-network) + 컨테이너 DNS</td>
</tr>
<tr>
<td>데이터 영속성</td>
<td>volumes로 DB 데이터 계층 분리</td>
</tr>
<tr>
<td>기동 순서 보장</td>
<td><code>depends_on</code> + <code>healthcheck</code></td>
</tr>
<tr>
<td>독립 배포</td>
<td><code>docker-compose up --build -d backend</code> (계층별 독립 업데이트)</td>
</tr>
</tbody></table>
<br>

<h3 id="n-tier-application-구성-spring-boot--mysql">N-tier Application 구성 (Spring Boot + MySQL)</h3>
<ul>
<li>Docker를 활용하여 Spring Boot 백엔드 서버와 MySQL DB를 컨테이너로 구성하는 실무 아키텍처</li>
<li>서비스 간 통신은 사용자 정의 네트워크 + 컨테이너 이름(DNS) 으로 처리</li>
</ul>
<br>

<p>Spring Boot 프로젝트 생성 → 깃허브와 연동 → DB세팅 → Dockerfile 작성 → Docker Compose 구성</p>
<p>! Dockerfile의 경로가 중요함 !</p>
<br>

<p><strong>구조</strong></p>
<pre><code class="language-java">**Case A — 하위 디렉터리에 프로젝트**

**my-project/ ← 작업 루트
├── docker-compose.yml
├── Dockerfile ← 여기 위치
└── backend/ ← 프로젝트**
├── src/
├── pom.xml
└── mvnw

**# Dockerfile**
COPY backend/src ./src
COPY backend/pom.xml .
COPY backend/mvnw .

**# docker-compose.yml**
build:
context: . ← my-project/</code></pre>
<pre><code class="language-java">**Case B — 프로젝트가 루트 디렉터리

backend/ ← 작업 루트 = 프로젝트**
├── src/
├── pom.xml
├── mvnw
**├── docker-compose.yml
└── Dockerfile ← 여기 위치

# Dockerfile**
COPY src ./src
COPY pom.xml .
COPY mvnw .

**# docker-compose.yml**
build:
context: . ← backend/</code></pre>
<hr>
<h3 id="클라우드-컴퓨팅">클라우드 컴퓨팅</h3>
<ul>
<li>인터넷으로 서버, 스토리지, 데이터베이스, 네트워크 등 IT 자원을 <strong>필요할 때, 필요한 만큼</strong> 사용하는 방식</li>
<li>온디맨드(On-Demand), 사용량 기반 과금(Pay-as-you-go), 탄력성(Elasticity)</li>
</ul>
<br>

<h3 id="온프레미스-vs-클라우드">온프레미스 vs 클라우드</h3>
<p><strong>온프레미스 (On-Premises)</strong>
서버, 네트워크 장비, 데이터센터를 회사가 직접 소유·운영하는 방식. 하드웨어부터 데이터까지 완전한 통제권 보유</p>
<p><strong>클라우드 (Cloud)</strong>
AWS·GCP·Azure 등 사업자의 인프라를 빌려 쓰는 방식. 물리 인프라 관리는 사업자가 담</p>
<br>

<p><strong>클라우드 컴퓨팅의 5가지 특성</strong></p>
<table>
<thead>
<tr>
<th>특성</th>
<th>설명</th>
<th>우리 경험과 비교</th>
</tr>
</thead>
<tbody><tr>
<td><strong>온디맨드 셀프 서비스</strong></td>
<td>사람 개입 없이 자동으로 자원 프로비저닝</td>
<td>VM 생성을 IT팀에 요청하지 않아도 됨</td>
</tr>
<tr>
<td><strong>광범위한 네트워크 접근</strong></td>
<td>인터넷만 있으면 어디서든 접근</td>
<td>SSH로 원격 접속한 것처럼</td>
</tr>
<tr>
<td><strong>자원 풀링</strong></td>
<td>여러 사용자가 자원을 공유 (멀티테넌시)</td>
<td>물리 서버를 나눠 쓰는 개념</td>
</tr>
<tr>
<td><strong>빠른 탄력성</strong></td>
<td>수요에 따라 자동 확장/축소</td>
<td>트래픽 증가 시 서버 자동 추가</td>
</tr>
<tr>
<td><strong>측정 가능한 서비스</strong></td>
<td>사용량 정확히 측정, 사용한 만큼 과금</td>
<td>전기 요금처럼 사용량 청구</td>
</tr>
</tbody></table>
<br>

<h3 id="클라우드-서비스-모델-iaas--paas--saas">클라우드 서비스 모델 (IaaS / PaaS / SaaS)</h3>
<table>
<thead>
<tr>
<th>모델</th>
<th>풀네임</th>
<th>핵심 개념</th>
<th>고객 관리 범위</th>
<th>클라우드 제공 범위</th>
</tr>
</thead>
<tbody><tr>
<td>IaaS</td>
<td>Infrastructure as a Service</td>
<td>인프라 임대</td>
<td>OS · 런타임 · App · 데이터</td>
<td>서버 · 네트워크 · 스토리지 · 가상화</td>
</tr>
<tr>
<td>PaaS</td>
<td>Platform as a Service</td>
<td>플랫폼 임대</td>
<td>App · 데이터</td>
<td>OS · 런타임 · 인프라 전체</td>
</tr>
<tr>
<td>SaaS</td>
<td>Software as a Service</td>
<td>소프트웨어 구독</td>
<td>사용만</td>
<td>전 레이어</td>
</tr>
</tbody></table>
<br>

<h3 id="클라우드-배포-모델">클라우드 배포 모델</h3>
<table>
<thead>
<tr>
<th>모델</th>
<th>개념</th>
<th>현실적 예시</th>
</tr>
</thead>
<tbody><tr>
<td><strong>퍼블릭 클라우드</strong></td>
<td>AWS 등 공용 인프라 사용</td>
<td>우리 실습 환경</td>
</tr>
<tr>
<td><strong>프라이빗 클라우드</strong></td>
<td>기업 전용 인프라 (외부 공유 없음)</td>
<td>기업 자체 IDC</td>
</tr>
<tr>
<td><strong>하이브리드 클라우드</strong></td>
<td>온프레미스 + 퍼블릭 또는</td>
<td></td>
</tr>
<tr>
<td>AWS 퍼블릭 + AWS 프라이빗 혼합</td>
<td>개인정보는 온프레미스, 나머지 AWS 또는 AWS 퍼블릭 + AWS 프라이빗</td>
<td></td>
</tr>
<tr>
<td><strong>멀티 클라우드</strong></td>
<td>AWS + GCP + Azure 동시 사용</td>
<td>벤더 종속 방지, 리스크 분산</td>
</tr>
</tbody></table>
<br>

<h3 id="리전-region">리전 (Region)</h3>
<ul>
<li>AWS 데이터센터가 물리적으로 모여 있는 지리적 단위</li>
<li>각 리전은 완전히 독립적 — 한 리전 장애가 다른 리전에 영향 없음</li>
</ul>
<br>

<h3 id="가용-영역-availability-zone-az">가용 영역 (Availability Zone, AZ)</h3>
<ul>
<li>리전 내에서 <strong>물리적으로 분리된 독립 데이터센터 그룹</strong></li>
<li>서로 다른 전력, 냉각, 네트워크 인프라 사용 → 하나의 AZ 장애가 다른 AZ에 전파 안 됨</li>
</ul>
<br>

<h3 id="엣지-로케이션-edge-location">엣지 로케이션 (Edge Location)</h3>
<ul>
<li>사용자와 가장 가까운 위치에서 콘텐츠를 캐싱/제공하는 거점</li>
</ul>
<br>

<h3 id="aws-책임-공유-모델">AWS 책임 공유 모델</h3>
<pre><code class="language-bash">┌──────────────────────────────────────────────┐
│              고객 책임                       │
│                                              │
│  데이터 암호화 여부      IAM 사용자/권한 관리  │
│  OS 패치 (EC2)          네트워크/SG 설정      │
│  애플리케이션 보안       S3 버킷 공개 설정     │
│                                              │
├──────────────────────────────────────────────┤
│              AWS 책임                        │
│                                              │
│  물리적 서버/네트워크 장비                    │
│  데이터센터 보안 (출입 통제)                  │
│  하이퍼바이저 (가상화 레이어)                 │
│  리전/AZ 인프라 운영                          │
│                                              │
└──────────────────────────────────────────────┘</code></pre>
<br>

<p><strong>서비스 유형별 책임 범위 변화</strong></p>
<pre><code class="language-bash">             고객 책임 많음 ◄─────────────────► AWS 책임 많음

온프레미스    EC2(IaaS)    RDS(관리형DB)    Lambda(서버리스)
──────────────────────────────────────────────────────────
앱            앱           앱               앱
런타임        런타임       런타임           (런타임)
OS            OS           (OS)             (OS)
가상화        (가상화)     (가상화)         (가상화)
서버          (서버)       (서버)           (서버)
──────────────────────────────────────────────────────────
                  ( ) = AWS가 관리</code></pre>
<br>

<h3 id="aws-n-tier-아키텍처"><strong>AWS N-tier 아키텍처</strong></h3>
<pre><code class="language-bash">인터넷
  │
[Route 53]           ← DNS (도메인 → IP 변환)
  │
[ALB]                ← 로드밸런서 (트래픽 분산 + 헬스체크)
  ├── EC2 (AZ-a)     ← SpringBoot 서버
  └── EC2 (AZ-b)     ← SpringBoot 서버 (다중화)
         │
    [RDS MySQL 8.0]  ← 관리형 DB
    Primary (AZ-a) ↔ Standby (AZ-b)   ← Multi-AZ 자동 Failover
</code></pre>
<p> ✓ 서버가 죽어도 Auto Scaling이 자동 재생성
 ✓ 트래픽 증가 시 EC2 자동 추가 (Auto Scaling)
 ✓ 퍼블릭 도메인으로 전 세계 접속
 ✓ RDS Multi-AZ로 DB 자동 장애 복구
 ✓ GitHub Actions로 코드 Push 시 자동 배포</p>
<br>

<h3 id="컨테이너-기반-아키텍처"><strong>컨테이너 기반 아키텍처</strong></h3>
<pre><code class="language-bash">인터넷
  │
[ALB]
  │
[ECS Fargate]        ← 우리 도커 컨테이너를 AWS에서 그대로 실행
  ├── Task (SpringBoot 컨테이너)
  └── Task (SpringBoot 컨테이너)
         │
    [RDS MySQL 8.0]  ← MySQL 컨테이너 → 관리형 DB로 교체

GitHub Actions 자동화:
  코드 Push → 빌드 → Docker 이미지 → ECR → ECS 자동 배포</code></pre>
<br>

<h3 id="aws-클라우드-디자인-패턴">AWS 클라우드 디자인 패턴</h3>
<p>AWS 환경에서 반복적으로 검증된 아키텍처 해법</p>
<br>

<p><strong>핵심 설계 원칙</strong></p>
<ol>
<li><strong>장애 대비 설계 (Design for Failure)</strong></li>
<li><strong>느슨한 결합 (Loose Coupling)</strong> : 컴포넌트 간 의존성을 줄여 한 부분 장애가 전체로 전파되지 않게</li>
<li><strong>탄력성 (Elasticity)</strong> : 수요에 맞게 자동으로 확장하고 수요가 줄면 자동으로 축소</li>
</ol>
<pre><code class="language-bash">장애 대비 설계 : SPOF 제거 → 다중화 (Multi-AZ, ALB)
느슨한 결합   : SQS로 서비스 간 비동기 분리
탄력성        : Auto Scaling → 수요에 따라 EC2 자동 조정
                스케일 아웃 = EC2 수 증가 (권장)
                스케일 업   = 더 큰 인스턴스 교체</code></pre>
<ol start="4">
<li><strong>불변 인프라 (Immutable Infrastructure) :</strong> 서버를 수정하지 않고, 새 서버로 교체한다</li>
<li><strong>자동화 우선 (Automate Everything)</strong> : 수동 작업은 실수와 시간 낭비의 근원</li>
</ol>
<br>

<h3 id="aws-서비스-및-기능-이해">AWS 서비스 및 기능 이해</h3>
<pre><code class="language-bash">N-tier와 연관

[로컬 도커]                    [AWS 서비스]                 [카테고리]
────────────────────────────────────────────────────────────────────
Ubuntu VM                    EC2 ★                        컴퓨팅
Docker                       ECS / EKS ★                  컨테이너
Docker Image Registry        ECR ★                        컨테이너
SpringBoot 컨테이너           EC2 or ECS Task ★            컴퓨팅
MySQL 컨테이너                RDS MySQL ★                  데이터베이스
Redis 컨테이너                ElastiCache ★                데이터베이스
Docker Network               VPC ★                        네트워킹
ufw / iptables               Security Group / NACL ★      네트워킹
포트 포워딩 / Nginx           ELB (ALB) ★                  네트워킹
로컬 파일 마운트(-v)           S3 / EBS / EFS ★             스토리지
사용자 계정 (sudo 설정)        IAM ★                        보안
docker logs                  CloudWatch Logs ★             모니터링
docker stats                 CloudWatch Metrics ★          모니터링</code></pre>
<br>

<h3 id="컴퓨팅-서비스">컴퓨팅 서비스</h3>
<pre><code class="language-bash">EC2 (Elastic Compute Cloud)
─────────────────────────────────────────────────────
&quot;우분투 VM과 동일한 개념, AWS 클라우드에서&quot;

우리 실습: VirtualBox에 Ubuntu 설치 → SSH 접속
EC2:       AWS 콘솔에서 인스턴스 선택 → SSH 접속
           차이: 클릭 몇 번으로 생성, 안 쓸 때 종료하면 과금 중단

인스턴스 타입 체계:
  예: m5.xlarge
      │ │  └── 크기 (nano/micro/small/medium/large/xlarge/2xlarge...)
      │ └───── 세대 (숫자 클수록 최신, 일반적으로 성능↑ 비용↓)
      └──────── 패밀리

인스턴스 패밀리 (우리 앱에 맞는 타입 선택):
  t (t3, t4g) : 버스트 가능, 저비용 → 개발/테스트, 소규모
  m (m5, m6i) : 범용 균형 → SpringBoot 운영 서버
  c (c5, c6i) : CPU 최적화 → 배치 처리, API 서버 (CPU 집약적)
  r (r5, r6i) : 메모리 최적화 → Redis, 대용량 캐시 서버
  g (g4dn)    : GPU → ML 추론, 영상 처리

AMI (Amazon Machine Image):
  &quot;도커 이미지와 같은 개념 — EC2 인스턴스의 스냅샷/템플릿&quot;

  Amazon Linux 2023 : AWS 최적화 (권장)
  Ubuntu 22.04       : 우리가 익숙한 환경
  Windows Server     : .NET 앱 등

  우리가 만든 환경을 AMI로 저장
  → 동일 환경 인스턴스를 N개 즉시 생성 가능 (Auto Scaling에서 활용</code></pre>
<pre><code class="language-bash">Lambda
─────────────────────────────────────────────────────
&quot;실행할 코드만 올리면 서버는 AWS가 알아서&quot;

특징:
  이벤트 트리거 시에만 실행, 실행 시에만 과금
  최대 실행 시간: 15분
  메모리: 128MB ~ 10GB
  지원 런타임: Java, Python, Node.js, Go, .NET 등

우리 구조에서 활용 예시:
  S3에 파일 업로드 → Lambda 자동 실행
  → 썸네일 생성, 메타데이터 추출
  → RDS에 결과 저장

  SpringBoot 전체를 Lambda로 올리는 건 부적합
  (JVM 콜드 스타트, 15분 제한)</code></pre>
<pre><code class="language-bash">ECS (Elastic Container Service)
─────────────────────────────────────────────────────
&quot;우리 docker-compose를 클라우드에서&quot;

Fargate 방식 (권장):
  EC2 서버 없이 컨테이너만 실행
  → 우리가 만든 SpringBoot Docker 이미지 그대로 올림
  → 서버 관리(패치, 용량 등) 없음
  → 컨테이너 실행 시간만큼만 과금

Task Definition = docker-compose.yml과 유사:
  이미지: [ECR 주소]/my-springboot:latest
  CPU: 0.5 vCPU
  메모리: 1GB
  포트: 8080
  환경변수:
    SPRING_DATASOURCE_URL=jdbc:mysql://[RDS엔드포인트]:3306/mydb

Service:
  원하는 Task 수 유지 (예: 항상 2개)
  ALB와 통합하여 트래픽 분산
  새 이미지 배포 시 롤링 업데이트 자동 처리</code></pre>
<br>

<h3 id="스토리지-서비스">스토리지 서비스</h3>
<pre><code class="language-bash">S3 (Simple Storage Service)
─────────────────────────────────────────────────────
&quot;파일 저장소 — 용량 제한 없는 클라우드 오브젝트 스토리지&quot;

우리 실습과 연결:
  도커에서 파일을 볼륨(-v)으로 마운트
  → 애플리케이션에서 파일은 S3에 저장 (확장 가능, 내구성 높음)

특징:
  단일 파일 최대 5TB
  내구성: 99.999999999% (11 nine) — 사실상 데이터 손실 없음
  정적 웹사이트 호스팅 가능 (HTML/CSS/JS 직접 서빙)
  버킷(Bucket): S3의 최상위 컨테이너 (이름이 전 세계에서 고유해야 함)

스토리지 클래스 (비용 최적화용):

  Standard        : 자주 접근하는 데이터 (기본)
  Standard-IA     : 가끔 접근 (월 1회 이하) — 저장 비용 낮음
  Glacier Instant : 아카이브, 즉시 조회 가능
  Glacier Flexible: 아카이브, 수분~수시간 조회
  Glacier Deep Archive: 장기 보관 (수 시간 조회) — 최저 비용
  Intelligent-Tiering: 접근 패턴 불명확 시 자동 최적화

수명 주기 정책 (Lifecycle Policy):
  &quot;30일 후 Standard-IA로, 90일 후 Glacier로 자동 이동&quot;
  → S3 비용 자동 최적화</code></pre>
<pre><code class="language-bash">EBS (Elastic Block Store)
─────────────────────────────────────────────────────
&quot;EC2에 연결되는 가상 하드디스크&quot;

우리 실습: docker run -v /data:/data (로컬 디렉터리 마운트)
EBS:       EC2에 마운트하여 블록 디바이스로 사용

특징:
  단일 EC2에만 연결 (일반적으로)
  동일 AZ 내에서만 사용 가능
  EC2 종료 후에도 데이터 유지 (설정에 따라 다름)
  스냅샷으로 백업 가능 → 다른 AZ/리전 복사 가능</code></pre>
<pre><code class="language-bash">EFS (Elastic File System)
─────────────────────────────────────────────────────
&quot;여러 EC2가 동시에 마운트하는 공유 파일시스템 (NFS)&quot;

여러 SpringBoot 서버 인스턴스가 동일한 파일에 접근 필요할 때
  예: 업로드된 파일을 모든 앱 서버에서 읽어야 하는 경우
  (단, 이 패턴보다는 S3에 저장하는 것이 더 일반적)</code></pre>
<br>

<p><strong>스토리지 비교 정리</strong></p>
<pre><code class="language-bash">               S3              EBS             EFS
─────────────────────────────────────────────────────────
유형          객체             블록            파일(NFS)
접근 방식     HTTP API         블록 디바이스   NFS 마운트
용량          무제한           최대 64TiB      페타바이트급
멀티 연결     가능(HTTP)       불가(1대)       가능(멀티 EC2)
EC2 필요      불필요           필요            필요
대표 용도     파일 저장/배포    OS 디스크       공유 데이터</code></pre>
<br>

<h3 id="네트워킹-서비스">네트워킹 서비스</h3>
<pre><code class="language-bash">VPC (Virtual Private Cloud)
─────────────────────────────────────────────────────
&quot;우리 도커 네트워크의 AWS 버전&quot;

우리 실습:
  docker network create mynet
  → SpringBoot, MySQL이 같은 네트워크에서 내부 통신
  → -p로 명시한 포트만 외부에 노출

VPC:
  우리만의 격리된 가상 네트워크 (CIDR 예: 10.0.0.0/16)

  Public Subnet  (10.0.1.0/24, 10.0.2.0/24)
    → 인터넷 접근 가능
    → ALB, Bastion Host(관리용 서버) 위치

  Private Subnet (10.0.11.0/24, 10.0.12.0/24)
    → 직접 외부 접근 차단
    → EC2 앱 서버, ECS Task 위치
    → NAT Gateway 통해 아웃바운드만 가능

  DB Subnet      (10.0.21.0/24, 10.0.22.0/24)
    → Private보다 더 격리
    → RDS, ElastiCache 위치</code></pre>
<br>

<h3 id="데이터베이스-서비스">데이터베이스 서비스</h3>
<pre><code class="language-bash">RDS (Relational Database Service)
─────────────────────────────────────────────────────
&quot;우리 MySQL 컨테이너의 관리형 버전&quot;

우리 도커:                          RDS:
docker run mysql:8.0                엔진/버전 선택만 하면 됨
  → 우리가 직접 MySQL 운영          → 운영은 AWS가 처리

기능 비교:
  도커 MySQL          RDS MySQL
  ─────────────────────────────────────────────
  백업: 수동           자동 (1~35일 보관)
  패치: 직접           AWS 자동 처리
  HA:   없음           Multi-AZ (자동 Failover)
  모니터링: 직접       CloudWatch 자동 연동
  읽기 분산: 없음      Read Replica (최대 5개)
  스케일업: 직접       콘솔에서 인스턴스 타입 변경

Multi-AZ (고가용성):
  Primary (AZ-a) ← 동기 복제 → Standby (AZ-b)
  Primary 장애 시 → 자동 Failover → Standby가 Primary로
  애플리케이션 엔드포인트는 동일 (DNS가 자동 업데이트)

Read Replica (읽기 성능):
  Primary ← 비동기 복제 → Read Replica 1~5개
  → 읽기 트래픽을 Replica에 분산
  → Primary는 쓰기 전담 → 성능 향상

  Multi-AZ vs Read Replica:
    Multi-AZ = HA(고가용성) 목적, 자동 Failover
    Read Replica = 읽기 성능 향상 목적
    실무: 둘 다 사용 (HA + 성능)</code></pre>
<pre><code class="language-bash">ElastiCache (Redis)
─────────────────────────────────────────────────────
&quot;세션 저장, DB 캐싱 — Redis 완전 관리형&quot;

우리 도커: docker run redis
AWS:       ElastiCache Redis 클러스터 생성
           → 설치, 설정, 백업, 장애 대응 AWS가 처리

SpringBoot 연동 패턴:

1. 세션 저장 (여러 서버 인스턴스에서 세션 공유)
   Spring Session + Redis
   → EC2 A에 로그인 → 세션 Redis에 저장
   → 다음 요청이 EC2 B로 가도 세션 정상 조회

2. DB 캐싱 (자주 조회되는 데이터 캐싱)
   @Cacheable → Redis에 캐싱
   → DB 쿼리 횟수 감소 → RDS 부하 감소 → 응답 속도 향상</code></pre>
<br>

<h3 id="보안--모니터링-서비스">보안 &amp; 모니터링 서비스</h3>
<pre><code class="language-bash">IAM (Identity and Access Management)
─────────────────────────────────────────────────────
&quot;누가, 무엇에, 어떻게 접근할 수 있는지 정의&quot;

실습:
  sudo 권한 있는 ubuntu 계정으로 모든 것 처리
  → 실무에서는 &quot;모든 권한&quot; 계정 사용은 위험

IAM 구성 요소:
  User    : 개별 계정 (개발자, 운영자)
  Group   : User의 묶음 (Developers, Ops, Admins)
  Role    : 서비스/계정 간 임시 권한 부여
  Policy  : 권한 정의 JSON 문서

최소 권한 원칙 (Least Privilege Principle):
  &quot;필요한 것만 허용, 그 이상은 차단&quot;

  개발자 계정:
    EC2 접근 O, RDS 직접 접근 X

  EC2 앱 서버:
    S3 업로드 O, EC2 관리 X, IAM 콘솔 X
    → IAM Role을 EC2에 부여 (키를 코드에 저장하지 않음)

  GitHub Actions (CI/CD):
    ECR Push O, ECS 배포 O, RDS 삭제 X
    → OIDC 방식으로 임시 토큰 사용 (Access Key 불필요)

! 주의:
  루트 계정 일상 사용 (루트 = 모든 권한, MFA 필수)
  Access Key를 코드/GitHub에 커밋
  EC2에 Access Key 직접 저장 (IAM Role 사용)</code></pre>
<pre><code class="language-bash">CloudWatch
─────────────────────────────────────────────────────
&quot;서버/서비스 상태를 한눈에, 이상 시 자동 알림&quot;

실습:
  docker stats        → 컨테이너 CPU/메모리 실시간 확인
  journalctl -f       → 로그 실시간 확인
  직접 보고 있어야 감지 가능, 이상 징후 놓칠 수 있음

CloudWatch:
  메트릭 (Metrics):
    EC2 CPU 사용률, RDS 연결 수, ALB 요청 수 자동 수집

  로그 (Logs):
    SpringBoot 로그를 CloudWatch Logs로 전송
    → 여러 서버의 로그를 한곳에서 검색/분석

  알람 (Alarms):
    &quot;CPU 70% 초과 3분 지속&quot;
    → SNS → 이메일/Slack 알림 발송
    → Auto Scaling 트리거 → EC2 자동 추가

  대시보드:
    여러 서비스의 메트릭을 하나의 화면에 시각화

실제 알람 시나리오:
  RDS 연결 수가 갑자기 급증
  → CloudWatch Alarm 발생
  → SNS → 개발자 이메일/Slack 알림
  → 개발자가 CloudWatch Logs에서 원인 확인
  → SpringBoot connection pool 설정 수정</code></pre>
<br>

<table>
<thead>
<tr>
<th>카테고리</th>
<th>우분투 / 도커</th>
<th>AWS (아키텍처)</th>
<th>역할</th>
</tr>
</thead>
<tbody><tr>
<td><strong>호스트 서버</strong></td>
<td>VirtualBox Ubuntu VM</td>
<td><strong>EC2</strong></td>
<td>애플리케이션이 실행되는 서버</td>
</tr>
<tr>
<td><strong>호스트 IP</strong></td>
<td>10.0.2.15 (VirtualBox NAT)</td>
<td><strong>EC2 Private IP(10.0.11.XX)</strong></td>
<td>서버의 내부 IP 주소</td>
</tr>
<tr>
<td><strong>가상 네트워크</strong></td>
<td>Docker bridge (172.18.0.0/16)</td>
<td><strong>VPC(10.0.0.0/16)</strong></td>
<td>격리된 내부 네트워크</td>
</tr>
<tr>
<td><strong>네트워크 게이트웨이</strong></td>
<td>docker0 (172.18.0.1)</td>
<td><strong>VPC 라우터 + 인터넷 게이트웨이</strong></td>
<td>내부 ↔ 외부 트래픽 출입구</td>
</tr>
<tr>
<td><strong>앱 서버 IP</strong></td>
<td>172.18.0.2 (SpringBoot 컨테이너)</td>
<td><strong>EC2 Private IP(10.0.11.XX)</strong></td>
<td>SpringBoot가 할당받는 IP</td>
</tr>
<tr>
<td><strong>DB 서버 IP</strong></td>
<td>172.18.0.3 (MySQL 컨테이너)</td>
<td><strong>EC2 Private IP(10.0.21.XX)</strong></td>
<td>MySQL이 할당받는 IP</td>
</tr>
<tr>
<td><strong>앱 서버</strong></td>
<td>backend-server 컨테이너 (Spring Boot)</td>
<td><strong>ECS</strong></td>
<td>Spring Boot :8080 실행</td>
</tr>
<tr>
<td><strong>DB 서버</strong></td>
<td>mysql-server 컨테이너 (MySQL 8.0)</td>
<td><strong>RDS for MySQL</strong></td>
<td>데이터베이스</td>
</tr>
<tr>
<td><strong>DB 연결</strong></td>
<td>컨테이너 이름으로 통신 (mysql:3306)</td>
<td><strong>RDS 엔드포인트 DNS</strong></td>
<td>JDBC 접속 대상</td>
</tr>
<tr>
<td><strong>DB 볼륨</strong></td>
<td>Volume mount (/var/lib/mysql)</td>
<td><strong>RDS 내장 EBS</strong></td>
<td>DB 데이터 영구 저장</td>
</tr>
<tr>
<td><strong>포트 외부 노출</strong></td>
<td>-p 8080:8080 (포트 포워딩)</td>
<td><strong>ALB</strong></td>
<td>외부 트래픽 내부로 전달</td>
</tr>
<tr>
<td><strong>내부 망 격리</strong></td>
<td>docker network (app-network)</td>
<td><strong>Private Subnet</strong></td>
<td>외부에서 직접 접근 차단</td>
</tr>
<tr>
<td><strong>방화벽 — 앱</strong></td>
<td>ufw allow 8080/tcp</td>
<td><strong>App-SG(:ALB-SG)</strong></td>
<td>허용된 소스만 접근 가능</td>
</tr>
<tr>
<td><strong>방화벽 — DB</strong></td>
<td>ufw allow 3306 (내부만)</td>
<td><strong>DB-SG:App-SG</strong></td>
<td>EC2에서만 DB 접근 허용</td>
</tr>
<tr>
<td><strong>방화벽 — 외부</strong></td>
<td>ufw allow 80/443</td>
<td><strong>ALB-SG</strong></td>
<td>인터넷 → ALB 허용</td>
</tr>
<tr>
<td><strong>라우팅</strong></td>
<td>ip route show (172.18.0.0/16 dev docker0)</td>
<td><strong>VPC 라우팅 테이블</strong></td>
<td>내부 트래픽 경로 설정</td>
</tr>
<tr>
<td><strong>외부 인터넷 연결</strong></td>
<td>VirtualBox NAT (10.0.2.2 게이트웨이)</td>
<td><strong>인터넷 게이트웨이(IGW)</strong></td>
<td>VPC ↔ 인터넷 출입구</td>
</tr>
<tr>
<td><strong>OS 최고 권한 계정</strong></td>
<td>root</td>
<td><strong>AWS root 계정</strong></td>
<td>모든 권한 · 일상 사용 금지</td>
</tr>
<tr>
<td><strong>일반 사용자 계정</strong></td>
<td>ubuntu (sudo 권한)</td>
<td><strong>IAM User</strong></td>
<td>콘솔/CLI 접근 계정</td>
</tr>
<tr>
<td><strong>서비스 전용 계정</strong></td>
<td>mysql OS 유저 (mysqld 실행)</td>
<td><strong>IAM Role</strong></td>
<td>프로세스가 AWS API 호출 시 사용</td>
</tr>
<tr>
<td><strong>권한 제한</strong></td>
<td>/etc/sudoers</td>
<td><strong>IAM Policy</strong></td>
<td>허용 명령/액션 범위 지정</td>
</tr>
<tr>
<td><strong>계정 그룹</strong></td>
<td>/etc/group</td>
<td><strong>IAM Group</strong></td>
<td>사용자 묶음 단위 권한 관리</td>
</tr>
<tr>
<td><strong>파일 권한</strong></td>
<td>chmod 600 (키 파일)</td>
<td><strong>IAM 최소 권한 원칙</strong></td>
<td>필요한 것만 허용</td>
</tr>
<tr>
<td><strong>SSH 인증</strong></td>
<td>~/.ssh/authorized_keys</td>
<td><strong>EC2 키페어(.pem)</strong></td>
<td>서버 접속 인증 수단</td>
</tr>
<tr>
<td><strong>DB 사용자</strong></td>
<td>MySQL root / app_user</td>
<td><strong>RDS 계정</strong></td>
<td>DB 레벨 계정 (변경 없음)</td>
</tr>
<tr>
<td><strong>DB 권한</strong></td>
<td>GRANT SELECT ON mydb.* TO app_user</td>
<td><strong>RDS 계정</strong></td>
<td>테이블 단위 접근 제어</td>
</tr>
<tr>
<td><strong>로그 확인</strong></td>
<td>docker logs / journalctl</td>
<td><strong>CloudWatch Logs</strong></td>
<td>애플리케이션 로그 수집</td>
</tr>
<tr>
<td><strong>리소스 모니터링</strong></td>
<td>docker stats / top</td>
<td><strong>CloudWatch Metrics</strong></td>
<td>CPU · 메모리 · 네트워크 지표</td>
</tr>
</tbody></table>
<br>

<h3 id="aws-아키텍처-설계">AWS 아키텍처 설계</h3>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/60f2a625-9743-47ea-9e4c-58a0e5f139ef/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[멋쟁이사자처럼부트캠프 클라우드 엔지니어링 5기 [8주차]]]></title>
            <link>https://velog.io/@its-jihyeon/%EB%A9%8B%EC%9F%81%EC%9D%B4%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A7%81-5%EA%B8%B0-8%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@its-jihyeon/%EB%A9%8B%EC%9F%81%EC%9D%B4%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A7%81-5%EA%B8%B0-8%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Sun, 22 Mar 2026 09:47:59 GMT</pubDate>
            <description><![CDATA[<h3 id="01-리눅스-개요--셸-기본">01. 리눅스 개요 &amp; 셸 기본</h3>
<h3 id="리눅스linux"><strong>리눅스(Linux)</strong></h3>
<p>1991년 Linus Torvalds가 개발한 오픈소스 운영체제 커널</p>
<br>

<p><strong>Linux 구성</strong></p>
<ul>
<li><strong>커널 (Kernel) :</strong> OS의 핵심. <strong>하드웨어 ↔ 소프트웨어</strong> 사이의 인터페이스</li>
<li><strong>쉘 (Shell) :</strong> 사용자가 입력한 명령어를 커널에 전달하는 인터페이스</li>
<li><strong>응용 프로그램 (Application) :</strong> 커널 위에서 동작하는 모든 프로그램</li>
</ul>
<br>

<p><strong>주요 셸 종류</strong></p>
<table>
<thead>
<tr>
<th>셸</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>bash</code></td>
<td>Bourne Again SHell — Ubuntu 기본 셸, 가장 널리 사용</td>
</tr>
<tr>
<td><code>zsh</code></td>
<td>bash 확장 — macOS 기본 셸, 자동완성 강화</td>
</tr>
<tr>
<td><code>sh</code></td>
<td>전통적인 Bourne Shell — 스크립트 호환성 기준</td>
</tr>
<tr>
<td><code>fish</code></td>
<td>사용자 친화적 셸 — 자동완성, 색상 강조 내장</td>
</tr>
</tbody></table>
<br>

<h3 id="echo">echo</h3>
<p>문자열 또는 변수 값을 터미널에 출력 (메시지 출력, 변수 확인, 파일 내용 작성)</p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/de84e15c-7666-4031-9f4f-ef67aa66d312/image.png" alt=""></p>
<br>

<h3 id="man----help">man / --help</h3>
<p>명령어 사용법과 옵션을 확인하는 내장 문서</p>
<p>man : 다른페이지로 넘어가서 보여줌 (상세 문서)</p>
<p>help : 그 페이지에서 보여줌 (빠른 옵션 확인용)</p>
<br>

<h3 id="history">history</h3>
<p>이전에 실행한 명령어 목록 확인 및 재실행</p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/92474532-42da-4b16-b421-8678930fe82f/image.png" alt=""></p>
<br>

<p><strong>단축키</strong></p>
<p>Ctrl + C : 현재 실행 중인 명령어 강제 중단</p>
<br>

<p>기타 기본 명령어</p>
<table>
<thead>
<tr>
<th>명령어</th>
<th>기능</th>
<th>예시</th>
</tr>
</thead>
<tbody><tr>
<td><code>clear</code></td>
<td>터미널 화면 지우기</td>
<td><code>clear</code></td>
</tr>
<tr>
<td><code>whoami</code></td>
<td>현재 사용자명 출력</td>
<td><code>whoami</code></td>
</tr>
<tr>
<td><code>hostname</code></td>
<td>호스트명 출력</td>
<td><code>hostname</code></td>
</tr>
<tr>
<td><code>date</code></td>
<td>현재 날짜/시간 출력</td>
<td><code>date</code></td>
</tr>
<tr>
<td><code>uname</code></td>
<td>시스템 정보 출력</td>
<td><code>uname -a</code></td>
</tr>
<tr>
<td><code>which</code></td>
<td>명령어 실행 파일 경로 확인</td>
<td><code>which java</code></td>
</tr>
<tr>
<td><code>type</code></td>
<td>명령어 종류 확인 (내장/외부/alias)</td>
<td><code>type ls</code></td>
</tr>
</tbody></table>
<br>

<h3 id="리눅스-주요-디렉터리-구조">리눅스 주요 디렉터리 구조</h3>
<pre><code class="language-java">/
├── bin         실행 가능한 기본 명령어 (ls, cp, mv 등)
├── sbin        시스템 관리용 명령어 (root 전용, fdisk, reboot 등)
├── etc         시스템 전체 설정 파일 (sshd_config, hosts, fstab 등)
├── home        일반 사용자 홈 디렉터리 (/home/ubuntu)
├── root        root 사용자 홈 디렉터리
├── var         가변 데이터 (로그, 캐시, 메일 스풀)
│   └── log     시스템 로그 파일
├── tmp         임시 파일 (재시작 시 삭제)
├── usr         사용자 프로그램 및 라이브러리
│   ├── bin     사용자 명령어 (python3, git 등)
│   ├── lib     공유 라이브러리
│   └── local   수동 설치 소프트웨어
├── opt         서드파티 소프트웨어 설치 경로 (JDK, Tomcat 등)
├── proc        실행 중인 프로세스 정보 (가상 파일시스템)
├── sys         커널 및 하드웨어 정보 (가상 파일시스템)
├── dev         장치 파일 (디스크, 터미널 등)
├── mnt         임시 마운트 포인트
├── media       외부 미디어 자동 마운트 (USB 등)
├── lib         부팅에 필요한 공유 라이브러리
└── boot        부트로더 및 커널 이미지</code></pre>
<hr>
<h3 id="02-디렉터리-구조--탐색">02. 디렉터리 구조 &amp; 탐색</h3>
<h3 id="ls">ls</h3>
<p>디렉터리 내용 목록 출력</p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/f59fea84-c344-4147-a018-a8c7d1db0f33/image.png" alt="">
<img src="https://velog.velcdn.com/images/its-jihyeon/post/8d25f989-4dff-4a50-bd5d-0b38df4f55f9/image.png" alt=""></p>
<br>

<p><strong>ls -l 출력 형식</strong></p>
<pre><code class="language-bash">drwxr-xr-x  2 ubuntu ubuntu 4096 Mar 15 09:00 config
│││││││││││  │ │      │      │    │             │
│││││││││││  │ │      │      │    │             └─ 파일/디렉터리명
│││││││││││  │ │      │      │    └─────────────── 수정 날짜
│││││││││││  │ │      │      └──────────────────── 크기 (bytes)
│││││││││││  │ │      └─────────────────────────── 그룹명
│││││││││││  │ └─────────────────────────────────── 소유자명
│││││││││││  └───────────────────────────────────── 링크 수
│││││││ └────────────────────────────────────────── 기타 권한 (r-x)
││││└────────────────────────────────────────────── 소유자 권한 (rwx)
│└───────────────────────────────────────────────── 그룹 권한 (r-x)
│       
└───────────────────────────────────────────────── 파일 타입 (d=디렉터리, -=파일, l=링크)</code></pre>
<br>

<h3 id="cd">cd</h3>
<p>현재 작업 디렉터리 변경</p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/d18ed41d-98fc-4681-a495-14a30f1e71f7/image.png" alt=""></p>
<br>

<h3 id="pwd">pwd</h3>
<p>현재 작업 디렉터리의 절대 경로 출력</p>
<br>

<h3 id="tree">tree</h3>
<p>디렉터리 구조를 트리 형태로 시각화</p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/ffd208ea-adbb-4bab-833c-b181e700d75b/image.png" alt=""></p>
<hr>
<h3 id="03-파일--디렉터리-관리">03. 파일 &amp; 디렉터리 관리</h3>
<h3 id="mkdir">mkdir</h3>
<p>디렉터리 생성</p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/bfbcd331-157a-44fd-a296-b9f4189a3207/image.png" alt=""></p>
<br>

<h3 id="touch">touch</h3>
<p>빈 파일 생성 또는 기존 파일의 타임스탬프 갱신</p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/6a680519-fc3e-4157-a8ce-e2d1abc353fd/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/2c234d0e-2366-44b7-9487-a00bc5b5d6e9/image.png" alt=""></p>
<br>

<h3 id="cp">cp</h3>
<p>파일 또는 디렉터리 복사</p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/c4d18788-4378-4974-9638-1e3a571e208f/image.png" alt=""></p>
<br>

<h3 id="mv">mv</h3>
<p>파일 또는 디렉터리 이동 / 이름 변경</p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/ee5d3548-fab5-4b3d-9a71-b74185802224/image.png" alt=""></p>
<br>

<h3 id="rm">rm</h3>
<p>파일 또는 디렉터리 삭제 (휴지통 없이 즉시 삭제 — 복구 불가)</p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/be45b099-a022-4396-b959-6097cb448cc6/image.png" alt=""></p>
<hr>
<h3 id="04-파일-내용-확인--검색">04. 파일 내용 확인 &amp; 검색</h3>
<h3 id="cat">cat</h3>
<p>파일 내용을 터미널에 전체 출력</p>
<br>

<h3 id="head">head</h3>
<p>파일 앞부분 출력 (기본 10줄)</p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/a6b951a0-4b59-4295-9d1f-78b9747628a2/image.png" alt=""></p>
<br>

<h3 id="tail">tail</h3>
<p>파일 끝부분 출력 (기본 10줄)</p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/9eb565f6-6352-4996-80af-0b3e80b1ad78/image.png" alt=""></p>
<br>

<h3 id="more--less">more / less</h3>
<p>파일 내용을 페이지 단위로 출력 (more 보다 less 를 권장)</p>
<br>

<h3 id="find">find</h3>
<pre><code class="language-bash">find [PATH] [OPTION] [EXPRESSION]</code></pre>
<p>PATH 생략 시 현재 디렉터리(.) 기준으로 탐색</p>
<br>

<p><strong>액션</strong></p>
<table>
<thead>
<tr>
<th>표현식</th>
<th>기능</th>
</tr>
</thead>
<tbody><tr>
<td><code>-exec CMD {} \;</code></td>
<td>검색된 파일 각각에 명령 실행</td>
</tr>
<tr>
<td><code>-exec CMD {} +</code></td>
<td>검색된 파일을 한 번에 묶어 명령 실행 (성능 우수)</td>
</tr>
</tbody></table>
<br>

<pre><code class="language-bash">find /tmp -name &quot;*.log&quot; -exec rm {} \;          # 각 파일마다 rm 실행
find /tmp -name &quot;*.log&quot; -exec rm {} +           # 한 번에 묶어 rm 실행 (권장)
find . -name &quot;*.java&quot; -exec grep -l &quot;TODO&quot; {} \;  # TODO 포함 Java 파일 검색</code></pre>
<br>

<pre><code class="language-Bash">- .yml 검색?
- .JAVA 검색?
- .log 파일 검색?
find find_test/ -name &quot;*.yml&quot;
find find_test/ -iname &quot;*.JAVA&quot;
find find_test/ -type f -name &quot;*.log&quot;

- 30 일 이전에 수정된 파일?
- 정확히 1일 이전에 수정된 파일?
- 7일 이상 접근하지 않은 파일?
find find_test/ -mtime +30
find find_test/ -mtime 1
find find_test/ -atime +7

- 빈 파일?
- 10kb 미만 파일?
find find_test/ -size 0c
find find_test/ -size -10k

- .java 제외하고 나머지 파일들?
- .log 또는 .yml 파일?
find find_test/ -not -name &quot;*.java&quot;
find find_test/ -name &quot;*.log&quot; -o -name &quot;*.yml&quot;

- *.log 파일 검색 -&gt; cat 출력
find find_test/ -name &quot;*.log&quot; -exec cat {} \;

- *.log 파일 검색 -&gt; 30 일 이전에 수정된 파일 -&gt; 삭제?
find find_test/ -name &quot;*.log&quot; -mtime +30 -exec rm {} +</code></pre>
<hr>
<h3 id="05-링크">05. 링크</h3>
<p><strong>하드 링크 vs 심볼릭 링크</strong></p>
<table>
<thead>
<tr>
<th>구분</th>
<th>하드 링크</th>
<th>심볼릭 링크</th>
</tr>
</thead>
<tbody><tr>
<td>참조 방식</td>
<td>동일한 inode를 직접 가리킴</td>
<td>원본 파일 경로를 가리키는 별도 파일</td>
</tr>
<tr>
<td>원본 삭제 시</td>
<td>데이터 유지 (inode 참조 카운트 감소)</td>
<td>링크가 깨짐 (dangling link)</td>
</tr>
<tr>
<td>디렉터리 대상</td>
<td>불가 (루트 제외)</td>
<td>가능</td>
</tr>
<tr>
<td>파일시스템 경계</td>
<td>동일 파일시스템만 가능</td>
<td>다른 파일시스템, 네트워크 경로도 가능</td>
</tr>
<tr>
<td><code>ls -l</code> 표시</td>
<td>일반 파일과 동일</td>
<td><code>l</code> 타입 + <code>-&gt;</code> 경로 표시</td>
</tr>
<tr>
<td>실무 활용</td>
<td>중요 설정 파일 이중 보관</td>
<td>배포 버전 전환, 설정 파일 공유</td>
</tr>
</tbody></table>
<br>

<h3 id="ln-링크-생성">ln 링크 생성</h3>
<p><strong>옵션</strong></p>
<table>
<thead>
<tr>
<th>옵션</th>
<th>기능</th>
</tr>
</thead>
<tbody><tr>
<td><code>-s</code></td>
<td>심볼릭 링크 생성 (생략 시 하드 링크)</td>
</tr>
<tr>
<td><code>-f</code></td>
<td>기존 링크 파일이 있으면 덮어쓰기</td>
</tr>
<tr>
<td><code>-n</code></td>
<td>링크 대상이 디렉터리인 경우 링크 자체로 처리</td>
</tr>
<tr>
<td><code>-v</code></td>
<td>생성 과정 출력 (verbose)</td>
</tr>
<tr>
<td><code>-r</code></td>
<td>심볼릭 링크를 상대 경로로 생성</td>
</tr>
</tbody></table>
<pre><code class="language-bash"># 심볼릭 링크 생성
ln -s /home/ubuntu/app/config/application.yml /etc/myapp/application.yml
ln -s /home/ubuntu/app-v2.0/ /home/ubuntu/app-current     # 디렉터리 링크

# 하드 링크 생성
ln /home/ubuntu/app/config/application.yml /home/ubuntu/backup/application.yml.hard

# 링크 교체 (배포 버전 전환)
ln -sfn /home/ubuntu/releases/v2.1.0 /home/ubuntu/current  # -n: 디렉터리 링크 안전 교체</code></pre>
<br>

<h3 id="ls--l--readlink">ls -l / readlink</h3>
<p>링크 확인</p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/767f9f25-26e8-4eaf-9417-d2c01ce07c11/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/dd51dca9-f4ff-4e69-aed5-edcd7365a8c9/image.png" alt=""></p>
<br>

<p><strong>링크 관련 find 활용</strong></p>
<pre><code class="language-bash"># 깨진 심볼릭 링크 검색 (원본이 없는 링크)
find . -type l ! -exec test -e {} \; -print

# 하드 링크 카운트가 2 이상인 파일 검색 (하드 링크 존재)
find . -type f -links +1</code></pre>
<hr>
<h3 id="06-압축--아카이브">06. 압축 &amp; 아카이브</h3>
<p><strong>아카이브 vs 압축</strong> </p>
<table>
<thead>
<tr>
<th>구분</th>
<th>도구</th>
<th>결과</th>
<th>특징</th>
</tr>
</thead>
<tbody><tr>
<td>아카이브만</td>
<td><code>tar</code></td>
<td><code>.tar</code></td>
<td>묶기만, 압축 없음</td>
</tr>
<tr>
<td>압축만</td>
<td><code>gzip</code></td>
<td><code>.gz</code></td>
<td>단일 파일만 압축</td>
</tr>
<tr>
<td>아카이브 + 압축</td>
<td><code>tar + gzip</code></td>
<td><code>.tar.gz</code> / <code>.tgz</code></td>
<td>실무 표준</td>
</tr>
<tr>
<td>아카이브 + 압축</td>
<td><code>tar + bzip2</code></td>
<td><code>.tar.bz2</code></td>
<td>높은 압축률, 느림</td>
</tr>
<tr>
<td>아카이브 + 압축</td>
<td><code>tar + xz</code></td>
<td><code>.tar.xz</code></td>
<td>최고 압축률, 매우 느림</td>
</tr>
<tr>
<td>zip 형식</td>
<td><code>zip</code> / <code>unzip</code></td>
<td><code>.zip</code></td>
<td>Windows 호환, 파일별 압축</td>
</tr>
</tbody></table>
<br>

<h3 id="tar">tar</h3>
<p>여러 파일·디렉터리를 하나의 아카이브 파일로 묶기 / 풀기</p>
<pre><code class="language-bash">tar [OPTION] [ARCHIVE] [FILE...]</code></pre>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/81c9c6ab-915c-4002-9f18-baaad61360d6/image.png" alt=""></p>
<br>

<p><strong>주요 옵션</strong></p>
<table>
<thead>
<tr>
<th>옵션</th>
<th>기능</th>
</tr>
</thead>
<tbody><tr>
<td><code>-c</code></td>
<td>아카이브 생성 (Create)</td>
</tr>
<tr>
<td><code>-x</code></td>
<td>아카이브 해제 (eXtract)</td>
</tr>
<tr>
<td><code>-t</code></td>
<td>아카이브 내용 목록 출력 (lisT)</td>
</tr>
<tr>
<td><code>-f FILE</code></td>
<td>아카이브 파일명 지정 — 항상 마지막에 위치</td>
</tr>
<tr>
<td><code>-v</code></td>
<td>처리 과정 출력 (Verbose)</td>
</tr>
<tr>
<td><code>-z</code></td>
<td>gzip 압축 / 해제 (<code>.tar.gz</code>)</td>
</tr>
<tr>
<td><code>-j</code></td>
<td>bzip2 압축 / 해제 (<code>.tar.bz2</code>)</td>
</tr>
<tr>
<td><code>-J</code></td>
<td>xz 압축 / 해제 (<code>.tar.xz</code>)</td>
</tr>
<tr>
<td><code>-C DIR</code></td>
<td>지정한 디렉터리에 해제</td>
</tr>
<tr>
<td><code>--exclude=PATTERN</code></td>
<td>특정 파일 제외</td>
</tr>
<tr>
<td><code>-p</code></td>
<td>권한 유지</td>
</tr>
</tbody></table>
<pre><code class="language-bash"># logs/*.log 를 제외하고 재압축 (--exclude 사용)
tar -cvzf release-nolog.tar.gz --exclude=&quot;*.log&quot; release/</code></pre>
<br>

<h3 id="gzip--gunzip">gzip / gunzip</h3>
<p>단일 <strong>파일</strong> 압축 / 해제 </p>
<pre><code class="language-bash">gzip [OPTION] FILE
gunzip [OPTION] FILE.gz</code></pre>
<br>

<h3 id="zip--unzip">zip / unzip</h3>
<p>Windows 호환 zip 형식 압축 / 해제</p>
<pre><code class="language-bash">zip [OPTION] ARCHIVE.zip FILE...
unzip [OPTION] ARCHIVE.zip</code></pre>
<hr>
<h3 id="07-권한--소유자-관리">07. 권한 &amp; 소유자 관리</h3>
<p>모든 파일과 디렉터리에 <strong>소유자(owner), 그룹(group), 기타(others)</strong> 세 주체에 대한 권한을 부여</p>
<br>

<p><strong>권한 숫자 표기</strong></p>
<table>
<thead>
<tr>
<th>권한</th>
<th>숫자</th>
</tr>
</thead>
<tbody><tr>
<td><code>r</code></td>
<td>4</td>
</tr>
<tr>
<td><code>w</code></td>
<td>2</td>
</tr>
<tr>
<td><code>x</code></td>
<td>1</td>
</tr>
<tr>
<td><code>-</code></td>
<td>0</td>
</tr>
</tbody></table>
<br>

<h3 id="chmod">chmod</h3>
<p>파일 또는 디렉터리의 권한 변경</p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/6899924c-958e-428c-970a-a47490f161f3/image.png" alt=""></p>
<br>

<p><strong>심볼릭 모드</strong></p>
<table>
<thead>
<tr>
<th>대상</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td><code>u</code></td>
<td>owner (소유자)</td>
</tr>
<tr>
<td><code>g</code></td>
<td>group (그룹)</td>
</tr>
<tr>
<td><code>o</code></td>
<td>others (기타)</td>
</tr>
<tr>
<td><code>a</code></td>
<td>all (전체)</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>
<br>

<h3 id="chown">chown</h3>
<p>파일 또는 디렉터리의 소유자 / 그룹 변경</p>
<pre><code class="language-bash">chown [OPTION] OWNER[:GROUP] FILE</code></pre>
<br>

<h3 id="chgrp">chgrp</h3>
<p>파일 또는 디렉터리의 그룹만 변경</p>
<br>

<h3 id="umask">umask</h3>
<p>새로 생성되는 파일/디렉터리의 기본 권한 마스크 설정</p>
<pre><code class="language-bash">파일 기본 최대 권한 : 666 (rw-rw-rw-)
디렉터리 기본 최대 권한 : 777 (rwxrwxrwx)</code></pre>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/489cadc5-cc0d-4c78-8583-8dc77ef6ce0f/image.png" alt=""></p>
<hr>
<h3 id="08-디스크--용량-관리">08. 디스크 &amp; 용량 관리</h3>
<h3 id="df">df</h3>
<p>파일시스템별 디스크 사용량 확인
<img src="https://velog.velcdn.com/images/its-jihyeon/post/986ccd10-a0c4-48fb-987f-fb22a249223e/image.png" alt=""></p>
<pre><code class="language-bash">df -h

Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1        20G  4.2G   15G  23% /
tmpfs           992M     0  992M   0% /dev/shm
│               │     │     │     │   └─ 마운트 포인트
│               │     │     │     └─── 사용률
│               │     │     └───────── 사용 가능 용량
│               │     └─────────────── 사용 중 용량
│               └───────────────────── 전체 용량
└───────────────────────────────────── 파일시스템 (디바이스)</code></pre>
<br>

<h3 id="du">du</h3>
<p>디렉터리 또는 파일의 실제 사용 용량 확인</p>
<hr>
<h3 id="01-텍스트-검색--치환">01. 텍스트 검색 &amp; 치환</h3>
<h3 id="grep">grep</h3>
<p>파일 또는 표준 입력에서 <strong>패턴(문자열/정규식)과 일치하는 행</strong>을 검색·출력</p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/f2ac6d57-6766-4577-a134-11fe3ee248fa/image.png" alt=""></p>
<pre><code class="language-bash"># 대소문자 구분 없이 검색
grep -i &quot;error&quot; app.log

# 일치하는 행의 수 출력
grep -c &quot;ERROR&quot; app.log

# 패턴과 일치하지 않는 행 출력
grep -v &quot;ERROR&quot; app.log

# 정규 표현식 (ERROR 또는 WARN 메시지 포함하는 행 출력)
grep -E &quot;ERROR|WARN&quot; app.log

# 정규 표현식 (user_id가 숫자로 지정되어 있는 행 출력)
grep -E &quot;user_id=[0-9]+&quot; app.log</code></pre>
<br>

<h3 id="sed">sed</h3>
<p>스트림 에디터(Stream Editor) — 파일을 열지 않고 텍스트를 읽으면서 변환·치환·삭제</p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/7c960505-6a5a-4cf8-81ed-aa0bda93515d/image.png" alt=""></p>
<pre><code class="language-bash"># 1 ~ 5번째 행만 출력
sed -n &#39;1,5p&#39; application.yml</code></pre>
<br>

<h3 id="awk">awk</h3>
<p>패턴 스캔 및 텍스트 처리 언어 — 파일을 행 단위로 읽어 필드(열) 추출, 조건 필터링, 집계 처리</p>
<pre><code class="language-bash">awk [OPTION] &#39;PROGRAM&#39; [FILE...]
awk [OPTION] -f SCRIPT_FILE [FILE...]</code></pre>
<br>

<p><strong>PROGRAM 구조</strong></p>
<pre><code class="language-bash">awk &#39;BEGIN { 초기화 } /패턴/ { 처리 } END { 마무리 }&#39; FILE</code></pre>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/2ef84cb2-2777-4405-8900-af4aee07e8b9/image.png" alt=""></p>
<hr>
<h3 id="02-파이프--리다이렉션">02. 파이프 &amp; 리다이렉션</h3>
<p><strong>표준 스트림</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>
<br>

<h3 id="기본-파이프-">기본 파이프 (<code>|</code>)</h3>
<p>앞 명령어의 stdout을 뒷 명령어의 stdin으로 전달</p>
<br>

<h3 id="tee">tee</h3>
<p>stdout을 파일에 저장하면서 동시에 화면에도 출력</p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/1f36ea7a-c014-40b5-9e13-307a0e0ce49a/image.png" alt=""></p>
<br>

<h3 id="sort">sort</h3>
<p>텍스트를 줄 단위로 정렬</p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/16587bb2-14f7-4322-8652-bbe37768b846/image.png" alt=""></p>
<br>

<h3 id="uniq">uniq</h3>
<p>연속된 중복 행 제거 또는 집계</p>
<p>! <strong>정렬(sort)  후 사용</strong>해야 한다 !</p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/f8d8a135-afc3-4d23-8513-d0d06dc56825/image.png" alt=""></p>
<br>

<h3 id="wc">wc</h3>
<p>파일 또는 입력의 줄 수, 단어 수, 바이트 수 카운트</p>
<br>

<h3 id="cut">cut</h3>
<p>각 행에서 특정 필드 또는 문자 범위를 잘라내기</p>
<br>

<h3 id="tr">tr</h3>
<p>문자 변환, 삭제, 압축
<img src="https://velog.velcdn.com/images/its-jihyeon/post/71872f67-e679-46bd-b687-5e0cd974c35c/image.png" alt=""></p>
<hr>
<h3 id="04-파일-비교--패치">04. 파일 비교 &amp; 패치</h3>
<ul>
<li><strong>diff</strong> : 두 파일의 차이점을 줄 단위로 비교</li>
<li><strong>patch</strong> : diff 결과를 적용하여 파일 변경</li>
<li><strong>comm</strong> : 정렬된 두 파일의 공통/차이 행 비교</li>
</ul>
<br>

<h3 id="05-vim-편집기">05. vim 편집기</h3>
<p>리눅스 서버 환경에서 GUI 없이 파일을 직접 편집하는 CLI 텍스트 편집기</p>
<table>
<thead>
<tr>
<th>명령</th>
<th>기능</th>
</tr>
</thead>
<tbody><tr>
<td><code>:w</code></td>
<td>저장</td>
</tr>
<tr>
<td><code>:q</code></td>
<td>종료 (변경사항 없을 때)</td>
</tr>
<tr>
<td><code>:wq</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>:w filename</code></td>
<td>다른 이름으로 저장</td>
</tr>
<tr>
<td><code>ZZ</code></td>
<td><code>:wq</code>와 동일</td>
</tr>
</tbody></table>
<br>

<p><strong>입력모드</strong></p>
<table>
<thead>
<tr>
<th>키</th>
<th>기능</th>
</tr>
</thead>
<tbody><tr>
<td><code>i</code></td>
<td>커서 앞에서 입력</td>
</tr>
<tr>
<td><code>a</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>o</code></td>
<td>현재 행 아래 새 줄 추가 후 입력</td>
</tr>
<tr>
<td><code>O</code></td>
<td>현재 행 위 새 줄 추가 후 입력</td>
</tr>
</tbody></table>
<pre><code class="language-bash">set number          &quot; 줄 번호 표시
set tabstop=4       &quot; 탭 크기 4
set expandtab       &quot; 탭을 스페이스로
set autoindent      &quot; 자동 들여쓰기
set hlsearch        &quot; 검색 결과 강조
set ignorecase      &quot; 검색 대소문자 무시
set smartcase       &quot; 대문자 포함 시 대소문자 구분
syntax on           &quot; 문법 색상 강조</code></pre>
<hr>
<h3 id="01-프로세스-관리">01. 프로세스 관리</h3>
<p><strong>프로세스(Process)</strong> : 실행 중인 프로그램의 인스턴스 — 각각 고유한 PID(Process ID) 보유</p>
<br>

<p><strong>프로세스 상태</strong></p>
<table>
<thead>
<tr>
<th>상태</th>
<th>기호</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>Running</td>
<td><code>R</code></td>
<td>실행 중 또는 실행 대기</td>
</tr>
<tr>
<td>Sleeping</td>
<td><code>S</code></td>
<td>인터럽트 가능한 대기 상태</td>
</tr>
<tr>
<td>Disk Sleep</td>
<td><code>D</code></td>
<td>인터럽트 불가능한 대기 (I/O 대기)</td>
</tr>
<tr>
<td>Stopped</td>
<td><code>T</code></td>
<td>일시 중지 (Ctrl+Z)</td>
</tr>
<tr>
<td>Zombie</td>
<td><code>Z</code></td>
<td>종료됐지만 부모가 회수 안 함</td>
</tr>
</tbody></table>
<br>

<h3 id="ps">ps</h3>
<p>현재 실행 중인 프로세스 목록 출력</p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/5c13fcd3-5ae2-4891-8938-ffcd3d1fa7f5/image.png" alt=""></p>
<pre><code class="language-bash">USER       PID  %CPU %MEM    VSZ   RSS TTY   STAT START   TIME COMMAND
ubuntu    1234   0.0  0.1  12345  1024 pts/0 S    09:00   0:00 bash
│          │     │    │     │     │    │      │    │       │    └─ 명령어
│          │     │    │     │     │    │      │    │       └─── CPU 사용 시간
│          │     │    │     │     │    │      │    └─────────── 시작 시간
│          │     │    │     │     │    │      └──────────────── 프로세스 상태
│          │     │    │     │     │    └─────────────────────── 터미널
│          │     │    │     │     └──────────────────────────── 실제 메모리(KB)
│          │     │    │     └────────────────────────────────── 가상 메모리(KB)
│          │     │    └──────────────────────────────────────── 메모리 사용률
│          │     └───────────────────────────────────────────── CPU 사용률
│          └─────────────────────────────────────────────────── PID
└────────────────────────────────────────────────────────────── 사용자</code></pre>
<br>

<h3 id="kill--killall--pkill">kill / killall / pkill</h3>
<p>프로세스에 시그널 전송</p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/87b30c46-0308-4297-9bfd-28dbcb643b55/image.png" alt=""></p>
<br>

<p><strong>주요 시그널</strong></p>
<table>
<thead>
<tr>
<th>시그널</th>
<th>번호</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td><code>SIGTERM</code></td>
<td>15</td>
<td>정상 종료 요청 (기본값) — 프로세스가 정리 후 종료</td>
</tr>
<tr>
<td><code>SIGKILL</code></td>
<td>9</td>
<td>강제 종료 — 프로세스가 무시 불가</td>
</tr>
<tr>
<td><code>SIGHUP</code></td>
<td>1</td>
<td>재시작 / 설정 재로드</td>
</tr>
<tr>
<td><code>SIGINT</code></td>
<td>2</td>
<td>인터럽트 (Ctrl+C)</td>
</tr>
<tr>
<td><code>SIGSTOP</code></td>
<td>19</td>
<td>일시 중지 (Ctrl+Z)</td>
</tr>
<tr>
<td><code>SIGCONT</code></td>
<td>18</td>
<td>일시 중지된 프로세스 재개</td>
</tr>
</tbody></table>
<br>

<h3 id="jobs--bg--fg">jobs / bg / fg</h3>
<p>터미널 세션 내 작업(Job) 관리</p>
<ul>
<li>bg : 백그라운드</li>
<li>fg : 포그라운드</li>
</ul>
<br>

<h3 id="nohup">nohup</h3>
<p>SSH 접속 종료 후에도 프로세스가 계속 실행되도록 함</p>
<hr>
<h3 id="02-시스템-모니터링">02. 시스템 모니터링</h3>
<h3 id="top">top</h3>
<p>실시간 시스템 자원 현황 및 프로세스 목록 출력</p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/df0c7756-cf3b-462d-a5e3-ad80c7956829/image.png" alt=""></p>
<br>

<h3 id="free">free</h3>
<p>메모리 사용 현황 확인</p>
<pre><code class="language-bash">free -h

               total    used    free  shared  buff/cache  available
Mem:           1.9Gi   800Mi   500Mi    10Mi       687Mi      1.0Gi
Swap:          2.0Gi     0Bi   2.0Gi
│               │        │      │                  │           └─ 실제 사용 가능 메모리
│               │        │      │                  └─────────── 버퍼/캐시 (재사용 가능)
│               │        │      └────────────────────────────── 완전히 비어있는 메모리
│               │        └───────────────────────────────────── 실제 사용 중인 메모리
│               └────────────────────────────────────────────── 전체 메모리
└────────────────────────────────────────────────────────────── 물리 메모리 / 스왑</code></pre>
<br>

<h3 id="vmstat">vmstat</h3>
<p>CPU, 메모리, 스왑, I/O, 시스템 통계를 주기적으로 출력</p>
<br>

<h3 id="iostat">iostat</h3>
<p>디스크 I/O 통계 확인</p>
<hr>
<h3 id="03-서비스-관리">03. 서비스 관리</h3>
<ul>
<li><p><strong>systemd :</strong> Ubuntu 24.04의 기본 init 시스템 — 서비스(데몬) 시작/중지/자동 시작 관리</p>
</li>
<li><p><strong>systemctl :</strong> systemd를 제어하는 CLI 명령어</p>
<p>  <img src="https://velog.velcdn.com/images/its-jihyeon/post/7abfe403-0942-48bf-b114-8f366425ed87/image.png" alt=""></p>
</li>
</ul>
<ul>
<li><strong>journalctl</strong> : systemd가 수집하는 구조화된 로그를 조회하는 명령어</li>
</ul>
<br>

<p><strong>시스템 전체 명령</strong></p>
<table>
<thead>
<tr>
<th>명령</th>
<th>기능</th>
</tr>
</thead>
<tbody><tr>
<td><code>list-units --type=service</code></td>
<td>실행 중인 서비스 목록</td>
</tr>
<tr>
<td><code>list-units --type=service --all</code></td>
<td>전체 서비스 목록</td>
</tr>
<tr>
<td><code>daemon-reload</code></td>
<td>Unit 파일 변경 후 재로드</td>
</tr>
<tr>
<td><code>list-unit-files</code></td>
<td>Unit 파일 목록 및 자동 시작 상태</td>
</tr>
</tbody></table>
<hr>
<h3 id="04-로그-관리">04. 로그 관리</h3>
<p><strong>주요 로그 파일</strong></p>
<table>
<thead>
<tr>
<th>파일</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td><code>/var/log/syslog</code></td>
<td>시스템 전반 로그 (Ubuntu)</td>
</tr>
<tr>
<td><code>/var/log/auth.log</code></td>
<td>인증/SSH 로그인 로그</td>
</tr>
<tr>
<td><code>/var/log/kern.log</code></td>
<td>커널 로그</td>
</tr>
<tr>
<td><code>/var/log/dpkg.log</code></td>
<td>패키지 설치/제거 로그</td>
</tr>
<tr>
<td><code>/var/log/apt/</code></td>
<td>apt 명령 로그</td>
</tr>
<tr>
<td><code>/var/log/nginx/</code></td>
<td>Nginx 접근/에러 로그</td>
</tr>
<tr>
<td><code>/var/log/journal/</code></td>
<td>systemd 구조화 로그</td>
</tr>
</tbody></table>
<hr>
<h3 id="05-스케줄링">05. 스케줄링</h3>
<ul>
<li><strong>cron</strong> : 지정한 시간에 명령어나 스크립트를 자동 실행하는 작업 스케줄러</li>
<li><strong>crontab</strong> : 사용자별 cron 작업 목록 관리 명령어</li>
</ul>
<br>

<p><strong>cron 표현식</strong></p>
<pre><code class="language-bash">*  *  *  *  *  실행할 명령어
│  │  │  │  └─ 요일 (0=일요일, 1=월, ... 6=토, 7=일)
│  │  │  └──── 월 (1~12)
│  │  └─────── 일 (1~31)
│  └────────── 시 (0~23)
└───────────── 분 (0~59)</code></pre>
<h3 id="at">at</h3>
<p>지정한 시간에 <strong>한 번만</strong> 실행하는 일회성 스케줄러</p>
<hr>
<h3 id="06-환경변수--셸-설정">06. 환경변수 &amp; 셸 설정</h3>
<p><strong>환경변수(Environment Variable)</strong> : 프로세스가 참조하는 키-값 형태의 시스템 설정값</p>
<hr>
<h3 id="07-사용자--그룹-관리">07. 사용자 &amp; 그룹 관리</h3>
<p>/etc/passwd 구조</p>
<pre><code class="language-bash">ubuntu:x:1000:1000:Ubuntu User:/home/ubuntu:/bin/bash
│       │ │    │    │            │            └─ 기본 셸
│       │ │    │    │            └──────────── 홈 디렉터리
│       │ │    │    └───────────────────────── 사용자 설명
│       │ │    └────────────────────────────── GID
│       │ └─────────────────────────────────── UID
│       └───────────────────────────────────── 패스워드 (x = shadow 사용)
└───────────────────────────────────────────── 사용자명</code></pre>
<br>

<h3 id="useradd--adduser">useradd / adduser</h3>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/f5db6b0f-8bcf-4922-9dbc-ad1f19c4adf6/image.png" alt=""></p>
<br>

<h3 id="groupadd--groupmod--groupdel">groupadd / groupmod / groupdel</h3>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/1181e9c7-28f2-4371-a68a-16497a84f164/image.png" alt=""></p>
<br>

<h3 id="passwd">passwd</h3>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/998e8071-2b4a-46d0-b42e-6e16cda9f468/image.png" alt=""></p>
<hr>
<h3 id="08-패키지-관리">08. 패키지 관리</h3>
<h3 id="apt">apt</h3>
<table>
<thead>
<tr>
<th>명령어</th>
<th>기능</th>
</tr>
</thead>
<tbody><tr>
<td><code>apt update</code></td>
<td>패키지 목록 갱신 (실제 설치 아님)</td>
</tr>
<tr>
<td><code>apt upgrade</code></td>
<td>설치된 패키지 전체 업그레이드</td>
</tr>
<tr>
<td><code>apt install PACKAGE</code></td>
<td>패키지 설치</td>
</tr>
<tr>
<td><code>apt remove PACKAGE</code></td>
<td>패키지 삭제 (설정 파일 유지)</td>
</tr>
<tr>
<td><code>apt purge PACKAGE</code></td>
<td>패키지 + 설정 파일 완전 삭제</td>
</tr>
<tr>
<td><code>apt autoremove</code></td>
<td>불필요한 의존성 패키지 자동 삭제</td>
</tr>
<tr>
<td><code>apt search KEYWORD</code></td>
<td>패키지 검색</td>
</tr>
<tr>
<td><code>apt show PACKAGE</code></td>
<td>패키지 상세 정보</td>
</tr>
<tr>
<td><code>apt list --installed</code></td>
<td>설치된 패키지 목록</td>
</tr>
<tr>
<td><code>apt list --upgradable</code></td>
<td>업그레이드 가능한 패키지 목록</td>
</tr>
</tbody></table>
<br>

<h3 id="dpkg">dpkg</h3>
<p>저수준 패키지 관리</p>
<hr>
<h3 id="01-네트워크-기본">01. 네트워크 기본</h3>
<p>리눅스 서버의 <strong>네트워크 인터페이스, IP 주소, 라우팅, DNS</strong> 를 확인하고 관리하는 기본 명령어</p>
<br>

<h3 id="ip">ip</h3>
<p>네트워크 인터페이스, IP 주소, 라우팅 테이블 관리</p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/fb0a3a0b-136e-4aa9-a838-c0ae0db4ce2a/image.png" alt=""></p>
<br>

<h3 id="ifconfig">ifconfig</h3>
<p>네트워크 인터페이스 설정 확인</p>
<br>

<h3 id="ping">ping</h3>
<p>대상 호스트에 ICMP 패킷을 전송하여 연결 가능 여부 및 응답 시간 확인</p>
<br>

<h3 id="traceroute">traceroute</h3>
<p>목적지까지의 경로(홉)를 단계별로 추적</p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/3c005c25-c505-4ee7-b6fb-b460867d2f7f/image.png" alt=""></p>
<br>

<h3 id="nslookup--dig">nslookup / dig</h3>
<p>DNS 조회 — 도메인 → IP 변환 확인</p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/d819c281-85b3-4cf6-91fb-b14bc7ae453a/image.png" alt=""></p>
<hr>
<h3 id="02-포트--연결-확인">02. 포트 &amp; 연결 확인</h3>
<p><strong>포트 개수 : 65536</strong></p>
<br>

<h3 id="ss">ss</h3>
<p>소켓 상태 확인</p>
<br>

<p><strong>옵션</strong></p>
<table>
<thead>
<tr>
<th>옵션</th>
<th>기능</th>
</tr>
</thead>
<tbody><tr>
<td><code>-t</code></td>
<td>TCP 소켓</td>
</tr>
<tr>
<td><code>-u</code></td>
<td>UDP 소켓</td>
</tr>
<tr>
<td><code>-l</code></td>
<td>LISTEN 상태만</td>
</tr>
<tr>
<td><code>-n</code></td>
<td>서비스명 대신 숫자 포트 출력</td>
</tr>
<tr>
<td><code>-p</code></td>
<td>프로세스 정보 포함</td>
</tr>
<tr>
<td><code>-a</code></td>
<td>전체 소켓 (LISTEN + ESTABLISHED)</td>
</tr>
<tr>
<td><code>-4</code></td>
<td>IPv4만</td>
</tr>
<tr>
<td><code>-6</code></td>
<td>IPv6만</td>
</tr>
</tbody></table>
<br>

<h3 id="netstat">netstat</h3>
<p>네트워크 연결, 라우팅 테이블, 인터페이스 통계 확인 (레거시)</p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/9ec23022-a0d7-4965-8e26-e1b1822782a4/image.png" alt=""></p>
<br>

<h3 id="lsof">lsof</h3>
<p>열린 파일 목록 확인 (리눅스에서는 소켓도 파일이므로 네트워크 연결 확인에 활용)</p>
<hr>
<h3 id="03-http-통신">03. HTTP 통신</h3>
<ul>
<li><strong>curl</strong> : URL 기반 데이터 전송 도구 — REST API 테스트, 파일 다운로드, 헤더 확인에 활용</li>
<li><strong>wget</strong> : 파일 다운로드 전용 — 재시도, 재귀 다운로드 지원</li>
<li><strong>jq</strong> : JSON 파싱 및 가공 CLI 도구 — curl과 함께 API 응답 처리에 필수</li>
</ul>
<br>

<h3 id="curl">curl</h3>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/907c464f-2d33-4fb5-b3cf-d1134f667c5f/image.png" alt=""></p>
<hr>
<h3 id="04-ssh--원격-접속">04. SSH &amp; 원격 접속</h3>
<p><strong>SSH(Secure Shell)</strong> : 암호화된 채널로 원격 서버에 접속하여 명령어를 실행하는 프로토콜</p>
<br>

<h3 id="scp">scp</h3>
<p>SSH 기반 파일 복사</p>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/e5a55150-b079-40dd-b66b-dae7fa5976f3/image.png" alt="">
<img src="https://velog.velcdn.com/images/its-jihyeon/post/923ec7dd-3bd6-4dfc-96d3-0fdbf86f5552/image.png" alt=""></p>
<hr>
<h3 id="05-방화벽-관리">05. 방화벽 관리</h3>
<h3 id="ufw">ufw</h3>
<p>상태 확인
<img src="https://velog.velcdn.com/images/its-jihyeon/post/f54e2af5-11af-41d3-8f26-4fcf5dcae0ec/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[멋쟁이사자처럼부트캠프 클라우드 엔지니어링 5기 [7주차]]]></title>
            <link>https://velog.io/@its-jihyeon/%EB%A9%8B%EC%9F%81%EC%9D%B4%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A7%81-5%EA%B8%B0-7%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@its-jihyeon/%EB%A9%8B%EC%9F%81%EC%9D%B4%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A7%81-5%EA%B8%B0-7%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Sun, 15 Mar 2026 08:59:16 GMT</pubDate>
            <description><![CDATA[<h3 id="spring-ai">Spring AI</h3>
<ul>
<li>Spring 팀이 직접 만든 <strong>Java 개발자를 위한 AI 표준 프레임워크</strong></li>
<li>간단한 설정만으로 LLM 기능 연동 가능</li>
</ul>
<br>

<p><strong>전체 비교표</strong></p>
<table>
<thead>
<tr>
<th>항목</th>
<th>Spring AI</th>
<th>LangChain (Python)</th>
<th>생성형 모델 API 직접 호출</th>
</tr>
</thead>
<tbody><tr>
<td>개발 환경</td>
<td>Java/Spring</td>
<td>Python</td>
<td>Java(Spring)</td>
</tr>
<tr>
<td>구조</td>
<td>단일 앱 내부</td>
<td>Java ↔ Python 이원화</td>
<td>각 모델 API 직접 호출</td>
</tr>
<tr>
<td>코드 복잡도</td>
<td>낮음</td>
<td>높음</td>
<td>중간</td>
</tr>
<tr>
<td>모델 전환</td>
<td>매우 쉬움</td>
<td>어려움</td>
<td>어렵고 재작성 필요</td>
</tr>
<tr>
<td>유지보수</td>
<td>용이</td>
<td>어려움</td>
<td>중간</td>
</tr>
<tr>
<td>배포</td>
<td>Spring 인프라 그대로</td>
<td>Python 서버 추가</td>
<td>Spring 단독</td>
</tr>
</tbody></table>
<br>

<p><strong>장점</strong></p>
<ul>
<li>유지보수 감소 : 모델이 바뀌어도 코드 구조 유지</li>
<li>코드 구조 일관성 : Service, Controller, Config 패턴 그대로 적용</li>
<li>보안·정책·로그 관리 용이 : API Key를 Spring Config에서 안전하게 관리</li>
<li>서비스 성능 개선 : Spring 내부에서 직접 LLM 요청 처리</li>
</ul>
<hr>
<h3 id="네트워크-network">네트워크 Network</h3>
<ul>
<li>그물 Net + 작업 Work  = 긴밀히 연결</li>
<li>컴퓨터, 서버, 라우터, 스위치등의 장치로 연결 및 데이터 통신 가능 연결 구조</li>
</ul>
<br>

<p><strong>유/무선 구성</strong></p>
<ul>
<li>무선 = 무선 랜 카드</li>
<li>유선 = 유선 랜 카드(이더넷 랜 카드), 랜 케이블</li>
</ul>
<br>

<p><strong>리소스</strong></p>
<ul>
<li><strong>하드웨어 자원</strong> → 프린터, 스캐너 등 장치를 여러 PC가 함께 사용</li>
<li><strong>저장 자원</strong> → 파일 서버, NAS 등 저장공간을 여러 사용자가 공유</li>
<li><strong>인터넷 회선</strong> → 하나의 인터넷 회선을 여러 기기가 공유 (공유기)</li>
<li><strong>소프트웨어/데이터</strong> → 데이터베이스, 애플리케이션 서버 등</li>
</ul>
<hr>
<h3 id="프로토콜-protocol"><strong>프로토콜 Protocol</strong></h3>
<p><strong>데이터 통신을 위한 규약</strong></p>
<blockquote>
<p>기본 요소 
구문(Syntax) - 데이터 형식, 부호화, 신호크기 
의미(Semantic) - 전송제어, 오류수정
타이밍(Timing) - 신호의 지속시간, 순서</p>
</blockquote>
<br>

<table>
<thead>
<tr>
<th>프로토콜</th>
<th>포트</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>FTP</td>
<td>20 (데이터 전송) / 21 (제어)</td>
<td>파일 전송 프로토콜 (File Transfer Protocol)</td>
</tr>
<tr>
<td>Telnet</td>
<td>23</td>
<td>원격지 컴퓨터 시스템에 로그인</td>
</tr>
<tr>
<td>HTTP</td>
<td>80</td>
<td>하이퍼 텍스트 전송 프로토콜 (Hyper Text Transfer Protocol), 인터넷에서 하이퍼 텍스트 문서 교환</td>
</tr>
<tr>
<td>SMTP</td>
<td>25</td>
<td>전자 우편 <strong>송신</strong> 프로토콜 (Simple Mail Transfer Protocol), TCP/IP 호스트의 우편함에 ASCII 문자 메시지 전송</td>
</tr>
<tr>
<td>POP3</td>
<td>110</td>
<td>전자 우편 <strong>수신</strong> 프로토콜 (Post Office Protocol)</td>
</tr>
<tr>
<td>DHCP</td>
<td>67 (BOOTP 서버) / 68 (BOOTP 클라이언트)</td>
<td>Dynamic Host Configuration Protocol, 클라이언트에 동적 IP 주소 할당</td>
</tr>
<tr>
<td>ICMP</td>
<td>포트 없음 (IP 프로토콜 번호 1)</td>
<td>운영체제에서 오류 메시지 전달</td>
</tr>
<tr>
<td>NetBIOS</td>
<td>139</td>
<td>애플리케이션이 근거리 통신망을 통해 통신 가능</td>
</tr>
</tbody></table>
<br>

<h3 id="osi-7-layers"><strong>OSI 7 Layers</strong></h3>
<p><strong>응용(Application)</strong> : 어플리케이션 프로세스 정의 및 서비스 수행</p>
<p><strong>표현(Presentation)</strong> : 표현 방식이 다른 어플리케이션 또는 시스템 간의 통신이 가능하도록 하나의 통일된 구문 형식으로 변환</p>
<p><strong>세션(Session)</strong> : ****TCP/IP 세션을 생성하여 처리</p>
<p><strong>전송(Transport)</strong> : 데이터가 정상적으로 전송 되는지 확인 (TCP/UDP)</p>
<p><strong>네트워크(Network)</strong> : 논리적인 주소를 정의 (라우터)</p>
<p><strong>데이터 링크(Data Link)</strong> : 전기 신호 → 데이터 형태 (스위치)</p>
<ul>
<li>MAC 주소 : 통신을 위해 네트워크 인터페이스에 할당된 고유 식별자 (기기마다 가지고 있는 고유값)</li>
<li>IP 주소 : 논리적 주소로 네트워크 주소 + 호스트 주소로 구성</li>
</ul>
<p><strong>물리(Physical)</strong> : 물리적 연결과 관련된 정보 (허브, 리피터)</p>
<hr>
<h3 id="3계층--네트워크-계층">3계층 : 네트워크 계층</h3>
<p><strong>클래스를 통한 네트워크 주소 구분</strong></p>
<ul>
<li>A 클래스 : 1.0.0.0 ~ 126.0.0.0 [(2^24) - 2개의 호스트 주소]</li>
<li>B 클래스 : 128.0.0.0 ~ 191.255.255.255 [(2^16) - 2개의 호스트 주소]</li>
<li>C 클래스 : 192.0.0.0 ~ 223.255.255.255 [(2^8) - 2개의 호스트 주소]</li>
</ul>
<br>

<ul>
<li>클래스, 네트워크, 호스트 값은 무엇인가?<ul>
<li><strong>10.3.4.3 →</strong> A클래스 / 네트워크 주소(8bit) : 10.0.0.0 / 호스트 주소(24bit): 3.4.3</li>
<li><strong>203.10.1.1 →</strong> C클래스 / 네트워크 주소(24bit) : 203.10.1.0 / 호스트 주소(8bit): 1</li>
<li><strong>192.12.100.2 →</strong> C클래스 / 네트워크 주소(24bit) : 192.12.100.0 / 호스트 주소(8bit): 2</li>
<li><strong>261.12.4.1 →</strong> 존재하지 않음</li>
</ul>
</li>
</ul>
<br>

<p><strong>서브넷 Subnet</strong></p>
<p>IP 주소를 작은 단위로 쪼개서 분할</p>
<ul>
<li>10.10.10.10/8 → 네트워크 ID : 10 / 호스트 ID : 10.10.10</li>
<li>172.16.1.10/16 → 네트워크 ID : 172.16 / 호스트 ID : 1.10</li>
<li>192.168.100.10/24 → 네트워크 ID : 192.168.100 / 호스트 ID : 10</li>
</ul>
<br>

<p><strong>서브넷팅 Subnetting</strong></p>
<p>효율적인 IP 사용을 위한 분할</p>
<ul>
<li>192.168.10.0/24 → 100개의 IP 사용 <strong>(192.168.10.0/25)</strong></li>
</ul>
<pre><code>서브넷 1

- 192.168.10.00000000 ~ 192.168.10.01111111
- 192.168.10.0 ~ 192.168.10.127
- 실제 호스트 : 192.168.10.1 ~ 192.168.10.126</code></pre><p>  서브넷 2</p>
<pre><code>- 192.168.10.10000000 ~ 192.168.10.11111111
- 192.169.10.128 ~ 192.168.10.255
- 실제 호스트 : 192.168.10.129 ~ 192.168.10.254</code></pre><br>

<ul>
<li><p>현재 사용하고 있는 IP 주소가 192.168.10.250을 사용하고 있다.
이때, 재할당할수있는 IP의 주소의 범위는? 192.168.10.129 ~ 192.168.10.254</p>
<br>

<p><strong>! 할당된 범위(영역)내에서 재할당 가능 !</strong></p>
</li>
</ul>
<br>

<ul>
<li>192.168.10.0/24 → 50개의 IP 사용 <strong>(192.168.10.0/26)</strong></li>
</ul>
<pre><code>서브넷1

- 192.168.10.00000000 ~ 192.168.10.00111111
- 192.168.10.0 ~ 192.168.10.63
- 실제 호스트 : 192.168.10.1 ~ 192.168.10.62</code></pre><p>  서브넷2</p>
<pre><code>- 192.168.10.01000000 ~ 192.168.10.01111111
- 192.168.10.64 ~ 192.168.10.127
- 실제 호스트 : 192.168.10.65 ~ 192.168.10.126</code></pre><p>  서브넷3</p>
<pre><code>- 192.168.10.10000000 ~ 192.168.10.10111111
- 192.168.10.128 ~ 192.168.10.191
- 실제 호스트 : 192.168.10.129 ~ 192.168.10.190</code></pre><p>  서브넷4</p>
<pre><code>- 192.168.10.11000000 ~ 192.168.10.11111111
- 192.168.10.192 ~ 192.168.10.255
- 실제 호스트 : 192.168.10.193 ~ 192.168.10.254</code></pre><br>

<ul>
<li>현재 사용하고 있는 IP 주소가 192.168.10.172을 사용하고 있다.
이때, 재할당할수있는 IP의 주소가 아닌것은? 192.168.10.129 ~ 192.168.10.190</li>
</ul>
<br>

<p><strong>NAT (Network Address Translation)</strong></p>
<p><strong>사설 IP ↔ 공인 IP 주소를 변환</strong>하는 기술</p>
<pre><code class="language-java">IPv4 주소는 약 43억 개로 한정
→ 전 세계 모든 기기에 공인 IP 할당 불가
→ 사설 IP 대역을 내부에서 사용하고
   NAT로 공인 IP와 변환하는 방식 등장</code></pre>
<br>

<p>공유기, 방화벽 등에서 동작</p>
<pre><code class="language-java">내부 → 외부 요청 시
192.168.0.2 ──&gt; 공유기 ──&gt; 123.456.789.10 으로 변환 ──&gt; 인터넷

외부 → 내부 응답 시
인터넷 ──&gt; 123.456.789.10 ──&gt; 공유기 ──&gt; 192.168.0.2 로 변환</code></pre>
<hr>
<h3 id="4계층--전송-계층">4계층 : 전송 계층</h3>
<p><strong>로드밸런서</strong></p>
<p>여러 서버에 트래픽을 분산 시켜 주는 장치</p>
<br>

<p><strong>역할</strong></p>
<table>
<thead>
<tr>
<th>역할</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td><strong>트래픽 분산</strong></td>
<td>요청을 여러 서버에 나눠서 전달</td>
</tr>
<tr>
<td><strong>고가용성</strong></td>
<td>서버 1대 죽어도 나머지 서버로 자동 우회</td>
</tr>
<tr>
<td><strong>헬스체크</strong></td>
<td>주기적으로 서버 상태 확인 → 죽은 서버 제외</td>
</tr>
<tr>
<td><strong>SSL 종료</strong></td>
<td>HTTPS 복호화를 대신 처리 → 서버 부하 감소</td>
</tr>
</tbody></table>
<br>

<p><strong>종류</strong></p>
<table>
<thead>
<tr>
<th>종류</th>
<th>예시</th>
</tr>
</thead>
<tbody><tr>
<td><strong>L4 (Transport)</strong></td>
<td>TCP/UDP 레벨 분산 → AWS NLB</td>
</tr>
<tr>
<td><strong>L7 (Application)</strong></td>
<td>HTTP URL/헤더 기반 분산 → AWS ALB, Nginx</td>
</tr>
</tbody></table>
<br>

<p><strong>방화벽 (Firewall)</strong></p>
<p>네트워크 트래픽을 규칙에 따라 허용(Allow) / 차단(Deny) 하는 보안 장치</p>
<br>

<h3 id="pdu-protocol-data-unit"><strong>PDU Protocol Data Unit</strong></h3>
<p>네트워크의 계층에서 전달되는 데이터의 단위</p>
<table>
<thead>
<tr>
<th>계층</th>
<th>PDU 명칭</th>
</tr>
</thead>
<tbody><tr>
<td>응용 (Application)</td>
<td>메시지 (Message)</td>
</tr>
<tr>
<td>전송 (Transport)</td>
<td>세그먼트 (Segment) - TCP / 데이터그램 (Datagram) - UDP</td>
</tr>
<tr>
<td>네트워크 (Network / Internet)</td>
<td>패킷 (Packet)</td>
</tr>
<tr>
<td>네트워크 인터페이스 - 데이터 링크</td>
<td>프레임 (Frame)</td>
</tr>
<tr>
<td>네트워크 인터페이스 - 물리</td>
<td>비트 (Bit)</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[멋쟁이사자처럼부트캠프 클라우드 엔지니어링 5기 [6주차]]]></title>
            <link>https://velog.io/@its-jihyeon/%EB%A9%8B%EC%9F%81%EC%9D%B4%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A7%81-5%EA%B8%B0-6%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@its-jihyeon/%EB%A9%8B%EC%9F%81%EC%9D%B4%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A7%81-5%EA%B8%B0-6%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Sun, 08 Mar 2026 02:14:19 GMT</pubDate>
            <description><![CDATA[<h3 id="rest-representational-state-transfer">REST Representational State Transfer</h3>
<ul>
<li>자원의 이름(표현)으로 구분하여 정보를 주고받는 것을 정의하는 소프트웨어 개발 아키텍쳐의 스타일</li>
</ul>
<br>

<p><strong>구성</strong></p>
<ul>
<li><p>자원 - URL을 통해 명확하게 구분</p>
<ul>
<li><p>구조</p>
<table>
<thead>
<tr>
<th><strong>구성 요소</strong></th>
<th><strong>설명</strong></th>
<th><strong>예시</strong></th>
</tr>
</thead>
<tbody><tr>
<td><strong>Scheme</strong></td>
<td>사용할 프로토콜 (통신 규약)</td>
<td><code>https</code>, <code>ftp</code>, <code>mailto</code></td>
</tr>
<tr>
<td><strong>Authority</strong></td>
<td>호스트 이름(도메인) 및 포트 번호</td>
<td><code>www.example.com:80</code></td>
</tr>
<tr>
<td><strong>Path</strong></td>
<td>자원의 상세 경로</td>
<td><code>/images/logo.png</code></td>
</tr>
<tr>
<td><strong>Query</strong></td>
<td>서버에 전달하는 추가 파라미터</td>
<td><code>?id=123&amp;page=2</code></td>
</tr>
<tr>
<td><strong>Fragment</strong></td>
<td>자원 내부의 특정 지점 (북마크)</td>
<td><code>#section1</code></td>
</tr>
</tbody></table>
</li>
</ul>
</li>
<li><p>행위 - Http Method 방식을 이용</p>
</li>
<li><p>표현 - 클라이언트가 요청했던 자원을 서버는 응답 (보통 JSON 형태)</p>
</li>
</ul>
<br>

<p><strong>특징</strong></p>
<ul>
<li>클라이언트 - 서버 구조</li>
<li>무상태성</li>
<li>캐시 처리</li>
<li>인터페이스 일관성</li>
<li>계층 구조</li>
<li>자체 표현</li>
</ul>
<br>

<p><strong>참고 (반드시 지켜야 하는 규칙은 아니다 - 하나의 스타일)</strong></p>
<ul>
<li>동사보다는 <strong>명사</strong>, 대문자보다는 <strong>소문자</strong> 사용을 권장</li>
<li>마지막 주소는 포함X</li>
<li><strong>_ → - 사용</strong></li>
<li>파일확장자는 포함X</li>
<li>행위 포함 명명X</li>
</ul>
<hr>
<h3 id="스프링-시큐리티-spring-security">스프링 시큐리티 Spring Security</h3>
<p>스프링 MVC 기반의 애플리케이션의 인증과 인가 기능을 지원하는 보안 프레임워크</p>
<ul>
<li><strong>인증 Authentication</strong> : 해당 사용자 본인 확인</li>
<li><strong>인가 Authorization</strong> : 인증 사용자의 접근 권한 및 가능 결정</li>
</ul>
<br>

<p><strong>특징</strong></p>
<ul>
<li>다양한 유형(폼, 토큰 등)의 인증 적용</li>
<li>역할에 따른 권한 적용</li>
<li>데이터 암호화 및 기본 보안 공격 차단</li>
<li><strong>Filter 기반</strong>의 흐름<ul>
<li>핵심 관심사(Core Concerns)와 공통 관심사(Cross-cutting Concerns)를 분리</li>
</ul>
</li>
</ul>
<hr>
<h3 id="jwt-json-web-token"><strong>JWT (JSON Web Token)</strong></h3>
<ul>
<li>웹표준으로 Json을 사용하여 Token에 <strong>정보 저장 및 활용하는 Web Token</strong></li>
</ul>
<br>

<p><strong>구조</strong></p>
<pre><code class="language-java">// Header 
Signature 해싱위한 알고리즘 정보

// Payload 
서버, 클라이언트의 사용 정보

// Signature
Token 유효성 검증을 위한 암호화 문자열
서버에서 유효한 Token인지 검증 가능</code></pre>
<blockquote>
<p>장점 : 인증 서버가 필요 없으므로 확장이 용이 (실제 실무에서는 어느 정도 서버를 마련함)</p>
</blockquote>
<blockquote>
<p>단점 : 외부에 정보 유출 가능, 클라이언트에 저장되므로 요청시에만 처리 가능</p>
</blockquote>
<br>

<p><strong>종류</strong></p>
<table>
<thead>
<tr>
<th><strong>구분</strong></th>
<th><strong>Access Token</strong></th>
<th><strong>Refresh Token</strong></th>
</tr>
</thead>
<tbody><tr>
<td><strong>역할</strong></td>
<td>자원 접근을 위한 &quot;출입증&quot;</td>
<td>새로운 Access Token을 발급받기 위한 &quot;재발급권&quot;</td>
</tr>
<tr>
<td><strong>유효 기간</strong></td>
<td>매우 짧음 (예: 30분 ~ 1시간)</td>
<td>상대적으로 김 (예: 2주 ~ 한 달)</td>
</tr>
<tr>
<td><strong>저장 위치</strong></td>
<td>클라이언트 메모리, 로컬 스토리지 등</td>
<td>HTTP-only 쿠키, 서버 DB 등 보안 구역</td>
</tr>
</tbody></table>
<br>

<p><strong>인증 프로세스 (실무 로직)</strong></p>
<ol>
<li><strong>로그인 완료:</strong> 서버는 두 토큰을 모두 생성하여 클라이언트에 전달</li>
<li><strong>API 요청:</strong> 클라이언트는 헤더에 토큰을 담아 요청</li>
<li><strong>만료 발생:</strong> Access Token이 만료되면 서버는 401 에러를 반환</li>
<li><strong>토큰 갱신:</strong> 클라이언트는 가지고 있던 <strong>Refresh Token</strong>을 서버의 별도 갱신 엔드포인트로 보냄</li>
<li><strong>재발급:</strong> 서버는 Refresh Token이 유효한지 확인 후, 새로운 <strong>Access Token</strong>을 발급</li>
<li><strong>반복:</strong> Refresh Token마저 만료되면 비로소 사용자는 다시 로그인을 해야 함</li>
</ol>
<hr>
<h3 id="쿠키-cookie"><strong>쿠키 Cookie</strong></h3>
<ul>
<li>웹브라우저, 서버간 연결 유지를 위한 식별데이터</li>
<li>응답과 함께 서버로 부터 웹브라우저에 전달</li>
</ul>
<br>

<h3 id="세션-session"><strong>세션 Session</strong></h3>
<ul>
<li>요청 시, 서버에서 세션객체 생성후 상태 데이터 저장</li>
</ul>
<hr>
<h3 id="rdbms"><strong>RDBMS</strong></h3>
<p>관계형 데이터베이스 관리 시스템</p>
<ul>
<li>관계형 데이터 모델을 기초로 두고 모든 데이터를 2차원의 열과 행(테이블)의 형태로 표현</li>
<li>ACID(Atomicity, Consistency, Isolation, Durability) 원칙</li>
</ul>
<br>

<blockquote>
<p>장점 : 데이터 정합성 (어떤 데이터들이 값이 서로 일치하는 상태), 데이터 무결성 (데이터가 전송, 저장되고 처리되는 모든 과정에서 변경되거나 손상되지 않고 완전성, 정확성, 일관성을 유지)</p>
</blockquote>
<blockquote>
<p>단점 : 테이블 간 관계 때문에 시스템이 커질 경우 JOIN문이 많은 복잡한 쿼리 발생</p>
</blockquote>
<br>

<h3 id="nosql"><strong>NoSQL</strong></h3>
<p><strong>Key-Value DB</strong></p>
<ul>
<li>Key-Value 방식으로 데이터를 저장</li>
<li>Key값은 모든 데이터 타입을 수용할 수 있고, 중복되지 않는 값</li>
</ul>
<br>

<p><strong>Document DB</strong></p>
<ul>
<li>비정형 대량 데이터를 저장</li>
<li>Key-Value에서 확장된 방식으로, → Key-Document 형태로 저장</li>
</ul>
<br>

<blockquote>
<p>장점 : 스키마가 없어 유연하고 자유로운 데이터 구조</p>
</blockquote>
<blockquote>
<p>단점 : 데이터의 중복 발생 가능 (데이터를 변경하려면 모든 컬렉션에서 update)</p>
</blockquote>
<hr>
<h3 id="redis">Redis</h3>
<ul>
<li>고성능 Key-Value 구조의 저장소 (여러가지 데이터 타입들이 저장되고 관리됨)</li>
<li>비정형 데이터를 저장, 관리하기 위한 오픈 소스 기반의 NoSQL</li>
<li>데이터의 유실 가능성이 있음</li>
</ul>
<br>

<p><strong>Redis Key</strong></p>
<ul>
<li>데이터를 식별하기 위한 고유한 이름</li>
<li>문자열(String) 형태로 저장</li>
<li>최대 길이: 512MB (권장 X, 일반적으로 짧게 사용)</li>
<li>binary-safe</li>
</ul>
<br>

<p><strong>Strings</strong></p>
<pre><code class="language-java"># set
set testkey testvalue  # 키의 값 설정
get testkey            # 키의 값 반환

# del
del testkey </code></pre>
<br>

<p><strong>Lists</strong></p>
<pre><code class="language-java"># lpush, rpush
rpush testlist x  # 리스트 뒤(오른쪽)에 요소 추가
rpush testlist y

# rpop
rpush testlist2 x y z
rpop testlist2    # 뒤 요소 제거 후 반환</code></pre>
<br>

<p><strong>Sets</strong></p>
<pre><code class="language-java"># sadd
sadd testset 3 1 2   # 멤버 추가 (순서 보장X)

# scard
scard testset        # 멤버 수 반환

# sismember
sismember testset 2  # 멤버 존재 여부 확인</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[멋쟁이사자처럼부트캠프 클라우드 엔지니어링 5기 [5주차]]]></title>
            <link>https://velog.io/@its-jihyeon/%EB%A9%8B%EC%9F%81%EC%9D%B4%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A7%81-5%EA%B8%B0-5%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@its-jihyeon/%EB%A9%8B%EC%9F%81%EC%9D%B4%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A7%81-5%EA%B8%B0-5%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Sun, 01 Mar 2026 08:52:49 GMT</pubDate>
            <description><![CDATA[<p>5주차에서는 JavaScript 문법과 Spring Boot의 MVC, JPA 등에 대해 배웠다.</p>
<hr>
<h3 id="javascript">Javascript</h3>
<p>웹페이지의 <strong>복잡한 기능을 구현</strong>하는 스크립팅 프로그래밍 언어</p>
<br>

<p><strong>기능</strong></p>
<ul>
<li>동적인 기능 수행 가능</li>
<li>비동기로 서버와의 기능 수행 가능<ul>
<li>동기 : 요청 → 결과 진행 (순서를 유지)</li>
<li>비동기 : 요청과 결과 진행 (순서를 유지하지 않음)</li>
</ul>
</li>
</ul>
<br>

<h3 id="basic"><strong>basic</strong></h3>
<p><strong>결과 출력 Output</strong></p>
<pre><code class="language-jsx">// alert : 알림
alert(&#39;알림&#39;);

// prompt : 입력창
prompt(&#39;입력&#39;);

// document.write : 브라우저 출력
document.write(&#39;브라우저&#39;);

// console.log : 브라우저 콘솔
console.log(100);</code></pre>
<br>

<p><strong>변수 Variable</strong></p>
<pre><code class="language-jsx">// 키워드

var : 일반 변수 선언

// 재 할당 불가능
let : 일반 변수 선언
let y;
y = 20;
let y = 30; (X)

// 재 할당 불가능
const : 상수 변수 선언</code></pre>
<br>

<p><strong>연산 Operation</strong></p>
<pre><code class="language-jsx">let v1 = 10;
let v2 = 3;
let v3 = &#39;10&#39;;

// 10의 3승
console.log(v1 ** v2);

console.log(v1 == v3);
// 값이랑 타입이 같은지 비교
console.log(v1 === v3); </code></pre>
<br>

<h3 id="function"><strong>function</strong></h3>
<p><strong>함수 Function</strong></p>
<pre><code class="language-jsx">// 함수 선언
function 함수명(파라미터) {  
        // 코드
}

// 함수 표현
키워드 변수 = function(파라미터) {
        // 코드
}

// 화살표 함수
키워드 변수 =&gt; (파라미터) =&gt; { 코드 };

// 콜백 함수
다른 함수의 파라미터로 전달 -&gt; 함수 내부에서 호출</code></pre>
<hr>
<h3 id="object"><strong>object</strong></h3>
<p><strong>객체 Object</strong></p>
<pre><code class="language-jsx">// 생성
const 변수명 = {property:value, ...};
const 변수명 = new Object();
const 변수 = new 생성자함수();

function 생성자함수() {
    // 코드

    // 필드와 메서드(객체 내부의 함수)
}</code></pre>
<br>

<p><strong>Call By</strong></p>
<ul>
<li>객체는 참조에 의해 저장 및 복사</li>
</ul>
<br>

<p><strong>JSON</strong></p>
<ul>
<li>자바스크립트 객체 표기법으로 텍스트 기반의 데이터 교환 표준</li>
<li>“” 쌍따옴표의 구조</li>
</ul>
<pre><code class="language-jsx">// 문법 
{&quot;property&quot;:&quot;value&quot;}

// value 사용가능
문자열
숫자
객체 (JSON)
배열
불리언
null

// 객체 -&gt; 문자열
JSON.stringify(arg)

// 문자열(JSON 형식이여야 함) -&gt; 객체
JSON.parse(arg)
</code></pre>
<br>

<p><strong>try ~ catch</strong></p>
<ul>
<li>에러 종류</li>
</ul>
<pre><code class="language-html">SyntaxError (구문 오류): 괄호 닫기 누락, 잘못된 예약어 사용 등 문법이 틀린 경우
ReferenceError (참조 오류): 선언되지 않은 변수를 참조하거나 잘못된 변수명을 사용한 경우
TypeError (타입 오류): 숫자형에 함수를 호출하거나, 존재하지 않는 속성에 접근하는 등 데이터 타입이 맞지 않을 때 발생
RangeError (범위 오류): 숫자가 허용된 범위를 벗어났을 때 발생
URIError (URI 오류): encodeURI() 등 URI 처리 함수 오류
InternalError (내부 오류): 자바스크립트 엔진 내부 오류</code></pre>
<br> 

<h3 id="object_model"><strong>object_model</strong></h3>
<p><strong>문서 객체 모델 DOM</strong></p>
<p><strong>DOM(Document Object Model)</strong> </p>
<ul>
<li>웹페이지(HTML, XML)의 컨텐츠, 구조, 스타일 요소를 구조화하여 프로그래밍언어가 컨트롤 할 수 있도록 api를 제공하는 <strong>객체모델</strong></li>
<li>계층적 구조로 표현, 트리 자료구조이며 프로퍼티, 메서드, 이벤트 사용가능</li>
</ul>
<br>

<p><strong>DOM 요소</strong></p>
<blockquote>
<ul>
<li>프로퍼티(property) : HTML 요소 속성</li>
</ul>
</blockquote>
<ul>
<li>메서드(method) : HTML 요소 기능</li>
<li>컬렉션(collection) : 일종의 배열</li>
<li>이벤트 리스너(event listener) : 이벤트(onclick, onchange 등)제공</li>
<li>스타일(style) : HTML 요소의 CSS 스타일 접근</li>
</ul>
<br>

<p>기능</p>
<pre><code class="language-jsx">document.querySelector(selectors)
document.querySelectorAll(selectors)
document.getElementById(id)
document.getElementByTagName(name)
document.createElement(name)
node.append(node)
node.remove(node)</code></pre>
<br>

<p><strong>브라우저 객체 모델 BOM</strong></p>
<ul>
<li>웹브라우저의 창, 프레임 등을 프로그래밍적으로 제어 할 수 있도록 api를 제공하는 객체모델</li>
</ul>
<br>

<h3 id="api"><strong>api</strong></h3>
<p><strong>비동기 Asynchronous</strong></p>
<ul>
<li>자바스크립트에서 <strong>여러 작업을 동시에 처리</strong>하기 위한 기술</li>
</ul>
<br>

<p><strong>Ajax</strong></p>
<ul>
<li>JavaScript와 XML(→ JSON)을 이용한 비동기적 데이터 요청/응답 기술</li>
</ul>
<br>

<p>HTTP 메서드</p>
<pre><code class="language-jsx">GET : 요청

POST : 추가(삽입)

PUT : 수정

DELETE : 삭제</code></pre>
<hr>
<h3 id="스프링-부트-spring-boot">스프링 부트 Spring Boot</h3>
<ul>
<li>기존 스프링 설정을 단순화시킨 프레임워크</li>
</ul>
<br>

<p><strong>Maven</strong></p>
<ul>
<li>자바 프로젝트 관리 도구</li>
<li>프로젝트 빌드, 패키지, 배포 등의 역할, 라이브러리 관리</li>
</ul>
<br>

<p><strong>제어의 역전 IoC; Inversion of Control</strong></p>
<ul>
<li><strong>스프링 컨테이너(</strong>Spring Container)가 객체의 생명주기를 관리</li>
<li>컨테이너를 통해 관리하는 객체를 <strong>빈</strong>(Bean)이라고 함</li>
</ul>
<p><strong>! 개발자가 관리 하는 게 아니고 스프링 컨테이너가 관리 !</strong></p>
<br>

<p><strong>의존성 주입 DI; Dependency Injection</strong></p>
<ul>
<li>사용 객체를 주입하여 사용 하는 방식</li>
</ul>
<br>

<p><strong>어노테이션</strong></p>
<pre><code class="language-jsx">@Component
개발자 제어 가능 class를 Bean으로 등록

@Bean
개발자 제어 불가능 외부 라이브러리 Bean 설정 (클래스에 선언 불가능, @Configuration 내부 메소드에 사용)

@ComponentScan
컴포넌트, 빈을 Context에 등록

@Autowired 
Type에 따라 Bean을 주입

@Qualifier
원하는 Bean 주입</code></pre>
<br>

<p><strong>종류</strong> </p>
<blockquote>
<ul>
<li>*생성자</li>
</ul>
</blockquote>
<ul>
<li>필드</li>
<li>setter</li>
</ul>
<br>

<table>
<thead>
<tr>
<th><strong>구분</strong></th>
<th><strong>생성자 주입</strong></th>
<th><strong>필드 주입</strong></th>
<th><strong>수정자 주입</strong></th>
</tr>
</thead>
<tbody><tr>
<td><strong>불변성 보장</strong></td>
<td><strong>O (<code>final</code> 가능)</strong></td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td><strong>테스트 용이성</strong></td>
<td><strong>매우 높음</strong></td>
<td>매우 낮음</td>
<td>보통</td>
</tr>
<tr>
<td><strong>스프링 권장</strong></td>
<td><strong>최우선 권장</strong></td>
<td>지양 (비권장)</td>
<td>선택적 사용</td>
</tr>
<tr>
<td><strong>순환 참조 감지</strong></td>
<td>컴파일/실행 시점</td>
<td>런타임 시점</td>
<td>런타임 시점</td>
</tr>
</tbody></table>
<hr>
<h3 id="spring-mvc">Spring MVC</h3>
<ul>
<li>Model, View, Controller MVC 기능별 계층 분리 패턴</li>
</ul>
<br>

<p><strong>Server :</strong> 애플리케이션 내부에 서버를 포함 (자체적으로 가지고 있음)</p>
<p><strong>Front Controller Pattern</strong> : controller 앞 단에서 처리 (스프링부트에 기본적으로 들어가 있음)</p>
<p><strong>MVC Flow</strong> :</p>
<pre><code class="language-java">// DispatcherServlet
Front Controller 역할 수행 
Request -&gt; Controller 요청

// HandlerMapping
요청 처리 컨트롤러 탐색

// HandlerAdapter
매핑 컨트롤러 실행 요청

// Controller
DispatcherServlet으로 부터 전달된 요청 처리 후, 결과 Model에 저장
HandlerAdapter -&gt; ModelAndView 객체 변경
View Name, View 정보 포함

// ModelAndView
Controller가 반환한 Model, View Wrapping 객체

// View Resolver
View Name 확인
결과 출력할 View 탐색

// View
로직 처리 결과 최종 화면 생성 후 응답</code></pre>
<br>

<p><strong>Controller 어노테이션</strong></p>
<pre><code class="language-java">// @Controller 
controller 역할의 bean 등록

// @RestController
controller 역할의 bean 등록

// @RequestMapping 
해당 메소드의 url 매핑 및 method 등 여러가지 설정 값 지정

// @*Mapping    
메소드별 Mapping 어노테이션

// @RequestParam    
server로 전송되는 parameter 획득

// @PathVariable    
url path를 통해 전달되는 parameter 획득

// @ModelAttribute
http 파라미터 또는 multipart/form-data 형태의 파라미터를 객체로 매핑 획득
</code></pre>
<hr>
<h3 id="view-jsp"><strong>View (JSP)</strong></h3>
<p>HTML 문서에 <strong>Java 코드를 사용하여 동적 웹페이지 생성 기술</strong> (자바의 문법을 사용 가능)</p>
<br>

<p><strong>활용 문법</strong></p>
<p>표현 언어 EL : JSP 화면 값 표현 특화 문법</p>
<pre><code class="language-java">// 문법 
${속성명}

// 활용
${객체.속성명}
${객체[&quot;속성명&quot;]}
// cf. DTO의 경우 getter 호출

// 연산
${num = 10}
${num1 산술 num2}   : +, -, *, /, %
${num1 비교 num2}   : &gt;(gt), &gt;=(ge), &lt;(lt), &lt;=(le), ==(eq), !=(ne)
${조건1 조건 조건2}  : &amp;&amp;(and), ||(or), not
${empty 변수명}     : null, 빈문자열, 빈배열, 빈컬렉션   </code></pre>
<br>

<h3 id="view-thymeleaf"><strong>View (Thymeleaf)</strong></h3>
<p>JSP와 같은 템플릿 엔진</p>
<br>

<p><strong>특징</strong></p>
<blockquote>
<ul>
<li>서버 사이드 렌더링</li>
</ul>
</blockquote>
<ul>
<li>순수 HTML 최대한 유지</li>
<li>스프링 통합 지원</li>
</ul>
<h3 id="spring-data-jpa">Spring Data JPA</h3>
<p>JPA를 쉽고 편하게 사용하여 개발 가능하도록 한 Spring의 모듈 프로젝트 중 하나</p>
<ul>
<li><p>ORM Object Relational Mapping : Java 객체와 DB를 매핑 시겨주는 기술</p>
</li>
<li><p>JPA Java(Jakarta) Persistent API : Java의 ORM API 표준 인터페이스</p>
</li>
</ul>
<br>

<p><strong>개념</strong></p>
<ul>
<li>엔터티 : DB 테이블과 매핑되는 Java의 객체 (어노테이션(<strong>@Entity)</strong>을 이용하여 지정)</li>
<li>엔터티매니저 (EM) : 엔터티를 관리, 객체 CRUD의 역할 수행 (어노테이션<strong>(@PersistenceContext)</strong>을 이용하여 지정)<ul>
<li>EntityManagerFactory (EMF)가 필요 - 엔터티매니저를 생성 및 관리</li>
</ul>
</li>
<li>영속성 컨텍스트 : 엔티티를 비휘발성으로 저장하고 관리하는 가상의 영역<ul>
<li>특징 : 1차 캐시, 쓰기 지연, 변경 감지, 지연 로딩</li>
</ul>
</li>
</ul>
<br>

<p><strong>JPA의 Entity간 Mapping  전략 활용 방법</strong></p>
<p>관계 어노테이션</p>
<pre><code class="language-java">@OneToOne : 일대일(1:1)
@OneToMany : 일대다(1:N)
@ManyToOne : 다대일(N:1)
ManyToMany : 다대다(N:M)</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[멋쟁이사자처럼부트캠프 클라우드 엔지니어링 5기 [4주차]]]></title>
            <link>https://velog.io/@its-jihyeon/%EB%A9%8B%EC%9F%81%EC%9D%B4%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A7%81-5%EA%B8%B0-4%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@its-jihyeon/%EB%A9%8B%EC%9F%81%EC%9D%B4%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A7%81-5%EA%B8%B0-4%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Sun, 15 Feb 2026 08:54:46 GMT</pubDate>
            <description><![CDATA[<p>4주차에서는 JDBC와 HTML/CSS 기초에 대해 배웠다.</p>
<hr>
<h3 id="jdbcjava-database-connectivity">JDBC(Java DataBase Connectivity)</h3>
<p><strong>자바 애플리케이션 ←→ DB 연결</strong>을 위한 라이브러리</p>
<ul>
<li><strong>JDBC API</strong>: 자바에서 DB 연결을 위해 사용하는 클래스와 인터페이스의 집합</li>
<li><strong>JDBC Driver</strong>: 각 DB 제조사(Oracle, MySQL 등)에서 제공하는 JDBC API 구현체</li>
<li><strong>Driver Manager</strong>: 여러 종류의 드라이버를 관리하고 적절한 연결을 생성하는 역할</li>
</ul>
<p><strong>JDBC 흐름</strong></p>
<ul>
<li><strong>드라이버 로드</strong>: <code>Class.forName()</code>을 통해 특정 DB 드라이버를 메모리에 적재</li>
<li><strong>연결 생성</strong>: <code>DriverManager</code>를 통해 DB와의 통결(Connection) 확보</li>
<li><strong>구문 생성</strong>: SQL문을 실행하기 위한 객체 생성</li>
<li><strong>SQL 실행</strong>: 생성된 객체를 통해 쿼리(Query) 전송</li>
<li><strong>결과 처리</strong>: SELECT문의 경우 결과값(<code>ResultSet</code>)을 받아 처리</li>
<li><strong>자원 해제</strong>: 사용한 객체들을 역순으로 닫기(<code>close</code>)</li>
</ul>
<br>

<h3 id="핵심-인터페이스객체-및-주요-메소드"><strong>핵심 인터페이스/객체 및 주요 메소드</strong></h3>
<p><strong>DriverManager (클래스)</strong></p>
<ul>
<li>JDBC 드라이버를 관리하고 DB 연결을 시도하는 관리자</li>
</ul>
<p><strong>Connection (인터페이스)</strong></p>
<ul>
<li>특정 데이터베이스와의 연결 세션. SQL 실행을 위한 통로 역할</li>
</ul>
<p><strong>Statement &amp; PreparedStatement (인터페이스)</strong></p>
<ul>
<li><p>SQL 문장을 DB로 전달하여 실행하는 도구</p>
<p>  <strong>SQL Injection</strong></p>
<ul>
<li><p>예시</p>
<blockquote>
<p>기존 취약 코드: <code>stmt.executeQuery(&quot;SELECT * FROM dept WHERE deptno = &quot; + deptno);</code></p>
</blockquote>
<ul>
<li>공격자의 입력: <code>50 OR 1=1</code></li>
<li>실행되는 최종 쿼리: <code>SELECT * FROM dept WHERE deptno = 50 OR 1=1</code></li>
<li>결과: <code>1=1</code>이 항상 참이므로 부서 번호와 상관없이 모든 부서 정보가 노출됨</li>
</ul>
</li>
</ul>
</li>
</ul>
<pre><code>- 방어 기법 (Best Practices)
    - PreparedStatement 활용
    - 특수 문자 필터링
    - 최소 권한 원칙
    - 에러 메시지 은닉</code></pre><ul>
<li>주요 메소드<ul>
<li><code>executeQuery(sql)</code>: SELECT문을 실행할 때 사용 (결과로 <code>ResultSet</code> 반환)</li>
<li><code>executeUpdate(sql)</code>: INSERT, UPDATE, DELETE 등 데이터 변경 시 사용 (영향받은 행의 개수 반환)</li>
</ul>
</li>
</ul>
<p><strong>ResultSet (인터페이스)</strong></p>
<ul>
<li>SELECT 쿼리의 실행 결과를 담고 있는 가상의 테이블(커서)</li>
</ul>
<br>

<h3 id="계층별-역할-정리">계층별 역할 정리</h3>
<p><strong>Util 계층 (company.util) : 공통 유틸리티 계층</strong></p>
<ul>
<li>DB 연결 및 자원 해제 담당</li>
</ul>
<br>

<p><strong>View 계층 (company.view) : 출력 전용 계층</strong></p>
<ul>
<li><p>사용자의 입출력 담당</p>
</li>
<li><p>DB, Service 로직 미포함</p>
<p>주요 역할</p>
<ul>
<li>조회 결과 출력</li>
<li>성공 / 실패 메시지 출력</li>
</ul>
</li>
</ul>
<br>

<p><strong>Controller 계층 (company.controller) : 요청 흐름 제어 담당</strong></p>
<ul>
<li><p>사용자 요청을 받아 Service 계층에 위임</p>
</li>
<li><p>View를 통해 결과 출력</p>
<p>주요 책임</p>
<ul>
<li>메뉴 선택 처리</li>
<li>Service 호출</li>
<li>예외 흐름 제어</li>
</ul>
</li>
</ul>
<br>

<p><strong>Service 계층 (company.service) : 비즈니스 로직 담당 계층</strong></p>
<ul>
<li><p>Controller와 DAO 사이의 중간 계층</p>
</li>
<li><p>트랜잭션 단위 로직 담당</p>
<p>주요 책임</p>
<ul>
<li>비즈니스 규칙 처리</li>
<li>여러 DAO 조합 가능</li>
<li>Controller가 SQL을 몰라도 되도록 추상화</li>
</ul>
<p>예시 역할</p>
<ul>
<li>부서 등록 전 중복 체크</li>
<li>삭제 전 존재 여부 확인</li>
</ul>
</li>
</ul>
<hr>
<h3 id="htmlhypertext-markup-language">HTML(Hypertext Markup Language)</h3>
<p>웹페이지의 구조를 정의하는 마크업(Markup)언어</p>
<br>

<p><strong>종류</strong></p>
<ul>
<li>텍스트 Text</li>
<li>이미지 Image , 링크 Link</li>
<li>레이아웃 Layout<ul>
<li>블록 요소 : 웹페이지 body 전체 너비만큼 차지</li>
<li>인라인 요소 : 요소 컨텐츠의 너비만큼 차지</li>
</ul>
</li>
<li>리스트 List, 테이블 Table<ul>
<li>리스트 : 항목을 나열할 때 사용</li>
<li>테이블의 형태로 항목을 표현</li>
</ul>
</li>
<li>폼 Form<ul>
<li>유저의 입력 값을 관리하고, 서버로 데이터를 전달</li>
<li>폼 요소 Element : 라벨, 셀렉트, 버튼, 텍스트, 인풋</li>
</ul>
</li>
</ul>
<br>


<h3 id="csscascading-style-sheet">CSS(<strong>C</strong>ascading <strong>S</strong>tyle <strong>S</strong>heet)</h3>
<p>웹페이지의 스타일과 레이아웃을 위해 사용하는 언어</p>
<ul>
<li>Cascading <strong>:</strong> 일정 순위의 규칙이 적용</li>
<li>Style Sheet <strong>:</strong>.css 파일로 적용</li>
</ul>
<br>

<p><strong>구조</strong></p>
<p><strong>선택자(Selector)</strong>로 HTML 요소를 선택</p>
<pre><code class="language-css">선택자 { 프로퍼티명:값; 프로퍼티명:값; }</code></pre>
<br>

<p><strong>CSS적용</strong></p>
<ul>
<li>인라인 스타일</li>
</ul>
<pre><code class="language-css">&lt;태그 style=&quot;속성명:값&quot;&gt;</code></pre>
<ul>
<li>내부 스타일 시트</li>
</ul>
<pre><code class="language-css">&lt;head&gt;
        &lt;style&gt;
            ...
        &lt;/style&gt;
&lt;/head&gt;</code></pre>
<ul>
<li>외부 스타일 시트</li>
</ul>
<pre><code class="language-css">&lt;head&gt;
        &lt;link rel=&quot;stylesheet&quot; href=&quot;style.css&quot;&gt;
&lt;/head&gt;</code></pre>
<br>

<h3 id="basic">basic</h3>
<p><strong>선택자 Selector</strong></p>
<ul>
<li>기본 선택자 : 전체 선택자, 태그 선택자 등</li>
<li>복합 선택자 : 2개 이상의 선택자의 조합으로 요소 선택</li>
<li>속성 선택자 : 요소의 속성명 또는 속성 값을 통해 요소 선택</li>
<li>가상클래스 선택자 : 실제로 존재하지 않으나 임의로 선택자를 지정</li>
<li>가상요소 선택자 : 실제로 존재하지 않으나 임의로 선택자를 지정</li>
</ul>
<br>

<p><strong>리스트 List, 테이블 Table</strong> </p>
<ul>
<li>리스트</li>
</ul>
<pre><code class="language-css">/* list */
/* -style-type : 마커 */
/* -style-image : 이미지 사용 마커 */</code></pre>
<ul>
<li>테이블<pre><code class="language-css">/* table */
/* -collapse : 경계선 */
/* border : 두께 형식 색 */</code></pre>
</li>
</ul>
<br>

<h3 id="box_model"><strong>box_model</strong></h3>
<p><strong>패딩 Padding</strong></p>
<ul>
<li>컨텐츠와 테두리 사이의 공간</li>
</ul>
<pre><code class="language-css">/* padding */
/* -top, -right, -bottom, -left : 상우하좌 */</code></pre>
<p><strong>마진 Margin</strong></p>
<ul>
<li>테두리 밖 요소간 간격</li>
</ul>
<p><strong>디스플레이 Display</strong></p>
<ul>
<li>웹문서에서 요소의 출력 방식</li>
</ul>
<p><strong>오버플로우 Overflow</strong></p>
<ul>
<li>컨텐츠가 요소를 벗어나는 경우 출력 방식</li>
</ul>
<pre><code class="language-css">/* overflow */
- visible : 기본 출력
- hidden  : 넘치는 부분 생략
- scroll  : 넘치는 부분 스크롤
- auto    : 넘치는 부분 스크롤(스크롤 옵션)</code></pre>
<br>

<h3 id="layout"><strong>layout</strong></h3>
<p><strong>플로트 Float</strong></p>
<ul>
<li>요소가 화면에 떠있는 느낌</li>
</ul>
<p><strong>포지션 Position</strong></p>
<ul>
<li>요소의 위치를 지정</li>
</ul>
<br>

<h3 id="feature"><strong>feature</strong></h3>
<p><strong>트랜지션 Transition</strong></p>
<ul>
<li>요소의 프로퍼티 값을 동적으로 변경할 수 있도록 효과 지정</li>
</ul>
<pre><code class="language-css">/* transition */
/* -property : 속성 */
/* -duration : 지속시간 */
/* -timing-function : 타이밍 함수(https://easings.net/ko 참고) */
/* -delay : 대기시 */</code></pre>
<p><strong>애니메이션 Animation</strong></p>
<ul>
<li>요소의 동작 및 변환을 세분화 하여 지정</li>
</ul>
<hr>
<h3 id="부트스트랩-bootstrap"><strong>부트스트랩 Bootstrap</strong></h3>
<ul>
<li>웹사이트를 쉽게 만들수 있도록 도와주는 오픈소스 프레임워크</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[멋쟁이사자처럼부트캠프 클라우드 엔지니어링 5기 [3주차]]]></title>
            <link>https://velog.io/@its-jihyeon/%EB%A9%8B%EC%9F%81%EC%9D%B4%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A7%81-5%EA%B8%B0-3%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@its-jihyeon/%EB%A9%8B%EC%9F%81%EC%9D%B4%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A7%81-5%EA%B8%B0-3%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Sun, 08 Feb 2026 03:09:34 GMT</pubDate>
            <description><![CDATA[<p>3주차에서는 Java DTO 객체 및 API 활용과 DB SQL 문법에 대해 배웠다.</p>
<hr>
<h3 id="열거형enum-enumeration">열거형(Enum; Enumeration)</h3>
<p>상수 데이터 집합 <strong>(하나의 객체이다)</strong></p>
<ul>
<li>name() : 객체 문자열 반환</li>
<li>ordinal() : 객체 순서 반환</li>
<li>valueOf(String name) : 문자열과 일치하는 객체 반환 (일치하는 문자열이 없으면 에러 발생)</li>
<li>values() : 모든 객체 배열로 반환</li>
</ul>
<br>

<p><strong>Enum 생성</strong></p>
<pre><code class="language-java">package step01;

public enum Color {
    // 상수형 객체
    RED,
    BLUE,
    GREEN
}</code></pre>
<h4 id="biginteger-bigdecimal"><strong>BigInteger, BigDecimal</strong></h4>
<p>정수형 또는 실수형의 범위를 지정하고 기능 수행</p>
<p>(long or int가 아닌 정확한 수치에 대한 계산)</p>
<br>

<h3 id="lombok">Lombok</h3>
<p>다양한 인스턴스를 만들기 위한 생성 도구</p>
<ul>
<li>코드 간결성, 가독성 향상, 유지보수 비용 절감</li>
<li>외부 라이브러리이기 때문에 별도 설치 필요</li>
<li>설치 옵션 : java -jar lombok.jar</li>
</ul>
<br>

<p><strong>주요 어노테이션 및 기능</strong></p>
<table>
<thead>
<tr>
<th>어노테이션</th>
<th>기능 설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong><code>@Getter</code> / <code>@Setter</code></strong></td>
<td>필드의 접근자(Getter)와 설정자(Setter) 메소드 자동 생성</td>
</tr>
<tr>
<td><strong><code>@ToString</code></strong></td>
<td>객체의 정보를 출력하는 <code>toString()</code> 메소드 자동 생성</td>
</tr>
<tr>
<td><strong><code>@EqualsAndHashCode</code></strong></td>
<td>객체 비교를 위한 <code>equals()</code>, <code>hashCode()</code> 메소드 자동 생성</td>
</tr>
<tr>
<td><strong><code>@Data</code></strong></td>
<td>위 항목들(<code>@Getter</code>, <code>@Setter</code>, <code>@ToString</code> 등)을 모두 포함하는 종합 세트</td>
</tr>
<tr>
<td><strong><code>@Builder</code></strong></td>
<td>복잡한 객체 생성을 유연하게 돕는 빌더 패턴 적용</td>
</tr>
</tbody></table>
<blockquote>
<p><strong>@EqualsAndHashCode :</strong>  모든 필드를 다 비교 
<strong>@EqualsAndHashCode(of =</strong> “비교하고 싶은 하나의 필드&quot;) : 하나의 필드만 비교 (of 사용)</p>
</blockquote>
<br>

<p><strong>생성자 관련 어노테이션</strong></p>
<p><strong>@NoArgsConstructor</strong>: 파라미터가 없는 기본 생성자 생성</p>
<p><strong>@AllArgsConstructor</strong>: 모든 필드를 파라미터로 받는 생성자 생성</p>
<p><strong>@RequiredArgsConstructor</strong> : final 필드나 @NonNull 필드만 파라미터로 받는 생성자 생성 (<strong>final 필드 : 필수 + 불변</strong>)</p>
<hr>
<h3 id="생성자-패턴">생성자 패턴</h3>
<ul>
<li><p><strong>정적 팩토리 메서드 패턴 (Static Factory Method)</strong> : static 메서드로 객체의 생성을 단순화 하는 패턴</p>
<p>  메서드를 이용한 객체 생성 (호출)</p>
<pre><code class="language-java">User user1 = User.*of*(&quot;java&quot;, &quot;ADMIN&quot;);
User user2 = User.*createAdmin*(&quot;db&quot;);</code></pre>
<ul>
<li>메서드의 이름만 봐도 의도 파악 가능</li>
<li>new 같은 메모리 사용X -&gt; 성능을 높일 수 있음</li>
</ul>
</li>
<li><p><strong>빌더 패턴</strong> : 복잡한 객체를 단계별로 생성할 수 있게 도와주는 패턴</p>
<pre><code class="language-java">public class Hamburger {
  private final String bun; // 필수
  private final int patty;  // 필수
  private final boolean cheese; // 선택
  private final boolean lettuce; // 선택
</code></pre>
</li>
</ul>
<p>}</p>
<p>// 사용 예시
Hamburger myBurger = new Hamburger.Builder(&quot;플레인번&quot;, 2)
                                            .cheese(true)
                                            .lettuce(false)
                                            .build();</p>
<pre><code>
---

### Maven
자바 프로젝트 빌드 자동화 및 의존성 관리 도구

핵심 기능
- 의존성 관리
- 빌드 자동화
- 구조 표준화

&lt;br&gt;

### Jsoup
HTML을 자바 객체처럼 파싱 해주는 라이브러리

- 크롤링 : 웹 페이지의 소스를 그대로 가져와서 그 안에서 **특정 데이터를 추출**해 내는 행위
    - 정적 데이터(Jsoup), 동적 데이터 수집 가능

&lt;br&gt;

### JSON(JavaScript Object Notation)
데이터를 저장하거나 전송할 때 사용하는 경량의 텍스트 기반 데이터 형식

- 키-값 형태로 저장
- 어떤 언어와도 통신 가능



**어노테이션**

| 어노테이션 | 역할 |
| --- | --- |
| **`@JsonProperty`** | JSON의 키 이름과 자바 필드 이름을 매핑 (이름이 다를 때 필수) |
| **`@JsonIgnore`** | 특정 필드를 JSON 결과에서 제외 (예: 비밀번호 등) |
| **`@JsonFormat`** | 날짜(`Date`, `LocalDateTime`)의 출력 형식을 지정 |
| **`@JsonInclude`** | 값이 `null`인 필드는 JSON에 포함하지 않도록 설정 |
| **`@JsonNaming`** | 클래스 전체에 적용 (예: 모든 필드를 Snake Case로 일괄 변환) |

---

### 관계형 데이터베이스(RDB, Relational Database System)

데이터를 테이블 형식으로 표현

- 열, 필드 : 항목의 속성
- 행, 레코드 : 각 데이터 항목 저장
- 스키마 : 데이터의 구조, 구성 및 관계를 정의하는 논리적인 형식

관계형 데이터 베이스를 관리하는 시스템을 사용 → DB 활용

&lt;br&gt;

**모델링**

**개념적 모델링 :** 요구사항을 토대로 필요한 데이터는 무엇인지 등을 추려내는 것

**논리적 모델링 :** 데이터베이스의 논리적인 구조를 설계하는 것

**물리적 모델링 :** 논리적 모델을 물리적인 데이터베이스 구조로 변환하는 것

&lt;br&gt;

**관계형 데이터베이스 특징**

- 데이터 값 → 정확, 완전, 일관 유지
- 정형화된 데이터 구조

&lt;br&gt;

**NoSQL 데이터베이스 (비관계형 데이터베이스)**

**테이블을 사용하지 않고** 다양한 데이터 모델을 사용해서 데이터들을 저장하고 관리

→ 주로 대량의 **분산 데이터 또는 유연한 데이터 모델이 필요**한 환경에서 사용

---

### DQL (Data Query Language)

- 데이터 검색(Read) 기능에 사용하는 SQL
- SELECT 키워드 사용

&lt;br&gt;

**구조**

```sql
SELECT fields(속성명), ...
FROM table(테이블명)
WHERE field condition other table field
GROUP BY field
HAVING field condition 
ORDER BY field</code></pre><ul>
<li>비구조적인 데이터 모델</li>
<li>분산 처리 환경</li>
<li>높은 가용성, 확장성</li>
</ul>
<hr>
<h3 id="조인join">조인(<strong>JOIN)</strong></h3>
<ul>
<li>두 개 이상의 테이블에서 필요 데이터 검색 기능에 사용하는 명령 구문</li>
<li>JOIN 키워드 사용</li>
</ul>
<p><strong>구조</strong></p>
<pre><code class="language-sql">SELECT column명(s)
FROM table1
INNER JOIN table2
ON table1.column명 = table2.column명;

// LEFT, RIGHT
SELECT column명(s)
FROM table1
LEFT JOIN table2
ON table1.column명 = table2.column;</code></pre>
<hr>
<h3 id="서브쿼리subquery">서브쿼리(<strong>SubQuery</strong>)</h3>
<ul>
<li>다른 쿼리 내부에 포함되어 있는 쿼리</li>
<li>서브 쿼리가 먼저 실행 → 메인 쿼리 실행</li>
<li>쿼리의 구조를 명확히 제시, 가독성 향상</li>
</ul>
<p><strong>구조</strong></p>
<pre><code class="language-sql">// 기본
메인 쿼리
        (
            서브쿼리
        )

// 종류
SELECT field, (SELECT ...) -- 스칼라 서브쿼리(Scalar Sub Query)
FROM (SELECT ...)         -- 인라인 뷰(Inline View)
WHERE field = (SELECT ...)  -- 일반 서브쿼리
</code></pre>
<hr>
<h3 id="정리">정리</h3>
<table>
<thead>
<tr>
<th><strong>상황</strong></th>
<th><strong>추천 방식</strong></th>
<th><strong>이유</strong></th>
</tr>
</thead>
<tbody><tr>
<td>여러 테이블의 컬럼을 동시에 출력할 때</td>
<td><strong>JOIN</strong></td>
<td>서브쿼리는 SELECT 절에 컬럼당 하나씩 써야 해서 비효율적</td>
</tr>
<tr>
<td>단순히 존재 여부만 체크할 때 (<code>EXISTS</code>)</td>
<td><strong>Subquery</strong></td>
<td>메인 테이블의 중복 방지 및 Early-exit(발견 즉시 종료) 가능</td>
</tr>
<tr>
<td>그룹화(GROUP BY) 후 그 결과와 비교할 때</td>
<td><strong>Subquery (Inline View)</strong></td>
<td>집계 데이터를 먼저 확정한 뒤 매칭하는 것이 논리적으로 명확함</td>
</tr>
<tr>
<td>데이터 양이 아주 많을 때</td>
<td><strong>JOIN</strong></td>
<td>옵티마이저가 인덱스를 활용한 조인 경로를 더 잘 찾음</td>
</tr>
</tbody></table>
<hr>
<h3 id="dml-data-manipulation-language">DML (Data Manipulation Language)</h3>
<ul>
<li>데이터 변형(삽입, 수정, 삭제) 기능에 사용하는 SQL</li>
<li>INSERT, UPDATE, DELETE 키워드 사용</li>
</ul>
<p><strong>문법</strong></p>
<pre><code class="language-sql">// INSERT
INSERT INTO table명 [(column1, column2, column3, ...)]
VALUES (value1, value2, value3, ...);

// UPDATE
UPDATE table명
SET column1 = value1, column2 = value2, ...
WHERE condition;

// DELETE
DELETE FROM table명 WHERE condition;</code></pre>
<hr>
<h3 id="ddldata-definition-language">DDL(Data Definition Language)</h3>
<ul>
<li>데이터 구조를 정의하는 기능에 사용하는 명령어</li>
<li>생성, 변경, 삭제 등의 기능 제공</li>
<li>CREATE, ALTER, DROP, … 키워드 사용</li>
</ul>
<p><strong>문법</strong></p>
<pre><code class="language-sql">// Database
CREATE DATABASE database명;
DROP DATABASE database명;

// CREATE
CREATE TABLE table명 (
    column1 datatype,
    column2 datatype,
    column3 datatype,
   ....
);

// DROP
DROP TABLE table명;

// ALTER
ALTER TABLE table명
ADD column명 datatype;

ALTER TABLE table명
DROP COLUMN column명;

ALTER TABLE table명
MODIFY COLUMN column명 datatype;</code></pre>
<p><strong>제약 조건(Constraints)</strong></p>
<ul>
<li>테이블의 데이터에 지정하는 규칙</li>
<li>데이터의 정확한 사용을 위함</li>
</ul>
<pre><code class="language-sql">// 기본 제약조건
NOT NULL - null 불가
UNIQUE - 고유한값 유지(중복 불가)
PRIMARY KEY - PK, 기본키, NOT NULL + UNIQUE, 행(레코드)를 구분
FOREIGN KEY - FK, 참조키, 다른 테이블의 컬럼(필드)를 참조
CHECK - 지정한 규칙의 데이터만 사용 가능
DEFAULT - 기본값 지정</code></pre>
<hr>
<h3 id="dcldata-control-language">DCL(Data Control Language)</h3>
<ul>
<li>DB 접근 및 객체의 사용 권한을 정의하는 명령어</li>
<li>GRANT(권한 부여), REVOKE(권한 제거)키워드 사용</li>
</ul>
<p><strong>문법</strong></p>
<pre><code class="language-sql">// 권한 옵션
ALL PRIVILEGES : 모든 권한
CREATE, DROP, ALTER : 테이블 DDL 권한
SELECT, INSERT, UPDATE, DELETE : 테이블 DQL, DML 권한
USAGE : 권한 없음(계정 생성시에만 사용)

// IP 주소
% : 모든 IP
127.0.0.1 : 자신
localhost : 자신

// USER
CREATE USER &#39;계정명&#39;@&#39;IP주소&#39; IDENTIFIED BY &#39;비밀번호&#39;;
DROP USER &#39;계정명&#39;@&#39;IP주소&#39;;

// GRANT
GRANT 권한 옵션 ON 데이터베이스명.테이블 TO &#39;계정명&#39;@&#39;IP주소&#39;; 

// REVOKE
REVOKE 권한 옵션 ON 데이터베이스명.테이블 FROM &#39;계정명&#39;@&#39;IP주소&#39;; 

// 적용
FLUSH PRIVILEGES;</code></pre>
<hr>
<h3 id="tcltransaction-control-language">TCL(Transaction Control Language)</h3>
<p><strong>Transaction</strong>  : 수행하는 작업을 논리적으로 묶은 단위</p>
<pre><code class="language-sql">// 예시
필요한 작업: MySQL -&gt; DB 10만원 송금
① MySQL 계좌에서 10만원 차감 (UPDATE)
② DB 계좌에 10만원 추가 (UPDATE)</code></pre>
<p><strong>ACID</strong></p>
<table>
<thead>
<tr>
<th><strong>특성</strong></th>
<th><strong>영문</strong></th>
<th><strong>설명</strong></th>
</tr>
</thead>
<tbody><tr>
<td><strong>원자성</strong></td>
<td>Atomicity</td>
<td>트랜잭션 내 모든 작업이 전부 성공하거나 전부 실패함. 중간 상태는 없음</td>
</tr>
<tr>
<td><strong>일관성</strong></td>
<td>Consistency</td>
<td>트랜잭션 실행 전후로 데이터베이스가 일관된 상태를 유지함</td>
</tr>
<tr>
<td><strong>격리성</strong></td>
<td>Isolation</td>
<td>동시에 실행되는 트랜잭션들이 서로 영향을 주지 않음</td>
</tr>
<tr>
<td><strong>지속성</strong></td>
<td>Durability</td>
<td>COMMIT된 트랜잭션은 시스템 장애가 발생해도 영구적으로 반영됨</td>
</tr>
</tbody></table>
<blockquote>
<p><strong>락(Lock) - 데이터 잠금</strong> 
여러 사용자가 동시에 같은 데이터에 접근할 때 <strong>데이터의 일관성을 보장하기 위해 접근을 제한</strong>하는 메커니즘</p>
</blockquote>
<br>

<h3 id="리버스-엔지니어링reverse-engineer">리버스 엔지니어링(Reverse Engineer)</h3>
<p>기존 MySQL 데이터베이스의 스키마, 테이블, 뷰, 관계(Foreign Key) 정보를 분석하여 시각적인 ERD(Entity Relationship Diagram) 모델로 자동 변환하는 기능</p>
<hr>
<h3 id="정규화-normalization">정규화 (Normalization)</h3>
<ul>
<li>테이블을 <strong>올바르게 설계</strong>하는 규칙</li>
<li>데이터 <strong>중복 최소화</strong></li>
</ul>
<p><strong>제1정규형 (1NF) - 원자값 (Atomic Value)</strong></p>
<ul>
<li><strong>한 컬럼에는 하나의 값만 저장</strong></li>
</ul>
<p><strong>제2정규형 (2NF) - 부분 종속 제거</strong></p>
<ul>
<li><strong>모든 일반 컬럼은 기본키 전체에 종속</strong></li>
</ul>
<p><strong>제3정규형 (3NF) - 이행 종속 제거</strong></p>
<ul>
<li><strong>일반 컬럼은 기본키에만 종속되어야 하고, 다른 일반 컬럼에 종속되면 안됨</strong></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[AWS] yum 락, SSH 접속 이슈]]></title>
            <link>https://velog.io/@its-jihyeon/AWS-yum-%EB%9D%BD-SSH-%EC%A0%91%EC%86%8D-%EC%9D%B4%EC%8A%88</link>
            <guid>https://velog.io/@its-jihyeon/AWS-yum-%EB%9D%BD-SSH-%EC%A0%91%EC%86%8D-%EC%9D%B4%EC%8A%88</guid>
            <pubDate>Sun, 01 Feb 2026 06:18:17 GMT</pubDate>
            <description><![CDATA[<h2 id="1-yum-프로세스-대기-현상-lock">1. yum 프로세스 대기 현상 (Lock)</h2>
<blockquote>
<p>AWS에서 인스턴스를 생성하고 nginx를 설치하던 중 뜬 에러</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/16520f89-58f8-470e-a3ee-dc61933bf122/image.png" alt=""></p>
<p>명령어 전체를 복붙 하고 엔터를 쳤고 제대로 안 돌아가 Ctrl+z를 눌러 다시 명령어 한 줄을 입력한 상태였다.</p>
<p><strong>원인</strong> : 작업 중 Ctrl+z를 눌러 프로세스가 종료되지 않고 백그라운드에서 정지된 채 락을 잡고 있었다.</p>
<p><strong>해결</strong> : sudo kill -9 2044 명령어로 정지된 프로세스를 강제 종료하여 해결</p>
<h3 id="ctrlc-vs-ctrlz">Ctrl+C vs Ctrl+Z</h3>
<p><strong>Ctrl+c</strong> : 실행 중인 프로그램을 <strong>즉시 종료</strong>
<strong>Ctrl+z</strong> : 프로그램을 종료하지 않고 <strong>잠시 멈춰서 백그라운드</strong>로 보냄</p>
<p>Ctrl+z 가 익숙해 계속 사용하다 보니 Ctrl+c 와의 차이점은 몰라 발생한 간단한 에러였다. </p>
<hr>
<h2 id="2-ssh-접속-실패-connection-timeout">2. SSH 접속 실패 (Connection Timeout)</h2>
<blockquote>
<p>보안 그룹에서 인스턴스에 접근하기 위해 인바운드 규칙에 SSH 통신을 추가하고 인스턴스에 연결한 상태에서 뜬 에러</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/its-jihyeon/post/0174d48d-7496-4071-89f7-5e5ef723935e/image.png" alt=""></p>
<p>규칙을 추가할 때 내 IP로 선택하여 연결했지만 계속 에러가 나는 상태였다.</p>
<p>원인 : 보안 그룹 규칙에 현재 IP가 허용되지 않는 상태</p>
<p>해결 : Anywhere-IPv4로 소스를 다시 설정하고 연결해서 사용 (0.0.0.0/0 으로 임시 개방)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[멋쟁이사자처럼부트캠프 클라우드 엔지니어링 5기 [2주차]]]></title>
            <link>https://velog.io/@its-jihyeon/%EB%A9%8B%EC%9F%81%EC%9D%B4%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A7%81-5%EA%B8%B0-2%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@its-jihyeon/%EB%A9%8B%EC%9F%81%EC%9D%B4%EC%82%AC%EC%9E%90%EC%B2%98%EB%9F%BC%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A7%81-5%EA%B8%B0-2%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Sat, 31 Jan 2026 13:15:31 GMT</pubDate>
            <description><![CDATA[<p>Java 객체 지향 프로그래밍과 Lambda, Stream 등을 배운 2주차 과정을 
간단하게 정리해보았다.</p>
<hr>
<h3 id="상속">상속</h3>
<p>부모 클래스를 자식 클래스가 물려 받는 행위
<strong>변수(필드)와 메서드</strong>를 상속 가능</p>
<p><strong>메서드 오버라이딩(Method Overriding)</strong> : 자식 클래스에서 부모의 메서드의 기능을 변경(재정의)</p>
<ul>
<li><strong>반환타입, 메서드명, 파라미터</strong>가 모두 동일</li>
</ul>
<h3 id="추상abstract-클래스">추상(Abstract) 클래스</h3>
<p>공통적인 속성과 기능을 추출하여 상위 클래스로 정의</p>
<ul>
<li>abstract 키워드 사용</li>
</ul>
<h3 id="인터페이스interface">인터페이스(Interface)</h3>
<p>추상 클래스와 함께 자바에서 추상화를 구현하는 역할</p>
<ul>
<li>interface, implements 키워드 사용</li>
<li>클래스가 아니므로 <strong>다중상속 가능</strong></li>
</ul>
<table>
<thead>
<tr>
<th align="center"><strong>구분</strong></th>
<th align="center"><strong>추상 클래스 (abstract class)</strong></th>
<th align="center"><strong>인터페이스 (interface)</strong></th>
</tr>
</thead>
<tbody><tr>
<td align="center"><strong>핵심 목적</strong></td>
<td align="center">상속을 통한 <strong>기능 확장</strong></td>
<td align="center">동일한 <strong>동작 보장</strong></td>
</tr>
<tr>
<td align="center"><strong>다중 상속</strong></td>
<td align="center">불가능 (단일 상속만 가능)</td>
<td align="center"><strong>가능</strong> (다중상속  가능)</td>
</tr>
<tr>
<td align="center"><strong>필드(변수)</strong></td>
<td align="center">일반 변수 가질 수 있음</td>
<td align="center">상수(<code>public static final</code>)만 가능</td>
</tr>
<tr>
<td align="center"><strong>관계 키워드</strong></td>
<td align="center"><code>extends</code></td>
<td align="center"><code>implements</code></td>
</tr>
</tbody></table>
<h3 id="예외-처리exception-handling">예외 처리(Exception Handling)</h3>
<p><strong>try ~ catch ~ finally</strong></p>
<ul>
<li>catch : 예외를 처리</li>
<li>finally : 예외 발생 상관없이 무조건 실행 해야함</li>
</ul>
<p>예외 발생시 밑에 코드 실행X (중간에 예외가 발생해도 처리되지 않음)</p>
<p><strong>throws, throw (다른 곳으로 예외를 전달)</strong></p>
<pre><code class="language-java">void throwing() throws Exception타입 {
        throw new Exception생성자();
    }</code></pre>
<hr>
<h3 id="mvc-패턴">MVC 패턴</h3>
<p>구조</p>
<ul>
<li>M (Model) : 핵심 로직</li>
<li>V (View) : 출력 로직</li>
<li>C (Controller) : View와 Model 연결 로직</li>
</ul>
<h3 id="기본api-클래스">기본API 클래스</h3>
<p>Object : toString(), hashCode(). equals()
System : getProperty() / getenv(), gc(), currentTimeMillis()
Class : getClass(), forName(String className)
String : charAt(), indexOf(), substring() ...
StringBuffer, StringBuilder : append(), delete(), insert()
Wrapper : Byte, Short, Integer, </p>
<hr>
<h3 id="제네릭generic">제네릭(Generic)</h3>
<p>일반적인 데이터 타입을 규칙으로 정의(일반화)
<T> (Type), <E> (Element), <K> (Key), <V> (Value) 등의 약어를 사용</p>
<pre><code class="language-java">// 제네릭 + ArrayList
ArrayList&lt;Integer&gt; arrayList2 = new ArrayList&lt;Integer&gt;();
arrayList2.add(2);
arrayList2.add(Integer.parseInt(&quot;3&quot;));</code></pre>
<h3 id="컬렉션-프레임워크collection-framework">컬렉션 프레임워크(Collection Framework)</h3>
<p>데이터를 효율적으로 관리하기 위한 인터페이스, 클래스를 제공</p>
<ul>
<li>List 인터페이스 : 저장 순서 유지 인터페이스(나열), 빈 공간 허용X <ul>
<li>ArrayList, LinkedList …</li>
</ul>
</li>
<li>Set 인터페이스 : 저장 순서 유지X 인터페이스, 중복 저장 불가능 <ul>
<li>HashSet, TreeSet</li>
</ul>
</li>
<li>Map 인터페이스 : 키와 값의 쌍으로 데이터를 관리(K:V), 중복 저장 불가능 <ul>
<li>HashMpa, LinkedHashMap, TreeMap (put(), get())</li>
</ul>
</li>
</ul>
<hr>
<h3 id="람다lambda-expression">람다(Lambda Expression)</h3>
<p>자바에서 함수형 프로그래밍 문법 (익명 함수)</p>
<pre><code class="language-java">// 기존
int sum(int num1, int num2) {
    return num1 + num2;
}

// 람다식 1
(int num1, int num2) -&gt; {
    num1 + num2
}

// 람다식 2
(num1, num2) -&gt; num1 + num2;</code></pre>
<h3 id="스트림stream">스트림(Stream)</h3>
<p>데이터를 연속적으로 전달하는 통로 </p>
<blockquote>
<ol>
<li>스트림 생성 - List객체.stream()</li>
<li>중간 연산 &emsp; - filter(), distinct(), sorted() ... </li>
<li>최종 연산 &emsp; - forEach(), sum(), collect() ...</li>
</ol>
</blockquote>
<pre><code class="language-java">// 스트림 생성
List&lt;String&gt; stringList1 = Arrays.asList(&quot;a&quot;, &quot;b&quot;, &quot;c&quot;, &quot;d&quot;, &quot;e&quot;, &quot;a&quot;);
stringList1.stream()

// 중간 연산
            .filter(str -&gt; str.equals(&quot;d&quot;)) // 조건에 맞는 데이터 출력 - d만 출력
            .map(str -&gt; str.toUpperCase())  // 지정 값을 특정 형태로 저장

// 최종 연산
            .forEach(str -&gt; System.out.println(str)); // 순회 (출력)</code></pre>
<h3 id="옵셔널">옵셔널</h3>
<p>Null 혹은 Nullable 한 값을 저장하는 Wrapper 클래스 <strong>(NPE를 방지)</strong></p>
<hr>
<h3 id="입출력io-inputoutput">입/출력(I/O; Input/Output)</h3>
<p>데이터를 읽고 쓰는 행위
기본 스트림의 방향은 단방향</p>
<p><strong>스트림 종류</strong></p>
<ul>
<li>바이트 기반 스트림 : 바이트 단위 (이미지, 동영상 데이터 처리시 사용)<ul>
<li>InputStream / OutputStream</li>
</ul>
</li>
<li>문자 기반 스트림 : 문자(char 2byte) 단위 (텍스트 데이터 처리시 사용)<ul>
<li>Reader / Writer</li>
</ul>
</li>
<li>보조 스트림 : 스트림의 기능을 높이기 위해 사용 **(버퍼를 활용, 보조이기 때문에 데이터를 흘려보낼 수 없다.)<ul>
<li>InputStream / OutputStream / Reader Writer</li>
</ul>
</li>
</ul>
<h3 id="표준-입출력">표준 입출력</h3>
<p>콘솔(Console)을 통한 데이터의 입출력</p>
<ul>
<li>System.in : 데이터를 입력 (Scanner 클래스 이용)</li>
<li>System.out : 데이터를 출력</li>
<li>System.err : 에러 데이터를 출력</li>
</ul>
<h3 id="직렬화serialize">직렬화(Serialize)</h3>
<p>자바 객체를 외부 데이터 or 파일로 전달할 때 / 받아올 때 바이트 스트림의 형태로 변환</p>
<ul>
<li>ObjectOuputStream / ObjectInputStream</li>
<li>Serializable 인터페이스 (직렬화를 사용 할 수 있도록 하는 기능)</li>
</ul>
<h3 id="file">File</h3>
<p>파일, 디렉터리에 생성, 접근 기능 수행</p>
<pre><code class="language-java">// 생성
new File(경로);

// 기본 기능
exists() : 존재 여부 확인
createNewFile() : 파일 생성
mkdir() : 디렉터리 생성
isFile()/isDirectory() : 파일/디렉터리인지 여부 확인
delete() : 파일 또는 디렉터리 삭제</code></pre>
]]></description>
        </item>
    </channel>
</rss>