본문 바로가기
IT/Cloud

[인프라] Ansible을 활용한 프라이빗 네트워킹 자동화 구축 사례

by 수누다 2026. 6. 25.

[인프라] Ansible 프라이빗 네트워킹 자동화 구축 사례

Ansible 프라이빗 네트워킹 작업을 손으로 해보신 분들은 아마 비슷한 경험 있으실 겁니다. 서버 2~3대일 때는 상관없었는데, 노드가 늘어나고 서브넷이 여러 개로 갈라지기 시작하면 어느 순간부터 설정이 서로 조금씩 달라지더라고요. 저도 홈랩에서 서비스용 VM, 모니터링 노드, 백업 서버를 따로 굴리면서 프라이빗 네트워킹을 수동으로 맞추다가 꽤 크게 삽질했습니다. 처음엔 이게 뭔가 싶었는데, 결국 답은 Ansible(앤서블, 에이전트 없이 설정을 배포하는 자동화 도구)로 네트워크 상태를 코드로 관리하는 쪽이었어요. 이번 글에서는 제가 직접 구축했던 흐름을 바탕으로, Ansible 프라이빗 네트워킹을 어떻게 정리했고 어떤 문제를 만났는지, 그리고 왜 이 방식이 네트워크 자동화와 IaC 구축 사례로 꽤 실용적인지 풀어보겠습니다.

핵심은 단순합니다. 네트워크 장비를 거창하게 바꾸는 게 아니라, 리눅스 서버들 사이의 프라이빗 네트워킹 규칙을 플레이북으로 통일하고, 터널 인터페이스와 라우팅, 방화벽 정책을 반복 가능하게 만드는 거거든요. 말은 쉬운데 실제로 해보면 변수 설계, 템플릿 관리, 검증 순서에서 많이 흔들립니다. 여기서 중요한 포인트! 처음부터 완벽하게 만들려고 하기보다, 재현 가능한 최소 구성부터 잡는 게 훨씬 중요해요.

Ansible 프라이빗 네트워킹 전체 아키텍처를 보여주는 홈랩 다이어그램

홈랩 서버, 관리 노드, 프라이빗 터널 구간이 한눈에 보이는 전체 구성 예시입니다.

Ansible로 프라이빗 네트워킹을 관리하면 편한 이유

쉽게 말해 수동 설정의 정반대 방식입니다. 예전에는 서버 한 대마다 <code>/etc 아래 설정 파일을 열고, 라우팅을 넣고, 방화벽 규칙을 맞추고, 서비스 재시작까지 사람이 기억에만 의존했거든요. 근데 이 방식은 꼭 한 군데가 빠집니다. 저도 실제로 써보니 어떤 노드는 MTU가 다르고, 어떤 노드는 AllowedIPs가 빠져 있고, 또 어떤 노드는 재부팅 후 설정이 안 살아나는 식으로 문제가 생겼더라고요.

이걸 Infrastructure as Code(IaC, 인프라를 코드로 관리하는 방식)로 바꾸면 상황이 달라집니다.

  • Inventory(인벤토리, 관리 대상 목록)에 어떤 서버가 있는지 정의해요.
  • Variables(변수)로 사설 대역, 터널 주소, 허용 라우트를 분리하죠.
  • Template(템플릿)으로 노드별 설정 파일을 자동 생성합니다.
  • Playbook(플레이북)으로 같은 절차를 모든 노드에 반복 적용하죠.
  • Idempotency(멱등성, 여러 번 실행해도 결과가 같음)를 활용해 drift를 줄여요.

즉, 네트워크 자동화를 하겠다는 말이 대단한 솔루션 도입이 아니라, 사람이 기억하던 설정을 코드로 옮기는 과정일 뿐입니다. 저도 처음엔 네트워크 자동화라고 하면 스위치나 라우터 자동화부터 떠올렸는데, 막상 운영에서는 서버 간 프라이빗 네트워킹 통일만 해도 체감 효과가 엄청 컸거든요.

이번 IaC 구축 사례: 전제 조건과 구성

