개발 철학

12-Factor Application

Operation CWAL 2021. 3. 12. 22:24

SaaS(Software as a Service)

불과 10~20년 전만 하더라도 소프트웨어는 패키지를 구매하여, 사용자가 직접 자신의 PC에 설치하는 방식으로 제공되었다.  하지만 지금은 웹을 통해 언제 어디서나 서비스를 이용할 수 있으며, 대부분의 소프트웨어는 클라우드 플랫폼 위에 배포된다. 감이 안온다면 Netflix를 생각해보자. 예전엔 집에서 영화를 보기 위해선 동영상 플레이어와 DVD가 필요했지만, 지금은 비용만 결제하면 브라우저 또는 앱에서 바로 시청이 가능하다. 이런 방식의 서비스를 Software as a Service 또는 SaaS 라고 정의할 수 있으며, 'Web Application'이라는 이름으로 부르기도 한다.

12-Factor Application

12-Factor는 클라우드 환경에서 SaaS App을 보다 효율적으로 개발할 수 있게 고안된 일종의 방법론이자 가이드라인이다. 

초창기 클라우드 플랫폼인 Heroku의 개발자들이 수많은 App을 만들고 운영하면서 얻은 경험과 노하우를 12개의 Factor로 정리하였으며, 현재 대부분의 Cloud Native Application이 이를 충실히 따르고 있다. Cloud Native Application 개발자 또는 DevOps 엔지니어라면 반드시 이해하고 있어야 할 기본소양 중 하나로, 12-Factor를 적용하였을 때, 얻을 수 있는 효과는 다음과 같다.

 

  • 선언형(Declarative) 프로그래밍을 통한 설치 자동화로 신규 개발자의 프로젝트 참여하는데 걸리는 시간과 비용을 최소화한다.
  • OS에 따라 달라질 수 있는 부분을 명확히하여, 서로 다른 실행 환경간의 이식성을 최대화한다.
  • 개발과 운영 환경의 차이점을 최소화함으로써 지속적 배포의 신속성을 극대화한다.
  • 소프트웨어의 변화를 최소화하면서 Scale-up이 가능하다.

 

이제 12개의 Factor가 무엇인지 하나씩 알아볼 시간이다.

 

1. CodeBase

  • 12 Factor App은 반드시 Git, SVN 등을 통해 형상관리가 이루어져야 한다.
  • App과 코드베이스는 1:1 관계를 형성한다. 만약 하나의 시스템에 여러개의 코드베이스가 존재한다면 이는 여러개의 App으로 구성된 분산 시스템(ex: MSA)이다.
  • 하나의 코드베이스로부터 여러개의 Deploy를 생성할 수 있다. Deploy는 앱을 실행한 인스턴스를 의미한다.
    • 예를 들어, 브랜치 전략을 통해 Staging 배포, Production 배포 구분이 가능하다.

 

CodeBase and Deploys

 

2. Depedencies

  • App의 모든 종속성(ex: library)을 명시적으로 선언해야한다. node의 package.json, maven의 pom.xml 같이 dependency를 예로 들 수 있다. 이를 통해 신규 개발자가 코드베이스를 체크아웃하고 빌드할 때, 라이브러리를 직접 찾아서 설치하는 비효율을 방지할 수 있다.
  • shell에서 curl 등의 명령어로 의존성을 해결하는 암묵적 방식은 언제든지 오동작할 가능성이 존재하므로 사용하지 않는다. 

 

3. Config

  • App의 Config(설정값)는 환경변수(env)에 저장하고 이를 코드에서 호출하는 방식으로 구현한다. Config는 Deploy(dev, staging, prod, etc.)마다 다르기 때문에 코드 안에 작성해선 안된다.
  • Config를 파일에 저장하는 방식은 사용하지 않는다. 실수로 코드베이스에 반영될 위험성이 있으며, 위치와 형식이 제각각이기 때문에 한 곳에서 관리하기 어려울 뿐만 아니라 특정 개발 언어나 프레임워크에 종속적일 수 있다.
  • 일반적으로 사용하는 config는 다음과 같다.
    • Database, Message Queue, Cache 등의 Backing Service 리소스
    • 리소스 인증정보(API Key, Token 등)
    • Deploy에 따라 가변적인 항목(ex: Hostname)

 

 

 

4. Backing Services

  • App은 서비스를 제공할 뿐만 아니라, 외부 서비스를 이용하기도 한다. 이러한 외부 서비스를 Backing Service라고 정의한다. DB, Message Queue, 메일서버, Cache 등의 서비스가 대표적인 예이다.
  • 12-Factor App은 Backing Service을 어디서 제공하더라도 무방하게 동작한다. 예를 들어, Localhost에 위치한 MySQL을 사용하다가 3rd Party에서 제공하는 외부 DB(ex: Amazon RDS)를 변경하더라도 코드 수정은 없어야 한다. 이때 변경할 것은 오로지 리소스 정보(ex: URL, Locator)가 위치한 config 뿐이다.

 

Backing services

 

 

