글 목록으로 돌아가기

노드별 네트워크를 스크립트 하나로 설정하고 장애에 대비하기

Client Isolation 제약 속에서 노드 5대의 네트워크를 하나의 스크립트로 관리하고, 게이트웨이 장애와 원격 작업 실패에 대비한 복구 경로를 구성하고 검증한 과정

Dohyeon Kim
Dohyeon Kim 2026년 10월 27일 · 14분 읽기
Project Orbit Network Netplan Tailscale

1. 아이디어

ORBIT 프로젝트는 물리 서버 5대로 k3s 클러스터를 구성하는 프로젝트입니다. 인프라 초기 설계 과정에서 노드의 역할과 구성이 계속 바뀌었고, 그때마다 노드마다 netplan을 손으로 고치는 일이 반복됐습니다. 그래서 네트워크 설정을 코드로 관리해 변경을 쉽게 하고, 제가 없어도 팀원이 네트워크를 수정하거나 복구하고 다른 환경에서도 같은 구성을 재현할 수 있도록 하는 것을 목표로 했습니다. 네트워크 범위에 한정되지만 IaC에 가까운 접근이라고 생각합니다.

글에서 사용하는 호스트 이름과 역할은 다음과 같습니다.

호스트 이름 기기 역할
polaris ODROID-M1S (SBC) k3s Control Plane
astra 데스크톱 게이트웨이(NAT), 모니터링 VM 호스트, 클러스터 밖
sol 갤럭시북 k3s Worker
terra 갤럭시북 k3s Worker
luna 갤럭시북 k3s Worker, Observability
io 홈서버 부하 발생기, 클러스터 밖 (이 글의 범위 밖)

1) 환경의 제약

강의실 네트워크를 사용하면서 확인한 제약은 다음과 같습니다.

  • 공유기에서 예약받은 와이파이 IP는 .200, .201, .202 세 개뿐입니다. 예약되지 않은 IP를 기기에서 고정으로 사용하면 공유기가 같은 IP를 다른 기기에 할당해 충돌이 생기고, 실제로 luna가 계속 끊기는 문제가 있었습니다.
  • 강의실 와이파이는 client isolation이 적용되어 있어 같은 와이파이에 붙은 기기끼리 직접 통신이 막힙니다. 노드끼리 와이파이로 통신할 수 없고, tailscale도 강의실 안에서는 직접 연결이 안 되어 릴레이를 거칩니다.
  • 작업은 대부분 강의실 밖에서 SSH로 진행합니다. 네트워크 설정을 바꾸다가 접속이 끊기면 강의실에 가서 콘솔로 복구해야 합니다.

2) 네트워크 구성

이 제약을 고려해 astra 한 대만 와이파이로 인터넷에 나가고, 나머지 노드는 유선 내부망으로 묶어 astra를 거쳐 나가는 게이트웨이 구조로 결정했습니다. 주소는 각 서버의 역할과 중요도, 성능에 따라 와이파이와 유선 끝자리를 같은 번호로 고정했습니다.

노드 유선 (내부망) 와이파이 평소 인터넷 경로
polaris 10.0.0.200 192.168.200.200 (예약) 자기 와이파이
astra 10.0.0.201 192.168.200.201 (예약) 자기 와이파이, 내부망 NAT
sol 10.0.0.202 192.168.200.202 (예약) astra 경유
terra 10.0.0.203 192.168.200.203 astra 경유
luna 10.0.0.204 192.168.200.204 astra 경유

예약 IP 세 개는 와이파이에 실제로 의존하는 노드에 배정했습니다. polaris는 Control Plane이라 astra가 죽어도 살아 있어야 하므로 자기 와이파이를 우선 경로로 두고, astra는 인터넷 출구이므로 예약 IP가 필요합니다. terra와 luna는 평소에 유선으로 astra를 거치고 와이파이는 예비 경로로만 유지하므로 충돌이 생겨도 영향이 적습니다.

게이트웨이를 astra로 둔 이유

