본문으로 건너뛰기
에이전트 하네스와 QLoRA — 모델에서 클라우드까지

생성 수정

IBM * 레드햇 AX 과정 · 26강

에이전트 하네스와 QLoRA — 모델에서 클라우드까지

2026년 7월 20일(월) ~ 7월 24일(금)

이 강의 문제풀이 — 배운 내용을 주관식 문답으로 확인해보세요.풀어보기 →

7. 에이전트 = 모델 + 하네스 — 헤르메스

7.1 이 등식을 다시 꺼내는 이유

1주차 끝에 예고했고 3~4주차에 이름을 붙였던 등식입니다.

⭐ 에이전트 = 모델 + 하네스

  • 모델(Model) — 추론하는 능력 그 자체. 갈아끼울 수 있습니다.
  • 하네스(Harness) — 그 모델을 규칙 문서와 검증 장치로 감싼 틀. 도구 목록, 페르소나, 실행 권한, 기억 장치가 여기 들어갑니다.

카파시의 원칙으로 다시 쓰면 이렇습니다.

"LLM을 절대 날것(Raw)으로 세상과 직접 부딪히게 두지 말고, 외부에서 통제하는 규칙과 환경틀을 짜라."

이번 주에 이 등식을 직접 조립해 봅니다. 하네스는 헤르메스(Hermes)라는 CLI 에이전트가 제공하고, 모델은 내 컴퓨터의 Ollama를 끼워 넣습니다. 4.3절에서 만든 auth-gateway-harness.md가 문서로 된 하네스였다면, 7장은 실행되는 하네스입니다.

7.2 MCP와 A2A — 무엇을 잇는가

에이전트 시대의 프로토콜이 둘 나왔습니다. 이름이 비슷해서 섞이는데, 잇는 대상이 다릅니다.

MCP (Model Context Protocol)A2A (Agent2Agent)
잇는 것에이전트 ↔ 도구·데이터에이전트 ↔ 다른 에이전트
상대는수동적 (시키면 함)능동적 (스스로 판단하고 답함)
비유손에 쥔 연장다른 부서의 담당자
이 책에서9주차 (FastAPI + MCP + n8n)이번 주
  • ⭐ A2A (Agent2Agent) — [에이전트 통신 프로토콜]인데, [서로 다른 조직·플랫폼의 자율 에이전트가 서로를 발견하고 협상하며 작업을 위임한다]는 점에서 도구 호출 규약인 MCP와 구별된다.

실습 코드랩은 구매 컨시어지 에이전트가 원격 판매자 에이전트와 상호작용하는 구조입니다. Cloud Run에 컨시어지를 올리고, Agent Engine 쪽에 판매자 에이전트를 두어 둘이 대화하게 합니다.

🔗 A2A 코드랩: https://codelabs.developers.google.com/intro-a2a-purchasing-concierge

7.3 하이브리드 구성 — AI 없는 에이전트와 온프레미스 추론

수업에서 설계한 구조는 여기서 한 걸음 더 나갑니다.

   [AWS]  ── A2A ──▶  [온프레미스]
   AI 없는 에이전트      EXAONE 2.4B / Qwen (GPU 연산)
   · 오케스트레이션      · 실제 추론
   · MCP 로 도구 호출    · 사내 데이터 접근
   · 그래프DB 로 관계 관리

⭐ "AI 없는 에이전트"라는 표현이 핵심입니다. 에이전트가 반드시 모델을 품고 있을 필요가 없습니다. 판단을 다른 에이전트에게 위임하면 됩니다. 그러면 클라우드에는 흐름 제어만 두고, 실제 추론은 데이터가 있는 사내에서 합니다. 10주차 1절의 "왜 모델을 회사 안으로 들이는가"에 대한 아키텍처 차원의 답입니다. 데이터가 밖으로 나가지 않습니다.

이 구조에서 각 부품의 역할:

부품역할축으로 보면
A2A에이전트 간 대화 규약토폴로지 — 누가 누구와 이어지는가
MCP에이전트 → 도구 호출어댑터 — 바깥 세계에 붙는 구멍
그래프DB관계 저장온톨로지 — 무엇이 무엇과 어떤 뜻으로 이어지는가

9주차의 온톨로지·토폴로지 두 지도가 여기서 각각 자기 자리를 찾습니다.

7.4 헤르메스 설치 (WSL Ubuntu)

curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash

설치 스크립트 하나가 다음을 전부 처리합니다.

처리 항목내용
의존성Python, Node.js, ripgrep, ffmpeg
저장소클론
격리가상환경 생성 (1주차 5절의 그 격리)
전역 명령hermes 등록
프로바이더LLM 프로바이더 초기 설정

헤드리스 서버(GUI 없는 서버)에서 브라우저 자동화가 필요 없다면:

curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash -s -- --skip-browser

설치 위치는 실행 사용자에 따라 갈립니다.

설치 주체위치
일반 사용자~/.hermes/
root/usr/local/bin/hermes

설정 경로는 ~/.hermes — 점으로 시작하므로 숨김 디렉터리입니다.

ls -a ~            # 숨김 항목까지 보기
ls -la ~/.hermes   # 내용 확인

초기 설정과 실행:

hermes setup --portal      # Nous Portal OAuth 로그인 + 프로바이더 + Tool Gateway 를 한 번에
hermes model               # API 키를 직접 쓰고 싶을 때 — 대화형 선택

hermes                     # 대화형 실행
hermes --tui               # 최신 TUI
hermes chat -q "질문"       # 단발 질문
hermes --continue          # 최근 세션 이어가기
hermes doctor              # ⭐ 문제 진단

⚠️ ModuleNotFoundError: No module named 'dotenv' 가상환경 런처가 아니라 시스템 파이썬으로 실행되고 있다는 뜻입니다. PATH를 확인하세요. 1주차에서 "환경이 격리되어 있다"를 배운 그 지점이고, 증상은 항상 "있는 패키지를 없다고 한다" 입니다.

설치가 끝나면 VSCode 터미널에서 claude 대신 hermes 를 입력하는 것만으로 창이 헤르메스로 바뀝니다. 헤르메스 자신을 MCP 서버로 등록할 수도 있습니다. AI에게 이렇게 시킵니다.

MCP 서버로 등록되어 있는지 확인해줘
.mcp.json 파일에 헤르메스 서버 등록해줘

7.5 로컬 Ollama를 프로바이더로 — 플랜 없이 쓰기

질문: Nous Portal 플랜이 없으면 헤르메스를 못 쓰나요? 답: 쓸 수 있습니다. 플랜은 Nous 클라우드 추론과 Tool Gateway를 쓸 때만 필요합니다. 로컬 Ollama를 프로바이더로 지정하면 플랜 없이 무료입니다. 플랜 화면은 그냥 닫으면 됩니다.

축 1을 여기에 적용하면 명확합니다. 추론을 남에게 맡길 것인가, 내가 할 것인가. 내가 하면 돈을 안 냅니다.

설정 절차

# 1) 툴 콜링을 지원하는 모델 준비
ollama pull qwen3:14b

# 2) ⭐ 컨텍스트를 크게 잡고 서빙 (가장 중요)
OLLAMA_CONTEXT_LENGTH=64000 ollama serve
# 3) 프로바이더 지정
hermes model
#   → Custom endpoint (enter URL manually)  선택
#   → Base URL   : http://localhost:11434/v1
#   → API key    : 공란으로 Enter
#   → Model name : qwen3:14b

⚠️ 목록 중간의 Ollama Cloud는 다른 것입니다. 그것은 ollama.com이 호스팅하는 유료 서비스이지 내 머신의 Ollama가 아닙니다. 이름이 같아서 반드시 한 번은 잘못 고릅니다. 내 컴퓨터의 Ollama를 쓰려면 Custom endpoint 입니다.

# 확인 — 모델 목록 JSON 이 나오면 정상
curl http://localhost:11434/v1/models

설정 파일(~/.hermes/config.yaml)에 직접 적어도 됩니다.

model:
  default: qwen3:14b
  provider: custom
  base_url: http://localhost:11434/v1
  context_length: 64000

