한 줄 정의

서버 변경을 예외가 아닌 일상 이벤트로 만들어, 언제 적용할지(지속적인 동기화 vs 불변 서버 교체)와 어떻게 전달할지(Push vs Pull)를 패턴으로 관리하는 방법입니다.

쉽게 말하면

스마트폰을 관리하는 세 가지 방식을 생각하면 됩니다. 문제가 생겼을 때만 손대는 폰은 어느새 앱과 OS 버전이 제각각이 되어, 막상 업데이트하려면 꼬인 것부터 풀어야 합니다(변경 중 적용). 자동 업데이트를 켜 두면 폰이 매일 밤 스스로 최신 상태를 맞추고, 내가 임의로 바꿔 둔 것도 제자리로 돌아옵니다(지속적인 동기화). 아예 폰 설정은 건드리지 않고 주기적으로 새 폰으로 갈아타며 데이터만 옮기는 사람도 있습니다(불변 서버).

핵심은 변경을 특별한 사건이 아니라 매일 일어나는 일상으로 만드는 것입니다. 예외는 자동화할 수 없지만, 일상은 자동화할 수 있습니다.

왜 중요한가?

많은 조직이 서버를 구축하는 프로세스와 도구에는 투자하면서, 문제 해결·보안 패치·업그레이드를 위한 변경은 비정상적인 이벤트로 취급합니다. 모든 변경이 예외라면 자동화할 수 없고, 그 결과 일관성 없고 불안정한 시스템과 “재미없는 유지보수 ↔ 긴급 장애 대응”을 오가는 운영만 남습니다.

시스템에서 유일하게 변하지 않는 점은 시스템이 변한다는 것입니다. 시스템이 코드와 파이프라인을 통해서만 생성·변경되면 자주 쉽게 변경할 수 있고, 일관성이 보장되며, 정책 준수를 확인할 수 있습니다.

핵심 내용

변경 적용 시기 — 안티패턴 하나, 패턴 둘

안티패턴 — 변경 중 적용 (apply on change)

적용해야 할 변경이 있을 때만 구성 코드를 서버에 적용하는 방식으로, 임시(ad hoc) 자동화 라고도 부릅니다. 신규 서버를 만들 때만 Ansible 플레이북을 돌리고, 신규 Tomcat 버전이 나오면 그제서야 플레이북을 갱신해 적용하는 식입니다. 가장 극단적인 형태는 문제가 있는 특정 서버에만 코드를 적용하는 것입니다.

  • 오랫동안 적용하지 않은 코드는 그 사이 쌓인 서버와의 차이 때문에 막상 적용할 때 실패합니다
  • 서버는 계속 변합니다 — 누군가 장애를 수동으로 잡고, 다른 사람이 OS·패키지를 패치합니다. 작은 변경이라 일주일 뒤에는 아무도 기억하지 못합니다
  • 일부 서버에만 적용하면 최악입니다. 나중에 전체 서버에 다른 변경을 적용할 때 이전의 서버별 변경(예: 특정 서버의 Tomcat 성능 최적화)이 나머지 서버에도 함께 딸려 가고, 원인을 기억하는 사람이 없어 디버깅에 훨씬 오랜 시간이 걸립니다

본질은 수동 관리에 익숙한 사람이 IaC 도구를 “문법이 어색한 스크립트 도구”로 취급하는 것입니다. 파이프라인 대신 로컬 컴퓨터에서 직접 도구를 실행합니다.

패턴 — 지속적인 구성 동기화 (continuous configuration synchronization)

코드 변경 여부와 관계없이 일정에 따라 구성 코드를 서버에 반복 적용합니다. 예기치 않은 차이가 되돌려지거나 표면화됩니다.