astra는 데스크톱이라 항상 켜두기 좋고, NAT는 패킷 주소를 바꿔 넘기는 작업뿐이라 부하가 거의 없습니다. polaris를 게이트웨이로 두는 방법도 있었지만, polaris의 와이파이 동글이 USB 2.0에 5GHz 일부 채널만 지원해 느리고, Control Plane 역할까지 겹치면 장애가 한 곳에 몰린다고 판단했습니다.

astra와 polaris는 스위치에 묶여 있지만 인터넷은 각자의 와이파이를 쓰는 이유

astra는 내부망의 출구 자체이므로 자기 와이파이로 나가는 것이 당연하고, 유선은 워커의 패킷을 받아 넘기는 용도입니다. polaris는 Control Plane이라 astra가 꺼져도 클러스터 상태를 유지해야 합니다. polaris까지 astra를 거쳐 인터넷에 나가면 astra 한 대의 장애가 게이트웨이와 Control Plane 양쪽으로 번지므로, polaris는 자기 와이파이를 우선 경로(metric 100)로 두고 astra 경유를 예비 경로(metric 600)로 뒀습니다. 워커와 반대 구성입니다. 유선은 클러스터 내부 통신과 노드 간 접속에만 사용하므로 인터넷 경로가 달라도 노드끼리 통신하는 데는 영향이 없습니다.

3) 복구 경로

네트워크를 바꾸는 작업은 실수하면 접속 자체가 끊기므로, 문제 상황별로 돌아올 수 있는 길을 세 단계로 나눠 준비했습니다.

상황 복구 수단
netplan 적용 후 접속이 끊김 120초 안에 확정하지 않으면 이전 설정으로 자동 복구
astra가 죽어 워커 인터넷이 끊김 워커의 감시 서비스가 와이파이 경로로 자동 전환, astra 복구 시 복귀
와이파이나 tailscale로 노드에 접속이 안 됨 공유기 포트포워딩을 통한 예비 SSH, 같은 스위치의 다른 노드에서 유선 링크 로컬 주소로 접속

2. 네트워크 설정 스크립트

1) 구조

노드마다 netplan 파일을 손으로 작성하는 대신, 모든 노드에 같은 스크립트 파일 하나를 두고 실행한 노드가 자기 MAC 주소를 찾아 자기 설정을 생성하는 방식으로 만들었습니다.

orbit-net.sh
├── 공통 값      SSID, 게이트웨이, DNS, metric
├── 비밀값       와이파이 비밀번호는 저장하지 않고 apply 할 때 입력
├── 노드 목록    이름 / 역할 / 유선 MAC / 와이파이 MAC / 유선 IP / 와이파이 IP
└── 로직
    1. 이 기기의 MAC을 노드 목록에서 찾음
    2. 역할(gateway / cp / worker)에 맞는 netplan을 생성
    3. 적용, 호스트 이름, NAT, 장애 전환 서비스

노드 목록은 노드당 한 줄입니다.

polaris  cp       <유선 MAC>  <와이파이 MAC>  10.0.0.200  192.168.200.200
astra    gateway  <유선 MAC>  <와이파이 MAC>  10.0.0.201  192.168.200.201
sol      worker   <유선 MAC>  <와이파이 MAC>  10.0.0.202  192.168.200.202
terra    worker   <유선 MAC>  <와이파이 MAC>  10.0.0.203  192.168.200.203
luna     worker   <유선 MAC>  <와이파이 MAC>  10.0.0.204  192.168.200.204

netplan 파일 대신 스크립트를 사용한 이유

처음에는 5대의 설정을 MAC 매칭으로 netplan 파일 하나에 넣었는데, netplan에는 변수나 조건문이 없어서 SSID, 비밀번호, DNS 같은 공통 값을 노드마다 반복해야 했습니다. 판단은 스크립트가 하고 netplan은 결과만 받도록 나누면 공통 값은 한 번만 적고 노드별 값은 한 줄씩만 관리할 수 있습니다. Ansible이 실무 표준이고 구조도 같지만, 아직 Mac에서 각 노드로 가는 접속 경로가 정리되기 전이라 노드에서 직접 실행하는 스크립트를 먼저 만들었습니다. 노드 목록은 나중에 그대로 Ansible inventory로 옮길 수 있습니다.

장치를 이름이 아니라 MAC으로 찾는 이유