💡 /v1 이 붙는 이유: Ollama가 OpenAI 호환 API를 그 경로로 제공하기 때문입니다. 헤르메스는 OpenAI 규격으로 말을 걸고, Ollama가 그 규격을 알아듣습니다. 6주차 어댑터 패턴의 실물입니다 — 서로 다른 두 제품이 "OpenAI 규격"이라는 포트를 사이에 두고 붙습니다.

7.6 ⭐ 컨텍스트 길이 — 왜 4096으로는 안 되는가

에이전트가 일반 채팅보다 컨텍스트를 훨씬 많이 먹는 이유가 있습니다. 질문만 보내는 것이 아니기 때문입니다.

   [일반 채팅]  요청 = 대화 기록 + 질문

   [에이전트]   요청 = 시스템 프롬프트(하네스)
                    + 메모리(MEMORY.md)
                    + 스킬 정의
                    + ⭐ 툴 스키마 전체 (도구 수십 개의 이름·설명·인자 명세)
                    + 대화 기록
                    + 질문

Ollama의 기본 컨텍스트는 대부분 4096 토큰입니다. 툴 스키마만으로도 그걸 넘깁니다. 그래서 64K 이상이 필요합니다. 미만이면 두 가지 증상이 나타납니다.

  1. 작업 도중에 앞을 "잊어버립니다" (초반 지시가 창 밖으로 밀려남)
  2. 시작 시점에 모델이 아예 거부합니다

7.7 에러 대응 — 40,960 → 65,536

실제로 만나는 에러 메시지입니다.

Ollama runtime context is too small for Hermes tool use
  current   : 40,960 tokens
  recommended starting value : 65,536 tokens

원인: qwen3:14b 자체는 더 큰 컨텍스트를 지원합니다. 문제는 Ollama가 num_ctx 설정값만큼만 할당한다는 점입니다. 모델의 능력과 런타임의 할당이 다릅니다.

방법 1 (권장) — 헤르메스 설정

model:
  ollama_num_ctx: 65536
  context_length: 65536

방법 2 — Ollama Modelfile로 모델을 새로 굽기

FROM qwen3:14b
PARAMETER num_ctx 65536
ollama create qwen3-14b-64k -f Modelfile

⚠️ 40K → 64K로 올리면 KV 캐시 VRAM 사용량이 늘어납니다. 공짜가 아닙니다. ollama ps 로 GPU/CPU 분배를 확인하고, VRAM이 빠듯하면 캐시를 양자화합니다.

ollama ps                              # GPU/CPU 로 얼마나 나뉘어 올라갔는지
OLLAMA_KV_CACHE_TYPE=q8_0 ollama serve # KV 캐시를 8비트로 양자화

⚠️ 설정을 바꿨으면 반드시 모델을 재로드해야 합니다. ollama stop qwen3:14b 후 다시 실행. 이미 메모리에 올라간 모델은 옛 설정을 그대로 들고 있습니다. 3주차의 "환경변수를 고쳤으면 서버를 재시작한다"와 같은 부류의 실수입니다.

7.8 컨텍스트 설정을 시스템에 영구 반영하기

매번 환경변수를 앞에 붙이는 대신 서비스 설정에 넣습니다.

sudo systemctl edit ollama.service

열린 편집기에 다음을 넣습니다. 저장 위치는 /etc/systemd/system/ollama.service.d/override.conf 입니다.

[Service]
Environment="OLLAMA_CONTEXT_LENGTH=64000"
sudo systemctl daemon-reload && sudo systemctl restart ollama

⚠️ WSL에서는 systemd가 기본으로 돌지 않습니다. systemctl이 에러를 뱉으면 수동으로 합니다.

sudo pkill ollama
OLLAMA_CONTEXT_LENGTH=64000 ollama serve

⚠️ WSL은 PC를 꺼두면 스케줄 작업과 게이트웨이가 함께 멈춥니다. 이것이 이번 주 후반에 AWS EC2로 넘어가는 실무적 이유입니다. (9장) "내 컴퓨터 안의 서버"는 내 컴퓨터가 꺼지면 죽습니다. 축 4의 왼쪽에 있는 것의 한계입니다.

7.9 8GB에서는 어디까지 내려가야 하는가 — qwen3:4b

VRAM이 부족하면 모델을 낮춥니다. 실제로 내려간 절차입니다.

# VS Code TERMINAL (wsl)
sudo systemctl edit ollama.service       # → override.conf 저장
sudo systemctl daemon-reload && sudo systemctl restart ollama
ollama pull qwen3:4b
# 헤르메스 설정 네 줄 — 각 설정은 ~/.hermes/config.yaml 에 저장된다
hermes config set model.provider custom
hermes config set model.base_url http://localhost:11434/v1
hermes config set model.default qwen3:4b
hermes config set model.context_length 64000
hermes doctor      # 진단
hermes chat        # 기동

기동하면 이런 화면이 뜹니다.

Hermes Agent v0.19.0
Available Tools : browser, clarify, code_execution, computer_use,
                  cronjob, delegation, file, image_gen ...
Available Skills : ...

하단 상태바 읽는 법:

qwen3:4b | 17.6K/64K | 28% | 2m | 17s
   │          │         │     │    └ 마지막 응답 시간
   │          │         │     └ 세션 경과
   │          │         └ 컨텍스트 사용률
   │          └ 사용 토큰 / 전체 컨텍스트
   └ 현재 모델

동작 확인 결과

질문결과
대한민국 수도는 어디야⭕ 정상
이 프로젝트의 디렉토리 구조를 알려줘.⭕ fastapi/, nextjs/, flutter/ 모노레포 구조를 정확히 답함

4B로 내리면 간단한 답변은 되지만 복잡한 멀티스텝 작업은 어렵습니다. 트레이드오프입니다.

7.10 ⭐ 툴 콜링이 되는 모델인지 판별하는 법

이것이 이번 주 에이전트 실습의 가장 중요한 판별 기준입니다.

첫 질문으로 이렇게 묻습니다. 현재 디렉토리의 파일 목록을 보여줘

  • 실제로 명령을 실행하고 결과를 보여주면 → ⭕ 성공
  • "ls 명령을 쓰시면 됩니다"라고 말로만 설명하면 → ❌ 모델 교체 신호

말로 설명하는 모델은 툴 스키마를 무시하고 있는 것입니다. 순수 채팅용 모델의 전형적인 반응입니다.

계열툴 콜링비고
Qwen3⭕ 잘 동작이번 주 선택
Llama 3.x⭕ 잘 동작—
Mistral Small⭕ 잘 동작—
순수 채팅 모델❌말로만 설명함
EXAONE 2.4B급⚠️ 어려움크기가 부족

⭐ 8GB VRAM의 현실적 하한선은 양자화된 Qwen3 8B~14B입니다. 그 아래로 내려가면 툴 콜링의 안정성이 급격히 떨어집니다. 10주차에서 "sLLM을 회사 안으로"라고 배웠지만, 아무 작은 모델이나 에이전트가 되는 것은 아닙니다. 에이전트가 되려면 툴 스키마를 읽고 정확한 JSON으로 호출을 만들어 내는 능력이 필요하고, 이건 크기와 학습 방식에 달려 있습니다.

검증용 질문 네 개 — 순서대로 물으면 능력이 층별로 드러납니다.

#질문확인하는 능력
1현재 디렉토리의 파일 목록을 보여주고, 가장 큰 디렉토리 상위 5개를 알려줘툴 콜링 + 결과 가공
2~/.hermes/config.yaml 을 읽고 내 현재 설정을 요약해줘파일 읽기
3내 이름은 ...이고 ... 기억해둬 → /new 로 새 세션 → 내가 누구라고 했지?⭐ 장기 기억 (MEMORY.md 기반)
4멀티스텝 실전 과제계획 수립 + 연속 실행

3번이 특히 중요합니다. /new로 세션을 새로 열었는데도 기억한다면, 그 기억은 대화 기록이 아니라 디스크의 MEMORY.md에서 온 것입니다. 2주차의 축 1(디스크 ↔ 메모리)이 에이전트의 기억에도 그대로 적용됩니다.

