devlink 서브시스템 심화

devlink은 NIC/스위치 ASIC의 장치 단위 제어면을 제공하는 커널 인프라입니다. netdev 하나가 아닌 장치 전체를 대상으로 포트 분할, 리소스, trap, health, 펌웨어 갱신을 제어할 수 있습니다. 인스턴스 생명주기, 포트 타입(PF/VF/SF), subfunction 관리, devlink-param/region/resource/rate/linecard/sb, trap 그룹과 policer, health reporter 상태 머신, flash update, reload, 드라이버 구현 패턴(mlx5/ice)까지 devlink의 모든 것을 다룹니다.

전제 조건: Open vSwitchethtool 문서를 먼저 읽으세요. 데이터패스와 링크 계층 지표를 이해하면 devlink의 제어면 가치가 명확해집니다.
일상 비유: 이 개념은 데이터센터 설비 제어실과 비슷합니다. 각 서버 포트(netdev)보다 한 단계 높은 위치에서 전력, 경보, 증설 계획을 통합 관리합니다.

핵심 요약

  • devlink device -- 물리 장치(PCI 함수/ASIC) 단위의 제어 대상
  • devlink port -- 물리 포트, PF/VF, CPU 포트 등의 논리 표현
  • resource -- TCAM, FDB, ACL 같은 하드웨어 자원 관리
  • health reporter -- 오류 탐지와 자동 복구 트리거
  • trap -- 드롭/예외 패킷 이벤트를 제어면으로 수집

단계별 이해

  1. 장치 등록
    드라이버가 devlink_alloc(), devlink_register()를 호출합니다.
  2. 포트/파라미터 노출
    포트 타입과 튜닝 파라미터를 Netlink로 공개합니다.
  3. 운영 중 모니터링
    trap/health 카운터를 감시하고 이상 시 자동 복구를 시도합니다.
  4. 유지보수 작업
    reload/flash로 펌웨어와 런타임 구성을 업데이트합니다.

devlink 아키텍처

유저 공간의 devlink 유틸리티는 Generic Netlink를 통해 커널 net/devlink/ 코어와 통신합니다. 드라이버는 struct devlink_ops 콜백으로 기능을 제공합니다. devlink 코어는 2015년 커널 4.6에서 도입되었고, 이후 버전마다 trap, health, rate, linecard 등의 기능이 계속 추가되고 있습니다.

사용자 공간 devlink CLI orchestrator (Ansible/Salt) libnl / pyroute2 Generic Netlink (DEVLINK_CMD_*) devlink core (net/devlink/) devl_lock, ops dispatch, netlink policy, notification port / split / SF param / region resource trap / policer health reload / flash mlx5_core ice (E810) bnxt_en nfp etc. NIC / SmartNIC / Switch ASIC Hardware

devlink은 기존 netdev 중심 제어와 달리, 물리 장치(PCI function/ASIC) 전체를 하나의 관리 단위로 묶습니다. 이를 통해 다음과 같은 장치 단위 작업이 가능합니다:

영역기존 방식devlink 방식이점
포트 관리ifconfig/ip link 개별 제어devlink port show/split/add물리 포트 분할, SF 생성 통합
펌웨어벤더 전용 도구devlink dev flash표준화된 인터페이스
장애 관리dmesg 파싱devlink health show/diagnose구조화된 장애 정보
패킷 예외tcpdump 추측devlink trap showHW 드롭 원인 코드 직접 확인
자원 관리벤더별 sysfsdevlink resource show/set계층적 자원 트리
대역폭tc qdisc 개별 설정devlink rate setVF/SF별 계층적 대역폭 보장
커널 소스 위치: devlink 코어는 net/devlink/ 디렉터리에 있으며, 주요 파일은 dev.c, port.c, health.c, trap.c, resource.c, param.c, region.c, rate.c, linecard.c, sb.c입니다. 커널 6.x에서 단일 devlink.c가 여러 파일로 분리되었습니다.

인스턴스 생명주기

devlink 인스턴스는 네트워크 장치 드라이버의 probe() 함수에서 할당되어, remove()에서 해제됩니다. 커널 6.x 이후 devl_lock 기반 잠금이 도입되어 등록 순서와 동시성 제어가 정교해졌습니다.

devlink 인스턴스 생명주기 1. devlink_alloc() ops + priv_size 전달 2. 하위 객체 등록 port/param/resource 3. devlink_register() Netlink 노출 시작 4. 운영 상태 CLI/자동화 접근 가능 5. devlink_unregister() Netlink 노출 중단 6. 하위 객체 해제 port/param/resource 7. devlink_free() 메모리 해제 생명주기 핵심 규칙 1) devlink_alloc()은 struct devlink를 할당하고 ops를 연결합니다. devlink_priv()로 드라이버 전용 데이터에 접근합니다. 2) register 전에 port, param, resource, health reporter를 먼저 등록해야 합니다 (lock-free 초기화). 3) devlink_register() 이후부터 Netlink를 통한 사용자 접근이 가능합니다. 4) 해제 순서는 등록의 역순: unregister -> 하위 객체 해제 -> free. 5) 커널 6.x: devl_lock()으로 하위 객체 등록/해제도 잠금 아래에서 수행 가능 (devl_port_register 등). 6) reload 동작 중에도 lock 보호로 Netlink 요청과의 경합을 방지합니다.
/* devlink 인스턴스 생명주기 - 드라이버 probe/remove 패턴 */
static const struct devlink_ops my_dl_ops = {
    .info_get         = my_info_get,
    .reload_actions   = BIT(DEVLINK_RELOAD_ACTION_DRIVER_REINIT),
    .reload_down      = my_reload_down,
    .reload_up        = my_reload_up,
    .port_type_set    = my_port_type_set,
    .flash_update     = my_flash_update,
};

static int my_probe(struct pci_dev *pdev,
                    const struct pci_device_id *id)
{
    struct devlink *dl;
    struct my_priv *priv;
    int err;

    /* 1단계: devlink 인스턴스 할당 */
    dl = devlink_alloc(&my_dl_ops, sizeof(*priv), &pdev->dev);
    if (!dl)
        return -ENOMEM;

    priv = devlink_priv(dl);
    priv->pdev = pdev;

    /* 2단계: 하위 객체 등록 (register 전) */
    devl_lock(dl);
    err = my_devlink_params_register(dl);
    if (err)
        goto err_unlock;

    err = my_devlink_resources_register(dl);
    if (err)
        goto err_params;

    err = my_devlink_ports_register(dl);
    if (err)
        goto err_resources;

    err = my_health_reporters_create(dl);
    if (err)
        goto err_ports;
    devl_unlock(dl);

    /* 3단계: Netlink 노출 시작 */
    devlink_register(dl);

    pci_set_drvdata(pdev, dl);
    return 0;

err_ports:
    my_devlink_ports_unregister(dl);
err_resources:
    my_devlink_resources_unregister(dl);
err_params:
    my_devlink_params_unregister(dl);
err_unlock:
    devl_unlock(dl);
    devlink_free(dl);
    return err;
}

static void my_remove(struct pci_dev *pdev)
{
    struct devlink *dl = pci_get_drvdata(pdev);

    /* 5단계: Netlink 노출 중단 */
    devlink_unregister(dl);

    /* 6단계: 하위 객체 해제 (역순) */
    devl_lock(dl);
    my_health_reporters_destroy(dl);
    my_devlink_ports_unregister(dl);
    my_devlink_resources_unregister(dl);
    my_devlink_params_unregister(dl);
    devl_unlock(dl);

    /* 7단계: 메모리 해제 */
    devlink_free(dl);
}
devl_lock 전환: 커널 6.3 이후 devlink_port_register() 같은 기존 API는 devl_port_register()로 대체되었습니다. 새로운 API는 호출자가 devl_lock을 잡은 상태에서 호출해야 합니다. 기존 API는 내부에서 lock을 획득하므로, 동일 경로에서 이중 잠금이 발생하지 않도록 주의해야 합니다.
API용도잠금 요구커널 버전
devlink_alloc()인스턴스 할당불필요4.6+
devlink_register()Netlink 노출불필요4.6+
devl_lock() / devl_unlock()인스턴스 잠금-6.1+
devl_port_register()포트 등록 (lock 아래)devl_lock 필수6.3+
devlink_unregister()Netlink 노출 중단불필요4.6+
devlink_free()인스턴스 해제불필요4.6+

포트/리소스 모델

devlink 포트는 물리 포트뿐 아니라 PCI PF/VF, CPU 포트까지 표현합니다. 리소스 트리는 하드웨어 자원을 계층적으로 모델링해, 사전 점검 후 재분배를 지원합니다.

대상예시활용Netlink 속성
port flavourphysical / pci_pf / pci_vf / cpu / pci_sf포트 정체성 구분DEVLINK_ATTR_PORT_FLAVOUR
port split4x25G / 2x50G / 1x100G물리 포트 대역폭 분할DEVLINK_ATTR_PORT_SPLIT_GROUP
resourceFDB, ACL, TCAM용량 할당, 검증DEVLINK_ATTR_RESOURCE_*
paraminline-mode, enable_roce드라이버별 런타임 제어DEVLINK_ATTR_PARAM_*

포트 flavour 세부

포트 flavour는 devlink 포트의 종류를 구분합니다. 각 flavour는 다른 용도와 생명주기를 가집니다:

Flavour설명생성 방식netdev 연결
physical물리 네트워크 포트하드웨어 고정enp3s0f0 등
cpuCPU 포트 (제어면 연결)드라이버 자동일반적으로 없음
dsaDSA 스위치 포트DSA 프레임워크lan1, lan2 등
pci_pfPCI Physical FunctionPCI 열거 시호스트 netdev
pci_vfPCI Virtual Function (SR-IOV)sriov_numvfs 설정VF netdev / representor
pci_sfPCI Subfunctiondevlink port addSF netdev
virtual가상 포트드라이버 정의드라이버별 상이
# 포트 전체 목록 및 flavour 확인
devlink port show
# 출력 예시:
# pci/0000:03:00.0/0: type eth netdev enp3s0f0np0 flavour physical port 0
# pci/0000:03:00.0/1: type eth netdev enp3s0f0np1 flavour physical port 1
# pci/0000:03:00.0/65536: type eth netdev enp3s0f0v0 flavour pci_vf controller 0 pfnum 0 vfnum 0

# JSON 형식으로 자동화에 적합한 출력
devlink -j port show | jq '.port | to_entries[] | {index: .key, flavour: .value.flavour, netdev: .value.netdev}'

# 리소스 트리 확인
devlink resource show pci/0000:03:00.0
# 출력 예시:
# pci/0000:03:00.0:
#   name kvd size 245760 unit entry
#     name linear size 98304 unit entry size_min 0 size_max 147456 size_gran 128
#     name hash_double size 60416 unit entry size_min 0 size_max 147456 size_gran 128
#     name hash_single size 87040 unit entry size_min 0 size_max 147456 size_gran 128

# 리소스 크기 변경 (reload 필요)
devlink resource set pci/0000:03:00.0 path kvd/linear size 131072
devlink dev reload pci/0000:03:00.0
리소스 재할당 주의: 리소스 크기를 변경한 후에는 반드시 devlink dev reload를 실행해야 적용됩니다. size_new 속성으로 미적용 변경 사항이 있는지 확인할 수 있습니다. size_min, size_max, size_gran을 확인하여 유효한 값만 설정하세요.

health reporter와 trap

장애 대응은 devlink 핵심 기능입니다. health reporter는 상태 점검 및 복구 훅을 제공하고, trap은 드롭 또는 예외 패킷을 원인 코드와 함께 관찰하게 해줍니다.

# 장치/포트 확인
devlink dev show
devlink port show

# health 상태 및 복구
devlink health show pci/0000:03:00.0
devlink health recover pci/0000:03:00.0 reporter fw

# trap 관찰
devlink trap show pci/0000:03:00.0
devlink trap group show pci/0000:03:00.0

eSwitch와 오프로드 통합

SR-IOV 환경에서는 devlink로 eSwitch 모드(legacy/switchdev)를 전환하고 representor 기반 오프로드 경로를 구성합니다. OVS/TC flower와 직접 연결되는 지점입니다.

eSwitch 모드 전환과 오프로드 경로 legacy 모드 VF MAC/VLAN 설정 중심 SW 기반 전달, 제한적 HW 제어 switchdev 모드 representor netdev 생성 TC/OVS 규칙 HW 오프로드 전환 OVS / TC flower 규칙 representor 포트에 규칙 설치 -> HW 플로우 테이블로 오프로드 eSwitch 전환 절차 1) echo 0 > /sys/class/net/enp3s0f0/device/sriov_numvfs (VF 먼저 해제) 2) devlink dev eswitch set pci/0000:03:00.0 mode switchdev 3) echo 4 > /sys/class/net/enp3s0f0/device/sriov_numvfs (VF 재생성 -> representor 자동 생성) 4) ovs-vsctl add-port br-int enp3s0f0_0 (representor를 OVS 브릿지에 추가) 5) tc filter add dev enp3s0f0_0 ... action mirred egress redirect dev enp3s0f0 (TC 오프로드)
# eSwitch 현재 모드 확인
devlink dev eswitch show pci/0000:03:00.0
# 출력: pci/0000:03:00.0: mode legacy

# switchdev 모드로 전환 (VF 해제 후 실행)
echo 0 > /sys/class/net/enp3s0f0/device/sriov_numvfs
devlink dev eswitch set pci/0000:03:00.0 mode switchdev

# VF 재생성 (representor 자동 생성)
echo 4 > /sys/class/net/enp3s0f0/device/sriov_numvfs
devlink port show

# eSwitch 인라인 모드 설정 (ConnectX-5 이하)
devlink dev eswitch set pci/0000:03:00.0 inline-mode transport

# encap 모드 설정 (VXLAN/Geneve 오프로드)
devlink dev eswitch set pci/0000:03:00.0 encap-mode basic
eSwitch 전환 영향: legacy에서 switchdev로 전환하면 기존 VF 설정이 초기화됩니다. 반드시 VF를 먼저 해제(sriov_numvfs=0)하고 전환해야 합니다. switchdev 모드에서는 VF의 MAC/VLAN 제어가 representor를 통해 이루어지므로, ip link set 기반 VF 설정이 동작하지 않을 수 있습니다.

드라이버 구현 패턴

devlink ops를 구현하는 드라이버는 구조체의 콜백을 채워야 합니다. 아래는 주요 콜백과 구현 요구사항을 정리한 것입니다.

콜백용도필수 여부구현 복잡도
info_get드라이버/FW 버전 정보권장낮음
reload_down / reload_up장치 재초기화선택높음
flash_update펌웨어 갱신선택높음
port_type_set포트 타입 변경선택중간
port_split / port_unsplit포트 분할/복구선택중간
port_new / port_delSF 포트 생성/삭제선택높음
port_fn_hw_addr_get/set포트 함수 MAC 설정선택낮음
port_fn_state_get/setSF 상태 관리SF 지원 시중간
trap_init / trap_finitrap 초기화/정리trap 지원 시중간
sb_pool_set공유 버퍼 풀 설정SB 지원 시중간
/* info_get 콜백 구현 예 */
static int my_info_get(struct devlink *dl,
                        struct devlink_info_req *req,
                        struct netlink_ext_ack *extack)
{
    struct my_priv *priv = devlink_priv(dl);
    int err;

