· 6105 words · 29 min read

5. 실행#

플레이북은 몇 달 동안 완벽하게 실행되고 있었다: 랩의 액세스 스위치 10대, 처음부터 끝까지 2분, 매번 깔끔한 결과. 팀이 800대 스위치의 전체 인벤토리로 출시하기로 결정했을 때, 아무도 문제를 예상하지 않았다. 처음 600대는 문제없이 업데이트됐다. 그다음 작업이 느려졌다. 그다음 멈췄다. 갑자기 150개의 동시 SSH 인증 요청을 받은 RADIUS 서버가 연결을 거부하기 시작했다. Ansible이 150대 장비에서 실행 도중 중단됐다. 엔지니어가 작업을 강제 종료했다.

이어진 것이 더 어려운 문제였다: 어떤 600대가 업데이트됐고 어떤 150대가 안 됐는지 아무도 알지 못했다. 실행 상태가 기록되지 않았다. 플레이북을 재실행하고 멱등성이 구해주기를 바랐다, 그번은 구해줬다. 하지만 이 사고는 중요한 것을 드러냈다: 실행 레이어가 랩을 위해 설계됐지, 네트워크를 위해서가 아니었다. 속도, 병렬성, 오류 경계, 상태 추적, 그 무엇도 고려되지 않았다. 자동화는 작동했지만, 실행 아키텍처는 그렇지 않았다.

대부분의 사람들은 네트워크 자동화를 이렇게 생각한다: “동일한 데이터를 가져다가, CLI에서 사람으로 연결하지 않고, 네트워크에서 무언가를 한다.” 대부분의 사람들이 네트워크 자동화를 시작할 때 거기서 시작한다고 장담할 수 있다. 그렇다, 그것이 실행 블록이 하는 것이다. 하지만 이 책 전반에서 봤듯이, 네트워크 자동화는 이 첫 번째 단계보다 크고, 실행은 아키텍처 내의 한 구성 요소일 뿐이다.

실행 블록은 가장 위험한 작업 (예: 설정 변경이나 재부팅)을 위해 네트워크와 직접 상호작용한다. 그러니 이 장을 건너뛰지 말라; 올바르게 하는 것이 중요하다.

이 장에서는 이 블록이 제공하는 목표와 기둥, 그리고 달성하기 위해 필요한 내부 기능들을 다룰 것이다.

5.1. 기초#

5.1.1. 컨텍스트#

실행은 행동을 어떻게 실행할지 정의한다. 무엇을은 인텐트 블록에서 오고, 언제는 오케스트레이션에서 온다.

초기 네트워크 자동화 프로젝트에서, 주어진 장비와 인터페이스 이름에 대해 인터페이스를 재시작하는 스크립트가 첫 번째 성과가 될 수 있다. 그 여정은 훨씬 더 정교한 시스템으로 성장할 수 있다.

5.1.2. 목표#

실행 시스템이 실제로 해야 하는 것은 무엇인가? 다섯 가지가 중요하다:

  1. 올바른 데이터를 올바른 위치에 가져다 놓아라. 무언가를 실행하기 전에, 무엇을 실행할지 (인텐트), 어디서 실행할지 (어떤 장비), 어떻게 접근할지 (자격 증명, 연결 세부 정보)를 알아야 한다. 이것은 진실의 원천에서 (4장에서 더 자세히) 인벤토리를 가져오고 의도된 상태를 가져오는 것을 의미하며, 때로는 현재 상태를 이해하기 위해 옵저버빌리티 데이터를 쿼리하는 것을 의미한다. 이 통합 없이는 실행 엔진이 그냥 맹목적으로 명령을 실행하는 것이다. 정신 건강을 위해 피하라.

  2. 지금이든 나중이든 필요할 때 시작하라. 때로는 즉각적인 실행이 필요하다: 엔지니어가 “배포"를 클릭하고 즉시 발생하기를 기대한다. 다른 경우에는 실행이 기다려야 한다: 유지보수 윈도우 중에 배포하거나, 장비가 온라인이 될 때까지 기다리거나, 옵저버빌리티에서 이벤트에 반응한다. 시스템은 동기와 비동기 트리거 모두를 지원해야 한다.

  3. 시간이 지남에 따라 상태를 추적하라. 네트워크 전반의 작업은 즉각적이지 않다. 수 시간에 걸쳐 수백 대의 장비에 배포할 수 있다. 어떤 장비가 성공했는가? 어떤 것이 실패했는가? 어떤 것이 아직 대기 중인가? 상태 관리는 또한 멱등성 (동일한 작업을 두 번 실행해도 동일한 결과), 롤백 (방금 한 것을 취소), 재개 (실패 후 중단한 곳에서 계속)를 가능하게 한다. 상태 추적 없이는, 모든 실행이 일회성 도박이다.

  4. 규모에서 안정적으로 실행하라. 5대 장비에서 작동하는 스크립트는 종종 500대에서 중단되거나 저하된다. 병렬 실행, 오류 처리, 재시도, 속도 제한, 드라이런 기능이 필요하다. 시스템은 부분적 실패를 우아하게 처리하고, 무엇이 잘못됐는지에 대한 명확한 피드백을 제공하며, 네트워크를 정의되지 않은 상태로 남기지 않아야 한다. 다른 작업은 다른 전략이 필요하다. 일부는 빠르고 병렬적이며, 다른 것들은 느리고 직렬적이다.

  5. 모든 네트워크 장비나 플랫폼과 함께 작동하라. 네트워크에는 Cisco, Arista, Juniper, 클라우드 Application Programming Interface (API), Linux 박스, 방화벽, 로드 밸런서가 있을 것이다. 각각은 다른 프로토콜을 사용하고 다른 운영 패턴을 가지고 있다. 실행 레이어는 이 모든 것에 대한 어댑터가 필요하며, 나머지 자동화가 차이점을 신경 쓰지 않도록 공통 인터페이스가 필요하다.

이러한 목표를 염두에 두고, 실제로 어떤 아키텍처 기능이 필요한가?

5.1.3. 기둥#

각 목표는 특정 기능으로 변환된다:

  1. 데이터 통합 레이어. 실행 엔진은 독립적으로 존재하지 않는다. 진실의 원천 (인벤토리, 자격 증명, 인텐트), 옵저버빌리티 시스템 (현재 상태, 상태 메트릭), 잠재적으로 다른 시스템 (티케팅, 변경 관리, 승인 워크플로)에 프로그래매틱 접근이 필요하다. 이는 Application Programming Interface (API) 클라이언트 구현, 보안 인증 처리, 데이터 적절하게 캐싱, 실행 시작 전 필요한 모든 것이 있는지 유효성 검사를 의미한다.

  2. 유연한 트리거 메커니즘. 실행을 시작하는 여러 방법이 중요하다. 동기 트리거에는 Representational State Transfer (REST) Application Programming Interface (API) (직접 호출), 웹훅 (외부 시스템 통합), 원격 프로시저 호출이 포함된다. 비동기 트리거에는 이벤트 리스너 (옵저버빌리티 경보, 장비 상태 변화, 외부 이벤트에 반응), 스케줄러 (cron 유사 주기적 실행 또는 일회성 예약 작업), 메시지 큐 소비자가 포함된다. 기본적인 체이닝도 도움이 된다: 하나의 실행이 완료되고 다른 것을 트리거한다.

  3. 상태 관리 인프라. 이것은 “장비에 이 설정이 있는가"를 넘어선다. 실행 상태 (어떤 작업이 실행 중, 대기 중, 완료, 실패), 원하는 상태 (무엇이 설정되어야 하는지), 실제 상태 (무엇이 설정되어 있는지)가 필요하다. 이는 지속적인 스토리지, 원자적 작업을 위한 트랜잭션 지원, 동시 충돌을 방지하는 잠금 메커니즘, 명확한 상태 모델이 필요하다. 인프라는 여러 실행 워커에 걸쳐 상태가 공유되는 분산 시나리오를 처리해야 한다.

  4. 견고한 실행 엔진. 이것이 시스템의 핵심이다. 명령형 워크플로 (명령 1 실행, 그다음 명령 2, 그다음 명령 3)와 선언적 접근 (장비가 이렇게 보이게 해, 단계는 알아서 파악해) 모두를 지원한다. 엔진은 동시성 (여러 장비에 대해 병렬로 또는 배치로 실행), 지수 백오프를 사용한 재시도 로직 구현, 드라이런 기능 (실제로 하지 않고 무슨 일이 일어날지 예측), 상세한 실행 로그 캡처, 부분 실패의 우아한 처리를 수행한다. 오류 처리가 중요하다: 무언가 실패하면, 모든 것을 중단할지, 다른 장비로 계속할지, 또는 재시도할지?

  5. 프로토콜 추상화 레이어. 네트워크 장비는 이질적인 혼란이다. 일부는 Secure Shell (SSH)를 사용하고 Command Line Interface (CLI) 명령을 기대한다. 다른 것들은 NETCONF 또는 RESTCONF를 사용한다. 현대 장비는 스트리밍 텔레메트리와 설정을 위해 gRPC Network Management Interface (gNMI)를 지원한다. 클라우드 플랫폼은 Representational State Transfer (REST) API를 노출한다. 실행 시스템은 이 모든 것에 대한 어댑터가 필요하며, 상위 레벨 로직에 통합된 인터페이스를 제공한다. 이 레이어는 연결 풀링, 세션 관리, 인증, 명령 형식화, 응답 파싱을 처리한다. 좋은 추상화는 전체 자동화를 다시 작성하지 않고 새 장비 유형에 대한 지원을 추가할 수 있음을 의미한다.

