
생성 수정
IBM * 레드햇 AX 과정 · 29강
맨 아래층과 맨 바깥층 — 커널에서 파이프라인까지
2026년 8월 3일(월) ~ 8월 25일(화)
마지막 장입니다.
이 장은 두 방향으로 동시에 움직입니다. 하나는 아래로 — 지금까지 우리가 서 있던 바닥을 뜯어내고 운영체제의 최하단인 커널(Kernel) 까지 내려갑니다. 다른 하나는 바깥으로 — 코드를 짜는 일에서 벗어나 그 코드가 자동으로 검증되고 자동으로 배포되는 바깥 루프(CI/CD) 와, 시스템의 경계에 서서 요청을 대신 받아주는 프록시(Proxy) 까지 나갑니다.
아래로 내려가든 바깥으로 나가든, 우리는 이미 아는 것을 다시 만납니다.
- 커널을 뜯으면 3주차의 모놀리식 ↔ MSA 축이 그대로 있습니다. 모놀리식 커널과 마이크로커널입니다.
- 컨테이너 엔진을 비교하면 9주차의 단일 장애점(SPOF) 이 그대로 있습니다.
dockerd데몬입니다. - 프록시를 배우면 3주차 GoF 23패턴에서 스쳐 지나간 프록시 패턴이 네트워크 크기로 커져 있습니다.
- CI/CD를 배우면 하네스 엔지니어링에서 배운 외부 루프(센서) 가 이름만 바꿔 서 있습니다.
그래서 이 장의 마지막 절은 「전 과정 되돌아보기」입니다. 3주차에서 우리는 "객체지향의 근본 구조가 프랙탈이다" 라는 문장을 만났습니다. 그때는 그 말이 그럴듯한 비유처럼 들렸을 것입니다. 이 장 끝에서 그 문장은 검증된 사실이 됩니다. 같은 판별 축 여덟 개가 CPU의 링(Ring) 권한 구조부터 멀티 에이전트 워크플로우까지 크기만 바꿔 반복된다는 것을, 표 하나로 확인하고 이 책을 닫습니다.
이 장을 다 읽으면
- 커널을 상위 범주와 종차로 정의하고, 4대 역할을 각각 예를 들어 말할 수 있다.
- 사용자 모드(Ring 3)와 커널 모드(Ring 0)의 경계가 왜 필요한지, 시스템 콜이 그 경계를 어떻게 넘는지 설명할 수 있다.
- 모놀리식 커널과 마이크로커널의 트레이드오프를 말하고, 그것이 3주차의 모놀리식 ↔ MSA 축과 같은 축임을 설명할 수 있다.
- Docker와 Podman을 아키텍처·권한·단일 장애점·Pod·systemd 다섯 항목으로 비교하고, 상황에 맞게 고를 수 있다.
- 포워드 프록시와 리버스 프록시를 위치·중계 방향·대상·목적·예시로 구별하고, 우리 시스템 안에서 리버스 프록시가 어디에 있었는지 지목할 수 있다.
- CI와 CD를 구분하고, Continuous Delivery와 Continuous Deployment가 사람의 승인 유무로 갈린다는 것을 설명할 수 있다.
- S3에 파일을 올릴 때 액세스 키를 서버에 심지 않고 IAM 역할로 처리하는 절차를 순서대로 수행할 수 있다.
- 하네스 문서(
CLAUDE.md,DESIGN.md,rfp-harness.md)의 4원칙 — 명확성·원자성·추적성·검증성 — 을 자기 프로젝트에 적용할 수 있다. - 포트폴리오를 "무엇을 만들었다"가 아니라 "무엇을 얼마나 개선했다"로 다시 쓸 수 있다.
- 이 교재 전체를 관통하는 판별 축 여덟 개를 대고, 각 축이 어디서 처음 나와 어디서 반복됐는지 추적할 수 있다.
0. 이 장의 판별 축
지난 열네 장 동안 해온 그대로입니다. 새 용어를 만나면 비유를 찾기 전에 아래 축 중 어디에 꽂히는지부터 정하세요. 이번 장은 특히 축이 중요합니다. 이 장에 나오는 개념 대부분이 이미 배운 축의 재등장이기 때문입니다. 새로 외울 것이 적다는 뜻이고, 동시에 "새 것처럼 보이지만 새 것이 아님"을 알아채지 못하면 다시 처음부터 외우게 된다는 뜻입니다.
| # | 축 | 왼쪽 (0) | 오른쪽 (1) | 이 축으로 갈리는 것 |
|---|---|---|---|---|
| 1 | 어떤 권한으로 도는가 | 사용자 모드 (Ring 3·제약) | 커널 모드 (Ring 0·무제한) | 응용 프로그램 ↔ 커널·드라이버 / 일반 함수 호출 ↔ 시스템 콜 |
| 2 | 하나로 뭉쳤는가 여럿으로 쪼갰는가 | 하나의 주소 공간에 전부 | 최소만 남기고 밖으로 분리 | 모놀리식 커널 ↔ 마이크로커널 / dockerd ↔ Podman / 모놀리식 ↔ MSA |
| 3 | 누구 앞에 서는가 | 클라이언트 앞 (포워드) | 서버 앞 (리버스) | Squid·VPN ↔ Nginx·HAProxy·Cloudflare |
| 4 | 사람이 손을 대는가 | 수동 승인 (Continuous Delivery) | 무개입 자동 (Continuous Deployment) | 스테이징까지 자동 ↔ 운영까지 자동 |
| 5 | 상주하는가 | 데몬 상주 (Client-Server) | 데몬 없음 (Fork-Exec) | Docker ↔ Podman / 서버 ↔ 스크립트 |
축 2가 이 장의 중심입니다. 그리고 축 2는 이 교재 전체의 중심이기도 합니다.
⭐ 축 2는 3주차 축 5의 세 번째 재등장입니다. 3주차에서 모놀리식 ↔ MSA / 모노레포 ↔ 멀티레포 로 처음 나왔습니다. 9주차에서 스타 토폴로지 ↔ 메시 토폴로지 로 이름을 바꿔 나왔습니다. 14주차에서 한 대의 서버 ↔ 스케일아웃한 여러 대 로 다시 나왔습니다. 이번 장에서는 운영체제 커널 그 자체 안에서 발견됩니다. 배율만 다릅니다. 이 사실을 13절에서 표 하나로 정리하고 이 책을 닫습니다.
앞 장에서 이어지는 나머지 축
- 축 1(사용자 모드 ↔ 커널 모드)은 5주차 축 5(좁은 스코프 ↔ 넓은 스코프)가 CPU 하드웨어 수준으로 내려간 것입니다. 지역 변수 → 필드 → 전역 → 환경 변수로 넓어지던 그 축이, 여기서는 Ring 3 → Ring 0 이라는 물리적 특권 레벨이 됩니다.
- 축 3(누구 앞에 서는가)은 6주차 축 2(들어오는가 나가는가, Inbound ↔ Outbound)의 네트워크 판본입니다. 포워드 프록시는 아웃바운드 쪽 관문이고, 리버스 프록시는 인바운드 쪽 관문입니다.
- 축 4(사람이 손을 대는가)는 2주차 축 4(수동형 ↔ 추론형)와 하네스 엔지니어링의 Human-in-the-Loop 논의가 배포 파이프라인에서 회수되는 자리입니다.
- 축 5(상주하는가)는 3주차 축 1(디스크 ↔ 메모리)의 변형입니다. 데몬은 "메모리에 계속 떠 있는 것", Fork-Exec은 "부를 때만 올라왔다 내려가는 것"입니다.
1. 커널 — 모든 층의 맨 아래
1.1 정의
이 교재의 방식대로, 비유가 아니라 유(類)와 종차(種差) 부터 씁니다.
커널(Kernel) — [운영체제(OS)를 구성하는 소프트웨어 모듈]인데, [최하단에 위치하여 하드웨어 자원과 응용 프로그램 사이를 직접 중계한다]는 점에서 셸·유틸리티·라이브러리 같은 다른 OS 구성요소와 구별된다.
형제 개념과의 경계선을 그으면 이렇습니다.
| 구성요소 | 위치 | 하드웨어 직접 접근 | 하는 일 |
|---|---|---|---|
| 커널 (Kernel) | OS 최하단 | ⭕ 가능 (Ring 0) | 자원·프로세스·메모리·입출력을 총괄 |
| 셸 (Shell) | 커널 바깥 | ❌ 불가 | 사용자의 명령을 커널에 전달하는 껍데기 |
| 시스템 라이브러리 | 사용자 공간 | ❌ 불가 | 시스템 콜을 편하게 부르도록 감싼 것 (libc 등) |
| 응용 프로그램 | 사용자 공간 | ❌ 불가 | 실제 업무 (브라우저·에디터·우리가 짠 FastAPI) |
Kernel은 "핵(核)" 이라는 뜻입니다. 견과류의 알맹이, 씨앗의 배아를 가리키는 단어입니다. 그 바깥을 감싸는 껍데기가 Shell(껍질) 이라서, 두 단어가 짝을 이룹니다. 우리가 매일 여는 터미널이 "셸"인 이유가 여기 있습니다. 셸은 껍질이고, 그 안에 커널이 있습니다.
⚠️ 한국어 번역어 주의 — 커널을 "핵심 모듈"로만 외우면 "중요한 부분" 정도로 흐려집니다. 커널의 종차는 "중요함"이 아니라 "하드웨어에 직접 손댈 수 있는 유일한 권한 영역" 입니다. 중요한 코드는 많습니다. 권한을 가진 코드는 커널뿐입니다.
1.2 언제 메모리에 올라오는가
커널은 컴퓨터를 켜는 순간 가장 먼저 메모리(RAM)에 로드되어, 전원을 끌 때까지 내려가지 않습니다.
3주차에서 배운 디스크 ↔ 메모리 축으로 보면 이렇습니다.
| 상태 | 커널의 위치 |
|---|---|
| 전원 꺼짐 | 디스크에 파일로 존재 (/boot/vmlinuz-... 같은 이미지) |
| 부팅 중 | 부트로더가 그 파일을 읽어 메모리로 올림 |
| 실행 중 | 메모리에 상주(resident). 절대 내려가지 않음 |
| 종료 | 마지막으로 내려감 |
일반 프로그램은 필요할 때 올라왔다가 끝나면 내려갑니다. 커널은 끝나지 않습니다. 이 "상주 여부"가 이 장 판별 축 5(데몬 ↔ 데몬리스)와 같은 축입니다. 커널은 컴퓨터에서 가장 오래 사는 데몬이라고 보면 정확합니다.
1.3 4대 핵심 역할
커널이 하는 일은 네 가지로 정리됩니다. 이 네 가지는 시험 문제로도, 실무 장애 진단 순서로도 그대로 쓰입니다.
① 자원 관리 (Resource Management)
CPU 시간, RAM 메모리 공간, I/O 디바이스 제어권을 여러 프로세스에 균등하고 효율적으로 배분합니다.
- 브라우저와 VS Code와 도커 데몬이 동시에 도는데 서로 CPU를 뺏지 않는 이유
- 메모리를 다 쓴 프로세스가 다른 프로세스의 메모리를 밟지 않는 이유
② 프로세스 관리 (Process Management)
실행 중인 프로그램(프로세스 및 스레드)의 생성 · 실행 · 일시 중지 · 종료를 관리합니다.
여기서 3주차 2.3절의 4P가 회수됩니다.
| 3주차에서 배운 것 | 커널의 관점 |
|---|---|
| 프로그램 — 실행되려고 메모리에 올라온 명령어 집합 | 커널이 "적재(load)"까지 마친 상태 |
| 프로세스 — 실제로 CPU를 쓰며 실행 중인 것 | 커널이 PCB(Process Control Block) 로 관리하는 대상 |
| 스레드 — 프로세스 안에서 코드를 실행하는 일꾼 | 커널이 실제로 CPU에 올리는 최소 단위 |
| 런타임 — 실행되고 있는 시간과 환경 | 커널이 그 프로세스에 시간을 배분해준 구간 |
⭐ 스케줄링(Scheduling) — 제한된 CPU 자원을 여러 프로세스가 번갈아 사용하도록 순서를 조율하는 일.
CPU 코어는 유한한데 프로세스는 수백 개입니다. 커널은 프로세스를 아주 짧은 시간(수 밀리초) 단위로 바꿔 끼우면서 실행합니다. 사람 눈에는 동시에 도는 것처럼 보이지만 실제로는 번갈아 돕니다.
이 대목이 3주차의 동기 ↔ 비동기 축과 정확히 이어집니다.
비동기(async)가 "한 스레드 안에서 I/O 대기 시간을 다른 일에 쓰는 것"이었다면, 스케줄링은 커널이 CPU 하나를 여러 프로세스에게 시간 단위로 나눠 쓰게 하는 것입니다. 배율만 다르고 원리는 같습니다. "기다리는 시간을 놀리지 않는다."
③ 메모리 관리 (Memory Management)
- 가상 메모리(Virtual Memory) 기법으로 각 프로그램이 독립된 메모리 공간을 갖도록 할당합니다.
- 프로세스 간 메모리 침범을 방지합니다.
- RAM 용량이 부족하면 디스크의 스왑(Swap) 영역을 활용합니다.
여기가 디스크 ↔ 메모리 축이 무너지는 유일한 지점이라 중요합니다.
⚠️ 스왑(Swap)은 "디스크를 메모리인 척 쓰는 것"입니다. 3주차에서 디스크와 메모리를 칼같이 갈랐는데, 커널은 그 경계를 일부러 흐립니다. RAM이 모자라면 당장 안 쓰는 메모리 페이지를 디스크로 내려보내고, 필요해지면 다시 올립니다. 그래서 스왑이 많이 일어나는 서버는 갑자기 아주 느려집니다. 디스크는 메모리보다 느리기 때문입니다. 저사양 EC2에서 LLM을 돌릴 때 "멈춘 것 같다"는 증상의 상당수가 이것입니다.
가상 메모리는 3주차의 추상 ↔ 구상 축입니다.
프로세스가 보는 주소(0x00400000)는 추상이고, 실제 RAM 칩 위의 물리 주소는 구상입니다.
그 사이를 커널과 MMU가 번역합니다. 포트와 어댑터를 CPU가 하드웨어로 구현한 셈입니다.
④ 입출력 및 장치 제어 (I/O & Device Management)
키보드, 마우스, 모니터, 디스크, 네트워크 카드 등 하드웨어 장치 드라이버(Device Driver) 와 통신하여 데이터를 안전하게 입출력합니다.
우리가 open("a.txt") 한 줄을 쓰면, 그 아래에서 실제로 일어나는 일은 이렇습니다.
파이썬 open()
→ libc의 open()
→ 시스템 콜 open() ← 여기서 Ring 3 → Ring 0 전환
→ 커널 VFS 계층
→ 파일시스템 드라이버(ext4 등)
→ 블록 디바이스 드라이버
→ 실제 SSD
우리가 짠 코드는 저 사다리의 맨 위 한 칸입니다. 나머지 여섯 칸이 커널입니다.
1.4 동작 원리 — CPU 실행 모드
하드웨어 자원의 무단 접근과 시스템 붕괴를 막기 위해, CPU 자체가 권한을 분리해서 작동합니다. 이것은 소프트웨어 약속이 아니라 CPU 하드웨어에 새겨진 물리적 제약입니다.
| 사용자 모드 (User Mode) | 커널 모드 (Kernel Mode) | |
|---|---|---|
| 별칭 | Ring 3 | Ring 0 |
| 무엇이 도는가 | 일반 응용 프로그램 (웹 브라우저, 에디터, 우리 FastAPI) | 커널 코드 및 디바이스 드라이버 |
| 권한 | 제약된 권한 | 무제한 권한 |
| 하드웨어 직접 접근 | ❌ 불가 | ⭕ 가능 |
| 실행 가능한 CPU 명령 | 일부만 | 모든 명령 |
| 접근 가능한 메모리 | 자기 몫만 | 전체 메모리 |
Ring이라는 이름은 x86 CPU가 권한 레벨을 동심원(Ring 0 ~ Ring 3) 으로 설계한 데서 왔습니다. 안쪽 원일수록 특권이 큽니다. Ring 1·2는 현대 OS에서 거의 쓰지 않아, 실질적으로 0과 3만 씁니다.
⭐ 왜 이렇게 갈라놓았는가 응용 프로그램이 하드웨어를 직접 만질 수 있다면, 버그 하나로 디스크 전체가 날아가거나 다른 프로그램의 메모리를 읽어갈 수 있습니다. "할 수 있게 해두고 조심하자"가 아니라 "애초에 불가능하게 만든다" 는 것이 이 설계의 요점입니다.
이 사고방식은 이미 두 번 나왔습니다. 하네스 엔지니어링 5단계 — "에이전트가 실수하면, 그 실수가 영구적으로 불가능하도록 환경을 고쳐라." 9주차 축 5 — "규칙을 사람이 지키는가, 도구가 강제하는가." CPU의 링 구조는 그 원칙의 하드웨어 판본입니다.
1.5 시스템 콜 — 경계를 넘는 유일한 문
응용 프로그램은 Ring 3에 갇혀 있습니다. 그런데 파일도 읽어야 하고 네트워크로 데이터도 보내야 합니다. 어떻게 하느냐 — 정해진 문으로만 넘어갑니다.
시스템 콜(System Call) — [응용 프로그램이 커널의 기능을 요청하는 방법]인데, [CPU를 사용자 모드에서 커널 모드로 전환시키는 표준 인터페이스 API]라는 점에서 일반 함수 호출과 구별된다.
| 일반 함수 호출 | 시스템 콜 | |
|---|---|---|
| 넘어가는 경계 | 없음 (같은 프로세스, 같은 링) | Ring 3 → Ring 0 |
| 비용 | 매우 쌈 | 상대적으로 비쌈 (모드 전환 오버헤드) |
| 누가 실행 | 내 프로세스 | 커널 |
| 예 | len(s), my_func() | open, read, write, socket, mmap, fork |
대표적인 시스템 콜과 우리가 이미 써본 코드의 대응입니다.
| 하고 싶은 일 | 시스템 콜 | 우리가 쓴 코드 |
|---|---|---|
| 파일 읽기·쓰기 | open / read / write | pd.read_csv(...), open(path) |
| 네트워크 송수신 | socket / bind / listen / accept | uvicorn 이 8000번 포트를 여는 순간 |
| 메모리 할당 | brk / mmap | 파이썬 리스트를 키울 때마다 (내부적으로) |
| 프로세스 생성 | fork / execve | subprocess.run(...), 터미널에서 명령 실행 |
| 프로세스 종료 | exit | Ctrl+C, sys.exit() |
💡 이것이 왜 실무 감각인가 "이 코드가 느리다"의 원인이 계산인지 I/O인지 판단할 때, 시스템 콜을 몇 번 부르는가가 기준이 됩니다. 7주차에서 배운 "CPU-bound는
def, I/O-bound만async def" 의 근거가 여기 있습니다.async가 도움이 되는 구간은 정확히 시스템 콜을 부르고 커널의 응답을 기다리는 구간입니다. 계산만 하는 코드는 시스템 콜을 안 부르므로async를 붙여도 아무 일도 일어나지 않습니다.
1.6 커널 구조 분류 — 모놀리식 커널 ↔ 마이크로커널
이 장에서 가장 중요한 절입니다. 3주차의 축이 여기서 그대로 발견됩니다.
모놀리식 커널 (Monolithic Kernel)
프로세스 관리, 메모리 관리, 장치 드라이버, 파일 시스템 등 모든 서비스가 하나의 커널 주소 공간에서 동작합니다.
- 장점 — 모듈 간 통신 오버헤드가 적어 성능 및 실행 속도가 빠릅니다. 함수 호출 한 번이면 끝입니다.
- 단점 — 특정 드라이버나 모듈의 에러가 전체 시스템 멈춤(Crash) 으로 이어질 수 있습니다.
- 예시 — Linux, macOS, FreeBSD
마이크로커널 (Microkernel)
메모리 관리, IPC(프로세스 간 통신), 기본 스케줄링 등 최저한의 기능만 커널에 남기고, 파일 시스템 및 드라이버는 사용자 공간(User Space)의 서비스 프로세스로 분리합니다.
- 장점 — 모듈화가 뛰어나고, 특정 서비스가 다운되어도 전체 시스템이 안전합니다. 보안성·안정성이 높습니다.
- 단점 — 사용자 공간과 커널 공간 사이의 메시지 전달(IPC) 비용이 발생하여 상대적으로 속도가 느립니다.
- 예시 — QNX, L4, Minix
비교표
| 비교 항목 | 모놀리식 커널 | 마이크로커널 |
|---|---|---|
| 무엇이 커널 안에 있나 | 드라이버·파일시스템까지 전부 | 메모리·IPC·스케줄링 최소한만 |
| 주소 공간 | 하나로 뭉침 | 커널 + 여러 사용자 공간 서비스 |
| 모듈 간 통신 | 함수 호출 (쌈) | IPC 메시지 (비쌈) |
| 속도 | 빠름 | 상대적으로 느림 |
| 드라이버 하나가 죽으면 | 전체 크래시 가능 | 그 서비스만 죽고 재시작 가능 |
| 안정성·보안성 | 낮음 | 높음 |
| 대표 예 | Linux, macOS, FreeBSD | QNX, L4, Minix |
| 주로 쓰이는 곳 | 서버·데스크톱·클라우드 | 자동차·항공·의료기기 등 실패가 곧 사고인 곳 |
⭐ 이 표를 다시 읽어보세요. 어디서 본 표입니까?
1.7 ⭐ 같은 축의 네 번째 등장
3주차 6절의 모놀리식 ↔ MSA 표와 나란히 놓으면 이렇게 됩니다.
| 하나로 뭉침 (0) | 여럿으로 쪼갬 (1) | 쪼갠 대가 | |
|---|---|---|---|
| 3주차 · 애플리케이션 | 모놀리식 | MSA (마이크로서비스) | 네트워크 호출 비용·분산 트랜잭션 |
| 3주차 · 저장소 | 모노레포 | 멀티레포 | 버전 정합·CI 중복 |
| 9주차 · 토폴로지 | 스타 (허브 집중) | 메시 (직접 연결) | 연결 수 폭증 / 허브는 SPOF |
| 6주차 · 코드 내부 | 퍼사드 (창구 하나) | 책임 연쇄 (체인) | 흐름 추적이 어려워짐 |
| 15주차 · OS 커널 | 모놀리식 커널 | 마이크로커널 | IPC 메시지 비용 |
| 15주차 · 컨테이너 엔진 | Docker (dockerd 데몬) | Podman (Fork-Exec) | 생태계·GUI 도구가 적음 |
여섯 줄이 전부 같은 문장입니다.
하나로 뭉치면 빠르지만 같이 죽고, 여럿으로 쪼개면 안전하지만 통신 비용을 낸다.
이 문장이 이 교재 전체에서 가장 자주 재활용되는 문장입니다. 그리고 이 문장이 통하는 배율의 범위가 — CPU 안의 커널부터 서버 수십 대의 MSA까지 — 이 장이 증명하는 것입니다.
⚠️ "마이크로커널이 더 발전한 것"이라고 외우지 마세요. 3주차에서 "MSA가 모놀리식보다 발전한 것" 이 틀렸다고 했던 것과 정확히 같은 이유입니다. 세상에서 가장 많이 쓰이는 커널(Linux)은 모놀리식입니다. 마이크로커널은 자동차 제어, 항공 전자장비처럼 한 번의 크래시가 곧 사고인 영역에서 선택됩니다. 축의 왼쪽과 오른쪽은 우열이 아니라 트레이드오프입니다.
2. 컨테이너 엔진 — Docker와 Podman
2.1 둘은 무엇의 형제인가
먼저 상위 범주부터 잡습니다.
Docker / Podman — [OCI(Open Container Initiative) 표준을 준수하는 컨테이너 런타임 도구]인데, [내부 아키텍처(데몬 유무)와 보안 설계(Rootless 여부)]에서 서로 구별된다.
여기서 중요한 것은 둘 다 같은 표준을 따른다는 점입니다.
- 같은 이미지 포맷을 읽습니다. Docker Hub에서 받은 이미지를 Podman이 그대로 실행합니다.
- CLI 명령어가 100% 호환됩니다.
alias docker=podman한 줄이면 대부분의 스크립트가 그대로 돕니다.
같은 계약(추상)을 두 개의 구현(구상)이 지키고 있는 것 — 5·6주차의 포트 ↔ 어댑터 구조입니다. OCI 표준이 포트이고, Docker와 Podman이 어댑터입니다. 그래서 갈아끼울 수 있습니다. 이것이 표준이 존재하는 이유 전부입니다.
2.2 주요 차이점 상세 비교
| 항목 | Docker (도커) | Podman (팟맨) |
|---|---|---|
| 아키텍처 | Client-Server (Daemon 기반)<br>항상 백그라운드에서 dockerd 실행 | Daemonless (Fork-Exec 모델)<br>단일 실행 파일 기반 모듈식 구조 |
| 권한 모델 | Root 권한 중심<br>(Rootless 지원하나 추가 설정 필요) | Rootless 기본 탑재<br>일반 사용자 계정으로 완벽 동작 |
| 단일 장애점 (SPOF) | 데몬(dockerd) 장애 시 실행 중인 모든 컨테이너가 영향을 받음 | 데몬이 없으므로 개별 컨테이너 프로세스가 독립적으로 동작 |
| Pod 개념 | 기본 단위가 단일 컨테이너<br>(다중 컨테이너는 Docker Compose 사용) | 쿠버네티스와 동일한 Pod 단위 생성 지원 |
| Systemd 통합 | 컨테이너 프로세스를 Systemd가 직접 제어하기 어려움 | Systemd 유닛 파일 생성으로 완벽 제어 가능 |
| CLI 명령어 | docker run ... | podman run ... (100% 호환 alias 가능) |
| 생태계·GUI | Docker Desktop, 방대한 커뮤니티 자료 | 상대적으로 적음 |
2.3 핵심 차이 ① 데몬 존재 여부 (Daemon vs Daemonless)
Docker
클라이언트 CLI가 REST API를 통해 백그라운드에 구동 중인 대형 데몬 프로세스(dockerd)에 명령을 전달하고,
데몬이 컨테이너를 생성·제어합니다.
[docker CLI] --REST--> [dockerd (root, 상주)] --> [containerd] --> [컨테이너들]
Podman
podman 명령을 실행하면 그 프로세스 자신이 컨테이너의 부모 프로세스가 되어 자식 컨테이너를 직접 실행합니다.
[podman 프로세스] --fork/exec--> [컨테이너]
[podman 프로세스] --fork/exec--> [컨테이너] ← 서로 독립
중앙 데몬이 없으므로 메모리 리소스 소비가 적고, 데몬이 죽어서 생기는 대형 장애를 방지합니다.
⭐ 이것이 바로 9주차에서 배운 단일 장애점(SPOF)입니다. 스타 토폴로지의 허브가 죽으면 전체가 멈추듯,
dockerd가 죽으면 그 위의 컨테이너가 전부 영향을 받습니다. Podman은 허브를 없애고 각 컨테이너를 자기 부모에게만 매다는 구조를 택했습니다.그리고 이것은 1.6절의 모놀리식 커널 ↔ 마이크로커널과 정확히 같은 선택입니다. Docker = 모놀리식, Podman = 마이크로. 같은 축, 다른 배율.
2.4 핵심 차이 ② 보안 모드 (Root vs Rootless)
Docker — 기본적으로 시스템 루트 권한(root)으로 데몬이 실행됩니다.
따라서 컨테이너 탈옥(Container Escape) 공격이 성공하면 호스트 OS 전체 권한이 탈취될 위험이 있습니다.
Podman — 사용자 네임스페이스(User Namespaces) 기술을 사용해, 일반 사용자(Non-root)가 root 권한 없이 컨테이너를 실행하도록 처음부터 설계되었습니다. 보안 요구사항이 높은 기업 환경에 적합합니다.
여기서 1.4절의 Ring 0 ↔ Ring 3 축이 컨테이너 층에서 다시 나옵니다.
| 층 | 특권 쪽 | 제약 쪽 | 넘어가는 문 |
|---|---|---|---|
| CPU | Ring 0 (커널 모드) | Ring 3 (사용자 모드) | 시스템 콜 |
| 리눅스 계정 | root | 일반 사용자 | sudo |
| 컨테이너 | Docker 기본 (root 데몬) | Podman 기본 (rootless) | 사용자 네임스페이스 매핑 |
⚠️ "Rootless면 안전하다"로 끝내지 마세요. Rootless는 탈옥했을 때의 피해 범위를 줄이는 장치입니다. 탈옥 자체를 막는 것이 아닙니다. 7주차·13주차에서 배운 최소 권한 원칙(IAM), 이 장 6절의 IAM 역할과 같은 사고방식입니다. "뚫리지 않게 한다" 가 아니라 "뚫려도 가져갈 게 없게 한다."
2.5 핵심 차이 ③ 쿠버네티스 친화성
Docker — 단일 컨테이너 단위 제어에 초점이 맞춰져 있습니다. 여러 컨테이너를 묶으려면 Docker Compose를 씁니다.
Podman — 하나의 IP와 스토리지 공간을 공유하는 컨테이너 집합인 Pod 개념을 CLI 수준에서 내장 제공합니다.
# 파드를 만들고 그 안에 컨테이너를 여러 개 넣는다
podman pod create --name myapp -p 8000:8000
podman run -d --pod myapp --name api my-fastapi:latest
podman run -d --pod myapp --name cache redis:7
# 지금 구성을 쿠버네티스 매니페스트(YAML)로 즉시 출력
podman generate kube myapp > myapp-pod.yaml
podman generate kube 는 내 손으로 만든 컨테이너 구성을 쿠버네티스용 YAML로 바로 뽑아주는 명령입니다.
로컬에서 돌려본 것을 그대로 클러스터로 옮길 수 있다는 뜻입니다.
💡 Pod가 왜 필요한가 — Pod는 "같은 IP와 스토리지를 공유하며 반드시 함께 살고 함께 죽어야 하는 컨테이너 묶음"입니다. 5주차에서 배운 DDD의 애그리거트(Aggregate) — "함께 다뤄져야 하는 엔티티들의 묶음" — 과 같은 발상입니다. 경계를 어디에 그을 것인가라는 질문이, 도메인 모델에서든 컨테이너 배치에서든 똑같이 반복됩니다.
2.6 Systemd 통합
Podman은 컨테이너를 systemd 유닛 파일로 만들어, 리눅스의 표준 서비스 관리 체계에 그대로 얹을 수 있습니다.
# 실행 중인 컨테이너를 systemd 유닛 파일로 생성
podman generate systemd --name api --files --new
# 사용자 단위로 등록하고 부팅 시 자동 시작
systemctl --user enable --now container-api.service
이렇게 하면 서버가 재부팅돼도 컨테이너가 자동으로 다시 뜹니다.
Docker에서는 restart: always 옵션으로 데몬에게 맡기지만, Podman에서는 OS가 직접 관리합니다.
"데몬을 없앤 대신 OS의 기본 기능을 쓴다" — 이것이 Podman의 일관된 설계 철학입니다.
2.7 상황별 선택 가이드
| 이럴 때 | 고르는 것 | 이유 |
|---|---|---|
| 학습 중이고 자료를 많이 찾아야 함 | Docker | 커뮤니티 자료·튜토리얼이 압도적으로 많음 |
| GUI(Docker Desktop)로 상태를 눈으로 보고 싶음 | Docker | Podman Desktop도 있으나 성숙도 차이 |
| Compose 파일로 이미 다 짜여 있음 | Docker | 전환 비용 대비 실익 적음 |
| 회사 보안 정책상 root 사용이 금지됨 | Podman | Rootless가 기본 |
| 쿠버네티스로 갈 예정 | Podman | Pod 개념 동일, generate kube |
| 데몬 장애로 전체가 멎는 것을 피해야 함 | Podman | 구조적으로 SPOF 없음 |
| systemd로 서비스를 통합 관리 중 | Podman | 유닛 파일 생성 지원 |
요약 — 생태계·자료·GUI가 우선이면 Docker / Root 금지 환경·쿠버네티스 연계가 우선이면 Podman.
2.8 컨테이너는 왜 가벼운가 — 커널과 이어 붙이기
8주차에서 이미지(정적) ↔ 컨테이너(동적) 축을 배웠고, 그때 "컨테이너는 VM보다 가볍다"는 사실만 확인하고 넘어갔습니다. 이제 커널을 배웠으니 왜 가벼운지 말할 수 있습니다.
| 가상 머신 (VM) | 컨테이너 | |
|---|---|---|
| 포함하는 것 | OS 통째로 (커널 포함) | 앱 + 필요한 라이브러리만 |
| 무게 | GB, 분 단위 부팅 | MB, 초 단위 기동 |
| 격리 강도 | 강함 (커널까지 분리) | 상대적으로 약함 |
| 커널 | 자기 커널을 따로 가짐 | 호스트의 커널을 공유 |
⭐ 한 줄로 — 컨테이너가 가벼운 이유는 커널을 새로 켜지 않기 때문입니다. 컨테이너 안의 프로세스는 결국 호스트 커널에 시스템 콜을 겁니다. 네임스페이스와 cgroup이라는 커널 기능으로 서로 안 보이게 가려놓았을 뿐, CPU 위에서는 그냥 호스트의 프로세스입니다.
그래서 컨테이너 격리는 VM 격리보다 약합니다. 커널을 공유하니, 커널에 구멍이 나면 다 뚫립니다. Podman이 Rootless를 기본으로 삼은 이유도 여기 있습니다.
3. 웹 프록시 — 경계에 서는 대리인
3.1 정의
웹 프록시(Web Proxy) — [클라이언트와 목적지 서버 사이에서 통신을 중계하는 서버]인데, [요청을 자기가 대신 받아 대신 보내고, 응답도 자기가 받아 대신 돌려준다]는 점에서 단순히 패킷을 흘려보내는 라우터·스위치와 구별된다.
Proxy는 라틴어 procuratio(대리, 위임)에서 온 말로, 대리인·위임장이라는 뜻입니다. 주주총회에서 대신 표를 행사하는 "위임장"도 proxy입니다. 본인 대신 나서는 자 — 이 뜻만 잡으면 포워드든 리버스든 헷갈릴 일이 없습니다.
동작은 이렇습니다.
[클라이언트] → [프록시] → [웹 서버]
← ←
클라이언트가 요청한 웹 자원(HTML, 이미지 등)을 프록시가 받아 서버에 대신 요청하고, 응답받은 데이터를 클라이언트에 다시 전달합니다.
⭐ 이 이름을 어디서 봤는지 기억하세요. 3주차 5.3절 GoF 23패턴의 구조 패턴 목록에 프록시(Proxy) — 접근을 제어하는 대리자 가 있었습니다. 그때는 객체 하나를 감싸는 코드 패턴이었습니다. 지금은 서버 한 대가 서버 무리를 감싸는 네트워크 장비입니다. 이름이 같은 것이 우연이 아닙니다. 같은 구조가 배율만 바뀐 것입니다.
3.2 핵심 역할 네 가지
① 보안 및 익명성 (Anonymity)
클라이언트의 실제 IP 주소를 은닉하고 프록시 서버의 IP로 통신하여, 외부 위협으로부터 내부망을 보호합니다. 서버 쪽 로그에는 프록시의 IP만 남습니다.
② 캐싱(Caching)을 통한 속도 향상
자주 요청되는 정적 파일(이미지, CSS, 스크립트)을 프록시에 저장(Cache)해 두고, 동일한 요청이 오면 웹 서버를 거치지 않고 즉시 응답합니다. 트래픽 비용과 지연 시간을 함께 줄입니다.
💡 캐시는 3주차의 디스크 ↔ 메모리 축이 네트워크 거리로 확장된 것입니다. "느린 곳(원 서버)의 결과를 빠른 곳(프록시)에 잠깐 둔다." RAM이 디스크의 캐시이고, CPU 캐시가 RAM의 캐시이고, CDN이 원 서버의 캐시입니다. 같은 문장, 다른 배율.
③ 트래픽 제어 및 필터링
기업이나 학교 네트워크에서 유해 사이트·게임·SNS 등 특정 도메인/IP 접속을 차단하거나, 사용자 이용 기록(Log)을 감시합니다. 회사에서 특정 사이트가 안 열리는 이유의 대부분이 이것입니다.
④ 우회 접속
국가나 지역별로 접근이 제한된 사이트에 접근할 때, 해당 지역의 프록시 서버를 거쳐 우회합니다.
3.3 ⭐ 포워드 프록시 ↔ 리버스 프록시
이 장의 판별 축 3입니다. "누구 앞에 서 있는가" 하나만 정하면 나머지가 전부 따라옵니다.
| 비교 항목 | 포워드 프록시 (Forward Proxy) | 리버스 프록시 (Reverse Proxy) |
|---|---|---|
| 위치 | 클라이언트(내부망) 앞에 위치 | 웹 서버(운영망) 앞에 위치 |
| 중계 방향 | 클라이언트의 요청을 외부 인터넷으로 전달 | 외부의 요청을 내부 웹 서버군으로 전달 |
| 주요 대상 | 클라이언트 보호 및 통제 | 웹 서버 보호 및 로드 밸런싱 |
| 주요 목적 | · 클라이언트 IP 은닉<br>· 사내 인터넷 접근 통제 및 캐싱<br>· 차단 사이트 우회 접속 | · 서버 IP 은닉 및 보안 강화<br>· 로드 밸런싱(트래픽 분산)<br>· SSL/TLS 종료(암호화 복호화 처리) |
| 누구를 숨기나 | 클라이언트를 숨김 | 서버를 숨김 |
| 누가 설치하나 | 회사 전산팀 / 개인(VPN) | 서비스 운영자 (우리) |
| 대표 예시 | Squid, Charles, Fiddler, VPN 프록시 | Nginx, HAProxy, Apache Traffic Server, Cloudflare |
그림으로 보면 대칭입니다.
[포워드 프록시]
사내 PC들 → ●프록시● ────인터넷────→ 어떤 웹서버든
(클라이언트를 대변)
[리버스 프록시]
아무 브라우저 ────인터넷────→ ●프록시● → 내부 서버 A / B / C
(서버를 대변)
⚠️ "리버스는 반대 방향으로 흐른다"는 뜻이 아닙니다. 요청은 양쪽 다 클라이언트 → 서버로 흐릅니다. 거꾸로인 것은 "누구를 대리하느냐" 입니다. 포워드는 요청하는 쪽을, 리버스는 응답하는 쪽을 대리합니다.
3.4 우리 시스템 안에 이미 있었던 리버스 프록시
이 개념이 새롭게 느껴지겠지만, 우리는 이미 여러 번 리버스 프록시를 세웠습니다.
| 언제 | 무엇을 했나 | 그것의 정체 |
|---|---|---|
| EC2에 도커 → 깃 → nginx 순서로 설치 | 80번 포트를 열고 nginx 설정 | 리버스 프록시 |
| Certbot으로 Let's Encrypt 인증서 발급 | HTTPS 종료를 nginx가 담당 | SSL/TLS 종료 |
| Cloudflare에 도메인을 물리고 주황색 구름을 켬 | 모든 요청이 Cloudflare를 먼저 통과 | 리버스 프록시 + CDN 캐시 |
| Cloudflare Tunnel로 로컬 서비스를 외부에 노출 | 인바운드 포트를 하나도 열지 않고 노출 | 리버스 프록시(아웃바운드 연결형) |
⭐ 왜 그때는 "리버스 프록시"라고 부르지 않았는가 그때는 하는 일을 배웠고, 지금은 속하는 범주를 배웁니다. 이 교재의 1주차 첫 원칙 — "먼저 소속 범주부터, 그다음에 형제 개념과의 경계선" — 을 마지막 장에서 다시 실행하는 셈입니다. nginx는 웹 서버이기도 하지만, 우리 구조에서 맡은 역할은 리버스 프록시였습니다.
3.5 요청 한 건이 지나는 길 (장애 진단 순서)
리버스 프록시의 자리를 정확히 알면, 장애가 났을 때 어디부터 끊어볼지가 정해집니다.
브라우저
└ DNS 조회 → 도메인 → IP (가비아 · Cloudflare)
└ TLS 핸드셰이크 → 인증서 확인 (Certbot / Cloudflare)
└ :443 도착 → nginx (리버스 프록시) ← SSL 종료가 여기서 끝남
└ 내부 :8000 → uvicorn (FastAPI 컨테이너)
└ :5432 → PostgreSQL (RDS / Neon)
장애가 나면 이 순서대로 확인합니다.
nslookup api.example.com # 1. DNS가 맞는 IP를 주는가
curl -I https://api.example.com # 2. TLS·nginx까지 살아 있는가
docker ps # 3. 앱 컨테이너가 떠 있는가
docker logs -f api # 4. 앱이 에러를 뱉는가
psql "$DATABASE_URL" -c 'select 1' # 5. DB까지 닿는가
| 증상 | 가장 흔한 원인 |
|---|---|
| 도메인이 아예 안 열림 | DNS 전파 미완료 / A·CNAME 레코드 오타 |
ERR_CERT_... | 인증서 만료(90일) 또는 Certbot 자동 갱신 실패 |
| 502 Bad Gateway | nginx는 살아 있는데 뒤의 앱이 죽음 |
| 504 Gateway Timeout | 앱이 살아는 있는데 응답이 너무 느림 |
| 서버 안에서는 되는데 밖에서 안 됨 | EC2 보안 그룹 인바운드에 HTTP(80)·HTTPS(443) 미허용 |
💡 502와 504의 차이를 외우는 법 — 둘 다 "게이트웨이(=리버스 프록시)"가 내는 에러입니다. 502는 뒤가 없다(Bad Gateway), 504는 뒤가 늦다(Timeout). 프록시라는 개념을 모르면 이 두 숫자가 그냥 암기 대상이지만, 알고 나면 자연스러운 결과입니다.
4. CI/CD — 바깥에서 도는 루프
4.1 정의
CI/CD — [소프트웨어 개발 라이프사이클(SDLC)을 자동화하는 방법론이자 문화]인데, [소스 코드 변경 → 테스트 → 빌드 → 운영 배포까지의 전 과정을 자동화한다]는 점에서 코드 품질만 다루는 리뷰·린팅이나, 배포만 다루는 배포 스크립트와 구별된다.
이름은 두 덩어리입니다.
- CI — Continuous Integration (지속적 통합)
- CD — Continuous Delivery (지속적 제공) / Continuous Deployment (지속적 배포)
CD가 두 가지라는 것이 이 절의 핵심이고, 시험에도 면접에도 가장 자주 나오는 지점입니다.
4.2 CI — 지속적 통합
개발자들이 변경한 소스 코드를 주기적으로 중앙 저장소(예: Git)에 통합하고 자동으로 검증하는 프로세스입니다.
주요 단계
- 개발자가 기능을 구현하고 저장소에
Push또는Pull Request생성 - CI 서버가 변경 사항을 감지하여 소스 코드를 자동 빌드(Build)
- 자동화 테스트(Unit Test, Integration Test) 를 수행하여 오류 검출
주요 목적
- 코드 변경에 따른 충돌(Merge Conflict)을 초기에 발견
- 버그 수정 비용 절감 및 상시 작동 가능한 코드 베이스 유지
⭐ "지속적 통합"에서 통합의 대상은 코드가 아니라 사람입니다. 혼자 짜면 CI가 필요 없습니다. 여러 명이 각자 브랜치에서 일주일씩 작업한 뒤 한꺼번에 합치면 충돌이 산더미가 됩니다. 자주 합치면 한 번에 합칠 양이 적어집니다. CI는 기술이기 전에 "작게 자주 합친다"는 합의입니다. 하네스 엔지니어링에서 배운 "세션을 작고 명확한 단위로 쪼개라" 와 같은 원리입니다.
4.3 ⭐ CD 두 종류 — 사람이 손을 대는가
CI 단계를 거친 소프트웨어를 실제 고객이 사용하는 운영 환경까지 전달하는 과정입니다. 그런데 "어디까지 자동인가"에 따라 두 이름으로 갈립니다.
| 지속적 제공 (Continuous Delivery) | 지속적 배포 (Continuous Deployment) | |
|---|---|---|
| 자동화 범위 | 배포 가능한 상태까지 자동 | 운영 환경 반영까지 자동 |
| 마지막 단계 | 담당자의 수동 승인(Manual Approval) | 사람 개입 없음 |
| 결과물이 놓이는 곳 | 스테이징 환경에 릴리스 후보(Release Candidate)로 대기 | 곧바로 프로덕션 |
| 적합한 곳 | 규제 산업, 금융, 공공, 릴리스 노트가 필요한 제품 | SaaS, 내부 도구, 실험이 잦은 서비스 |
| 위험 | 낮음 (사람이 한 번 본다) | 높음 (테스트가 곧 안전장치) |
[공통] push → 빌드 → 테스트 → 이미지 생성 → 스테이징 배포
│
Continuous Delivery ─────────────────────────┤→ ⏸ 사람이 승인 → 운영 배포
Continuous Deployment ───────────────────────┴──────────────→ 운영 배포 (바로)
⚠️ 약자가 같아서 생기는 최대의 혼동
CD하나만 보고 어느 쪽인지 알 수 없습니다. "사람이 승인 버튼을 누르는가?" 를 물어야 합니다.
- 누른다 → Delivery (제공)
- 안 누른다 → Deployment (배포)
이 축은 하네스 엔지니어링의 Human-in-the-Loop 논의와 같은 축입니다. 자동화를 어디까지 밀고, 어디에 사람을 남겨둘 것인가. 답은 도메인의 위험도가 정합니다. 금융 리포트를 자동 발행하는 이 기수의 팀 프로젝트라면 Delivery 쪽이 맞습니다.
4.4 도입의 주요 효과
| 효과 | 내용 |
|---|---|
| 출시 주기 단축 (Time to Market) | 수작업 배포 절차를 없애 신규 기능·수정 사항을 빠르게 서비스에 반영 |
| 휴먼 에러(Human Error) 방지 | 반복적인 빌드·배포 작업을 스크립트화하여 사람의 실수 감소 |
| 코드 품질 관리 강화 | 자동화된 테스트가 필수로 동작하여 회귀 버그(Regression) 방지 |
💡 회귀 버그(Regression) — 예전에 고쳐놨던 것이 새 변경 때문에 다시 망가지는 현상입니다. 사람은 자기가 안 건드린 곳을 다시 테스트하지 않습니다. 기계는 매번 전부 돌립니다. CI가 실제로 막아주는 것의 대부분이 이것입니다.
4.5 대표 도구
| 분류 | 도구 |
|---|---|
| CI/CD 파이프라인 관리 | Jenkins, GitHub Actions, GitLab CI/CD, CircleCI |
| 클라우드 / 쿠버네티스 배포 | AWS CodePipeline, ArgoCD, FluxCD |
| 도구 | 특징 | 어울리는 상황 |
|---|---|---|
| Jenkins | 자체 호스팅, 플러그인 방대, 역사가 김 | 사내망·온프레미스에 직접 세울 때 |
| GitHub Actions | 저장소에 .github/workflows/*.yml 만 두면 끝 | 이미 GitHub를 쓰는 대부분의 경우 |
| GitLab CI/CD | GitLab에 내장, .gitlab-ci.yml | GitLab 기반 조직 |
| CircleCI | SaaS, 설정이 간결 | 빠르게 붙이고 싶을 때 |
| ArgoCD / FluxCD | GitOps — Git 저장소의 상태를 클러스터에 계속 맞춤 | 쿠버네티스 운영 |
| AWS CodePipeline | AWS 서비스들과 바로 연결 | AWS 안에서 끝낼 때 |
💡 GitOps — ArgoCD·FluxCD의 방식으로, "Git 저장소에 적힌 것이 곧 운영 환경의 진실" 로 삼는 접근입니다. 사람이 클러스터에 명령을 넣는 게 아니라, Git을 고치면 도구가 알아서 클러스터를 그 상태로 맞춥니다. 6주차의 제어의 역전(IoC) 이 인프라 운영에서 반복되는 자리입니다. 내가 배포하는 게 아니라, 도구가 나를 대신해 상태를 수렴시킵니다.
4.6 파이프라인과 롤백
push → 테스트 → 빌드 → 이미지 푸시 → 배포 → 헬스체크 → (실패 시) 롤백
실무 감각 세 가지입니다.
- 롤백 가능한 배포가 무중단보다 먼저입니다. 화려한 무중단 배포보다, 3분 안에 되돌릴 수 있는 것이 낫습니다.
- 이전 이미지 태그를 지우지 마세요.
:latest만 쓰면 되돌릴 대상 자체가 없습니다.myapp:2026-08-25-a1b2c3d처럼 커밋 해시가 들어간 태그를 쓰면 언제든 그 시점으로 돌아갑니다. - DB 마이그레이션은 코드 롤백과 분리해서 계획하세요. 컬럼 삭제 같은 되돌릴 수 없는 변경은 코드 배포 두 단계 뒤에 실행합니다. 코드는 되돌아가는데 스키마는 안 되돌아가면 그때부터 진짜 사고입니다.
4.7 ⭐ CI/CD는 하네스의 외부 루프다
이 절이 이 장에서 가장 중요한 연결입니다.
하네스 엔지니어링에서 배운 마틴 파울러의 두 축을 다시 꺼냅니다.
| 가이드(Guide) — 행동 전 방향 설정 | 센서(Sensor) — 행동 후 결과 체크 |
|---|---|
| 피드포워드 (Feedforward) | 피드백 (Feedback) |
| Principles & Rules (코딩 표준, 아키텍처 원칙) | Logs & Browser (실행 결과·런타임 에러) |
| CFRs (보안·성능·가용성 요구사항) | Review Agents (다른 AI 리뷰, 정적 분석) |
| How-tos & Ref Docs | Tests (유닛/통합 테스트) |
그리고 "에이전트 밖에 또 하나의 루프" 라는 원칙 — 모델의 "완료" 선언 대신 빌드·테스트·벤치마크가 종료를 판정한다.
빌드 → 테스트 → 벤치마크 → 재시도 게이트 → 새 세션 시작
이 문장을 그대로 도구로 옮긴 것이 CI 파이프라인입니다.
| 하네스 이론의 용어 | CI/CD에서의 이름 |
|---|---|
| 가이드 (Guide) | CLAUDE.md, AGENTS.md, 린터 설정, 코딩 규칙 |
| 센서 (Sensor) | 자동 빌드 · 자동 테스트 · 정적 분석 · 헬스체크 |
| 외부 루프 (Outer Loop) | CI 파이프라인 전체 |
| 종료 판정자 | "녹색 체크가 떠야 머지된다"는 규칙 |
| 재시도 게이트 | 실패 시 자동 롤백 |
⭐ 결론 — 우리는 AI 에이전트를 다루기 위해 "가이드와 센서"를 배웠습니다. 그런데 그 구조는 AI가 등장하기 20년 전부터 CI/CD라는 이름으로 존재하고 있었습니다. 새로운 것은 도구가 아니라 적용 대상입니다. 사람 개발자에게 걸던 안전망을 이제 에이전트에게 겁니다.
9주차 축 5를 다시 씁니다 — "규칙을 사람이 지키는가, 도구가 강제하는가." CI는 그 축의 오른쪽 끝입니다.
4.8 GitHub Actions 최소 예시
.github/workflows/ci.yml 한 파일이면 CI가 돕니다.
name: CI
on:
push:
branches: [ main ]
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 파이썬 준비
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: 의존성 설치
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: 린트 (가이드)
run: ruff check .
- name: 테스트 (센서)
run: pytest -q
⚠️ 여기가 CI까지입니다. 이 파일에는 배포가 없습니다. 배포를 붙이면 CD가 되고, 배포 앞에
environment:로 승인 게이트를 두면 Continuous Delivery, 게이트 없이 바로 배포하면 Continuous Deployment 입니다. 파일 한 곳의 차이입니다.
5. 앱까지 넓어진 시스템 — 플러터와 카카오 OAuth
5.1 구조가 한 겹 늘어난다
지금까지 우리 프로덕션 구조는 이랬습니다.
[웹(Next.js)] ─→ [온프레미스 서버(개발)] / [EC2(배포)] ─→ [DB · Redis · S3]
8월에 여기에 한 겹이 추가됩니다.
[웹(Next.js)] ┐
├─→ [게이트웨이] → [인바운드 라우터] → [앱 유스케이스] → [아웃바운드] → [DB · Redis · S3]
[앱(Flutter)] ┘
앱(플러터)을 추가하고, 웹과 똑같이 카카오 OAuth 인증을 구현합니다. 여기서 중요한 것은 바뀌지 않은 부분입니다.
⭐ 게이트웨이 뒤쪽 — 인바운드 라우터 · 유스케이스 · 아웃바운드 · 저장소 — 은 한 줄도 바뀌지 않습니다. 6주차에서 헥사고날 일곱 칸을 배울 때 "어댑터를 하나 더 붙이면 된다" 고 했던 그 약속이, 여기서 실제로 지켜집니다. 플러터는 새로운 인바운드 어댑터일 뿐입니다.
아키텍처를 제대로 잡았는지는 새 클라이언트가 붙을 때 안쪽이 얼마나 바뀌는가로 판정됩니다. 0줄이면 성공입니다.
5.2 모바일과 웹 로그인은 철저히 분리한다
핵심 결정 하나가 있습니다.
모바일 로그인과 웹 로그인은 철저히 분리합니다. 도커 컨테이너 중 Redis에 모바일 로그인 토큰 컬럼을 추가하고, 이를 하네스 md로 적용합니다.
왜 분리하는가:
| 이유 | 설명 |
|---|---|
| 세션 수명이 다름 | 웹은 브라우저를 닫으면 끝나기도 하지만, 앱은 몇 주씩 로그인 상태를 유지합니다 |
| 갱신 정책이 다름 | 앱은 백그라운드에서 토큰을 조용히 갱신해야 합니다 |
| 강제 로그아웃 단위가 다름 | "이 기기에서만 로그아웃"이 되려면 기기별로 토큰이 따로 있어야 합니다 |
| 사고 시 격리 | 한쪽 토큰이 유출돼도 다른 쪽이 살아 있습니다 |
그리고 이 저장소가 Redis라는 것도 근거가 있습니다. 토큰은 만료가 있는 휘발성 상태입니다. 영구 보관할 개체가 아닙니다. 3주차의 디스크 ↔ 메모리 축에서 메모리 쪽에 속하는 데이터이므로, 인메모리 저장소가 맞습니다. PostgreSQL에 토큰을 넣는 설계가 나쁜 이유가 이것입니다. 성격이 다른 데이터를 성격이 다른 저장소에 둡니다. 5주차에서 배운 폴리글랏 퍼시스턴스(Polyglot Persistence) 의 실제 적용입니다.
5.3 하네스 md를 먼저 쓴다 — 코드는 금지
작업 순서가 정해져 있습니다.
flutter/_docs/flutter-kakao-oauth-harness.md와fastapi/_docs/fast-003-flutter-kakao-oauth-harness.md에 백엔드와 플러터를 구분해 md만 작성(코드 금지).
이 규칙은 7월부터 이 수업에서 반복돼 온 것입니다. flutter-android-harness.md,
langgraph + neo4j 확장도 전부 "하네스 md만 작성하고 코딩은 금지" 로 시작했습니다.
| 왜 md를 먼저 쓰는가 | 설명 |
|---|---|
| 경계를 먼저 확정 | 백엔드 문서와 플러터 문서를 따로 씁니다. 문서가 갈리면 코드도 갈립니다 |
| 컨텍스트 비용 절감 | 다음 세션에서 코드를 다 읽히는 대신 md 한 장을 읽힙니다 |
| 검토 가능 | 코드 300줄보다 md 30줄이 검토가 빠릅니다. 틀렸으면 여기서 잡습니다 |
| 재현 가능 | 같은 md를 주면 다른 세션에서도 비슷한 결과가 나옵니다 |
파일명 규칙(fast-003-...)에도 의미가 있습니다. 번호가 붙은 순서 있는 문서 묶음입니다.
8절에서 볼 RFP 하네스의 추적성(Traceability) 원칙 — "모든 코드·테스트·설정 파일 상단에 요구사항 ID 주석" — 의 예고편입니다.
5.4 화면 흐름
flutter/lib/auth.dart 에 카카오 OAuth 화면을 만듭니다. 앱을 켜면 이렇게 흐릅니다.
main.dart (인트로 영상 4~5초)
│
▼
auth.dart (카카오 OAuth 로그인)
│ 로그인 성공
▼
토큰을 Redis의 "모바일" 컬럼에 추가
│
▼
stopwatch_page.dart
그리고 모바일 세션이 유지되는 한, 다음부터는 인트로 후 곧바로 stopwatch_page.dart 로 갑니다.
| 코드 위치 | 역할 | 이 교재의 개념 |
|---|---|---|
main.dart | 진입점 · 인트로 영상 재생 후 라우팅 | 애플리케이션의 진입점 (2주차) |
auth.dart | 인증 화면 · 토큰 획득 | 인바운드 어댑터 (6주차) |
| Redis 모바일 컬럼 | 세션 상태 보관 | 상태(Stateful) 를 밖에 둔 것 (4·8주차) |
stopwatch_page.dart | 실제 기능 화면 | 유스케이스를 부르는 화면 |
💡 "로그인했으면 다시 묻지 않는다"가 왜 설계 문제인가 HTTP는 무상태(Stateless)입니다(4주차 축 1). 서버는 이번 요청이 누구인지 기억하지 않습니다. 그래서 상태를 밖에 둡니다. 그 밖이 Redis입니다. 8주차에서 배운 "서버를 무상태로 유지하고 상태는 밖으로 뺀다" 는 원칙이, 앱을 껐다 켜도 로그인이 유지되는 경험으로 사용자에게 도달하는 지점입니다.
5.5 안드로이드 개발 환경
| 항목 | 내용 |
|---|---|
| 입문 자료 | 플러터 입문 교재 (12절 참조) |
| 하네스 문서 | flutter/_docs/flutter-android-harness.md — 안드로이드 개발 가이드를 최신 사양으로 업그레이드해 작성 |
| WSL에서 스튜디오 열기 | VS Code(WSL 연동) 터미널에서 명령을 넣으면 윈도우의 Android Studio가 정확한 WSL 경로로 열림 |
| 실기기 사용자 | 스튜디오 없이 바로 연결 권장 |
| 에뮬레이터 기기 | 갤럭시 S 기본형(6.2인치, 1080x2400, 420dpi)과 유사한 Medium Phone 또는 Pixel 8/9/10 |
| 인트로 영상 | flutter/lib/main.dart 에서 4초 재생 후 다음 화면. 영상 파일은 assets/video 아래 |
| 와이어프레임 도구 | Balsamiq, Claude Design (12절 참조) |
⚠️ 에뮬레이터 스펙을 아무거나 고르지 마세요. 화면 크기와 dpi가 다르면 레이아웃이 다르게 깨집니다. 실제 사용자가 쓸 기기와 가장 비슷한 것을 골라야 개발 중에 발견됩니다.
LangTailor