hermes skills list      # Skills Hub 경고가 뜰 때

7.11 SOUL.md — 페르소나를 문서로 못 박기

하네스의 한 축은 "이 에이전트는 어떻게 말하고 어떻게 판단하는가" 를 문서로 고정하는 일입니다. 헤르메스는 이 역할을 SOUL.md가 맡습니다. CLAUDE.md와 같은 자리입니다.

# SOUL.md

## 말투
- 한국어로 대화한다.
- 동료처럼 대등하게 말한다. 과한 존대나 아부는 하지 않는다.
- 모르면 모른다고 말한다. 추측을 사실처럼 말하지 않는다.
- "물론이죠!", "훌륭한 질문입니다!" 같은 빈말로 시작하지 않는다.

## 작업 방식
- 작업 전에 계획을 한 줄로 요약한다.
- 위험한 명령(삭제·덮어쓰기·배포)은 실행 전에 확인을 받는다.
- 에러가 나면 바로 고치지 말고 원인 분석을 먼저 한다.
- 방법이 여러 개면 장단점을 비교해서 제시한다.

## 출력 형식
- 결론(핵심)을 먼저 쓴다.
- 코드 블록에는 주석을 반드시 단다.
- 서술형보다 표와 목록을 우선한다.

⭐ 이 문서의 각 줄이 하나씩 실패 사례에 대응합니다. "빈말 금지"는 토큰 낭비를, "위험 명령 확인"은 사고를, "에러 시 원인 분석 우선"은 증상만 덮는 수정을 막습니다. 하네스 문서는 좋은 말을 모아 두는 곳이 아니라, 겪은 사고를 규칙으로 바꿔 쌓는 곳입니다. 4.3절의 절대 규칙 여섯 개와 정확히 같은 성격의 문서입니다.


8. 8GB로 무엇을 학습시킬 수 있는가 — QLoRA

8.1 QLoRA는 무엇의 한 종류인가

세 단어가 포개져 있습니다. 안에서 밖으로 읽습니다.

  • PEFT (Parameter-Efficient Fine-Tuning) — [파인튜닝 방법]인데, [전체 파라미터가 아니라 일부만 학습한다]는 점에서 전체 미세조정(Full Fine-Tuning)과 구별된다.
  • LoRA (Low-Rank Adaptation) — [PEFT의 한 방식]인데, [원본 가중치는 얼려 두고 옆에 작은 행렬 두 개를 붙여 그것만 학습한다]는 점에서 다른 PEFT 방식과 구별된다.
  • ⭐ QLoRA (Quantized LoRA) — [LoRA]인데, [베이스 모델을 4비트로 양자화해 메모리에 올린 뒤 그 위에서 LoRA를 학습한다]는 점에서 일반 LoRA와 구별된다.
   Full Fine-Tuning : 전체 가중치를 다 학습  →  VRAM 수십~수백 GB
   LoRA             : 어댑터만 학습          →  VRAM 크게 감소
   QLoRA            : 4비트 + 어댑터만 학습  →  ⭐ 8GB 소비자용 GPU 에서도 가능

⭐ LoRA는 6주차의 어댑터 패턴입니다. 이름부터 Adaptation입니다. 원본(포트)을 건드리지 않고 옆에 작은 것(어댑터)을 붙여 동작을 바꿉니다. 그래서 12주차에서 "나중에 베이스 모델 이름만 3B로 교체하면 된다"고 말할 수 있었던 것입니다. 어댑터는 갈아끼우는 물건입니다.

8.2 RTX 3050 8GB에서 무엇을 돌릴 것인가

수업에서 다룬 여덟 개의 대표 과제와, 각각의 고전 모델입니다. 이 목록을 8GB 환경에서 QLoRA로 돌릴 수 있는 대체 모델로 바꾸는 것이 과제였습니다.

#과제고전 모델8GB 대응 방향
1이미지 분류 / 전이학습VGG경량 백본 + 마지막 층만 학습
2물체 감지SSDYOLO Nano 계열 (11주차에서 다룸)
3시멘틱 분할PSPNet경량 세그멘테이션 백본
4자세 추정OpenPose경량 키포인트 모델
5GAN 이미지 생성DCGAN, SAGAN해상도를 낮춰 학습
6GAN 이상 탐지AnoGAN, Efficient GAN배치 크기 축소
7감정 분석Transformer⭐ QLoRA가 가장 잘 맞는 자리
8동영상 분류3DCNN, ECO⭐ 프레임 샘플링 + 2D 백본 + 시계열

⭐ 8번이 이 목록에서 가장 무겁습니다. 3DCNN은 시간 축까지 합성곱을 돌리므로 메모리가 폭증합니다. 8GB에서의 현실적 대안은 "동영상을 프레임 몇 장으로 줄여 2D로 특징을 뽑고, 그 특징들의 시간 순서를 따로 처리" 하는 구조입니다. 문제를 그대로 푸는 대신 문제를 다시 쪼개는 것 — 이것이 자원 제약 아래의 설계입니다.

8.3 VRAM 산수를 손으로 해 보기

"돌아갈까?"를 감으로 판단하지 않는 법입니다.

필요 VRAM ≈ 모델 가중치 + KV 캐시 + 활성값(배치·시퀀스 길이에 비례) + 여유
항목무엇이 정하는가줄이는 방법
모델 가중치파라미터 수 × 비트 수⭐ 양자화 (4비트 → 1/4)
KV 캐시컨텍스트 길이 × 층 수컨텍스트 축소 / KV_CACHE_TYPE=q8_0
활성값배치 크기 × 시퀀스 길이배치 축소, 그래디언트 체크포인팅
여유—90% 이상 채우지 않기

7.7절에서 컨텍스트를 40K에서 64K로 올렸을 때 VRAM이 늘어난 이유가 이 표의 두 번째 줄입니다. 컨텍스트를 늘리는 것은 공짜가 아니라 KV 캐시를 사는 일입니다.


9. AWS — 내 컴퓨터를 남의 데이터센터에 세우기

9.1 왜 지금 AWS인가

7.8절 마지막 경고가 이 장의 출발점입니다. WSL은 PC를 꺼두면 함께 멈춥니다. 지금까지 만든 구조 — 터널, 인증 서버, 에이전트 — 는 전부 내 방의 컴퓨터가 켜져 있다는 전제 위에 있습니다.

원격 PC (11~12주차)EC2 (이번 주)
전원내가 켬AWS가 켬
IP사설, 재부팅 시 변동공인, 고정 가능
비용전기요금시간당 과금
확장물리적 한계클릭으로 Scale-up

7주차의 IaaS/PaaS/SaaS, 10주차의 EC2·RDS·S3를 개념으로 배웠습니다. 이번 주에 실제로 만듭니다.

9.2 설치 순서 — VPC → EC2 → RDS → S3

무엇을 먼저 만들어야 하는가. 순서가 있습니다.

  ① VPC   — 내 전용 네트워크(땅)를 먼저 만든다
  ② EC2   — 그 땅 위에 서버(건물)를 세운다
  ③ RDS   — 같은 땅 안에 DB를 놓고 EC2 에서만 접근하게 한다
  ④ S3    — 네트워크와 무관한 저장소. 마지막에 붙인다

이 순서가 나온 이유는 의존 방향입니다.

자원무엇에 의존하는가
VPC아무것에도 의존하지 않음
EC2VPC의 서브넷 안에 놓여야 함
RDSVPC + 서브넷 그룹 + 보안 그룹
S3VPC와 무관 (전역 서비스, URL로 접근)

⭐ 5주차의 의존 방향 원칙이 인프라에서 반복됩니다. 의존받는 쪽이 먼저 있어야 합니다. VPC 없이 EC2를 만들려 하면 AWS가 기본 VPC에 알아서 넣어 주는데, 그 순간 네트워크 설계권을 잃습니다. 그래서 직접 만들고 시작합니다.

