한 줄 정의

서버의 내용물(소프트웨어·구성·데이터)을 코드로 정의하고, 이미지에 미리 구워 둘 것(베이킹)과 생성 시점에 적용할 것(플라잉)을 나누어 일관된 서버를 반복 생성하는 방법입니다.

쉽게 말하면

주문이 몰리는 식당을 생각하면 됩니다. 손님이 몰릴 때마다(트래픽 급증, 장애 복구) 반죽부터 시작하면 너무 늦고, 반대로 완제품을 전부 만들어 쌓아 두면 신선도(최신 패치·버전)가 떨어지고 레시피가 바뀔 때마다 재고를 다시 만들어야 합니다.

그래서 실제 주방은 오래 걸리고 잘 안 바뀌는 밑준비(육수, 소스)는 미리 해 두고, 자주 바뀌는 마무리(토핑, 플레이팅)는 주문 즉시 합니다. 서버 구축도 똑같습니다. 어디까지 이미지에 미리 구워 두고(베이킹) 무엇을 생성 시점에 적용할지(플라잉)를 정하는 것이 이 장의 중심 질문입니다.

왜 중요한가?

컨테이너와 서버리스 시대에도 서버는 사라지지 않았습니다. 클러스터를 운영하는 팀조차 호스트 노드용 서버를 구축·실행해야 하고, 서버는 움직이는 부품과 변형이 많아 네트워크·스토리지보다 복잡하므로 시스템팀은 여전히 OS·패키지·구성 파일 관리에 상당한 시간을 씁니다.

이 과정을 수작업이나 실행 중 서버 복제로 때우면 서버마다 이력이 다른, 재현 불가능한 서버가 쌓이고 이는 구성 드리프트의 원인이 됩니다. 서버를 코드로 구축해야 “동일한 서버를 몇 대든, 언제든” 만들 수 있습니다.

핵심 내용

서버 생명 주기

flowchart LR
    img["서버 이미지"] -->|"생성과 구성"| run["서버 인스턴스 실행"]
    run -->|"변경"| run
    run -->|"삭제"| del["폐기"]

생성과 구성 → 실행 중 변경 → 삭제, 세 가지 전환 단계가 있고 이 장은 첫 단계인 생성과 구성을 다룹니다.

서버 안에는 무엇이 있나 — 소프트웨어·구성·데이터

유형내용구성 관리 도구의 취급
소프트웨어앱·라이브러리·코드. 시스템마다 달라지지 않는 정적 파일 (예: Linux time zone 데이터)모든 관련 서버에서 동일하게 유지. 내부 내용은 상관하지 않음
구성시스템·앱의 동작을 제어하는 파일. 역할·환경·인스턴스마다 다르며 인프라의 일부로 관리파일 콘텐츠를 직접 빌드하여 일관성 보장
데이터시스템·앱이 생성·변경하는 파일 (DB 데이터 파일, 로그)블랙박스. 배포·백업·복제는 책임질 수 있으나 내용은 관리하지 않음

구분 기준은 자동화 도구가 파일 내용을 관리하는가 입니다. 시스템 로그가 인프라에 필수여도 도구가 내용을 만들지 않으므로 데이터고, 애플리케이션이 파일에 저장하는 사용자 계정·기본 설정도 인프라 관점에서는 데이터입니다.

서버 내용의 출처

  • 기본 OS: 물리 디스크, ISO, 서버 이미지의 설치 이미지
  • OS 패키지 저장소: 벤더·서드파티·사내 저장소에서 패키지 다운로드
  • 언어·프레임워크 저장소: Java 라이브러리, Ruby gem 등
  • 비표준 패키지: 자체 설치 프로그램이나 추가 단계가 필요한 소프트웨어
  • 외부 유형: 사용자 계정 추가, 로컬 방화벽 규칙처럼 컴포넌트 밖의 파일 추가·수정

서버 구성 도구 — Pull/Push는 도구의 제약이 아니다