    /* 드라이버 이름 */
    err = devlink_info_driver_name_put(req, "my_driver");
    if (err)
        return err;

    /* 시리얼 번호 (고유 식별자) */
    err = devlink_info_serial_number_put(req, priv->serial);
    if (err)
        return err;

    /* 펌웨어 버전 정보 (running vs stored) */
    err = devlink_info_version_running_put(req,
            DEVLINK_INFO_VERSION_GENERIC_FW_MGMT,
            priv->fw_version);
    if (err)
        return err;

    err = devlink_info_version_stored_put(req,
            DEVLINK_INFO_VERSION_GENERIC_FW_MGMT,
            priv->fw_stored_version);

    return err;
}

/* port 등록 예 */
static int my_devlink_ports_register(struct devlink *dl)
{
    struct my_priv *priv = devlink_priv(dl);
    struct devlink_port_attrs attrs = {};
    int i, err;

    for (i = 0; i < priv->num_ports; i++) {
        memset(&attrs, 0, sizeof(attrs));
        devlink_port_attrs_set(&priv->ports[i].dl_port, &attrs);

        /* devl_lock이 이미 잡혀 있으므로 devl_ 접두사 사용 */
        err = devl_port_register(dl, &priv->ports[i].dl_port, i);
        if (err)
            goto err_unregister;
    }
    return 0;

err_unregister:
    for (i--; i >= 0; i--)
        devl_port_unregister(&priv->ports[i].dl_port);
    return err;
}

devlink는 단순 텍스트 CLI가 아니라 엄격한 Netlink ABI 위에서 동작합니다. 자동화 도구는 CLI 출력 문자열이 아니라 속성(attribute)을 기준으로 파싱해야 장기 호환성을 확보할 수 있습니다.

명령군대표 명령핵심 속성운영 포인트
deviceDEVLINK_CMD_INFO_GETdriver_name, serial_number자산 식별 기준
portDEVLINK_CMD_PORT_GETflavour, split_group, netdev_ifindex토폴로지 동기화
resourceDEVLINK_CMD_RESOURCE_SETname, size, size_newreload 필요 여부 확인
trapDEVLINK_CMD_TRAP_GETgroup, action, metadata드롭 원인 분류
healthDEVLINK_CMD_HEALTH_REPORTER_GETerror, recover_count장애 자동 복구 지표
reload/flashDEVLINK_CMD_RELOAD, DEVLINK_CMD_FLASH_UPDATEaction, limit, component무중단 범위 제어
paramDEVLINK_CMD_PARAM_GET/SETname, value, cmode런타임/드라이버/영구 모드
regionDEVLINK_CMD_REGION_GETname, snapshot_id, address레지스터/메모리 덤프
rateDEVLINK_CMD_RATE_GET/SETtx_share, tx_max, parent계층적 대역폭 제어
linecardDEVLINK_CMD_LINECARD_GETstate, type, index모듈형 라인카드 관리
# JSON 출력으로 파싱 안정성 확보
devlink -j dev show | jq .
devlink -j port show pci/0000:03:00.0 | jq .
devlink -j resource show pci/0000:03:00.0 | jq .
devlink -j trap show pci/0000:03:00.0 | jq .

# 머신파서가 문자열 대신 키를 사용하도록 고정
devlink -j health show pci/0000:03:00.0 | jq '.[][] | {name,error,recover_count}'

# 장치 정보 조회 (드라이버/FW 버전)
devlink dev info pci/0000:03:00.0
# 출력 예시:
# pci/0000:03:00.0:
#   driver mlx5_core
#   serial_number MT2116X09299
#   versions:
#     fixed:
#       fw.psid MT_0000000228
#     running:
#       fw.mgmt 22.36.1010
#     stored:
#       fw.mgmt 22.36.1010

# Netlink 모니터링 (이벤트 실시간 수신)
devlink monitor all
Netlink 모니터: devlink monitor all 명령은 포트 추가/삭제, trap 변경, health 이벤트 등을 실시간으로 수신합니다. 자동화 시스템에서 이벤트 기반 반응을 구현할 때 필수적인 도구입니다. devlink monitor trap처럼 특정 이벤트만 필터링할 수도 있습니다.

포트 토폴로지와 split/subfunction

실무에서 가장 많이 실수하는 지점은 포트 정체성을 netdev 이름으로만 추적하는 것입니다. devlink 포트 인덱스와 flavour를 기준으로 추적해야 split, VF 생성, SF(Subfunction) 생성 이후에도 일관성이 유지됩니다.

포트 정체성: physical / PF / VF / SF PCI 장치 pci/0000:03:00.0 (devlink device) 라인카드/ASIC 단위 제어면 physical port 1 split 가능 pci_pf 0 호스트 제어 함수 pci_vf 0..N SR-IOV pci_sf 0..N Subfunction 실무 규칙 1) automation key는 "port index + flavour + controller" 조합으로 고정 2) netdev 이름(eth0/enp3s0f0)만으로 상태를 매칭하면 rename/재부팅 후 깨짐 3) split/unsplit, VF 증감 이후 즉시 devlink port show로 토폴로지 재동기화 4) representor 생성/삭제 이벤트를 OVS/TC 규칙 재배포 트리거로 연결
# 포트 분할/복구 (드라이버 지원 시)
devlink port show pci/0000:03:00.0
devlink port split pci/0000:03:00.0/1 count 4
devlink dev reload pci/0000:03:00.0 action driver_reinit
devlink port show pci/0000:03:00.0
devlink port unsplit pci/0000:03:00.0/1

# SF 예시(드라이버 지원 시)
devlink port add pci/0000:03:00.0 flavour pcisf pfnum 0 sfnum 11
devlink port function set pci/0000:03:00.0/32768 hw_addr 00:11:22:33:44:55 state active

subfunction (SF) 심화

Subfunction은 PCI VF의 한계를 넘어 더 가볍고 유연한 하드웨어 격리를 제공하는 메커니즘입니다. VF가 PCI 레벨 함수인 반면, SF는 같은 PF 안에서 소프트웨어적으로 격리된 함수입니다. mlx5 드라이버에서 처음 구현되었으며, 컨테이너/마이크로서비스 환경에서 네트워크 리소스 격리에 활용됩니다.

subfunction (SF) 생명주기 상태 머신 inactive port add 직후 active auxiliary bus 등록 migrating 라이브 마이그레이션 중 state active migratable state inactive SF vs VF 비교 항목 VF (Virtual Function) SF (Subfunction) 격리 수준 PCI 함수 레벨 (IOMMU) 소프트웨어 레벨 생성 비용 PCI config 공간, FLR 필요 경량 (devlink port add) 최대 수 256 (PCIe 사양 한계) 수천 개 가능 (HW 의존) 리소스 할당 고정적 (MSI-X, queue) 동적 (필요한 만큼) 드라이버 바인딩 PCI 드라이버 auxiliary bus 드라이버 라이브 마이그레이션 VFIO 기반 devlink 상태 관리 DevOps 통합 sriov_numvfs sysfs devlink port add/del eSwitch 연동 representor 생성 representor 생성 지원 드라이버 대부분의 SmartNIC mlx5, ice (제한적)
# SF 생성 전체 절차
# 1. SF 포트 생성
devlink port add pci/0000:03:00.0 flavour pcisf pfnum 0 sfnum 88
# 출력: pci/0000:03:00.0/32768: type eth netdev ... flavour pcisf ...

# 2. MAC 주소 설정 (active 전에)
devlink port function set pci/0000:03:00.0/32768 \
    hw_addr 00:00:00:00:88:88

# 3. SF 활성화 (auxiliary bus probe 발생)
devlink port function set pci/0000:03:00.0/32768 state active

# 4. 확인
devlink port show pci/0000:03:00.0/32768
devlink port function show pci/0000:03:00.0/32768

# 5. SF에 대한 devlink 인스턴스 확인
devlink dev show
# auxiliary/mlx5_core.sf.2 등으로 나타남

# 6. SF 비활성화 및 삭제
devlink port function set pci/0000:03:00.0/32768 state inactive
devlink port del pci/0000:03:00.0/32768
SF의 auxiliary bus 연동: SF가 active 상태로 전환되면 auxiliary bus에 디바이스가 등록됩니다. mlx5의 경우 mlx5_core.sf.N이라는 이름으로 등록되며, 이 디바이스에 대한 별도의 devlink 인스턴스가 생성됩니다. 따라서 SF는 자체적인 health reporter, trap, param을 가질 수 있습니다.

devlink-param은 드라이버별 설정 파라미터를 표준화된 인터페이스로 노출합니다. 각 파라미터는 세 가지 설정 모드(cmode)를 가질 수 있으며, 이를 통해 런타임 설정과 영구 설정을 분리합니다.

설정 모드 (cmode)설명적용 시점재시작 후
runtime즉시 적용명령 실행 즉시초기화
driverinit드라이버 재초기화 시 적용reload 후초기화
permanentNVM에 저장재부팅 후유지
# 전체 파라미터 조회
devlink dev param show pci/0000:03:00.0
# 출력 예시:
# pci/0000:03:00.0:
#   name enable_roce type generic
#     values:
#       cmode driverinit value true
#   name internal_err_reset type generic
#     values:
#       cmode runtime value true

# 특정 파라미터 조회
devlink dev param show pci/0000:03:00.0 name enable_roce

# 파라미터 설정 (runtime)
devlink dev param set pci/0000:03:00.0 \
    name internal_err_reset value false cmode runtime

# 파라미터 설정 (driverinit - reload 필요)
devlink dev param set pci/0000:03:00.0 \
    name enable_roce value false cmode driverinit
devlink dev reload pci/0000:03:00.0

주요 Generic 파라미터

파라미터타입지원 cmode설명
enable_rocebooldriverinitRoCE(RDMA over Converged Ethernet) 활성화
enable_ethbooldriverinit이더넷 기능 활성화
enable_iwarpbooldriverinitiWARP 활성화
internal_err_resetboolruntime내부 오류 시 자동 리셋
max_macsu32driverinit최대 MAC 주소 수
region_snapshot_enableboolruntimeregion 스냅샷 자동 생성
enable_remote_dev_resetboolruntime원격 장치 리셋 허용
enable_vnetbooldriverinitVirtIO-net 에뮬레이션 활성화
/* 드라이버에서 param 등록하기 */
static const struct devlink_param my_params[] = {
    DEVLINK_PARAM_GENERIC(ENABLE_ROCE,
                         BIT(DEVLINK_PARAM_CMODE_DRIVERINIT),
                         NULL, NULL, NULL),
    DEVLINK_PARAM_DRIVER(100, "my_custom_param",
                        DEVLINK_PARAM_TYPE_U32,
                        BIT(DEVLINK_PARAM_CMODE_RUNTIME),
                        my_param_get, my_param_set, NULL),
};

/* probe 시 등록 */
err = devl_params_register(dl, my_params, ARRAY_SIZE(my_params));

/* driverinit 초기값 설정 */
union devlink_param_value val;
val.vbool = true;
devl_param_driverinit_value_set(dl,
    DEVLINK_PARAM_GENERIC_ID_ENABLE_ROCE, val);

devlink-region은 NIC의 내부 메모리, 레지스터 공간, 또는 펌웨어 구성을 사용자 공간에서 읽을 수 있도록 하는 인터페이스입니다. 주로 디버깅과 장애 분석에 사용되며, 스냅샷을 통해 특정 시점의 상태를 보존할 수 있습니다.

# region 목록 확인
devlink region show
# 출력 예시:
# pci/0000:03:00.0/cr-space: size 1048576 snapshot [0 1]
# pci/0000:03:00.0/fw-health: size 64 snapshot [0]

# 스냅샷 생성
devlink region snapshot new pci/0000:03:00.0/cr-space

# 스냅샷 데이터 읽기 (주소 범위 지정)
devlink region dump pci/0000:03:00.0/cr-space snapshot 0
devlink region read pci/0000:03:00.0/cr-space snapshot 0 \
    address 0x1000 length 256

# 스냅샷 삭제
devlink region del pci/0000:03:00.0/cr-space snapshot 0

# 직접 읽기 (스냅샷 없이, 지원 시)
devlink region read pci/0000:03:00.0/cr-space address 0 length 64
region 활용 사례: mlx5 드라이버는 cr-space(Configuration Register 공간), fw-health(펌웨어 상태), icm-event(ICM 이벤트 로그) 등의 region을 제공합니다. 장애 발생 시 cr-space 스냅샷을 벤더에 전달하면 하드웨어 레벨 디버깅이 가능합니다.
/* 드라이버에서 region 등록 */
static const struct devlink_region_ops my_cr_region_ops = {
    .name       = "cr-space",
    .snapshot   = my_cr_snapshot,
    .destructor = my_cr_snapshot_destroy,
    .read       = my_cr_direct_read,  /* 직접 읽기 지원 시 */
};

/* probe 시 등록 */
priv->cr_region = devl_region_create(dl, &my_cr_region_ops,
                                      1,  /* max_snapshots */
                                      cr_space_size);

/* health reporter에서 자동 스냅샷 생성 */
static int my_fw_reporter_dump(struct devlink_health_reporter *reporter,
                               struct devlink_fmsg *fmsg,
                               void *priv_ctx,
                               struct netlink_ext_ack *extack)
{
    struct my_priv *priv = reporter->priv;

    /* region 스냅샷을 자동으로 생성하여 장애 상태 보존 */
    devlink_region_snapshot_id_get(priv->devlink, &snapshot_id);
    devlink_region_snapshot_create(priv->cr_region, data, snapshot_id);

    return 0;
}

trap 파이프라인과 원인 분류

trap은 단순 카운터가 아니라 제어면으로 전달되는 예외 이벤트 파이프라인입니다. 드롭 원인별 우선순위를 나눠야 경보 폭주를 막고 실제 장애 신호를 살릴 수 있습니다.

trap 이벤트 수집/정책/경보 흐름 ASIC 파서 L2/L3/L4 예외 탐지 trap group l2_drops / l3_drops action drop / trap / mirror policer rate/burst 제한 counter 통계 trap action 유형 DROP 패킷을 버리고 카운터만 증가. HW에서 직접 드롭되며 CPU 부하 없음. TRAP 패킷을 제어면(CPU)으로 전달. 드라이버가 sk_buff로 수신하여 처리. MIRROR 패킷을 정상 전달하면서 복사본을 제어면으로 전달. 모니터링 용도. 운영 분류 권장 A. 즉시 경보: loop, STP 차단, ACL miss 급증, MTU mismatch 폭증 B. 경향 경보: unknown_unicast 완만 증가, checksum error 증가 추세 C. 정보성: 희소한 control-plane exception 정책 포인트 - trap policer로 제어면 폭주 보호 (rate/burst 설정) - 수치 절대값보다 변화율(delta/s) 기반 경보가 오탐이 적음 - trap metadata(in_port, reason)를 로그에 함께 저장해 복구 시간 단축 - OVS/TC 규칙 변경 배포 직후 5분은 별도 baseline 구간으로 분리