9.3 IAM — 루트 계정으로 일하지 않는다

  • ⭐ IAM (Identity and Access Management) — [AWS의 접근 제어 서비스]인데, [루트 계정과 별도로 사용자·역할·정책을 만들어 최소 권한만 부여한다]는 점에서 계정 하나로 전부 하는 방식과 구별된다.

4.5절의 RBAC와 완전히 같은 사고방식입니다. 사용자에게 권한을 직접 붙이지 않고 정책(Policy)에 묶습니다.

절차:

  1. IAM 계정 생성 → 사용자 생성 → 권한 부여
  2. 액세스 방식은 CLI 선택 (콘솔 로그인용이 아니라 프로그래밍 접근용)
  3. 인증 관리자 앱(Authenticator)으로 MFA 등록 — 책에 나오는 하드웨어 키 대신 휴대폰 앱을 씁니다

⚠️ 루트 계정은 만들고 나서 잠급니다. MFA를 걸고 평소에는 쓰지 않습니다. 루트는 결제 정보 변경과 계정 폐쇄까지 할 수 있는 유일한 계정이라, 유출 시 복구가 불가능합니다. 3.5절에서 개인키를 auth 컨테이너 하나에 가둔 것과 같은 사고방식입니다 — 가장 강한 권한은 가장 좁은 곳에 둡니다.

키를 코드에 두지 않기. 프로젝트 최상위 .env:

AWS_ACCESS_KEY_ID=YOUR_ACTUAL_ACCESS_KEY
AWS_SECRET_ACCESS_KEY=YOUR_ACTUAL_SECRET_ACCESS_KEY
AWS_DEFAULT_REGION=ap-northeast-2

ap-northeast-2는 서울 리전입니다. 리전을 잘못 잡으면 만든 자원이 콘솔에서 안 보입니다(다른 리전 화면을 보고 있는 것).

키메이커로 한 번 더 감싸기. fastapi/core/matrix/aws_tank_s3_manager.py를 참조해 .env.example에 IAM 키 항목을 작성하되, os.getenv("AWS_SECRET_ACCESS_KEY") 로 직접 읽는 부분은 키메이커 방식으로 수정합니다.

# ❌ OS 환경변수에 직접 의존
secret = os.getenv("AWS_SECRET_ACCESS_KEY")

# ✅ 키메이커(싱글톤)가 관리해서 내려준다
secret = KeyMaker.get("AWS_SECRET_ACCESS_KEY")

⭐ 왜 한 겹을 더 두는가 — OS 의존성 해소. os.getenv가 코드 곳곳에 흩어지면, 키의 출처를 .env에서 시크릿 매니저나 볼트로 바꿀 때 모든 파일을 열어야 합니다. 키메이커를 통과하게 하면 바꿀 곳이 한 군데입니다. 3주차의 싱글톤 키메이커, 12주차의 vault_keymaker_secret_manager.py가 여기서 AWS 키까지 흡수합니다. 5주차 DIP 그대로입니다.

9.4 ⭐ VPC와 CIDR — 사설 IP는 공짜, 공인 IP는 돈

  • VPC (Virtual Private Cloud) — [클라우드 네트워크]인데, [AWS 안에 나만 쓰는 논리적으로 격리된 사설 네트워크를 만든다]는 점에서 공용 인터넷과 구별된다.
  • CIDR (Classless Inter-Domain Routing) — [IP 주소 범위 표기법]인데, [/숫자로 앞에서 몇 비트가 고정인지 표시해 범위 크기를 나타낸다]는 점에서 주소 하나를 적는 방식과 구별된다.

/숫자를 읽는 법: 숫자가 작을수록 범위가 큽니다. 직관과 반대라 매번 헷갈립니다.

표기고정 비트주소 개수쓰임
10.0.0.0/16앞 16비트65,536개⭐ VPC 전체
10.0.1.0/24앞 24비트256개서브넷 하나
10.0.1.0/28앞 28비트16개작게 쪼갠 서브넷

⭐ 원칙: VPC는 작은 숫자(/16)로 크게, 서브넷은 큰 숫자(/24, /28)로 세분화.

왜 넉넉하게 잡아도 되는가 — 비용 구조 때문입니다. 축 4가 여기서 금액으로 나타납니다.

사설 IP공인 IP
개수무제한계정당 제한 있음
비용무료과금 (미사용 탄력적 IP는 특히)
도달 범위VPC 안에서만인터넷 전체

사설 IP는 아껴 봐야 얻는 게 없습니다. 오히려 좁게 잡으면 나중에 서브넷을 못 늘려 네트워크를 다시 설계해야 합니다. CIDR은 만든 뒤에 넓히기가 까다롭기 때문에 처음에 크게 잡는 것이 정답입니다.

9.5 VPC 생성 시 반드시 체크하는 두 가지

만들면서 놓치면 나중에 원인을 못 찾는 항목이 둘 있습니다.

체크 항목안 켜면 생기는 일
⭐ DNS 호스트 이름 활성화인스턴스에 ec2-...compute.amazonaws.com 형식의 DNS 이름이 안 붙습니다. IP로만 접속해야 하고, 9.8절의 HostName 에 쓸 이름이 없습니다
⭐ 퍼블릭 IPv4 자동 할당 활성화서브넷에 띄운 인스턴스에 공인 IP가 안 붙어 SSH 접속이 아예 불가능합니다

⚠️ 둘 다 "만들 때 켜는 것" 입니다. 나중에 바꿀 수는 있지만, 이미 만든 인스턴스에는 소급 적용되지 않습니다. "터널은 녹색인데 502"와 같은 부류의 함정입니다 — 설정은 되어 있는데 그 설정이 만들어진 시점 이후에만 적용되는 종류.

9.6 인스턴스 타입 고르기 — 메모리를 먼저 계산한다

선택: m7i-flex.large

이유는 CPU가 아니라 메모리입니다. 이 서버 위에서 무엇을 돌릴지부터 더합니다.

  Qwen 3B (RAM 3~4GB)
+ 헤르메스 컨텍스트 메모리 (64K 컨텍스트 + 툴 스키마)
+ OS · 도커 · 컨테이너들
─────────────────────────────
= 최소 8GB 필요           ← 이보다 작으면 OOM(Out Of Memory)

⭐ 인스턴스 타입은 "빠른가"가 아니라 "안 죽는가"로 먼저 고릅니다. CPU가 부족하면 느려질 뿐이지만, 메모리가 부족하면 프로세스가 강제 종료됩니다. 8주차에서 배운 CPU-bound / I/O-bound 구분과는 다른 축입니다. 여기는 용량의 문제입니다.

m7i-flex의 flex는 성능을 일정 수준까지 유연하게 쓰는 대신 단가가 낮은 계열이라는 뜻입니다. 계속 100%로 돌리는 워크로드가 아니라면 이쪽이 유리합니다.

9.7 요금 — 프리 티어와 크레딧의 차감 순서

항목값
온디맨드 단가시간당 $0.14651
10일 × 24시간약 $35
신규 계정 프리 티어월 750시간

여기서 반드시 알아야 할 것이 차감 순서입니다.

  ① 프리 티어  →  ② 크레딧($100)  →  ③ 등록한 카드

⭐ 월 750시간 프리 티어 안에서 쓰면 요금이 0원이고, $100 크레딧도 차감되지 않습니다. 750시간은 한 달 내내(24×31=744시간) 한 대를 켜 둘 수 있는 양입니다. 두 대를 켜면 그 순간 초과합니다. "한 대만"이 프리 티어의 실질적 의미입니다.

10주차 11.7절에서 배운 대로, 클라우드 사고는 안 쓰는 것이 켜져 있어서 생깁니다. 실험용 인스턴스를 만들었으면 예산 알림(Budget Alert) 을 걸고, 끝나면 중지가 아니라 종료할지 판단합니다.

동작컴퓨팅 요금스토리지(EBS) 요금데이터
중지 (Stop)멈춤⚠️ 계속 나감유지
종료 (Terminate)멈춤멈춤⚠️ 삭제

9.8 VSCode Remote-SSH로 EC2에 붙기

~/.ssh/config (윈도우는 C:\Users\<사용자>\.ssh\config):

