
생성 수정
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 이상이 필요합니다. 미만이면 두 가지 증상이 나타납니다.
- 작업 도중에 앞을 "잊어버립니다" (초반 지시가 창 밖으로 밀려남)
- 시작 시점에 모델이 아예 거부합니다
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 | 물체 감지 | SSD | YOLO Nano 계열 (11주차에서 다룸) |
| 3 | 시멘틱 분할 | PSPNet | 경량 세그멘테이션 백본 |
| 4 | 자세 추정 | OpenPose | 경량 키포인트 모델 |
| 5 | GAN 이미지 생성 | DCGAN, SAGAN | 해상도를 낮춰 학습 |
| 6 | GAN 이상 탐지 | 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 | 아무것에도 의존하지 않음 |
| EC2 | VPC의 서브넷 안에 놓여야 함 |
| RDS | VPC + 서브넷 그룹 + 보안 그룹 |
| S3 | VPC와 무관 (전역 서비스, URL로 접근) |
⭐ 5주차의 의존 방향 원칙이 인프라에서 반복됩니다. 의존받는 쪽이 먼저 있어야 합니다. VPC 없이 EC2를 만들려 하면 AWS가 기본 VPC에 알아서 넣어 주는데, 그 순간 네트워크 설계권을 잃습니다. 그래서 직접 만들고 시작합니다.
9.3 IAM — 루트 계정으로 일하지 않는다
- ⭐ IAM (Identity and Access Management) — [AWS의 접근 제어 서비스]인데, [루트 계정과 별도로 사용자·역할·정책을 만들어 최소 권한만 부여한다]는 점에서 계정 하나로 전부 하는 방식과 구별된다.
4.5절의 RBAC와 완전히 같은 사고방식입니다. 사용자에게 권한을 직접 붙이지 않고 정책(Policy)에 묶습니다.
절차:
- IAM 계정 생성 → 사용자 생성 → 권한 부여
- 액세스 방식은 CLI 선택 (콘솔 로그인용이 아니라 프로그래밍 접근용)
- 인증 관리자 앱(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 Protection | Require 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절) |
| 터널은 녹색인데 502 | Service 주소가 위치와 안 맞음 | localhost / 컨테이너 이름 / host.docker.internal 삼분법 (6.5절) |
| 방화벽 걸었는데 LAN에서 접근됨 | 도커가 iptables 직접 조작 | ports: 자체를 제거 (6.2절) |
| 재부팅하면 서비스 죽음 | LAN IP 하드코딩 | 컨테이너 이름으로 교체 (6.4절) |
Ollama runtime context is too small | num_ctx 40,960 | 65,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 디버거
왜 이 주에 나왔는가: 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
왜 이 주에 나왔는가: 6.9절에서 말한 "이제 콘텐츠를 채우는 기간"의 도구입니다. 인증 구조가 서면 로그인한 사용자가 볼 화면이 필요해집니다. 백엔드 아키텍처만 정교해지고 보여줄 화면이 없으면, 검증할 방법이 없습니다.
11.6 이 주의 자료가 서로 어떻게 묶이는가
[OAuth · JWT] 누가 들어오는가 ──┐
[터널 · 프록시] 어디로 들어오는가 ──┼──▶ 게이트웨이 하나
[A2A · 헤르메스] 무엇이 들어오는가 ──┤ (사람도 기계도 이 문으로)
[AWS] 그 문이 어디 서 있는가 ──┘
이번 주의 네 덩어리는 전부 "문"에 관한 이야기입니다. 누가·어디로·무엇이·어디에.
부록 A. 용어 사전
| 용어 | 영문 | 상위 범주 | 정의 |
|---|---|---|---|
| IP 주소 | IP Address | 식별자 | 네트워크상의 장치 하나를 가리키는 숫자 주소 |
| URL | Uniform Resource Locator | 식별자 | 규약·호스트·경로를 합쳐 자원 하나를 가리키는 주소 |
| HTTP | HyperText Transfer Protocol | 응용 규약 | 요청·응답 한 쌍으로 문서·데이터를 주고받는 약속 |
| SMTP | Simple Mail Transfer Protocol | 응용 규약 | 메일을 보내는 방향의 약속 |
| FTP | File Transfer Protocol | 응용 규약 | 파일을 통째로 옮기는 약속 |
| SSH | Secure Shell | 응용 규약 | 원격 셸에 암호화된 통로로 로그인하는 약속 |
| DNS | Domain Name System | 이름 해석 체계 | 도메인 이름을 IP로 바꿔주는 분산 시스템 전체 |
| NS | Name Server | DNS의 부품 | 특정 도메인의 답을 실제로 들고 있는 서버 |
| API | Application Programming Interface | 계약 | 프로그램이 호출하는 창구의 목록과 규격 |
| 포트 | Port | 식별자 | 한 기계 안의 서비스 창구 번호 |
| 인증 | Authentication (AuthN) | 출입 절차 | "너는 누구냐" 확인. 실패 시 401 |
| 인가 | Authorization (AuthZ) | 출입 절차 | "너는 뭘 해도 되냐" 확인. 실패 시 403 |
| OAuth 2.0 | — | 인가 프로토콜 | 비밀번호를 넘기지 않고 제한된 권한만 위임하는 규약 |
| OIDC | OpenID Connect | OAuth 확장 | OAuth 위에 신원(ID Token)을 표준화한 층 |
| 자원 소유자 | Resource Owner | OAuth 역할 | 사용자 본인 |
| 클라이언트 | Client | OAuth 역할 | 권한을 빌리려는 우리 서비스 (브라우저가 아님) |
| 인가 서버 | Authorization Server | OAuth 역할 | 동의를 받고 토큰을 발급하는 곳 |
| 자원 서버 | Resource Server | OAuth 역할 | 실제 데이터를 들고 있는 곳 |
| 인가 코드 | Authorization Code | OAuth 산출물 | 토큰으로 교환하기 위한 1회용 단명 코드 |
| Redirect URI | — | OAuth 설정 | 인가 코드를 받을 사전 등록된 주소 |
| state | — | OAuth 파라미터 | 왕복 대조로 CSRF를 막는 임의 문자열 |
| JWT | JSON Web Token | 인증 토큰 | JSON에 서명을 붙여 저장소 조회 없이 검증되는 토큰 |
| 클레임 | Claim | JWT 구성요소 | Payload에 담긴 정보 항목 (sub, aud, exp …) |
sub | Subject | 클레임 | 이 토큰이 가리키는 주체(사용자) |
aud | Audience | 클레임 | 이 토큰을 쓸 수 있는 대상 서비스 |
jti | JWT ID | 클레임 | 토큰 고유 번호. 블랙리스트 조회 키 |
kid | Key ID | Header 필드 | 어느 공개키로 검증할지 가리키는 값 |
| HS256 | HMAC-SHA256 | 서명 알고리즘 | 하나의 키로 서명·검증하는 대칭 방식 |
| RS256 | RSA-SHA256 | 서명 알고리즘 | 개인키로 서명하고 공개키로 검증하는 비대칭 방식 |
| JWKS | JSON Web Key Set | 배포 형식 | 공개키들을 kid와 함께 JSON으로 게시한 것 |
| 액세스 토큰 | Access Token | JWT | 매 요청의 신분증. 수명이 짧음(10분) |
| 리프레시 토큰 | Refresh Token | 토큰 | 새 액세스 토큰을 받는 교환권. Redis에 저장 |
| 로테이션 | Rotation | 보안 기법 | 쓸 때마다 새 리프레시 토큰으로 교체하고 옛것을 폐기 |
| 재사용 감지 | Reuse Detection | 보안 기법 | 폐기된 토큰이 다시 오면 세션 전체를 끊는 것 |
| RBAC | Role-Based Access Control | 접근 제어 | 사용자→역할→권한 순으로 묶는 인가 방식 |
| ACL | Access Control List | 접근 제어 | 사용자마다 권한을 직접 나열하는 방식 |
| 최소 권한 원칙 | Least Privilege | 보안 원칙 | 필요한 만큼만 권한을 주는 것 |
| 프록시 | Proxy | 중계 서버 | 클라이언트와 서버 사이에서 요청을 대신 전달하는 것 |
| 포워드 프록시 | Forward Proxy | 프록시 | 클라이언트 앞에 서서 클라이언트를 대리 |
| 리버스 프록시 | Reverse Proxy | 프록시 | 서버 앞에 서서 서버를 대리 |
| API 게이트웨이 | API Gateway | 리버스 프록시 | 인증·라우팅·속도 제한을 앞단에서 처리 |
| 로드밸런서 | Load Balancer | 리버스 프록시 | 여러 서버에 부하를 나눔 |
| CDN | Content Delivery Network | 리버스 프록시 | 정적 자원을 가까운 곳에 캐시 |
| 터널 | Tunnel | 리버스 프록시 | 안에서 밖으로 연 연결을 역방향 통로로 씀 |
| TLS 종료 | TLS Termination | 프록시 기능 | 앞단에서 https를 풀고 뒤로는 http로 보냄 |
| WAF | Web Application Firewall | 보호 장치 | 웹 공격 패턴을 앞단에서 차단 |
| 레이트 리밋 | Rate Limit | 보호 장치 | 일정 시간당 요청 수를 제한 |
| 공격 표면 | Attack Surface | 보안 개념 | 외부에서 닿을 수 있는 지점의 총합 |
| 도커 내장 DNS | Docker embedded DNS | 이름 해석 | 컨테이너 이름을 사설 IP로 해석해 주는 도커 기능 |
host.docker.internal | — | 특수 호스트명 | 컨테이너에서 본 호스트 머신 |
host-gateway | — | 도커 설정값 | 리눅스에서 위 이름을 쓰게 해 주는 extra_hosts 값 |
| ingress | — | 터널 설정 | hostname별로 뒤쪽 서비스를 지정하는 규칙 목록 |
| 블루-그린 | Blue-Green Deployment | 배포 방식 | 새것을 세우고 옮긴 뒤 옛것을 지우는 무중단 전환 |
| import-linter | — | 검사 도구 | 모듈 간 import 금지 규칙을 CI가 강제하게 하는 도구 |
| 에이전트 | Agent | AI 시스템 | 모델 + 하네스 |
| 하네스 | Harness | 방법론 | 모델을 규칙 문서와 검증 장치로 감싸는 틀 |
| MCP | Model Context Protocol | 프로토콜 | 에이전트를 도구·데이터에 잇는 규약 |
| A2A | Agent2Agent | 프로토콜 | 에이전트를 다른 에이전트에 잇는 규약 |
| 툴 콜링 | Tool Calling | 모델 능력 | 툴 스키마를 읽고 정확한 호출을 만들어 내는 능력 |
| 컨텍스트 길이 | Context Length | 런타임 설정 | 한 번에 실을 수 있는 토큰 수 (num_ctx) |
| KV 캐시 | KV Cache | 추론 자원 | 이미 계산한 키·값을 저장해 두는 메모리 |
| 양자화 | Quantization | 경량화 기법 | 가중치를 더 적은 비트로 표현해 메모리를 줄임 |
| PEFT | Parameter-Efficient Fine-Tuning | 학습 방법 | 일부 파라미터만 학습하는 방식 |
| LoRA | Low-Rank Adaptation | PEFT | 원본을 얼리고 작은 행렬만 학습 |
| QLoRA | Quantized LoRA | PEFT | 4비트 양자화 위에서 LoRA를 학습 |
| OOM | Out Of Memory | 장애 | 메모리 부족으로 프로세스가 강제 종료됨 |
| IAM | Identity and Access Management | AWS 서비스 | AWS 자원에 대한 사용자·역할·정책 관리 |
| MFA | Multi-Factor Authentication | 보안 | 비밀번호 외 두 번째 인증 수단 |
| VPC | Virtual Private Cloud | AWS 서비스 | AWS 안의 격리된 나만의 사설 네트워크 |
| CIDR | Classless Inter-Domain Routing | 표기법 | /숫자로 IP 범위 크기를 나타내는 방식 |
| 서브넷 | Subnet | 네트워크 | VPC를 잘게 나눈 주소 구획 |
| 리전 | Region | AWS 개념 | 지리적 위치 단위 (ap-northeast-2 = 서울) |
| 온디맨드 | On-Demand | 요금제 | 약정 없이 쓴 시간만큼 내는 방식 |
| 프리 티어 | Free Tier | 요금제 | 신규 계정에 주어지는 무료 사용량 (월 750시간) |
| 중지 / 종료 | Stop / Terminate | EC2 동작 | 중지는 스토리지 과금 유지, 종료는 데이터 삭제 |
부록 B. 스스로 확인하기
| # | 질문 | 답이 있는 곳 |
|---|---|---|
| 1 | IP와 URL, HTTP와 API는 각각 어느 범주인가? | 1.1 |
| 2 | 401과 403은 무엇이 다른가? | 2.1 |
| 3 | OAuth는 인증 프로토콜인가 인가 프로토콜인가? | 2.2 |
| 4 | ⑤단계(토큰 교환)가 브라우저를 거치지 않는 이유는? | 2.4 |
| 5 | 네이버에서 동의를 받았는데 왜 우리 약관 동의가 또 필요한가? | 2.7 |
| 6 | JWT Payload에 비밀번호를 넣으면 안 되는 이유는? | 3.4 |
| 7 | 왜 HS256이 아니라 RS256을 골랐는가? | 3.5 |
| 8 | alg=none 공격과 HS256 강제 공격을 한 줄로 막는 방법은? | 3.7 |
| 9 | 리프레시 토큰 재사용이 감지되면 왜 세션 전체를 끊는가? | 3.9 |
| 10 | 개인키를 모듈 로드 시점이 아니라 호출 시점에 읽는 이유는? | 4.4 |
| 11 | RBAC가 ACL보다 나은 점을 사용자 1만 명 기준으로 설명하면? | 4.5 |
| 12 | 포워드 프록시와 리버스 프록시는 무엇으로 갈리는가? | 5.1 |
| 13 | ports: 8000:8000 이 왜 보안 구멍인가? 네 가지를 대라 | 6.2 |
| 14 | localhost / 컨테이너 이름 / host.docker.internal 을 각각 언제 쓰는가? | 6.5 |
| 15 | 무중단 전환에서 ports: 제거를 왜 마지막에 하는가? | 6.7 |
| 16 | MCP와 A2A는 각각 무엇을 잇는가? | 7.2 |
| 17 | 에이전트가 일반 채팅보다 컨텍스트를 많이 먹는 이유는? | 7.6 |
| 18 | 툴 콜링이 되는 모델인지 첫 질문 하나로 판별하는 법은? | 7.10 |
| 19 | VPC 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.2 |
| 7/21 (화) | JWT 디버거 화면 해설(Header/Payload/Signature, 서명 검증), OAuth 개념 정리, 네이버·카카오·구글·인스타 .env 4종 템플릿, 네이버·카카오 앱 등록 절차, RTX 3050 8GB QLoRA 모델 추천 8개 주제 | 3.2 |
| 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 clone | 9 |
부록 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주차의 프록시·어댑터가 이번 장에서 어떻게 두 층위로 반복되는지 설명해줘.
LangTailor