주요 trap 그룹과 trap

그룹대표 trap기본 action설명
l2_dropssource_mac_is_multicastdrop출발지 MAC이 멀티캐스트인 프레임
l2_dropsvlan_tag_mismatchdropVLAN 태그 불일치
l2_dropsingress_vlan_filterdrop입력 VLAN 필터 위반
l3_dropsblackhole_routedrop블랙홀 경로로의 패킷
l3_dropsttl_value_is_too_smalltrapTTL 소진 패킷 (ICMP 응답 필요)
l3_dropsnon_routabledrop라우팅 불가 패킷
l3_exceptionsmtu_value_is_too_smalltrapMTU 초과 (ICMP 필요)
buffer_dropstail_dropdrop버퍼 오버플로우
acl_dropsingress_flow_action_dropdropACL 규칙에 의한 드롭
stpstptrapSTP BPDU 패킷
ospfospftrapOSPF 프로토콜 패킷
bgpbgptrapBGP 프로토콜 패킷
# trap 그룹/개별 정책 확인
devlink trap group show pci/0000:03:00.0
devlink trap show pci/0000:03:00.0

# 특정 trap 통계 확인
devlink -s trap show pci/0000:03:00.0 trap source_mac_is_multicast
# 출력 예시:
# pci/0000:03:00.0:
#   name source_mac_is_multicast type drop
#     stats:
#       rx:
#         bytes 0 packets 0
#       drops:
#         bytes 1234 packets 10

# trap action 변경 (droppoly -> trap로 전환하여 패킷 수집)
devlink trap set pci/0000:03:00.0 trap ttl_value_is_too_small action trap
devlink trap set pci/0000:03:00.0 trap blackhole_route action drop

# trap policer 설정 (제어면 보호)
devlink trap policer set pci/0000:03:00.0 policer 1 rate 1000 burst 128

# trap group에 policer 연결
devlink trap group set pci/0000:03:00.0 group l3_drops policer 1
trap policer 필수: trap action을 trap으로 설정하면 해당 패킷이 CPU로 전달됩니다. DDoS 공격 등으로 대량의 예외 패킷이 발생할 경우 CPU 과부하를 초래할 수 있으므로, 반드시 policer를 설정하여 rate/burst를 제한해야 합니다.

health reporter 실전 설계

health reporter는 "문제 발견"보다 "안전한 복구 경계"를 어디에 두는지가 핵심입니다. recover 훅은 강력하지만 데이터면 영향이 크므로 단계별 복구 전략이 필요합니다.

health reporter 상태 머신 healthy 정상 동작 중 error 오류 감지됨 error (recover 초과) 자동 복구 불가 오류 발생 recover 성공 grace period 초과 복구 전략 계층 Level 0: 진단 (diagnose) 상태 정보만 수집. 데이터면 영향 없음. Level 1: 부분 복구 (soft recover) 큐/CQ 리셋. 영향받는 흐름만 짧은 중단. Level 2: 함수 리셋 (function reset) PF/VF 리셋. 해당 함수의 모든 흐름 중단. Level 3: FW 리셋 펌웨어 재시작. 장치 전체 중단. reload 필요. 자동 복구 제어 - auto_recover: true/false로 자동 복구 활성화/비활성화 - auto_dump: true/false로 오류 발생 시 자동 덤프 생성 - grace_period: 연속 복구 시도 간 최소 대기 시간 (밀리초) - grace_period 내 재오류 시 자동 복구를 중단하고 운영자 개입 대기 - recover_count, error_count를 모니터링하여 반복 장애를 탐지
리포터관측 대상일반 복구 전략위험도지원 드라이버
fw펌웨어 assert, command timeoutfw reset -> driver reinit중간~높음mlx5, ice
fw_fatalFW 크래시, 복구 불가 오류전체 리셋높음mlx5
tx큐 정지, completion stallqueue reset중간mlx5
rxRX 큐 정지, 타임아웃queue reset중간mlx5
pciAER, 링크 다운function reset높음ice
internal드라이버 자체 상태 이상soft recover낮음~중간다수
vnicVF/SF의 가상 NIC 상태진단 전용낮음mlx5
# reporter 상태 확인
devlink health show pci/0000:03:00.0
# 출력 예시:
# pci/0000:03:00.0:
#   reporter fw
#     state healthy error 0 recover 0 grace_period 60000 auto_recover true auto_dump true
#   reporter fw_fatal
#     state healthy error 0 recover 0 grace_period 0 auto_recover true auto_dump true
#   reporter tx
#     state healthy error 2 recover 2

# dump 수집 (장애 직후 보존)
devlink health dump show pci/0000:03:00.0 reporter fw

# 진단 정보 확인
devlink health diagnose pci/0000:03:00.0 reporter fw

# 수동 복구
devlink health recover pci/0000:03:00.0 reporter fw

# 자동 복구 비활성화 (디버깅 시)
devlink health set pci/0000:03:00.0 reporter fw auto_recover false

# grace period 설정 (밀리초)
devlink health set pci/0000:03:00.0 reporter fw grace_period 120000
/* health reporter 구현 예 */
static int my_fw_reporter_recover(
    struct devlink_health_reporter *reporter,
    void *priv_ctx,
    struct netlink_ext_ack *extack)
{
    struct my_priv *priv = devlink_health_reporter_priv(reporter);

    /* 펌웨어 리셋 실행 */
    my_fw_reset(priv);

    /* 드라이버 재초기화 */
    my_reinit(priv);

    return 0;
}

static int my_fw_reporter_dump(
    struct devlink_health_reporter *reporter,
    struct devlink_fmsg *fmsg,
    void *priv_ctx,
    struct netlink_ext_ack *extack)
{
    struct my_priv *priv = devlink_health_reporter_priv(reporter);

    /* 구조화된 덤프 정보 작성 */
    devlink_fmsg_obj_nest_start(fmsg);
    devlink_fmsg_put_name(fmsg, "fw_version");
    devlink_fmsg_put_value(fmsg, priv->fw_ver);
    devlink_fmsg_put_name(fmsg, "error_code");
    devlink_fmsg_u32_put(fmsg, priv->last_error);
    devlink_fmsg_obj_nest_end(fmsg);

    return 0;
}

static const struct devlink_health_reporter_ops my_fw_reporter_ops = {
    .name    = "fw",
    .recover = my_fw_reporter_recover,
    .dump    = my_fw_reporter_dump,
};

/* probe 시 reporter 생성 */
priv->fw_reporter = devl_health_reporter_create(dl,
    &my_fw_reporter_ops,
    60000,  /* grace_period_ms */
    priv);

/* 오류 발생 시 보고 */
devlink_health_report(priv->fw_reporter, "FW assert detected", &ctx);

reload/flash 절차 심화

reload와 flash는 가장 위험한 유지보수 동작입니다. 반드시 "지원 action/limit 조회 -> 사전 스냅샷 -> 단계별 실행 -> 사후 검증" 순서를 자동화해야 합니다.

무중단 지향 reload/flash 절차 지원 범위 조회 action/limit 사전 스냅샷 resource/trap/health 작업 실행 reload or flash 사후 검증 데이터면/카운터 reload action과 limit action: driver_reinit 드라이버를 재초기화합니다. driverinit param 적용, resource 재분배. action: fw_activate 저장된 펌웨어를 활성화합니다. flash 후 재부팅 없이 적용 시도. limit: no_reset 장치 리셋 없이 reload. 데이터면 중단 최소화. 드라이버 지원 필요. 점검 항목 1) reload 후 port index 변동 여부와 representor 재생성 여부 확인 2) flash 후 fw versions, running/stored 상태를 분리 확인 3) health recover_count 급증 시 자동 롤백 또는 작업 중단 4) OVS/TC 오프로드 재동기화 성공 전까지 트래픽 완전 개방 금지 5) 변경 전후 trap 변화율 비교로 제어면 이상 여부 확인 실패 시 복구 - 한 장치씩 순차 처리, 병렬 flash 금지 - 복구 실패 장치 격리 후 잔여 장치 작업 중단
# 지원 reload 액션/limit 확인
devlink dev info pci/0000:03:00.0
# reload 지원 여부는 드라이버 capabilities에 의존

# 드라이버 재초기화 기반 reload
devlink dev reload pci/0000:03:00.0 action driver_reinit

# FW activate (flash 후 재부팅 없이 적용)
devlink dev reload pci/0000:03:00.0 action fw_activate

# no_reset limit 적용 (데이터면 중단 최소화)
devlink dev reload pci/0000:03:00.0 action fw_activate limit no_reset

# flash (드라이버 지원 component 사용)
devlink dev flash pci/0000:03:00.0 file /var/tmp/fw.bin component fw.mgmt

# flash 진행률 모니터링 (별도 터미널)
devlink monitor
/* reload 콜백 구현 */
static int my_reload_down(struct devlink *dl,
                          bool netns_change,
                          enum devlink_reload_action action,
                          enum devlink_reload_limit limit,
                          struct netlink_ext_ack *extack)
{
    struct my_priv *priv = devlink_priv(dl);

    switch (action) {
    case DEVLINK_RELOAD_ACTION_DRIVER_REINIT:
        /* 데이터면 정지 */
        my_stop_data_path(priv);
        /* 하드웨어 자원 해제 */
        my_teardown_hw(priv);
        break;
    case DEVLINK_RELOAD_ACTION_FW_ACTIVATE:
        if (limit == DEVLINK_RELOAD_LIMIT_NO_RESET)
            return my_fw_activate_no_reset(priv);
        return my_fw_activate(priv);
    default:
        return -EOPNOTSUPP;
    }
    return 0;
}

static int my_reload_up(struct devlink *dl,
                        enum devlink_reload_action action,
                        enum devlink_reload_limit limit,
                        u32 *actions_performed,
                        struct netlink_ext_ack *extack)
{
    struct my_priv *priv = devlink_priv(dl);

    /* driverinit param 적용 */
    my_apply_driverinit_params(priv);
    /* 하드웨어 재초기화 */
    my_setup_hw(priv);
    /* 데이터면 재시작 */
    my_start_data_path(priv);

    *actions_performed = BIT(action);
    return 0;
}
reload 중 데이터 손실: action driver_reinit은 드라이버를 완전히 재초기화합니다. 이 과정에서 in-flight 패킷이 손실될 수 있습니다. 프로덕션 환경에서는 반드시 트래픽을 다른 경로로 우회(drain)시킨 후 실행하세요. limit no_reset을 지원하는 드라이버는 데이터면 중단을 최소화할 수 있습니다.

rate / shared-buffer / queue 연계

대규모 멀티테넌트 환경에서는 링크 대역폭만 제어하면 부족합니다. devlink rate와 shared buffer를 함께 사용해 혼잡 도메인을 분리해야 안정적입니다.

rate 계층 트리와 shared buffer 연계 root node tx_max: 100Gbps tenant-A node tx_share: 40G, tx_max: 60G tenant-B node tx_share: 30G, tx_max: 50G tenant-C node tx_share: 30G, tx_max: 50G VF0 (5G) VF1 (5G) SF0 (10G) SF1 (10G) VF2 (15G) shared buffer (sb) pool 0 (ingress): type static, size 8MB -- 기본 입력 버퍼 pool 1 (egress): type dynamic, size 12MB -- 동적 출력 버퍼, 혼잡 시 확장 pool 2 (multicast): type static, size 2MB -- 멀티캐스트 전용 occupancy 모니터링: devlink sb occupancy show -- 실시간 사용량 확인
기능목표대표 지표문제 징후
rate nodeVF/SF별 최소/최대 대역폭tx_bw, tx_share특정 tenant 독점
shared buffer우발 burst 흡수pool occupancytail drop 급증
queue depth큐 지연 관리cq overrun, timeoutlatency 급등
# rate node 생성 및 설정
# 1. 중간 노드 생성 (tenant 그룹)
devlink port function rate add pci/0000:03:00.0/tenant-a

# 2. 중간 노드에 대역폭 설정
devlink port function rate set pci/0000:03:00.0/tenant-a \
    tx_share 40gbit tx_max 60gbit

# 3. VF/SF를 중간 노드에 연결
devlink port function rate set pci/0000:03:00.0/65536 \
    tx_share 5gbit tx_max 10gbit parent tenant-a

# 4. rate tree 확인
devlink port function rate show pci/0000:03:00.0

# shared buffer 관리
devlink sb show pci/0000:03:00.0
devlink sb pool show pci/0000:03:00.0 sb 0

# sb pool 크기 조정
devlink sb pool set pci/0000:03:00.0 sb 0 pool 0 size 8388608 \
    thtype static

# sb occupancy 실시간 확인
devlink sb occupancy show pci/0000:03:00.0
devlink sb occupancy snapshot pci/0000:03:00.0

# tc-port 바인딩
devlink sb tc bind set pci/0000:03:00.0/1 sb 0 tc 0 type ingress \
    th 6 pool 0
rate와 tc qdisc 차이: devlink rate는 하드웨어 레벨에서 VF/SF별 대역폭을 보장/제한하는 반면, tc qdisc는 소프트웨어 큐잉 레벨에서 동작합니다. 멀티테넌트 환경에서는 devlink rate로 하드웨어 격리를 설정한 후, 세밀한 QoS가 필요한 경우에만 tc qdisc를 추가로 사용하는 것이 권장됩니다.

devlink-linecard는 모듈형 네트워크 장비(섀시 스위치)의 라인카드를 관리하는 인터페이스입니다. 라인카드의 삽입/제거, 타입 설정, 프로비저닝 상태를 제어합니다. Mellanox SN3700 등의 모듈형 스위치에서 사용됩니다.

라인카드 상태 전환 unprovisioned 슬롯 비어있음 provisioning 설정 적용 중 provisioned 포트 노출 active 트래픽 전달 set type 카드 삽입 provisioning_failed 오류 발생 라인카드 관리 절차 1) devlink lc show -- 현재 라인카드 상태와 지원 타입 확인 2) devlink lc set pci/0000:03:00.0 lc 1 type 16x100G -- 라인카드 타입 프로비저닝 3) 물리 카드 삽입 시 자동으로 active 상태로 전환, devlink port에 포트 노출
# 라인카드 상태 확인
devlink lc show pci/0000:03:00.0
# 출력 예시:
# pci/0000:03:00.0:
#   lc 1 state unprovisioned supported_types: 16x100G 32x50G 4x400G

# 라인카드 타입 프로비저닝
devlink lc set pci/0000:03:00.0 lc 1 type 16x100G

# 프로비저닝 해제
devlink lc set pci/0000:03:00.0 lc 1 notype