Host aws
    HostName     ec2-XX-XX-XX-XX.ap-northeast-2.compute.amazonaws.com
    IdentityFile C:\Users\<사용자>\Downloads\myserver-keypair.pem
    User         ubuntu
항목뜻
Host aws내가 부를 별명. ssh aws 만으로 접속됨
HostName실제 주소 (9.5절에서 DNS 호스트 이름을 켠 덕분에 이름이 있음)
IdentityFile.pem 키 파일 경로 — 비밀번호 대신 이걸로 인증
User우분투 AMI의 기본 사용자는 ubuntu

VSCode에서 좌측 하단 원격 표시 → Connect to Host → aws 를 고르면 붙습니다. 11~12주차에 원격 PC에 붙던 방식과 완전히 같은 절차입니다. 바뀐 것은 HostName뿐입니다.

⚠️ .pem 키 권한 에러가 나면(UNPROTECTED PRIVATE KEY FILE) 파일 권한을 좁힙니다.

chmod 400 ~/.ssh/myserver-keypair.pem

💡 WSL에서 윈도우 다운로드 폴더의 .pem에 접근하려면, WSL은 C 드라이브를 /mnt/c 에 마운트하므로 경로가 /mnt/c/Users/<사용자>/Downloads/myserver-keypair.pem 이 됩니다.

9.9 EC2에 git clone 하기 전에 볼륨부터

기본 볼륨(8GB)으로는 프로젝트를 받다가 디스크가 찹니다. 도커 이미지와 모델 파일이 들어가면 더욱 그렇습니다.

⭐ 볼륨을 20GB로 확장한 뒤 git clone을 진행합니다.

순서가 중요합니다. 먼저 늘리고 받습니다. 받다가 멈추면 중간 상태가 남아 정리가 번거롭습니다.

# 확장 후, OS 가 늘어난 크기를 인식했는지 확인
df -h
lsblk

⚠️ AWS 콘솔에서 볼륨 크기를 늘려도 OS 파일시스템은 자동으로 늘어나지 않는 경우가 있습니다. df -h에서 여전히 8G로 보이면 파일시스템 확장(growpart + resize2fs)이 한 단계 더 필요합니다. "콘솔에서는 바꿨는데 안에서는 그대로"는 클라우드에서 반복되는 패턴입니다. 콘솔의 상태와 OS의 상태는 따로 봅니다.

9.10 이 구성이 지금까지의 구조와 어떻게 이어지는가

EC2 한 대가 생기면, 지금까지의 모든 조각이 자리를 바꿀 수 있게 됩니다.

지금까지EC2 이후
원격 PC(WSL)에 도커 컨테이너EC2에 같은 docker-compose.yml
cloudflared가 내 방에서 터널EC2에서 터널 (PC를 꺼도 살아 있음)
Ollama가 내 GPU에서선택: EC2 CPU 추론 / 온프레미스 GPU를 A2A로 호출
로컬 pgvector 컨테이너선택: 그대로 / RDS 로 이관

⭐ 여기서 이 책 전체의 구조가 한 번 회수됩니다. docker-compose.yml 하나가 그대로 옮겨 갈 수 있는 이유는, 6.5절에서 주소를 컨테이너 이름으로 바꿔 뒀기 때문입니다. localhost나 192.168.x.x가 남아 있었다면 옮기는 순간 전부 깨집니다. 이름으로 부르는 설계는 편의가 아니라 이식성(Portability) 이었습니다.


10. 실습 — 이번 주에 손으로 한 것

앞 절들에 흩어진 절차를 실행 순서대로 모은 것입니다. 각 항목의 "왜"는 괄호 안의 절을 보세요.

10.1 OAuth 앱 등록 (2.8절)

공급자반드시 할 일
네이버애플리케이션 설정값 입력 → 확인 클릭 시 Client ID·Secret 발급 → .env에 붙여넣기
카카오앱 생성 → 카카오 로그인 > 일반 > 사용설정 ON → 플랫폼키 → REST API 키 복사 → 리다이렉트 URI 등록 → 시크릿 발급
구글사용자 인증 정보 → OAuth 2.0 클라이언트 ID 생성
인스타그램제품에 Instagram Basic Display 또는 Graph API 추가

⚠️ 카카오는 기본이 REST API 키이고, Client Secret은 별도로 켜야 합니다. 켜는 것을 권장합니다.

10.2 .env 네 공급자 템플릿 (2.8절)

# ── 1. Naver ──────────────────────────────────────────
NAVER_CLIENT_ID=
NAVER_CLIENT_SECRET=
NAVER_REDIRECT_URI=http://localhost:3000/api/auth/callback/naver

# ── 2. Kakao ──────────────────────────────────────────
KAKAO_CLIENT_ID=카카오_REST_API_키
KAKAO_CLIENT_SECRET=
KAKAO_REDIRECT_URI=http://localhost:3000/api/auth/callback/kakao

# ── 3. Google ─────────────────────────────────────────
GOOGLE_CLIENT_ID=....apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=
GOOGLE_REDIRECT_URI=http://localhost:3000/api/auth/callback/google

# ── 4. Instagram (Meta) ───────────────────────────────
INSTAGRAM_CLIENT_ID=앱_ID
INSTAGRAM_CLIENT_SECRET=앱_시크릿
INSTAGRAM_REDIRECT_URI=https://localhost:3000/api/auth/callback/instagram

⭐ 비밀 키는 절대 코드에 하드코딩하지 않고 .env로 관리합니다. (절대 규칙 5번) 배포 후에는 REDIRECT_URI를 https://auth.example.cloud/auth/callback/... 로 바꾸고, 공급자 콘솔도 함께 바꿉니다. (6.8절)

10.3 RS256 키 생성 (4.7절)

openssl genrsa -out jwt_private.pem 2048
openssl rsa -in jwt_private.pem -pubout -out jwt_public.pem
base64 -w 0 jwt_private.pem > jwt_private.b64
echo "*.pem"  >> .gitignore
echo "*.b64"  >> .gitignore
echo ".env.*" >> .gitignore

10.4 auth 컨테이너 기동과 검증 (4.6·4.10절)

docker compose up -d auth backend cloudflared
docker compose ps
docker compose logs -f auth

# 컨테이너 안에서 헬스체크 (호스트 포트가 없으므로)
docker compose exec auth curl -s localhost:9000/healthz

# 공개키 게시 확인
curl -s https://auth.example.cloud/.well-known/jwks.json

# 규칙 검사
lint-imports
pytest

10.5 cloudflared 컨테이너 전환 (6.3~6.7절)

① cloudflared 컨테이너 기동
② 대시보드 Published application routes 에서 Service 를 컨테이너 이름으로 하나씩 변경
③ 전부 정상 확인
④ 호스트의 기존 cloudflared 내리기
⑤ 마지막에 모든 서비스에서 ports: 제거
cloudflared tunnel route dns <터널이름> auth.example.cloud

10.6 헤르메스 + 로컬 Ollama 전체 절차 (7.4~7.9절)

# 1) 설치
curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash

# 2) 모델 준비
ollama pull qwen3:4b            # 8GB 환경. 여유가 있으면 qwen3:14b

# 3) 컨텍스트 영구 설정
sudo systemctl edit ollama.service
#   [Service]
#   Environment="OLLAMA_CONTEXT_LENGTH=64000"
sudo systemctl daemon-reload && sudo systemctl restart ollama
#   ⚠️ WSL 이라 systemctl 이 안 되면:
#   sudo pkill ollama && OLLAMA_CONTEXT_LENGTH=64000 ollama serve

# 4) 헤르메스 프로바이더 설정
hermes config set model.provider       custom
hermes config set model.base_url       http://localhost:11434/v1
hermes config set model.default        qwen3:4b
hermes config set model.context_length 64000

# 5) 진단 · 기동 · 검증
curl http://localhost:11434/v1/models
hermes doctor
hermes chat
#   첫 질문: 현재 디렉토리의 파일 목록을 보여줘
#   → 실제로 실행하면 성공 / 말로만 설명하면 모델 교체
hermes skills list