이번 예시는 제가 홈랩에서 자주 쓰는 구조를 단순화한 형태입니다. 관리 노드 한 대에서 Ansible을 실행하고, 여러 리눅스 서버에 WireGuard(와이어가드, 경량 VPN 터널) 기반의 사설 통신 구간을 배포하는 방식이에요. 특정 벤더 장비에 종속되지 않아서 연습용으로도 좋고, 나중에 클라우드 VM과 온프레미스 서버를 함께 묶을 때도 응용하기 편하더라고요.

항목 수동 구성 Ansible 활용
터널 설정 배포 서버마다 직접 작성 템플릿으로 일괄 생성
라우팅 반영 명령어 수동 입력 변수 기반 반복 적용
방화벽 정책 누락 가능성 높음 태스크로 표준화
검증 접속 후 개별 확인 애드혹 명령과 플레이북으로 점검
변경 추적 문서 의존 Git 기반 이력 관리

제가 실제로 정리한 목표는 아래 네 가지였습니다.

  1. 사설 터널 인터페이스 설정을 노드별로 자동 생성할 것
  2. 서브넷 광고와 허용 라우트를 변수로 분리할 것
  3. 방화벽 정책을 최소 허용 원칙으로 맞출 것
  4. 검증 명령까지 플레이북 운용 흐름에 포함할 것

사실 여기까지만 정리해도 운영 난이도가 확 내려갑니다. 특히 장애가 났을 때 "이 서버는 누가 어떻게 바꿨지?"가 아니라 "현재 Git에 있는 정의와 실제 상태가 같은가?"로 질문이 바뀌는 게 큽니다.

실전 구현 1: 디렉터리와 Inventory 설계

제가 직접 해보니 제일 먼저 잡아야 할 게 파일 구조였습니다. 플레이북보다 구조가 먼저더라고요. 구조가 흔들리면 변수 이름이 꼬이고, 나중에는 어디를 고쳐야 하는지 찾느라 시간이 더 들어가거든요.

ansible-private-network/
├── inventory/
│   └── hosts.yml
├── group_vars/
│   └── private_nodes.yml
├── host_vars/
│   ├── node-a.yml
│   ├── node-b.yml
│   └── node-c.yml
├── templates/
│   └── wg0.conf.j2
└── playbooks/
    └── private-network.yml

인벤토리는 이렇게 시작했습니다.

all:
  children:
    private_nodes:
      hosts:
        node-a:
          ansible_host: 10.10.0.11
        node-b:
          ansible_host: 10.10.0.12
        node-c:
          ansible_host: 10.10.0.13

그룹 변수에는 공통 속성을 넣어요.

private_network_interface: wg0
private_network_port: 51820
private_network_cidr: 10.200.0.0/24
private_network_mtu: 1380
private_network_service_name: wg-quick@wg0
private_network_allowed_udp_port: 51820

그리고 호스트 변수에는 노드별 정보만 남깁니다.

wireguard_address: 10.200.0.1/24
wireguard_private_key: "{{ vault_node_a_private_key }}"
wireguard_peers:
  - name: node-b
    public_key: "{{ vault_node_b_public_key }}"
    endpoint: "node-b.example.internal:51820"
    allowed_ips:
      - 10.200.0.2/32
  - name: node-c
    public_key: "{{ vault_node_c_public_key }}"
    endpoint: "node-c.example.internal:51820"
    allowed_ips:
      - 10.200.0.3/32

여기서 중요한 포인트는 공통 값과 개별 값을 철저히 분리하는 거예요. 처음엔 저도 peers까지 그룹 변수에 밀어 넣었다가, 특정 노드만 광고해야 하는 라우트가 달라지면서 구조를 다시 뜯었거든요. 처음 설계가 깔끔하면 이후 네트워크 자동화가 훨씬 편해집니다.

Ansible 프라이빗 네트워킹 인벤토리와 변수 구조를 설명하는 구성도

인벤토리, 그룹 변수, 호스트 변수의 역할 분리를 설명하는 구성도입니다.

실전 구현 2: 템플릿과 플레이북으로 프라이빗 네트워킹 배포

이제 실제 설정을 만듭니다. 템플릿은 Jinja2(진자2, 변수 치환 템플릿 엔진)를 사용하고, 노드마다 다른 값만 렌더링되게 구성하죠.

