soobook
NETWORK

InfiniBand Fabric과 Subnet Manager

Subnet Manager, 그리고 PKey partition이 만드는 격리

InfiniBand 스위치는 스스로 경로를 배우지 않는다. Subnet Manager라는 중앙 소프트웨어가 fabric 전체를 발견하고, 주소를 나눠주고, 모든 스위치의 포워딩 테이블을 계산해 내려보낸다. 테넌트를 나누는 partition도 이 관리자의 소관이다.

Intro

InfiniBand는 GPU 클러스터에서 서버와 서버를 잇는 전용 네트워크다. 멀티노드 학습의 gradient 교환이 전부 이 길을 지나므로, 이 네트워크의 속도가 곧 학습 속도가 된다.

NIC, RDMA, and InfiniBand에서 본 그림이다.

거기서 fabric은 HCA, switch, link, routing, subnet manager, QoS 정책이 합쳐진 network 전체다.

케이블과 스위치 같은 부품 목록이 아니라, 그 부품들을 하나의 네트워크로 조립하고 유지하는 관리 체계까지 포함한 말이다.

HPC (High-Performance Computing)는 수많은 서버를 묶어 하나의 큰 계산을 돌리는 환경이다. GPU 클러스터의 멀티노드 학습도 여기에 속한다.

HCA (Host Channel Adapter)는 HPC 환경에서 쓰는 InfiniBand NIC이다. 서버는 이 카드로 fabric에 붙는다.

이 관리 체계가 InfiniBand 이해의 핵심이다. Ethernet은 케이블을 꽂으면 그냥 동작한다. 스위치가 알아서 주소를 배우기 때문이다.

InfiniBand는 누군가 전체 지도를 그리고, 주소를 나눠주고, 스위치마다 경로를 적어 줘야 비로소 패킷이 흐른다. 그 누군가가 Subnet Manager다.

그래서 fabric을 이해한다는 것은 장비 스펙을 아는 일이라기보다, 이 관리자가 무엇을 하고 무엇을 통제하는지 아는 일이다.

포트가 이유 없이 Down에 머물 때도, VFIO 글에서 IB HCA만은 VF 대신 PF를 통째로 VM에 넘긴다고 했을 때도, 한 fabric을 여러 테넌트가 나눠 쓰는 클라우드에서 테넌트 사이에 벽을 세울 때도, 답은 전부 Subnet Manager에게 있다.

Control Plane

Ethernet 글에서 봤듯 Ethernet 스위치는 스스로 배운다. frame이 들어오면 source MAC을 테이블에 적고, 모르는 목적지는 flooding으로 찾는다. 제어 평면이 스위치마다 분산되어 있어서, 케이블만 꽂으면 네트워크가 만들어진다.

InfiniBand는 반대편 극단이다. 스위치는 아무것도 배우지 않는다. 포워딩 테이블을 계산하는 주체가 따로 있고, 스위치는 그 결과를 받아 적기만 한다.

Subnet Manager (SM)는 IB subnet의 제어 평면을 전담하는 소프트웨어다. fabric의 토폴로지를 발견하고, 모든 포트에 주소를 할당하고, 모든 스위치의 포워딩 테이블을 계산해 기록한다.

Ethernet 스위치는 frame을 보고 MAC table을 스스로 배우지만, InfiniBand는 Subnet Manager가 경로를 계산해 스위치의 LFT에 기록한다

이 구조라서 SM이 없는 IB 네트워크는 동작하지 않는다. Red Hat 문서는 스위치 없이 장비 두 대를 직결한 경우조차 SM이 있어야 한다고 못박는다.

중앙의 소프트웨어가 경로를 계산해 장비에 내려보내는 그림은 SDN 그 자체다.

SDN (Software-Defined Network)은 경로 계산 같은 제어 평면을 개별 장비에서 떼어내 중앙 컨트롤러 소프트웨어에 모으는 네트워크 아키텍처다.

Subnet Manager