“코드를 안 바꿨으면 다시 적용할 필요가 없다”고 믿고 싶지만, 서버와 서버 코드는 교활합니다.

  • 누군가 로그인해 “사소하니까 안전하겠지”라며 수동 변경을 합니다
  • 팀이 보안 패치 같은 일부 영역을 별도 도구·프로세스로 관리합니다
  • 코드가 참조하는 중앙 구성 레지스트리의 파라미터가 바뀌면 같은 코드가 다른 결과를 냅니다
  • 패키지 버전을 고정하지 않으면 저장소의 신규 버전이 설치되고, 고정하면 알려진 취약점이 있는 낡은 패키지가 쌓이거나 버전 번호 수동 관리에 막대한 노력이 들어갑니다

불변 서버보다 구현이 쉽고, Ansible·Chef·Puppet 등 주류 도구가 이 패턴을 염두에 두고 설계되었습니다. 기존 인스턴스를 업데이트하는 것이 신규 구축보다 빠르고 중단이 적습니다. 구현은 대부분 정해진 일정으로 실행되며, 모든 서버가 동시에 돌지 않도록 실행 시간을 분산합니다(예: chef-client --splay).

대신 자동 프로세스가 전체 서버에 구성을 계속 적용하므로 잘못된 코드가 전체를 손상시킬 수 있습니다. 효과적인 모니터링과, 프로덕션 적용 전에 코드를 테스트·전달하는 프로세스가 전제 조건입니다.

패턴 — 불변 서버 (immutable server)

실행 중인 인스턴스에는 변경을 적용하지 않고, 변경된 구성으로 신규 인스턴스를 만들어 기존 서버를 교체함으로써 변경을 전달합니다.

  • 변경 위험이 줄어듭니다. 신규 인스턴스를 테스트한 뒤 교체하고, 원본 제거 전에 동작을 확인하며, 문제가 생기면 원래대로 복구할 수 있습니다
  • 전제 조건이 무겁습니다. 서버 이미지를 빌드·테스트·업데이트하는 자동화 파이프라인과, 중단 없이 인스턴스를 교체할 수 있는 시스템 설계가 반드시 필요합니다
  • 베이킹을 선호합니다. 구성 대부분을 이미지에서 처리하고, 더 엄격한 형태는 생성 후 플라잉조차 하지 않고 이미지를 만들어 테스트한 뒤 환경에서 환경으로 승격합니다. 인스턴스마다 차이가 거의 없어 환경 이동 시 문제가 생길 위험이 적습니다
  • ‘불변’은 은유입니다. 로그·메모리·프로세스 공간은 변합니다. 요점은 구성 변경을 인스턴스에 가하지 않는다는 것입니다

함정

프로세스를 우회해 로그인·수작업 변경을 하면 구성 드리프트가 발생합니다. 또 서버를 오래 교체하지 않으면 이후에 빌드된 서버가 받은 패치·개선이 빠진 채 실행됩니다 — 변경 중 적용 안티패턴과 결합된 꼴입니다. 서버 접근을 비활성화한다면 긴급 상황용 브레이크 글라스(break glass) 프로세스를 함께 고려해야 합니다.

패치도 같은 파이프라인으로

별도의 특별한 패치 프로세스를 두는 대신, 서버 코드를 지속적으로 동기화하는 파이프라인이 있다면 동일한 프로세스로 패치를 전달할 수 있습니다. OS·핵심 패키지의 최신 보안 패치를 매주(때로는 매일) 가져와 테스트·제공한 팀은, 언론이 핵심 OS 패키지의 취약점을 보도했을 때 “그 패치는 오늘 아침 정기 업데이트로 이미 배포됐다”고 답할 수 있었습니다.

구성 코드를 적용하는 방법 — Push vs Pull

지속적인 동기화든 신규 인스턴스 구축이든, 구성 도구를 실행해 코드를 적용하는 방식은 두 가지입니다.