Ansible, CFEngine, Chef, Puppet, Saltstack 같은 1세대 코드형 인프라 도구는 자동화된 서버 구성에 집중합니다. 대부분 각 서버의 에이전트가 주기적으로 동기화하는 Pull 패턴이나, 중앙 서버가 각 서버에 접속하는 Push 패턴을 쓰는데 도구가 패턴을 강제하지는 않습니다. Push 설계인 Ansible을 서버에 미리 설치해 크론으로 돌리면 Pull이 되고, Pull 에이전트를 제공하는 Chef·Puppet을 중앙 서버에서 로그인해 실행하면 Push처럼 쓸 수 있습니다.

벤더들은 구성 코드를 제공하는 저장소 서버(Ansible Tower, Chef Server, Puppetmaster)에 구성 레지스트리·대시보드·중앙 실행을 얹어 제공합니다. 올인원 에코시스템은 팀 업무를 단순화하지만, 여러 도구를 섞어 쓴다면 특정 도구에 묶인 레지스트리보다 독립형 범용 구성 레지스트리가 낫습니다.

모듈 — 서버 구성 코드의 단위

서버 구성 코드도 코드라서 방치하면 지저분해지며, 도구들은 이를 막을 모듈 단위(Ansible 플레이북, Chef 쿡북, Puppet 모듈)를 제공합니다. 모듈은 개별적으로 구성·버전화·릴리스할 수 있습니다.

  • 관심사 분리: 하나의 관심사(보통 애플리케이션 하나) 중심으로 모듈을 설계합니다. Tomcat 모듈이라면 소프트웨어 설치, 실행 계정·그룹, 권한이 설정된 폴더, 포트·설정을 포함한 구성 파일 빌드, 로그 집계·모니터링 통합까지 담당합니다
  • 라이브러리 모듈 vs 애플리케이션 모듈: 라이브러리 모듈은 파라미터를 받는 재사용 가능한 핵심(예: 범용 Tomcat 모듈), 애플리케이션(래퍼) 모듈은 라이브러리를 가져와 구체적 목적의 파라미터를 설정합니다 (제품 카탈로그용 Tomcat, 고객 관리용 Tomcat이 공유 라이브러리를 재사용)
  • 버전 관리와 의존성: 모든 모듈을 한 단위로 버전화·프로모션하거나 모듈별로 독립 딜리버리합니다. 독립 관리 시 디스크립터(Chef 쿡북 메타데이터의 Depends 필드)로 의존성을 선언하며, RPM·pip 같은 패키지 관리 시스템과 동일한 방식으로 동작합니다

역할(role) — 모듈 그룹에 이름 붙이기

역할은 서버의 용도별로 적용할 모듈 그룹과 기본 파라미터를 정의합니다. 세분화된 역할 여러 개를 조합하거나(ApplicationServer + MonitoredServer + PublicFacingServer), 서버당 구체적인 역할 하나만 부여할 수 있습니다(ShoppingServiceServer, JiraServer). 일반적인 접근은 역할 상속 입니다.

Role: base-role                  # 모든 서버 공통
server_modules:
  - monitoring_agent
  - logging_agent
  - network_hardening
 
role: application-server
include_roles:
  - base_role
server_modules:
  - tomcat
parameters:
  - inbound_port: 8443           # network_hardening이 이 포트만 열고 나머지를 잠금
 
role: shopping-service-server
include_roles:
  - application-server
server_modules:
  - shopping-service-application

서버 코드 테스트

  • 점진적 테스트: 모듈마다 커밋 시 실행되는 개별 테스트 단계를 두고, 역할은 통합 테스트 단계를 둡니다. 역할 테스트는 포함된 모듈이 변경되어 통과할 때와 역할 코드 자체(모듈 추가·제거, 파라미터 변경)가 바뀔 때 모두 실행됩니다
  • 무엇을 테스트할까: 선언형 코드는 코드를 되풀이하는 테스트가 되기 쉽습니다. 실행 시 잘못될 가능성(명령이 기본 경로에서 실행되는가), 파라미터에 따른 결과 차이(Java 배포판별 설치 검증), 코드 조합에 집중합니다. 통합하는 항목이 많을수록 테스트 가치가 커지므로 개별 모듈보다 역할 수준 테스트에 무게를 둡니다 — Tomcat 모듈과 보안 강화 모듈의 충돌 같은 문제는 역할 테스트에서 드러납니다
  • 방법: 서버·컨테이너 인스턴스에서 명령을 실행해 패키지·파일·계정·프로세스의 존재와 상태를 확인합니다 (Inspec, Serverspec, Terratest). 컨테이너나 로컬 VM으로 오프라인 테스트가 가능하고, 파이프라인과 로컬이 같은 전제 조건 설정 스크립트를 써야 결과가 일관됩니다