드라이버별 구현 비교 (mlx5 / ice / bnxt)

devlink의 실질적인 기능 범위는 드라이버마다 크게 다릅니다. 운영 자동화 스크립트를 작성할 때는 대상 드라이버의 지원 범위를 반드시 확인해야 합니다.

기능mlx5 (ConnectX-6+)ice (E810)bnxt (BCM57500+)nfp (Agilio)
info_getO (상세)O (상세)OO
port splitOOXO
subfunction (SF)OO (제한적)XX
eSwitch modeOOXO
paramO (다수)OOO
resourceXXXO (상세)
health reporterO (fw/tx/rx/vnic)O (fw/pci)O (fw)O
trapO (mlxsw)OXO
regionO (cr-space)O (nvm/caps)XX
rateOXXX
linecardO (mlxsw)XXX
flash updateOOOO
reloadO (reinit+fw_activate)O (reinit)O (reinit)X

mlx5 드라이버 devlink 구현 상세

# mlx5 전체 devlink 정보
devlink dev info pci/0000:03:00.0
# 출력 예시:
# pci/0000:03:00.0:
#   driver mlx5_core
#   serial_number MT2116X09299
#   versions:
#     fixed:
#       fw.psid MT_0000000228
#       board.id-revid LNV0000000032
#     running:
#       fw.mgmt 22.36.1010
#       fw.undi 14.29.15
#       fw.bundle_id 22360_1010
#     stored:
#       fw.mgmt 22.36.1010
#       fw.undi 14.29.15

# mlx5 health reporter 목록
devlink health show pci/0000:03:00.0
# fw, fw_fatal, vnic, tx, rx 리포터 제공

# mlx5 VNIC 진단 (VF/SF별 상태 진단)
devlink health diagnose pci/0000:03:00.0 reporter vnic

# mlx5 region (cr-space) 스냅샷
devlink region show pci/0000:03:00.0
devlink region snapshot new pci/0000:03:00.0/cr-space

# mlx5 SF 생성 예
devlink port add pci/0000:03:00.0 flavour pcisf pfnum 0 sfnum 4
devlink port function set pci/0000:03:00.0/32768 \
    hw_addr 02:00:00:00:00:04 state active

# mlx5 rate 설정
devlink port function rate add pci/0000:03:00.0/group-1
devlink port function rate set pci/0000:03:00.0/group-1 \
    tx_share 10gbit tx_max 25gbit
devlink port function rate set pci/0000:03:00.0/32768 \
    tx_max 5gbit parent group-1

ice 드라이버 devlink 구현 상세

# ice 장치 정보
devlink dev info pci/0000:31:00.0
# 출력 예시:
# pci/0000:31:00.0:
#   driver ice
#   serial_number 00-01-02-03-04-05
#   versions:
#     fixed:
#       board.id K94210-000
#     running:
#       fw.mgmt 3.2.5
#       fw.undi 2.1.0
#       fw.netlist 3.2.5-1

# ice 포트 분할
devlink port split pci/0000:31:00.0/0 count 4

# ice reload (driverinit param 적용)
devlink dev param set pci/0000:31:00.0 \
    name enable_roce value false cmode driverinit
devlink dev reload pci/0000:31:00.0

# ice flash update
devlink dev flash pci/0000:31:00.0 file /tmp/ice_fw.bin

# ice eSwitch 모드
devlink dev eswitch set pci/0000:31:00.0 mode switchdev

# ice region (NVM/Capabilities)
devlink region show pci/0000:31:00.0
devlink region dump pci/0000:31:00.0/nvm-flash snapshot 0
드라이버 소스 위치: mlx5 devlink 구현은 drivers/net/ethernet/mellanox/mlx5/core/devlink.c, ice는 drivers/net/ethernet/intel/ice/ice_devlink.c, bnxt는 drivers/net/ethernet/broadcom/bnxt/bnxt_devlink.c에 있습니다. 각 드라이버의 devlink 구현을 참고하면 자체 드라이버에 기능을 추가할 때 도움이 됩니다.

운영 자동화 패턴

devlink는 사람이 직접 조작하는 횟수를 줄이고, 상태 수집과 검증을 자동화할 때 효과가 큽니다. 특히 장치 수가 많아질수록 "명령 실행"보다 "사전/사후 검증 규칙"이 중요합니다.

DevOps 자동화 파이프라인 Discovery dev show + info Snapshot port/health/trap Validate 사전 조건 검증 Execute reload/flash/set Verify 사후 검증 Rollback (실패 시) 자동화 핵심 원칙 1) 항상 JSON 출력(-j)을 사용하고, jq/python으로 파싱합니다. 텍스트 출력은 버전별로 변할 수 있습니다. 2) 변경 작업 전후로 스냅샷을 저장합니다. diff로 예상하지 못한 변경 사항을 탐지합니다. 3) 사전 검증 실패 시 절대 작업을 진행하지 않습니다. 드라이버 버전, FW 버전, health 상태를 확인합니다. 4) 장치별 순차 처리합니다. 병렬 flash/reload는 절대 금지합니다. 5) 사후 검증에서 health error_count 증가, trap 급증, port 변동이 감지되면 즉시 롤백합니다. 6) devlink monitor를 별도 프로세스로 실행하여 이벤트를 실시간 로깅합니다.
#!/usr/bin/env bash
# devlink 종합 자동화 스크립트 (사전/사후 검증 포함)
set -euo pipefail

DEV="${1:-pci/0000:03:00.0}"
ACTION="${2:-snapshot}"  # snapshot | reload | flash
FW_FILE="${3:-}"
TS="$(date +%Y%m%d-%H%M%S)"
OUT="/var/tmp/devlink-${TS}"
mkdir -p "$OUT"

# 함수: JSON 스냅샷 수집
snapshot() {
    local suffix="${1:-before}"
    devlink -j dev show "$DEV"              > "$OUT/dev.${suffix}.json"
    devlink -j dev info "$DEV"              > "$OUT/info.${suffix}.json"
    devlink -j port show "$DEV"             > "$OUT/port.${suffix}.json"
    devlink -j health show "$DEV"           > "$OUT/health.${suffix}.json"
    devlink -j trap show "$DEV"             > "$OUT/trap.${suffix}.json" 2>/dev/null || true
    devlink -j resource show "$DEV"         > "$OUT/resource.${suffix}.json" 2>/dev/null || true
    echo "[INFO] snapshot saved: $OUT/*.${suffix}.json"
}

# 함수: 사전 검증
pre_validate() {
    local health_errors
    health_errors=$(jq '[.[][] | .error // 0] | add' "$OUT/health.before.json")
    if [ "$health_errors" -gt 0 ]; then
        echo "[WARN] 기존 health 오류 ${health_errors}건 존재"
    fi

    echo "[INFO] FW version: $(jq -r '.info[][].versions.running."fw.mgmt" // "N/A"' "$OUT/info.before.json")"
    echo "[INFO] 포트 수: $(jq '.port | length' "$OUT/port.before.json")"
}

# 함수: 사후 검증
post_validate() {
    snapshot "after"

    # 포트 수 변동 확인
    local before_ports after_ports
    before_ports=$(jq '.port | length' "$OUT/port.before.json")
    after_ports=$(jq '.port | length' "$OUT/port.after.json")
    if [ "$before_ports" != "$after_ports" ]; then
        echo "[ALERT] 포트 수 변동: ${before_ports} -> ${after_ports}"
    fi

    # health error 증가 확인
    local before_err after_err
    before_err=$(jq '[.[][] | .error // 0] | add' "$OUT/health.before.json")
    after_err=$(jq '[.[][] | .error // 0] | add' "$OUT/health.after.json")
    if [ "$after_err" -gt "$before_err" ]; then
        echo "[ALERT] health 오류 증가: ${before_err} -> ${after_err}"
    fi

    echo "[INFO] 사후 검증 완료"
}

# 메인 로직
echo "[INFO] devlink automation: DEV=$DEV ACTION=$ACTION"
snapshot "before"
pre_validate

case "$ACTION" in
    snapshot)
        echo "[INFO] 스냅샷만 수집합니다."
        ;;
    reload)
        echo "[INFO] reload 실행..."
        devlink dev reload "$DEV" action driver_reinit
        post_validate
        ;;
    flash)
        if [ -z "$FW_FILE" ]; then
            echo "[ERROR] flash 파일 경로를 지정하세요."
            exit 1
        fi
        echo "[INFO] flash 실행: $FW_FILE"
        devlink dev flash "$DEV" file "$FW_FILE"
        post_validate
        ;;
    *)
        echo "[ERROR] 알 수 없는 ACTION: $ACTION"
        exit 1
        ;;
esac

echo "[INFO] 결과: $OUT"

pyroute2를 이용한 Python 자동화

# pyroute2를 이용한 devlink 자동화 예제
import json
import subprocess

def devlink_json(cmd):
    """devlink 명령을 JSON으로 실행하여 파싱된 결과 반환"""
    result = subprocess.run(
        ["devlink", "-j"] + cmd.split(),
        capture_output=True, text=True, check=True
    )
    return json.loads(result.stdout)

def get_health_status(dev):
    """health reporter 상태를 구조화하여 반환"""
    data = devlink_json(f"health show {dev}")
    reporters = {}
    for key, items in data.get("health", {}).items():
        for item in items:
            reporters[item["name"]] = {
                "state": item.get("state", "unknown"),
                "error": item.get("error", 0),
                "recover": item.get("recover", 0),
            }
    return reporters

def get_port_topology(dev):
    """포트 토폴로지를 flavour별로 분류하여 반환"""
    data = devlink_json(f"port show {dev}")
    topology = {}
    for port_id, info in data.get("port", {}).items():
        flavour = info.get("flavour", "unknown")
        if flavour not in topology:
            topology[flavour] = []
        topology[flavour].append({
            "id": port_id,
            "netdev": info.get("netdev"),
            "type": info.get("type"),
        })
    return topology

# 사용 예
dev = "pci/0000:03:00.0"
health = get_health_status(dev)
for name, status in health.items():
    if status["error"] > 0:
        print(f"[ALERT] reporter {name}: {status['error']} errors")

ports = get_port_topology(dev)
print(f"포트 요약: {', '.join(f'{k}: {len(v)}개' for k, v in ports.items())}")

운영 체크리스트

프로덕션 환경에서 devlink를 활용할 때 반드시 확인해야 하는 항목들을 정리합니다.

일상 모니터링

유지보수 전 확인

장애 대응 절차

운영 자동화 팁: Prometheus/Grafana와 연동할 때는 devlink -j 출력을 주기적으로 수집하는 exporter를 작성하는 것이 효과적입니다. health error_count, trap counter, sb occupancy를 시계열 메트릭으로 수집하면 장애 징후를 사전에 탐지할 수 있습니다.

네트워크 네임스페이스와 devlink

커널 5.x 이후 devlink는 네트워크 네임스페이스 이동을 지원합니다. 컨테이너 환경에서 SF나 VF를 특정 네임스페이스에 격리하여 운영할 수 있습니다.

# devlink 인스턴스를 특정 네임스페이스로 이동
devlink dev reload pci/0000:03:00.0 netns my_container_ns

# 특정 네임스페이스에서 devlink 명령 실행
ip netns exec my_container_ns devlink dev show
ip netns exec my_container_ns devlink port show

# SF를 네임스페이스로 이동하여 격리
# 1. SF 생성 후 활성화
devlink port add pci/0000:03:00.0 flavour pcisf pfnum 0 sfnum 10
devlink port function set pci/0000:03:00.0/32768 \
    hw_addr 02:00:00:00:00:10 state active

# 2. SF의 auxiliary devlink를 네임스페이스로 이동
devlink dev reload auxiliary/mlx5_core.sf.0 netns tenant_ns

# 3. 네임스페이스 내에서 SF 독립 운영
ip netns exec tenant_ns devlink dev show
ip netns exec tenant_ns devlink health show auxiliary/mlx5_core.sf.0
네임스페이스 격리 범위: devlink 인스턴스가 네임스페이스로 이동하면, 해당 인스턴스의 포트, health, trap 정보도 함께 이동합니다. 단, 부모 PF의 devlink 인스턴스는 기본 네임스페이스에 남아있으므로, PF 레벨의 제어(eSwitch, flash, reload)는 기본 네임스페이스에서만 가능합니다.

커널 설정

옵션설명권장비고
CONFIG_NET_DEVLINKdevlink 코어y대부분 자동 선택됨
CONFIG_NET_SWITCHDEVswitchdev 오프로드yeSwitch 사용 시 필수
CONFIG_MLX5_COREMellanox ConnectX 시리즈m가장 풍부한 devlink 지원
CONFIG_MLX5_ESWITCHmlx5 eSwitch 지원yswitchdev 모드 필수
CONFIG_MLX5_SFmlx5 Subfunction 지원ySF 사용 시 필수
CONFIG_ICEIntel E810 계열mice devlink 지원
CONFIG_BNXTBroadcom NetXtremembnxt devlink 지원
CONFIG_NFPNetronome Agiliomresource 모델 참조 구현
CONFIG_MLXSW_COREMellanox Spectrum 스위치mtrap/linecard 참조 구현
CONFIG_AUXILIARY_BUSauxiliary busySF 사용 시 필수
# devlink 관련 커널 설정 확인
grep -E 'DEVLINK|SWITCHDEV|MLX5|ICE|BNXT|NFP|MLXSW|AUXILIARY' /boot/config-$(uname -r)

# iproute2 패키지 확인 (devlink CLI 포함)
devlink --version
# devlink utility, iproute2-6.x.0

# 필요한 커널 모듈 로드
modprobe devlink
modprobe mlx5_core
modprobe ice

문제 해결과 디버깅

devlink 관련 문제를 진단할 때 체계적인 접근이 필요합니다. 아래는 자주 발생하는 문제와 해결 방법을 정리합니다.

증상원인해결 방법
devlink dev show 빈 출력devlink 지원 드라이버 미로드modprobe mlx5_core 등 드라이버 로드
eSwitch 전환 실패VF가 아직 활성 상태sriov_numvfs=0 후 전환
SF 생성 실패CONFIG_MLX5_SF=n커널 설정 확인 및 재빌드
reload 실패지원하지 않는 action/limitdevlink dev info로 지원 범위 확인
flash 진행 중단FW 파일 손상/호환성벤더 제공 FW 파일 확인, MD5 검증
trap 카운터 안 올라감드라이버 trap 미지원드라이버 소스에서 trap 등록 확인
health dump 비어있음reporter의 dump 콜백 미구현diagnose 사용 또는 드라이버 확인
rate 설정 실패드라이버 미지원mlx5만 rate 지원 (현재)
port show에 netdev 없음네트워크 드라이버 미바인딩auxiliary bus 드라이버 확인
# 디버깅 도구

# 1. Netlink 메시지 추적
modprobe netlink_diag
ss -f netlink -a

# 2. devlink 이벤트 모니터링
devlink monitor all &
# 별도 터미널에서 작업 수행