5. Build, release, run

  • 빌드부터 실행까지의 과정은 엄격하게 구분되어야 한다. 
  • 코드베이스를 아래와 같은 세 단계를 거쳐서 배포한다.
    • 빌드: 코드를 실행 가능한 번들(Binary, Container Image 등)로 변환하는 단계이다. 새로운 코드가 발생할 때마다 새로 빌드한다.
    • 릴리즈: 빌드 완료된 번들을 deploy의 config와 합쳐서 바로 실행 가능한 상태로 만든다. 개별 릴리즈는 식별 가능한 ID(ex: timestamp, numbering)가 있으며, 이미 작업한 릴리즈는 수정이 불가능하다. 
    • 실행: 런타임으로 부르기도 하며, 선택한 릴리즈로부터 App을 실행한다. 

 

Stages

 

6. Processes

  • App은 한개 이상의 statless process 형태로 실행되어야 하며 프로세스 간에는 어떠한 것도 공유하지 않는다. 영구적으로 저장해야하는 데이터는 Backing Service를 이용한다.
  • 메모리나 파일시스템을 cache로 사용할 수는 있으나 인스턴스가 재시작될 때, 이 데이터들은 초기화된다.

 

7. Port binding

  • App을 Port binding하여 직접 서비스를 제공한다. Tomcat이나 HTTPD와 같은 runtime injection 방식은 사용하지 않는다.
  • 12-Factor App은 일반적으로 개발언어마다 존재하는 라이브러리나 프레임워크를 통해 웹서버 형태로 구현된다. 하지만 굳이 HTTP가 아니더라도 Port를 통해 접근할 수 있으면 무방하다. 이를 통해 App은 다른 App의 Backing Service가 될 수 있다.

 

8. Concurrency

  • Process Model을 적용하여, 기능별(ex: web, worker)로 프로세스를 분리해야 한다. 
  • 12-Factor App은 프로세스 사이 어떠한 공유도 없으므로 쉽고 신뢰할 수 있는 수평적 확장(scale out)이 가능하다.
  • 데몬 방식으로 수행하거나, PID 파일을 수정하는 것은 허용하지 않는다. 대신, OS에서 제공하는  프로세스 관리자(ex: systemd)를 통해 출력 스트림 관리, 충돌 프로세스 대응, 재시작 및 종료를 처리할 수 있다.

 

 

9. Disposability

  • 빠른 시작(fast startup)과 우아한 종료(graceful shutdown)을 통해 App의 강건성(robustness)을 최대화한다.
  • 12-Factor App의 프로세스는 필요에 따라 언제든지 제거될 수 있다. 따라서, 서비스 제공까지 걸리는 시간을 최소화하고, 프로세스 관리자로부터 'SIGTERM' signal을 받았을 때 우아한 종료 동작을 구현해야 한다. 또한 갑작스러운 프로세스 종료(crash) 상황에도 대처 가능하도록 설계한다.

 

10. Dev/prod parity

  • Development, Staging, Production 환경 간의 차이(gap)를 최소화한다. 환경에 따른 차이는 다음과 같은 세가지 유형으로 구분할 수 있다.
    • 시간 차이: 코딩에서 Production 반영까지 걸리는 시간
    • 인력 차이: 개발자는 코드를 작성, 운영자(또는 Ops 엔지니어)는 배포
    • 도구 차이: 개발자가 사용했던 서비스 스택과 실제 Production에서 사용중인 스택이 다를 수 있음
  • 12-Factor App은 이러한 차이를 최소함으로써 지속적 배포(Continuous Deployment)가 가능하도록 설계되어야 한다. 새로 추가한 코드는 몇 시간 이내에 Production으로 반영되며, 코드를 작성한 개발자는 이 배포 과정에 참여하여 문제가 없는지 모니터링한다. 그리고 개발과 Production 환경을 최대한 비슷하게구성한다.
  • DB나 Message Queue와 같은 Backing Service는 Dev/Prod parity 를 어렵게 하는 원인 중 하나다. 예를 들어, '개발시 DB는 SQLite를 가정했는데, 실제 Prod 환경은 MySQL 또는 PostgreSQL를 사용한다'와 같은 케이스다. 12-Factor App은 이러한 경우 서비스 유형에 맞는 'Adapter'를 사용하도록 가이드한다. 위와 같은 경우, ORM(Ojbect Relational Mapping) 라이브러리를 사용하면 RDBMS 종류에 상관없이 하나의 코드로 대응할 수 있다.

 

11. Logs

  • 프로세스에서 발생하는 로그는 별도의 파일이 아닌 표준출력(stdout, stderr)을 통해 스트리밍한다. 이로 인해 얻을 수 있는 장점은 다음과 같다.
    • 로컬 환경에서 작업중인 개발자가 터미널에서 발생하는 로그를 바로 확인 가능하다.
    • Staging 또는 Production 배포시, 외부에 존재하는 Log Router(ex: fluentd, logstash)를 통해 로그를 수집하고 이를 로그 저장소에 모으는 Log Aggregation이 가능하다. 

 

12. Admin processes

  • 관리용 Task는 일회성의 Batch 프로세스(One-off admin process)로 실행한다. 예를 들어, DB Migration 또는 Report 생성과 같은 작업은 웹서버 형식으로 제공할 이유가 없다.
  • One-off process 역시 App의 다른 프로세스와 마찬가지로 동일한 환경에서 실행되어야 하며, 동기화 문제를 피하기 위해서 같은 코드베이스에 위치해야 한다.

 

 

 

참고

The Twelve-Factor App - 12factor.net

 

'개발 철학' 카테고리의 다른 글

DevOps  (1) 2021.04.26