SM의 실체는 fabric 어딘가에서 도는 소프트웨어 프로세스 하나다. 서버의 데몬일 수도 있고, 스위치 안에 내장된 프로세스일 수도 있다.

방금 전원이 들어온 fabric은 스위치도 HCA도 서로가 누군지 모른다.

이 상태에서 누가 어디에 붙어 있는지 알아내고, 모두에게 주소를 나눠주고, 모든 스위치에 경로를 적어 넣어야 비로소 패킷이 흐른다.

SM이 해결하는 문제가 바로 이 부분이다. 장비가 빠지고 케이블이 바뀔 때마다 그 변화를 쫓아 같은 그림을 다시 그린다.

SM이 fabric을 세우는 순서를 따라가 보자.

Subnet Manager가 fabric을 세우는 4단계 — directed route SMP로 discovery, LID 할당, LFT 프로그래밍까지 마치면 SUBNET UP, 이후 sweep이 변화를 감지하면 처음부터 다시 도는 사이클

Discovery and LID

먼저 주소 체계 두 개를 구분해야 한다.

GUID (Globally Unique Identifier)는 제조 시점에 박히는 64bit 식별자다. MAC address처럼 하드웨어에 고정된다.

LID (Local Identifier)는 SM이 subnet 안에서 할당하는 16bit 주소다. 스위칭은 전부 LID 기준으로 이뤄진다.

GUID는 하드웨어에 고정된 식별자, LID는 Subnet Manager가 할당하는 이름표로 생각하자.

여기에 닭과 달걀 문제가 있다. LID는 SM이 주는데, SM이 장비와 통신하려면 주소가 필요하다.

???: LID 주세요
SM: 누구세요?

풀려면 먼저 이 통신에 쓰는 패킷을 알아야 한다.

SMP (Subnet Management Packet)는 SM과 장비 사이를 오가는 관리 패킷이다. fabric을 세우고 고치는 데 필요한 질의와 지시가 전부 이 패킷에 실린다.

공식 문서에 자주 나오는 MAD(Management Datagram)는 SMP가 속한 관리 패킷 계열의 이름이다. 이 글에서는 둘을 구분하지 않아도 된다.

SMP를 받는 창구도 정해져 있다. IB에서 통신의 끝점은 QP(Queue Pair)다. 보내는 큐와 받는 큐가 한 쌍이라 붙은 이름으로, 소켓에 대응하는 개념이다.

모든 포트에는 0번 QP가 관리용으로 예약되어 있고, SMP는 언제나 이 QP0로 도착한다.

각 장비에서는 SMA(Subnet Management Agent)가 QP0 뒤에 상주하며 SM의 질의에 응답한다. QP0는 뒤에서 HCA를 VM에 넘길 때 다시 나오니 기억해 두자.

남은 문제는 배달이다. 주소가 없는 장비에게 SMP를 어떻게 보낼까.

답이 directed route다. 목적지 주소 대신, 나갈 포트 번호를 hop 단위로 나열해 경로 자체를 패킷 머리에 적는다.

시나리오로 보자.

SM이 자기 포트에 붙은 스위치까지는 찾았고, 이제 그 너머를 더듬는 중이다.

SM은 “내 1번 포트로 나가서, 스위치의 3번 포트로 나가라”라고 적은 SMP를 보낸다.

스위치는 자기 주소가 없어도 적힌 포트 번호대로 패킷을 넘길 수 있고, 그 끝에 있던 HCA가 자기 정보로 응답한다.

답장은 같은 경로를 거꾸로 되짚어 돌아온다.

directed route SMP의 배달 과정 — SM이 경로(1번 포트, 3번 포트)를 패킷에 적어 보내면 스위치가 LID 없이 hop 단위로 중계하고, 아직 LID가 없는 HCA의 응답은 같은 경로를 거꾸로 돌아온다

SM은 이 질의를 모든 스위치의 모든 포트에 반복하면서 지도를 넓힌다. 주소가 하나도 없는 미구성 fabric의 전체 토폴로지가 이렇게 그려진다.