# 3. dmesg에서 devlink/드라이버 관련 로그
dmesg | grep -iE 'devlink|mlx5|ice|bnxt'

# 4. Netlink 디버그 (커널 디버그 활성화 시)
echo 1 > /sys/kernel/debug/tracing/events/devlink/enable
cat /sys/kernel/debug/tracing/trace_pipe

# 5. health dump를 파일로 저장
devlink health dump show pci/0000:03:00.0 reporter fw > /tmp/fw_dump.json

# 6. 모든 devlink 정보 종합 수집
for cmd in "dev show" "dev info" "port show" "health show" \
    "trap show" "resource show" "param show"; do
    echo "=== devlink $cmd ==="
    devlink -j $cmd 2>/dev/null || echo "(미지원)"
done
ftrace 연동: devlink 코어의 함수 호출을 추적하려면 ftrace를 활용합니다. echo devlink_* > /sys/kernel/debug/tracing/set_ftrace_filter로 devlink 관련 함수만 필터링하면, 특정 Netlink 명령이 어떤 ops 콜백을 호출하는지 정확히 추적할 수 있습니다. 자세한 내용은 ftrace 문서를 참고하세요.

devlink tracepoint 활용

커널은 devlink 관련 tracepoint를 제공합니다. 이를 활용하면 Netlink 명령 수준이 아닌 커널 내부 동작 수준에서 devlink의 동작을 추적할 수 있습니다.

# devlink tracepoint 목록 확인
ls /sys/kernel/debug/tracing/events/devlink/
# devlink_hwmsg, devlink_hwerr, devlink_health_report,
# devlink_health_recover_aborted, devlink_trap_report 등

# health report tracepoint 활성화
echo 1 > /sys/kernel/debug/tracing/events/devlink/devlink_health_report/enable

# hwmsg tracepoint (하드웨어 메시지 추적)
echo 1 > /sys/kernel/debug/tracing/events/devlink/devlink_hwmsg/enable
cat /sys/kernel/debug/tracing/trace_pipe
# 출력 예시:
# mlx5_core-1234 [003] .... 12345.678: devlink_hwmsg: bus_name=pci dev_name=0000:03:00.0
#   driver_name=mlx5_core incoming=true type=0 buf=... len=64

# trap report tracepoint (trap 이벤트 추적)
echo 1 > /sys/kernel/debug/tracing/events/devlink/devlink_trap_report/enable

# 특정 드라이버의 devlink 이벤트만 필터링
echo 'bus_name == "pci" && dev_name == "0000:03:00.0"' > \
    /sys/kernel/debug/tracing/events/devlink/devlink_hwmsg/filter

# perf를 이용한 devlink 이벤트 수집
perf record -e 'devlink:*' -a -- sleep 10
perf script

Netlink 메시지 덤프

devlink CLI와 커널 간의 Netlink 메시지를 직접 관찰하면 ABI 호환성 문제나 파싱 오류를 정확히 진단할 수 있습니다.

# nlmon 커널 모듈을 이용한 Netlink 캡처
modprobe nlmon
ip link add nlmon0 type nlmon
ip link set nlmon0 up

# tcpdump로 Netlink 메시지 캡처
tcpdump -i nlmon0 -w /tmp/devlink_netlink.pcap &

# devlink 명령 실행 (캡처됨)
devlink dev show
devlink port show

# 캡처 중단
kill %1

# Wireshark에서 pcap 파일 분석
# Display filter: genl.family_name == "devlink"

# 정리
ip link del nlmon0
Wireshark 활용: Wireshark는 Generic Netlink의 devlink 프로토콜을 파싱할 수 있습니다. genl.family_name == "devlink" 필터로 devlink 메시지만 선별하면, 각 명령의 TLV 속성 구조를 시각적으로 확인할 수 있습니다. 자동화 도구의 Netlink 메시지가 올바르게 구성되었는지 검증할 때 유용합니다.

devlink core 내부 구조

devlink 코어의 내부 구조를 이해하면 드라이버 개발과 디버깅에 도움이 됩니다. 커널 6.x에서 단일 파일이었던 devlink.c가 기능별로 분리되었고, devl_lock 기반의 새로운 잠금 모델이 도입되었습니다.

net/devlink/ 소스 구조 (커널 6.x) devl_lock (struct mutex) -- 인스턴스별 직렬화 dev.c 장치 등록/해제, info port.c 포트 관리, split, SF health.c reporter, dump, fmsg trap.c trap, group, policer param.c 파라미터 관리 resource.c 자원 트리 관리 region.c 메모리 영역, 스냅샷 rate.c 대역폭 제어 트리 sb.c shared buffer linecard.c 라인카드 관리 netlink.c / leftover.c Netlink 정책, 명령 디스패치, 알림 devl_internal.h 내부 헤더, 구조체 정의, 유틸리티 잠금 모델 (커널 6.x) devl_lock: per-instance mutex. 하위 객체(port/param/resource/health 등)의 등록/해제 시 보호. devlink_net_lock: 네트워크 네임스페이스 이동 시 net별 devlink 리스트 보호. Netlink 요청 경로: Netlink 콜백이 devl_lock을 자동 획득 후 ops를 호출. 드라이버는 lock을 잡지 않아도 됨. reload 경로: reload_down/up 콜백은 devl_lock 아래에서 호출됨. 하위 객체 재등록 시 devl_ 접두사 API 사용.

주요 내부 구조체

/* 핵심 구조체 관계 (간략화) */
struct devlink {
    struct list_head        list;           /* 전역 devlink 리스트 */
    struct list_head        port_list;      /* 포트 리스트 */
    struct list_head        reporter_list;  /* health reporter 리스트 */
    struct list_head        param_list;     /* 파라미터 리스트 */
    struct list_head        region_list;    /* region 리스트 */
    struct list_head        rate_list;      /* rate 노드 리스트 */
    struct list_head        linecard_list;  /* 라인카드 리스트 */
    struct list_head        sb_list;        /* shared buffer 리스트 */

    const struct devlink_ops *ops;           /* 드라이버 콜백 */
    struct mutex           lock;           /* devl_lock */
    struct device          *dev;           /* 부모 디바이스 */

    u8                     reload_failed:1; /* reload 실패 플래그 */
    refcount_t             refcount;       /* 참조 카운트 */

    /* ... private data는 이 구조체 뒤에 할당됨 */
};

struct devlink_port {
    struct list_head        list;
    struct list_head        region_list;
    struct devlink          *devlink;
    unsigned int           index;
    struct devlink_port_attrs attrs;
    struct net_device      *type_dev;   /* 연결된 netdev */
    struct devlink_rate    *devlink_rate; /* 연결된 rate 노드 */
    struct devlink_linecard *linecard;   /* 소속 라인카드 */
};

struct devlink_health_reporter {
    struct list_head        list;
    struct devlink          *devlink;
    const struct devlink_health_reporter_ops *ops;
    void                   *priv;
    struct devlink_fmsg    *dump_fmsg;
    u64                    graceful_period; /* 밀리초 */
    bool                   auto_recover;
    bool                   auto_dump;
    u64                    error_count;
    u64                    recovery_count;
    enum devlink_health_reporter_state health_state;
};

struct devlink_trap {
    struct list_head        list;
    const struct devlink_trap_item *trap;
    enum devlink_trap_action action;
    struct devlink_stats   __percpu *stats;
    void                   *priv;        /* 드라이버 전용 */
};

struct devlink_rate {
    struct list_head        list;
    enum devlink_rate_type type;       /* node or leaf */
    struct devlink          *devlink;
    u64                    tx_share;    /* 보장 대역폭 */
    u64                    tx_max;      /* 최대 대역폭 */
    struct devlink_rate    *parent;     /* 계층 트리 */
    union {
        struct devlink_port *devlink_port; /* leaf */
        struct {
            char           *name;          /* node 이름 */
            refcount_t     refcnt;
        };
    };
};

Netlink 명령 디스패치 흐름

사용자가 devlink dev show를 실행하면, Generic Netlink를 통해 DEVLINK_CMD_GET 명령이 커널에 전달됩니다. 커널의 devlink 코어는 다음과 같은 순서로 처리합니다:

/* Netlink 명령 디스패치 흐름 (간략화) */

/* 1. Generic Netlink 프레임워크가 devlink 패밀리의 명령을 디스패치 */
static const struct genl_small_ops devlink_nl_ops[] = {
    { .cmd = DEVLINK_CMD_GET,
      .doit = devlink_nl_cmd_get_doit,
      .dumpit = devlink_nl_cmd_get_dumpit, },
    { .cmd = DEVLINK_CMD_PORT_GET,
      .doit = devlink_nl_cmd_port_get_doit,
      .dumpit = devlink_nl_cmd_port_get_dumpit, },
    /* ... 수십 개의 명령 ... */
};

/* 2. doit 콜백에서 devlink 인스턴스 찾기 */
static int devlink_nl_cmd_get_doit(struct sk_buff *skb,
                                   struct genl_info *info)
{
    /* bus_name + dev_name으로 devlink 인스턴스 조회 */
    struct devlink *dl = devlink_get_from_attrs(genl_info_net(info),
                                               info->attrs);
    /* devl_lock 획득 */
    devl_lock(dl);

    /* 3. ops 콜백 호출 (예: info_get) */
    if (dl->ops->info_get)
        err = dl->ops->info_get(dl, req, info->extack);

    /* 4. 응답 Netlink 메시지 구성 */
    devlink_nl_fill(msg, dl, DEVLINK_CMD_GET, ...);

    devl_unlock(dl);
    return genlmsg_reply(msg, info);
}
API 전환 가이드: 커널 6.3+ 드라이버를 작성한다면, 기존 devlink_port_register() 대신 devl_port_register()를 사용하세요. 기존 API는 내부적으로 devl_lock을 획득하지만, probe/remove에서 이미 lock을 잡고 있다면 데드락이 발생합니다. 새 API는 호출자가 lock을 잡은 상태를 전제로 합니다.
기존 API (deprecated)새 API (devl_ 접두사)전환 시점
devlink_port_register()devl_port_register()커널 6.3
devlink_port_unregister()devl_port_unregister()커널 6.3
devlink_params_register()devl_params_register()커널 6.3
devlink_resource_register()devl_resource_register()커널 6.3
devlink_health_reporter_create()devl_health_reporter_create()커널 6.3
devlink_region_create()devl_region_create()커널 6.3
devlink_trap_register()devl_trap_register()커널 6.3
devlink_rate_node_create()devl_rate_node_create()커널 6.3

flash update 구현 심화

flash update는 NIC의 펌웨어를 원격으로 갱신하는 기능입니다. 수백~수천 대의 장치를 관리하는 데이터센터에서는 표준화된 flash 인터페이스가 운영 효율을 크게 높입니다. devlink flash update는 request_firmware() 프레임워크와 연동하여 펌웨어 이미지를 로드하고, 드라이버별 flash 로직을 실행합니다.

flash update 내부 처리 흐름 devlink dev flash pci/0000:03:00.0 file fw.bin component fw.mgmt DEVLINK_CMD_FLASH_UPDATE (Netlink) devlink core request_firmware() 드라이버 ops flash_update() devlink_flash_update_status_notify() 진행률 Netlink 알림 전송 성공: stored 버전 갱신 reload fw_activate로 적용 실패: extack 메시지 반환 FW 파일 손상, 호환성 오류 등
/* flash_update 콜백 구현 예 */
static int my_flash_update(struct devlink *dl,
                           struct devlink_flash_update_params *params,
                           struct netlink_ext_ack *extack)
{
    struct my_priv *priv = devlink_priv(dl);
    const struct firmware *fw = params->fw;
    u32 offset = 0;
    int err;

    /* component 확인 */
    if (params->component &&
        strcmp(params->component, "fw.mgmt") != 0) {
        NL_SET_ERR_MSG_MOD(extack,
            "Unsupported component, use fw.mgmt");
        return -EOPNOTSUPP;
    }

    /* FW 이미지 유효성 검증 */
    err = my_validate_fw_image(priv, fw->data, fw->size);
    if (err) {
        NL_SET_ERR_MSG_MOD(extack, "Invalid firmware image");
        return err;
    }

    /* 진행률 보고하며 flash 실행 */
    devlink_flash_update_status_notify(dl, "Preparing",
        "fw.mgmt", 0, fw->size);

    while (offset < fw->size) {
        u32 chunk = min((u32)4096, fw->size - offset);

        err = my_write_fw_chunk(priv, fw->data + offset, chunk);
        if (err) {
            NL_SET_ERR_MSG_MOD(extack, "Flash write failed");
            return err;
        }

        offset += chunk;
        devlink_flash_update_status_notify(dl, "Flashing",
            "fw.mgmt", offset, fw->size);
    }

    devlink_flash_update_status_notify(dl, "Activating",
        "fw.mgmt", fw->size, fw->size);

    err = my_activate_fw(priv);
    if (err) {
        NL_SET_ERR_MSG_MOD(extack, "FW activation failed");
        return err;
    }

    devlink_flash_update_status_notify(dl, "Done",
        "fw.mgmt", fw->size, fw->size);

    return 0;
}
# flash update 운영 절차

# 1. 현재 FW 버전 확인
devlink dev info pci/0000:03:00.0 | grep fw.mgmt
# running: 22.36.1010
# stored:  22.36.1010

# 2. FW 파일을 /lib/firmware 또는 임시 경로에 배치
cp mlx5_fw_22.37.2010.bin /var/tmp/

# 3. 별도 터미널에서 진행률 모니터링
devlink monitor &

# 4. flash 실행
devlink dev flash pci/0000:03:00.0 file /var/tmp/mlx5_fw_22.37.2010.bin

# 5. 결과 확인 (stored 버전만 변경됨)
devlink dev info pci/0000:03:00.0 | grep fw.mgmt
# running: 22.36.1010
# stored:  22.37.2010

# 6. 새 FW 활성화 (재부팅 없이)
devlink dev reload pci/0000:03:00.0 action fw_activate

# 7. 최종 확인
devlink dev info pci/0000:03:00.0 | grep fw.mgmt
# running: 22.37.2010
# stored:  22.37.2010
flash 중 전원 차단 주의: flash update 도중 전원이 차단되면 NIC 펌웨어가 손상되어 장치가 부팅 불가 상태가 될 수 있습니다. UPS가 없는 환경에서는 IPMI/BMC를 통한 원격 복구 방법을 사전에 확인하세요. 대부분의 SmartNIC은 dual-bank NVM을 사용하여 하나의 이미지가 손상되어도 복구할 수 있지만, 모든 장치가 이를 지원하는 것은 아닙니다.

devlink과 다른 서브시스템 연계

devlink는 독립적으로 동작하지 않고 다양한 커널 서브시스템과 연계됩니다. 각 연계 지점을 이해하면 전체적인 네트워크 스택에서 devlink의 위치를 파악할 수 있습니다.