호스트 이름은 이번 작업에서 바꾸는 대상이라 기준으로 쓸 수 없고, enp2s0 같은 장치 이름은 랜카드를 다른 슬롯에 꽂거나 BIOS 설정이 바뀌면 달라질 수 있습니다. MAC은 장치에 고정되어 있어 위치나 순서가 바뀌어도 그대로입니다. 대신 USB 와이파이 동글처럼 장치 자체를 교체하면 MAC이 바뀌므로 그때는 노드 목록의 MAC만 수정합니다.

2) 명령

sudo ./orbit-net.sh              # preview. 감지된 노드와 생성될 netplan 출력. 아무것도 바꾸지 않음
sudo ./orbit-net.sh apply        # netplan 생성 및 적용
sudo ./orbit-net.sh hostname     # 호스트 이름, /etc/hosts, tailscale 이름
sudo ./orbit-net.sh gateway      # astra만. 패킷 전달, NAT, 통과 허용 규칙
sudo ./orbit-net.sh watch        # 워커만. 장애 전환 서비스 설치
sudo ./orbit-net.sh all          # watch, apply, hostname, gateway 순서로 실행

역할에 맞지 않는 단계는 스크립트가 건너뛰므로 모든 노드에서 같은 명령을 사용합니다.

3) 생성되는 netplan

luna에서 preview로 확인한 netplan의 핵심 부분입니다.

      addresses: [10.0.0.204/24]
      routes:
        - to: default
          via: 10.0.0.201
          metric: 100
        - to: 9.9.9.9/32
          via: 10.0.0.201
  wifis:
    wlo1:
      addresses: [192.168.200.204/22]
      routes:
        - to: default
          via: 192.168.200.1
          metric: 600
  • 유선 기본 경로는 astra 10.0.0.201을 거치며 metric 100, 와이파이 기본 경로는 metric 600입니다. metric은 값이 낮을수록 우선하므로 평소에는 유선을 사용하고, 유선 경로가 삭제되면 와이파이를 사용합니다. polaris는 반대로 와이파이가 100, 유선이 600입니다.
  • 9.9.9.9/32 경로는 장애 감시용입니다. 이 주소로 가는 ping은 와이파이로 전환된 상태에서도 항상 astra 쪽으로 가기 때문에 astra가 복구되었는지 계속 확인할 수 있습니다.

4) apply의 안전장치

apply는 원격에서 실행해도 접속이 끊기지 않도록 세 가지를 먼저 확인합니다.

먼저 와이파이 비밀번호를 입력받으면 설정 파일을 건드리기 전에 임시로 와이파이에 접속해 봅니다. 워커는 SSH가 유선으로 들어오기 때문에 와이파이 비밀번호가 틀려도 접속이 끊기지 않고, 예비 와이파이가 죽은 채로 확정된 것을 astra가 죽는 날에야 알게 됩니다. 그래서 확인된 비밀번호로만 설정을 작성합니다.

