본문으로 건너뛰기

버그 제보

이상한 점을 알려 주시면 고칠게요. 지금 보고 있는 페이지와 브라우저 정보가 함께 담겨요.

페이지:

DevOps

12요소 앱

작성 2026. 10. 31.추가
목차5개 항목
출처와 참고

ByteByteGo의 System Design 101 가이드 「The 12-Factor App」을 바탕으로, 원 문서 「The Twelve-Factor App」으로 확인해 재구성한 노트다. 컨테이너에서 원칙을 지키는 방법은 12요소 앱 한국어 번역(「The Twelve-Factor App」), 「도커로 루비 온 레일스 배포하기」, 「Rails on Docker」로 보강했다.

12요소 앱이란

12요소 앱(The Twelve-Factor App)은 SaaS 애플리케이션을 이식성 있고, 수평 확장하기 쉽고, 지속적으로 배포할 수 있게 만드는 열두 가지 원칙이다. Heroku 개발자들이 PaaS에서 수많은 앱을 운영하며 정리했고, 컨테이너·Kubernetes 시대의 클라우드 네이티브 앱 설계에도 거의 그대로 들어맞는다. 2024년 오픈소스로 전환되어 커뮤니티가 개정을 이어 가고 있다.

열두 원칙에 깔린 생각은 앱은 자기가 돌아가는 서버에 대해 아무것도 가정하지 않는다는 것이다. 그래야 어느 서버든 지우고 새로 띄울 수 있다.

열두 가지 원칙

#원칙규칙어기면 생기는 일지금의 구현
I코드베이스(Codebase)앱 하나 = 버전 관리되는 코드베이스 하나, 배포는 여러 개환경마다 코드가 갈라진다Git 저장소 하나에서 개발·스테이징·운영으로 배포
II의존성(Dependencies)의존성을 명시적으로 선언하고 격리한다. 시스템에 깔린 도구를 암묵적으로 쓰지 않는다「내 컴퓨터에서는 되는데」lock 파일, 컨테이너 이미지
III설정(Config)배포마다 달라지는 설정(DB 주소, 자격 증명)은 코드가 아니라 환경 변수에 둔다비밀 값이 저장소에 커밋된다. 환경 추가 때 코드를 고친다환경 변수, Kubernetes ConfigMap·Secret, 비밀 관리 서비스
IV백엔드 서비스(Backing services)DB·큐·캐시·외부 API를 붙였다 뗄 수 있는 자원으로 다룬다. 로컬이든 외부든 URL만 바꾸면 교체된다특정 서버에 묶여 교체가 어렵다연결 문자열을 설정으로 주입
V빌드·릴리스·실행(Build, release, run)빌드(코드 → 실행 파일), 릴리스(빌드 + 설정), 실행을 엄격히 나눈다. 실행 중인 코드를 고치지 않는다운영 서버에서 직접 고친 코드가 어디에도 남지 않는다CI가 이미지를 빌드, 릴리스마다 고유 버전, 롤백은 이전 릴리스로
VI프로세스(Processes)앱은 무상태 프로세스로 돈다. 남겨야 할 상태는 백엔드 서비스에 둔다서버 메모리의 세션 때문에 늘리거나 죽일 수 없다세션은 Redis·DB에, 파일은 객체 스토리지에
VII포트 바인딩(Port binding)앱이 스스로 HTTP 서버를 품고 포트에 바인딩해 서비스를 내보낸다. 외부 웹 서버에 끼워 넣지 않는다실행 환경의 웹 서버 설정에 의존한다앱이 PORT 환경 변수를 읽어 리슨
VIII동시성(Concurrency)같은 프로세스를 여러 개 띄워 수평으로 늘린다. 작업 종류별로 프로세스 유형(web, worker)을 나눈다한 프로세스를 키우는 수직 확장에 갇힌다레플리카 수 조정, 오토스케일링
IX폐기 가능성(Disposability)빨리 뜨고, 종료 신호(SIGTERM)를 받으면 하던 일을 마무리하고 우아하게 내려간다배포·스케일 축소 때 요청이 끊긴다그레이스풀 셧다운, 헬스 체크, 작업을 멱등하게
X개발·운영 일치(Dev/prod parity)개발·스테이징·운영의 시간·사람·도구 차이를 최소로 줄인다운영에서만 나는 버그같은 컨테이너 이미지, 로컬에서도 같은 종류의 DB
XI로그(Logs)로그를 파일로 관리하지 않고 표준 출력에 이벤트 스트림으로 흘린다. 수집·보관은 실행 환경이 맡는다서버가 사라지면 로그도 사라진다stdout → 로그 수집기(Fluent Bit 등) → 중앙 저장소
XII관리 프로세스(Admin processes)마이그레이션·일회성 스크립트는 같은 코드·설정으로 별도의 일회성 프로세스로 돌린다운영 서버에 접속해 손으로 고친다Kubernetes Job, 배포 파이프라인의 마이그레이션 단계

