서론
공식 사이트인 http://12factor.net/ 의 내용을 기반으로 해서 정리를 해 볼 까 한다.처음 이 사이트를 방문했을 때는 한국어가 없었는데, 어떤 고마운 분이 한국어 번역판을 올려주신 듯 하다. 한국어는 http://12factor.net/ko/ 에서 확인할 수 있다.
the Twelve-Factor App이 지향하는 특성을 원문의 서문에 나온 굵은 긁씨의 단어들로 정리하면 다음과 같다.
declarative, clean contract, maximum portability, deployment, cloud platforms, Minimize divergence, continuous deployment, scale up
많은 단어가 있지만, 결국 지향점은 클라우드 환경에서 쉽게 확장가능한 애플리케이션이라는 것으로 귀결되지 않을까 생각한다.
즉, the Twelve-Factor App은 클라우드 환경에 적합한 애플리케이션의 특성을 12가지 요소로 정리한 내용이다. 각 특성을 자세히 보면 이전 포스트에서 정리했던 마이크로서비스의 개념과도 통하는 부분들이 많은것을 볼 수 있을 것이다. 이것이 마이크로서비스 포스트를 작성하다가 12 Factor App을 정리한 이유이다.
The Twelve-Factor 란?
사이트 첫페이지에 나오는 12개의 요소를 요약해 놓은 것을 그대로 옮겨보았다.I. 코드베이스
버전 관리되는 하나의 코드베이스와 다양한 배포
II. 종속성
명시적으로 선언되고 분리된 종속성
III. 설정
환경(environment)에 저장된 설정
IV. 백엔드 서비스
백엔드 서비스를 연결된 리소스로 취급
V. 빌드, 릴리즈, 실행
철저하게 분리된 빌드와 실행 단계
VI. 프로세스
애플리케이션을 하나 혹은 여러개의 무상태(stateless) 프로세스로 실행
VII. 포트 바인딩
포트 바인딩을 사용해서 서비스를 공개함
VIII. 동시성(Concurrency)
프로세스 모델을 사용한 확장
IX. 폐기 가능(Disposability)
빠른 시작과 그레이스풀 셧다운(graceful shutdown)을 통한 안정성 극대화
X. dev/prod 일치
development, staging, production 환경을 최대한 비슷하게 유지
XI. 로그
로그를 이벤트 스트림으로 취급
XII. Admin 프로세스
admin/maintenance 작업을 일회성 프로세스로 실행
위의 12개 요소에 대한 제목만 봐도 대부분의 사람들은 다 이해를 하지 않을까 싶다.
그렇다고 여기서 다 정리했으니 끝 하기에는 뭔가 좀 아쉽다.
그래서 뭐 단순한 개념일지는 몰라도 정리하다 보면 꽤 난해한 부분도 있으니 각 요소마다 한번 정리해보자. 일부 요소의 경우는 논란이 되거나 동의하지 않는 사람들도 꽤 많아 보인다.
일부 개발자들의 경우는 몇가지 요소에 대해 동의할 수 없어서 12 Factor App 이라는 것 자체를 부정하는 포스트를 작성하는 경우도 있던데, 그러지 말고 도움이 될 수 있는 부분들도 많으니 참고삼아 한번 보도록 하자.
I. 코드베이스 : One codebase, Multiple deploys
12개 요소 중 처음을 코드베이스에서 시작한것은 그만큼 코드베이스가 중요하기 때문일 것이다.
1개의 애플리케이션당 1개의 코드베이스를 강조하고 있다. 즉, 애플리케이션 대 코드베이스를 1:1로 만들라는 이야기!
그리고 한개의 코드베이스, 여러개의 배포라는 의미는 코드베이스 한가지로 여러 환경에 대한 배포가 가능하다는 것이다. 즉 코드베이스 하나를 가지고 test/staging/production... 등의 많은 환경에 배포가 가능한 것을 말한다. 즉, 배포 환경이 다르다고 해서 코드베이스를 여러 개로 나누거나 해서는 안된다는 어쩌면 요즘은 너무나도 당연해진 이야기로 시작하고 있다.
여기서 한가지 걸리는 부분은 하나의 코드베이스에 여러개의 애플리케이션을 두지 말라는 부분이다. 내 블로그 포스트 중에도 있지만 우리 회사는 주로 전체 프로젝트를 멀티 프로젝트로 구성한다. 이렇게 사용하지 말라는 것으로 보이는데 우리 같은 작은 팀에서 각 애플리케이션별로 코드베이스를 나누어야 할 것인가 하는 부분에 대해서는 좀 생각해 봐야겠다.
II. 종속성
원문에서는 CPAN이나 Rubygems 를 이용해서 설명하고 있지만, 자바 생태계에서도 충분히 설명이 가능하다. 최근의 프로젝트를 보면 gradle이나 maven을 이용해서 프로젝트의 의존성을 관리하는 것이 당연한 것처럼 되어있다. 약 10년전쯤에 프로젝트 할 때는 ant를 이용해서 의존성을 관리하는 것도 꽤 발전된 형태였던 것으로 기억되는데, 현재는 이런 의존성관리는 물론 빌드 태스크를 담당해주는 툴의 사용이 매우 당연한 것으로 인식되고 있다.
또한, Springframework에서는 최근 Spring-boot 를 이용해서 jetty/tomcat 등의 WAS까지도 embeded 형태로 포함하여 배포가 가능하다. 즉, 최근의 애플리케이션은 거의 모든 의존성을 관리할 수 있도록 되고 있는 추세라는 것이다. spring-boot 에 docker file 까지 설정한다면, 더 많은 것들에 대한 관리도 가능하다. 다시 말해서 단순히 라이브러리들에 대한 종속성 뿐 아니라, 서비스를 구동할 때 필요한 거의 모든 것을 관리할 수 있는 시대가 되었다고 보면 될 것 같다.
III. 설정
설정은 배포 환경(Test, Staging, Production 등)에 따라 달라질 수 있는 모든 것이라고 이야기한다.
이 설정 정보들은 코드에 포함되어서는 안되는 것은 당연하고, 코드베이스에 올라가서도 안된다고 이야기하고 있다. 코드베이스는 특정 그룹으로 한정되면 안되고 상황에 따라 그룹을 추가할 수 있어야 한다. 또한 설정 정보는 여러 곳으로 분리되어 있어서도 안된다.
가끔은 각 서버마다 특정 디렉터리에 환경 파일을 미리 올려놓고 애플리케이션 실행시에 해당 디렉터리를 참고하도록 하는 경우도 있는데, 이것은 두번째 요소인 종속성에도 어긋날 뿐더라 이 에도 어긋날 수 있다.
원문에서는 envvars 또는 env 등을 이용한 예를 이야기하고 있다. 코드베이스에 포함될 일도 없고, 특정 OS나 언어에도 독립적이기 때문이다.
다만, 이 설정에 애플리케이션 내부 설정 정보는 포함되지 않는다. 원문에서는 rails의 routes.rb 파일에 저장되는 설정과 springframework의 DI 설정등은 이 설정에 포함되지 않는다고 이야기한다. 즉, 배포 환경에 따라 변화되지 않는 애플리케이션의 내부 설정들은 코드베이스에 포함되어있는 것이 가장 바람직다.
최근에 읽었던 글 중에 Pivotal 의 Josh Long 이 쓴 "Configuring It All Out" or "12-Factor App-Style Configuration with Spring" 글이 있으니 참고해보면 좋겠다.
IV. 백엔드 서비스
여기서의 백엔드 서비스는 Third-party 에 의해 서비스되는 모든 것을 이야기하는 것이다.
원문에서는 MySQL 데이터베이스라던가 SMTP 서비스, 메시지 큐 등을 예로 들고 있다.
이런 백엔드 서비스는 애플리케이션의 외부 리소스로 취급해야 한다. 즉, 설정을 변경하는 것으로 코드 변경없이 이용이 가능하도록 해야 한다는 것이다.
테스트 때는 테스트용 데이터베이스를 이용하다가 Production으로 가면 또 다른 데이터베이스를 이용해서 서비스가 가능해야 한다. 운영중에 데이터베이스 서버의 접속정보가 바뀌어도 코드의 수정이 있어서는 안된다. 다른 예로 개발단에서는 로컬 SMTP를 이용하다가 production에서는 Google의 SMTP로 서비스가 가능해야 한다.
뭐 이 요소도 워낙 요즘은 당연하게 받아들여지는 개념이라 크게 이견은 없을 것으로 보인다.
V. 빌드, 릴리즈, 실행
빌드/릴리즈/실행은 배포와 실행의 기본적인 3단계이다. 이 각 단계는 각각 엄격하게 분리되어야 한다는 이야기다. 빌드에서는 실행가능한 애플리케이션으로 만들기 위해 코드베이스에서 코드를 가져오고 종속성들이 합쳐지고 컴파일 된다. 릴리즈 단계에서 적절한 환경설정과 결합되어지고 실행단계에서 애플리케이션이 실행되어지게 된다.
절대로 릴리즈나 실행단계에서 코드가 변경되어서는 안된다. 코드의 변경에 따른 빌드는 빌드 단계에서만 이루어져야 하는 행위인 것이다.
또 하나의 중요한 부분이 바로 롤백과 관련된 부분이다. 어떠한 툴을 쓰건 현재 버전은 이전버전으로 롤백이 가능해야 하다.
모든 릴리즈는 유니크해야 하며, 릴리즈는 변경되어서는 안된다. 릴리즈는 계속 새롭게 릴리즈되어야 한다.
우리 회사는 여전히 쉘스크립트를 이용해서 배포를 하고 있지만, 요즘 많은 회사들이 CI/CD 툴을 이용해서 배포를 진행하고 있고, 해당 CI/CD 툴 들은 대부분 빌드/릴리즈/실행 과정을 모두 담당할 수 있을 뿐 아니라, 롤백까지도 처리해 주는 것으로 알고 있다. 그런 시스템에서 개발을 하는 사람들은 어쩌면 너무 당연하다고 생각하면서 무시할 수도 있다.
하지만 한가지 생각을 해 볼 필요가 있다. 웹의 경우 static 리소스가 많이 있다. css, js, html 같은 경우가 대표적이고 jsp나 themeleaf 파일의 경우도 코드가 포함되어있긴 하지만 충분히 static resource 라고 볼수도 있다. 우선은 css, js, html 의 경우는 외부 리소스로 판단하여 별도 서버 또는 애플리케이션과 분리되어 배포가 된 경우도 많으니 어쩌면 별로 논의하지 않아도 될지 모르겠다. 하지만 jsp 나 themeleaf 파일의 경우는 어떨까? 이 파일들은 과연 코드베이스에 포함된 코드로 봐야 할 것인가 아니면 별도 리소스로 볼 것인가. 외부 리소스라면 동일한 코드베이스에 포함되면 안되는 것일까? 외부 리소스이니 변경될 때마다 릴리즈 절차와 상관없이 배포가 가능한 것이 맞지 않을까? 외부 리소스가 아니라고 생각한다면, 템플릿 파일에 태그하나 또는 오타가 있을 경우 전체 릴리즈를 다시 해야 하는 것일까? 한번쯤 생각해봐야 할 요소이지 싶다.
VI. 프로세스
모든 애플리케이션은 무상태로 동작해야 하며, 메모리나 디스크의 자원에 의한 공유가 이루어져서는 안된다. 현재 동작하는 애플리케이션에 의해서 만들어진 어떤 상태가 해당 애플리케이션이 다시 동작할 때 유지 될 것이라는 보장이 없기 때문이라는 것이다.
뭐 표현이 어려우니 간단히 웹 프로그램의 Session을 예로 들면(원문에서도 Sticky session을 예로 들고 있다), 메모리나 디스크에 저장된 Session을 사용하지 말라는 것이다. 애플리케이션에 종속된 Session은 해당 애플리케이션이 재실행되거나 이후 변경/이동 등이 발생했을 경우 정상적인 동작을 보장할 수 없기 때문이다. Session은 Redis 라던가 Memcached 등을 이용해서 사용해야 한다.(역시 이렇게 되면 외부 리소스가 된다)
VII. 포트 바인딩
이 요소는 약간 이해가 안되는 요소이다. 그래서 개념만 간단히 정리하면 다음과 같다.
애플리케이션은 포트 바인딩에 의해서 외부에 공개해야 한다. 이를 통해서 해당 애플리케이션은 다른 서비스의 백앤드 서비스가 될 수 있다.
VIII. 동시성(Concurrency)
보통 Concurrency 라고 하면 Thread를 통해 이루어지는 동시성에 집중하게 된다. 프로그램을 오래 한 사람일수록 더 그럴것이라고 생각한다. 예전에는 거의 그 의미였으니까 말이다.
하지만 여기서 말하는 동시성은 이 부분도 포함을 하긴 하지만, 기본적으로 수평확장에 의한 동시성을 이야기하고 있다. 6번째 요소인 무상태 동작과도 통한다고 할 수 있다.
12 Factor App에서 애플리케이션은 아무것도 공유하지 않으므로 수평으로 확장하는 것을 통해서 동시성을 높이는 것이 간단하고도 안정적인 작업일 수 있다.
최근의 포스트를 작성하게 된 이유이기도 한 마이크로서비스 아키텍처가 이 부분을 대표한다고 볼 수 있다. 자유롭게 확장하고 자유롭게 축소할 수 있으니 말이다.
IX. 폐기 가능(Disposability)
원문을 그대로 옮겨보면 빠른 시작과 그레이스풀 셧다운(graceful shutdown)을 통한 안정성 극대화다.
우선은 애플리케이션을 시작하는 시간의 최소화가 중요하다. 애플리케이션의 시작시간을 최소화함으로써 쉽게 배포하고 쉽게 실행하여 릴리즈 작업과 확장을 용이하게 해야 한다.
또 한편으로는 종료시간의 최소화이다.
이 부분에 대해서 원문은 두가지로 나눠서 이야기한다.
첫번째 HTTP 요청과 같은 경우는 짧은 처리시간을 통해서 Shutdown Signal을 받은 후에 애플리케이션은 신규 요청의 수신을 중지하고 기존 처리되고 있는 요청은 빠르게 처리한 후 종료되어야 한다. 파일 업로드와 같은 long polling 처리의 경우는 종료가 되어 연결이 끊어진 시점에서 바로 다시 연결을 시도하여 요청을 이어갈 수 있도록 만들어야 한다.
두번째 Worker 프로그램과 같은 경우는 현재 처리중인 작업을 다시 Worker Queue로 되돌리고 종료되야 한다.
시작과 종료외에 오류에 의한 애플리케이션 종료에도 대응해야 한다. 마이크로서비스의 failover 관련 처리(fallback method, circuit breaker 등)가 이 경우에 해당한다고 볼 수 있다.
X. dev/prod 일치
배포환경에 따른 불일치는 크게 시간, 담당자, 툴 세가지의 차이에서 비롯된다.
12 Factor App 은 이 세가지를 아래와 같은 방법으로 극복한다.
- 시간의 차이 : 손쉬운 배포를 통해 개발된 내용을 바로 배포하여 극복
- 담당자의 차이 : 개발자가 직접 배포와 모니터링에 참여하여 극복
- 툴의 차이 : 개발과 배포 환경을 최대한 비슷하게 유지해서 극복
특히 12 Factor App 에서는 개발환경과 배포환경을 최대한 동일하게 유지하는 것을 중요하게 생각한다. 앞서 백엔드 서비스를 설명하면서 사실 개발에서는 H2 데이터베이스를 Production에서는 MySQL을 사용하는 것도 가능하다는 것을 이야기하려 했으나 이 요소에 어긋나는 것이라 삭제했다.
삭제한 예와 같이 보통의 개발자들은 로컬 개발환경의 백앤드 서비스를 경량화 하려고 하는 경향이 있으나, 12 Factor App에서는 이런 경향을 강력하게 거부하고 있다.
최근에는 Docker 등의 경량 Container 들이 나와있어서 사실 툴의 불일치를 해결하기가 매우 편리해 진 만큼 이 원칙을 지키면서도 본인의 로컬 환경이 어지러워지는 것을 방지할 수 있으니 정말 다행이다.
XI. 로그
원문에 의하면 로그는 모든 실행중인 프로세스와 백그라운드 서비스의 아웃풋 스트림으로부터 수집된 이벤트가 시간 순서로 정렬된 스트림이다. 즉 시간의 흐름에 따라 지속되는 스트림이다.
애플리케이션은 애플리케이션 자체적으로 이런 스트림을 저장할 파일을 생성하거나 관리해서는 안된다. 모든것은 실행환경 설정에 의해서 결정되어야만 한다.
예를 들어 로컬 환경에서는 콘솔에 그대로 표시가 되고 production 에서는 특정 파일에 저장되거나 중앙집중식 로그관리 시스템에 보낼 수 있어야 한다.(코드의 변경없이 실행환경에 따라서)
마이크로서비스 아키텍처에서는 많은 작은 단위의 애플리케이션이 수평확장까지 된 상태로 실행되기 때문에 보통 로그를 관리하는 시스템(예를 들면 ELK stack 등)을 함께 이용한다. 하지만, 로컬에서 개발할 때는 각자의 콘솔에 그대로 출력이 된다.
또한 12 Factor 애플리케이션은 로그를 분석할 수 있는 다양한 시스템에 의해서 과거 이력에 대한 검색이나 그래프 등을 통한 통계 또는 현황 대시보드, 특정 이벤트에 대한 알림 등의 다양한 이점을 누릴 수 있도록 개발되어진다.
XII. Admin 프로세스
설명하기가 좀 애매한데, 일회성의 유지보수나 관리 작업은 실행환경의 일회성 스크립트나 명령을 통해 실행이 되어야 한다는 것이다. 이를 위해서 12 Factor App은 실행환경과 동일한 환경에서 사용가능한 REPL을 지원하는 언어를 적극적으로 권장한다고 이야기한다. 이 때문에 주로 설명이 rails 나 python 의 예가 많은가보다.
아쉽게도 JDK의 경우는 아직까지 공식적인 REPL을 지원하지는 않는다. 하지만, java9 에는 jshell REPL 이 지원될 것으로 보이니 한번 기대해볼만하겠다.
결론
지금까지 the Twelve Factor App 에 대해서 설명했다.
위와 같은 12가지의 특성을 가지는 App을 12 Factor App 이라고 부른다.
마이크로서비스 포스트를 올리던 중에 12 Factor App 을 정리하게 된 것은 12개의 특성들 중 많은 부분이 마이크로서비스 개발에 있어서도 고려되어야 하는 상황이기 때문이다.
그건 어쩌면 당연하다. 12 Factor App도 Cloud 환경에서 실행되는 애플리케이션을 위한 것이고, 마이크로서비스도 그 시작이 Cloud 환경에서 손쉽게 확장 가능한 애플리케이션을 개발하기 위한 아키텍처로 등장했기 때문이다.
지금 소개한 이런 특성이 동의할 수 있는 것들이냐, 절대적인 것이냐 등등을 논하고 싶지는 않다.
그저 고려한다면 확장하기 용이한 애플리케이션을 개발하는데 큰 도움이 될 것이라고 생각한다.
댓글
댓글 쓰기