10.7 EC2 만들기 (9장)

① VPC 생성 — CIDR /16, DNS 호스트 이름 ✅, 퍼블릭 IPv4 자동 할당 ✅
② EC2 인스턴스 — m7i-flex.large, 키페어(.pem) 다운로드
③ 볼륨 20GB 로 확장
④ ~/.ssh/config 에 Host aws 등록 → VSCode Remote-SSH 접속
⑤ git clone
chmod 400 ~/.ssh/myserver-keypair.pem
ssh aws
df -h && lsblk
git clone <저장소>

10.8 이번 주 에러 대응 모음

증상원인조치
OAuth 콜백만 실패 (브라우저는 정상)Vercel Deployment ProtectionRequire Log In 토글 OFF → Save (2.9절)
redirect_uri_mismatch콘솔 등록값과 .env 불일치슬래시·http/https까지 글자 단위 일치 (2.8절)
인스타그램만 등록 거부Meta는 로컬도 https:// 요구터널로 https 주소 확보 (2.8절)
로그인은 되는데 API만 401쿠키 domain 앞의 점 누락domain=".example.cloud" (3.10절)
로컬에서 쿠키가 안 심김secure=True + http로컬은 터널의 https 주소로 테스트 (3.10절)
aud 다른데 통과됨verify_token에 audience 미전달검증 함수에 aud 필수 인자 (3.8절)
백엔드 import 시점에 터짐개인키를 모듈 로드 시점에 읽음호출 시점에 읽기 (4.4절)
터널은 녹색인데 502Service 주소가 위치와 안 맞음localhost / 컨테이너 이름 / host.docker.internal 삼분법 (6.5절)
방화벽 걸었는데 LAN에서 접근됨도커가 iptables 직접 조작ports: 자체를 제거 (6.2절)
재부팅하면 서비스 죽음LAN IP 하드코딩컨테이너 이름으로 교체 (6.4절)
Ollama runtime context is too smallnum_ctx 40,96065,536 으로 상향 + 모델 재로드 (7.7절)
설정 바꿔도 그대로모델이 메모리에 이미 올라가 있음ollama stop <모델> 후 재실행 (7.7절)
에이전트가 말로만 설명툴 콜링 미지원 모델Qwen3 / Llama 3.x / Mistral Small 계열로 교체 (7.10절)
ModuleNotFoundError: dotenv시스템 파이썬으로 실행 중PATH 확인 (7.4절)
UNPROTECTED PRIVATE KEY FILE.pem 권한이 넓음chmod 400 (9.8절)
EC2에 SSH가 안 붙음퍼블릭 IPv4 자동 할당 미체크VPC/서브넷 설정 확인 (9.5절)
볼륨 늘렸는데 df -h 그대로파일시스템 미확장growpart + resize2fs (9.9절)

11. 이 주의 읽을거리와 그 맥락

11.1 JWT 디버거

🔗 https://www.jwt.io/

왜 이 주에 나왔는가: 3장 전체가 이 화면 하나로 설명됩니다. 토큰을 붙여넣으면 비밀키 없이도 Payload가 읽힌다는 사실을 눈으로 확인하게 되고, 그것이 3.4절("서명은 암호화가 아니다")의 증거가 됩니다. 직접 만든 토큰을 여기 넣어 ✔ Signature Verified 를 확인하는 것이 인증 서버 개발의 첫 디버깅 도구입니다.

11.2 OAuth 개념 정리

🔗 https://www.ibm.com/kr-ko/think/topics/oauth

왜 이 주에 나왔는가: 소셜 로그인을 붙이기 전에 읽어야 하는 글입니다. OAuth를 "로그인 버튼 붙이는 법"으로 배우면 등장인물 네 명(2.3절)이 끝까지 정리되지 않습니다. "인가 위임"이라는 원래 목적에서 출발해야 CLIENT_SECRET을 왜 프론트에 두면 안 되는지가 스스로 설명됩니다.

11.3 A2A 프로토콜 코드랩

🔗 https://codelabs.developers.google.com/intro-a2a-purchasing-concierge

왜 이 주에 나왔는가: 같은 주에 게이트웨이를 분리하고 인증을 붙였습니다. 그 작업의 다음 단계가 "내 에이전트가 남의 에이전트와 이야기한다" 이기 때문입니다. 서비스가 사람만 상대할 때는 로그인 화면이면 충분하지만, 기계가 기계를 부르기 시작하면 신원 증명이 토큰의 문제가 됩니다. A2A와 JWT가 같은 주에 나온 것은 우연이 아닙니다.

11.4 하네스 레퍼런스와 헤르메스 문서

🔗 하네스 레퍼런스 저장소: https://github.com/jikime/harness-lab 🔗 헤르메스 문서: https://wikidocs.net/334920 🔗 하네스 책: https://wikidocs.net/book/19689

왜 이 주에 나왔는가: 7.1절의 등식(에이전트 = 모델 + 하네스)을 남이 만든 실물로 확인하기 위해서입니다. 이번 주에 우리는 하네스를 두 형태로 만들었습니다 — 문서로 된 하네스(auth-gateway-harness.md)와 실행되는 하네스(헤르메스). 레퍼런스 저장소는 그 사이의 중간 형태들을 보여줍니다.

11.5 Framer

🔗 https://www.framer.com/

왜 이 주에 나왔는가: 6.9절에서 말한 "이제 콘텐츠를 채우는 기간"의 도구입니다. 인증 구조가 서면 로그인한 사용자가 볼 화면이 필요해집니다. 백엔드 아키텍처만 정교해지고 보여줄 화면이 없으면, 검증할 방법이 없습니다.

11.6 이 주의 자료가 서로 어떻게 묶이는가

   [OAuth · JWT]  누가 들어오는가        ──┐
   [터널 · 프록시] 어디로 들어오는가      ──┼──▶  게이트웨이 하나
   [A2A · 헤르메스] 무엇이 들어오는가     ──┤     (사람도 기계도 이 문으로)
   [AWS]          그 문이 어디 서 있는가 ──┘

이번 주의 네 덩어리는 전부 "문"에 관한 이야기입니다. 누가·어디로·무엇이·어디에.


부록 A. 용어 사전