flowchart LR
    subgraph push["Push 서버 구성"]
        central["중앙 서버 (CI·CD)"] -->|"SSH 접속 후 코드 실행"| s1["서버 인스턴스"]
    end
    subgraph pull["Pull 서버 구성"]
        s2["서버 인스턴스<br/>(도구 사전 설치)"] -->|"코드 다운로드 후 적용"| repo["중앙 저장소"]
    end
PushPull
실행 주체인스턴스 외부 프로세스가 접속해 실행인스턴스 내부 프로세스가 스스로 실행
이미지 요구사항구성 도구 사전 설치 불필요도구가 설치된 이미지 필요 (퍼블릭 클라우드 이미지 대부분 cloud-init·주요 도구 내장)
보안네트워크 접속 경로 자체가 공격 벡터인바운드 연결 불필요 — 공격 범위 축소
자동 생성 인스턴스자동 확장·자동 복구로 생긴 인스턴스에 어색함부팅 시 스스로 구성 — 궁합이 좋음
적합 상황여러 서버에 걸친 배포처럼 시기·순서를 중앙에서 조율할 때자동 확장·복구 환경, 상시 동기화
구현중앙 CI·CD 서버의 작업·파이프라인 단계, Ansible Tower 류cloud-init로 첫 부팅 시 실행, cron·백그라운드 프로세스로 동기화, Saltstack은 이벤트 버스
Push의 핵심 과제 — 인증과 키 관리

로컬 컴퓨터에서 도구를 실행할 수도 있지만, 일관성과 제어를 위해 중앙 서버·서비스에서 실행하는 것이 바람직합니다. 대부분 SSH로 접속하므로 강력한 인증과 비밀 관리가 필수이며, 그렇지 않으면 서버 구성 시스템 자체가 큰 보안 취약점이 됩니다.

  • 모든 서버에 단일 키: 가장 간단하지만 키가 노출되면 해커가 모든 서버에 접근합니다
  • 인스턴스별 고유 키: 신규 인스턴스 생성 시 동적으로 발급하고 불필요해지면 삭제합니다
  • 시스템을 보안 영역으로 나누고 구성 서비스 인스턴스마다 한 영역에만 접근하게 해 침해 범위를 줄입니다
Pull의 구현 — cloud-init

이미지에 사전 설치된 도구를 첫 부팅 시 실행하도록 구성하며, cloud-init이 이런 프로세스 자동화에 널리 쓰입니다. 인프라 플랫폼 API로 실행할 명령과 파라미터를 전달합니다.

server:
  source_image: stock-linux-1.23
  memory: 2GB
  vnet: ${APPSERVER_VNET}
  instance_data:
    - server_tool: servermaker
    - parameter: server_role=appserver
    - parameter: code_repo=servermaker.shopspinner.xyz

기존 서버를 지속적으로 동기화하는 경우에도 백그라운드 프로세스든 cron 작업이든, 중앙 저장소에서 구성 코드를 다운로드해 실행하도록 구성합니다.

구성 분산 — 중앙 서비스 없이 운영하기

중앙 구성 서버는 관리가 필요한 또 하나의 영역이고, 단일 장애점(다운되면 신규 시스템 구축 불가)이자 수백~수천 인스턴스 연결을 처리하는 성능 병목이 될 수 있습니다. 그래서 일부 팀은 chef-client 대신 chef-solo 같은 오프라인 모드로 도구를 실행하고, 웹 서버·S3 같은 단순하고 신뢰성 높은 정적 파일 저장소에서 코드를 내려받습니다. 코드가 인스턴스에 로컬로 저장되므로 저장소 장애 시에도 도구를 실행할 수 있습니다. 구성 코드를 .rpm·.deb 패키지로 묶어 사설 저장소에 올리고 yum update·apt-get update로 배포하는 방법도 있습니다.

서버 생명 주기 이벤트 — 중지·교체·복구

생성·변경·삭제라는 기본 생명 주기 외에, 연장된 생명 주기에는 세 가지 이벤트가 더 있습니다.

