[21-cloud-native] 03. 12-Factor 방법론

suhyen·2026년 3월 23일

2026-TIL

목록 보기
6/15
post-thumbnail

12-Factor란 무엇인가?

12-Factor App은 Heroku의 엔지니어들이 수많은 SaaS 애플리케이션을 배포하고 운영하면서 체득한 모범 사례(Best Practice)를 12가지 원칙으로 정리한 방법론이다.

왜 이것이 중요한가? 개발자라면 누구나 "내 로컬에서는 됐는데 서버에서 안 된다", "환경 변수를 어디 넣어야 할지 모르겠다", "배포할 때마다 무언가 예상치 못한 문제가 생긴다"는 경험이 있을 것이다. 12-Factor는 이런 문제들이 왜 발생하는지, 어떻게 설계하면 이를 근본적으로 예방할 수 있는지를 명확하게 제시한다.

12-Factor의 4가지 주요 목표

  • 배포 자동화: 환경별 설정 차이를 없애 파이프라인을 통한 자동 배포가 가능하도록
  • 환경 간 일관성: 개발, 스테이징, 운영 환경의 동작이 최대한 동일하도록
  • 수평 확장성: 서버를 추가하는 것만으로 스케일 아웃이 가능하도록
  • 이식성(Portability): 특정 인프라나 벤더에 종속되지 않도록

12-Factor를 적용한 앱은 어느 환경에서든 동일하게 동작하고, 사람 손 없이 자동으로 배포되며, 필요할 때 인스턴스를 늘리기만 하면 확장된다. 이는 클라우드 네이티브 설계의 기초 체력과 같다.


Factor 1 ~ 4: 코드, 의존성, 설정, 외부 서비스

① Codebase (코드베이스)

"하나의 코드베이스, 여러 환경으로의 배포"

하나의 앱은 하나의 Git 레포지토리에서 관리된다. 개발, 스테이징, 운영 환경이 달라도 코드는 동일하고, 환경에 따라 달라지는 것은 설정값뿐이어야 한다.

이 원칙이 깨지면 어떻게 되는가? 운영용 코드, 스테이징용 코드가 따로 존재하게 된다. 그러면 개발자는 두 버전을 동기화하는 데 에너지를 쏟게 되고, "스테이징에서 됐는데 운영에서 다르다"는 문제가 반복된다.

② Dependencies (종속성)

"모든 의존성은 명시적으로 선언하고 격리하라"

requirements.txt(Python), package.json(Node.js), pom.xml(Java)처럼 모든 라이브러리 의존성을 명시적으로 선언해야 한다. "서버에 이미 설치되어 있겠지"라고 암묵적으로 가정하면 안 된다.

가상 환경(virtualenv, venv), 컨테이너 이미지 등으로 의존성을 격리하는 것도 여기 포함된다. 이렇게 해야 "팀원이 새로 합류했을 때 환경 셋업에 하루가 걸리는" 상황을 막을 수 있다.

③ Config (설정)

"설정은 코드가 아닌 환경 변수로 관리하라"

DB 접속 정보, API 키, 외부 서비스 URL 등 환경마다 달라지는 값은 절대 코드에 하드코딩해서는 안 된다. 이 값들은 환경 변수(Environment Variable) 로 주입받아야 한다.

왜 코드에 설정이 있으면 안 되는가? 보안 문제도 있지만, 근본적으로 설정은 배포 환경에 따라 달라지는 값이고 코드는 어느 환경에서나 동일해야 하는 것이기 때문이다. Kubernetes에서는 ConfigMap(비민감 설정)과 Secret(민감 정보)으로 이 원칙을 구현한다.

④ Backing Services (백엔드 서비스)

"DB, 캐시, 메시지 큐는 모두 교체 가능한 외부 자원으로 취급하라"

로컬 MySQL이든 AWS RDS든, 로컬 Redis든 ElastiCache든 코드에서 바라보는 방식이 동일해야 한다. URL과 인증 정보만 바꾸면 다른 서비스로 교체할 수 있어야 한다.

이 원칙이 지켜지면, 개발 환경에서는 로컬 DB를 쓰다가 운영 환경에서는 클라우드 관리형 DB를 쓰는 전환이 코드 수정 없이 설정 변경만으로 가능해진다.