용어영문상위 범주정의
IP 주소IP Address식별자네트워크상의 장치 하나를 가리키는 숫자 주소
URLUniform Resource Locator식별자규약·호스트·경로를 합쳐 자원 하나를 가리키는 주소
HTTPHyperText Transfer Protocol응용 규약요청·응답 한 쌍으로 문서·데이터를 주고받는 약속
SMTPSimple Mail Transfer Protocol응용 규약메일을 보내는 방향의 약속
FTPFile Transfer Protocol응용 규약파일을 통째로 옮기는 약속
SSHSecure Shell응용 규약원격 셸에 암호화된 통로로 로그인하는 약속
DNSDomain Name System이름 해석 체계도메인 이름을 IP로 바꿔주는 분산 시스템 전체
NSName ServerDNS의 부품특정 도메인의 답을 실제로 들고 있는 서버
APIApplication Programming Interface계약프로그램이 호출하는 창구의 목록과 규격
포트Port식별자한 기계 안의 서비스 창구 번호
인증Authentication (AuthN)출입 절차"너는 누구냐" 확인. 실패 시 401
인가Authorization (AuthZ)출입 절차"너는 뭘 해도 되냐" 확인. 실패 시 403
OAuth 2.0—인가 프로토콜비밀번호를 넘기지 않고 제한된 권한만 위임하는 규약
OIDCOpenID ConnectOAuth 확장OAuth 위에 신원(ID Token)을 표준화한 층
자원 소유자Resource OwnerOAuth 역할사용자 본인
클라이언트ClientOAuth 역할권한을 빌리려는 우리 서비스 (브라우저가 아님)
인가 서버Authorization ServerOAuth 역할동의를 받고 토큰을 발급하는 곳
자원 서버Resource ServerOAuth 역할실제 데이터를 들고 있는 곳
인가 코드Authorization CodeOAuth 산출물토큰으로 교환하기 위한 1회용 단명 코드
Redirect URI—OAuth 설정인가 코드를 받을 사전 등록된 주소
state—OAuth 파라미터왕복 대조로 CSRF를 막는 임의 문자열
JWTJSON Web Token인증 토큰JSON에 서명을 붙여 저장소 조회 없이 검증되는 토큰
클레임ClaimJWT 구성요소Payload에 담긴 정보 항목 (sub, aud, exp …)
subSubject클레임이 토큰이 가리키는 주체(사용자)
audAudience클레임이 토큰을 쓸 수 있는 대상 서비스
jtiJWT ID클레임토큰 고유 번호. 블랙리스트 조회 키
kidKey IDHeader 필드어느 공개키로 검증할지 가리키는 값
HS256HMAC-SHA256서명 알고리즘하나의 키로 서명·검증하는 대칭 방식
RS256RSA-SHA256서명 알고리즘개인키로 서명하고 공개키로 검증하는 비대칭 방식
JWKSJSON Web Key Set배포 형식공개키들을 kid와 함께 JSON으로 게시한 것
액세스 토큰Access TokenJWT매 요청의 신분증. 수명이 짧음(10분)
리프레시 토큰Refresh Token토큰새 액세스 토큰을 받는 교환권. Redis에 저장
로테이션Rotation보안 기법쓸 때마다 새 리프레시 토큰으로 교체하고 옛것을 폐기
재사용 감지Reuse Detection보안 기법폐기된 토큰이 다시 오면 세션 전체를 끊는 것
RBACRole-Based Access Control접근 제어사용자→역할→권한 순으로 묶는 인가 방식
ACLAccess Control List접근 제어사용자마다 권한을 직접 나열하는 방식
최소 권한 원칙Least Privilege보안 원칙필요한 만큼만 권한을 주는 것
프록시Proxy중계 서버클라이언트와 서버 사이에서 요청을 대신 전달하는 것
포워드 프록시Forward Proxy프록시클라이언트 앞에 서서 클라이언트를 대리
리버스 프록시Reverse Proxy프록시서버 앞에 서서 서버를 대리
API 게이트웨이API Gateway리버스 프록시인증·라우팅·속도 제한을 앞단에서 처리
로드밸런서Load Balancer리버스 프록시여러 서버에 부하를 나눔
CDNContent Delivery Network리버스 프록시정적 자원을 가까운 곳에 캐시
터널Tunnel리버스 프록시안에서 밖으로 연 연결을 역방향 통로로 씀
TLS 종료TLS Termination프록시 기능앞단에서 https를 풀고 뒤로는 http로 보냄
WAFWeb Application Firewall보호 장치웹 공격 패턴을 앞단에서 차단
레이트 리밋Rate Limit보호 장치일정 시간당 요청 수를 제한
공격 표면Attack Surface보안 개념외부에서 닿을 수 있는 지점의 총합
도커 내장 DNSDocker embedded DNS이름 해석컨테이너 이름을 사설 IP로 해석해 주는 도커 기능
host.docker.internal—특수 호스트명컨테이너에서 본 호스트 머신
host-gateway—도커 설정값리눅스에서 위 이름을 쓰게 해 주는 extra_hosts 값
ingress—터널 설정hostname별로 뒤쪽 서비스를 지정하는 규칙 목록
블루-그린Blue-Green Deployment배포 방식새것을 세우고 옮긴 뒤 옛것을 지우는 무중단 전환
import-linter—검사 도구모듈 간 import 금지 규칙을 CI가 강제하게 하는 도구
에이전트AgentAI 시스템모델 + 하네스
하네스Harness방법론모델을 규칙 문서와 검증 장치로 감싸는 틀
MCPModel Context Protocol프로토콜에이전트를 도구·데이터에 잇는 규약
A2AAgent2Agent프로토콜에이전트를 다른 에이전트에 잇는 규약
툴 콜링Tool Calling모델 능력툴 스키마를 읽고 정확한 호출을 만들어 내는 능력
컨텍스트 길이Context Length런타임 설정한 번에 실을 수 있는 토큰 수 (num_ctx)
KV 캐시KV Cache추론 자원이미 계산한 키·값을 저장해 두는 메모리
양자화Quantization경량화 기법가중치를 더 적은 비트로 표현해 메모리를 줄임
PEFTParameter-Efficient Fine-Tuning학습 방법일부 파라미터만 학습하는 방식
LoRALow-Rank AdaptationPEFT원본을 얼리고 작은 행렬만 학습
QLoRAQuantized LoRAPEFT4비트 양자화 위에서 LoRA를 학습
OOMOut Of Memory장애메모리 부족으로 프로세스가 강제 종료됨
IAMIdentity and Access ManagementAWS 서비스AWS 자원에 대한 사용자·역할·정책 관리
MFAMulti-Factor Authentication보안비밀번호 외 두 번째 인증 수단
VPCVirtual Private CloudAWS 서비스AWS 안의 격리된 나만의 사설 네트워크
CIDRClassless Inter-Domain Routing표기법/숫자로 IP 범위 크기를 나타내는 방식
서브넷Subnet네트워크VPC를 잘게 나눈 주소 구획
리전RegionAWS 개념지리적 위치 단위 (ap-northeast-2 = 서울)
온디맨드On-Demand요금제약정 없이 쓴 시간만큼 내는 방식
프리 티어Free Tier요금제신규 계정에 주어지는 무료 사용량 (월 750시간)
중지 / 종료Stop / TerminateEC2 동작중지는 스토리지 과금 유지, 종료는 데이터 삭제

부록 B. 스스로 확인하기

#질문답이 있는 곳
1IP와 URL, HTTP와 API는 각각 어느 범주인가?1.1
2401과 403은 무엇이 다른가?2.1
3OAuth는 인증 프로토콜인가 인가 프로토콜인가?2.2
4⑤단계(토큰 교환)가 브라우저를 거치지 않는 이유는?2.4
5네이버에서 동의를 받았는데 왜 우리 약관 동의가 또 필요한가?2.7
6JWT Payload에 비밀번호를 넣으면 안 되는 이유는?3.4
7왜 HS256이 아니라 RS256을 골랐는가?3.5
8alg=none 공격과 HS256 강제 공격을 한 줄로 막는 방법은?3.7
9리프레시 토큰 재사용이 감지되면 왜 세션 전체를 끊는가?3.9
10개인키를 모듈 로드 시점이 아니라 호출 시점에 읽는 이유는?4.4
11RBAC가 ACL보다 나은 점을 사용자 1만 명 기준으로 설명하면?4.5
12포워드 프록시와 리버스 프록시는 무엇으로 갈리는가?5.1
13ports: 8000:8000 이 왜 보안 구멍인가? 네 가지를 대라6.2
14localhost / 컨테이너 이름 / host.docker.internal 을 각각 언제 쓰는가?6.5
15무중단 전환에서 ports: 제거를 왜 마지막에 하는가?6.7
16MCP와 A2A는 각각 무엇을 잇는가?7.2
17에이전트가 일반 채팅보다 컨텍스트를 많이 먹는 이유는?7.6
18툴 콜링이 되는 모델인지 첫 질문 하나로 판별하는 법은?7.10
19VPC CIDR을 /16으로 크게 잡아도 되는 이유는?9.4
20프리 티어·크레딧·카드의 차감 순서는?9.7

부록 C. 자주 하는 실수