신규 서버 인스턴스 생성 방법

전체 프로비저닝은 리소스 할당 → OS·초기 소프트웨어 설치 → 추가 구성 적용 → 네트워크 리소스·정책 연결 → 서비스 등록(모니터링 등)을 포함하며, 인스턴스 생성을 트리거하는 방법은 여러 가지입니다.

방법내용비고
수동 (UI·CLI)매번 소스 이미지·리소스·네트워크 옵션 선택재현 불가
스크립트CLI 명령을 스크립트로 감쌈재현 가능하지만 스크립트 개선·수정에 시간을 소모
스택 관리 도구스택 코드로 서버 정의오류 검사 로직 내장, ${APPSERVER_VNET}처럼 다른 인프라 리소스와 통합
플랫폼 자동 생성자동 확장·자동 복구 규칙을 스택 코드로 정의아래 예제 참고
네트워크 프로비저닝베어메탈: PXE 부팅 → 부트스트랩 OS → OS 설치 이미지 복사 → 재부팅Crowbar, Cobbler, FAI, Foreman, MAAS, Tinkerbell 등

자동 생성 정의는 유지 대수·확장 지표·상태 확인까지 하나의 스택 코드로 선언합니다.

server_cluster:
  server_instance:
    source_image: stock-linux-1.23
    memory: 2GB
    vnet: ${APPSERVER_VNET}
  scaling_rules:
    min_instances: 2
    max_instances: 5
    scaling_metric: cpu_utilization
    scaling_value_target: 40%
  health_check:
    type: http
    port: 8443
    path: /health
    expected_code: 200
    wait: 90s

CPU 사용률을 40%에 가깝게 유지하도록 2~5대 사이에서 인스턴스를 넣고 빼며, 90초 안에 헬스체크 응답이 없으면 실패로 간주해 서버를 삭제하고 새로 만듭니다.

서버 프로비저닝에 도움이 되는 FaaS 이벤트

플랫폼은 신규 인스턴스 생성 전·중·후 여러 지점에서 FaaS 서버리스 코드를 실행할 수 있습니다. 신규 서버에 보안 정책 할당, 모니터링 서비스 등록, 서버 구성 도구 실행 같은 일을 여기에 걸 수 있습니다.

서버 사전 빌드 — 핫 클로닝에서 클린 이미지로

방법내용문제
핫 클로닝실행 중인 서버를 그대로 복제원본의 로그·프로덕션 참조(예: 프로덕션 DB 연결)가 딸려 오고, 시점에 따라 결과가 달라 재현 불가 → 구성 드리프트의 원인
서버 스냅샷실행 중 서버의 정적 이미지를 떠서 생성시작 이미지와의 동일성은 보장되지만 원본 오염은 그대로
클린 서버 이미지시스템에 편입되기 전의 깨끗한 원본에서 이미지 생성없음 — 모든 신규 서버가 깨끗해짐

클린 이미지 빌드 프로세스는 알려진 원본(OS 벤더 설치 이미지, 플랫폼 제공 이미지)에서 인스턴스 생성 → 구성 코드로 초기 설정(표준 패키지·에이전트, 보안 강화, 최신 패치) → 스냅샷을 이미지로 표시(태그·버전 관리)입니다. 이것이 흔히 말하는 골든 이미지 이며, 체크리스트 기반 수동 빌드보다 프로세스를 자동화하고 이미지를 코드로 관리하는 편이 낫습니다.

구성 적용 시점 — 베이킹 vs 플라잉