Routing

토폴로지를 알았으니 경로를 계산한다.

IB 스위치의 포워딩 테이블은 LFT(Linear Forwarding Table)로, 목적지 LID를 인덱스 삼아 출력 포트를 찾는 단순한 배열이다.

이 테이블 전부를 SM이 채운다.

discovery로 지도를 다 그렸으니, 이제 모든 교차로에 이정표를 세우는 셈이다.

계산 방법은 routing engine으로 고른다.

기본은 최소 hop을 찾는 min-hop이고, 토폴로지에 루프가 있으면 deadlock을 피하도록 경로에 제약을 거는 up/down, leaf-spine으로 짠 GPU 클러스터라면 fat-tree 전용 엔진이 congestion 없는 경로 배치를 만든다.

계산이 끝나고 모든 스위치에 LFT가 기록되면 SM 로그에 SUBNET UP이 찍힌다. 이때부터 fabric은 데이터 트래픽을 나를 수 있다.

LFT는 SM이 들고 있는 게 아니라 각 스위치에 기록된다. 그래서 일단 세워진 fabric은 SM이 잠시 죽어도 패킷을 계속 나른다.

Sweep

운영이 시작되면 SM은 감시자가 된다.

기본 10초 간격의 light sweep으로 포트 상태 변화를 폴링하고, 변화가 감지되거나 장비가 보낸 trap이 도착하면 heavy sweep으로 전환한다.

여기서 Sweep은 토폴로지를 다시 발견하고, 필요한 LID를 다시 할당하고, LFT를 다시 계산하는 전체 사이클이다.

대규모 fabric에서 heavy sweep의 라우팅 재계산은 무거운 작업이라, opensm은 토폴로지가 그대로면 이전 라우팅을 보존하고 링크 하나가 빠져도 바뀔 필요가 없는 경로는 건드리지 않는다.

opensm은 오픈소스 SM 구현체다. 서버에 IB 드라이버 스택(OFED)을 깔면 함께 설치되고, 데몬으로 띄우면 그 서버가 SM 역할을 맡는다.

Master and Standby

SM이 하나뿐이면 그 SM이 죽는 순간 fabric은 고아가 된다. 그래서 SM은 여러 개를 띄우고 election으로 역할을 나눈다.

master는 하나다. 나머지는 standby 상태로 대기하다가 master가 사라지면 이어받는다. 선출 기준은 priority(0~15, 높은 쪽이 우선)이고 동률이면 GUID가 가른다. failover가 일어나도 이미 열려 있는 연결은 영향을 받지 않는다. 새 경로 계산부터 새 master의 몫이다.

SM이 도는 위치는 세 갈래다.

호스트에서 opensm 데몬으로 돌리거나, managed 스위치에 내장된 SM을 켜거나, UFM 같은 fabric 관리 플랫폼에 맡긴다.

opensm은 기능 추가가 멈춘 지 오래라, 규모가 있는 fabric은 대부분 스위치 내장 SM이나 UFM을 쓴다.

Partition

fabric이 완성됐다. 이제 이 fabric에 테넌트 여럿이 올라온다면 어떻게 나눌 것인가.

PKey (Partition Key)는 IB fabric을 논리적으로 나누는 16bit 값이다. Ethernet의 VLAN에 대응하는 격리 단위다.

16bit 중 하위 15bit가 partition을 식별하는 key이고, 최상위 1bit는 membership이다. 1이면 full member, 0이면 limited member.

membership이 통신 규칙을 만든다. 매치가 성립하려면 양쪽 중 적어도 하나는 full member여야 한다. 그래서 full은 같은 partition의 누구와도 통신하지만, limited끼리는 같은 partition이어도 통신하지 못한다.

이 비대칭이 쓸모 있는 이유는 공유 자원 때문이다. 상황을 하나 만들어 보자.