실수바로잡기
"IP와 HTTP는 비슷한 것"❌ 하나는 주소, 하나는 대화 방식입니다. 비교 대상이 아닙니다
"401은 권한 부족"❌ 401은 인증 실패(누군지 모름). 권한 부족은 403
"OAuth는 로그인 프로토콜"❌ 인가 위임 프로토콜입니다. 로그인은 그 위의 응용
"OAuth의 Client는 브라우저"❌ 우리 서비스입니다
"소셜 로그인 썼으니 약관 동의 생략"❌ 수집 주체가 따로 받아야 합니다 (개인정보보호법)
"Redirect URI는 대충 맞으면 됨"❌ 슬래시 하나, http/https 차이로도 거부됩니다
"JWT는 암호화되어 있다"❌ Payload는 Base64URL 인코딩일 뿐, 누구나 읽습니다
"서명이 있으니 비밀도 넣어도 됨"❌ 서명은 무결성을 보장하지 기밀성을 보장하지 않습니다
"algorithms를 환경변수로 빼자"❌ 보안 불변식은 리터럴로 고정합니다 (3.7)
"공개키는 감춰야 한다"❌ 공개키는 공개해도 안전합니다. 그래서 JWKS로 게시합니다
"액세스 토큰만 있으면 됨"⚠️ 즉시 무효화가 안 됩니다. 짧은 수명 + 리프레시 로테이션이 짝
"쿠키 domain은 example.cloud"⚠️ 앞에 점(.) 을 붙여야 서브도메인이 공유합니다
"개인키를 파일 상단에서 읽자"❌ 백엔드 컨테이너에서 import 자체가 터집니다
"규칙은 문서에 적어 두면 지켜짐"❌ lint-imports 로 도구가 강제하게 합니다
"리버스는 트래픽이 거꾸로 간다는 뜻"❌ 방향은 같습니다. 누구를 대리하느냐가 뒤집힌 것
"방화벽 켰으니 도커 포트도 안전"❌ 도커는 iptables를 직접 조작해 UFW를 우회합니다
"ports: 는 편의 기능일 뿐"❌ 0.0.0.0 바인딩이라 LAN 전체에 열립니다
"터널이 녹색이니 설정은 맞다"⚠️ 502면 Service 주소를 보세요. 커넥터 위치에 따라 답이 달라집니다
"컨테이너 안에서도 localhost"❌ 컨테이너의 localhost는 자기 자신입니다
"ports: 부터 지우고 전환하자"❌ 새 길을 먼저 열고 마지막에 지웁니다 (6.7)
"작은 모델도 에이전트가 된다"⚠️ 툴 콜링 능력이 있어야 합니다. 말로만 설명하면 실패
"컨텍스트는 크게 잡을수록 좋다"⚠️ KV 캐시 VRAM을 그만큼 삽니다. 공짜가 아닙니다
"설정 바꿨으니 반영됐다"⚠️ Ollama는 모델 재로드, FastAPI는 서버 재시작이 필요합니다
"Ollama Cloud가 내 Ollama"❌ 그건 유료 호스팅 서비스입니다. Custom endpoint를 고르세요
"VPC는 아껴서 작게"❌ 사설 IP는 무료·무제한. /16으로 크게 잡습니다
"인스턴스는 CPU 보고 고른다"⚠️ 메모리부터 계산합니다. 부족하면 느려지는 게 아니라 죽습니다
"인스턴스 중지했으니 과금 0"⚠️ EBS 스토리지 요금은 계속 나갑니다
"루트 계정으로 다 하면 편함"❌ IAM 사용자 + MFA. 루트는 잠급니다

부록 D. 날짜별 색인

날짜다룬 내용이 장의 절
7/20 (월)API·URL·IP·HTTP·SMTP·FTP·SSH·NS·DNS 정리, A2A 프로토콜 코드랩, AWS+온프레미스 하이브리드(EXAONE·Qwen·MCP·그래프DB), 소셜 로그인 5종 화면과 백엔드 1종 선택, OAuth라도 약관 동의 필수(방식 A/B), Vercel Deployment Protection 해제1, 7.27.3, 2.62.7, 2.9
7/21 (화)JWT 디버거 화면 해설(Header/Payload/Signature, 서명 검증), OAuth 개념 정리, 네이버·카카오·구글·인스타 .env 4종 템플릿, 네이버·카카오 앱 등록 절차, RTX 3050 8GB QLoRA 모델 추천 8개 주제3.23.4, 2.22.5, 2.8, 10.1~10.2, 8
7/22 (수)에이전트 = 모델 + 하네스, 하네스 레퍼런스, 게이트웨이 서브도메인 분리 설계, auth-gateway-harness.md 전문(RS256·절대 규칙 6·완료 기준 8), Cloudflare 터널을 컨테이너 이름으로, ports: 제거, .env 콜백 주소 변경과 Redis 저장, 다음은 콘텐츠7.1, 4, 3.5~3.10, 5, 6
7/23 (목)Framer, 헤르메스 WSL 설치, 플랜 없이 로컬 Ollama로 구동, 컨텍스트 40,960→65,536 에러 해결, qwen3:4b 다운시프트, 헤르메스 MCP 서버 등록, ~/.hermes, SOUL.md 페르소나7.4~7.11, 11.5
7/24 (금)AWS 설치 순서 VPC→EC2→RDS→S3, IAM과 인증 관리자 앱, .env AWS 키와 키메이커, VPC CIDR /16과 사설/공인 IP 비용, DNS 호스트 이름·퍼블릭 IPv4 체크, m7i-flex.large 메모리 산수, 온디맨드·프리 티어 750시간·차감 순서, VSCode Remote-SSH ~/.ssh/config, 볼륨 20GB 확장 후 git clone9

부록 E. Gemini / NotebookLM 프롬프트

복습 문제 받기

이 13주차 교재만 근거로 8문제를 내줘. 비전공자 기준으로.
"인증과 인가의 차이", "HS256과 RS256", "ports: 를 제거하는 이유"는 반드시 포함해줘.
답은 바로 주지 말고, 내가 답한 다음에 채점하고
틀린 이유를 교재의 어느 절을 근거로 설명해줘. 교재에 없는 내용은 꺼내지 마.

인포그래픽 ① — OAuth 흐름

「2.4 인가 코드 흐름」을 인포그래픽 1장으로 만들어줘.
- 등장인물 4명(자원 소유자/클라이언트/인가 서버/자원 서버)을 가로로 배치
- ①~⑦ 단계를 화살표로, 각 화살표 위에 오가는 값(code, access_token, JWT)을 표시
- ⑤단계(서버↔서버)는 다른 색으로 강조하고 "브라우저를 거치지 않음"이라고 표기
- 전문용어는 한글(영문) 병기

인포그래픽 ② — 열쇠를 둘로, 문을 하나로

「3.5 HS256과 RS256」과 「6.6 ports: 제거」를 한 장으로 합쳐줘.
- 왼쪽: auth 컨테이너(개인키)와 backend 컨테이너(공개키)를 그리고
  "발급은 여기서만 / 검증은 어디서나"를 표시
- 오른쪽: 변경 전(문 2개 - 터널 + ports)과 변경 후(문 1개 - 터널만)를 대비
- 아래에 이 장의 한 문장: "열쇠는 둘로 나누고, 문은 하나로 모은다"

인포그래픽 ③ — 주소 삼분법

「6.5 localhost · 컨테이너 이름 · host.docker.internal」을 한 장으로 만들어줘.
호스트 머신 박스 안에 도커 네트워크 박스를 넣고,
세 가지 화살표를 각각 다른 색으로 그린 뒤 "언제 쓰는가"를 옆에 적어줘.

막힐 때

13주차에서 "___" 가 이해가 안 돼.
이 교재의 판별 축 5개(신원 위임 / 대칭·비대칭 / 클라이언트 앞·서버 앞 / 사설·공인 / 문 개수)만
써서 다시 설명해줘. 교재에 없는 새 비유는 만들지 마.

미국식 분류로 다시 정리하기

이 교재에 나온 비유(문, 열쇠, 정문과 뒷문, 악수한 손)를 전부 걷어내고,
"이것은 무엇의 한 종류인가 / 형제 개념과 무엇으로 갈리는가" 형식으로만
프록시·리버스 프록시·게이트웨이·로드밸런서·CDN·터널을 다시 정의해줘.

앞 장과의 연결 확인

이 13주차 교재에서 앞 장(1·3·5·6·7·8·9·10주차)을 참조한 부분을 전부 찾아서,
"앞 장의 어떤 개념이 / 이번 장에서 어떤 이름으로 다시 나오는가"를 표로 정리해줘.
특히 6주차의 프록시·어댑터가 이번 장에서 어떻게 두 층위로 반복되는지 설명해줘.