5.1.4. 범위#

실행 블록은 계획과 행동 사이에 위치한다. 인텐트 (무엇이 설정되어야 하는지)와 오케스트레이션 (언제, 어떻게 할지)에서 지시를 받은 다음, 변경이 실제로 일어나도록 네트워크 장비와 직접 상호작용한다.

범위 내:

  • 지원되는 모든 프로토콜을 통해 네트워크 장비에 연결
  • 설정 변경, 운영 명령, 파일 전송, 재부팅 실행
  • 실행 상태 관리 및 진행 상황 추적
  • 오류, 재시도, 롤백 처리
  • 오케스트레이션에 피드백 제공

범위 밖:

  • 무엇을 설정할지 결정 (그것은 인텐트)
  • 복잡한 다단계 워크플로 조율 및 언제 트리거할지 결정 (그것은 오케스트레이션)
  • 실행 결과의 장기 저장 및 분석 (그것은 옵저버빌리티)

실행을 자동차의 엔진으로 생각하라: 동력과 움직임을 제공하지만, 어디로 가거나 언제 회전할지는 결정하지 않는다. 그 결정들은 지도 (인텐트)를 따르는 운전자 (오케스트레이션)에서 온다.

5.2. 기능#

다섯 가지 핵심 기능 영역이 함께 네트워크 상태를 안전하고 안정적으로 수정한다:

  1. 데이터 통합: 업스트림 시스템에서 인벤토리, 자격 증명, 인텐트, 옵저버빌리티 데이터 검색
  2. 트리거링: 동기 또는 비동기 메커니즘을 통한 실행 시작
  3. 상태 관리: 실행 진행 상황 추적, 멱등성 및 롤백 가능
  4. 엔진: 적절한 동시성과 오류 처리로 작업을 실행하는 핵심 로직
  5. 네트워크 어댑터: 다양한 네트워크 장비와 통신하기 위한 프로토콜별 인터페이스

이 구성 요소들은 파이프라인을 형성한다: 데이터 통합이 입력을 제공하고, 트리거링이 프로세스를 시작하며, 엔진이 네트워크 어댑터를 사용해 작업을 실행하고, 상태 관리가 전반에 걸쳐 모든 것을 추적한다.

graph LR
    subgraph 목표
        G1[올바른 데이터를 올바른 위치에]
        G2[필요할 때 시작]
        G3[상태 추적]
        G4[규모에서 안정적으로 실행]
        G5[모든 네트워크 장비와 호환]
    end

    subgraph 기둥
        P1[데이터 통합 레이어]
        P2[유연한 트리거 메커니즘]
        P3[상태 관리 인프라]
        P4[견고한 실행 엔진]
        P5[프로토콜 추상화 레이어]
    end

    subgraph 기능
        F1[데이터 통합]
        F2[트리거링]
        F3[상태 관리]
        F4[엔진]
        F5[네트워크 어댑터]
    end

    G1 --> P1 --> F1
    G2 --> P2 --> F2
    G3 --> P3 --> F3
    G4 --> P4 --> F4
    G5 --> P5 --> F5

내부 아키텍처는 다음과 같다:

graph TD
    A[데이터 통합] --> C[엔진]
    B[트리거링] --> C[엔진]
    C --> E[상태 관리]
    E --> C
    C --> D[네트워크 어댑터]
    classDef component fill:#e1f5ff,stroke:#4a90e2,stroke-width:2px;
    class A,B,C,D,E component;

5.2.1. 데이터 통합#

무언가를 실행하기 전에 데이터가 필요하다. 실행 엔진은 여러 소스에서 무엇을 할지, 어디서 할지 이해하기 위한 정보를 가져온다.

5.2.1.1. 인벤토리#

인벤토리 데이터는 대상 장비를 정의한다. 4장에서 다뤘지만, 최소한:

인벤토리 데이터는 진실의 원천에서 와야 한다. 파일만 사용하는 것은 매우 작은 환경에서만 잘 작동한다. 실행 엔진은 런타임에 진실의 원천 Application Programming Interface (API)를 쿼리하거나 (또는 트리거링 시 오케스트레이션을 통해 데이터를 받는다). 일부 시스템은 설정 가능한 갱신 간격으로 성능을 위해 인벤토리를 캐시하거나, 트리거와 관련 정보를 결합하는 이벤트 기반 인벤토리를 활용한다.

자격 증명을 인벤토리 파일이나 로그에 절대 저장하지 마라. 비밀 관리 시스템 (HashiCorp Vault, AWS Secrets Manager, CyberArk)을 사용하고 실행 시간에 자격 증명을 주입하라. 진실의 원천은 그 민감한 데이터에 대한 포인터를 제공하고 런타임에 검색해야 한다. 실행 엔진은 여러 자격 증명 소스와 장비별 자격 증명 오버라이드를 지원해야 한다.

5.2.1.2. 의도된 데이터#

의도된 데이터는 설정하거나 변경하려는 이다. 인텐트/진실의 원천 블록에서 오며 다음을 포함할 수 있다:

  • 설정 아티팩트: API와 직접 사용할 수 있는 구조화된 데이터이거나, 설정을 구축하는 일련의 CLI 명령인 배포 준비된 아티팩트.
  • 명령: 장비 작업을 위해 실행할 특정 Command Line Interface (CLI) 명령 또는 Application Programming Interface (API) 호출.
  • 파일: 업그레이드를 위한 소프트웨어 이미지, 가져오기를 위한 설정 파일.

실행 엔진이 인텐트 데이터를 직접 가져와야 하는가, 아니면 오케스트레이션이 전달해야 하는가? 둘 다 작동한다. 인텐트를 직접 가져오는 것은 실행을 진실의 원천에 결합시키지만 데이터가 항상 최신임을 보장한다. 파라미터로 인텐트를 받는 것은 실행을 더 범용적으로 만들지만 오케스트레이션이 데이터 가져오기를 처리해야 한다.

설정 아티팩트를 직접 소비하는 것을 권장한다. 인텐트 블록은 상태 (VLAN 설정, 라우팅 정책, ACL)를 나타내는 구조화된 데이터로 설정 템플릿을 렌더링하여 자체 로직으로 설정 아티팩트를 생성하는 책임이 있어야 한다.

설정 아티팩트는 생성되는 순간과 실행기가 소비하는 순간 사이에 오래될 수 있다. 사전 생성된 아티팩트가 캐시된 후 SoT 데이터가 변경되면, 실행기가 오래된 설정을 적용할 수 있다. 오래됨 윈도우는 마지막 SoT 커밋과 실행 트리거 사이의 시간이다. 트리거 시간에 아티팩트를 가져오는 자동화 (캐시에서가 아닌)는 이 윈도우가 최소화된다. 아티팩트를 일정에 따라 사전 렌더링하고 나중에 사용하기 위해 저장하는 파이프라인은 윈도우가 몇 시간까지 커질 수 있다. 데이터 최신성이 중요한 경우, 오케스트레이터나 실행 트리거는 항상 캐시된 파일이 아닌 SoT에서 신선한 아티팩트를 가져와야 한다.