이벤트내용포인트
중지·재시작하드웨어 재구성, OS 커널 업그레이드, 비용 절약을 위한 종료중지된 서버에는 구성 업데이트·패치가 적용되지 않아 재시작 시 누락될 수 있음. 리빌드가 쉽다면 삭제 후 재생성이 비용도 더 절약됨
교체신규 인스턴스 생성 → 준비 확인 → 다른 인프라 재구성 → 테스트 → 이전 인스턴스 제거불변 서버 등 이 책의 많은 패턴이 의존하는 기본 동작. 파이프라인에서는 ‘확장과 축소’ — 추가하는 변경을 먼저 푸시하고 제거하는 변경을 나중에 푸시
실패 복구교체와 같은 프로세스, 순서만 반대 — 서버가 먼저 죽고 그 후에 신규 생성클라우드는 예고 없이 인스턴스를 종료할 수 있음(AWS 인스턴스 폐기 정책). 교체는 의도적이지만 실패는 아님 — 예외는 복구 가능성을 증명하려고 일부러 실패를 만드는 카오스 엔지니어링

서버를 삭제·리빌드하는 대신 중지하고 유지하는 것은 서버를 가축이 아니라 반려동물로 취급하는 것입니다. 팀이 중지·재시작을 선택하는 진짜 이유는 자신 있게 리빌드할 수 없기 때문이고, 자주 걸리는 문제는 애플리케이션 데이터의 유지·복원입니다. 서버를 중지했다가 재시작하지 않는 정책을 세우면 팀은 리빌드를 위한 안정적인 프로세스와 도구를 갖출 수밖에 없습니다.

비교 / 트레이드오프

지속적인 구성 동기화불변 서버
변경 전달실행 중인 서버에 코드 반복 적용신규 인스턴스 생성 후 교체
구현 난이도낮음 — 주류 도구의 기본 설계높음 — 이미지 파이프라인과 무중단 교체 설계 필수
변경 위험자동 적용이 전체 서버를 손상시킬 수 있음낮음 — 교체 전 테스트, 문제 시 이전 인스턴스로 복구
드리프트 대응반복 적용으로 되돌리거나 표면화교체 주기가 길어지면 패치 누락 위험
적합 상황대부분의 팀의 현실적 출발점엄격한 제어와 일관성이 필요한 조직 (예: 수천 개 서버 이미지를 운영하는 통신 회사)

두 패턴 모두 Push 또는 Pull 구성으로 구현되며, 서로가 서로의 대안입니다. 불변 서버는 불변 인프라의 하위 집합입니다.

내 생각

  • 컨테이너는 불변 서버 패턴의 극단적 승리입니다. Kubernetes에서 파드는 수정하지 않고 교체합니다. VM 시대의 지속적인 동기화는 이미지 빌드에 수십 분 걸리던 시절의 타협이었고, 빌드가 초 단위로 내려온 컨테이너에서는 교체 비용이 낮아 불변 쪽이 기본값이 됐습니다.
  • 변경 중 적용 안티패턴의 갈림길은 도구가 아니라 프로세스입니다. Ansible을 쓰느냐가 아니라 파이프라인이 코드를 적용하느냐 사람이 적용하느냐가 본질이며, “IaC 도구를 도입했는데 여전히 드리프트가 쌓이는” 조직은 대부분 여기에 해당합니다.
  • 패치를 정규 딜리버리에 태우면 보안 대응 속도가 곧 평상시 배포 속도가 됩니다. 취약점 공개 때만 가동되는 비상 패치 프로세스는 훈련되지 않은 프로세스라 느리고 위험합니다.
  • 자동 확장 환경에서 Push가 어색한 이유는 인스턴스 생성 주체가 플랫폼이기 때문입니다. 생성 시점을 사람이 모르는데 외부에서 접속해 구성할 수는 없으므로, AWS의 user data + cloud-init(Pull)이 사실상 표준이 됩니다.

관련 개념