테넌트 A의 노드 A1, A2와 테넌트 B의 노드 B1, B2가 한 fabric에 있고, 모두가 마운트해야 하는 공용 스토리지 서버가 한 대 있다. 테넌트끼리는 격리되어야 한다.

full

full member만으로 풀면 두 방법 다 아쉽다. storage partition 하나에 전부 full로 넣으면 테넌트끼리도 통신이 열려 격리가 깨진다.

테넌트마다 스토리지와의 전용 partition을 만들면 격리는 되지만, 테넌트 수만큼 partition이 늘고 스토리지 서버는 그 전부에 가입해야 한다. 포트의 PKey table은 크기가 정해진 하드웨어 테이블이라 낭비가 크다.

limited

limited는 이걸 partition 하나로 줄인다. storage partition에 스토리지 서버만 full로, 테넌트 노드 전부를 limited로 넣는다.

“적어도 한쪽은 full” 규칙에 따라 limited인 노드들은 full인 스토리지와만 통신되고, limited끼리인 노드 사이는 전부 막힌다.

바퀴살이 중심만 바라보는 허브-스포크 모양이 partition 하나로 만들어진다.

storage partition의 허브-스포크 구조 — full member인 스토리지 서버는 limited인 노드 A1, A2, B1, B2 모두와 통신되지만, limited끼리인 노드 사이의 패킷은 버려진다

같은 테넌트인 A1과 A2도 이 partition 안에서는 서로 막히지만, 문제가 되지는 않는다. 한 포트는 여러 partition에 동시에 속할 수 있어서, A1의 PKey table에는 storage partition의 limited 자격과 테넌트 A 전용 partition의 full 자격이 나란히 들어간다. 팀 안의 학습 트래픽은 전용 partition이 나른다.

partition을 하나도 설정하지 않으면 어떻게 될까. OpenSM은 default partition을 무조건 만든다. PKey 값 0x7FFF, 그리고 설정 파일이 없으면 모든 end port가 여기에 full member로 들어간다.

케이블만 꽂으면 fabric의 모두가 서로 통신되는 이유

SM이 각 포트의 PKey table을 기록하면 같은 partition(테넌트 A는 0x8001, 테넌트 B는 0x8002) 안에서만 통신이 되고, partition을 넘는 패킷은 BTH.PKey 불일치로 버려진다

집행 방식이 이 글의 핵심이다. 모든 IB 패킷의 BTH(Base Transport Header)에는 보내는 쪽의 PKey가 실린다.

패킷을 받은 HCA와 enforcement가 켜진 스위치 포트도, 그 값을 자기 PKey table과 대조한다.

일치하는 항목이 없으면 패킷은 조용히 버려지고 위반 카운터만 올라간다.

그리고 그 PKey table을 쓸 수 있는 주체는 SM뿐이다. SM이 SMP로 각 포트의 table을 프로그래밍하고, 노드는 자기가 어느 partition에 속할지 스스로 정할 수 없다.

“Nodes cannot determine their own partitions.”

 

PKey table을 쓰는 자는 SM뿐이다.격리는 host가 아니라 fabric이 집행한다.\begin{gathered} \textbf{\text{PKey table을 쓰는 자는 SM뿐이다.}} \\[2pt] \textbf{\text{격리는 host가 아니라 fabric이 집행한다.}} \end{gathered}

 

정리하면, SM이 해주는 일은:

  • directed route SMP로 fabric을 더듬어 전체 토폴로지를 발견한다.(discovery)
  • 모든 포트에 LID를 할당한다.
  • 경로를 계산해 각 스위치의 LFT에 기록한다.
  • sweep으로 변화를 감시하고, 바뀌면 이 사이클을 다시 돈다.
  • 각 포트의 PKey table을 프로그래밍한다. 관리는 SM이 하고, 실제 저장과 검증은 HCA와 스위치가 한다.
  • 그 PKey에는 어느 partition에 속하는지와 membership이 담긴다.

Why Not iptables

