Cloud-Native Design Patterns
클라우드 환경의 동적 자원을 활용하여 확장성, 복원력, 유연성을 극대화하는 12-Factor App과 클라우드 특화 설계 패턴을 다루는 학습 노드입니다.
Article
M
Me
hyunyoun's Blog
system-architecture-distributed-systemssystem-architecturedistributed-systemscloud-nativeserverless-evolutioncloud-native-design-patternsserverlesslearning10 min read
1. Overview
클라우드 네이티브 설계 패턴(Cloud-Native Design Patterns)은 단순히 서버를 남의 집 컴퓨터(AWS)에 올려놓는 '클라우드 호스팅(Cloud Hosted)'의 초보적 단계를 벗어나, 언제든 서버가 파괴되고 IP가 바뀔 수 있는 클라우드의 극한 환경에서 태생적으로 생존하도록 시스템을 깎아내는 '클라우드 원주민(Cloud-Native)'의 철학을 해부합니다.
학습자는 클라우드 네이티브의 성경과도 같은 The Twelve-Factor App(12 팩터 앱) 방법론을 뜯어보며, 코드와 설정을 분리하고 완벽한 무상태(Stateless)를 유지하는 모던 백엔드의 절대 규칙을 장악합니다. 나아가 컨테이너 환경의 설계 패턴인 사이드카(Sidecar), 앰배서더(Ambassador), 어댑터(Adapter) 패턴을 통해, 비즈니스 로직과 인프라 로직(관심사)을 물리적으로 찢어내는 우아한 컨테이너 컴포지션을 뜯어봅니다. 마지막으로 서비스 디스커버리(Service Discovery)와 외부 설정 주입(Externalized Configuration)을 통해, 소스 코드를 단 한 줄도 고치지 않고 개발 환경에서 프로덕션 환경으로 배포를 쏘아버리는 궁극의 배포 유연성을 확보합니다.
2. Scope & Boundaries
In-Scope
- The Twelve-Factor App: 형상 관리, 의존성 격리, 설정 분리, 무상태 프로세스 등 12가지 원칙.
- Cloud-Native vs Cloud-Hosted: Lift-and-Shift 이관의 한계와 태생적 클라우드 아키텍처의 차이.
- Container Design Patterns: Sidecar (기능 확장), Ambassador (외부 프록시), Adapter (인터페이스 통일).
- Service Discovery & Config: 동적 IP 환경에서의 서비스 탐색, 환경 변수 주입(Externalized Config).
Out-of-Scope
- Service Mesh (Istio)의 딥 다이브: 사이드카를 1,000개 엮은 중앙 제어망 07-06-04 Service Mesh 영역.
- Docker/Kubernetes 세부 명령어: 컨테이너 띄우는 법 07-06-02 Containerization 영역으로 위임.
Boundaries
- Lift-and-Shift vs Cloud-Native: 데이터센터(On-premise)에서 돌던 거대한 자바 애플리케이션과 오라클 DB를 그대로 떠서 AWS EC2에 올려놓은 것(Lift-and-Shift)은 '클라우드 호스티드'일 뿐입니다. 진정한 클라우드 네이티브는 서버의 특정 IP나 로컬 디스크 파일에 의존하지 않고, 언제든 스케일아웃(Scale-out)이 가능하며 죽었을 때 1초 만에 다른 노드에서 부활할 수 있는 '무상태(Stateless)' 체질 개선을 강제한다는 점을 명확히 경계 짓습니다.
3. Counterexample
- 설정 하드코딩의 늪: 소스 코드 안에
DB_HOST = "192.168.0.10"이라고 적어 놨습니다. 개발 환경, 스테이징 환경, 프로덕션 환경에 배포할 때마다 개발자가 소스 코드를 수정하고 컴파일을 다시 해서 각기 다른 이미지 3개를 만듭니다. 12-Factor App의 3원칙(설정을 환경 변수로 분리하라)을 어긴 죄로, 실수로 스테이징 이미지가 프로덕션에 배포되어 라이브 DB가 박살 나는 형상 관리의 끔찍한 안티 패턴입니다. - 상태 의존적(Stateful) 프로세스: 웹 서버 컨테이너가 유저가 업로드한 이미지를 자기 자신의
/var/www/uploads디렉토리(로컬 파일시스템)에 저장합니다. 10분 뒤 트래픽이 몰려 오토스케일링(ASG)으로 2번 웹 서버가 떴습니다. 유저가 2번 웹 서버로 라우팅되면 방금 자기가 올린 엑스박스 이미지를 보게 됩니다. 상태(State)를 분산 스토리지(S3)나 DB(Redis)로 빼내지 않고 컨테이너 내부에 품고 있는(Stateful) 구시대적 설계가 스케일아웃을 불가능하게 만드는 참사입니다.
4. Prerequisites
- 컨테이너 아키텍처 (Basic): 도커 컨테이너의 휘발성(Ephemeral). (07-06-02 Containerization)
- 마이크로서비스 (Basic): 분산 환경의 네트워크 통신. (07-06-01 Microservices)
5. Learning Map
6. Learning Topics
Basic
Core Topic 01: 원주민의 체질, Cloud-Native (Cloud-Native vs Hosted)
- Why to Learn: 남의 컴퓨터(AWS)를 쓰면서도 옛날 데이터센터(On-prem) 시절의 낡은 설계 방식을 고집하다 클라우드 비용만 10배로 내는 리프트 앤 시프트(Lift-and-Shift)의 참사를 막기 위함입니다.
- What to Learn:
- Concepts: Cloud-Native, Cloud-Hosted (Lift and Shift), Statelessness (무상태), Immutable Infrastructure (불변 인프라), Ephemeral (휘발성).
- Skills: 상태(Session, File)를 쥐고 있는 기존 애플리케이션을 무상태(Stateless) 구조로 체질 개선하기.
- How to Learn:
- 1단계: 가짜 클라우드 (Hosted): 온프레미스에서 돌던 WAS 서버를 그대로 AWS EC2 하나에 올려놨습니다. 트래픽이 폭주해서 서버가 뻗었습니다. 수동으로 재부팅하는 동안 유저들은 에러 화면을 봅니다. 이건 그냥 호스팅을 바꾼 것뿐입니다.
- 2단계: 진짜 원주민 (Native): 클라우드 네이티브 앱은 자기가 돌아가는 컴퓨터(노드)를 절대 믿지 않습니다. "이 컴퓨터는 언제든 번개 맞아 죽을 수 있다(Ephemeral)"고 가정하고 메모리에 아무것도 저장하지 않습니다. 서버가 죽으면 클라우드 매니저(K8s)가 1초 만에 다른 노드에서 새 컨테이너를 부활시킵니다. 데이터는 모두 외부(S3, RDS)에 둔 무상태(Stateless) 철학을 해부합니다.
- Implement: Stateless 스케일아웃 시뮬레이션. 터미널 스크립트.
Server_A가 글로벌 변수(global_count)에 로그인 상태를 저장함(Stateful). 로드밸런서가Server_B를 띄워 트래픽을 넘겼을 때 로그인 정보가 증발하는 에러 출력. 이를 극복하기 위해Redis라는 외부 딕셔너리로 상태를 빼내어 어떤 서버가 떠도 상태가 공유되는 클라우드 네이티브 렌더링.
Recommended
Core Topic 02: 성경책, The 12-Factor App (The 12-Factor App)
- Why to Learn: Heroku가 제창한 현대 웹 애플리케이션의 절대 원칙 12가지를 장악하여, 환경에 구애받지 않고 유연하게 스케일링되는 강력한 백엔드를 조립하기 위함입니다.
- What to Learn:
- Concepts: The Twelve-Factor App, 1. Codebase, 3. Config (환경 분리), 6. Processes (무상태 프로세스), 9. Disposability (빠른 시작과 종료).
- Skills: 소스 코드(Git)에서 하드코딩된 환경 변수(Config)를 완전히 분리해내기.
- How to Learn:
- 1단계: III. 설정(Config): 소스 코드 안에
API_KEY="abc"를 적어두면 오픈소스에 올라가 해킹당합니다. 설정은 반드시 환경 변수(Environment Variables)로 빼내어 코드와 완벽히 격리(Decoupling)시켜야 합니다. 빌드된 하나의 바이너리(도커 이미지)가 환경 변수만 갈아 끼워져 개발/운영 환경에서 동일하게 돌아감을 해부합니다. - 2단계: IX. 폐기 가능(Disposability): 프로세스는 눈 깜짝할 사이에 켜져야 하고, 죽으라는 신호(SIGTERM)가 오면 먹던 걸 안전하게 내려놓고(Graceful Shutdown) 우아하게 자결해야 합니다. 강제로
kill -9를 당해도 데이터가 꼬이지 않는 불사조 설계를 뜯어봅니다.
- 1단계: III. 설정(Config): 소스 코드 안에
- Implement: 12-Factor Config 분리 스크립트. 파이썬 코드.
os.environ.get('DB_URL')을 사용하여 코드 내부에 아무런 URL도 없음. 터미널에서export DB_URL="dev_db"를 주고 실행하면 개발 모드로,export DB_URL="prod_db"를 주고 실행하면 운영 모드로 똑같은 코드가 다르게 동작하는 환경 주입(Injection) 렌더링.
Practical
Core Topic 03: 컨테이너 조립식 장난감, 사이드카와 앰배서더 (Container Patterns)
- Why to Learn: 남이 짠 컨테이너 코드(Legacy)를 1줄도 수정하지 못하는 상황에서, 그 컨테이너 옆에 기생하는 컨테이너(보조)를 찰싹 붙여 새로운 기능(로깅, 라우팅)을 창조하는 마법을 부리기 위함입니다.
- What to Learn:
- Concepts: Sidecar Pattern (기능 확장), Ambassador Pattern (외부 연결 대행), Adapter Pattern (인터페이스 통일), Pod (K8s 공용 네트워크).
- Skills: 낡은 레거시 애플리케이션에 사이드카를 붙여 로그 수집(Fluentd) 기능 주입하기.
- How to Learn:
- 1단계: 사이드카(Sidecar): NGINX 컨테이너가
/var/log에 로그를 무식하게 파일로만 씁니다. NGINX 코드는 못 고칩니다. 이때 NGINX와 같은 Pod(네트워크, 디스크 공유) 안에Fluentd컨테이너(사이드카)를 하나 더 띄웁니다. Fluentd가 NGINX의 로그 파일을 슬쩍 읽어서 중앙 서버로 예쁘게 쏴줍니다. NGINX는 옆에 사이드카가 있는지도 모르는 우아한 확장을 해부합니다. - 2단계: 앰배서더(Ambassador): 앱이 외부 DB에 연결해야 하는데, DB IP가 10분마다 바뀝니다. 앱 소스 코드를 고치기 힘듭니다. 앱은 그냥 자기 자신(
localhost:3306)으로 통신을 쏩니다. 그러면 옆에 붙어 있던 앰배서더 컨테이너가 그걸 쓱 받아서, 현재 살아있는 진짜 외부 DB IP를 찾아내어 몰래 연결해 주는 대행(Proxy) 패턴을 뜯어봅니다.
- 1단계: 사이드카(Sidecar): NGINX 컨테이너가
- Implement: 사이드카 패턴 파이썬 모사.
App스레드는 1초마다 무식하게log.txt에 문자열을append함.Sidecar스레드는 이log.txt를tail -f로 엿보다가, 'ERROR'라는 단어가 보이면 가짜 슬랙(Slack) 서버로 HTTP POST를 쏴버리는 알람 확장을, 두 스레드의 코드 결합(Import) 없이 파일 시스템 공유만으로 렌더링.
Advanced
Core Topic 04: 미로 속의 나침반, Service Discovery와 External Config
- Why to Learn: 수천 개의 컨테이너가 1분마다 죽고 태어나면서 IP가 난수처럼 바뀌는 구름 속(Cloud)에서, 하드코딩된 IP의 저주를 풀고 서로를 귀신같이 찾아내는 전화번호부(Registry)를 쥐기 위함입니다.
- What to Learn:
- Concepts: Service Discovery (서비스 탐색), Service Registry (전화번호부 - Eureka, Consul, K8s DNS), Externalized Configuration (외부 설정).
- Skills: 하드코딩 IP를 제거하고 K8s 내부 DNS(예:
http://user-service)를 활용한 동적 라우팅 구축.
- How to Learn:
- 1단계: 동적 IP의 공포: 주문 서비스가 결제 서비스(10.0.1.5)를 불렀습니다. 1분 뒤 결제 서비스가 죽고 10.0.1.9로 다시 태어났습니다. 주문 서비스는 10.0.1.5만 찾다가 타임아웃으로 같이 죽어버립니다.
- 2단계: Service Registry (전화번호부): 결제 서비스가 태어나면 즉시 중앙 서버(Registry)에 "나 10.0.1.9로 태어났어, 이름은 결제야"라고 신고합니다. 주문 서비스는 결제 서비스의 IP를 모른 채, 그저 "결제 서비스 줘!"라고 전화번호부(DNS)에 묻습니다. 전화번호부가 방금 갱신된 10.0.1.9를 던져주는 다이내믹한 탐색(Discovery)의 기적을 해부합니다.
- Implement: 미니 Service Registry (Eureka/Consul) 시뮬레이터.
Registry_Dict전역 변수.Service_B가 구동 시Registry.register('Service_B', '192.168.1.10')호출. 5초 뒤 랜덤 IP로 변경 후 재등록.Service_A가 통신을 원할 때Registry.lookup('Service_B')를 통해 항상 최신 IP를 획득하여 통신 성공률 를 유지하는 콘솔 데모.
7. Terminology
8. References
Primary
- [P1] CS2023 - Software Engineering (SE) - Cloud Computing and Cloud-Native Architecture
- [P5] SFIA - Software Design (DESN) - Cloud Native Design Patterns
Secondary
- [12factor.net] - The Twelve-Factor App methodology
- [Kubernetes Patterns] Bilgin Ibryam - Behavioral Patterns (Sidecar, Ambassador, Adapter)
Industry
- [CNCF (Cloud Native Computing Foundation)] - Cloud Native Definition
- [Netflix TechBlog] - Eureka at Scale (Service Discovery)
9. Final Checklist
Primary
- 기존 데이터센터의 낡은 애플리케이션을 소스 코드 수정 없이 그대로 AWS 인스턴스에 올리는 리프트 앤 시프트(Lift-and-Shift) 방식이 왜 진정한 클라우드 네이티브(Cloud-Native)가 아닌지, 상태(Stateful)의 관점에서 설명할 수 있는가?
- The Twelve-Factor App 원칙 중 'Config(설정)' 원칙을 어기고 데이터베이스 비밀번호를 소스 코드 내부에 하드코딩(Hard-coding)했을 때, 다중 환경(Dev/Prod) 배포 시 발생하는 치명적인 보안 및 파이프라인 병목을 지적할 수 있는가?
Secondary
- 쿠버네티스의 단일 Pod 내에 NGINX 컨테이너(메인)와 Fluentd 컨테이너(사이드카)를 동시에 배치하여, NGINX 소스 코드를 건드리지 않고 로그 수집 파이프라인을 뚫어내는 사이드카(Sidecar) 패턴의 관심사 분리(SoC)를 해부할 수 있는가?
- 클라이언트 앱이 외부 API를 찌를 때, 동적으로 바뀌는 외부 IP를 추적하는 무거운 로직을 앱 내부에 짜넣는 대신,
localhost로 요청을 던지면 앰배서더(Ambassador) 컨테이너가 가로채어 우회 라우팅해 주는 설계 패턴을 논증할 수 있는가?
Industry
- 마이크로서비스가 1,000개가 넘어가는 환경에서, 특정 서비스의 IP가 바뀌었을 때 이를 모두에게 브로드캐스트하는 대신 Service Registry(예: Netflix Eureka, K8s CoreDNS)를 통해 중앙 집중식으로 서비스 디스커버리(Service Discovery)를 수행하는 룩업(Look-up) 메커니즘을 설계할 수 있는가?
- 클라우드 인프라가 언제든 컨테이너를 강제 종료시킬 수 있는 휘발성(Ephemeral) 환경에서, 프로세스가 종료 신호(SIGTERM)를 받았을 때 트랜잭션을 안전하게 마무리하고 연결을 끊는 우아한 종료(Graceful Shutdown)가 오토스케일링의 필수 전제임을 평가할 수 있는가?