Twelve-Factor App 운영 원칙
Twelve-Factor App은 로컬, 배포, 운영 환경의 계약을 맞춰 클라우드에서 같은 방식으로 동작하게 만드는 12가지 원칙이다. 특히 Config, Processes, Disposability, Logs는 배포 직후 장애와 직접 연결된다.
Script Companion
오디오와 함께 스크립트 보기
- 01
Twelve-Factor App의 출발점은 클라우드 자체가 어렵다는 말보다 더 구체적이다. 로컬 개발, 수동 배포, 서버별 설정 파일, 인스턴스 로컬 상태가 서로 다른 운영 계약을 만들면, 내 로컬에서는 됐다는 문제가 계속 반복된다. 환경변수 누락으로 localhost에 붙으려다 502가 나고, 인메모리 세션 때문에 배포 중 사용자가 강제 로그아웃되며, SIGTERM 처리가 없어 진행 중인 요청이 끊긴다. 그래서 12-Factor는 ECS, K8s, CI/CD 어디에 올려도 같은 방식으로 동작하게 하는 운영 안전 표준으로 볼 수 있다.
- 02
공식 문서는 이 원칙이 Heroku 플랫폼에서 직접 배포한 수백 개 앱과 관찰한 수십만 개 앱의 운영 경험에서 정리됐다고 설명한다. 특히 Dev/prod parity는 개발과 운영의 간격을 줄이는 데 초점을 둔다. 전통적 앱에서는 배포 간격이 weeks였고, 12-Factor 앱은 이를 hours로 줄이는 것을 목표로 한다. 개발자와 배포 담당자가 분리되고, 로컬 SQLite와 운영 MySQL처럼 도구가 달라지는 상황은 단순한 취향 차이가 아니라 장애 원인이 된다.
- 03
해결 메커니즘은 Clean Architecture의 의존성 경계와 닮았다. 비즈니스 코드는 특정 서버의 파일 경로, localhost DB, 로컬 세션 메모리, 로그 저장소를 직접 알지 않고, 실행 환경이 주입하는 계약만 의존한다. 12가지 원칙은 하나의 Codebase, 명시적 Dependencies, 환경변수 Config, 교체 가능한 Backing services, Build와 Release와 Run의 분리, 무상태 Processes, Port binding, 프로세스 모델 기반 Concurrency, Disposability, Dev/prod parity, 이벤트 스트림으로서의 Logs, 일회성 Admin processes로 이어진다. ECS에서 K8s로 옮길 때 바뀌어야 하는 것은 코드가 아니라 Task Definition, Secret, Service, 로그 라우터 같은 실행 환경의 결선이다.
- 04
Config 원칙에서 중요한 기준은 단지 환경변수를 쓰느냐가 아니다. 배포별 값을 코드와 분리하고, 필수 값이 없으면 부팅 단계에서 바로 실패해야 한다. config 파일이 여러 위치와 형식으로 흩어지거나 development, staging, joes-staging 같은 환경 이름 묶음이 늘어나면 deploy가 많아질수록 취약해진다. 더 위험한 것은 환경변수가 없을 때 localhost로 계속 실행되는 fallback 패턴이다. 헬스체크가 DB까지 보지 않으면 서비스는 200 OK를 반환하지만, 실제 주문 API 같은 DB 경로에서만 장애가 터지는 silent failure가 된다.
- 05
Processes 원칙은 앱 프로세스를 무상태로 다루라는 요구다. ECS 컨테이너나 K8s 파드는 언제든 교체될 수 있으므로, 다음 요청을 위해 프로세스 메모리에 세션이나 상태를 남긴다고 가정하면 안 된다. 세션은 Redis 같은 외부 저장소로 보내고, DB나 캐시는 Backing services로 취급해 URL이나 접속 정보를 환경에서 주입받는다. 이 관점에서는 RDS URL을 바꿔 다른 DB로 교체하는 일도 코드 수정이 아니라 실행 환경의 변경에 가깝다. 인메모리 세션을 유지한 채 롤링 배포를 하면 사용자가 강제 로그아웃되는 VI번 위반으로 드러난다.
- 06
Logs 원칙은 로그 파일을 애플리케이션이 직접 관리하지 말라는 뜻이다. 웹 API뿐 아니라 워커와 일회성 관리 작업도 stdout/stderr로 로그를 내보내고, 실행 환경이 스트림을 수집해야 한다. ECS Run Task로 마이그레이션을 실행했다면 성공과 실패의 근거는 컨테이너 내부 파일이 아니라 CloudWatch Logs의 같은 stream prefix에서 확인되어야 한다. 파일 로그만 남기면 task 종료 뒤 증거가 사라지고, Admin processes의 일회성 실행 원칙과도 충돌한다. Datadog 같은 로그 수집도 이 스트림 모델 위에 놓인다.
- 07
Disposability는 빠른 시작과 우아한 종료를 요구한다. ECS는 배포 시 SIGTERM을 보내고 30초를 기다린 뒤 SIGKILL을 보내므로, 그 안에 진행 중인 요청을 마무리해야 한다. 이 기준은 p95 요청 시간이 800ms이고 DB 트랜잭션 timeout이 5초인 서비스에는 현실적인 운영 장치가 될 수 있다. stopTimeout=30과 ALB deregistration delay를 맞추면 배포 중 502를 줄이는 데 도움이 된다. 다만 결제 승인 후 외부 정산 API를 기다리는 3분짜리 동기 요청이나 10분짜리 리포트 생성은 이 방식에 맞지 않는다.
- 08
긴 작업은 요청 스레드 안에서 버티게 하지 말고 큐 작업으로 넘긴 뒤 작업 id를 반환하거나, 중간 checkpoint를 저장해 재시작 가능하게 만들어야 한다. 여기서 핵심은 ECS, K8s, Heroku dyno restart 모두에서 프로세스는 언제든 교체될 수 있다는 같은 원리가 작동한다는 점이다. Port binding과 Concurrency도 같은 방향을 가진다. 앱은 스스로 포트를 열어 서비스로 노출되고, ECS desired count나 K8s replicas처럼 프로세스 수를 늘리는 방식으로 수평 확장된다. 레고 블록처럼 어디서든 같은 방식으로 조립되고 교체되는 구조가 목표다.
- 09
운영 장애가 이미 발생했다면 원인 분석보다 복구 순서가 먼저다. 사용자가 5xx를 보고 있거나 세션 유실이 발생했다면 직전 정상 revision으로 롤백한 뒤 원인을 특정한다. 반대로 누락된 환경변수 하나가 원인이고 새 이미지 없이 task definition만 고치면 되며 target health와 에러율이 안정적이라면, 새 revision 등록 후 롤링 배포가 더 작은 조치일 수 있다. 검증은 ALB target group health, 5xx 비율, 그리고 VI번 위반이라면 사용자 세션 유실 여부를 본다. 정리하면 III번 Config 누락, VI번 인메모리 상태, IX번 SIGTERM 미처리가 배포 직후 장애의 핵심 축이다.
같은 레이어
L9에서 이어 듣기
- 설계 원칙을 운영 가능한 코드로 잇기 길이 미정
- Clean Architecture의 의존성 규칙 길이 미정
- DDD 기본기: 도메인 언어와 경계 설계 길이 미정
- CAP과 일관성으로 보는 분산 시스템 선택 길이 미정
- MSA 패턴, 분리의 이득과 운영 비용 길이 미정
- Saga Pattern: 로컬 커밋과 역순 보상 길이 미정
- CQRS와 이벤트 소싱의 운영 경계 길이 미정
- TDD와 테스트 피라미드로 설계하는 테스트 전략 길이 미정
- 대규모 웹 크롤러의 큐, 정중함, 중복 제거 길이 미정
- API 계약으로 안전하게 서비스 경계를 진화시키기 길이 미정
- URL Shortener와 Rate Limiter로 보는 시스템 디자인 길이 미정