최근 소프트웨어를 서비스 형태로 제공하는게 일반화 되면서, 웹앱 혹은 SaaS(Software As A Service)라고 부르게 되었다.
- 설정 자동화를 위한 절차(declarative) 를 체계화 하여 새로운 개발자가 프로젝트에 참여하는데 드는 시간과 비용을 최소화한다.
- OS에 따라 달라지는 부분을 명확히하고, 실행 환경 사이의 이식성을 극대화 한다.
- 최근 등장한 클라우드 플랫폼 배포에 적합하고, 서버와 시스템의 관리가 필요없게 된다.
- 개발 환경과 운영 환경의 차이를 최소화하고 민첩성을 극대화하기 위해 지속적인 배포가 가능하다.
- 툴, 아키텍처, 개발 방식을 크게 바꾸지 않고 확장(scale up) 할 수 있다.
Twelve-Factor app은 SaaS 앱을 만들기 위한 방법론이다.
어떤 프로그래밍 언어로 작성된 앱에도 적용할 수 있고, 백엔드 서비스(데이터베이스, 큐, 메모리 캐시 등) 과 다양한 조합으로 사용할 수 있다.
목차
- 코드 베이스
- 종속성
- 설정
- 백엔드 서비스
- 빌드, 릴리즈, 실행
- 프로세스
- 포트 바인딩
- 동시성
- 폐기 가능
- 개발/프로덕션 환경 일치
- 로그
- Admin 프로세스
1. 코드베이스
Twelve-Factor 앱은 항상 Git, Mercurial, Subversion 같은 버전 컨트롤 시스템을 사용하여 변화를 추적한다.
코드베이스는 단일 저장소 일수도 있고, 여러 저장소 일 수도 있다.
- 단일 저장소 : Subversion 같은 중앙 집중식 버전 관리 시스템
- 여러 저장소 : Git 같은 분산 버전 관리 시스템, 루트 커밋을 공유함.
코드베이스와 앱 사이에는 항상 1대1 관계가 성립해야 한다.
- 코드 베이스가 여러개 있는 경우 : 앱이 아니라 분산 시스템임
- 여러 개 앱이 동일한 코드를 공유하는 경우 : Tweleve-Factor 위반. sol -> 공유하는 코드를 라이브러리화 시키고, 라이브러리로 종속성 관리
앱의 코드베이스는 1개이지만, 배포(deploy)는 여러 개일 수 있다. (1:N) 이때 배포는 실행중인 앱의 인스턴스를 가리킨다.
배포마다 다른 버전이 활성화 될수 있지만, 코드베이스 자체는 모든 배포에 대해 동일하다. (사진)
예를 들어, 개발자는 아직 스테이징 환경에 배포하지 않은 커밋이 있을 수 있으며, 스테이징 환경에는 아직 운영 환경에 배포되지 않은 커밋이 있을 수 있다. 하지만 이 모든 것들이 같은 코드베이스를 공유하고, 같은 앱의 다른 배포라고 할 수 있다.
2. 종속성
Twelve-Factor App은 종속선 선언(manifest)를 통해 모든 종속성을 엄격하게 선언한다.
또한,
종속성 분리 툴을 사용하여 실행되는 동안 종속성 “유출”이 발생하지 않게 보장한다.
이렇게 하는 이유를 생각해보면, 파이썬을 이용한 장고 프로젝트에 필요한 A라는 라이브러리가 있는데,
이게 현재 내 로컬 개발 환경의 시스템상에는 파이썬에 디폴트로 A 라이브러리가 설치되어 있다.
그래서 A 라이브러리를 설치할 필요없이 신나게 A 라이브러리를 활용해서 개발을 한 후에, 배포를 한다고 한다면?
그런데 배포 서버의 환경에 따라 배포 시스템에는 A 라이브러리가 없을수도, 있을수도 있다.
해당 서버를 저희한테 내준 사람/기업이 A 라이브러리를 개인적으로 설치했는지를 일일히 신경을 쓸 수 없기때문에, 우리는 프로젝트의 필요 라이브러리를 시스템에 의존하면 안된다.
그래서 파이썬에서는 종속성 선언과 종속성 분리를 위한 도구가 있습니다.
- pip : 종속성 선언
- virtualenv: 종속선 분리
이런 도구들을 쓴다면 다른 개발자가 프로젝트를 실행하기 위해서 필요한 패키지들을 일일히 문서를 보고 설치할 필요가 없고
단지 종속성 관련 도구를 이용해 손쉽게 필요한 패키지를 설치하여 개발에 참여할 수 있다.
3. 설정
애플리케이션의 설정에는 다음의 것들이 포함된다.
- 데이터베이스, memcached 등 백엔드 서비스들의 리소스 핸들
- Amazon S3 이나 트위터 등의 외부 서비스 인증 정보
- 배포된 호스트의 정규화된 호스트 이름(canonical hostname)처럼 각 배포마다 달라지는 값
Twelve-Factor는 설정을 코드에서 엄격하게 분리해야 한다.
애플리케이션에서 설정을 저장하는 방법은 여러가지가 있는데,
1. 설정을 상수로 코드에 저장 -> 설정을 코드에서 분리해야 하는 Twelve-Factor 위반
2. yaml 과 같은 버전 관리 시스템에 등록되지 않은 설정 파일을 이용하는 것 -> 설정 파일이 여러 위치에 여러 포맷으로 흩어지고 언어와 프레임워크를 따라가는 경향이 있음.
따라서 Twelve-Factor App은 설정을 환경 변수 (envvars나 env라고도 불림)에 저장한다. 환경변수는 다음과 같은 특징을 가진다.
- 코드 변경 없이 쉽게 배포 때마다 쉽게 변경할 수 있음
- 설정 파일과 달리, 잘못해서 코드 저장소에 올라갈 가능성도 낮음.
- 언어나 OS에 의존하지 않는 표준임
- 그룹으로 묶이지 않고, 각 배포마다 독립적으로 관리됨.
4. 백엔드 서비스
백엔드 서비스는 애플리케이션 정상 동작 중 네트워크를 통해 이용하는 모든 서비스를 의미한다. 예를 들어, 데이터 저장소(MySQL), 메시지 큐잉 시스템(RabbitMQ)등이 있다.
Twelve-Factor App의 코드는 로컬 서비스와 *서드파티 서비스를 구별하지 않는다.
(* 서브파티 : 어떠한 분야에서 처음 개발하거나 원천기술을 가지고 있는 게 아닌, 원천기술과 호환되는 상품을 출시하거나 해당 기술을 이용한 상품을 생산함)
즉, 애플리케이션에게는 로컬 서비스와 서드파티 서비스 모두 연결된 리소스이며, URL 혹은 다른 로케이터를 이용하여 접근이 가능하다.
Twelve-Factor App은 데이터베이스들을 첨부된(Attached) 리소스로 다룬다.
따라서, 리소스 핸들만 변경하여 애플리케이션 코드를 수정하지 않고 로컬에서 관리되는 MySQL DB를 서드파티에서 관리되는DB(Amazon RDS)로 전환할 수 있다.
만약 애플리케이션의 데이터베이스가 하드웨어 이슈로 작동이 이상한 경우, 애플리케이션의 관리자는 코드를 전혀 수정하지 않고 현재 운영에 사용하고 있는 데이터베이스를 분리하고 새로운 데이터베이스 서버를 시작시킬 것이다.
5. 빌드, 릴리즈, 실행
코드베이스는 빌드 -> 릴리즈 -> 실행 단계를 거쳐 배포하게 된다.
- 빌드 : 코드 저장소를 빌드라는 실행 가능한 번들로 변환시키는 단계
- 릴리즈 : 빌드 단계에서 만들어진 빌드와 배포의 현재 설정을 결합하는 단계, 모든 릴리즈는 유니크한 릴리즈 아이디를 가진다. 릴리즈 관리 도구를 통해 이전 릴리즈로 되돌리는 롤백기능을 사용할 수 있다.
- 실행 : 애플리케이션을 실행 환경에서 돌아가도록 하는 단계
Twelve-Factor App은 빌드, 릴리즈, 실행 단계를 엄격하게 서로 분리한다.
예를 들어, 실행 단계에서 코드를 변경할 수는 없다. 실행 단계보다 앞에 있는 빌드 단계로 변경사항을 전달할 수 없기 때문이다.
6. 프로세스
실행 환경에서 앱은 하나 이상의 프로세스로 실행된다.
Twelve-Factor 프로세스는 무상태(stateless)이며, 아무 것도 공유하지 않는다. 유지될 필요가 있는 모든 데이터는 데이터베이스 같은 안정된 백엔드 서비스에 저장되어야 한다.
Twelve-Factor앱은 하나 이상의 프로세스로 실행되므로, 메모리나 디스크에 캐시된 내용이 미래의 요청에서 유효하지 않을 수 있다.
왜냐하면, 프로세스가 여러개 돌아가고 있는 경우, 미래의 요청은 다른 프로세스에 의해서 처리될 가능성이 있기 때문이다. 따라서, 이러한 세션 데이터는 Redis처럼 유효기간을 제공하는 백엔드 서비스(데이터 저장소)에 저장하는 것이 적합하다.
7. 포트 바인딩
Twelve-Factor 앱은 포트 바인딩을 사용해서 서비스를 공개한다.
예를 들어, 로컬 개발 환경에서는 http://localhost:3000 과 같은 주소를 통해 개발자가 애플리케이션 서비스에 접근할 수 있다.
배포 시에는 라우팅 레이어가 외부에 공개된 호스트명으로 들어온 요청을 포트에 바인딩된 웹 프로세스에 전달한다.
HTTP 뿐만아니라 거의 모든 종류의 서버 소프트웨어는 포트를 바인딩하고 요청이 들어오길 기다리는 프로세스를 통해 실행된다.
포트 바인딩을 사용한다는 것은 하나의 앱이 다른 앱을 위한 백엔드 서비스가 될 수 있다는 것을 의미한다.
따라서 백엔드 앱의 URL을 사용할 앱의 설정의 리소스 핸들로 추가하는 방식으로 앱이 다른 앱을 백엔드 서비스로 사용할 수 있다.
8. 동시성(Concurrency)
모든 프로그램은 실행되면 하나 이상의 프로세스로 표현된다.
Twelve-Factor App에서의 프로세스는 서비스 데몬들을 실행하기 위한 유닉스 프로세스 모델에서 큰 힌트를 얻었다.
이 모델을 사용하면 개발자는 애플리케이션의 작업을 적절한 프로세스 타입에 할당함으로서 다양한 작업 부하를 처리할 수 있도록 설계할 수 있다. 예를 들어, HTTP 요청은 웹 프로세스가 처리하며, 시간이 오래 걸리는 백그라운드 작업은 worker 프로세스가 처리하도록 할 수 있다.
Twelve-Factor App 프로세스는 독립적이고, 수평적으로 분할이 가능한 성질을 가진다.
즉, 동시성을 높이기 위해서 Twelve-Factor App 프로세스 모델을 이용하면 여러개의 프로세스로 수평적으로 확장이 가능하다.
9. 폐기 가능(Disposability)
Twelve-Factor App의 프로세스는 간단하게 폐기 가능하다. 즉, 프로세스는 바로 시작하거나 종료 될 수 있다.
프로세스는 SIGTERM 신호를 받았을 때, 그레이스풀 셧다운(graceful shutdown)을 한다.
- 웹 프로세스 : 서비스 포트의 수신을 중지하고, 현재 처리 중인 요청이 끝나길 기다린 뒤에 프로세스가 종료된다. (HTTP 요청이 짧다는 가정)
- worker 프로세스 : 현재 처리중인 작업을 작업 큐로 되돌린다. (모든 작업은 재입력 가능하다고 가정)
만약, 그레이스풀 셧다운이 아닌 하드웨어 에러에 의한 갑작스러운 프로세스 죽음의 경우, 큐잉 백엔드를 사용하여 클라이언트의 접속이 끊긴 경우, 작업을 큐로 되돌린다.
따라서, Twelve-Factor App은 프로세스를 간단하게 종료가능하고, 예기치 못한 프로세스의 종료도 처리할 수 있도록 설계된다.
10. 개발/프로덕션환경 일치
일반적으로, 개발환경과 production 환경은 시간의 차이, 담당자의 차이, 툴의 차이 등 큰 차이가 있다.
Twelve Factor App은 개발 환경과 production 환경의 차이를 작게 유지하여 지속적인 배포가 가능하도록 디자인 되었다.
Twelve Factor 개발자는 개발 환경과 production 환경에서 다른 백엔드 서비스를 사용하고 싶을 수 있다.
예를들어, 로컬에서는 더 가벼운 백엔드 서비스를 사용하고 싶을수 있지만, 개발환경과 production 환경사이의 약간의 불일치가 production 환경에서는 오류를 일으킬 수 있다. 이런 종류의 오류는 Twelve-Factor App의 지속적인 배포를 방해하므로, Twelve Factor App은 production 환경과 최대한 유사한 개발 환경(무거운 백엔드 서비스 일지라도)을 유지한다.
11. 로그
로그는 실행 중인 app의 동작을 확인할 수 있는 수단이다
Twelve-Factor App은 아웃풋 스트림의 전달이나 저장에 절대 관여하지 않는다. (작성, 수정이 금지)
즉, 로컬 개발환경에서 작업 중인 개발자는 app의 동작을 관찰하기 위해선 각자의 터미널에 출력되는 이 스트림을 보면 된다.
12. Admin 프로세스
개발자들은 종종 일회성 admin 프로세스를 이용하여 관리나 유지 보수 작업을 수행하는 경우가 있다. 그 경우는 아래와 같다.
- 데이터 베이스 마이그레이션
- 임의의 코드를 실행하거나 라이브 데이터베이스에서 앱의 모델을 조사하기 위해 콘솔 실행
- 애플리케이션 저장소에 커밋된 일회성 스크립트의 실행
이 일회성 admin프로세스는 애플리케이션의 일반적인 프로세스들과 동일한 환경에서 실행되어야 한다.
따라서, Twelve-Factor는 일회성 스크립트를 쉽게 실행하게 해주는 REPL shell을 제공하는 언어를 선호한다. 로컬 배포에서, 개발자는 일회성 admin 프로세스를 shell 명령어로 바로 실행시킨다. 또한 production 배포에서, 개발자는 ssh나 배포의 실행 환경에서 제공하는 다른 원격 명령어 실행 메커니즘을 사용하여 admin 프로세스를 실행할 수 있다.