[Interface]
Address = {{ wireguard_address }}
ListenPort = {{ private_network_port }}
PrivateKey = {{ wireguard_private_key }}
MTU = {{ private_network_mtu }}

{% for peer in wireguard_peers %}
[Peer]
PublicKey = {{ peer.public_key }}
Endpoint = {{ peer.endpoint }}
AllowedIPs = {{ peer.allowed_ips | join(', ') }}
PersistentKeepalive = 25
{% endfor %}

플레이북은 아래처럼 구성했습니다.

- name: Configure private networking with Ansible
  hosts: private_nodes
  become: true
  vars:
    wireguard_config_path: "/etc/wireguard/{{ private_network_interface }}.conf"
  tasks:
    - name: Install WireGuard package on Debian or Ubuntu
      ansible.builtin.apt:
        name: wireguard
        state: present
        update_cache: true
      when: ansible_facts['os_family'] == 'Debian'

    - name: Ensure wireguard config directory exists
      ansible.builtin.file:
        path: /etc/wireguard
        state: directory
        owner: root
        group: root
        mode: '0700'

    - name: Render wireguard configuration
      ansible.builtin.template:
        src: ../templates/wg0.conf.j2
        dest: "{{ wireguard_config_path }}"
        owner: root
        group: root
        mode: '0600'
      notify: restart wireguard

    - name: Allow WireGuard UDP port
      ansible.builtin.iptables:
        chain: INPUT
        protocol: udp
        destination_port: "{{ private_network_allowed_udp_port }}"
        jump: ACCEPT
        comment: Allow WireGuard

    - name: Enable IP forwarding for routed nodes
      ansible.posix.sysctl:
        name: net.ipv4.ip_forward
        value: '1'
        state: present
        reload: true
      when: routed_node | default(false)

    - name: Enable and start wireguard service
      ansible.builtin.systemd:
        name: "{{ private_network_service_name }}"
        enabled: true
        state: started

  handlers:
    - name: restart wireguard
      ansible.builtin.systemd:
        name: "{{ private_network_service_name }}"
        state: restarted

실행은 단순합니다.

ansible-playbook -i inventory/hosts.yml playbooks/private-network.yml --ask-vault-pass

여기서 저는 Ansible Vault(앤서블 볼트, 민감정보 암호화 기능)를 꼭 같이 쓰는 편입니다. 프라이빗 키를 평문으로 저장해 두면 언젠가 반드시 문제가 돼거든요. 특히 홈랩이라고 방심하면 안 되더라고요. Git에 한 번 잘못 올라가면 정리도 번거롭고요.

또 하나, 라우팅이 필요한 노드라면 단순 터널 생성만으로 끝나지 않습니다. 예를 들어 특정 노드가 192.168.50.0/24 대역 뒤에 있는 내부 자원을 광고해야 한다면, peer의 allowed_ips에 그 대역을 넣고 해당 노드에는 포워딩을 켜야 하죠. 이 부분이 빠지면 터널은 올라왔는데 실제 통신은 안 되는, 가장 답답한 상태가 돼요. 저도 여기서 한참 막혔었습니다.

템플릿에서 실제 설정 파일이 생성되고 서비스가 재시작되는 흐름을 보여주는 다이어그램입니다.

실전 구현 3: 운영 관점에서 꼭 넣어야 할 검증 절차

제가 처음 만든 플레이북은 배포까지만 자동화하고 검증은 손으로 했는데, 그러면 반쪽짜리더라고요. 배포보다 더 중요한 게 결과 확인이거든요. 그래서 아래처럼 최소 검증 루틴을 꼭 넣었습니다.

  1. 서비스 상태 확인
  2. 인터페이스 주소 확인
  3. 피어 간 핑 테스트
  4. 필요 시 라우팅된 내부 대역까지 도달성 확인
ansible private_nodes -i inventory/hosts.yml -m ansible.builtin.shell -a "systemctl is-active wg-quick@wg0"
ansible private_nodes -i inventory/hosts.yml -m ansible.builtin.shell -a "ip addr show wg0"
ansible private_nodes -i inventory/hosts.yml -m ansible.builtin.shell -a "wg show"
ansible private_nodes -i inventory/hosts.yml -m ansible.builtin.shell -a "ping -c 2 10.200.0.1"