컨테이너에서 지키는 법

컨테이너는 서버 파일 시스템에 들어가 로그를 꺼내는 모델이 아니라서 12요소를 어기면 바로 티가 난다.

원칙컨테이너에서
III 설정docker run -e DATABASE_URL=postgres://... -e SECRET_KEY_BASE=...처럼 이미지 하나에 환경 변수만 바꿔 여러 환경에 올린다. 많은 프레임워크가 DATABASE_URL이 있으면 설정 파일보다 먼저 쓴다
VIII 동시성Procfile의 프로세스 유형(web, worker)을 컨테이너 하나 안에서 foreman 같은 관리자로 함께 돌리지 않고, 유형마다 별도 컨테이너·서비스로 띄워 따로 늘린다
XI 로그운영 모드에서 로그를 파일에만 쓰는 앱은 docker logs에 아무것도 안 나온다. stdout으로 바꾼다(Rails는 RAILS_LOG_TO_STDOUT)
XII 관리 프로세스같은 이미지로 일회용 컨테이너를 띄운다: docker run --rm -e DATABASE_URL=... app:1.4 bin/rails db:migrate, docker compose run --rm, 쿠버네티스 Job

원칙끼리의 관계

flowchart TB
  f1["III 설정 분리 + IV 백엔드 서비스<br/>→ 같은 빌드를 어느 환경에나 배포"] --> f2["V 빌드·릴리스·실행 분리<br/>→ 릴리스 = 빌드 + 설정, 롤백이 쉬움"]
  f2 --> f3["VI 무상태 + VII 포트 바인딩<br/>→ 어느 서버에서든 띄울 수 있음"]
  f3 --> f4["VIII 동시성 + IX 폐기 가능성<br/>→ 마음대로 늘리고 죽일 수 있음 = 수평 확장"]

수평 확장은 무상태(VI)와 폐기 가능성(IX)에 달려 있다. 상태가 없고 언제든 죽여도 되는 프로세스만이 개수를 자유롭게 바꿀 수 있다.

정리

  • 앱은 실행 환경에 대해 아무것도 가정하지 않는다. 설정은 환경 변수, 상태는 백엔드 서비스, 로그는 표준 출력.
  • 빌드는 한 번, 설정을 입혀 릴리스하고, 실행 중인 코드는 고치지 않는다.
  • 무상태 프로세스를 여러 개 띄워 확장하고, 빨리 뜨고 우아하게 죽게 만든다.
  • 개발과 운영의 차이를 줄이고, 관리 작업도 같은 코드로 별도 프로세스에서 돌린다.

관련 노트: 클라우드 네이티브와 코드형 인프라, VM과 컨테이너, Dockerfile 작성과 컨테이너의 PID 1, CI/CD, NestJS 설정과 환경 변수

원본 기록5

댓글

GitHub 계정으로 댓글을 남길 수 있어요.