5.2.1.3. 관찰된 데이터#

옵저버빌리티 유효성 검사는 일반적으로 오케스트레이션에 속하며, 진행할지 결정할 수 있다. 아직 오케스트레이션이 존재하지 않는 단순한 경우에는, 실행이 이 역할을 통합할 수 있다.

실행 흐름 가까이에서 옵저버빌리티 데이터가 사용되는 유스케이스는 다음과 같다:

  • 사전 실행 유효성 검사: 장비에 도달할 수 있는가? 업그레이드를 위한 충분한 디스크 공간이 있는가? 중단될 활성 세션이 있는가?
  • 우아한 저하: 스위치를 재부팅하기 전에, 옵저버빌리티를 쿼리해 이중화 연결이 있는지 확인하라. 없다면, 지연하거나 중단하라.
  • 조건부 로직: 특정 조건이 충족된 경우에만 설정 적용 (CPU가 임계값 이하, 활성 경보 없음, 당일 시간이 윈도우 내)
  • 용량 소진: 소진 작업을 실행하기 전에, SLO를 깨지 않고 변경을 흡수할 충분한 가용 용량이 있는지 확인하라.

이는 옵저버빌리티 시스템과의 통합이 필요하다. 실행 엔진은 현재 메트릭을 위한 Application Programming Interface (API)를 쿼리하거나, 장비 상태를 확인하거나, 준비 신호를 기다릴 수 있다. 오케스트레이션도 이 통합을 수행하고 최신성과 위험을 기반으로 실행을 게이팅할 수 있다.

일반적인 패턴: Ansible의 “네트워크 상태 검사” 모듈이나 Nornir의 데이터 수집 작업 같은 도구가 실행 전후에 옵저버빌리티 쿼리를 실행하여 예상치 못한 변경을 감지하기 위해 결과를 비교한다. “전/후 스냅샷” 접근 방식은 그렇지 않으면 눈치채지 못했을 회귀를 잡아낸다.

5.2.2. 트리거링#

실행은 스스로 시작하지 않는다. 무언가가 트리거해야 한다. 현대 실행 시스템은 여러 트리거 메커니즘을 지원한다:

동기 (즉각적, 차단):

  • Representational State Transfer (REST) Application Programming Interface (API) 호출: 외부 시스템이나 사용자가 Application Programming Interface (API) 엔드포인트를 호출하고, 실행이 실행되며, 응답에 결과가 포함된다. 간단하고 직접적이다.
  • 웹훅: 이벤트가 발생할 때 외부 시스템 (Term "git" not found, 티케팅, CI/CD)이 HTTP 요청을 보낸다. 일반적인 패턴: Term "git" not found 푸시가 설정 배포를 트리거한다.
  • Command Line Interface (CLI) 명령: 엔지니어가 실행을 직접 호출하는 명령을 실행한다 (ansible-playbook, terraform apply, 커스텀 스크립트). 이 CLI 명령들은 이러한 도구들이 제공하는 경량 프레젠테이션 레이어의 일부다 (8장에서 더).

비동기 (지연 또는 이벤트 기반):

  • 이벤트 리스너: 옵저버빌리티 (장비 다운, 임계값 초과), 메시지 큐, 외부 시스템의 이벤트에 반응한다. Ansible 이벤트 기반 자동화 (EDA)가 이 패턴을 명시적으로 구축했다: 이벤트를 듣고, 규칙을 매칭하며, 자동으로 플레이북을 트리거한다.
  • 메시지 큐: 큐 (RabbitMQ, Kafka, AWS SQS)에 제출된 작업, 워커가 가져와 실행한다. 버퍼링, 우선순위 큐잉, 속도 제한을 가능하게 한다.

올바른 접근 방식은 유스케이스에 따라 다르다. 즉각적인 변경 (프로덕션 문제 수정)은 동기 트리거가 필요하다. 반응형 자동화 (경보에 반응)는 이벤트 리스너를 사용한다. 규모 고려 사항도 중요하며, 11장에서 다룰 것이다.

트리거링은 일반적으로 오케스트레이션에서 오지만, 그것이 없는 경우 다른 블록이 실행을 직접 트리거할 수 있다. 예를 들어, 인간이 노출된 작업으로 프레젠테이션 레이어에서 직접 트리거하거나, 진실의 원천 블록에서.

한 가지 중요한 고려 사항: 무엇이 실행으로 계산되는가? 내게 실행은 단순한 작업 이상이다. 복잡한 워크플로가 필요하지 않은 여러 연쇄된 작업을 포함할 수 있다 (그것은 오케스트레이션의 역할이다). 경계는 흐릿해진다 (또). 예를 들어, 이 시퀀스는 여전히 하나의 실행 흐름일 수 있다: 펌웨어 업그레이드, 장비 재부팅 대기, 작동 확인, 인벤토리 업데이트. 하지만 인간 유효성 검사나 더 광범위한 유효성 검사를 기반으로 한 조건부 분기가 필요하다면, 오케스트레이션 블록에 속한다.

5.2.3. 상태 관리#

상태 관리는 실행에서 어디에 있는지세상이 어떻게 보이는지를 추적하여 지능적인 결정을 내릴 수 있게 하는 것이다. 두 가지 주요 범주가 있다:

  • 실행 상태 (자동화 자체 추적):

    • 어떤 장비가 처리됐는가?
    • 어떤 작업이 성공, 실패 또는 대기 중인가?
    • 작업이 일시적 오류로 실패했다면, 재시도할 수 있는가?
    • 실행이 중단됐다면, 중단한 곳에서 재개할 수 있는가?
  • 실제 인프라 상태 (대상 설정 상태 추적):

    • 현재 무엇이 설정되어 있는가?

두 가지 접근 방식이 있다:

  • 상태 없음/에이전트리스 (예: Ansible). 각 실행은 독립적으로 실행된다. 실행 간에 지속적인 상태가 없다. 모든 실행은 처음부터 시작한다: 현재 상태 수집, diff 계산, 변경 적용. 운영이 단순하지만 (상태 데이터베이스 없음), 덜 효율적이고 (매번 모든 것을 재발견) 내장 롤백이 없다.

  • 상태 있음 (예: Terraform). 시스템은 마지막으로 배포된 것을 추적하는 지속적인 상태 파일을 유지한다. 각 실행에서, 원하는 상태 (설정)를 기록된 상태 (마지막에 배포한 것)와 실제 상태 (장비에 있는 것)와 비교한다. 이것은 정밀한 변경 계획, 효율적인 실행 (다른 것만 변경), 롤백 (이전 상태로 되돌리기)을 가능하게 한다. 하지만 이제 보호하고, 잠그고, 여러 운영자에 걸쳐 동기화할 상태 파일이 있다.

트랜잭션 실행은 “최선의 노력"보다 더 강한 안전 보장을 원할 때 다음 단계다. 트랜잭션은 여러 장비 변경을 단일 단위로 그룹화한다: 모든 것이 적용되고 유효성 검사되거나, 시스템이 이전 상태로 롤백한다. 이를 위해 세 가지가 필요하다:

  • 명확한 경계 (어떤 작업이 트랜잭션 내에 있는지)
  • 변경 전 상태의 내구성 있는 기록 (롤백용)
  • 동시 변경이 원자성을 깨지 않도록 잠금 또는 리스 메커니즘

실제로, 대부분의 네트워크 자동화는 모든 장비가 네이티브 커밋/롤백을 지원하지 않기 때문에 엄격한 ACID 의미론보다 “트랜잭션과 유사한” 동작을 사용한다. 그래도 스냅샷을 찍고, 후보 설정을 사용하며 (지원되는 경우), 독점 잠금을 적용하고, 롤백 경로를 실행 흐름의 일급 부분으로 만들어 트랜잭션을 근사할 수 있다.

과제? 상태 동기화와 공유. 여러 사람이나 시스템이 네트워크 장비를 수정하면 상태가 오래된다. Terraform은 원격 상태 스토리지와 잠금으로 이를 해결한다. Ansible은 상태가 없어서 문제를 피하지만 효율성을 희생한다. 중간 지점: 일시적으로 현재 상태를 캐시하고, 각 작업 전에 유효성 검사하며, 잠깐의 불일치가 허용되는 “최종 일관성” 패턴을 구축하라.