devlink 연계 서브시스템 devlink core net/devlink/ ethtool 링크/드라이버 정보 TC / OVS 플로우 오프로드 Netfilter flowtable 오프로드 PCI / SR-IOV VF 관리 Auxiliary Bus SF 드라이버 바인딩 RDMA / IB RoCE 설정 연동 net_device 포트-netdev 매핑 switchdev FDB/VLAN 오프로드 연계 포인트 요약 ethtool: 링크 정보와 드라이버 정보의 상호 보완. devlink info와 ethtool -i의 드라이버 정보가 일치해야 합니다. TC/OVS: eSwitch switchdev 모드에서 representor를 통한 HW 오프로드 규칙 설치. PCI/SR-IOV: VF 생성 시 devlink port가 자동 등록. eSwitch 모드에 따라 representor 생성 여부 결정.

devlink과 ethtool 정보 비교

# ethtool과 devlink의 정보 비교

# ethtool: 개별 netdev 중심 정보
ethtool -i enp3s0f0
# driver: mlx5_core
# version: 5.15.0
# firmware-version: 22.36.1010 (MT_0000000228)
# bus-info: 0000:03:00.0

# devlink: 장치 전체 정보 (더 상세)
devlink dev info pci/0000:03:00.0
# driver mlx5_core
# serial_number MT2116X09299
# versions:
#   fixed: fw.psid, board.id
#   running: fw.mgmt, fw.undi, fw.bundle_id
#   stored: fw.mgmt, fw.undi

# ethtool: 링크 통계
ethtool -S enp3s0f0 | head -20

# devlink: 장치 레벨 trap 통계
devlink -s trap show pci/0000:03:00.0

devlink과 sysfs 연계

# sysfs를 통한 SR-IOV VF 관리 (devlink 연계)
# VF 생성
echo 4 > /sys/class/net/enp3s0f0/device/sriov_numvfs

# devlink port에 자동 등록 확인
devlink port show pci/0000:03:00.0
# pci/0000:03:00.0/65536: type eth netdev enp3s0f0v0 flavour pci_vf ...

# sysfs: PCI 장치 정보 확인
lspci -vv -s 0000:03:00.0 | grep -i "Physical Slot\|LnkSta\|Region"

# sysfs: NUMA 노드 확인 (성능 최적화)
cat /sys/bus/pci/devices/0000:03:00.0/numa_node

# devlink과 일치하는 netdev 확인
devlink -j port show | jq '.port | to_entries[] | select(.value.netdev != null) | {port: .key, netdev: .value.netdev}'

Ansible을 이용한 대규모 devlink 관리

# Ansible playbook: devlink FW update
---
- name: devlink firmware update
  hosts: smartnic_hosts
  become: true
  serial: 1  # 한 대씩 순차 처리
  vars:
    devlink_dev: "pci/0000:03:00.0"
    fw_file: "/opt/firmware/mlx5_fw_22.37.bin"
    min_fw_version: "22.36.0"

  tasks:
    - name: 사전 스냅샷 수집
      shell: |
        devlink -j dev info {{ devlink_dev }}
      register: dev_info

    - name: 현재 FW 버전 확인
      set_fact:
        current_fw: "{{ (dev_info.stdout | from_json).info
          | dict2items | first | community.general.json_query('value.versions.running.\"fw.mgmt\"') }}"

    - name: FW 버전 사전 검증
      assert:
        that:
          - current_fw is version(min_fw_version, '>=')
        fail_msg: "FW {{ current_fw }}이 최소 버전 미달"

    - name: health 상태 확인
      shell: |
        devlink -j health show {{ devlink_dev }} |
        jq '[.[][] | .error] | add'
      register: health_errors
      failed_when: health_errors.stdout | int > 0

    - name: flash update 실행
      shell: |
        devlink dev flash {{ devlink_dev }} file {{ fw_file }}
      timeout: 600

    - name: FW 활성화
      shell: |
        devlink dev reload {{ devlink_dev }} action fw_activate

    - name: 사후 검증
      shell: |
        devlink -j dev info {{ devlink_dev }}
      register: post_info

    - name: 결과 보고
      debug:
        msg: "{{ inventory_hostname }}: FW 업데이트 완료"
대규모 배포 주의사항: 수백 대 이상의 NIC 펌웨어를 업데이트할 때는 반드시 serial: 1 또는 소규모 배치(serial: 5)로 순차 처리하세요. 동시에 많은 장치를 flash/reload하면 네트워크 전체가 마비될 수 있습니다. 카나리 배포 전략(일부 장치 먼저 업데이트 -> 검증 -> 전체 배포)을 권장합니다.

성능 고려사항

devlink 자체는 제어면 인프라이므로 데이터면 성능에 직접적인 영향은 적습니다. 그러나 설정에 따라 간접적으로 데이터면 성능에 영향을 줄 수 있습니다.

설정 항목성능 영향권장 사항
trap action=trap해당 패킷이 CPU로 전달되어 부하 발생policer 설정 필수, 불필요한 trap 최소화
eSwitch modeswitchdev 모드에서 slow path 패킷 증가 가능TC/OVS 규칙으로 fast path 오프로드 확보
resource 할당FDB/ACL/TCAM 크기가 오프로드 용량 결정워크로드에 맞게 리소스 분배
rate 설정tx_share/tx_max가 VF/SF 대역폭 결정과도한 제한 방지, burst 여유 확보
SB poolpool 크기가 burst 흡수 능력 결정tail drop 발생 시 pool 크기 증가
health auto_recover복구 중 데이터면 순간 중단grace_period를 적절히 설정
reload드라이버 재초기화 동안 패킷 손실트래픽 drain 후 실행
SF 수너무 많은 SF는 MSI-X/queue 자원 분산필요한 만큼만 생성, 리소스 모니터링
# 성능 관련 devlink 확인 명령

# 1. 현재 resource 사용량 확인
devlink resource show pci/0000:03:00.0
# FDB, ACL, TCAM 용량 확인 -> 오프로드 가능 규칙 수 결정

# 2. shared buffer occupancy 확인
devlink sb occupancy show pci/0000:03:00.0
# pool 점유율이 높으면 tail drop 위험

# 3. trap 통계로 제어면 부하 확인
devlink -s trap show pci/0000:03:00.0
# action=trap인 항목의 패킷 수가 많으면 CPU 부하

# 4. rate 설정 확인 (대역폭 병목 여부)
devlink port function rate show pci/0000:03:00.0

# 5. eSwitch 모드에서 representor 수 확인
devlink port show pci/0000:03:00.0 | grep -c representor
# representor 수가 많으면 slow path 부하 증가 가능

# 6. NUMA 로컬리티 확인 (성능 최적화)
cat /sys/bus/pci/devices/0000:03:00.0/numa_node
# IRQ affinity를 동일 NUMA 노드의 CPU에 설정
trap 폭주 방지: 네트워크 루프나 DDoS 공격 시 특정 trap의 패킷 수가 급증하면 CPU가 포화됩니다. 반드시 devlink trap policer를 설정하여 trap 당 최대 rate/burst를 제한하세요. 제어면 보호가 없으면 관리 접속(SSH) 자체가 불가능해질 수 있습니다.

devlink 발전 역사

devlink 서브시스템은 2015년 Jiri Pirko에 의해 처음 제안되어, 이후 지속적으로 기능이 확장되고 있습니다. 주요 마일스톤을 시간순으로 정리합니다.

커널 버전시기추가 기능핵심 변화
4.62016devlink 코어 도입device/port 기본 구조, switchdev 연계
4.82016shared buffer (sb)혼잡 관리, pool/TC 바인딩
4.112017eSwitch modelegacy/switchdev 전환, representor 개념
4.142017info_get, param표준화된 드라이버/FW 정보, 파라미터
4.182018resource, region하드웨어 자원 트리, 메모리 영역 접근
5.02019health reporter구조화된 장애 관리 프레임워크
5.12019flash update표준화된 펌웨어 갱신 인터페이스
5.32019trapHW 드롭/예외 패킷 관찰 프레임워크
5.72020reload action/limit세분화된 reload 제어
5.92020rateVF/SF별 계층적 대역폭 제어
5.132021subfunction (SF)경량 하드웨어 격리 메커니즘
5.182022linecard모듈형 라인카드 관리
6.12022devl_lock 도입per-instance mutex, 새로운 잠금 모델
6.32023devl_ 접두사 APIlock-aware API 전환, 기존 API 폐기 시작
6.52023소스 파일 분리단일 devlink.c -> 기능별 파일 분리
6.8+2024port function capsSF migratable, ipsec_crypto 등 확장
미래 방향: devlink는 계속 확장되고 있습니다. 주요 발전 방향으로는 (1) SF의 라이브 마이그레이션 완전 지원, (2) DPU/SmartNIC 전용 기능 확장, (3) eBPF 기반 trap 처리 연동, (4) 더 세분화된 rate 제어 (per-queue rate) 등이 논의되고 있습니다. 커널 메일링 리스트(netdev@vger.kernel.org)에서 최신 패치를 추적할 수 있습니다.

서브펑션 (SubFunction) 라이프사이클 심화

SF의 전체 라이프사이클은 단순한 생성/삭제를 넘어, 리소스 할당, 큐 구성, IRQ 바인딩, auxiliary 디바이스 프로브, 그리고 운영 중 상태 전환까지 복잡한 단계를 포함합니다. 프로덕션 환경에서 SF를 안정적으로 운영하려면 각 단계의 내부 동작과 실패 시 복구 경로를 정확히 이해해야 합니다.

SF 라이프사이클 전체 흐름 (8단계) 1. port add sfnum 할당, 포트 등록 2. hw_addr 설정 MAC 주소 바인딩 3. caps 설정 roce, migratable 등 4. state active auxiliary bus probe 5. 리소스 할당 EQ/CQ/SQ/RQ 생성 6. netdev 등록 SF netdev 생성 7. 운영 상태 트래픽 전달, 모니터링 8. 해제 inactive -> port del 각 단계 내부 동작 1. port add 드라이버의 port_new() 콜백 호출 -> HW에 SF context 할당 -> devlink port 등록 -> sfnum으로 고유 식별 2. hw_addr port_fn_hw_addr_set() -> HW MAC 테이블에 주소 등록 -> active 전에만 변경 가능 3. caps roce_en, migratable, ipsec_crypto, ipsec_packet 등 기능 플래그 설정 (드라이버별 상이) 4. active port_fn_state_set() -> auxiliary_device_add() -> SF 드라이버 probe 트리거 5. 리소스 SF 드라이버가 EQ(Event Queue), CQ(Completion Queue), SQ/RQ(Send/Receive Queue) 생성 6. netdev register_netdev() -> representor와 연결 -> eSwitch에 포워딩 규칙 설치 7. 운영 SF의 devlink 인스턴스로 독립적인 health/trap/param 관리 가능 8. 해제 state inactive -> auxiliary_device_delete() -> port del -> HW context 해제 실패 복구 active 전환 실패: auxiliary bus probe 오류 -> dmesg 확인 -> caps 재설정 후 재시도 리소스 부족: MSI-X/queue 소진 -> 기존 SF 삭제 또는 PF 리소스 재분배 후 재시도
# SF 라이프사이클 전체 운영 절차 (mlx5 예시)

# 1. SF 생성 (sfnum은 PF 내 고유해야 함)
devlink port add pci/0000:03:00.0 flavour pcisf pfnum 0 sfnum 100
# 출력: pci/0000:03:00.0/32768: type eth ... flavour pcisf ...

# 2. MAC 주소 설정
devlink port function set pci/0000:03:00.0/32768 \
    hw_addr 02:00:00:00:00:64

# 3. 기능 플래그 설정 (커널 6.8+)
devlink port function set pci/0000:03:00.0/32768 \
    roce true migratable true

# 4. 활성화
devlink port function set pci/0000:03:00.0/32768 state active

# 5. SF devlink 인스턴스 확인
devlink dev show
# auxiliary/mlx5_core.sf.0: ...

# 6. SF의 netdev 확인
devlink port show | grep sf
ip link show | grep "sf\|mlx5"

# 7. SF 독립 모니터링
devlink health show auxiliary/mlx5_core.sf.0
devlink dev info auxiliary/mlx5_core.sf.0

# 8. SF 해제 (역순)
devlink port function set pci/0000:03:00.0/32768 state inactive
# auxiliary 디바이스 제거 대기 (dmesg 확인)
sleep 2
devlink port del pci/0000:03:00.0/32768
SF 상태auxiliary 디바이스netdevdevlink 인스턴스HW 리소스
inactive미등록없음없음context만 할당
active (probe 중)등록 중생성 중생성 중EQ/CQ/SQ/RQ 할당
active (완료)등록됨존재 (up/down)존재전체 할당
inactive (해제 중)제거 중제거 중제거 중해제 중
SF 리소스 제한: SF마다 최소 1개의 MSI-X 인터럽트와 큐 세트가 필요합니다. PF의 총 MSI-X 수는 고정되어 있으므로, 생성 가능한 SF 수에는 물리적 한계가 있습니다. cat /sys/bus/pci/devices/0000:03:00.0/msi_irqs | wc -l로 현재 사용 중인 MSI-X 수를 확인하세요. SF 생성 실패 시 가장 먼저 MSI-X 여유분을 확인해야 합니다.
/* SF용 port_new/port_del 콜백 구현 패턴 */
static int my_port_new(struct devlink *dl,
                       const struct devlink_port_new_attrs *attrs,
                       struct netlink_ext_ack *extack,
                       unsigned int *new_port_index)
{
    struct my_priv *priv = devlink_priv(dl);
    struct my_sf *sf;
    int err;

    /* flavour 확인 */
    if (attrs->flavour != DEVLINK_PORT_FLAVOUR_PCI_SF) {
        NL_SET_ERR_MSG_MOD(extack, "Only PCI SF supported");
        return -EOPNOTSUPP;
    }

    /* sfnum 중복 검사 */
    if (my_find_sf(priv, attrs->sfnum)) {
        NL_SET_ERR_MSG_MOD(extack, "SF number already in use");
        return -EEXIST;
    }

    /* HW context 할당 */
    sf = my_sf_alloc(priv, attrs->pfnum, attrs->sfnum);
    if (IS_ERR(sf))
        return PTR_ERR(sf);

    /* devlink 포트 등록 */
    err = devl_port_register(dl, &sf->dl_port, sf->port_index);
    if (err) {
        my_sf_free(sf);
        return err;
    }

    *new_port_index = sf->port_index;
    return 0;
}

static int my_port_del(struct devlink *dl,
                       struct devlink_port *port,
                       struct netlink_ext_ack *extack)
{
    struct my_sf *sf = container_of(port, struct my_sf, dl_port);

    /* active 상태면 먼저 비활성화 필요 */
    if (sf->active) {
        NL_SET_ERR_MSG_MOD(extack, "Deactivate SF first");
        return -EBUSY;
    }

    devl_port_unregister(port);
    my_sf_free(sf);
    return 0;
}

devlink-trap 그룹과 필터링 심화