Factor 5 ~ 8: 빌드/배포, 프로세스, 포트, 동시성

⑤ Build/Release/Run (빌드/릴리즈/실행 분리)

"코드를 배포하는 과정을 세 단계로 명확히 분리하라"

  • Build: 소스 코드를 실행 가능한 Artifact(JAR, 컨테이너 이미지)로 변환
  • Release: Build 결과물과 환경 설정을 결합 (이미지 + ConfigMap)
  • Run: Release를 실행 환경에서 시작

핵심은 실행 중인 앱을 직접 수정하는 것을 금지한다는 것이다. "서버에 직접 접속해서 파일 하나 고쳤어요"가 허용된다면, 코드 레포지토리와 운영 환경이 달라지는 순간이 생긴다. 이는 재현 불가능한 장애와 "이게 왜 됐지?" 같은 미스터리를 만들어낸다.

이 세 단계의 분리는 CI/CD 파이프라인의 구조와 정확히 일치한다. Build는 테스트 및 이미지 빌드, Release는 Helm values와 이미지를 결합, Run은 Kubernetes가 Pod를 띄우는 단계다.

⑥ Processes (프로세스)

"앱은 하나 이상의 무상태(Stateless) 프로세스로 실행하라"

프로세스는 상태를 메모리에 보관하지 않는다. 세션, 사용자 데이터, 캐시처럼 지속되어야 하는 상태는 Redis, DB 등 외부 저장소에 위임한다.

이것이 왜 중요한가? 프로세스가 무상태여야 인스턴스를 여러 개 띄워도 어느 인스턴스가 요청을 받든 동일한 결과를 반환한다. 만약 "A 서버에 세션이 있으니까 A 서버로만 요청을 보내야 한다"는 상황이 된다면, 그 순간 스케일 아웃이 불가능해진다.

⑦ Port Binding (포트 바인딩)

"앱 스스로 포트를 열어 서비스를 제공하라"

앱이 외부 웹 서버(Apache, Nginx)에 의존하지 않고, 앱 자체가 HTTP 서버 기능을 내장하여 특정 포트를 열고 요청을 받는다. Python의 uvicorn, Java의 embedded Tomcat(Spring Boot), Node.js의 express 서버가 이 원칙의 구현체다.

Kubernetes의 Service, Ingress가 이 포트를 외부로 노출하는 역할을 한다. 결국 앱은 "나는 8080 포트로 HTTP 요청을 받는다"만 알면 되고, 어떻게 외부에 노출되는지는 인프라가 담당한다.

⑧ Concurrency (동시성)

"프로세스 모델을 통해 수평 확장하라"

처리량이 부족하면 프로세스/컨테이너의 수를 늘린다. 하나의 거대한 프로세스에 스레드를 무한히 늘리는 방식이 아니라, 같은 프로세스를 여러 개 실행하는 방식으로 스케일 아웃한다.

이는 Factor ⑥의 무상태 원칙이 지켜졌을 때 가능해진다. 프로세스가 상태를 가지지 않으므로 복제가 자유롭다.


Factor 9 ~ 12: 폐기 용이성, 환경 일치, 로그, 관리 프로세스

⑨ Disposability (폐기 용이성)

"프로세스는 빠르게 시작하고, 안전하게 종료해야 한다"

프로세스는 언제든지 시작되거나 종료될 수 있음을 전제로 설계해야 한다. 이를 위해:

  • 빠른 시작: 몇 초 안에 요청을 받을 수 있는 상태가 되어야 한다
  • Graceful Shutdown: 종료 신호(SIGTERM)를 받으면 처리 중인 요청은 마무리하고, 새로운 요청은 더 이상 받지 않으면서 안전하게 종료

Kubernetes 환경에서 Pod는 언제든지 재스케줄링되고 교체된다. 이 원칙이 지켜지지 않으면, Pod가 종료될 때 처리 중인 요청이 유실되거나 DB 연결이 비정상적으로 끊어지는 문제가 생긴다.

⑩ Dev/Prod Parity (개발/운영 환경 일치)

"개발 환경과 운영 환경의 차이를 최소화하라"