구성은 서버 이미지를 만들 때(베이킹), 이미지에서 인스턴스를 만들 때(플라잉), 서버가 실행 중일 때 적용할 수 있습니다. 실행 중 적용은 주로 서버 변경(보안 패치, 수작업 변경 복구) 목적이고, 신규 서버 관점의 선택지는 앞의 두 가지입니다.

  • 플라잉 극대화: 이미지를 최소로 유지하고 생성 시 전부 적용합니다. 신규 서버가 항상 최신 패치·패키지·구성을 받고, 하드웨어·OS 조합당 이미지 1개면 되어 이미지 관리가 단순해집니다. 고객마다 서버 구성이 다른 호스팅 회사처럼 서버 가변성이 높을 때 유리합니다
  • 베이킹 극대화: 거의 전부를 이미지에 넣고 인스턴스별 차이만 적용합니다. 서버 생성이 매우 빠르고 간단해, 유사한 서버를 많이 자주 빠르게 만들어야 할 때 유리합니다. 대신 이미지 업데이트·배포 자동화가 필요하고, 자동화가 있어도 새 이미지 빌드·릴리스에 보통 10~60분이 걸립니다

실제로 대부분의 팀은 둘을 결합하며, 기준은 소요 시간과 변경 빈도 입니다. 오래 걸리고 잘 안 바뀌는 것은 베이킹(애플리케이션 서버 소프트웨어), 빠르고 자주 바뀌는 것은 플라잉이 낫습니다. 사내 개발 애플리케이션이 대표적인데, CI가 하루 수십 개 빌드를 밀어내는 팀이 빌드마다 이미지를 굽는 것은 너무 느리므로 테스트 서버 생성 시 앱을 배포하는 편이 효율적입니다.

또 다른 결합 방식은 최대한 베이킹하되, 이미지를 다시 굽기 전까지의 업데이트(보안 패치, 구성 개선)만 생성 프로세스에서 베이킹된 이미지 위에 플라잉으로 얹는 것입니다. 업데이트된 이미지를 구울 때가 되면 해당 항목을 생성 프로세스에서 제거합니다.

서버 생성 시 구성 적용

서버를 생성하는 대부분의 도구는 서버 구성 코드를 적용하는 방법을 함께 제공합니다. 스택 코드의 configure 블록으로 구성 도구·코드 저장소·역할·파라미터(app_name, app_version)를 지정하는 식입니다. 일부 도구는 구성 코드를 스택 코드나 셸 명령어에 직접 넣을 수도 있지만, 대부분 코드의 크기와 복잡성이 커지므로 서버 구성 코드는 스택 코드로부터 분리해 추출하는 것이 좋습니다.

비교 / 트레이드오프

플라잉베이킹
생성 속도느림 — 생성 시마다 다운로드·구성빠름 — 인스턴스별 차이만 적용
최신성항상 최신 패치·버전이미지가 낡음 — 주기적 리베이크 필요
네트워크 효율20대면 같은 패치를 20번 다운로드이미지 빌드 시 1번
런타임 의존성저장소가 다운되면 서버 생성 불가이미지만 있으면 생성 가능
이미지 관리 부담최소 — OS 조합당 1개빌드·배포 파이프라인 필요, 1회 10~60분
적합 상황서버 가변성이 높거나 자주 바뀌는 구성유사 서버 다수, 빠른 스핀업·장애 복구

내 생각

  • 베이킹/플라잉은 AWS의 AMI + user data, 컨테이너의 이미지 레이어 + 기동 시 주입으로 그대로 재현됩니다. Packer로 굽고 cloud-init으로 튀기는 조합이 그 표준 구현이고, Dockerfile 빌드는 베이킹, 컨테이너 기동 시 환경 변수·구성 주입은 플라잉입니다.
  • 역할 상속은 Dockerfile의 FROM 체인과 같은 패턴입니다. base → application-server → shopping-service 계층은 베이스 이미지 → 미들웨어 이미지 → 앱 이미지 계층과 정확히 대응하며, 공통 강화·에이전트를 어디에 두는가라는 설계 질문도 동일합니다.
  • “통합할수록 테스트 가치가 커진다”는 선언형 코드에서 테스트 피라미드를 뒤집는 근거입니다. 선언형 모듈의 단위 테스트는 코드 복창이 되기 쉬우므로, 테스트 자원은 모듈 간 상호작용이 드러나는 역할(통합) 수준에 배분하는 것이 합리적입니다.
  • 클린 이미지 원칙은 불변 인프라의 전제 조건입니다. 스냅샷·클로닝 기반 이미지는 “동일한 서버 N대”를 보장하지 못하므로, 이미지 파이프라인의 출발점은 항상 알려진 원본이어야 합니다.

관련 개념