Ethernet 감각으로는 여기서 방화벽을 떠올린다. 테넌트끼리 통신을 막고 싶으면 host에서 iptables 규칙을 넣으면 되지 않나.

안 된다. NIC, RDMA, and InfiniBand에서 본 RDMA의 정의가 그 이유다. RDMA는 데이터 경로에서 kernel을 우회한다. HCA가 수신 데이터를 애플리케이션 버퍼에 직접 놓기 때문에, netfilter가 사는 kernel network stack을 패킷이 지나가지 않는다.

iptables가 볼 기회 자체가 없다.

그래서 IB의 테넌트 격리는 host 설정이 아니라 fabric 설정이다.

격리를 집행할 수 있는 위치는 하드웨어의 PKey table뿐이고, 그걸 쓰는 유일한 놈이 SM이다.

Partition in Practice

opensm에서 partition은 설정 파일 몇 줄이다. 포트는 GUID로 지정하고, membership은 GUID 뒤에 =full이나 =limited로 붙인다. 생략하면 limited다.

# /etc/opensm/partitions.conf
Default=0x7fff, ipoib : ALL, SELF=full ;
TenantA=0x8001, ipoib : 0x0002c9030001a1b1=full, 0x0002c9030001a2c2=full ;
TenantB=0x8002, ipoib : 0x0002c9030001b3d3=full, 0x0002c9030001b4e4=full ;

# 앞에서 본 storage partition. 스토리지 서버만 full,
# membership을 생략한 테넌트 노드 넷은 전부 limited가 된다.
Storage=0x1001, ipoib : 0x0002c90300aa0001=full,
    0x0002c9030001a1b1, 0x0002c9030001a2c2,
    0x0002c9030001b3d3, 0x0002c9030001b4e4 ;

스토리지 서버 GUID에만 =full이 붙어 있고, 테넌트 노드 넷은 TenantA, TenantB partition에 full로 든 것과 별개로 여기에는 limited로 든다. 한 포트가 여러 partition에 속한다는 것이 설정 파일에서는 이렇게 보인다.

여기 적을 GUID는 각 노드에서 직접 조회한다.

# 이 노드의 port GUID
ibstat -p
# → 0x0002c9030001a1b1

# fabric 전체 장비의 GUID 목록 — SM처럼 fabric을 훑어 나열한다
ibnetdiscover

한 가지 예외를 적어 두면, SMP MAD는 PKey 검증을 받지 않는다. partition을 아무리 나눠도 QP0의 관리 트래픽은 막히지 않는다는 뜻이다.

Fabric Meets Virtualization

이제 VFIO 글과 연결지어서, IB HCA를 VM에 넘기는 두 경로가 SM과 어떻게 얽히는지 보자.

SR-IOV로 만든 VF는 fabric의 1급 시민이 아니다.

IB의 SR-IOV는 shared port 모델이라, fabric에는 물리 HCA 하나만 보이고 VF들은 PF의 LID를 나눠 쓴다.

SM과 대화하는 QP0도 PF의 것이다. VF에도 형식상 QP0가 있지만 SMP를 받지 못하고, 보내면 버려진다.

VF의 관리 트래픽은 전부 host PF 드라이버가 터널로 중계해 줘야 fabric에 닿는다.

그래서 VF를 vfio-pci로 묶어 VM에 넘기면 문제가 생긴다. 그 중계 경로의 host 쪽 끝이 사라지기 때문이다. guest 드라이버가 아무리 기다려도 SM의 응답은 오지 않고, 포트는 LID 없이 Down에 머문다.

말이 좀 어려운데, 쉽게 말하자면 VF는 SM과 직접 대화할 입이 없다.

다시 상기해보면, SR-IOV 는 물리장치, PF가 가상 장치, VF 여러 개를 만들어내고, 이 VF를 vfio-pci에 바인딩해 VM에 넘긴다.