5.2.3.1. 멱등성#

멱등성은 동일한 자동화를 여러 번 실행하면 동일한 결과가 나온다는 것을 의미한다. VLAN 설정을 한 번 적용하면: VLAN이 생성된다. 다시 적용하면: 아무것도 변하지 않는다 (VLAN이 이미 존재). 이것은 안정성에 중요하다: 실행이 중간에 실패하면, 중복을 만들거나 망가뜨리지 않고 안전하게 재실행할 수 있다.

도구들이 멱등성을 달성하는 방법:

  • 내장 모듈 (예: Ansible): 대부분의 Ansible 모듈은 설계상 멱등적이다. ios_vlan 모듈은 VLAN을 생성하기 전에 존재하는지 확인한다. 이미 올바른 설정으로 있다면, Ansible은 “ok” (변경 없음)를 보고한다. 이를 위해 모듈 작성자가 확인 로직을 구현해야 한다.

  • 상태 비교 (예: Terraform): Terraform은 원하는 상태를 현재 상태와 비교하고, diff를 계산하며, 차이점만 적용한다. 설정을 변경하지 않고 terraform apply를 두 번 실행하면, 두 번째 실행은 아무것도 하지 않는다.

  • 선언적 API (예: NETCONF/YANG): 일부 프로토콜은 기본적으로 멱등성을 처리한다. merge 작업이 있는 NETCONF의 <edit-config>는 본질적으로 멱등적이다: 기존 설정에 설정을 병합하여 필요에 따라 생성하거나 업데이트한다.

  • 수동 확인 (예: 원시 스크립트): Python이나 Go 스크립트를 작성하는 경우, 직접 멱등성을 구현한다: 현재 상태 쿼리, 원하는 상태와 비교, diff가 있는 경우에만 변경.

멱등성은 보이는 것보다 어렵다. VLAN이 존재하지만 잘못된 설정이 있다면? 업데이트해야 하는가 (트래픽을 잠재적으로 중단) 아니면 충돌을 보고해야 하는가? 일시적 실패 (장비가 일시적으로 도달할 수 없음)는 어떻게? 재시도 로직은 “작업이 이미 완료됨” (멱등적 성공)과 “작업이 실패함” (실제 오류)을 구분해야 한다.

완벽한 멱등성은 오버헤드를 추가한다. 모든 작업은 먼저 현재 상태를 쿼리해야 한다. 대규모 배포에서는 속도가 느려진다. 일부 팀은 “완벽하게 멱등적” (항상 작동하지만 느리게 실행됨) 보다 “대부분 멱등적” (99% 작동)을 허용한다.

멱등성은 선언적 접근 방식의 요구 사항이다. 누군가가 멱등성을 제공하고 다른 모든 사람을 위해 복잡성을 숨기는 부담을 져야 한다.

5.2.4. 엔진#

실행 엔진은 작업 (“이 50개 스위치에 이 VLAN을 설정하라”)을 받아 안전하고 효율적으로 실행하는 핵심 로직이다. 이것은 오케스트레이션이 아니다. 오케스트레이션 블록은 시간과 종속성에 걸쳐 여러 실행 작업을 조율한다. 엔진은 그냥 하나의 작업을 잘 실행한다 (또는 단순한 작업 체인).

하는 일:

  • 작업 정의 수락 (무엇을 할지, 어떤 장비)
  • 원자적 작업으로 분해 (장비별 액션)
  • 적절한 동시성으로 작업 실행 (직렬, 병렬, 배치)
  • 오류, 재시도, 롤백 처리
  • 진행 상황과 결과 보고

실행 엔진은 100개의 라우터를 병렬로 설정할 수 있지만, 언제 설정할지, 비즈니스 로직에 따라 어떤 설정을 적용할지, 또는 결과를 기반으로 다음에 무엇을 할지를 결정하지 않는다. 그것들은 오케스트레이션 관심사다. 실행은 일꾼이고, 오케스트레이션은 반장이다.

그렇다고 해도, 실행 엔진은 종종 단순한 체이닝을 지원한다: “같은 장비에서 작업 A를 하고, 그다음 작업 B를 해라.” 그것은 기본적인 시퀀싱이지, 완전한 오케스트레이션이 아니다. 복잡한 워크플로 (외부 승인 대기, 결과에 따른 분기, 여러 시스템 조율)가 필요할 때는 실제 오케스트레이션 레이어가 필요하다 (7장에서 더).

5.2.4.1. 언어#

실행 로직을 어떻게 정의하는가? 다른 언어 스타일은 다른 트레이드오프를 가진다:

스타일예시강점트레이드오프
도메인 특화 언어 (DSL)Ansible (YAML), Terraform (HCL)낮은 진입 장벽, 자체 문서화 인텐트, 내장 실행 의미론제한된 유연성, 복잡한 시나리오에서 디버깅 어려움, 조건부 로직이 어색해질 수 있음
범용 프로그래밍Nornir (Python), 커스텀 Python/Go 스크립트완전한 유연성, 강력한 디버깅 도구, 쉬운 라이브러리 재사용높은 기술 요구 사항, 유지할 코드 더 많음, 팀 간 표준화 부족

많은 팀이 일반적인 패턴에는 DSL (Ansible)을 사용하고, 복잡한 엣지 케이스에는 커스텀 코드 (Python 모듈, 플러그인)로 내려간다. 이것은 접근성과 파워의 균형을 맞춘다. 인간 요소가 일반적으로 어떤 접근 방식이 이기는지 결정하며, 13장에서 더 다룬다.

5.2.4.2. 명령형 대 선언적#

명령형: 어떻게 할지를 단계별로 지정한다.

선언적: 최종 상태가 어떻게 되어야 하는지를 지정하고, 도구가 방법을 파악한다.

간단히 말해, 맞을 때는 선언적을 선호하라.

차이를 보는 좋은 방법은 두 가지를 모두 지원하는 Ansible을 보는 것이다:

  • 명령형 Ansible:

    - name: Create VLAN 100
      cisco.ios.ios_command:
        commands:
          - vlan 100
          - name Engineering

    Ansible에게 어떤 명령을 실행할지 문자 그대로 말하고 있다. VLAN이 존재해도 이것은 여전히 실행된다 (모듈에 따라 멱등적일 수 있음).

  • 선언적 Ansible:

    - name: Ensure VLAN 100 exists
      cisco.ios.ios_vlans:
        config:
          - vlan_id: 100
            name: Engineering
        state: merged

    원하는 상태를 기술한다. Ansible이 어떤 명령을 실행할지 파악한다. VLAN 100이 올바른 이름으로 이미 존재한다면, Ansible은 아무것도 하지 않는다.

접근 방식강점트레이드오프
선언적본질적으로 멱등적; 인텐트를 읽기 쉬움; 도구가 엣지 케이스를 처리하므로 운영자 실수 적음모듈/프로바이더 품질에 크게 의존; 실행 경로에 대한 제어 적음; 내부가 추상화되면 문제 해결이 어려울 수 있음
명령형각 단계에 대한 완전한 제어; 실행 흐름이 명시적; CLI 접근이 있는 거의 모든 환경에서 작동더 많은 코드와 테스트 노력; 멱등성은 자신이 책임; 엣지 케이스가 누적되면 장기 유지보수가 빠르게 성장

대부분의 팀은 가능할 때 선언적을, 필요할 때 명령형을 사용한다. 기억하라: 선언적은 겉에서 단순해 보여도 다른 누군가가 그 부담을 진다는 것을.

5.2.4.3. 직렬 대 병렬#

작업을 실행하는 두 가지 옵션이 있다:

  • 직렬 실행: 한 번에 하나씩 장비를 처리한다. 장비 1을 설정하고, 완료를 기다리고, 장비 2를 설정하는 방식. 안전하지만 (실패를 즉시 보고 중지할 수 있음) 느리다 (100대 장비 = 한 대 장비 시간의 100배).

  • 병렬 실행: 여러 장비를 동시에 처리한다. 장비 1-10을 한 번에 설정하고, 다음에 11-20, 등.

flowchart TD
    subgraph 직렬
        S1[장비 1] --> S2[장비 2] --> S3[장비 3]
    end
    subgraph 병렬
        P0[시작] --> P1[장비 1]
        P0 --> P2[장비 2]
        P0 --> P3[장비 3]
    end