조금 더 확실하게 하고 싶다면 검증용 태스크를 별도 태그로 분리하는 것도 좋습니다.

- name: Validate private networking status
  hosts: private_nodes
  become: true
  tasks:
    - name: Check wireguard service state
      ansible.builtin.command: systemctl is-active wg-quick@wg0
      register: wg_service
      changed_when: false

    - name: Print wireguard service state
      ansible.builtin.debug:
        var: wg_service.stdout

이렇게 해두면 변경 배포 직후에 같은 도구로 배포와 검증을 이어서 실행할 수 있어요. 운영에서는 이 연결감이 꽤 중요합니다. "설정은 들어갔습니다"와 "통신이 됩니다"는 완전히 다른 말이거든요.

⚠️ 트러블슈팅: 제가 실제로 막혔던 포인트들

이 섹션은 꼭 넣고 싶었어요. 블로그 글은 대개 예쁘게 완성된 결과만 보여주는데, 실제 운영은 그 전에 삽질이 있거든요 ㅎㅎ 저도 처음엔 금방 끝날 줄 알았는데, 프라이빗 네트워킹은 생각보다 자잘한 함정이 많았습니다.

1. CIDR(사이더, IP 대역 표기) 중복

가장 먼저 만난 문제였습니다. 터널용 대역과 기존 내부망 대역이 겹치면 라우팅이 애매해져요. 특히 홈랩에서는 예전에 대충 잡아둔 10.x 대역이 여기저기 숨어 있는 경우가 많거든요. 해결은 단순하지만 중요합니다. 터널 대역은 기존 VLAN, 내부 서브넷과 절대 겹치지 않게 별도로 예약해야 하죠.

2. MTU 불일치

터널은 올라오는데 큰 패킷에서만 통신이 불안정한 경우가 있었습니다. 처음엔 방화벽 문제인 줄 알았는데, 실제로는 MTU가 맞지 않아서 단편화 이슈가 생긴 거였어요. 이럴 때는 템플릿에 MTU를 명시해서 노드별 편차를 없애는 게 편합니다. 환경마다 정답 값은 다를 수 있어서, 확신 없는 수치를 무작정 복붙하기보다는 현재 경로 MTU를 기준으로 조정하시는 게 좋습니다.

3. AllowedIPs 설계 실수

WireGuard에서 AllowedIPs는 단순 허용 목록이 아니라 사실상 라우팅 의도까지 담아요. 저는 처음에 모든 대역을 넓게 넣었다가 의도치 않은 경로 선점 때문에 통신이 꼬인 적이 있습니다. 여기서 중요한 포인트! peer마다 정말 필요한 대역만 최소한으로 선언해야 합니다.

4. 키 관리

프라이빗 키와 퍼블릭 키를 어떻게 배포할지 초반에 기준이 없으면 금방 혼란스러워집니다. 제가 추천하는 방식은 이겁니다.

  • 프라이빗 키는 Vault로 암호화
  • 퍼블릭 키는 host_vars 또는 별도 데이터 파일에 저장
  • 운영 환경과 실험 환경의 키를 분리
  • 키 재생성 절차를 문서화

이걸 안 해두면 나중에 노드 교체할 때 "어느 키가 어디서 쓰였더라?" 하고 다시 추적하게 돼요. 실제로 한번 겪어보니 이거 진짜 번거롭더라고요.

5. 서비스 재시작 타이밍

설정 파일이 바뀔 때마다 바로 재시작하는 건 편하지만, 여러 태스크가 연달아 변경될 때 중간 상태로 서비스가 흔들릴 수 있어요. 그래서 handler로 재시작을 뒤로 모아두는 구조가 안정적이었습니다. 작은 차이 같아 보여도 운영 중엔 꽤 큽니다.

Ansible 프라이빗 네트워킹 트러블슈팅 포인트를 보여주는 운영 분석 이미지

CIDR 중복, MTU, 라우팅 설정 오류 같은 대표 장애 포인트를 정리한 체크리스트 이미지입니다.

검증 결과: 수동 작업이 줄어든 체감이 확실했습니다