지금까지 host의 PF가 통역을 맡아 왔는데, VF만 VM으로 데려가면 통역사는 host에 남는다. guest가 아무리 말을 걸어도 fabric에는 들리지 않는다.

VF passthrough와 PF passthrough의 대비 — VF는 host PF가 중계하던 관리 경로를 vfio-pci가 끊어 State Down에 머물고, PF는 SM과 MAD를 직접 교환해 State Active가 된다

PF를 통째로 넘기면 이야기가 달라진다. guest kernel이 QP0까지 포함해 포트를 직접 소유하므로, SM과 MAD를 직접 교환해 LID를 받고 Active가 된다. HCA 하나가 VM 하나에 묶이는 대가는 있지만, 노드 전체를 VM 하나로 파는 GPU 클러스터 모델과는 오히려 맞아떨어진다.

그런데 PF passthrough는 격리 문제를 다시 불러온다. guest가 fabric의 1급 시민이 됐다는 말은, partition 설정이 없다면 모든 테넌트의 VM이 default 0x7FFF에 full member로 함께 있다는 말이다. 서로 다른 고객의 VM 사이에 RDMA가 열려 있는 상태이고, 위에서 본 대로 host에서 이를 막을 수단은 없다.

답은 하나다. SM의 PKey partition으로 테넌트마다 fabric을 나누는 것. 그래서 SM을 누가 쥐고 있느냐가 멀티테넌시 설계를 결정한다.

자기 fabric이면 partitions.conf 몇 줄로 끝난다.

provider의 managed fabric이라 SM 접근이 없다면 정식 partition은 불가능하고, 몸비틀기만이 남아있다.

What to Check

IB fabric을 처음 만지면 확인할 것들이다.

# 이 포트를 관리하는 master SM과 그 상태
sminfo
# → sminfo: sm lid 1 sm guid 0x..., activity count ... priority 15 state 3 SMINFO_MASTER

# 포트 상태 — LID를 받았고 Active인지
ibstat
# → State: Active / Physical state: LinkUp / Base lid: 42

# 이 포트가 속한 partition
cat /sys/class/infiniband/mlx5_0/ports/1/pkeys/*
# → 0x8001 (테넌트 partition), 0x7fff (default)
  • ibstat의 State가 Initializing에 멈춰 있으면 물리 링크는 살았는데 SM이 없거나 닿지 않는 상태다.
  • partition을 설계할 때는 default 0x7FFF에 누가 full member로 남는지부터 본다. opensm 기본값은 전원 full이다.
  • 진단 도구 ibdiagnet, path 질의 saquery도 결국 SM/SA에 묻는 도구다. SM이 죽으면 fabric 관측 자체가 멈춘다.

Summary

InfiniBand fabric의 제어 평면은 스위치가 아니라 Subnet Manager 하나에 모여 있다. SM이 directed route SMP로 토폴로지를 발견하고, LID를 할당하고, routing engine으로 모든 스위치의 LFT를 계산해 기록해야 fabric이 선다. 운영 중에는 sweep으로 변화를 쫓고, master 하나에 standby 여럿으로 다중화한다.

격리도 같은 곳에 모여 있다. PKey는 VLAN에 대응하는 partition 단위로, 패킷마다 실려 다니며 수신 HCA와 스위치의 PKey table에서 검증된다. 그 table을 쓰는 주체는 SM뿐이다. RDMA가 kernel을 우회하는 이상 host 방화벽은 이 문제에 손댈 수 없고, 테넌트 격리는 fabric 설정, 곧 SM 설정이 된다.

가상화는 이 구조를 그대로 물려받는다. VF는 관리 트래픽을 host PF에 기대므로 vfio passthrough와 어긋나고, PF를 통째로 넘기면 guest가 fabric에 직접 참여하는 대신 partition 없이는 모든 테넌트가 한 방에 모인다. SM에 손이 닿는 환경이라면 partition 몇 줄로 풀리는 문제가, managed fabric에서는 설계 전체를 흔드는 제약이 된다.

References