대규모 네트워크에서 trap 이벤트는 초당 수만 건 이상 발생할 수 있습니다. 효과적인 trap 관리를 위해서는 그룹 단위 정책 설정, policer 계층 구성, 그리고 메타데이터 기반 필터링이 필수입니다. 이 섹션에서는 실전에서 활용하는 trap 필터링 패턴과 경보 자동화 전략을 다룹니다.

trap 그룹 정책과 policer 계층 패킷 입력 HW 파서 예외 코드 분류 l2_drops (P1) l3_drops (P2) l3_exceptions (P3) acl_drops (P4) policer 1: 1000/128 policer 2: 500/64 policer 3: 2000/256 policer 4: 100/32 CPU 제어면 trap 필터링 실전 전략 긴급 경보 (즉시 대응) l2_drops 급증 (루프 의심), acl_drops 폭증 (정책 오류), buffer_drops tail_drop (혼잡) 경향 경보 (추세 분석) l3_drops unknown_unicast 완만 증가, checksum_error 증가 추세 -> 케이블/HW 점검 정보성 (로그 수집) l3_exceptions mtu/ttl (정상 동작), stp/ospf/bgp (프로토콜 패킷) policer 설정 기준 - 프로토콜 trap (stp/ospf/bgp): 낮은 rate (100-500 pps) -- 정상 시 소량 - 예외 trap (ttl/mtu): 중간 rate (1000-5000 pps) -- 라우팅 환경에서 자연 발생 - 드롭 trap (l2_drops/acl_drops): 높은 burst (256+) -- 순간 폭주 흡수 필요
# trap 그룹별 policer 일괄 설정 자동화 스크립트
#!/usr/bin/env bash
DEV="pci/0000:03:00.0"

# 그룹별 policer 정책 정의
declare -A POLICIES=(
    ["l2_drops"]="1:1000:128"
    ["l3_drops"]="2:500:64"
    ["l3_exceptions"]="3:2000:256"
    ["acl_drops"]="4:100:32"
    ["buffer_drops"]="5:500:128"
    ["stp"]="6:100:16"
    ["ospf"]="7:200:32"
    ["bgp"]="8:200:32"
)

# policer 설정 적용
for group in "${!POLICIES[@]}"; do
    IFS=':' read -r id rate burst <<< "${POLICIES[$group]}"
    echo "[INFO] 그룹 $group: policer $id rate=$rate burst=$burst"
    devlink trap policer set "$DEV" policer "$id" \
        rate "$rate" burst "$burst" 2>/dev/null || true
    devlink trap group set "$DEV" group "$group" \
        policer "$id" 2>/dev/null || true
done

# 적용 확인
devlink trap group show "$DEV"
# trap 변화율 기반 경보 시스템 (Python)
import json, subprocess, time

class TrapMonitor:
    def __init__(self, dev, interval=10):
        self.dev = dev
        self.interval = interval
        self.prev_stats = {}

    def get_trap_stats(self):
        result = subprocess.run(
            ["devlink", "-j", "-s", "trap", "show", self.dev],
            capture_output=True, text=True
        )
        if result.returncode != 0:
            return {}
        data = json.loads(result.stdout)
        stats = {}
        for trap_info in data.get("trap", {}).values():
            for t in (trap_info if isinstance(trap_info, list) else [trap_info]):
                name = t.get("name", "unknown")
                pkts = t.get("stats", {}).get("rx", {}).get("packets", 0)
                drops = t.get("stats", {}).get("drops", {}).get("packets", 0)
                stats[name] = {"rx_pkts": pkts, "drop_pkts": drops}
        return stats

    def check_alerts(self):
        curr = self.get_trap_stats()
        alerts = []
        for name, s in curr.items():
            prev = self.prev_stats.get(name, {})
            delta_drops = s["drop_pkts"] - prev.get("drop_pkts", 0)
            rate = delta_drops / self.interval
            if rate > 100:  # 초당 100 drops 이상
                alerts.append(f"[ALERT] {name}: {rate:.0f} drops/s")
        self.prev_stats = curr
        return alerts

monitor = TrapMonitor("pci/0000:03:00.0")
while True:
    for alert in monitor.check_alerts():
        print(alert)
    time.sleep(10)
메타데이터 활용: trap 이벤트에는 input_port, flow_action_cookie 등의 메타데이터가 포함됩니다. 이 메타데이터를 로그에 함께 기록하면, 어떤 포트/규칙에서 예외가 발생했는지 빠르게 추적할 수 있습니다. devlink trap group set ... action trap으로 그룹 전체를 CPU로 전달하면 tcpdump로 패킷 내용까지 확인할 수 있지만, 반드시 policer와 함께 사용하세요.

헬스 리포터 자동 복구 상세

health reporter의 자동 복구(auto_recover)는 강력한 기능이지만, 잘못 설정하면 복구 루프에 빠져 장치가 불안정해질 수 있습니다. grace_period, recover 횟수 제한, 단계별 에스컬레이션 전략을 올바르게 설계해야 합니다.

자동 복구 에스컬레이션 전략 오류 감지 devlink_health_report() L0: auto_dump 진단 정보 자동 수집 L1: auto_recover recover 콜백 실행 성공 -> healthy 복귀 실패 -> grace 확인 Grace Period와 복구 루프 방지 grace_period는 연속적인 자동 복구 시도 사이의 최소 대기 시간입니다. 이 기간 내에 재오류가 발생하면, 자동 복구를 중단하고 운영자 개입을 대기합니다. 시나리오별 grace_period 권장값 TX/RX queue stall grace: 10000ms (10초) -- 큐 리셋은 빠르므로 짧은 grace. 3회 연속 실패 시 함수 리셋 에스컬레이션. FW command timeout grace: 60000ms (1분) -- FW 리셋 후 재초기화에 시간 소요. 2회 실패 시 장치 격리 권고. FW fatal crash grace: 0ms (한 번만 시도) -- 전체 리셋이므로 반복 시도는 위험. 실패 시 운영자 필수 개입. PCI AER grace: 120000ms (2분) -- PCIe 레벨 오류는 하드웨어 문제일 가능성이 높음. 장기 모니터링 필요. 모니터링 지표 - error_count / recover_count 비율: 1에 가까우면 정상 복구, 크게 벌어지면 반복 장애 - health_state 전환 빈도: 분당 2회 이상 healthy/error 전환이면 불안정 상태 - 마지막 recover 시각과 현재 시각 차이: grace_period보다 짧으면 복구 루프 의심
# 자동 복구 모니터링과 에스컬레이션

# reporter별 auto_recover/auto_dump 설정
devlink health set pci/0000:03:00.0 reporter fw \
    auto_recover true auto_dump true grace_period 60000

devlink health set pci/0000:03:00.0 reporter fw_fatal \
    auto_recover true auto_dump true grace_period 0

devlink health set pci/0000:03:00.0 reporter tx \
    auto_recover true auto_dump false grace_period 10000

# 복구 이력 확인
devlink health show pci/0000:03:00.0
# reporter fw:
#   state healthy error 3 recover 3 grace_period 60000
#   auto_recover true auto_dump true

# 복구 루프 감지 스크립트
ERRORS=$(devlink -j health show pci/0000:03:00.0 | \
    jq '[.[][] | select(.name == "fw") | .error] | add')
RECOVERS=$(devlink -j health show pci/0000:03:00.0 | \
    jq '[.[][] | select(.name == "fw") | .recover] | add')

if [ "$ERRORS" -gt "$((RECOVERS + 2))" ]; then
    echo "[ALERT] 복구 실패 누적: errors=$ERRORS, recovers=$RECOVERS"
    echo "[ACTION] auto_recover 비활성화 및 운영자 에스컬레이션"
    devlink health set pci/0000:03:00.0 reporter fw auto_recover false
fi
reporter권장 grace_periodauto_recoverauto_dump에스컬레이션 기준
tx10,000mstruefalse3회 연속 실패 시 함수 리셋
rx10,000mstruefalse3회 연속 실패 시 함수 리셋
fw60,000mstruetrue2회 실패 시 장치 격리
fw_fatal0mstruetrue1회 실패 시 운영자 호출
pci120,000mstruetrue1회 발생 시 HW 점검 예약
vnic30,000msfalsetrue진단 전용 (복구 없음)
복구 루프 경고: auto_recover가 활성화된 상태에서 하드웨어 결함이 원인이면, 복구 시도가 반복되면서 장치가 계속 불안정해집니다. error_count - recover_count > 2인 경우 자동으로 auto_recover false를 설정하고 운영자에게 알리는 자동화를 구축하세요. 동시에 devlink health dump show로 최신 덤프를 보존해 벤더에 전달합니다.

플래시 업데이트 프레임워크 심화

대규모 데이터센터에서 수천 대의 NIC 펌웨어를 안전하게 업데이트하려면, 단순한 devlink dev flash 명령 실행을 넘어 체계적인 프레임워크가 필요합니다. 이 섹션에서는 component 기반 부분 업데이트, 진행률 모니터링, 롤백 전략, 그리고 대규모 배포 자동화 패턴을 다룹니다.

component설명업데이트 시 영향활성화 방법
fw.mgmt관리 펌웨어 (주 FW)장치 기능 전체에 영향reload fw_activate 또는 재부팅
fw.undiPXE 부트 ROMPXE 부팅에만 영향재부팅 시 자동 적용
fw.app응용 펌웨어 (DPU 등)가속 기능에 영향component별 상이
fw.bundle_id전체 FW 번들모든 component 일괄reload 또는 재부팅
# 대규모 flash update 안전 프레임워크
#!/usr/bin/env bash
set -euo pipefail

DEVICES=(
    "pci/0000:03:00.0"
    "pci/0000:03:00.1"
    "pci/0000:05:00.0"
)
FW_FILE="$1"
EXPECTED_VERSION="$2"
LOG_DIR="/var/log/devlink-flash-$(date +%Y%m%d)"
mkdir -p "$LOG_DIR"

# 사전 검증
pre_check() {
    local dev="$1"
    local errors

    # health 상태 확인
    errors=$(devlink -j health show "$dev" | \
        jq '[.[][] | .error // 0] | add')
    if [ "$errors" -gt 0 ]; then
        echo "[FAIL] $dev: health errors=$errors"
        return 1
    fi

    # FW 이미지 무결성 검증
    if ! md5sum -c "${FW_FILE}.md5" 2>/dev/null; then
        echo "[FAIL] FW 이미지 무결성 검증 실패"
        return 1
    fi

    echo "[PASS] $dev: 사전 검증 통과"
    return 0
}

# flash 실행 (진행률 모니터링 포함)
do_flash() {
    local dev="$1"

    # 모니터링 백그라운드 프로세스
    devlink monitor > "$LOG_DIR/${dev//\//_}_monitor.log" 2>&1 &
    local mon_pid=$!

    echo "[INFO] $dev: flash 시작"
    if ! devlink dev flash "$dev" file "$FW_FILE" 2>"$LOG_DIR/${dev//\//_}_error.log"; then
        echo "[FAIL] $dev: flash 실패"
        kill $mon_pid 2>/dev/null || true
        return 1
    fi

    kill $mon_pid 2>/dev/null || true
    echo "[INFO] $dev: flash 완료"
}

# 사후 검증
post_check() {
    local dev="$1"
    local stored_ver

    stored_ver=$(devlink -j dev info "$dev" | \
        jq -r '.[][][].versions.stored."fw.mgmt" // "N/A"')

    if [ "$stored_ver" != "$EXPECTED_VERSION" ]; then
        echo "[FAIL] $dev: stored=$stored_ver, expected=$EXPECTED_VERSION"
        return 1
    fi

    echo "[PASS] $dev: stored 버전 확인 완료 ($stored_ver)"
}

# 메인: 순차 처리
failed=()
for dev in "${DEVICES[@]}"; do
    echo "====== $dev ======"

    if ! pre_check "$dev"; then
        failed+=("$dev")
        echo "[SKIP] $dev: 사전 검증 실패로 건너뜀"
        continue
    fi

    if ! do_flash "$dev"; then
        failed+=("$dev")
        echo "[ABORT] 실패 발생. 잔여 장치 작업 중단."
        break
    fi

    post_check "$dev" || failed+=("$dev")
    sleep 5  # 장치 간 안정화 대기
done

echo "====== 결과 ======"
echo "실패: ${#failed[@]}개 (${failed[*]:-없음})"
dual-bank NVM 보호: 대부분의 최신 SmartNIC은 dual-bank NVM을 사용합니다. flash update 중에는 비활성 bank에 새 이미지를 기록하고, 활성화 시점에 bank를 전환합니다. 이 덕분에 flash 도중 전원이 차단되어도 기존 이미지로 부팅할 수 있습니다. 다만 저가형 NIC은 single-bank일 수 있으므로, devlink dev infoboard.id로 모델을 확인하세요.

devlink-rate 트래픽 제어 심화

devlink-rate는 VF/SF별 하드웨어 레벨 대역폭 제어를 계층적으로 구성할 수 있는 메커니즘입니다. tc qdisc와 달리 NIC 하드웨어의 전송 스케줄러에 직접 대역폭 제한을 설정하므로, CPU 오버헤드 없이 정확한 대역폭 격리가 가능합니다.

속성설명단위적용 대상
tx_share보장 대역폭 (최소)bit/snode, leaf
tx_max최대 대역폭 (상한)bit/snode, leaf
tx_priority우선순위 (높을수록 우선)정수node, leaf (일부 드라이버)
tx_weight가중치 (비례 배분)정수node, leaf (일부 드라이버)
parent부모 노드 이름문자열node, leaf
# rate 트리 구성 실전 예제: 3-tier 테넌트 격리

DEV="pci/0000:03:00.0"

# 1단계: 루트 노드 (물리 링크 대역폭 = 100G)
# 루트 노드는 암시적으로 존재 (설정 불필요)

# 2단계: 테넌트 그룹 노드 생성
devlink port function rate add ${DEV}/premium
devlink port function rate set ${DEV}/premium \
    tx_share 50gbit tx_max 80gbit

devlink port function rate add ${DEV}/standard
devlink port function rate set ${DEV}/standard \
    tx_share 30gbit tx_max 60gbit

devlink port function rate add ${DEV}/best-effort
devlink port function rate set ${DEV}/best-effort \
    tx_share 10gbit tx_max 40gbit

# 3단계: VF/SF를 테넌트 그룹에 배치
# premium 테넌트의 VF들
devlink port function rate set ${DEV}/65536 \
    tx_share 10gbit tx_max 25gbit parent premium
devlink port function rate set ${DEV}/65537 \
    tx_share 10gbit tx_max 25gbit parent premium

# standard 테넌트의 SF들
devlink port function rate set ${DEV}/32768 \
    tx_share 5gbit tx_max 15gbit parent standard
devlink port function rate set ${DEV}/32769 \
    tx_share 5gbit tx_max 15gbit parent standard

# best-effort 테넌트
devlink port function rate set ${DEV}/65540 \
    tx_share 2gbit tx_max 10gbit parent best-effort

