노트북 한 대, 홈 라우터, 기가비트 스위치. 격리된 클러스터 서브넷 하나.
에피소드 2에서는 M715q 4대에 듀얼 채널 메모리와 NVMe 드라이브를 업그레이드하고, Slurm 작업을 망가뜨리는 iGPU 메모리 함정을 수정했습니다. 이번 에피소드에서는 클러스터를 온라인으로 올려요. Rocky Linux 설치, 네트워크 설계, 그리고 노트북을 내부 클러스터 서브넷의 DHCP 서버, NAT 게이트웨이, SSH 요새(bastion)로 만드는 작업입니다.
1. 토폴로지 결정 #
실제 HPC에서는 관리망과 컴퓨트망이 엄격하게 유선으로 분리되고, 물리적으로 나뉘며, VLAN이 설정된 관리형 스위치에 연결됩니다. 엔터프라이즈급 관리형 스위치 하나가 이 클러스터 전체 비용보다 비쌀 수 있습니다.
홈 빌드에서는 현실적인 경로가 두 가지입니다.
- 평탄한 홈 네트워크. 모든 노드를 홈 라우터에 연결합니다. 쉽지만 모든 노드가 스마트폰, TV, IoT 기기와 같은 네트워크에 노출됩니다. 격리가 없고, 기기 하나가 뚫리면 클러스터 전체에 접근할 수 있습니다.
- 전용 스위치로 물리적 격리. 모든 클러스터 노드가 자체 서브넷 뒤의 비관리형 스위치에 연결됩니다. 로그인 노드가 두 세계를 연결합니다.
저는 2번을 선택했습니다. Netgear GS308E가 격리를 담당합니다. 로그인 노드는 경계에 위치해서 내부 클러스터 서브넷의 DHCP, DNS, NAT를 처리합니다. 워커 노드는 홈 네트워크를 직접 볼 일이 없습니다.
결과는 실제 HPC와 동일한 패턴입니다. 로그인 노드가 가장자리에, 내부 패브릭이 그 뒤에, 컴퓨트 노드에는 외부에서 직접 접근 불가. 차이는 규모입니다. InfiniBand 대신 기가비트 이더넷, 스파인-리프 토폴로지 대신 비관리형 소비자 스위치. 아키텍처는 같고 규모의 차수만 다릅니다.
참고: HP Envy GPU 노드(corsair-01)는 동일한 스위치에 연결되고 다른 노드와 동일한 기본 OS 및 네트워크 설정을 받습니다. 그 박스의 GPU 쪽은 이후 에피소드에서 설정할 겁니다.
2. OS 설치 #
모든 노드는 Rocky Linux 10, 최소 설치입니다. NanoKVM으로 ISO를 마운트하고 브라우저로 인스톨러를 구동해서 기기를 하나씩 돌아가며 설치했습니다. 모니터와 키보드가 있다면 같은 방식으로 해도 됩니다.
설치 자체는 별게 없습니다. ISO 부팅, 최소 설치 선택, 부트 드라이브 지정, 실행, 재부팅.
설치 중에 미리 계획해둬야 할 두 가지가 있습니다.
모든 노드에 sudo 사용자를 만드세요. 나중에 SSH로 접속할 계정입니다. root SSH는 비활성화할 거라, 이 계정 없이는 잠기게 됩니다.
모든 노드에서 동일한 사용자 이름을 쓰세요. 나중에 ssh-copy-id를 실행할 때 기본적으로 로컬 사용자 이름이 사용되므로, 양쪽 사용자 이름이 일치하면 ssh-copy-id arbiter처럼 간단하게 됩니다. FreeIPA가 이후 에피소드에서 이 로컬 계정들을 중앙 집중식 아이덴티티로 대체하지만, 일관성이 있으면 전환이 더 매끄럽습니다.
3. WiFi의 로그인 노드 #
로그인 노드인 carrier는 리퍼비시된 Lenovo IdeaPad 1 노트북입니다. WiFi와 이더넷 포트 하나가 있습니다. 대부분의 가이드는 로그인 노드를 유선 연결해야 한다고 하는데, 저는 의도적으로 WiFi를 썼습니다.
외부 쪽에 왜 WiFi를 쓰나요? 로그인 노드는 패키지 업데이트, 데이터셋 다운로드, 외부에서 SSH 접속을 위해 인터넷이 필요합니다. 홈 라우터까지 이더넷 케이블을 연결해도 되지만, 클러스터 노드에 필요한 스위치 포트 하나를 낭비하고 방을 가로질러 케이블이 하나 더 생깁니다. WiFi는 로그인 노드가 실제로 필요하지 않은 대역폭을 희생해서 그 제약을 없애줍니다.
내부 쪽에 왜 이더넷을 쓰나요? 모든 무거운 트래픽(NFS 읽기, MPI 메시지, 스케줄러 하트비트)은 전체 기가비트 속도로 유선 스위치에 머물러야 합니다. 로그인 노드의 이더넷 포트가 그 패브릭으로 들어가는 게이트웨이입니다.
다른 것들이 작동하기 전에 노트북 특유의 세 가지 설정이 필요합니다.
필수 패키지. 이 시리즈 전반에서 컴파일러, git, 에디터가 필요합니다.
sudo dnf upgrade -y
sudo dnf install -y epel-release
sudo dnf install -y vim git wget tree curl gcc-c++ cmake m4덮개 닫힘 수정. 기본적으로 노트북 덮개를 닫으면 systemd-logind가 머신을 절전 모드로 전환합니다. 로그인 노드에서는 재앙입니다. 덮개를 닫는 순간 클러스터 전체가 DHCP 서버, NAT 게이트웨이, SSH 진입점을 잃습니다. /usr/lib/systemd/logind.conf의 한 줄 변경으로 수정합니다.
HandleLidSwitch=ignoresudo systemctl restart systemd-logind 후에는 노트북을 덮개 닫은 채로 클러스터 스택 위에 올려두어도 절전으로 빠지지 않습니다.
라우팅 우선순위. 두 개의 활성 인터페이스(WiFi, 이더넷)가 있으면 Linux는 라우트 메트릭 기준으로 어느 쪽으로 인터넷 트래픽을 보낼지 결정합니다. 낮은 메트릭이 우선입니다. 기본적으로 유선 연결이 WiFi보다 낮은 메트릭을 가질 때가 많아서, 인터넷 트래픽이 홈 라우터로의 경로가 없는 클러스터 스위치로 나가게 될 수 있습니다. WiFi의 메트릭을 강제로 낮추는 게 해결책입니다.
nmcli connection modify <WIFI NAME> ipv4.route-metric 10
nmcli connection down <WIFI NAME> && nmcli connection up <WIFI NAME>연결 이름은 nmcli connection show로 확인합니다. 이후 ip route show default에서 WiFi가 첫 번째 (기본) 경로로 표시되어야 합니다.
4. DHCP: IP 주소 배포 #
워커 노드들은 IP 주소가 필요합니다. 홈 라우터에 연결이 없으니 홈 라우터의 DHCP가 워커 노드에 닿지 않습니다. 로그인 노드가 DHCP 서버 역할을 해야 합니다.
먼저 로그인 노드의 클러스터 쪽에 고정 주소를 부여합니다. 워커들이 이걸 게이트웨이로 사용할 겁니다.
nmcli connection modify <WIRED NAME> ipv4.addresses 192.168.50.1/24 ipv4.method manual
nmcli connection up <WIRED NAME>이제 dnsmasq를 설치하고 설정합니다. isc-dhcp-server 대신 선택한 이유는 가볍고, 단일 바이너리이고, DHCP와 DNS를 동시에 처리하기 때문입니다. 6노드 클러스터에서 이보다 더 복잡한 건 과도합니다.
sudo dnf install -y dnsmasq
sudo mv /etc/dnsmasq.conf /etc/dnsmasq.conf.bak새 /etc/dnsmasq.conf는 약 열 줄 정도입니다.
interface=<WIRED INTERFACE>
dhcp-range=192.168.50.10,192.168.50.50,12h
dhcp-option=3,192.168.50.1
dhcp-option=6,1.1.1.1,8.8.8.8
log-queries
log-dhcp인터페이스 이름은 nmcli device로 확인합니다. 각 줄이 하는 일입니다.
interface=는 dnsmasq를 유선 쪽에만 제한합니다. 없으면 dnsmasq가 WiFi에서도 DHCP 요청에 응답하려 해서 홈 라우터와 충돌합니다.dhcp-range=는 dnsmasq가 배포할 IP 풀과 임대 기간(12시간)을 정의합니다.dhcp-option=3,192.168.50.1은 로그인 노드를 기본 게이트웨이로 광고합니다. 워커들이 인터넷 목적지 트래픽을 어디로 보낼지 이걸로 배워요.dhcp-option=6,1.1.1.1,8.8.8.8은 워커들에게 사용할 DNS 서버를 알려줍니다 (Cloudflare와 Google을 공개 폴백으로).log-queries와log-dhcp는 상세 로깅을 켭니다. 초기 구동 중에는 필수입니다. 클러스터가 안정화되면 꺼도 됩니다.
방화벽을 열고 서비스를 시작합니다.
sudo firewall-cmd --permanent --add-service=dhcp
sudo firewall-cmd --permanent --add-service=dns
sudo firewall-cmd --reload
sudo systemctl enable --now dnsmasq팁: 워커 부팅 중 로그인 노드에서
journalctl -u dnsmasq -f를 실행하면 DHCP 핸드셰이크 전체(DHCPDISCOVER,DHCPOFFER,DHCPREQUEST,DHCPACK)가 실시간으로 보입니다. 워커가 주소를 못 받을 때 원인 진단에 매우 유용합니다.
5. NAT: 워커가 인터넷에 접근하도록 #
DHCP가 192.168.50.x 범위의 IP를 배포했습니다. 이것들은 RFC 1918에서 인터넷에서 라우팅 불가로 정의된 사설 주소입니다. 워커가 dnf.rocky.example.com에 패킷을 보내면 클러스터 스위치로 나갔다가 길을 잃고 사라집니다. 외부로 나가는 경로가 없거든요.
해결책은 NAT(Network Address Translation, 네트워크 주소 변환)입니다. 로그인 노드가 모든 아웃바운드 패킷의 소스 주소를 자체 WiFi 쪽 IP로 다시 씁니다. 응답 패킷이 WiFi IP로 돌아오면 로그인 노드가 어느 내부 소스로 돌려보낼지 조회해서 포워딩합니다. 홈 라우터가 집 안의 모든 기기에 하는 것과 동일한 방식입니다.
두 가지가 필요합니다.
IP 포워딩. 기본적으로 Linux 머신은 인터페이스 간 패킷을 전달하지 않습니다. 명시적으로 허용해야 합니다.
sudo sysctl -w net.ipv4.ip_forward=1
echo "net.ipv4.ip_forward = 1" | sudo tee /etc/sysctl.d/99-ipforward.conf첫 번째 명령어가 즉시 포워딩을 활성화하고, 두 번째가 재부팅 후에도 유지되게 합니다.
마스커레이드 규칙. 포워딩이 활성화되면 커널이 인터페이스 간 패킷을 라우팅하지만, 소스 주소는 다시 쓰지 않습니다. firewalld의 마스커레이드 규칙이 그 재작성을 하도록 커널에 지시합니다.
sudo firewall-cmd --permanent --add-masquerade
sudo firewall-cmd --reload확인:
sudo firewall-cmd --list-all | grep masquerademasquerade: yes가 표시되어야 합니다.
워커를 온라인으로 올리고 테스트. 워커 노드를 켜세요. 로그인 노드에서 임대 파일을 확인합니다.
cat /var/lib/dnsmasq/dnsmasq.leases각 줄에 타임스탬프, MAC 주소, IP, 호스트명이 있습니다. 설치 중에 만든 sudo 계정으로 워커에 SSH 접속합니다.
ssh <user>@192.168.50.11
ping -c 3 1.1.1.1ping이 되면 DHCP, 라우팅, NAT, DNS의 모든 요소가 제대로 작동하는 겁니다.
6. IP 대신 호스트명 사용 #
IP 주소를 매번 입력하는 건 금방 지치는 일입니다. 게다가 서브넷에 새 번호를 매기면 모든 스크립트, 설정 파일, 커밋 히스토리에 잘못된 주소가 하드코딩됩니다. 호스트명은 간접 참조입니다. 간접 참조는 저렴한 보험이고요.
사용하는 이름입니다.
| 호스트명 | IP | 역할 |
|---|---|---|
carrier |
192.168.50.1 | 로그인 노드 |
arbiter |
192.168.50.50 | 관리/NFS |
interceptor-01 |
192.168.50.15 | 컴퓨트 |
interceptor-02 |
192.168.50.32 | 컴퓨트 |
observer |
192.168.50.19 | 시각화 |
corsair-01 |
192.168.50.11 | GPU |
로그인 노드를 포함한 각 노드에서:
sudo hostnamectl set-hostname <HOSTNAME>그런 다음 로그인 노드의 /etc/hosts에 모든 노드를 추가합니다.
192.168.50.1 carrier.cluster.local carrier
192.168.50.15 interceptor-01.cluster.local interceptor-01
192.168.50.32 interceptor-02.cluster.local interceptor-02
192.168.50.11 corsair-01.cluster.local corsair-01
192.168.50.19 observer.cluster.local observer
192.168.50.50 arbiter.cluster.local arbiter이제 ssh 192.168.50.50 대신 ssh arbiter가 가능합니다. 이건 임시방편입니다. 이후 에피소드에서 FreeIPA가 제대로 된 DNS 서버를 올려서 각 노드의 /etc/hosts를 수정하지 않아도 클러스터 전체에서 호스트명이 해석됩니다.
7. 노출된 표면 강화 #
로그인 노드만 홈 WiFi에서 접근 가능합니다. 워커들은 자체 서브넷 뒤 NAT에 있어서 홈 네트워크에서 직접 접근할 수 없습니다. 강화 작업은 carrier에 집중합니다.
중요한 세 가지입니다. SSH 설정 자체, 브루트포스 방지, 그리고 노트북 특유의 systemd 수정입니다.
SSH 드롭인 설정. Rocky 10의 기본 /etc/ssh/sshd_config는 /etc/ssh/sshd_config.d/*.conf에서 파일을 포함하고, 동일한 설정이 여러 파일에 있으면 첫 번째 값이 우선합니다. 이건 드롭인 구성 시스템입니다. 메인 설정을 편집하지 않고, 변경할 것들만 담은 새 파일을 추가합니다.
실질적으로 변경하는 건 root SSH 직접 로그인 비활성화 하나입니다.
sudo tee /etc/ssh/sshd_config.d/99-custom.conf > /dev/null <<'EOF'
PermitRootLogin no
EOF
sudo sshd -t # 문법 검증
sudo systemctl reload sshd몇 가지 설정은 의도적으로 기본값을 유지합니다.
- 공개 키 인증은 기본으로 활성화돼 있습니다. 설정 변경 없이
ssh-copy-id가 동작합니다. - 패스워드 인증도 기본으로 활성화되어 있고 그대로 둬요. 대학 클러스터를 쓰던 HPC 사용자들은 패스워드 로그인에 익숙하고, 이후 에피소드의 FreeIPA가 어차피 중앙 집중식 인증으로 라우팅할 겁니다.
fail2ban과 적절한 패스워드 정책의 조합으로 충분히 방어가 됩니다. - 호스트 키는
HostKey지시문이 없을 때 자동으로 로드됩니다. Rocky 10이 첫 부팅 시 RSA, ECDSA, Ed25519 호스트 키를 생성합니다.
SSH 포트가 firewalld를 통과하는지 확인합니다.
sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --reloadfail2ban. 포트 22는 홈 네트워크에서도 브루트포스 시도를 받습니다. 같은 WiFi의 침해된 IoT 기기 하나로도 시작할 수 있습니다. fail2ban은 인증 로그를 감시해서 짧은 시간 안에 같은 IP에서 너무 많은 실패가 오면 그 IP의 트래픽을 차단하는 임시 방화벽 규칙을 추가합니다.
업스트림 fail2ban 가이드에 따라 변경할 것들만 담은 짧은 jail.local을 작성합니다.
sudo dnf install -y fail2ban
sudo tee /etc/fail2ban/jail.local > /dev/null <<'EOF'
[DEFAULT]
bantime = 10m
maxretry = 3
[sshd]
enabled = true
mode = aggressive
EOF
sudo systemctl enable --now fail2ban하나의 IP에서 3번 인증 실패하면 10분 동안 방화벽 수준에서 차단됩니다. mode = aggressive는 normal, ddos, extra SSH 필터를 합쳐요. 현재 차단 상태는 sudo fail2ban-client status sshd로 확인합니다.
클러스터 전체 패스워드 없는 SSH. 키 인증이 활성화되고 SSH 감시가 설정된 다음, 마지막은 키 배포입니다. 로그인 노드에서 일반 sudo 사용자로 (root는 어차피 SSH 불가):
ssh-keygen -t ed25519
ssh-copy-id <user>@arbiterssh-keygen이 ~/.ssh/에 개인/공개 키 쌍을 만듭니다. ssh-copy-id가 패스워드 인증으로 대상 머신에 한 번 로그인한 다음, 공개 키를 그 머신의 ~/.ssh/authorized_keys에 추가합니다. 이후 SSH 시도에서 서버가 공개 키를 보고 대응하는 개인 키가 있는지 확인하고, 패스워드 없이 접속을 허용합니다. 각 워커에 반복합니다. 그러면 로그인 노드에서 ssh arbiter가 패스워드를 요구하지 않습니다.
선택적: 노트북 로그인 노드용 sshd 시작 오버라이드. 이 노트북 기반 로그인 노드에서 이더넷 인터페이스가 완전히 설정되기 전에 sshd가 시작 실패하는 부팅 타임 문제가 간혹 있었습니다. 정확한 원인을 당시에 캡처하지 못해서 확실하지는 않습니다. 표준 수정은 sshd가 network-online.target을 기다리고 실패 시 재시도하는 systemd 오버라이드입니다. 재부팅 후 sshd가 실패 상태라면 journalctl -u sshd -b로 확인합니다. 데스크탑이나 서버 하드웨어에서 빌드하면 이 설정이 필요 없을 겁니다.
sudo systemctl edit sshd.service로 적용하고 붙여넣습니다.
[Unit]
Wants=network-online.target
After=network-online.target
[Service]
Restart=on-failure
RestartSec=5s
StartLimitIntervalSec=0systemctl edit이 /etc/systemd/system/sshd.service.d/override.conf에 드롭인 파일을 만들고 daemon-reload를 자동으로 실행합니다.
내부 노드: 방화벽 끄기 #
이 섹션의 내용은 전부 carrier에 관한 거였습니다. 나머지 다섯 노드는 다른 이야기입니다.
그 노드들은 NAT 뒤 192.168.50.0/24에 있습니다. 홈 WiFi에서 직접 접근할 수 없고, 유일한 인바운드 경로는 carrier를 통해서입니다. 이 노드들의 firewalld는 실질적인 방어를 제공하지 않지만, 동작해야 하는 것들을 막습니다. NFS 콜백, Kerberos와 LDAP를 통한 FreeIPA 등록, srun과 스텝 실행을 위해 Slurm이 사용하는 동적 포트의 긴 목록입니다. 이 전체에 걸쳐 정확한 방화벽 규칙을 유지하는 건 지루하고 실수하기 쉽습니다.
더 단순한 접근법은 로그인 노드가 아닌 모든 노드에서 끄는 겁니다. 격리된 HPC 패브릭의 표준 관행이기도 합니다.
sudo systemctl disable --now firewalld보안 경계선은 carrier입니다. 경계 안의 모든 노드들은 서로 신뢰할 수 있습니다.
8. WiFi가 병목이 아닌 이유 #
이 토폴로지에서 가장 많이 받는 질문은 로그인 노드의 WiFi가 클러스터 병목이 되는지입니다. 그렇지 않습니다. 트래픽 경로가 비대칭이거든요.
SSH로 코드 편집, dnf로 패키지 가져오기, git pull 실행, 브라우저로 시스템 모니터링, 이 모든 게 WiFi로 나갑니다. 대역폭이 민감한 것들이 아닙니다. 수백 Mbps의 WiFi 처리량으로도 충분합니다.
무거운 작업은 전부 기가비트 스위치에서 일어납니다. interceptor-01이 arbiter의 NFS 공유에서 데이터셋을 읽을 때, 그 트래픽은 노드에서 스위치를 거쳐 노드로 갑니다. 로그인 노드는 건드리지 않습니다. 두 워커에서 MPI 작업이 메시지를 주고받을 때도 마찬가지입니다. 전체가 기가비트로 연결되고, 레이턴시도 예측 가능하며, WiFi도 없습니다.
컴퓨트 패브릭은 완전히 유선입니다. WiFi 쪽은 관리와 인터넷 접속에만 씁니다. 홈 네트워크에서 누군가 4K 동영상을 스트리밍해도 클러스터 성능에 영향이 없습니다.
9. 다음 에피소드 #
클러스터가 네트워크에 연결되고, 주소가 지정되고, 접근 가능해졌습니다. 모든 노드에 OS가 있습니다. 호스트명이 해석됩니다. 로그인 노드가 내부 서브넷의 DHCP, NAT, SSH를 처리합니다. 홈 네트워크의 어떤 기기에서든 carrier로 SSH 접속하고 거기서 어떤 워커로든 패스워드 없이 접속할 수 있습니다.
에피소드 4에서는 arbiter에 Samsung 990 Pro를 NFS 공유 스토리지로 마운트하고 중앙 집중식 사용자 관리를 위한 FreeIPA를 올립니다. 그 이후에는 한 번 만든 사용자 계정이 클러스터의 모든 노드에서 동작하고, 모든 노드가 홈 디렉토리 트리를 공유하게 됩니다.
이 에피소드의 모든 설정 파일과 전체 명령어 레퍼런스는 GitHub에 있습니다.
즐거운 컴퓨팅 되세요!