한 줄 정의
결합도를 낮추고 응집도를 높이는 소프트웨어 설계 원칙을 인프라에 적용해, 시스템이 커져도 작은 조각 단위로 안전하고 빠르게 변경할 수 있게 만드는 방법입니다.
쉽게 말하면
집을 한 덩어리로 지으면 부엌 싱크대 하나 고치는 데 온 집의 물을 끊어야 합니다. 층마다 밸브가 있고 방마다 두꺼비집이 나뉘어 있으면 부엌만 잠그고 작업할 수 있습니다.
이 장은 결국 밸브를 어디에 달 것인가 에 대한 이야기입니다. 그리고 밸브 위치는 배관 도면의 아름다움이 아니라 “실제로 어디를 자주 고치는가”로 정해야 한다는 것이 핵심입니다. 도면상 깔끔하게 배관끼리·배선끼리 묶어 두면, 정작 부엌을 고칠 때마다 온 집을 건드리게 됩니다.
왜 중요한가?
성공한 시스템은 사용자도 개발자도 기능도 늘어납니다. 그럴수록 변경은 위험해지고, 변경 관리 프로세스는 무거워지며, 한 번 바꾸는 데 드는 시간이 길어집니다. 이 오버헤드가 개선을 어렵게 만들어 기술 부채를 쌓고 품질을 떨어뜨립니다.
1장의 “빠른 변경이 좋은 품질을 만들고 좋은 품질이 빠른 변경을 가능하게 한다”를 거꾸로 돌린 악순환입니다. 대부분의 인프라 코딩 도구가 모듈·라이브러리를 지원하지만, 인프라 설계 사고는 아직 소프트웨어 설계만큼 성숙하지 않아서 도구가 있어도 잘 쓰이지 못합니다.
핵심 내용
잘 설계된 컴포넌트 — 낮은 결합도, 높은 응집도
결합도 는 한 컴포넌트의 변경이 다른 컴포넌트의 변경을 요구하는 빈도입니다. 목표는 제로 커플링이 아닙니다. 결합이 전혀 없다는 것은 애초에 같은 시스템의 일부가 아니라는 뜻이고, 현실적인 목표는 약하고 느슨한 결합입니다.
응집도 는 컴포넌트 내부 요소 간의 관계입니다. 응집도가 낮은 스택에서는 어떤 리소스를 바꿔도 같은 스택의 다른 리소스와 아무 상관이 없습니다. 두 개의 다른 스택이 프로비저닝한 서버를 하나의 네트워킹 스택으로 묶어 놓은 경우가 그렇습니다. 반대로 응집도가 높은 컴포넌트는 폭발 반경이 좁고 작고 단순해서 변경하기 쉽습니다.
두 개념 모두 결국 변경 패턴 을 보는 관점이라는 점이 중요합니다. 모듈화 설계 규칙에는 텐션이 있어서, 규칙을 부주의하게 밀어붙이면 오히려 시스템이 더 취약해지고 변경하기 어려워집니다.
켄트 벡의 단순한 설계 네 가지 규칙
- 테스트를 통과한다(해야 할 일을 수행한다)
- 의도를 밝힌다(명확하고 이해하기 쉽게 작성한다)
- 중복이 없다
- 최소한의 요소만 포함한다
컴포넌트 설계 규칙
| 규칙 | 인프라에서의 의미 |
|---|---|
| 중복 배제 원칙(DRY) | 모든 지식은 시스템 내에서 하나의 표현만 가집니다. provisioner 계정 로그인 정보를 모든 스택과 서버 이미지 빌더 코드에 흩뿌리는 대신 중앙 한 곳에 두고 참조합니다 |
| 구성 규칙 | 의존관계인 두 조각 중 하나를 다른 쪽에 영향 없이 교체할 수 있어야 합니다. Linux와 Windows 애플리케이션 서버 이미지 사이를 스택 코드 변경 없이 전환할 수 있는 상태 |
| 단일 책임 원칙(SRP) | 컴포넌트는 하나의 목적만 가지며 목적은 계층화됩니다. “애플리케이션을 위한 인프라 제공”(스택) 아래에 보안 트래픽 라우팅(스택 라이브러리), 애플리케이션 서버(서버 이미지), 데이터베이스 인스턴스(스택 모듈)가 각각 이해하기 쉬운 하나의 목적을 갖습니다 |
| 도메인 개념 중심 설계 | 기술적 개념(“서버를 정의하는 컴포넌트”)이 아니라 도메인 개념(“애플리케이션 서버”, “빌드 서버”)으로 나눕니다. 기술 중심 공유 컴포넌트는 그것을 쓰는 모든 코드를 서로 연결시켜 버립니다 |
| 디미터의 법칙 | 한 컴포넌트는 다른 컴포넌트의 구현 방식을 알지 못해야 합니다. 공유 네트워킹 스택이 특정 애플리케이션 서버 클러스터의 로드 밸런서와 방화벽 규칙까지 정의하고 있다면 위반입니다 |
| 순환 의존성 제거 | 공급자 컴포넌트가 자신의 직접·간접 소비자의 리소스를 사용해서는 안 됩니다 |
공급자와 소비자
의존성이 있는 관계에서 공급자 컴포넌트는 소비자 컴포넌트가 사용하는 리소스를 생성하거나 정의합니다. 서브넷을 만드는 공유 네트워킹 스택이 공급자라면, 그 서브넷 안에 서버와 로드 밸런서를 프로비저닝하는 애플리케이션 인프라 스택이 소비자입니다. 이 장의 핵심 주제는 인프라 컴포넌트 간의 인터페이스를 정의하고 구현하는 것입니다.
디미터의 법칙 위반과 순환 의존성은 대개 같은 곳에서 함께 나타납니다. 애플리케이션 서버 스택이 클러스터의 서버를 공유 네트워킹 스택의 구조에 할당하고, 공유 네트워킹 스택은 그 클러스터를 위한 로드 밸런서와 방화벽 규칙을 만드는 구조가 그렇습니다. 네트워킹 요소를 애플리케이션 스택으로 옮기면 순환이 끊기고 응집도와 결합도가 동시에 좋아집니다.
유용한 복제
DRY는 리터럴 코드 라인의 복제와 다른 개념의 구현이 우연히 닮은 것을 구분합니다. 여러 컴포넌트가 공유 코드에 의존하면 긴밀한 결합이 생겨 오히려 변경이 어려워집니다. 판단 기준은 “이 코드의 한 인스턴스를 변경하면 항상 다른 인스턴스도 변경되어야 하는가”입니다. 애플리케이션 서버·웹 서버·빌드 서버를 하나의 모듈로 다 생성하려 들면 모듈은 지나치게 복잡해지고, 조직의 모든 애플리케이션 서버를 동시에 업그레이드하는 것도 비현실적입니다.
여기서 나오는 경험칙이 재사용은 결합도를 증가시킨다 는 것입니다. 그래서 재사용은 컴포넌트 내부가 아니라 컴포넌트 사이에서 하는 것이 좋습니다.
테스트 가능성이 설계를 결정한다
모든 레벨의 인프라 코드를 생성하고 테스트할 수 있어야 하고, 파이프라인 단계는 분리된 각 컴포넌트의 인스턴스를 신속하게 만들 수 있어야 합니다. 의존성이 얽힌 스파게티 코드베이스나 프로비저닝에 30분이 걸리는 대규모 컴포넌트에서는 불가능한 요구입니다.
그래서 인프라에 자동화 테스트를 도입하려는 계획이 자주 좌초합니다. 잘못 설계된 시스템에서는 테스트를 작성하고 실행하는 것 자체가 어렵기 때문입니다. 뒤집으면 이것이 자동화 테스트의 비밀스러운 이점 입니다. 코드를 지속적으로 테스트하고 딜리버리하는 유일한 방법이 낮은 결합도와 높은 응집도를 유지하는 것이므로, 테스트에 대한 집착이 설계를 강제로 개선합니다.
스택 컴포넌트 vs. 컴포넌트로서의 스택
인프라 스택은 인프라의 배포 가능한 핵심 단위이자 아키텍처 퀀텀 의 한 예입니다. 시스템이 올바르게 작동하는 데 필요한 모든 구조적 요소를 포함하고 기능 응집도가 높아 독립적으로 배포 가능한 컴포넌트라는 뜻이고, 결국 스택은 자체적으로 프로덕션에 푸시할 수 있는 컴포넌트로 정의됩니다.
스택은 서버 인스턴스, 스택 코드 모듈, 라이브러리 같은 구성 요소로 만들어지지만, 스택 자체가 더 큰 환경의 컴포넌트가 되기도 합니다. 여기서 흔한 착각이 생깁니다.
모듈과 라이브러리는 코드를 재사용하게 해 주지만 스택을 쉽게 변경할 수 있게 만들지는 못합니다. 모놀리식 스택을 개선하겠다며 코드를 모듈로 분해하면 코드 추적성은 좋아지지만 각 스택 인스턴스는 여전히 크고 복잡합니다. 게다가 여러 스택에서 쓰이는 모듈은 그 스택들 사이에 결합을 새로 만들어서, 한 스택의 요구사항을 반영한 모듈 변경이 다른 스택에 영향을 미칩니다.
대규모 스택의 답은 모듈이 아니라 스택을 여러 스택으로 나누는 것입니다. 각 스택이 독립적으로 프로비저닝되고 관리되고 변경될 수 있어야 합니다.
스택에서 서버 사용하기 — 하드코딩 대신 파라미터
서버는 가장 흔한 스택 컴포넌트입니다. 컨테이너 호스트 노드 클러스터를 구축하는 스택 코드를 보면 서버 이미지 이름이 그대로 박혀 있습니다.
server_cluster:
name: "cluster_of_host_nodes"
min_size: 1
max_size: 3
each_server_node:
source_image: host_node_image
memory: 8GB문제는 이 스택의 첫 파이프라인 단계가 실제로 host_node_image를 필요로 하지 않는데도, 코드에 이미지 이름이 포함되어 있어 온라인 테스트 단계를 돌릴 수 없다는 점입니다. 무거운 호스트 노드 서버를 프로비저닝하지 않고 스택 코드의 문제만 빠르게 잡을 수 있다면 그편이 훨씬 유용합니다.
server_cluster:
name: "cluster_of_host_nodes"
min_size: 1
max_size: 3
each_server_node:
source_image: ${HOST_NODE_SERVER_IMAGE}
memory: 8GB온라인 테스트 단계에서 제거된 서버 이미지의 ID를 파라미터로 넣으면 클러스터가 올바르게 작동하는지, 확장·축소와 실패 인스턴스 복구가 되는지를 가볍게 검증할 수 있습니다. 이 제거된 이미지가 곧 테스트 더블입니다.
하드코딩된 참조를 파라미터로 바꾸는 것은 결합도를 낮추는 동시에 구성 규칙을 따르는 일이기도 합니다. 그래서 다른 서버 이미지로 클러스터 인스턴스를 만들어 다른 OS를 테스트하거나 점진적으로 교체하는 일이 쉬워집니다.
비공유 인프라
분산 컴퓨팅의 비공유 아키텍처(shared-nothing architecture)는 노드 외부 리소스에 대한 경합을 추가하지 않고 신규 노드를 붙일 수 있게 해 확장을 가능하게 합니다. 반대편에 있는 것이 여러 프로세서가 하나의 디스크를 공유하는 구조로, 공유 디스크에 대한 경합이 확장성의 상한을 만듭니다.
인프라에 적용하면, 공유 스택에 있던 리소스를 그것을 필요로 하는 각 스택으로 옮겨 공급자-소비자 관계 자체를 없애는 설계가 됩니다. 애플리케이션 인프라의 각 인스턴스가 고유한 네트워킹 구조 집합을 갖게 되므로 네트워킹 구조는 복제되지만 인스턴스끼리는 독립적입니다.
- 단일 공유 네트워킹 스택의 주소 공간에 묶이지 않으므로 확장 제한이 사라집니다
- 네트워킹 리소스를 더 쉽게 수정·리빌드·복구할 수 있습니다. 공유 네트워킹 스택은 폭발 반경과 관리 오버헤드를 키웁니다
- 스택을 개별적으로 보호할 수 있어 제로 트러스트 보안 모델에 맞습니다
한계는 모든 네트워킹 스택 인스턴스가 여전히 동일한 코드로 정의된다는 것입니다. 코드를 변경할 때 인스턴스가 손상되지 않도록 하는 추가 작업이 필요합니다.
컴포넌트 간 경계 — 이음새 찾기
인프라를 분할하려면 이음새(seam) 를 찾아야 합니다. 이음새는 해당 위치를 직접 수정하지 않고도 시스템의 작동을 변경할 수 있는 곳으로, 간단하고 깨끗한 통합점을 만들 수 있는 부품 사이의 경계입니다.
아래 전략들은 서로 다른 관심사를 기준으로 인프라 요소를 그룹화하지만, 결국 모두 변경 최적화 로 귀결됩니다.
| 기준 | 어떻게 나누는가 | 얻는 것 |
|---|---|---|
| 자연스러운 변경 패턴 | 변경 이력에서 함께 변경되는 것을 찾습니다. 티켓·스토리 같은 상위 작업보다 커밋 단위의 세부 변경이 가장 유용한 인사이트를 줍니다 | 이음새는 자연스러운 경계의 표현입니다. 함께 커밋되는 컴포넌트를 알면 응집도를 높이고 결합도를 낮추는 리팩터링 패턴이 보입니다 |
| 컴포넌트 생명 주기 | 매주 리빌드되는 서버 클러스터와 거의 바뀌지 않는 데이터베이스 스토리지를 별도 스택으로 나눕니다 | 단일 스택이면 앱 서버 이미지 업데이트가 실패했을 때 DB를 포함한 전체를 리빌드하고 데이터를 백업·복원해야 합니다. 분리하면 스택별 관리 이벤트(스토리지 스택 변경 시 백업 트리거)도 가능하고, 파이프라인 단계의 리빌드 시간이 짧아집니다 |
| 조직 구조 | 콘웨이의 법칙에 따라 여러 팀이 함께 변경해야 하는 컴포넌트를 만들지 않고, 역 콘웨이 전략으로 원하는 아키텍처 경계에 맞춰 팀을 구성합니다 | 인프라 인스턴스를 사용하는 팀에 맞춰 나누면 변경의 영향을 덜 받고 다운타임도 팀별로 정할 수 있습니다. 반대로 빌드와 실행을 별도 팀에 배치한 레거시 사일로는 프로덕션 경로 전반에 일관되지 않은 인프라를 만들어 시간·비용·위험을 키웁니다 |
| 복원력 | 독립적으로 배포 가능한 컴포넌트 단위로 리빌드·복구할 수 있게 설계합니다 | 수작업 “인프라 수술”의 대안입니다. 변경·업데이트에 쓰는 것과 동일한 자동화 프로세스로 스택 인스턴스를 리빌드할 수 있으면, 한밤중에 최고 전문가를 깨우지 않고 자동 복구를 트리거할 수 있습니다 |
| 확장성 | 확장이 필요한 컴포넌트만 여러 인스턴스로 만듭니다 | 제품 검색 서비스 스택만 여러 인스턴스를 배포하고 프런트엔드 트래픽 라우팅 스택과 데이터베이스 스택은 단일 인스턴스로 유지하면, 필요한 곳만 빠르게 확장하면서 전체 복제의 낭비를 줄입니다 |
| 보안과 거버넌스 | PCI 대상 결제 처리나 개인 데이터처럼 적용 규정이 다른 범위로 분할합니다 | 해당 컴포넌트에 어떤 조치가 취해졌는지 평가할 명확성이 생기고, 검토·승인·변경 보고서를 프로세스에 녹여 감사를 단순화할 수 있습니다 |
기능 계층이 아니라 서비스 단위로 자릅니다
전통적으로 많은 설계자는 시스템을 기능 단위로 묶었습니다. 네트워킹은 네트워킹끼리, 데이터베이스는 데이터베이스끼리, OS는 OS끼리 모으는 방식인데, 이 역시 팀이 기술 전문 분야로 조직된 결과라는 점에서 콘웨이의 법칙이 예측한 그대로입니다.
함정은 사용자에게 제공되는 서비스가 이 수평 계층을 가로지른다는 데 있습니다.
flowchart TB subgraph 수평["기술 계층별로 나뉜 스택"] direction LR A["애플리케이션"] B["서버"] C["데이터베이스"] D["네트워킹"] end 쇼핑["쇼핑 서비스"] --> A & B & C & D 검색["검색 서비스"] --> A & B & C & D
이 구조의 단점은 세 가지입니다. 하나의 서비스 인프라를 바꾸려면 여러 스택을 함께 바꿔야 하고, 이때 의존성이 공급자 스택에 반영되기 전에 소비자 스택으로 흘러들지 않도록 조심스럽게 조정해야 합니다. 단일 서비스의 인프라 소유권이 여러 팀에 흩어져 통신 오버헤드와 프로세스가 늘어납니다. 그리고 서비스 사이에 스택이 공유되므로, 한 서비스를 위해 서버를 바꾸면 스택 경계가 곧 폭발 반경이라 다른 서비스가 중단될 위험이 생깁니다.
네트워크 경계는 인프라 스택의 경계가 아니다
인프라를 프런트엔드·애플리케이션·데이터베이스 같은 네트워크 보안 영역으로 나누는 것은 네트워크 기반 공격을 막는 데 중요하지만, 인프라 코드를 배포 가능한 단위로 조직하는 기준으로는 적합하지 않습니다. 웹 서버와 로드 밸런서용 코드를 ‘프런트엔드’ 스택에 넣는다고 해서 애플리케이션 서버나 데이터베이스용 코드를 악의적으로 변경하는 것에 대한 방어 계층이 생기지는 않습니다. 인프라 코드와 도구를 악용하는 위협 모델은 네트워크를 공격하는 위협 모델과 다르기 때문입니다.
비교 / 트레이드오프
큰 스택을 다루는 두 가지 방법을 혼동하지 않는 것이 이 장의 실질적인 핵심입니다.
| 축 | 공유 코드 모듈로 재사용 | 스택을 여러 스택으로 분할 |
|---|---|---|
| 해결하는 문제 | 코드 중복과 추적성 | 스택 인스턴스 자체의 크기와 복잡성 |
| 스택 인스턴스 | 코드만 정리될 뿐 여전히 크고 복잡 | 각 스택을 독립적으로 프로비저닝·변경 |
| 결합 | 같은 모듈을 쓰는 스택끼리 새로운 결합이 생김 | 스택 간 인터페이스로 결합을 명시적으로 관리 |
| 폭발 반경 | 모듈 변경이 소비하는 모든 스택으로 파급 | 스택 경계가 곧 폭발 반경 |
| 테스트 | 스택 전체를 프로비저닝해야 검증 가능 | 스택별로 가볍게 검증하고 통합 문제만 따로 확인 |
내 생각
- “재사용은 결합도를 증가시킨다”가 이 장에서 가장 값진 한 줄입니다. Terraform 모듈을 만들 때 습관적으로 공통화부터 하는데, 그 모듈이 팀 간 릴리스 협의를 강제하는 순간 중복 제거로 아낀 시간보다 조율 비용이 커집니다.
- 모듈 분해와 스택 분할은 다른 문제를 푼다는 구분이 실무에서 자주 뭉개집니다. plan/apply가 느려서 괴로운 상황은 모듈을 아무리 잘게 쪼개도 해결되지 않고, state를 나눠야 풀립니다.
- 커밋 이력으로 경계를 찾으라는 조언은 즉시 실행 가능합니다.
git log로 함께 변경되는 파일 쌍을 세어 보면 설계 의도가 아니라 실제 변경 패턴이 드러나고, 그게 진짜 이음새입니다. - 수평 그룹화 문제는 인프라팀·DBA팀·네트워크팀으로 나뉜 조직에서 그대로 재현됩니다. 서비스 하나를 배포하는 데 세 팀의 티켓이 필요하다면 그건 프로세스 문제가 아니라 경계를 잘못 그은 아키텍처 문제입니다.
- 하드코딩을 파라미터로 빼는 것이 테스트 가능성으로 직결된다는 연결이 좋습니다. 결합도를 낮추라는 추상적 조언보다 “이미지 없이 스택 코드를 테스트할 수 있는가”라는 질문이 훨씬 구체적인 설계 압력입니다.
관련 개념
- Ch05 코드로 인프라 스택 구축하기 — 스택의 크기·범위 패턴과 모놀리식 스택 안티패턴
- Ch07 스택 인스턴스 구성하기 — 하드코딩을 걷어내는 스택 파라미터
- Ch09 인프라 스택 테스트하기 — 테스트 더블과 오프라인·온라인 테스트 단계
- Ch13 코드형 서버 이미지 — 스택의 공급자가 되는 서버 이미지 파이프라인
- Ch14 코드형 클러스터 구축 — 모놀리식 스택을 멀티 스택으로 분리한 실제 사례