"내 로컬에서는 됐는데"는 개발 환경과 운영 환경이 다르기 때문에 생기는 말이다. 12-Factor는 이 차이를 세 가지 측면에서 줄이라고 한다.

  • 시간 격차: 코드를 작성한 순간과 배포되는 순간의 간격을 최소화 (수 시간 ~ 수 일 수준으로)
  • 담당자 격차: 코드를 만든 개발자가 직접 배포하고 운영
  • 도구 격차: 로컬에서도 Docker Compose나 로컬 Kubernetes를 사용해 운영과 유사한 환경 구성

컨테이너 기반 개발이 이 원칙을 실현하는 가장 효과적인 방법이다. 개발자의 로컬과 CI/CD 서버, 운영 서버가 모두 동일한 컨테이너 이미지로 실행된다.

⑪ Logs (로그)

"앱은 로그를 파일이 아닌 stdout/stderr로 출력하라"

앱은 로그를 어떻게 수집하고 저장할지에 대해 알 필요가 없다. 그냥 stdout(표준 출력)에 출력하면 된다. 로그 파일을 직접 관리하거나 로그 집계 서버로 보내는 것은 실행 환경(인프라)의 책임이다.

Kubernetes에서는 각 Pod의 stdout/stderr를 Fluentd나 Fluent Bit이 수집해 Elasticsearch나 Loki로 전달한다. 앱 입장에서는 print() 또는 logger.info()만 하면 나머지는 인프라가 처리한다.

구조화 로그(Structured Logging, JSON 형태) 를 권장하는 이유는, 사람이 읽는 것보다 로그 시스템이 파싱하고 검색하기 좋기 때문이다.

⑫ Admin Processes (관리 프로세스)

"관리 작업도 코드와 함께 버전 관리하고, 운영 환경과 동일한 환경에서 실행하라"

DB 마이그레이션, 일회성 데이터 정리 스크립트, 관리자 도구 등도 일반 앱 코드와 같은 레포지토리에서 관리해야 한다. 그리고 운영 환경에서 실행할 때도 동일한 환경(같은 컨테이너 이미지)을 사용해야 한다.

이 원칙이 지켜지지 않으면 "스크립트를 어떤 서버에서 어떤 환경으로 실행했는지"가 관리되지 않아, 나중에 재현이 불가능한 상황이 생긴다.


12-Factor 전체 요약

#Factor핵심 원칙
1Codebase1 레포 = 1 앱, 환경은 설정으로 구분
2Dependencies명시적 선언 + 격리
3Config환경 변수로 분리
4Backing Services외부 자원은 교체 가능한 URL로 취급
5Build/Release/Run세 단계 엄격히 분리
6Processes무상태(Stateless) 프로세스
7Port Binding앱이 직접 포트 서빙
8Concurrency프로세스 수 늘려 스케일 아웃
9Disposability빠른 시작 + Graceful Shutdown
10Dev/Prod Parity개발/운영 환경 최대한 동일하게
11Logsstdout으로 출력, 수집은 인프라가
12Admin Processes관리 스크립트도 코드와 함께 버전 관리

💡 Callout: Beyond 12-Factor (강의자료 외 내용)

12-Factor는 2011년에 정립된 방법론이다. 이후 클라우드 환경이 더 발전하면서, 3가지 원칙이 추가로 제안됐다.

  • API First: 서비스 설계는 API 명세부터 시작하라. 구현 전에 계약(Contract)을 먼저 정의한다.
  • Telemetry: 앱이 런타임 메트릭, 헬스체크, 로그를 스스로 수집하고 노출해야 한다.
  • Authentication & Authorization: 보안은 선택이 아니라 기본(Security by Default)이어야 한다.

현대 클라우드 네이티브 환경에서 12-Factor는 Kubernetes의 ConfigMap/Secret(Factor 3), 컨테이너 이미지(Factor 5, 6), stdout 로깅 + Fluentd(Factor 11) 등과 자연스럽게 연결된다. 12-Factor를 이해하면 왜 Kubernetes가 이런 구조로 설계됐는지도 이해할 수 있다.


요약: 12-Factor는 "어떻게 하면 클라우드 환경에서 예측 가능하고 자동화 가능한 앱을 만들 수 있는가"에 대한 답이다. 원칙 하나하나가 독립적으로도 가치 있지만, 12개를 함께 적용할 때 비로소 완전한 클라우드 네이티브 앱이 된다.


0개의 댓글