다음으로 기존 /etc/netplan/*.yaml을 /root/netplan-backup/<시각>/에 백업하고, /etc/netplan/200-orbit.yaml 하나만 남긴 뒤 netplan generate로 문법을 확인합니다.

마지막으로 netplan apply 직전에 120초 뒤 이전 설정으로 되돌리는 systemd 타이머를 걸어 둡니다. 적용 후 접속이 유지되면 Enter를 눌러 타이머를 취소하고, 접속이 끊기거나 Enter를 누르지 못하면 타이머가 이전 설정으로 복구합니다. 확정하지 않으면 스크립트는 거기서 멈추고 다음 단계로 넘어가지 않습니다.

netplan try 대신 타이머를 사용한 이유

처음에는 netplan try를 사용했습니다. 확인 입력이 없으면 자동으로 이전 설정으로 돌아가는 명령이라 원격 작업에 적합하다고 판단했기 때문입니다. 그런데 netplan try는 실행 시점의 파일을 원래 상태로 보기 때문에, 파일을 먼저 바꾼 뒤 try를 하면 바뀐 파일 기준으로 되돌립니다. terra에서 Reverting이 출력됐는데도 새 설정이 그대로 남아 있는 것을 확인하고, 직접 백업하고 타이머로 되돌리는 방식으로 바꿨습니다.

5) 게이트웨이 설정

astra에서 gateway 단계가 하는 일은 다음과 같습니다.

항목 내용
패킷 전달 net.ipv4.ip_forward=1을 /etc/sysctl.d/에 작성. 받은 패킷을 다른 인터페이스로 넘김
NAT -s 10.0.0.0/24 -o wlo1 -j MASQUERADE. 내부망에서 와이파이로 나가는 패킷의 출발지를 astra 와이파이 주소로 변경
통과 허용 내부망에서 와이파이로, 그 응답만 와이파이에서 내부망으로. Docker가 있으면 DOCKER-USER 체인에, 없으면 FORWARD에 추가
저장 iptables-persistent로 재부팅 후에도 유지

Docker가 설치된 호스트는 FORWARD 체인의 기본 정책이 DROP으로 바뀌므로, Docker가 규칙을 평가하기 전에 거치는 DOCKER-USER 체인에 허용 규칙을 넣어야 합니다. 같은 규칙이 이미 있으면 다시 넣지 않으므로 여러 번 실행해도 됩니다.

3. 게이트웨이 장애 전환

1) 필요한 이유

astra가 꺼져도 워커의 랜선은 스위치에 연결된 상태라, 리눅스는 유선 경로가 살아 있다고 판단하고 계속 그쪽으로 패킷을 보냅니다. metric만으로 예비 경로로 넘어가는 것은 우선 경로의 연결선이 끊길 때뿐이므로, astra 너머로 인터넷이 되는지 주기적으로 확인해서 경로를 바꿔 주는 서비스가 필요했습니다.

2) 동작 방식

워커 3대에 orbit-gw-watch 서비스를 설치했습니다.

  1. 10초마다 9.9.9.9로 ping을 보냅니다. 이 주소는 netplan에서 항상 astra를 거치도록 고정되어 있습니다.
  2. 3번 연속 실패하면 astra 기본 경로(metric 100)를 삭제합니다. 와이파이 경로(metric 600)만 남아 인터넷이 와이파이로 나갑니다.
  3. ping이 다시 성공하면 astra 기본 경로를 다시 넣어 복귀합니다.
  4. 전환할 때마다 logger로 시스템 로그에 남깁니다.

전환과 복귀의 기준은 다음과 같습니다.

항목 기준 이유
감시 대상 9.9.9.9 astra를 거치도록 고정 경로를 둔 주소. 와이파이로 넘어간 뒤에도 이 주소로 가는 ping은 astra 쪽으로만 가므로 astra 복구 여부를 계속 알 수 있음
감시 주기 10초 너무 짧으면 일시적인 지연에도 전환이 잦아지고, 너무 길면 끊긴 시간이 길어짐
ping 대기 시간 2초 응답이 2초 안에 없으면 1회 실패로 집계
전환 조건 3회 연속 실패 약 30초 동안 응답이 없어야 전환. 한두 번의 패킷 손실로 전환하지 않도록 함
복귀 조건 1회 성공 astra가 응답하면 바로 복귀. 복귀는 늦출 이유가 없음
전환 방법 astra 기본 경로만 삭제 와이파이 경로는 건드리지 않고 우선순위가 높은 경로만 치워서 자연스럽게 와이파이가 선택되게 함

전환 시점에는 최대 30초 정도 인터넷이 끊길 수 있지만, 유선으로 이뤄지는 클러스터 내부 통신에는 영향이 없습니다. 와이파이 예비 경로는 와이파이가 연결된 상태여야 동작하므로 워커의 와이파이가 평소에도 붙어 있는지 확인해 둬야 합니다.

#!/bin/bash
PROBE=9.9.9.9
METRIC=100
LAN_IF=enp2s0
FAIL_LIMIT=3
INTERVAL=10
fail=0
while true; do
  GW=$(ip route show "$PROBE" | awk '/via/{print $3; exit}')
  if [ -z "$GW" ]; then sleep "$INTERVAL"; continue; fi
  if ping -c 1 -W 2 "$PROBE" >/dev/null 2>&1; then
    fail=0
    if ! ip route show default | grep -q "via $GW dev $LAN_IF"; then
      ip route replace default via "$GW" dev "$LAN_IF" metric "$METRIC"
      logger -t orbit-gw "게이트웨이($GW) 응답함, 게이트웨이 경로로 복귀"
    fi
  else
    fail=$((fail + 1))
    if [ "$fail" -ge "$FAIL_LIMIT" ] && ip route show default | grep -q "via $GW dev $LAN_IF"; then
      ip route del default via "$GW" dev "$LAN_IF" metric "$METRIC"
      logger -t orbit-gw "게이트웨이($GW) 응답 없음 (${fail}회), 와이파이로 전환"
    fi
  fi
  sleep "$INTERVAL"
done
  • ip route show 9.9.9.9 : 해당 주소로 가는 경로를 출력. awk로 via 뒤의 게이트웨이 주소만 추출
  • ping -c 1 -W 2 : 1회만 전송하고 2초 안에 응답이 없으면 실패
  • ip route replace / del default : 기본 경로를 추가하거나 삭제. metric까지 지정해 와이파이 경로는 건드리지 않음
  • logger -t orbit-gw : orbit-gw 태그로 journal에 기록. journalctl -t orbit-gw로 조회

게이트웨이 주소를 스크립트에 적지 않는 이유

처음에는 GW=10.0.0.200처럼 주소를 직접 적었습니다. 그런데 내부망 번호를 바꾸는 작업에서 netplan은 새 주소를 쓰는데 감시 서비스는 옛 주소로 기본 경로를 계속 되돌리는 문제가 생겼습니다. 같은 값을 두 곳에 적어 두면 하나를 바꿀 때 다른 하나를 빠뜨리기 쉬우므로, 주소는 netplan 한 곳에만 두고 스크립트는 9.9.9.9 고정 경로에서 매번 읽어오도록 바꿨습니다.

systemd 서비스는 Restart=always로 등록해 스크립트가 죽어도 5초 뒤 다시 시작하고, 부팅 시 자동 실행되도록 했습니다. 서비스 파일을 수정한 뒤에는 systemctl enable --now가 아니라 restart를 실행해야 합니다. enable --now는 이미 실행 중인 서비스를 다시 시작하지 않기 때문에 옛 코드가 계속 동작합니다.

4. 원격 접속과 복구 경로

1) tailscale

노드 5대에 tailscale을 설치하고 tailscale SSH를 켜 두었습니다. 기기 이름은 OS 호스트 이름과 같게 맞춰 ssh orbit@luna처럼 접속합니다.

sudo apt install -y tailscale
sudo systemctl enable --now tailscaled
sudo tailscale up --ssh

--ssh는 tailscale SSH를 사용하는 옵션으로, 비밀번호 대신 tailnet 계정으로 인증합니다.

tailscale은 강의실 와이파이의 client isolation 때문에 강의실 안에서도 직접 연결되지 않고 릴레이를 거칩니다. 그래서 노드 간 통신에는 쓰지 않고 원격 관리 용도로만 사용합니다. OS 호스트 이름을 바꿔도 tailscale 이름은 자동으로 바뀌지 않으므로 orbit-net.sh hostname이 tailscale set --hostname까지 함께 실행합니다.

2) 예비 SSH

tailscale이 안 될 때를 대비해 강의실 공유기에서 받은 포트 중 하나를 astra의 SSH로 포워딩해 두었습니다. astra를 거쳐 내부망 노드로 들어가는 방식입니다.

ssh -J orbit-gw orbit@10.0.0.200

-J는 ProxyJump 옵션으로, 먼저 orbit-gw(astra)에 로그인한 뒤 그 연결을 통해 목적지 노드의 22번 포트로 접속합니다. SSH는 웹과 달리 접속한 이름을 서버에 보내지 않기 때문에 이름으로 노드를 나눌 수 없고, 대신 게이트웨이를 한 번 거치는 방식을 사용합니다.

3) 유선 링크 로컬 주소

와이파이와 tailscale이 모두 안 되는 노드도 같은 스위치에 붙어 있으면 다른 노드에서 IPv6 링크 로컬 주소로 접속할 수 있습니다. 이 주소는 IP 설정 없이 MAC에서 자동으로 만들어지므로 netplan 적용에 실패한 노드에도 통합니다.

ping -6 ff02::1%enp2s0        # 같은 스위치의 모든 기기에 응답 요청
ip -6 neigh                   # 응답한 기기의 fe80 주소와 MAC 목록
ssh orbit@fe80::xxxx:xxxx:xxxx:xxxx%enp2s0
  • ff02::1 : 같은 링크의 모든 노드를 뜻하는 멀티캐스트 주소. %enp2s0으로 어느 인터페이스로 보낼지 지정
  • ip -6 neigh : 응답한 기기의 MAC이 함께 표시되므로 어느 노드인지 바로 구분 가능

실제로 astra 유선이 DOWN 상태라 tailscale로 들어갈 수 없었을 때, terra에서 이 방법으로 astra에 접속해 ip link set enp2s0 up으로 복구했습니다.

5. 적용과 검증

1) 배포와 미리보기

Mac에서 스크립트를 5대에 한 번에 복사합니다.

for h in astra sol terra luna polaris; do scp orbit-net.sh orbit@$h:~/ && echo "$h OK"; done

각 노드에서 preview로 자기 노드를 제대로 인식하는지 확인합니다.

chmod +x ~/orbit-net.sh
sudo ~/orbit-net.sh 2>&1 | head -3

첫 줄에 감지된 노드: luna (worker)처럼 노드 이름과 역할이 나와야 합니다. MAC이 노드 목록에 없음이 나오면 그 노드에서 ip -br link로 MAC을 확인해 노드 목록을 고칩니다.

2) 적용

이번 적용은 유선 끝자리를 와이파이와 같은 번호로 재배치하는 작업이었습니다. 이전 구성에서는 astra가 .200, sol이 .201이었기 때문에, 바꿀 주소를 지금 누가 쓰는지 확인해서 비어 있는 번호부터 채우는 순서(luna, terra, sol, astra, polaris)로 한 대씩 진행했습니다. 같은 주소를 두 노드가 동시에 쓰는 구간을 만들지 않기 위해서입니다.

적용 전 5대의 상태입니다. 워커 3대는 via 10.0.0.200으로 이전 astra 주소를 거치고 있습니다.

ip -br a | grep 10.0.0
ip route get 9.9.9.9
  • ip -br a : 인터페이스 목록을 한 줄씩 간략하게 출력
  • ip route get 9.9.9.9 : 해당 주소로 패킷을 보낼 때 어느 경로를 타는지 출력. traceroute가 없어도 경로 확인 가능

노드마다 Mac에서 SSH로 접속해 sudo ~/orbit-net.sh all을 실행했습니다. 리눅스 콘솔에는 한글 글꼴이 없어 “확정하려면 Enter” 안내가 깨져 보이므로 SSH로 진행했습니다.

3) 주소와 경로 확인

5대에 각각 접속하는 대신 Mac에서 for문으로 SSH를 반복 실행해 한 화면에서 비교했습니다.

for h in luna terra sol astra polaris; do
  ssh orbit@$h 'printf "%-8s " $(hostname); ip -br a | awk "/10\.0\.0\./{printf \$3\"  \"}"; ip route get 9.9.9.9 | head -1'
done

워커 3대는 새 astra 주소 .201을 거치고, polaris와 astra는 자기 와이파이로 나가는 것을 확인할 수 있습니다.

4) 워커 확인

for h in luna terra sol; do
  ssh orbit@$h 'echo "== $(hostname)"; ping -c1 -W2 google.com >/dev/null && echo 인터넷OK; for n in polaris astra sol terra luna; do ping -c1 -W1 $n >/dev/null && printf "$n OK  " || printf "$n FAIL  "; done; echo; systemctl is-active orbit-gw-watch; grep ORBIT -A7 /etc/hosts | grep 10.0.0'
done
  • ping google.com : astra NAT를 거쳐 인터넷이 되는지
  • 노드 이름 ping : /etc/hosts의 ORBIT 블록이 새 번호로 갱신되었는지, 내부망끼리 통하는지
  • systemctl is-active orbit-gw-watch : 장애 전환 서비스가 실행 중인지

5) astra NAT 확인

ssh orbit@astra 'sysctl -n net.ipv4.ip_forward'
ssh -t orbit@astra 'sudo iptables -t nat -S POSTROUTING | grep MASQ'

두 번째 명령에 ssh -t를 붙인 이유는 sudo가 비밀번호를 입력받으려면 터미널이 필요한데, 명령만 넘기는 SSH는 터미널을 할당하지 않아 a terminal is required 오류가 발생하기 때문입니다.

ip_forward가 1이고 -s 10.0.0.0/24 -o wlo1 -j MASQUERADE 규칙이 있는 것을 확인할 수 있습니다. 두 번째 규칙은 Docker가 컨테이너 네트워크용으로 추가한 것입니다.

6) 장애 전환 시험

astra의 랜선을 뽑았다가 다시 연결하면서 워커가 와이파이로 전환했다가 astra 경로로 복귀하는지 시험했습니다. 랜선을 뽑으면 워커의 9.9.9.9 ping이 astra에 닿지 않으므로 약 30초 뒤 3회 연속 실패 조건이 충족되어 전환되어야 하고, 다시 연결하면 다음 ping 주기에 복귀해야 합니다. 결과는 sol의 로그로 확인했습니다.

ssh orbit@sol 'journalctl -t orbit-gw --no-pager -n 50'
  • -t orbit-gw : 이 태그로 기록된 로그만 조회
  • -n 50 : 최근 50줄만 출력
  • --no-pager : 화면 넘김 없이 그대로 출력

노드 시간은 UTC이므로 한국 시간은 9시간을 더해서 봅니다.

로그 시각 (UTC) 한국 시간 내용
10/26 23:54:52, 23:55:38 10/27 08:54 응답 없음 후 복구. 처음 네트워크 적용 시점, 옛 스크립트
10/27 00:10:53 ~ 00:12:34 09:10 ~ 09:12 “복구됨”이 10초마다 반복. 옛 스크립트, 원인은 확인하지 못함
12:21:02, 12:21:36 21:21 응답 없음 후 복귀. 새 스크립트
12:32:35, 12:34:09 21:32 ~ 21:34 응답 없음 후 복귀. 새 스크립트
12:35:06, 12:35:16 21:35 응답 없음 후 복귀. 새 스크립트

새 스크립트의 전환 기록은 세 번입니다. 이 시간대에는 랜선 시험과 함께 번호 변경 작업으로 astra의 주소가 바뀌는 순간도 있었기 때문에, 세 번 중 어느 것이 랜선 시험이고 어느 것이 주소 변경인지는 시험 시각을 따로 기록하지 않아 구분하지 못했습니다. 다만 세 번 모두 응답 없음 (3회) 뒤에 와이파이로 전환이 기록되고, astra가 응답하자 게이트웨이 경로로 복귀가 이어진 것은 같으므로, 랜선이 빠지든 주소가 바뀌든 설계한 조건대로 전환과 복귀가 동작하는 것을 확인할 수 있습니다. 12:35:16 이후 기록이 없으므로 그 뒤로는 astra 경로를 유지하고 있습니다.

새 스크립트는 게이트웨이(10.0.0.201)처럼 판단 기준이 된 주소를 로그에 함께 남기므로 옛 스크립트의 로그와 구분됩니다. 다음 시험부터는 랜선을 뽑은 시각과 다시 연결한 시각을 함께 기록해 로그와 대조할 수 있도록 하려고 합니다.

7) 결과

항목 결과
내부망 새 번호 (.200 ~ .204) 통과 (5대)
워커 경로 astra 경유 통과 (3대)
워커 인터넷 통과 (3대)
노드 이름으로 서로 ping 통과 (3대 각각 5대 모두)
/etc/hosts 새 번호 통과
orbit-gw-watch active (3대)
astra ip_forward, MASQUERADE 통과
장애 전환 (랜선 분리, 재연결) 통과. 전환과 복귀가 로그로 확인됨

6. 다음 작업

1) Ansible로 옮기기

지금 구성은 스크립트 하나로 동작하지만, 노드마다 scp로 파일을 복사하고 각 노드에 접속해 실행해야 합니다. 스크립트를 고치면 5대에 다시 복사해서 버전을 맞춰야 하고, 어느 노드에 어떤 버전이 적용되어 있는지는 직접 확인하기 전까지 알 수 없습니다. 앞으로 k3s 설치, 패키지 설치, SSH 설정처럼 모든 노드에 반복해야 하는 작업이 늘어나면 이 방식으로는 관리가 어려워진다고 판단했습니다.

Ansible은 관리 노드(Control Node) 한 곳에서 SSH로 여러 서버에 접속해 설정을 적용하는 도구입니다. 서버 목록(inventory)과 변수, 작업 순서(playbook)를 YAML로 작성하고, 대상 서버에는 별도의 에이전트를 설치하지 않습니다. 지금 스크립트와 비교하면 다음과 같습니다.

항목 지금 스크립트 Ansible
실행 위치 각 노드에 접속해서 실행 Mac 또는 astra 한 곳에서 5대에 한 번에 실행
노드 목록 스크립트 안의 NODES 줄 inventory 파일. 그대로 옮길 수 있음
공통 값과 노드별 값 스크립트 변수 group_vars, host_vars로 분리
netplan 생성 스크립트가 문자열로 조립 Jinja2 템플릿
반복 실행 같은 규칙이 있으면 건너뛰도록 직접 처리 대부분의 모듈이 변경이 필요할 때만 적용
다른 작업 추가 스크립트에 기능을 계속 추가 role 단위로 분리해 추가

노드 목록, 공통 값, 노드별 값, 템플릿으로 나뉘는 구조는 지금 스크립트와 같습니다. 처음부터 Ansible로 만들지 않은 이유는 Mac에서 각 노드로 가는 접속 경로가 정리되기 전이라 노드에서 직접 실행하는 쪽이 먼저 쓰기 쉬웠기 때문입니다. 이제 tailscale과 유선 접속 경로가 정리됐으므로, 다음 작업으로 손으로 한 설정을 role로 옮기고 astra에서 실행하는 구성으로 전환할 예정입니다.

2) WoL을 이용한 전원 장애 복구

지금의 장애 전환은 astra가 응답하지 않을 때 워커가 와이파이로 우회하는 것까지입니다. 네트워크 경로는 유지되지만 astra가 전원이 꺼진 상태라면 누군가 강의실에 가서 켜기 전까지 게이트웨이와 모니터링 호스트가 비어 있게 됩니다.

이 구성에서는 모든 노드가 같은 스위치에 유선으로 묶여 있고, 각 노드가 와이파이라는 별도의 경로를 하나 더 갖고 있습니다. 그래서 한 노드가 꺼져도 나머지 노드는 와이파이로 인터넷에 나갈 수 있고, 유선으로는 꺼진 노드와 같은 네트워크에 남아 있습니다. WoL(Wake on LAN)은 같은 네트워크의 기기가 보낸 매직 패킷을 받아 꺼진 호스트의 전원을 켜는 기능이므로, 이 조건이라면 외부 개입 없이 노드끼리 서로를 깨울 수 있습니다.

구상하는 흐름은 다음과 같습니다.

  1. 워커의 감시 서비스가 astra 응답 없음으로 와이파이로 전환합니다.
  2. 전환 뒤에도 일정 시간 이상 astra가 응답하지 않으면 유선으로 astra의 MAC을 향해 WoL 매직 패킷을 보냅니다.
  3. astra가 부팅되면 netplan과 NAT 설정이 재부팅 후에도 유지되도록 해 두었으므로 게이트웨이 역할이 자동으로 복구되고, 워커는 기존 복귀 조건에 따라 astra 경로로 돌아옵니다.

같은 방식으로 astra에서 워커나 polaris를 깨우는 것도 가능합니다. 먼저 각 노드의 BIOS와 네트워크 장치에서 WoL이 켜져 있는지, 전원이 꺼진 상태에서도 유선 포트에 대기 전원이 들어오는지 확인해야 하고, 단순히 응답이 없는 것과 전원이 꺼진 것을 구분하는 기준도 정해야 합니다. 홈서버에서 이미 WoL로 원격 부팅을 사용하고 있으므로 같은 방식을 ORBIT 노드에 적용해 볼 예정입니다.

댓글