구성을 정리하고 나서 가장 먼저 달라진 게 신규 노드 추가 속도였습니다. 예전에는 서버 한 대를 네트워크에 편입하려면 체크리스트를 보면서 한 줄씩 넣어야 했는데, 지금은 host_vars 추가하고 플레이북 돌린 뒤 검증만 하면 되거든요. 드디어 됐다! 싶은 순간이 여기였어요.

또 좋았던 점은 운영 표준화였습니다. 이전에는 같은 역할 서버인데도 설정 파일이 미묘하게 달랐어요. 누가 언제 손봤는지 애매한 부분도 있었고요. 지금은 인벤토리와 변수 파일이 기준점이 되니, 장애 대응할 때도 시선이 한 군데로 모입니다. 이게 생각보다 큽니다.

  • 신규 노드 편입 절차가 단순해졌습니다.
  • 방화벽 규칙 누락 가능성이 줄었습니다.
  • 사설 통신 경로를 코드로 검토할 수 있게 됐습니다.
  • 변경 이력 추적이 쉬워졌습니다.
  • 다른 환경으로 복제하기 편해졌습니다.

특히 Ansible 활용 관점에서 좋았던 게, 네트워크 설정과 운영 문서가 따로 놀지 않게 됐다는 점이에요. 플레이북 자체가 운영 절차 문서 역할을 하거든요. 이것도 꽤 실용적인 IaC 구축 사례라고 느꼈습니다.

배포 완료 후 서비스 활성 상태와 피어 연결 상태를 검증하는 운영 화면 예시입니다.

정리와 다음 단계

이번 Ansible 프라이빗 네트워킹 구축에서 제가 배운 게 명확했어요. 네트워크 자동화는 거창한 출발이 아니라, 반복되는 수동 설정을 코드로 옮기는 것부터 시작한다는 점 말이죠. 처음엔 변수 분리나 템플릿 구조가 좀 귀찮게 느껴질 수 있는데, 노드가 늘어날수록 그 차이가 확실히 벌어집니다. 저도 처음엔 헷갈렸는데, 한 번 구조를 잡아두니 이후 확장은 훨씬 편했어요.

만약 지금 수동으로 사설 터널, 라우팅, 방화벽을 맞추고 계시다면 이렇게 시작해 보세요.

  1. 대상 노드를 인벤토리로 정리합니다.
  2. 공통 변수와 개별 변수를 분리합니다.
  3. 설정 파일을 템플릿으로 바꿉니다.
  4. 서비스 적용과 검증을 플레이북에 함께 넣습니다.
  5. 민감정보는 Vault로 분리합니다.

이 정도만 해도 운영 피로도가 꽤 줄어듭니다. 다음 글에서는 이번 구성을 이어서 멀티 서브넷 라우팅이나 Git 기반 변경 이력 관리 쪽까지 확장하는 방법도 다뤄볼 예정입니다. 이전 글에서 다뤘던 서버 표준화나 모니터링 자동화와 연결해서 보시면 흐름이 더 잘 보이실 겁니다.

수동 구성과 자동화 구성의 차이, 운영 이점, 다음 확장 방향을 요약한 인포그래픽입니다.

자주 묻는 질문

Q1. 꼭 WireGuard를 써야 하나요?

아니에요. 핵심은 특정 제품이 아니라 프라이빗 네트워킹 설정을 Ansible로 일관되게 관리하는 방식입니다. 다만 예제 설명에는 구조가 비교적 단순한 WireGuard가 잘 맞았거든요.

Q2. 네트워크 장비 자동화와는 다른가요?

조금 달라요. 이번 글은 서버 간 사설 통신 구성에 초점을 맞춘 사례입니다. 하지만 접근 방식 자체는 네트워크 자동화의 좋은 출발점이 되죠.

Q3. 테스트 환경 없이 바로 운영에 넣어도 될까요?

개인적으로는 권하지 않습니다. 저도 홈랩에서 먼저 검증하고 운영에 반영하는 편이거든요. 특히 라우팅과 방화벽은 예상과 다르게 동작할 수 있어서, 작은 테스트 환경이 큰 사고를 막아줘요.

Q4. 어떤 부분부터 문서화해야 하나요?

인벤토리 구조, 변수 규칙, 키 관리 방법, 검증 명령 순서부터 문서화하세요. 이 네 가지가 흔들리면 나머지도 금방 흔들립니다.