Nornir가 존재하는 이유: Ansible은 원래 직렬이었다. 규모에서 네트워크 자동화에는 고통스럽게 느렸다. Ansible이 병렬성을 가능하게 하는 strategy: free와 나중에 forks를 추가했지만, 여전히 근본적으로 순차 실행을 위해 설계됐다. Nornir는 처음부터 병렬성을 염두에 두고 구축됐다: 기본적으로 스레딩을 사용해 여러 장비에 대해 동시에 실행한다. 이것은 대규모 장비 수에서 10-100배 빠르게 만든다.

병렬 실행 고려 사항:

  • 컨트롤 플레인 영향: 1000대 장비에 동시에 접근하면 관리 네트워크, 데이터 소스, 인증 시스템, 또는 장비 컨트롤 플레인에 과부하가 걸릴 수 있다.
  • 종속성 처리: 장비가 서로 의존하는 경우 (leaf 전에 spine), 순서가 필요하다. 순수 병렬성은 작동하지 않는다.
  • 오류 폭발 반경: 자동화에 버그가 있다면, 병렬 실행은 알아차리기 전에 많은 장비에 배포한다. 직렬 실행은 장비 1에서 실패하여 장비 2-100을 손상시키기 전에 중지한다.

권장 사항: 배치를 사용한 병렬 실행을 사용하라. 장비를 10-50개 그룹으로 처리하고 (나는 “파도"라고 부르곤 했다), 계속하기 전에 각 배치의 성공을 확인하라. 이것은 속도와 안전 사이의 균형을 맞춘다. 배포 전략에 대한 자세한 내용은 10장에서.

5.2.4.4. 드라이런 (계획 모드)#

드라이런은 실행 엔진의 “계획” 단계다: 장비를 건드리지 않고 변경을 시뮬레이션하고, 구체적인 diff와 위험을 표시한다. 좋은 드라이런은 현재 상태를 가져오고, 실행될 정확한 장비 수준 작업을 계산하며, 전제 조건 (도달 가능성, 스키마, 종속성 순서)을 유효성 검사한다. 검토자가 신뢰할 수 있도록 빠르고, 결정론적이며, 재현 가능해야 한다.

예시 (검토 가능한 변경이 있는 AWS VPC에 대한 Terraform plan):

Terraform will perform the following actions:

  # aws_vpc.main will be created
  + resource "aws_vpc" "main" {
      + cidr_block           = "10.10.0.0/16"
      + enable_dns_support   = true
      + enable_dns_hostnames = true
      + tags = {
          + "Name" = "core-vpc"
        }
    }

Plan: 1 to add, 0 to change, 0 to destroy.

이것이 검토자가 승인하는 것이다: 클라우드를 건드리지 않고 변경될 정확한 리소스와 속성.

실제로, 드라이런은 승인을 의미 있게 만들고 롤백을 덜 필요하게 만드는 것이다. 인간에게 무슨 일이 일어날지에 대한 명확한 뷰를 제공하고, 엔진에게 안전하지 않거나 일관성 없는 변경을 일찍 거부할 기회를 제공한다. 플랫폼이 후보 설정이나 커밋 검사를 지원한다면, 엔진이 그것을 사용해야 한다. 그렇지 않으면, 드라이런은 계산된 diff와 사전 검사이지, 보장이 아니다.

내 경험상, 드라이런 기능을 제공하는 것은 네트워크 엔지니어들의 동의를 얻기 위해 자동화 프로젝트 초기에 매우 편리하다. 나중에 관련성이 감소한다.

5.2.4.5. 복원력#

네트워크 실행은 본질적으로 불안정하다 (대부분의 분산 시스템이 그렇듯이): 장비가 재부팅되고, 연결에 문제가 생기며, 컨트롤 플레인이 과부하된다. 인텐트 블록과 같은 다른 종속성도 일시적인 문제가 있을 수 있다. 복원력 있는 실행은 이것들을 우아하게 처리한다:

재시도 로직:

  • 일시적 실패 (연결 타임아웃, 일시적 CPU 스파이크)는 지수 백오프를 사용한 재시도를 트리거해야 한다
  • 재시도 가능한 오류 (타임아웃)와 영구적 오류 (인증 실패, 구문 오류)를 구분하라
  • 무한 루프를 피하기 위해 재시도 횟수를 제한하라

타임아웃 전략:

  • 연결 타임아웃: 포기하기 전에 장비 응답을 기다리는 시간
  • 작업 타임아웃: 완전한 작업이 실행될 수 있는 시간 (고착된 장비에서 중단 방지)
  • 전역 타임아웃: 전체 작업의 최대 실행 시간

오류 처리:

  • 빠른 실패: 하나의 장비가 실패하면 모든 것을 중단 (안전하지만 비효율적)
  • 계속 진행: 실패를 로그에 기록하고 다른 장비로 계속 진행 (효율적이지만 피해를 확산시킬 수 있음)
  • 임계값 기반: 장비의 >10%가 실패하면 중지 (균형)

롤백 기능:

  • 변경 전에 설정 스냅샷 찍기
  • 실행이 실패하면 스냅샷 자동 복원
  • 드라이런 모드 지원: 실제로 변경하지 않고 무엇이 변경될지 보여주기

서킷 브레이커:

  • 장비가 지속적으로 실패하면 비정상으로 표시하고 일시적으로 건너뜀
  • 죽은 장비에 반복적으로 연결 시도하느라 시간을 낭비하지 않도록 방지

체크포인팅:

  • 주기적으로 진행 상황 저장
  • 실행이 충돌하면 처음부터 시작하는 대신 마지막 체크포인트에서 재개

이 모든 것을 구축하는 것은 어렵다. 대부분의 팀은 기본 재시도 로직으로 시작하고 프로덕션에서 실패에 부딪히면서 복잡성을 추가한다.

하나의 유용한 패턴: “안전 모드"를 일급 실행 상태로 취급하라. 의도된 설정을 렌더링하고, 현재 설정을 파싱하며, 사실을 수집한 다음, 유효성 검사 게이트가 통과한 후에만 (merged/replaced/overridden) 적용하라. 이것은 프로덕션을 건드리기 전에 결정론적 체크포인트를 제공한다.

5.2.5. 네트워크 어댑터#

네트워크 장비는 다른 프로토콜을 사용한다. 네트워크 어댑터 레이어는 이러한 차이를 추상화하여 상위 레이어가 Secure Shell (SSH)를 통해 Cisco 스위치와 이야기하는지 Representational State Transfer (REST) Application Programming Interface (API)를 통해 Arista 스위치와 이야기하는지 신경 쓰지 않아도 된다.

5.2.5.1. 인터페이스#

다른 장비는 다른 관리 인터페이스를 지원한다:

인터페이스설명예시 라이브러리/도구일반적인 용도
Secure Shell (SSH) / Command Line Interface (CLI)가장 범용적, 가장 덜 구조화됨 (텍스트 입/출력)Netmiko (Python), Paramiko (Python), scrapli (Python), scrapligo (Go)레거시 플랫폼, 벤더별 운영 명령
NETCONF / RESTCONF구조화된 모델 기반 관리 (YANG 기반)ncclient (Python), scrapli-netconf (Python), nemith/netconf (Go)선언적 설정과 표준화된 데이터 모델
gRPC Network Management Interface (gNMI) / gNOI설정/상태와 운영 RPC를 위한 gRPC 기반 인터페이스pygnmi (Python), gnmic (Go CLI), openconfig/gnmi (Go)스트리밍 텔레메트리와 현대 운영 워크플로
Representational State Transfer (REST) APIHTTP/JSON 또는 XML API, 종종 플랫폼별requests/httpx (Python), net/http + OpenAPI 생성 클라이언트 (Go)컨트롤러 및 클라우드/네트워크 플랫폼 API
JSON-RPC / 벤더 gRPC특정 네트워크 운영 시스템이 사용하는 구조화된 RPC 패턴Arista eAPI (JSON-RPC), 벤더 gRPC SDK구조화된 페이로드를 사용한 빠른 원격 프로시저 실행

일부 라이브러리는 플랫폼 전반에 걸쳐 공통 인터페이스를 제공하는 추상화 레이어를 제공한다:

  • NAPALM (Network Automation and Programmability Abstraction Layer with Multivendor support): 벤더 전반에 통합 API를 제공하는 Python 라이브러리.
  • Pybatfish: 장비 전송 라이브러리가 아닌, 실행 안전성의 강력한 동반자. 실용적인 패턴은 “NAPALM이나 Ansible로 계획/적용하고, 변경 전후에 pybatfish로 네트워크 동작을 유효성 검사하라"이다.

추상화 레이어를 사용하는 이유? Cisco 대 Juniper를 위해 다른 코드를 작성하는 대신, NAPALM이 하나의 API를 제공한다. get_facts()를 호출하면 NAPALM이 Cisco IOS 장비 (Secure Shell (SSH) 통해), Arista EOS (eAPI 통해), Juniper Junos (NETCONF 통해) 여부에 관계없이 장비 사실을 가져오는 방법을 파악한다. 트레이드오프: 추상화는 벤더별 기능을 숨긴다. 일반적인 작업 (설정 가져오기, 설정 푸시, 사실 가져오기)에는 훌륭하다. 더 고급 작업이나 특이한 벤더 기능에는, 구현되지 않았을 가능성이 높기 때문에 네이티브 인터페이스로 돌아간다.

5.2.5.2. 작업#

네트워크 실행은 단순한 설정 관리가 아니다:

작업 유형다루는 내용참고
설정 변경완전한 설정/스니펫/선언적 상태 푸시; 병합/교체/삭제 모드가장 일반적인 작업 유형이며 도구에서 가장 잘 지원됨
제로 터치 프로비저닝 (Zero Touch Provisioning (ZTP))첫 부팅 시 자동화된 온보딩DHCP + 부트스트랩 서버 (TFTP/HTTP/HTTPS) 및 장비 지원 필요
파일 전송이미지 업로드, 로그/백업 다운로드일반적인 프로토콜: SCP, SFTP, TFTP, HTTP
장비 작업재부팅/리로드, 운영 명령 (ping/traceroute), 백업day-2 운영과 수정에 일반적
롤백설정을 이전에 알려진 양호한 상태로 되돌리기일부 플랫폼에서 네이티브 롤백; 다른 플랫폼에서는 백업 복원

각 작업 유형은 자체 오류 모드가 있으며 특정 처리가 필요하다. 소프트웨어 업그레이드는 사전 검사 (충분한 디스크 공간?), 사후 검사 (장비가 올바르게 부팅됐는가?), 롤백 계획 (업그레이드가 실패하면 이전 이미지로 리로드)이 필요하다. 결과는 결정을 위해 오케스트레이션에 노출되어야 하며, 실행은 여전히 로컬 가드레일과 재시도를 처리할 수 있다.

유효성 검사와 렌더링이 속하는 곳: 진실의 원천이 인텐트와 (선택적으로) 사전 렌더링된 설정을 소유한다. 실행이 장비 수준 안전 검사와 운영 유효성 검사 (도달 가능성, 전/후 검사)를 소유한다. 오케스트레이션은 언제 유효성 검사할지, 결과로 무엇을 할지 (승인, 일시 중지, 롤백, 또는 에스컬레이션) 결정한다. 간단한 규칙이 필요하다면: 워크플로를 변경하는 유효성 검사는 오케스트레이션에 속하고; 장비 액션을 변경하는 유효성 검사는 실행에 속한다.

5.2.6. 솔루션#

네트워크 실행을 위한 많은 도구가 있으며, 비교를 통해 트레이드오프를 이해하는 데 도움이 된다:

도구실행 모델과 강점최적 적합주요 제한 사항
AnsibleDSL (YAML), 에이전트리스, 대규모 모듈 생태계, 혼합 서버/네트워크 자동화에 강함빠른 성과와 광범위한 플랫폼 지원을 원하는 팀대규모에서 튜닝이 필요; 복잡한 로직이 YAML에서 유지하기 어려워질 수 있음
Terraform선언적 IaC (HCL), 강력한 diff/계획 엔진, 상태 기반 워크플로, 탁월한 클라우드 통합인프라에 대해 Terraform으로 표준화하는 팀네트워크 프로바이더 성숙도 차이; 상태 관리 운영 오버헤드 추가; day-2 운영에 약함
SaltPython 기반으로 에이전트 또는 에이전트리스 옵션, 이벤트 기반 아키텍처, 강력한 규모 특성기존 Salt 환경 또는 이벤트 집약적 운영더 작은 네트워크 자동화 커뮤니티와 가파른 온보딩
NornirPython 우선 프레임워크, 스레드 기반 병렬성, 매우 유연하고 쉬운 디버깅과 빠름커스텀이나 성능 민감한 필요가 있는 Python 역량 팀준비된 구성 요소 적음; 더 많은 엔지니어링 소유권 필요
커스텀 Python/Go도메인 특화 로직에 대한 최대 제어와 설계 자유엣지 케이스, 내부 플랫폼, 고도로 특화된 워크플로모든 것을 소유: 표준, 안정성 패턴, 테스트, 수명 주기 지원
벤더 컨트롤러벤더 네이티브 워크플로가 있는 인텐트 + 정책 컨트롤러 (예: Cisco Catalyst Center, Aruba Central, Juniper Mist)강력한 컨트롤러 API가 있는 하나의 벤더 생태계를 중심으로 표준화하는 팀다중 벤더 환경에서 덜 이식 가능한 패턴

많은 팀이 유스케이스와 네트워크 인프라에 따라 여러 도구를 사용한다.

5.2.7. 제로 터치 프로비저닝#

제로 터치 프로비저닝 (ZTP)은 장비가 처음 부팅될 때 장비에서 인간의 개입 없이 자동으로 전체 설정을 검색하고 적용하는 별개의 실행 패턴이다. 데이터 흐름은 day-2 운영과 구조적으로 다르며, 명명된 아키텍처 패턴으로 취급할 가치가 있다.

ZTP 흐름

flowchart LR
    A[장비 부팅\n설정 없음] --> B[DHCP가\n관리 IP와\n부트스트랩 URL 할당]
    B --> C[장비가 서버에서\n부트스트랩 설정\n가져오기]
    C --> D[장비가 SoT와 관리\n네트워크에 인증]
    D --> E[오케스트레이터가\n새 장비 감지, 전체\n프로비저닝 작업 트리거]
    E --> F[실행기가 SoT에서\n전체 인텐트 적용]

핵심 통찰은 부트스트랩 단계가 의도적으로 최소화된다는 것이다: 장비를 관리 네트워크에 올리고 인증할 수 있을 만큼만. 전체 프로비저닝은 그 이후에 표준 실행기 작업으로 실행되어 진실의 원천에서 완전한 인텐트를 가져온다. 이것은 ZTP가 별도의 프로비저닝 시스템이 아닌 day-2 운영과 동일한 실행 파이프라인을 재사용한다는 것을 의미한다.

일반적인 ZTP 패턴

패턴작동 방식트레이드오프
DHCP + 정적 파일DHCP가 장비별 정적 설정 파일을 가리킴 (MAC 또는 시리얼로 매칭)구현이 간단; SoT에서 가져오지 않음; 규모에서 중단됨
DHCP + 동적 생성부트스트랩 서버가 SoT를 쿼리하고 요청 시간에 장비별 초기 설정 생성day 0부터 SoT 기반; 부팅 전에 SoT에 장비가 등록되어 있어야 함
OS 이미지 + 설정장비가 부팅 중에 OS 이미지와 설정 모두 다운로드 (OS 프로비저닝이 필요한 장비에 필요)베어메탈 또는 공장 초기화 장비 처리; 부트스트랩 서버 복잡성 증가

ZTP는 시퀀싱 종속성을 도입한다: 장비가 물리적으로 부팅되기 전에 진실의 원천에 등록되어야 하고, 그렇지 않으면 부트스트랩 서버에 설정을 생성할 데이터가 없다. 캠퍼스 시나리오에서, 이것은 ServiceNow-Nautobot 동기화로 처리된다: 스위치가 ServiceNow에 자산으로 추가될 때 (사이트에 도착하기 전), Nautobot이 자동으로 위치, 역할, 벤더와 함께 장비 레코드를 생성한다. 스위치가 부팅될 때, SoT가 이미 준비되어 있다.

5.2.8. 하이브리드와 클라우드 실행#

실행기는 점점 두 가지 근본적으로 다른 환경을 동시에 운영해야 한다: 전통적인 네트워크 장비와 클라우드 네이티브 플랫폼. 이 환경들은 다른 API 패러다임, 인증 모델, 실패 모드를 가지고 있다. 동일하게 취급하면 취약한 자동화로 이어지고; 완전히 별개의 시스템으로 취급하면 노력이 중복되고 일관성 없는 운영이 발생한다.

발산 문제

차원네트워크 장비클라우드 플랫폼
API 스타일SSH, NETCONF, gNMI (상태 기반, 오래 지속되는 연결)REST/HTTP (무상태, 단기 요청)
인증 모델공유 자격 증명 (사용자명/비밀번호, SSH 키)임시 토큰, 서비스 계정, 인스턴스 역할 (AWS SigV4, Azure AD, GCP 서비스 계정)
작업 완료일반적으로 동기 (세션이 끝나기 전에 명령이 성공하거나 실패)종종 비동기 (API가 “수락됨"을 반환, 최종 상태를 위해 폴링 필요)
실패 모호성끊어진 NETCONF 세션은 명확한 실패클라우드 API 타임아웃은 변경이 적용됐을 수도 있고 아닐 수도 있음; 확인하기 위해 쿼리해야 함
멱등성 접근기본적으로 명령형; 선언적은 신중한 모듈 설계 필요대부분의 플랫폼에서 기본적으로 선언적 (Terraform, CloudFormation, Pulumi)

권장 아키텍처

인텐트 (SoT)와 오케스트레이션을 통합 상태로 유지하라. 캠퍼스 스위치에서 VLAN을 생성하는 동일한 비즈니스 요청이 AWS에서 보안 그룹을 생성해야 할 수도 있다. 진실의 원천이 두 가지를 모두 보유한다. 오케스트레이터가 두 가지를 모두 조율한다. 네트워크 어댑터 레이어 (5.2.5)에서만 실행 경로가 분기된다: 하나의 어댑터가 캠퍼스 스위치에 대해 NETCONF/Ansible을 처리하고; 다른 어댑터가 클라우드 환경에 대해 Terraform이나 클라우드 프로바이더 API를 처리한다.

이것은 클라우드 API 변경과 네트워크 장비 변경의 폭발 반경이 동일한 승인과 감사 추적에 의해 관리된다는 것을 의미하며, 프로토콜이 완전히 다르더라도 올바른 아키텍처 결과다.

비밀 관리 전략은 오래 지속되는 네트워크 장비 자격 증명과 단기 클라우드 토큰 모두를 수용해야 한다. 클라우드 플랫폼의 경우, 자격 증명은 작업 시작 시 생성되어야 하며 저장되어서는 안 된다. 대부분의 클라우드 프로바이더는 이를 위한 메커니즘을 제공한다 (AWS 인스턴스 프로파일, Azure 관리 ID, GCP 워크로드 ID). 네트워크 장비 자격 증명은 Vault에 남아 런타임에 주입된다. 실행기는 대상 유형에 관계없이 작업 실행 간에 자격 증명을 보유해서는 안 된다.

5.2.9. 보안 고려 사항#

실행기는 아키텍처에서 네트워크에 대한 쓰기 접근 권한을 가진 구성 요소다. 수백 대의 장비에 걸쳐 설정을 푸시하고, 서비스를 재시작하고, 펌웨어를 업그레이드하고, 라우팅 정책을 변경할 수 있다. 이것은 보안 자세를 거의 다른 어떤 구성 요소보다 더 중요하게 만들지만, 아키텍처 관점에서 가장 방치되는 경우가 많다. 손상되거나 잘못 설정된 실행기는 데이터 침해 위험이 아니다; 네트워크 중단 위험이다.

자격 증명 관리

네트워크 장비의 자격 증명은 플레이북, 템플릿, 작업 정의, 또는 버전 관리에 절대 나타나서는 안 된다. 패턴은 간단하다: 전용 비밀 관리자 (HashiCorp Vault, AWS Secrets Manager, CyberArk)에 비밀을 저장하고 런타임에 실행기의 환경에 주입한다. 실행기는 작업 시작 시 자격 증명을 가져오고, 영구적으로 보유하지 않는다.

이것은 또한 자격 증명 교체가 운영상 안전해진다는 것을 의미한다. 장비 비밀번호가 정기 일정에 따라 교체될 때, 실행기가 재배포나 재설정 없이 다음 실행에서 새 자격 증명을 가져온다.

작업별 최소 권한

모든 실행이 동일한 수준의 접근 권한이 필요한 것은 아니다. 읽기 작업 (사실 수집, 드라이런 검사, 사전 배포 유효성 검사)은 읽기 전용 자격 증명을 사용해야 한다. 쓰기 작업은 변경 유형에 범위가 지정된 자격 증명을 사용해야 한다: VLAN 변경을 배포하는 작업은 BGP 설정 변경을 허용하는 자격 증명을 보유해서는 안 된다. 네트워크 플랫폼이 역할 기반 접근을 지원하는 경우 (Arista 역할, Cisco 권한 수준, NETCONF 접근 제어), 단일 손상된 세션의 폭발 반경을 제한하기 위해 사용하라.

캠퍼스 예시에서, 이것은 Arista 스위치에서의 VLAN 배포 작업이 VLAN과 인터페이스 설정을 허용하지만 라우팅 프로토콜 설정이나 관리 플레인 설정을 건드릴 수 없는 역할을 사용한다는 것을 의미한다.

관심사 분리

실행을 트리거할 수 있는 사람과 자동화 로직을 수정할 수 있는 사람은 다른 접근 제어를 가진 다른 역할이어야 한다. VLAN 배포 작업을 승인하고 실행하는 운영자는 그것을 구현하는 Ansible 역할이나 플레이북 템플릿을 수정하는 접근 권한이 필요하지도 않고 가져서도 안 된다. AWX와 AAP에서, 이것은 역할 할당을 통해 강제된다: “Job Launcher"는 사전 승인된 작업 템플릿을 시작할 수 있고; “Project Editor"는 기저 자동화 로직을 변경할 수 있다.

이 분리는 자동화가 높은 영향 작업에 사용될 때 가장 중요하다. 800대 스위치에 걸쳐 펌웨어 업그레이드를 시작하는 사람이 업그레이드 플레이북을 작성한 사람이기도 해서는 안 되며, 적어도 두 번째 검토자가 프로덕션에서 사용 가능하기 전에 플레이북을 승인해야 한다.

감사 추적 요구사항

모든 실행 이벤트는 무슨 일이 일어났는지 재구성하기에 충분한 세부 정보와 함께 기록되어야 한다: 누가 작업을 트리거했는지, 어떤 작업 템플릿이 사용됐는지, 어떤 장비가 대상이었는지, 어떤 파라미터가 전달됐는지, 각 장비의 결과는 무엇이었는지, 어떤 타임스탬프에. 로그는 규제 산업의 변경 관리에 일반적으로 12-24개월인 조직에 적용되는 컴플라이언스 기간 동안 보존되어야 한다.

이것은 대부분의 엔터프라이즈 환경에서 선택 사항이 아니다. 변경 관리 프로세스는 변경 티켓을 특정 실행 이벤트와 특정 장비 변경 집합에 연결하는 추적 가능한 기록이 필요하다. 이 감사 추적을 나중에 개조하는 것이 아닌 처음부터 실행기에 설계하라.

자동화 인프라의 네트워크 분리

실행기와 그 컨트롤 플레인 (AWX, Ansible 컨트롤 노드)은 관리 네트워크 세그먼트에 있어야 한다. 장비에 대한 실행 트래픽은 실행기가 수정할 수 있는 동일한 데이터 플레인이 아닌, 가용한 경우 대역 외 관리 네트워크를 통해 흘러야 한다. 작업을 트리거하기 위한 인바운드 접근은 인증이 필요하며 신뢰할 수 없는 네트워크에서 도달할 수 없어야 한다.

피해야 할 실용적인 실패 모드: 프로덕션 사용자 VLAN에서 도달 가능한 실행기는 해당 VLAN의 손상된 호스트가 잠재적으로 네트워크 변경을 트리거할 수 있다는 것을 의미한다. 관리 플레인 접근은 관리 플레인 네트워크에 속한다.

5.3. 구현 예시#

5.3.1. 유스케이스: 이질적인 캠퍼스에 걸친 자동화된 VLAN 배포#

4장의 캠퍼스 네트워크를 계속한다. VLAN 서비스 정의 (ID, 서브넷, 벤더별 설정 템플릿, 대상 스위치 그룹)가 이제 진실의 원천에 저장되어 있다. 이 장은 실행 블록이 그 인텐트를 어떻게 선택하고 800대 스위치 전체에 안정적으로 배포하는지에 초점을 맞춘다.

시나리오: 비즈니스팀이 새 애플리케이션을 위한 새 VLAN을 요청한다. 요청이 세부 정보와 함께 티케팅 시스템을 통해 들어온다: VLAN ID, 이름, 캠퍼스 사이트, 승인. 네트워크 운영이 Arista, HPE, Cisco 액세스/배포 스위치 전반에 이 VLAN을 배포하고, 연결성을 확인하며, 성공을 보고한다.

요구사항:

  • 여러 스위치에 VLAN 설정을 병렬로 배포
  • 사전 조건 확인 (VLAN이 이미 존재하지 않음, 스위치에 도달 가능)
  • 변경될 내용을 보여주는 드라이런 수행
  • 실패가 발생하면 롤백 기능과 함께 실제 배포 실행
  • 배포 후 확인 (VLAN이 활성, 오류 없음)

5.3.2. 솔루션 아키텍처#

실행 세부 사항에 들어가기 전에 전체 아키텍처로 확대:

  1. 진실의 원천: NetBox (인벤토리, VLAN 정의 저장)
  2. 오케스트레이션/트리거링: AWX 워크플로 템플릿 조율 (API 실행, 웹훅 실행, 예약 실행)
  3. 옵저버빌리티: 의사 결정을 위한 실시간 데이터 제공
  4. 실행:
    • 실행 엔진: Ansible 작업 템플릿/작업 (사전 검사, 드라이런, 배포, 확인, 롤백)
    • 데이터 통합: Ansible/AWX의 NetBox 인벤토리 플러그인
    • 네트워크 어댑터: Cisco IOS XE, Arista EOS, HPE/Aruba를 위한 Ansible 모듈
    • 상태 관리: 단계별 AWX 작업 및 워크플로 상태 + 호스트별 결과, 실패 경로에서 롤백 작업 포함

이 솔루션은 설명 목적으로, 범용 권장 사항이 아니다.

5.3.3. 구현 흐름#

여러 작업 노드가 있는 AWX 워크플로로 실행된다:

  1. NetBox의 VLAN 인텐트 변경이 웹훅을 통해 AWX를 트리거한다
  2. AWX 워크플로가 시작되고 인벤토리 동기화 실행 (NetBox)
  3. 사전 검사 작업이 도달 가능성, 기존 VLAN 상태, 사이트 가드레일을 유효성 검사한다
  4. 드라이런 작업이 변경을 적용하지 않고 벤더별 작업을 렌더링한다
  5. 승인 노드 (선택적)가 프로덕션 실행을 게이팅한다
  6. 배포 작업이 벤더 전반에 걸쳐 병렬 배치로 VLAN 인텐트를 적용한다
  7. 확인 작업이 운영 및 의도된 상태를 확인한다
  8. 실패 시, 롤백 작업이 영향받은 범위에 대해서만 실행된다
  9. 옵저버빌리티가 최종 유효성 검사를 지원하기 위한 전/후 데이터를 수집한다
  10. 최종 유효성 검사가 실행되고 실행 요약이 게시된다
flowchart TD
    A[NetBox VLAN 인텐트 변경] --> B[AWX로 웹훅]
    B --> C[AWX 워크플로 시작]
    C --> E[NetBox에서 인벤토리 동기화]
    E --> F[사전 검사 작업]
    F --> G[드라이런 작업]
    G --> H[승인 노드]
    H --> I[배포 작업]
    I --> J[확인 작업]
    J --> N[네트워크 데이터 수집]
    N --> L[최종 유효성 검사]
    L --> M[티켓 업데이트 및 보고]

    F -->|실패| X[중지 및 유효성 검사 오류 반환]
    I -->|실패한 호스트| Y[영향받은 장비에 대한 롤백 작업]
    Y --> J
    F --> N

    classDef sot fill:#cfe8ff,stroke:#0b5fb3,stroke-width:2px;
    classDef orch fill:#ffe0cc,stroke:#c24b00,stroke-width:2px;
    classDef exec fill:#d7f4e1,stroke:#0f7a3b,stroke-width:2px;
    classDef obs fill:#ffd6d6,stroke:#b22222,stroke-width:2px;

    class A sot;
    class B,C,E,H,L,M orch;
    class F,G,I,J,Y,X exec;
    class N obs;

5.3.4. 솔루션 요약#

이 구현은 모든 주요 실행 기능을 시연한다:

  • 데이터 통합: 동적 인벤토리를 사용해 NetBox에서 인벤토리와 인텐트를 가져옴
  • 트리거링: AWX 워크플로가 API/웹훅/일정을 지원하고, 승인 게이팅 실행 포함
  • 상태 관리: AWX 워크플로/작업 상태가 진행 상황과 실패 도메인을 추적; 롤백 경로가 명시적
  • 엔진: Ansible이 병렬 실행 (forks/배치를 통해 설정), 오류 처리, 드라이런을 처리
  • 네트워크 어댑터: 벤더 네이티브 리소스 모듈이 하나의 인텐트를 다른 플랫폼에 매핑 (cisco.ios.ios_vlans, arista.eos.eos_vlans, HPE/Aruba 컬렉션 모듈)
  • 옵저버빌리티: 전/후 데이터 수집이 최종 유효성 검사와 보고를 지원

복원력 기능:

  • 사전 검사가 도달할 수 없는 장비나 중복 VLAN 생성을 방지
  • 드라이런이 적용 전에 변경을 보여줌
  • 안전 체크포인트는 적용 상태 (merged, replaced, overridden) 전에 리소스 모듈 상태 (rendered, parsed, gathered) 사용
  • 멱등적 모듈이 재실행을 안전하게 만듦
  • 배포 후 확인이 조용한 실패를 잡아냄
  • 롤백: 전용 AWX 롤백 노드가 롤백을 영향받은 장비 범위로 제한

확장 고려 사항:

  • forks: 50을 통한 병렬 실행이 50대 스위치를 동시에 처리
  • 더 큰 배포 (500대 이상 스위치)의 경우, 그룹으로 배치하고 워크플로 관리에 Ansible Tower/AWX 사용
  • 컨트롤 플레인이나 인증 시스템이 부하를 처리할 수 없는 경우 속도 제한 추가

5.4. 요약#

실행 블록은 네트워크 자동화가 실제로 드러나는 곳이다: 설정이 푸시되고, 장비가 재부팅되며, 변경이 발생한다. 하지만 안정적인 실행은 Secure Shell (SSH)를 통해 명령을 보내는 것 이상이다.

데이터 통합과 트리거링으로 시작해서, 상태 관리 (멱등성, 롤백, 트랜잭션과 유사한 안전성), 가능한 엔진 (직렬/병렬 선택, 드라이런/계획, 복원력), 벤더 전반의 프로토콜 차이를 숨기는 네트워크 어댑터 레이어에 의존한다. 옵저버빌리티가 유효성 검사와 가드레일에 피드를 제공하고, 오케스트레이션이 진행할지 중지할지 결정한다. 도구보다 실행 패턴이 더 중요하다: 데이터를 통합하고, 의도적으로 트리거하며, 결정론적으로 실행하고, 결과를 유효성 검사하며, 깔끔한 롤백 경로를 유지하라.

작게 시작하고 의도적으로 확장하라: 하나의 워크플로, 하나의 장비 클래스, 명확한 전/후 검사. 폭발 반경이 커짐에 따라 배치, 속도 제한, 더 강한 유효성 검사를 추가하라. 실행은 강력하고 위험하다; 철저하게 테스트하고, 점진적으로 출시하며, 지속적으로 모니터링하고, 항상 롤백을 계획하라.

참고 자료#

💬 Found something to improve? Send feedback for this chapter