# 4단계: rate 트리 확인
devlink port function rate show ${DEV}
# 출력:
# pci/0000:03:00.0/premium: type node tx_share 50gbit tx_max 80gbit
# pci/0000:03:00.0/standard: type node tx_share 30gbit tx_max 60gbit
# pci/0000:03:00.0/best-effort: type node tx_share 10gbit tx_max 40gbit
# pci/0000:03:00.0/65536: type leaf tx_share 10gbit tx_max 25gbit parent premium
# ...

# 5단계: rate 노드 삭제 (리프 먼저 해제)
devlink port function rate set ${DEV}/65540 noparent
devlink port function rate del ${DEV}/best-effort
tx_share 합산 주의: 자식 노드들의 tx_share 합산이 부모 노드의 tx_max를 초과하지 않도록 설계해야 합니다. 초과하면 보장 대역폭을 모두 충족할 수 없는 상황이 발생합니다. 또한 tx_share는 트래픽이 있을 때만 의미가 있으며, 유휴 대역폭은 다른 노드에 자동으로 재분배됩니다.

실제 드라이버 구현 예제 (mlx5, ice)

mlx5와 ice는 가장 많이 사용되는 devlink 지원 드라이버입니다. 두 드라이버의 구현 패턴을 비교하면 devlink 콜백을 어떻게 구현해야 하는지 실무적인 감을 잡을 수 있습니다.

mlx5 devlink 초기화 흐름

/* drivers/net/ethernet/mellanox/mlx5/core/devlink.c */
/* mlx5 devlink ops 정의 (주요 콜백) */
static const struct devlink_ops mlx5_devlink_ops = {
    .supported_flash_update_params =
        DEVLINK_SUPPORT_FLASH_UPDATE_COMPONENT |
        DEVLINK_SUPPORT_FLASH_UPDATE_OVERWRITE_MASK,
    .reload_actions    = BIT(DEVLINK_RELOAD_ACTION_DRIVER_REINIT) |
                         BIT(DEVLINK_RELOAD_ACTION_FW_ACTIVATE),
    .reload_limits     = BIT(DEVLINK_RELOAD_LIMIT_NO_RESET),
    .reload_down       = mlx5_devlink_reload_down,
    .reload_up         = mlx5_devlink_reload_up,
    .flash_update      = mlx5_devlink_flash_update,
    .info_get          = mlx5_devlink_info_get,
    .trap_init         = mlx5_devlink_trap_init,
    .trap_fini         = mlx5_devlink_trap_fini,
    .trap_action_set   = mlx5_devlink_trap_action_set,
    .port_new          = mlx5_devlink_sf_port_new,
    .port_del          = mlx5_devlink_sf_port_del,
    .port_fn_hw_addr_get = mlx5_devlink_port_fn_hw_addr_get,
    .port_fn_hw_addr_set = mlx5_devlink_port_fn_hw_addr_set,
    .port_fn_state_get = mlx5_devlink_sf_port_fn_state_get,
    .port_fn_state_set = mlx5_devlink_sf_port_fn_state_set,
    .rate_leaf_tx_share_set = mlx5_esw_devlink_rate_leaf_tx_share_set,
    .rate_leaf_tx_max_set   = mlx5_esw_devlink_rate_leaf_tx_max_set,
    .rate_node_tx_share_set = mlx5_esw_devlink_rate_node_tx_share_set,
    .rate_node_tx_max_set   = mlx5_esw_devlink_rate_node_tx_max_set,
    .rate_node_new          = mlx5_esw_devlink_rate_node_new,
    .rate_node_del          = mlx5_esw_devlink_rate_node_del,
};

/* mlx5 probe에서 devlink 초기화 */
static int mlx5_init_one(struct mlx5_core_dev *dev)
{
    struct devlink *devlink = priv_to_devlink(dev);

    /* param 등록 */
    mlx5_devlink_params_register(devlink);

    /* health reporter 생성 */
    mlx5_fw_reporter_create(dev);       /* fw reporter */
    mlx5_fw_fatal_reporter_create(dev);  /* fw_fatal reporter */

    /* eSwitch 설정 (switchdev 지원) */
    mlx5_eswitch_init(dev);

    /* devlink 등록 (Netlink 노출) */
    devlink_register(devlink);

    return 0;
}

ice devlink 초기화 흐름

/* drivers/net/ethernet/intel/ice/ice_devlink.c */
/* ice devlink ops 정의 */
static const struct devlink_ops ice_devlink_ops = {
    .supported_flash_update_params =
        DEVLINK_SUPPORT_FLASH_UPDATE_OVERWRITE_MASK,
    .reload_actions    = BIT(DEVLINK_RELOAD_ACTION_DRIVER_REINIT),
    .reload_down       = ice_devlink_reload_down,
    .reload_up         = ice_devlink_reload_up,
    .flash_update      = ice_devlink_flash_update,
    .info_get          = ice_devlink_info_get,
    .port_split        = ice_devlink_port_split,
    .port_unsplit      = ice_devlink_port_unsplit,
    .eswitch_mode_get  = ice_eswitch_mode_get,
    .eswitch_mode_set  = ice_eswitch_mode_set,
};

/* ice에서의 region 등록 예 */
static void ice_devlink_init_regions(struct ice_pf *pf)
{
    struct devlink *devlink = priv_to_devlink(pf);

    /* NVM flash 영역 */
    pf->nvm_region = devl_region_create(devlink,
        &ice_nvm_region_ops, 1, pf->hw.flash.size);

    /* Shadow RAM 영역 */
    pf->sram_region = devl_region_create(devlink,
        &ice_sram_region_ops, 1, pf->hw.flash.sr_size);

    /* Device Capabilities 영역 */
    pf->devcaps_region = devl_region_create(devlink,
        &ice_devcaps_region_ops, 10,
        ICE_AQ_MAX_BUF_LEN);
}
드라이버 개발 시작점: 새 NIC 드라이버에 devlink을 추가할 때, 먼저 info_getflash_update를 구현하세요. 이 두 콜백만으로도 자산 관리와 펌웨어 갱신이라는 핵심 가치를 제공할 수 있습니다. 이후 필요에 따라 health reporter, trap, rate 등을 점진적으로 추가하면 됩니다.

devlink 리로드 메커니즘 심화

devlink reload은 장치를 재부팅하지 않고 드라이버를 재초기화하거나 새 펌웨어를 활성화하는 메커니즘입니다. action과 limit 조합에 따라 영향 범위가 크게 달라지므로, 각 조합의 동작을 정확히 이해해야 합니다.

actionlimit데이터면 중단주요 용도지원 드라이버
driver_reinit없음전체 중단driverinit param 적용, resource 재분배mlx5, ice, bnxt
driver_reinitno_reset최소 중단리셋 없이 드라이버 재초기화mlx5 (제한적)
fw_activate없음전체 중단flash 후 새 FW 적용mlx5
fw_activateno_reset최소 중단리셋 없이 FW 활성화 (지원 시)mlx5 (일부 FW)
# reload 안전 운영 절차

# 1. 지원 범위 확인 (드라이버 capabilities)
devlink dev info pci/0000:03:00.0
# reload_actions: driver_reinit fw_activate
# reload_limits: no_reset

# 2. driverinit param 변경 (reload 전)
devlink dev param set pci/0000:03:00.0 \
    name enable_roce value false cmode driverinit

# 3. 사전 상태 저장
devlink -j port show pci/0000:03:00.0 > /tmp/ports_before.json
devlink -j health show pci/0000:03:00.0 > /tmp/health_before.json

# 4. reload 실행
devlink dev reload pci/0000:03:00.0 action driver_reinit

# 5. 사후 검증
devlink -j port show pci/0000:03:00.0 > /tmp/ports_after.json
diff <(jq '.port | keys' /tmp/ports_before.json) \
     <(jq '.port | keys' /tmp/ports_after.json)

# 6. param 적용 확인
devlink dev param show pci/0000:03:00.0 name enable_roce
# cmode driverinit value false (현재 적용됨)

# no_reset limit 사용 (데이터면 중단 최소화)
devlink dev reload pci/0000:03:00.0 \
    action fw_activate limit no_reset 2>/dev/null || \
    devlink dev reload pci/0000:03:00.0 action fw_activate
# no_reset 실패 시 일반 reload로 폴백
reload 중 경합 주의: reload 실행 중에는 devl_lock이 잡혀 있으므로, 다른 devlink 명령(port show, health show 등)이 블로킹됩니다. 모니터링 스크립트가 reload 중에 devlink 명령을 호출하면 타임아웃이 발생할 수 있습니다. reload 전에 모니터링 간격을 늘리거나, 타임아웃 처리를 추가하세요.

linecard 관리 심화

linecard는 모듈형 섀시 스위치에서 교체 가능한 네트워크 모듈을 관리하는 인터페이스입니다. Mellanox Spectrum 시리즈 스위치(mlxsw 드라이버)에서 주로 사용되며, 라인카드의 핫 플러그, 타입 프로비저닝, 포트 매핑을 제어합니다.

상태설명포트 노출트래픽 전달
unprovisioned슬롯 비어있거나 타입 미설정없음불가
provisioning타입 설정 적용 중준비 중불가
provisioned타입 설정 완료, 카드 미삽입있음 (비활성)불가
active물리 카드 삽입됨, 동작 중있음 (활성)가능
provisioning_failed프로비저닝 오류없음불가
# linecard 운영 시나리오

# 1. 지원 라인카드 타입 확인
devlink lc show pci/0000:03:00.0
# pci/0000:03:00.0:
#   lc 1 state unprovisioned
#     supported_types: 16x100G 32x50G 4x400G 8x200G

# 2. 사전 프로비저닝 (카드 삽입 전 포트 설정 준비)
devlink lc set pci/0000:03:00.0 lc 1 type 16x100G

# 3. 프로비저닝 상태 확인
devlink lc show pci/0000:03:00.0 lc 1
# lc 1 state provisioned type 16x100G

# 4. 포트 확인 (프로비저닝 후 포트가 미리 노출됨)
devlink port show pci/0000:03:00.0 | grep "lc 1"

# 5. 물리 카드 삽입 후 -> 자동으로 active 전환
devlink monitor  # lc state 변경 이벤트 수신

# 6. 프로비저닝 해제
devlink lc set pci/0000:03:00.0 lc 1 notype

# 7. 실패 시 복구
# provisioning_failed 상태면 notype 후 재설정
devlink lc set pci/0000:03:00.0 lc 1 notype
devlink lc set pci/0000:03:00.0 lc 1 type 16x100G
사전 프로비저닝의 가치: 물리 카드를 삽입하기 전에 타입을 프로비저닝하면, 자동화 시스템이 미리 포트 설정(IP, VLAN, 라우팅)을 준비할 수 있습니다. 카드가 삽입되어 active 상태로 전환되면 즉시 트래픽을 전달할 수 있으므로, 네트워크 확장 시 다운타임을 최소화할 수 있습니다.

성능 모니터링과 진단

devlink는 제어면 인프라이지만, 적절한 모니터링 구성을 통해 데이터면의 성능 문제를 간접적으로 탐지할 수 있습니다. trap 통계, health 지표, shared buffer 점유율, resource 사용량을 종합하면 네트워크 장비의 전반적인 건강 상태를 파악할 수 있습니다.

devlink 성능 모니터링 아키텍처 trap 통계 drop/exception 카운터 health 상태 error/recover 카운트 SB 점유율 pool occupancy resource 사용 FDB/ACL/TCAM port 상태 flavour/netdev devlink JSON exporter 주기적 수집 (10~30초) + 이벤트 기반 (monitor) Prometheus / VictoriaMetrics Grafana 대시보드 Alertmanager 경보 핵심 경보 기준: trap drops/s > 100 | health errors 증가 | SB occupancy > 80% | resource usage > 90% 모든 지표는 절대값보다 변화율(delta/interval) 기반 경보가 오탐 감소에 효과적
# Prometheus exporter 형태의 devlink 메트릭 수집기
import json, subprocess, time, sys

class DevlinkMetrics:
    """devlink 메트릭 수집기 (Prometheus textfile collector 호환)"""

    def __init__(self, dev):
        self.dev = dev
        self.metrics = []

    def _cmd(self, *args):
        try:
            r = subprocess.run(
                ["devlink", "-j"] + list(args),
                capture_output=True, text=True, timeout=5
            )
            return json.loads(r.stdout) if r.returncode == 0 else {}
        except:
            return {}

    def collect_health(self):
        data = self._cmd("health", "show", self.dev)
        for items in data.get("health", {}).values():
            for r in (items if isinstance(items, list) else [items]):
                name = r.get("name", "unknown")
                self.metrics.append(
                    f'devlink_health_errors{{dev="{self.dev}",reporter="{name}"}} {r.get("error", 0)}')
                self.metrics.append(
                    f'devlink_health_recovers{{dev="{self.dev}",reporter="{name}"}} {r.get("recover", 0)}')

    def collect_trap_stats(self):
        data = self._cmd("-s", "trap", "show", self.dev)
        for items in data.get("trap", {}).values():
            for t in (items if isinstance(items, list) else [items]):
                name = t.get("name", "unknown")
                drops = t.get("stats", {}).get("drops", {}).get("packets", 0)
                self.metrics.append(
                    f'devlink_trap_drops_total{{dev="{self.dev}",trap="{name}"}} {drops}')

    def collect_port_count(self):
        data = self._cmd("port", "show", self.dev)
        count = len(data.get("port", {}))
        self.metrics.append(
            f'devlink_port_count{{dev="{self.dev}"}} {count}')

    def output(self, path="/var/lib/prometheus/devlink.prom"):
        self.metrics = []
        self.collect_health()
        self.collect_trap_stats()
        self.collect_port_count()
        with open(path, "w") as f:
            f.write("\n".join(self.metrics) + "\n")

# 30초 간격 수집
m = DevlinkMetrics("pci/0000:03:00.0")
while True:
    m.output()
    time.sleep(30)
메트릭레이블경보 기준조치
devlink_health_errorsdev, reporterdelta > 0 (5분간)reporter별 진단/복구
devlink_trap_drops_totaldev, traprate > 100/strap 원인 분석, policer 조정
devlink_sb_occupancy_pctdev, pool> 80%pool 크기 증가 또는 트래픽 분산
devlink_resource_usage_pctdev, resource> 90%resource 재분배 (reload 필요)
devlink_port_countdev예상값과 불일치포트 토폴로지 검증
Grafana 대시보드 설계 팁: devlink 메트릭 대시보드는 다음 패널을 기본으로 구성하세요: (1) health reporter 상태 테이블 (색상 코딩), (2) trap drops/s 시계열 그래프, (3) SB pool occupancy 게이지, (4) resource 사용률 바 차트, (5) 최근 health/trap 이벤트 로그. 각 패널에 경보 규칙을 연결하면 이벤트 발생 시 즉시 대시보드에서 원인을 파악할 수 있습니다.

참고자료