PXE (Preboot Execution Environment)
PXE(Preboot Execution Environment)는 네트워크를 통해 운영체제를 부팅하는 업계 표준 기술입니다. 이 문서에서는 PXE의 역사와 스펙 구조부터 DHCP/BOOTP 프로토콜 상세, TFTP/HTTP 파일 전송, PXE ROM과 UNDI 드라이버, NBP(Network Bootstrap Program) 체인로딩, iPXE 고급 스크립팅, UEFI PXE/HTTP Boot, PXELINUX/GRUB 네트워크 부트로더(Bootloader), iSCSI/NFS 루트 파일시스템(Filesystem), 대규모 프로비저닝(Cobbler/MAAS/Foreman/FOG), Secure PXE Boot, 리눅스 커널 네트워크 부팅 파라미터, 디스크리스(diskless) 부팅 구성, 그리고 트러블슈팅까지 네트워크 부팅의 모든 것을 심층 분석합니다.
핵심 요약
- PXE — Intel이 1999년에 제정한 네트워크 부팅 표준. 펌웨어(BIOS/UEFI)에 내장된 PXE ROM이 DHCP+TFTP로 부트 파일을 받아 실행합니다.
- DHCP Option 66/67 — 실무에서 많이 쓰는 TFTP 서버/부트 파일 전달 수단입니다. 다만 장애 분석 시에는
siaddr,file, Option 66/67, ProxyDHCP 응답을 함께 봐야 합니다. - NBP — Network Bootstrap Program. PXELINUX, GRUB, iPXE 등 네트워크를 통해 전달되는 1차 부트로더입니다.
- iPXE — 오픈소스 PXE 구현. HTTP/HTTPS, iSCSI, Wi-Fi, 스크립팅 등 레거시 PXE의 한계를 넘는 기능을 제공합니다.
- UEFI HTTP Boot — UEFI가 정의한 현대적 네트워크 부팅 경로입니다. TFTP 대신 HTTP/HTTPS URI로 EFI 이미지를 전달합니다.
단계별 이해
- 펌웨어가 NIC를 깨운다
BIOS는 PXE Option ROM과 UNDI를, UEFI는 SNP/MNP와 PXE Base Code 또는 HTTP Boot 스택을 사용해 네트워크를 초기화합니다. - DHCP로 "어디서 무엇을 받을지" 묻습니다
클라이언트는 IP 주소만 받는 것이 아니라, 부트 파일 이름, 다음 서버, 아키텍처 코드, 벤더 클래스까지 함께 협상합니다. - 첫 번째 로더(Loader)인 NBP를 가져옵니다
레거시 PXE는 주로 TFTP로pxelinux.0같은 NBP를 가져오고, UEFI HTTP Boot는 HTTP URI로shimx64.efi나grubx64.efi를 받습니다. - NBP가 커널과 initrd를 로드합니다
PXELINUX, GRUB, iPXE가 커널 파라미터를 조합하고, 필요하면 HTTP, iSCSI, NFS 같은 더 고급 프로토콜로 2차 리소스를 가져옵니다. - 진짜 난이도는 rootfs부터 시작됩니다
네트워크 부팅 성공은 1차 로더 전달로 끝나지 않습니다. initramfs 안의 NIC 드라이버, NFS/iSCSI 재연결, Secure Boot 신뢰 체인(Chain of Trust), 자동 설치 스크립트가 최종 성공을 좌우합니다.
실무 선택 가이드
현장에서 "PXE"라는 말을 넓게 쓰는 경우가 많지만, 실제로는 Legacy PXE, UEFI PXE, UEFI HTTP Boot, iPXE 체인로딩이 서로 다른 설계 선택지입니다. 무엇을 고를지는 펌웨어 모드, Secure Boot 요구사항, 이미지 크기, 운영 자동화 수준, 루트 파일시스템 방식에 따라 달라집니다.
| 환경 | 우선 선택 | 이유 | 주의점 |
|---|---|---|---|
| 구형 서버, BIOS 전용 | PXELINUX 또는 undionly.kpxe | 가장 폭넓은 호환성. 기존 PXE ROM이 바로 이해 가능 | TFTP 성능이 낮고, Secure Boot 개념이 없음 |
| BIOS지만 대형 이미지, 동적 메뉴, iSCSI 필요 | iPXE 체인로딩 | 첫 단계만 TFTP, 이후는 HTTP/HTTPS/iSCSI로 전환 가능 | DHCP에서 iPXE 무한 루프를 반드시 차단해야 함 |
| UEFI + 표준 네트워크 부팅만 필요 | UEFI PXE | 펌웨어 기본 기능만으로 구현 가능 | 여전히 TFTP 중심이라 대규모 동시 부팅에는 비효율적 |
| UEFI + 큰 EFI 이미지 + 빠른 전송 | UEFI HTTP Boot | Boot URI를 직접 전달하므로 웹 서버 인프라 활용이 쉬움 | 펌웨어별 HTTP/HTTPS/TLS 구현 편차가 큼 |
| UEFI + Secure Boot 필수 | shim + GRUB 또는 서명된 iPXE | 서명 검증(Signature Verification) 체인과 배포판 기본 신뢰 체계를 재사용 가능 | 서명되지 않은 ipxe.efi는 기본적으로 거부될 수 있음 |
| 호스트별 분기, API 연동, 메타데이터 기반 부팅 | iPXE 스크립트 | MAC/UUID/Serial/Platform 기반 동적 정책 구현에 가장 유연 | DHCP보다 HTTP API 계층의 가용성과 인증이 더 중요해짐 |
| 진짜 디스크리스 운영 | PXE/iPXE + NFS root 또는 iSCSI SAN boot | 로컬 디스크 없이 운영체제 유지 가능 | 부팅보다 initramfs, 드라이버 내장, rootfs 재연결이 더 어렵다 |
| BIOS/UEFI 혼재 환경 | Option 93 분기 + iPXE 혼합 | 하나의 DHCP 정책에서 아키텍처별 NBP를 정교하게 나눌 수 있음 | IANA 아키텍처 코드와 HTTP Boot 코드를 정확히 관리해야 함 |
PXE 역사와 스펙 진화
역사적 타임라인
| 연도 | 기술 | 설명 |
|---|---|---|
| 1981 | BOOTP (RFC 951) | 최초의 네트워크 부팅 프로토콜. UDP 67/68 포트 사용. 정적 매핑(Mapping) 기반 |
| 1985 | RARP (RFC 903) | Reverse ARP. MAC→IP 역변환. 간단하지만 라우터 통과 불가 |
| 1993 | DHCP (RFC 1531) | BOOTP 확장. 동적 IP 할당, 옵션 확장 가능 |
| 1997 | DHCP Options (RFC 2132) | Option 66(TFTP server), 67(bootfile) 등 부팅 관련 옵션 표준화 |
| 1999 | PXE 2.1 (Intel) | Intel이 제정한 업계 표준. UNDI API, PXE Base Code, ProxyDHCP 정의 |
| 2006 | gPXE | Etherboot 후속. HTTP, iSCSI, DNS 지원 추가 |
| 2010 | iPXE | gPXE 포크. HTTPS, Wi-Fi, 스크립팅, SAN boot 등 현대적 기능 |
| 2015 | UEFI HTTP Boot | UEFI 2.5 스펙에 HTTP(S) 부팅 공식 포함 |
| 2020+ | UEFI HTTPS Boot + Redfish | HTTPS 기반 부팅과 BMC/Redfish 자동화가 결합된 운영 패턴 확산 |
PXE 2.1 스펙 구조
Intel PXE 스펙 2.1은 다음 컴포넌트로 구성됩니다:
| 컴포넌트 | 역할 | 상세 |
|---|---|---|
| PXE Base Code | 핵심 프로토콜 엔진 | DHCP/TFTP/UDP/IP 스택. BIOS에서는 16비트 리얼모드 API |
| UNDI | Universal Network Driver Interface | NIC 하드웨어 추상화. PXE API가 NIC를 제어하는 표준 인터페이스 |
| PXE Boot Server | 부팅 서버 프로토콜 | Boot Server Discovery Protocol. 여러 부팅 서버 중 선택 |
| ProxyDHCP | DHCP 보조 서버 | 기존 DHCP 변경 없이 PXE 옵션만 추가 전달 |
| BIS | Boot Integrity Services | 부팅 파일 무결성(Integrity) 검증 (실무에서는 거의 미사용) |
PXE 부팅 전체 흐름
4단계 부팅 과정
PXE 부팅은 크게 4단계로 진행됩니다: (1) DHCP 디스커버리 → (2) NBP 다운로드 → (3) 커널/initrd 로드 → (4) OS 부팅
상세 단계별 분석
Step 1: 전원 투입 → PXE ROM 실행
- BIOS POST 완료 후 부팅 장치 순회
- NIC의 Option ROM(PXE ROM) 발견 → 시그니처
0x55AA확인 - PXE ROM이 메모리에 로드되어
INT 19h부팅 벡터 설정 - PXE Base Code 초기화: UNDI 드라이버 로드 → NIC 활성화
Step 2: DHCP 4-Way Handshake (DORA)
Client Server
| |
|--- DHCPDISCOVER (broadcast) --| src: 0.0.0.0:68 → 255.255.255.255:67
| Option 60: PXEClient:Arch | Option 93: Client System Architecture
| Option 94: Client NDI | Option 97: Client Machine Identifier (UUID)
| |
|------ DHCPOFFER --------------| 서버 → 클라이언트 (unicast 또는 broadcast)
| yiaddr: 192.168.1.100 | 할당할 IP 주소
| siaddr: 192.168.1.1 | TFTP 서버 주소 (next-server)
| file: "pxelinux.0" | 부트 파일명
| |
|--- DHCPREQUEST ---------------| 선택한 서버에 IP 요청 확인
| |
|------ DHCPACK ----------------| IP 할당 확정 + 모든 옵션 전달
| |
Step 3: NBP 다운로드 및 실행
- PXE Base Code가 TFTP 클라이언트를 실행
siaddr(next-server)에서file(filename) 다운로드- NBP를 메모리
0x7C00(리얼모드) 또는 지정 주소에 로드 - PXE API 테이블 포인터를 레지스터(Register)에 설정한 후 NBP로 점프
Step 4: NBP가 커널 로드
- NBP(PXELINUX 등)가 PXE API를 통해 설정 파일 요청
- 설정 파일에 지정된 커널(
vmlinuz)과 initrd 다운로드 - 커널 커맨드 라인 설정 (예:
root=/dev/nfs nfsroot=...) - Protected Mode 전환 후 커널 엔트리 포인트로 점프
DHCP/BOOTP 프로토콜
DHCP 패킷(Packet) 구조
DHCP는 BOOTP(RFC 951)를 기반으로 확장된 프로토콜(RFC 2131)입니다. PXE 부팅에서는 각 요청과 응답을 구분하기 위해 트랜잭션(Transaction) ID를 함께 사용합니다. 아래는 PXE 부팅에서 핵심적인 패킷 구조입니다.
/* DHCP/BOOTP 패킷 구조 (RFC 2131) */
struct dhcp_packet {
uint8_t op; /* 1=BOOTREQUEST, 2=BOOTREPLY */
uint8_t htype; /* 하드웨어 타입: 1=Ethernet */
uint8_t hlen; /* 하드웨어 주소 길이: 6 (MAC) */
uint8_t hops; /* 릴레이 에이전트 홉 수 */
uint32_t xid; /* 트랜잭션 ID */
uint16_t secs; /* 클라이언트 시작 후 경과 시간 */
uint16_t flags; /* bit0=Broadcast flag */
uint32_t ciaddr; /* Client IP (이미 할당된 경우) */
uint32_t yiaddr; /* Your IP (서버가 할당하는 IP) */
uint32_t siaddr; /* Server IP (next-server = TFTP 서버) */
uint32_t giaddr; /* Gateway IP (릴레이 에이전트) */
uint8_t chaddr[16]; /* Client hardware address (MAC) */
char sname[64]; /* Server host name (옵션) */
char file[128]; /* Boot file name (NBP 경로) */
uint8_t options[]; /* DHCP 옵션 (magic cookie 0x63825363 시작) */
}; /* 최소 크기: 300 bytes, 일반적으로 576 bytes */
PXE 관련 핵심 DHCP 옵션
| 옵션 번호 | 이름 | 용도 | 예시 값 |
|---|---|---|---|
| 60 | Vendor Class Identifier | PXE 클라이언트 식별 | "PXEClient:Arch:00000:UNDI:002001" |
| 66 | TFTP Server Name | TFTP 서버 호스트명/IP | "192.168.1.1" |
| 67 | Bootfile Name | NBP 파일 경로 | "pxelinux.0" 또는 "grubx64.efi" |
| 93 | Client System Architecture | 클라이언트 아키텍처 | 0x0000(x86 BIOS), 0x0007(x64 UEFI), 0x000B(ARM64 UEFI) |
| 94 | Client Network Interface | UNDI 버전 정보 | UNDI:003:010 (v3.10) |
| 97 | Client Machine Identifier | UUID/GUID | SMBIOS에서 가져온 시스템 UUID |
| 128-135 | PXE Vendor Options | 벤더별 확장 | 멀티캐스트 부팅, 자격 증명 등 |
| 175 | iPXE Encapsulated | iPXE 전용 옵션 | iPXE 스크립트 URL 등 |
| 209 | PXELINUX Config | 설정 파일 경로 | "pxelinux.cfg/default" |
| 210 | PXELINUX Pathprefix | TFTP 경로 접두사 | "/tftpboot/linux/" |
DHCP 필드 우선순위(Priority)와 해석
실무에서 가장 많이 헷갈리는 부분은 "부트 파일 정보가 정확히 어디에 들어가느냐"입니다. RFC 2132 관점에서 Option 66/67은 sname/file 필드를 다른 용도로 오버로드했을 때의 보조 수단이지만, 실제 PXE/UEFI 펌웨어는 BOOTP 헤더와 DHCP 옵션을 함께 해석합니다.
| 위치 | 일반적 의미 | 실무 포인트 |
|---|---|---|
siaddr | next-server, 부트 서버 주소 | 레거시 PXE에서 가장 먼저 확인할 필드 중 하나입니다. |
file 헤더 필드 | NBP 이름 또는 Boot URI | UEFI HTTP Boot에서는 여기 자체에 http://... URI가 들어갈 수 있습니다. |
| Option 66 | TFTP Server Name | sname를 다른 목적으로 쓸 때 보조적으로 전달됩니다. 일부 서버는 관행적으로 항상 같이 보냅니다. |
| Option 67 | Bootfile Name | file 필드를 대체하거나 보완합니다. HTTP Boot에서는 URI를 담는 구현도 흔합니다. |
| Option 60 | Vendor Class | PXEClient와 HTTPClient를 구분해 BIOS/UEFI/HTTP Boot 정책을 나눕니다. |
| ProxyDHCP 응답 | PXE 전용 부가 정보 | 동일한 DORA 흐름처럼 보이지만, 실제로는 DHCP 서버 응답과 합쳐서 해석됩니다. |
siaddr, file, Option 66/67, ProxyDHCP(4011) 응답을 모두 캡처해서 비교해야 정확합니다.
아키텍처 자동 감지와 부트파일 분기
DHCP Option 93을 사용하여 클라이언트 아키텍처를 감지하고, 적절한 부트 파일을 제공합니다. 아래 값은 현장에서 자주 보게 되는 대표값이며, 최종 권위는 RFC 4578의 초기 표보다 IANA Processor Architecture Types 레지스트리에 있습니다.
| Option 93 값 | 아키텍처 | 권장 부트파일 |
|---|---|---|
0x0000 (0) | x86 BIOS (IA-32) | pxelinux.0 또는 undionly.kpxe |
0x0006 (6) | x86 UEFI (32-bit) | syslinux.efi32 또는 grubia32.efi |
0x0007 (7) | x64 UEFI (64-bit) | grubx64.efi 또는 shimx64.efi |
0x0009 (9) | EFI Byte Code (EBC) | ipxe.efi 또는 EBC 지원 EFI 애플리케이션 |
0x000A (10) | ARM 32-bit UEFI | grubarm.efi |
0x000B (11) | ARM64 UEFI (AArch64) | grubaa64.efi |
0x000F (15) | x86 UEFI HTTP Boot | http://server/boot/bootia32.efi |
0x0010 (16) | x64 UEFI HTTP Boot | http://server/boot/shimx64.efi |
0x0013 (19) | ARM64 UEFI HTTP Boot | http://server/boot/shimaa64.efi |
0x0019 (25) | RISC-V 32-bit UEFI | grubriscv32.efi |
0x001B (27) | RISC-V 64-bit UEFI | grubriscv64.efi |
Option 93=0x0010 같은 HTTP Boot 코드와 함께 Vendor Class가 HTTPClient인지도 확인해야, TFTP용 .efi와 HTTP URI를 잘못 섞는 사고를 막을 수 있습니다.
ISC DHCP 서버 PXE 설정 예제
# /etc/dhcp/dhcpd.conf - PXE 부팅을 위한 ISC DHCP 설정
# 전역 옵션
option arch-type code 93 = unsigned integer 16;
subnet 192.168.1.0 netmask 255.255.255.0 {
range 192.168.1.100 192.168.1.200;
option routers 192.168.1.1;
option domain-name-servers 8.8.8.8;
next-server 192.168.1.10; # TFTP 서버 주소
# 아키텍처별 부트파일 분기
if option arch-type = 00:07 {
# x64 UEFI 클라이언트
filename "grubx64.efi";
} elsif option arch-type = 00:0B {
# ARM64 UEFI 클라이언트
filename "grubaa64.efi";
} elsif option arch-type = 00:00 {
# Legacy BIOS 클라이언트
filename "pxelinux.0";
}
# 특정 MAC 주소에 고정 IP 할당
host server01 {
hardware ethernet 00:11:22:33:44:55;
fixed-address 192.168.1.50;
filename "pxelinux.0";
}
}
dnsmasq PXE 설정 예제
# /etc/dnsmasq.conf - dnsmasq PXE 부팅 설정 (DHCP + TFTP 통합)
# DHCP 범위
dhcp-range=192.168.1.100,192.168.1.200,255.255.255.0,12h
# 내장 TFTP 서버 활성화
enable-tftp
tftp-root=/var/lib/tftpboot
# PXE 아키텍처별 부트파일 분기
# tag:x86PC = BIOS (arch 0)
# tag:EFI_x64 = UEFI x64 (arch 7)
# tag:EFI_AA64 = UEFI ARM64 (arch 11)
dhcp-match=set:x86PC,option:client-arch,0
dhcp-match=set:EFI_x64,option:client-arch,7
dhcp-match=set:EFI_AA64,option:client-arch,11
dhcp-boot=tag:x86PC,pxelinux.0
dhcp-boot=tag:EFI_x64,grubx64.efi
dhcp-boot=tag:EFI_AA64,grubaa64.efi
# ProxyDHCP 모드 (기존 DHCP 서버가 있을 때)
# dhcp-range=192.168.1.0,proxy
# pxe-service=x86PC,"PXE Boot",pxelinux
# pxe-service=X86-64_EFI,"PXE Boot UEFI",grubx64.efi
# iPXE 체인로딩: 일반 PXE는 iPXE를 로드, iPXE는 스크립트를 로드
dhcp-match=set:ipxe,175 # iPXE sends option 175
dhcp-boot=tag:!ipxe,undionly.kpxe
dhcp-boot=tag:ipxe,http://192.168.1.10/boot.ipxe
# DHCP 로깅
log-dhcp
ProxyDHCP 메커니즘
ProxyDHCP 핵심 포인트:
- 기존 DHCP 서버의 IP 할당을 방해하지 않음
- ProxyDHCP는 yiaddr=0.0.0.0으로 응답 (IP를 할당하지 않음)
- PXE 클라이언트는 두 응답을 모두 수신하여 병합
- ProxyDHCP는 port 67 (broadcast) 또는 port 4011 (unicast) 사용
- dnsmasq의 dhcp-range=...,proxy 옵션으로 간단히 구성 가능
DHCP 옵션 바이너리 인코딩
DHCP 옵션은 TLV(Type-Length-Value) 형식으로 인코딩됩니다. PXE 관련 옵션의 실제 바이트 구조를 이해하면 패킷 분석과 트러블슈팅에 도움됩니다.
BOOTP vs DHCP vs PXE 패킷 비교
세 프로토콜은 동일한 패킷 구조를 공유하지만, 사용하는 필드와 옵션이 다릅니다:
| 필드/기능 | BOOTP (RFC 951) | DHCP (RFC 2131) | PXE (Intel 2.1) |
|---|---|---|---|
| 패킷 구조 | 300 bytes 고정 | 576+ bytes (옵션 확장) | DHCP + 벤더 확장 |
op 필드 | 1(req)/2(reply) | 동일 | 동일 |
siaddr | 부팅 서버 IP | next-server | TFTP 서버 (Option 66 우선) |
file (128B) | 부트 파일명 | 부트 파일명 | NBP 경로 (Option 67 우선) |
| IP 할당 | 정적 매핑만 | 동적 풀 + 정적 | DHCP 방식 사용 |
| 옵션 영역 | 64 bytes (vendor) | 가변 (magic cookie 이후) | DHCP + PXE 전용 옵션 |
| Magic Cookie | 없음 | 0x63825363 | 동일 |
| 메시지 타입 | 없음 | Option 53 | 동일 |
| 아키텍처 감지 | 불가 | 불가 (표준) | Option 93 (arch type) |
| UUID 전달 | 불가 | 불가 (표준) | Option 97 (UUID) |
| ProxyDHCP | 불가 | 불가 | port 4011 유니캐스트 |
| 재시도 | 단순 타임아웃 | 지수 백오프 | DHCP 방식 + PXE 타임아웃 |
/* BOOTP와 DHCP의 옵션 영역 차이 */
/* BOOTP: 64바이트 vendor-specific 영역 (RFC 951) */
struct bootp_packet {
/* ... 공통 필드 ... */
uint8_t vend[64]; /* vendor-specific area */
};
/* 매우 제한적: 서브넷 마스크, 게이트웨이 정도만 전달 가능 */
/* DHCP: 가변 길이 옵션 영역 (RFC 2131) */
struct dhcp_packet {
/* ... 공통 필드 (BOOTP와 동일) ... */
uint8_t options[]; /* 가변 길이, magic cookie 0x63825363로 시작 */
};
/* Magic Cookie 다음에 TLV 형식 옵션이 이어짐:
* [Type:1B][Length:1B][Value:N bytes] ...
* 마지막에 0xFF(End) 옵션
*
* PXE 확장:
* - Option 60: "PXEClient:Arch:NNNNN:UNDI:MMMMMM"
* - Option 93: 2바이트 아키텍처 코드 (0=BIOS, 7=x64UEFI, 11=ARM64)
* - Option 94: UNDI 버전 (major.minor)
* - Option 97: 1바이트 타입 + 16바이트 UUID (SMBIOS)
* - Option 175: iPXE 캡슐화 옵션
*/
TFTP 프로토콜
TFTP 개요
TFTP(Trivial File Transfer Protocol, RFC 1350)는 PXE의 기본 파일 전송 프로토콜입니다. UDP 기반으로 매우 단순하지만, 그만큼 한계가 있습니다.
| 특성 | TFTP | FTP | HTTP |
|---|---|---|---|
| 전송 계층 | UDP (포트 69) | TCP (포트 20/21) | TCP (포트 80/443) |
| 인증 | 없음 | 사용자/비밀번호 | 다양 (Basic, Token, ...) |
| 블록 크기 | 512B (기본), 최대 65464B | 가변 | 가변 |
| 최대 파일 크기 | ~32MB (512B블록), ~4GB (확장) | 무제한 | 무제한 |
| 디렉토리 목록 | 불가 | 가능 | 가능 |
| 보안 | 없음 | TLS (FTPS) | TLS (HTTPS) |
| PXE 지원 | 모든 PXE ROM | 미지원 | iPXE, UEFI HTTP Boot |
TFTP 패킷 타입
/* TFTP 패킷 타입 (RFC 1350) */
#define TFTP_RRQ 1 /* Read Request - 파일 읽기 요청 */
#define TFTP_WRQ 2 /* Write Request - 파일 쓰기 요청 (PXE에서 미사용) */
#define TFTP_DATA 3 /* Data - 데이터 블록 전송 */
#define TFTP_ACK 4 /* Acknowledgment - 데이터 수신 확인 */
#define TFTP_ERROR 5 /* Error - 오류 메시지 */
#define TFTP_OACK 6 /* Option ACK - 옵션 협상 응답 (RFC 2347) */
/* RRQ/WRQ 패킷 구조 */
struct tftp_rrq {
uint16_t opcode; /* 1 (RRQ) */
char filename[]; /* NUL 종료 문자열, 예: "pxelinux.0\0" */
char mode[]; /* "octet\0" (바이너리 모드) */
/* 이하 옵션들 (RFC 2347): "blksize\0" "1468\0" "tsize\0" "0\0" */
};
/* DATA 패킷 구조 */
struct tftp_data {
uint16_t opcode; /* 3 (DATA) */
uint16_t block; /* 블록 번호 (1부터 시작) */
uint8_t data[]; /* 데이터 (최대 blksize 바이트) */
};
/* 마지막 블록: data 길이 < blksize → 전송 완료 */
TFTP 확장 옵션 (RFC 2347-2349)
| 옵션 | RFC | 설명 | 기본값 | 권장값 |
|---|---|---|---|---|
blksize | RFC 2348 | 블록 크기 (8-65464) | 512 | 1468 (MTU 최적화) |
tsize | RFC 2349 | 전송 크기 (바이트) | 미지원 | 요청: 0 → 응답: 실제 크기 |
timeout | RFC 2349 | 재전송(Retransmission) 타임아웃 (초) | 미정의 | 1-5 |
windowsize | RFC 7440 | 윈도우 크기 (블록 수) | 1 | 4-64 (성능 향상) |
blksize=1468과 windowsize=64를 설정하면 전송 효율이 큰 폭으로 개선됩니다. 대규모 환경에서는 HTTP 기반 부팅(iPXE/UEFI HTTP Boot)을 권장합니다.
TFTP 서버 구성
# tftpd-hpa 설치 및 설정 (Debian/Ubuntu)
sudo apt install tftpd-hpa
# /etc/default/tftpd-hpa
TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/var/lib/tftpboot"
TFTP_ADDRESS=":69"
TFTP_OPTIONS="--secure --create --verbose --blocksize 1468"
# 디렉토리 구조
/var/lib/tftpboot/
├── pxelinux.0 # BIOS NBP (SYSLINUX 패키지)
├── lpxelinux.0 # BIOS NBP + HTTP 지원
├── ldlinux.c32 # SYSLINUX 필수 모듈
├── libutil.c32 # SYSLINUX 유틸리티
├── menu.c32 # 텍스트 메뉴 모듈
├── vesamenu.c32 # 그래픽 메뉴 모듈
├── libcom32.c32 # COM32 라이브러리
├── grubx64.efi # UEFI x64 NBP
├── grubaa64.efi # UEFI ARM64 NBP
├── shimx64.efi # Secure Boot shim
├── undionly.kpxe # iPXE chainload 이미지
├── ipxe.efi # iPXE UEFI 이미지
├── pxelinux.cfg/ # PXELINUX 설정 디렉토리
│ ├── default # 기본 설정 파일
│ ├── C0A80164 # IP 기반 (192.168.1.100 = C0A80164)
│ └── 01-00-11-22-33-44-55 # MAC 기반 설정
├── grub/ # GRUB 네트워크 설정
│ └── grub.cfg
├── vmlinuz # Linux 커널
├── initrd.img # initramfs
└── images/ # OS 설치 이미지
├── ubuntu/
├── rhel/
└── debian/
# TFTP 서비스 시작
sudo systemctl enable --now tftpd-hpa
# 방화벽 설정
sudo firewall-cmd --add-service=tftp --permanent
sudo firewall-cmd --reload
TFTP 윈도우 크기별 전송 비교
RFC 7440의 windowsize 옵션은 TFTP 성능에 극적인 영향을 줍니다. 윈도우 크기가 1(기본)이면 매 블록마다 ACK를 기다려야 하지만, 윈도우 크기를 늘리면 여러 블록을 연속 전송할 수 있습니다.
# TFTP 서버별 windowsize 설정
# tftpd-hpa: windowsize 직접 지원 안함 (blksize만 가능)
# → 대안: atftpd 또는 dnsmasq 사용
# atftpd: windowsize 및 멀티캐스트 지원
sudo apt install atftpd
# /etc/default/atftpd
OPTIONS="--daemon --port 69 --tftpd-timeout 300 \
--retry-timeout 5 --maxthread 100 \
--bind-address 0.0.0.0 \
--logfile /var/log/atftpd.log \
--windowsize 64 --blksize 1468 \
/var/lib/tftpboot"
# dnsmasq: TFTP windowsize 지원 (v2.87+)
# /etc/dnsmasq.d/tftp.conf
enable-tftp
tftp-root=/var/lib/tftpboot
# blksize는 지원하지만 windowsize는 클라이언트 협상에 의존
# iPXE 클라이언트의 TFTP 옵션 협상
# iPXE는 자동으로 blksize=1468, windowsize를 협상
# PXE ROM은 보통 blksize=512, windowsize=1만 지원
# 성능 측정 방법
# 서버에서 전송 속도 모니터링
sudo tcpdump -i eth0 -c 100 port 69 -w /tmp/tftp.pcap
# Wireshark에서 Statistics → I/O Graphs로 처리량 확인
UNDI 드라이버와 PXE ROM
UNDI 개요
UNDI(Universal Network Driver Interface)는 PXE 스펙에서 정의한 NIC 하드웨어 추상화 계층입니다. NBP가 특정 NIC 드라이버 없이도 네트워크 I/O를 수행할 수 있게 합니다.
| UNDI API 함수 | 설명 |
|---|---|
PXENV_UNDI_STARTUP | UNDI 드라이버 초기화 |
PXENV_UNDI_CLEANUP | UNDI 드라이버 정리 |
PXENV_UNDI_INITIALIZE | NIC 하드웨어 초기화 |
PXENV_UNDI_OPEN | NIC 열기 (패킷 수신 시작) |
PXENV_UNDI_CLOSE | NIC 닫기 |
PXENV_UNDI_TRANSMIT | 패킷 전송 |
PXENV_UNDI_SET_PACKET_FILTER | 수신 필터 설정 |
PXENV_UNDI_GET_INFORMATION | NIC 정보 조회 (MAC, IRQ, ...) |
PXENV_UNDI_GET_STATISTICS | 전송/수신 통계 |
PXENV_UNDI_ISR | 인터럽트 서비스 루틴(ISR) |
PXENV_UNDI_GET_NIC_TYPE | NIC 타입 (PCI vendor/device ID) |
PXENV_UNDI_GET_IFACE_INFO | 인터페이스 정보 (속도, 이름) |
PXE Option ROM 구조
PCI Option ROM은 장치가 PCI 버스(Bus)에 자신을 노출한 뒤 BIOS가 ROM 헤더를 읽어 초기화 루틴을 호출하는 구조입니다. 이때 각 필드가 ROM 이미지 내부 어느 오프셋(Offset)에 놓이는지 이해하면 실제 덤프와 스펙을 대조하기 쉽습니다.
/* PCI Option ROM 헤더 (BIOS) */
struct option_rom_header {
uint16_t signature; /* 0x55AA - ROM 시그니처 */
uint8_t rom_size; /* ROM 크기 (512바이트 단위) */
uint8_t init_entry[4]; /* 초기화 엔트리 포인트 (JMP 명령) */
uint8_t reserved[17]; /* 예약 */
uint16_t pci_data_ptr; /* PCI Data Structure 오프셋 */
uint16_t pnp_header_ptr;/* PnP Expansion Header 오프셋 */
};
/* PXE ROM 초기화 흐름 (16-bit Real Mode):
*
* 1. BIOS가 PCI 버스를 스캔하여 NIC의 Option ROM BAR 발견
* 2. ROM을 메모리 C000h-DFFFFh 영역에 매핑
* 3. signature 0x55AA 확인 후 init_entry 호출
* 4. PXE ROM이 UNDI 드라이버 설치
* 5. INT 1Ah (PCI BIOS) 연결, INT 18h/19h 부팅 벡터 후킹
* 6. BIOS 부팅 순서에서 네트워크 부팅 선택 시:
* → PXE Base Code 시작 → DHCP → TFTP → NBP 실행
*/
LOM 비트와 PXE ROM 활성화 제어
LOM(LAN-on-Motherboard)은 메인보드에 직접 납땜된 온보드 NIC를 의미합니다. PCIe 슬롯에 장착하는 애드인(Add-in) NIC와 달리, LOM은 메인보드 펌웨어(BIOS/UEFI)와 긴밀하게 연동되어 PXE 부팅 가능 여부를 NIC의 비휘발성 메모리(NVM, Non-Volatile Memory)에 저장된 LOM 비트로 제어합니다. 이 비트가 비활성화되어 있으면 BIOS POST 과정에서 해당 NIC의 Option ROM을 로드하지 않으므로, NIC가 OS에서는 정상 동작하더라도 PXE 네트워크 부팅은 수행할 수 없습니다.
LOM 하드웨어 아키텍처
LOM은 메인보드 설계 단계에서 PCB(Printed Circuit Board)에 직접 실장되는 네트워크 컨트롤러입니다. 데스크톱에서는 단일 GbE 포트가 일반적이고, 서버 메인보드에서는 듀얼(Dual) 또는 쿼드(Quad) 포트 구성이 표준입니다. LOM 컨트롤러는 칩셋의 PCH(Platform Controller Hub)와 내부 DMI 경로로 연결되며, 독립적인 PCIe 레인을 사용하는 애드인 NIC와는 물리적 토폴로지(Topology)가 다릅니다. 구형 플랫폼에서 말하는 사우스브리지의 브리지(Bridge) 역할을 오늘날에는 PCH가 더 많이 맡습니다.
LOM 구현 방식의 변천
LOM의 하드웨어 구현 방식은 PC 아키텍처의 발전에 따라 크게 세 세대로 나뉩니다. 각 세대에서 NVM과 LOM 비트의 저장 위치와 제어 방식이 달라집니다.
| 세대 | 시기 | LOM 구현 방식 | NVM / LOM 비트 위치 | 대표 칩셋·NIC |
|---|---|---|---|---|
| 1세대: 개별 NIC 칩 | ~2008 | 메인보드에 별도 PCI/PCIe NIC 칩을 납땜. 사우스브리지(Southbridge)의 PCI 버스에 연결 | NIC 칩 자체의 EEPROM (93C46/93C56 시리얼 EEPROM). LOM 비트는 EEPROM 내 Boot Config Word에 저장 | Intel 82559, Realtek RTL8111B, Broadcom BCM5751 |
| 2세대: PCH 통합 MAC | 2008~2020 | PCH 내부에 MAC(Media Access Controller) 통합. 외부에는 PHY(Physical Layer) 칩만 별도 실장. PCH ↔ PHY 간 MII/RGMII/SGMII 인터페이스 사용 | PCH의 SPI Flash 내에 LOM 설정 영역이 할당됨. BIOS 펌웨어와 동일한 SPI Flash를 공유하므로 BIOS가 직접 LOM 비트를 읽고 쓸 수 있음 | Intel I217/I218/I219-LM + PCH (6~400 시리즈) |
| 3세대: SoC 완전 통합 | 2020~ | MAC + PHY 모두 SoC(System on Chip)에 통합. 별도 NIC 칩 없이 CPU 다이(Die) 또는 PCH 다이에 네트워크 컨트롤러가 내장됨. 2.5GbE가 표준 | SoC 내장 Flash 또는 통합 NVM 컨트롤러. BIOS 설정이 곧 LOM 비트 제어 (별도 EEPROM 없음) | Intel I225-V/I226-V (Alder Lake/Raptor Lake 내장), AMD Realtek RTL8125B (B650/X670) |
서버 LOM 멀티포트 구성과 포트별 PXE 제어
서버 메인보드에서는 하나의 LOM 컨트롤러에 여러 물리 포트가 연결됩니다. 이 경우 NVM에는 포트별로 독립적인 설정 영역이 할당되며, 각 포트의 LOM 비트를 개별적으로 제어할 수 있습니다. 대규모 데이터센터에서는 일반적으로 Port 0만 PXE 부팅용으로 활성화하고 나머지 포트는 OS 부팅 후 데이터 트래픽용으로 사용합니다.
/* Intel I350 쿼드포트 LOM — NVM 포트별 설정 구조 */
/* NVM 전체 레이아웃 (듀얼/쿼드 포트 NIC) */
struct i350_nvm_layout {
/* 공유 영역 (Shared Area) — 모든 포트 공통 */
uint16_t nvm_signature; /* Word 0x00: NVM 유효성 시그니처 */
uint16_t hw_compat; /* Word 0x03: 하드웨어 호환성 */
uint16_t pcie_init_config; /* Word 0x05: PCIe 초기화 설정 */
/* ... */
/* 포트별 개별 영역 (Per-Port Area) */
/* 각 포트가 독립적인 Boot Configuration Word를 보유 */
struct {
uint16_t boot_config; /* PXE Enable (LOM 비트), 부팅 프로토콜 */
uint16_t lan_config; /* LAN Enable, 속도, 듀플렉스 설정 */
uint16_t mac_addr[3]; /* 포트별 고유 MAC 주소 (48-bit) */
uint16_t subsys_id; /* PCI Subsystem ID */
} port[4]; /* 최대 4포트 */
};
/* 포트별 PXE 활성화 예시 (서버 관리 정책):
*
* Port 0: pxe_enable=1 (PXE 부팅용 — 프로비저닝 네트워크)
* Port 1: pxe_enable=0 (관리 네트워크 — BMC/IPMI 공유)
* Port 2: pxe_enable=0 (프로덕션 데이터 트래픽)
* Port 3: pxe_enable=0 (프로덕션 데이터 트래픽)
*
* BIOS는 PXE 부팅 시도 시 pxe_enable=1인 포트만 순회
* → Port 0에서 DHCPDISCOVER 전송 시작
*/
SPI Flash 내부의 LOM NVM 영역 배치
2세대 이후 LOM에서는 메인보드의 SPI Flash를 BIOS 펌웨어, ME(Management Engine) 펌웨어, GbE(LOM) NVM이 함께 공유합니다. 각 영역은 플래시 디스크립터(Flash Descriptor)에 의해 분할되며, 접근 권한(읽기/쓰기)도 영역별로 독립적으로 관리됩니다.
/* Intel SPI Flash 레이아웃 (PCH 기반 LOM) */
/* Flash Descriptor — 플래시 영역 분할 테이블 */
struct flash_descriptor {
uint32_t flvalsig; /* 0x0FF0A55A — 유효 디스크립터 시그니처 */
struct {
uint32_t base; /* 영역 시작 주소 */
uint32_t limit; /* 영역 끝 주소 */
} region[5];
/* region[0]: Flash Descriptor 자체 (4KB)
* region[1]: BIOS 영역 (~8-32MB)
* region[2]: ME (Management Engine) 영역 (~2-5MB)
* region[3]: GbE 영역 (LOM NVM) — 8KB (2 × 4KB 미러링)
* region[4]: Platform Data 영역 (선택)
*/
uint32_t master_access; /* 영역별 읽기/쓰기 권한 */
};
/* GbE 영역 (region[3]) 내부 구조 — 8KB */
struct gbe_nvm_region {
/* Bank A (4KB) — 주 설정 */
uint16_t mac_address[3]; /* Word 0x00-0x02: MAC 주소 */
uint16_t nvm_version; /* Word 0x05: NVM 이미지 버전 */
uint16_t boot_config; /* Word 0x0F: Boot Configuration Word
* bit 1: PXE Enable (LOM 비트)
* bit 3: LAN Enable */
uint16_t subsys_vendor_id; /* Word 0x0B: PCI Subsystem Vendor ID */
uint16_t subsys_device_id; /* Word 0x0C: PCI Subsystem Device ID */
uint16_t led_config[3]; /* Word 0x1C-0x1E: LED 동작 설정 */
uint16_t checksum; /* Word 0x3F: 영역 체크섬 */
/* Bank B (4KB) — 미러 백업 */
/* Bank A와 동일 구조, 오류 복구용 이중화 */
};
/* SPI Flash 접근 권한 예시:
* BIOS: GbE 영역 읽기/쓰기 가능 → BIOS Setup에서 LOM 비트 변경
* ME: GbE 영역 읽기/쓰기 가능 → 원격 관리(AMT)로 LOM 제어
* Host (OS): 읽기 가능, 쓰기 제한 → ethtool은 읽기만, 쓰기는 도구 필요
*/
NVM Word 0x0F 비트 필드 상세
다음 다이어그램은 Intel NIC의 Boot Configuration Word(NVM Word 0x0F)의 16비트 구조를 시각적으로 보여줍니다. LOM 비트(bit 1)가 PXE 부팅의 핵심 제어 플래그입니다.
NVM 설정 워드(Configuration Word) 구조
Intel NIC(예: I210, I350, X550)의 NVM에는 PXE 부팅 동작을 제어하는 설정 워드가 포함되어 있습니다. 대표적으로 NVM Word 0x0F가 부팅 관련 플래그를 담고 있습니다.
/* Intel NIC NVM Word 0x0F — Boot Configuration Word (예시) */
struct nvm_boot_config_word {
uint16_t reserved0 : 1; /* bit 0: 예약 */
uint16_t pxe_enable : 1; /* bit 1: PXE Boot Enable (LOM 비트)
* 1 = Option ROM 로드 허용 (PXE 부팅 가능)
* 0 = Option ROM 로드 차단 (PXE 부팅 불가) */
uint16_t flash_enable : 1; /* bit 2: Flash 기반 Option ROM 사용 */
uint16_t lan_enable : 1; /* bit 3: LAN 포트 활성화 */
uint16_t reserved4 : 1; /* bit 4: 예약 */
uint16_t apm_wakeup : 1; /* bit 5: APM Wake-Up 활성화 */
uint16_t force_speed : 2; /* bit 7:6: 강제 링크 속도 설정
* 00 = 자동 협상, 01 = 10 Mbps
* 10 = 100 Mbps, 11 = 1000 Mbps */
uint16_t boot_protocol : 2; /* bit 9:8: 부팅 프로토콜 선택
* 00 = PXE, 01 = RPL (사용 중단)
* 10 = iSCSI Boot */
uint16_t legacy_boot : 1; /* bit 10: Legacy PXE Boot 모드 */
uint16_t reserved11 : 5; /* bit 15:11: 예약 */
};
/* NVM Word 0x0F 값 예시:
* 0x0002 → PXE Enable만 활성화 (최소 설정)
* 0x000A → PXE Enable + LAN Enable
* 0x002A → PXE Enable + LAN Enable + APM Wake-Up
* 0x0000 → 모든 부팅 기능 비활성화 (LOM 비트 꺼짐)
*/
PCI 확장 ROM BAR(Expansion ROM BAR)과 LOM 비트 연동
PCI 설정 공간(Configuration Space)의 레지스터 0x30은 확장 ROM 기본 주소 레지스터(Expansion ROM BAR)입니다. BIOS는 POST 과정에서 NVM의 LOM 비트를 확인한 후, 이 레지스터의 활성화 비트(bit 0)를 설정하여 Option ROM의 메모리 매핑 여부를 결정합니다.
/* PCI Configuration Space — Register 0x30: Expansion ROM BAR */
struct pci_expansion_rom_bar {
uint32_t enable : 1; /* bit 0: ROM 디코딩 활성화
* BIOS가 NVM LOM 비트를 확인한 후 설정
* 1 = ROM 주소 공간 활성화 → 0x55AA 시그니처 검사
* 0 = ROM 비활성화 → Option ROM 무시 */
uint32_t reserved : 10; /* bit 10:1: 예약 */
uint32_t base_address : 21; /* bit 31:11: ROM 기본 주소 (2KB 정렬)
* 일반적으로 C0000h-DFFFFh 영역에 매핑 */
};
/* LOM 비트 → Expansion ROM BAR 연동 흐름:
*
* 1. BIOS POST → PCI 버스 열거(Enumeration)
* 2. 온보드 NIC 발견 → NVM에서 Boot Configuration Word 읽기
* 3. LOM 비트(pxe_enable) 확인:
* ├─ 1 (활성화): Expansion ROM BAR.enable = 1
* │ → ROM을 C000h-DFFFFh에 매핑
* │ → 시그니처 0x55AA 확인 후 init_entry 호출
* │ → UNDI 드라이버 설치, INT 18h/19h 후킹
* │
* └─ 0 (비활성화): Expansion ROM BAR.enable = 0
* → Option ROM 스킵 → PXE 부팅 불가
* → NIC는 OS 드라이버로만 동작
*/
BIOS/UEFI 설정에서 LOM PXE 부팅 제어
대부분의 서버 및 데스크톱 BIOS/UEFI는 온보드 NIC의 PXE 부팅 기능을 설정 메뉴에서 제어할 수 있습니다. 이 설정은 내부적으로 NIC NVM의 LOM 비트를 변경하거나, BIOS 자체의 부팅 정책으로 Option ROM 로드를 제어합니다.
| BIOS/펌웨어 벤더 | 설정 경로 | 설정 항목 | 기본값 |
|---|---|---|---|
| AMI BIOS | Advanced → Onboard Devices Configuration | Onboard LAN Boot ROM | Disabled |
| Phoenix/Award | Integrated Peripherals → Onboard LAN | Boot ROM Function | Disabled |
| Dell (iDRAC) | System BIOS → Network Settings | PXE Device1 (Embedded NIC) | Enabled |
| HP (iLO) | System Configuration → BIOS/Platform → Network Boot | Embedded NIC PXE | Enabled |
| Supermicro | Advanced → Network Stack Configuration | Network Stack / PXE Boot | Enabled |
| UEFI 공통 | Boot Options → Network Boot | IPv4/IPv6 PXE Support | 벤더별 상이 |
서버 제조사(Dell, HP, Supermicro 등)는 기본적으로 PXE 부팅이 활성화되어 있는 반면, 데스크톱/소비자용 메인보드는 비활성화가 기본인 경우가 많습니다. BIOS 설정 변경은 대부분 재부팅 후 즉시 반영됩니다.
NVM 직접 읽기 및 수정 도구
OS 환경에서 NIC NVM의 LOM 비트를 직접 확인하거나 수정할 수 있습니다. Linux에서는 ethtool의 EEPROM 덤프(Dump) 기능을, Intel NIC에서는 전용 도구 eeupdate를 사용합니다.
# ethtool로 NVM (EEPROM) 내용 읽기
# Word 0x0F (오프셋 0x1E-0x1F)의 Boot Configuration Word 확인
$ ethtool -e eth0 offset 0x1E length 2
Offset Values
------ ------
0x001e: 0a 00
# 해석: 0x000A = 0000 0000 0000 1010 (binary)
# bit 1 (pxe_enable) = 1 → PXE 부팅 활성화
# bit 3 (lan_enable) = 1 → LAN 포트 활성화
# Intel eeupdate 도구로 NVM 수정 (관리자 권한 필요)
# PXE 부팅 활성화: Word 0x0F의 bit 1을 1로 설정
$ ./eeupdate /NIC=1 /D /MAC # 현재 NIC 정보 확인
$ ./eeupdate /NIC=1 /RW 0x0F # Word 0x0F 읽기
$ ./eeupdate /NIC=1 /WW 0x0F 0x000A # Word 0x0F에 0x000A 쓰기
# Broadcom NIC의 경우 bnxtnvm 또는 B57udiag 도구 사용
# Realtek NIC의 경우 PG Tool (Programmer Tool) 사용
ethtool -e eth0 > nvm_backup.hex)을 수행하고, 제조사 공식 도구와 문서를 참조하십시오. BIOS 설정으로 제어 가능한 경우 NVM 직접 수정보다 BIOS 설정 변경을 우선 권장합니다.
LOM(온보드 NIC) vs 애드인 NIC(Add-in NIC) 비교
| 구분 | LOM (온보드 NIC) | 애드인 NIC (PCIe) |
|---|---|---|
| 물리적 위치 | 메인보드에 납땜 (교체 불가) | PCIe 슬롯에 장착 (교체 가능) |
| NVM 위치 | 메인보드 SPI Flash 또는 NIC 내장 EEPROM | NIC 카드 자체의 EEPROM/Flash |
| PXE ROM 제어 | BIOS 설정 + NVM LOM 비트 | NIC 자체 NVM 설정 |
| BIOS 연동 | BIOS가 직접 LOM 비트를 읽고 제어 | BIOS가 PCI 스캔 시 Option ROM BAR 확인 |
| 부팅 우선순위 | 일반적으로 애드인 NIC보다 낮은 순위 | BIOS 부팅 순서에서 개별 설정 가능 |
| 실무 장점 | 추가 비용 없음, 서버 기본 제공 | 고성능 NIC 선택 가능, 이중화 구성 용이 |
| PXE 부팅 실패 시 | BIOS에서 "Onboard LAN Boot ROM" 확인 | NIC NVM 설정 또는 BIOS 부팅 순서 확인 |
- BIOS 설정에서 "Onboard LAN Boot ROM" 또는 "Network Boot" 항목이 Enabled인지 확인합니다.
- UEFI 모드인 경우 "Network Stack"과 "IPv4/IPv6 PXE Support"가 모두 활성화되어 있는지 확인합니다.
- BIOS 부팅 순서(Boot Order)에 네트워크 부팅 장치가 포함되어 있는지 확인합니다.
- OS에서
ethtool -e eth0 offset 0x1E length 2로 NVM의 PXE Enable 비트를 직접 확인합니다. - 듀얼 NIC 서버에서는 어떤 포트가 PXE 부팅용인지 확인합니다 (일반적으로 Port 0 / NIC1이 기본).
- 링크(Link) LED가 점등되는지 물리 계층 연결 상태를 확인합니다.
커널 드라이버의 NVM/EEPROM 접근 코드 분석
Linux 커널의 NIC 드라이버는 NVM(EEPROM/SPI Flash)에 직접 접근하여 MAC 주소, LOM 비트, 부팅 설정 등을 읽고 씁니다. 사용자 공간(User Space)의 ethtool -e 명령은 최종적으로 커널 드라이버의 struct ethtool_ops 콜백(Callback)을 호출하며, 각 드라이버는 하드웨어에 맞는 NVM 접근 프로토콜을 구현합니다. 또한 칩셋에 따라 NVM 세마포어(Semaphore)와 하드웨어 잠금(Lock) 절차가 다르므로, 동일한 ethtool 명령이라도 내부 구현은 크게 달라집니다. Intel NIC 드라이버 계열(e1000e, igb, ixgbe, ice)을 중심으로 NVM 접근의 커널 내부 구현을 분석합니다.
/* drivers/net/ethernet/intel/e1000e/ethtool.c
* e1000e 드라이버의 EEPROM 읽기 콜백 — ethtool -e 호출 시 실행
*
* ethtool이 ETHTOOL_GEEPROM ioctl을 발행하면 커널의 ethtool 계층이
* 이 함수를 호출합니다. 바이트 오프셋을 NVM 워드 단위로 변환하고,
* 드라이버의 저수준 NVM 읽기 함수를 통해 하드웨어에 접근합니다.
*/
static int e1000_get_eeprom(struct net_device *netdev,
struct ethtool_eeprom *eeprom, u8 *bytes)
{
struct e1000_adapter *adapter = netdev_priv(netdev);
struct e1000_hw *hw = &adapter->hw;
u16 *eeprom_buff;
int first_word, last_word, eeprom_len;
int ret_val = 0;
u16 i;
if (eeprom->len == 0)
return -EINVAL;
eeprom->magic = adapter->pdev->vendor |
(adapter->pdev->device << 16);
/* 바이트 오프셋 → NVM 워드 인덱스 변환
* NVM은 16-bit 워드 단위로 주소 지정됩니다.
* 예: ethtool -e eth0 offset 0x1E → first_word = 0x0F (Word 15)
* 이것이 바로 Boot Configuration Word (LOM 비트 포함) */
first_word = eeprom->offset >> 1;
last_word = (eeprom->offset + eeprom->len - 1) >> 1;
eeprom_len = last_word - first_word + 1;
eeprom_buff = kmalloc_array(eeprom_len, sizeof(u16), GFP_KERNEL);
if (!eeprom_buff)
return -ENOMEM;
/* 실제 NVM 하드웨어 읽기 수행
* e1000_read_nvm()는 hw->nvm.ops.read를 호출하며,
* 이는 칩 종류에 따라 EERD 레지스터 또는 SPI 프로토콜을 사용합니다. */
ret_val = e1000_read_nvm(hw, first_word, eeprom_len, eeprom_buff);
if (ret_val) {
/* NVM 읽기 실패 시 -EIO 반환
* 원인: SPI Flash 손상, PCH 잠금, 하드웨어 오류 */
kfree(eeprom_buff);
return -EIO;
}
/* 16-bit 워드 배열 → 바이트 배열로 변환하여 사용자 공간에 전달
* ethtool은 이 바이트 배열을 16진수 덤프로 출력합니다. */
for (i = 0; i < eeprom_len; i++)
le16_to_cpus(&eeprom_buff[i]);
memcpy(bytes, (u8 *)eeprom_buff + (eeprom->offset & 1), eeprom->len);
kfree(eeprom_buff);
return ret_val;
}
/* drivers/net/ethernet/intel/e1000e/nvm.c
* EERD(EEPROM Read) 레지스터를 통한 NVM 워드 읽기
*
* Intel I217/I218/I219-LM 등 PCH 통합 MAC에서 사용하는 방식입니다.
* CPU가 EERD 레지스터에 읽을 주소를 쓰면, PCH 하드웨어가 SPI Flash의
* GbE 영역에서 데이터를 읽어 EERD에 결과를 채웁니다.
*/
s32 e1000_read_nvm_eerd(struct e1000_hw *hw, u16 offset,
u16 words, u16 *data)
{
struct e1000_nvm_info *nvm = &hw->nvm;
u32 i, eerd = 0;
s32 ret_val = 0;
/* NVM 하드웨어 세마포어 획득
* 동시에 BIOS, ME(Management Engine), OS 드라이버가
* 같은 SPI Flash에 접근할 수 있으므로 하드웨어 수준 잠금이 필요합니다.
* SWSM(Software Semaphore) 레지스터의 SMBI 비트를 사용합니다. */
ret_val = nvm->ops.acquire(hw);
if (ret_val)
return ret_val;
for (i = 0; i < words; i++) {
/* EERD 레지스터에 읽기 요청 설정:
* bit 0: START (읽기 시작)
* bit 15:2: 주소 (NVM 워드 오프셋)
*
* 예: offset=0x0F (Boot Config Word)
* → eerd = (0x0F << E1000_NVM_RW_ADDR_SHIFT) | E1000_NVM_RW_REG_START
* → eerd = 0x0000003C | 0x00000001 = 0x0000003D */
eerd = ((offset + i) << E1000_NVM_RW_ADDR_SHIFT) +
E1000_NVM_RW_REG_START;
E1000_WRITE_REG(hw, E1000_EERD, eerd);
/* DONE 비트 폴링 — PCH가 SPI Flash에서 데이터를 읽을 때까지 대기
* 일반적으로 수 마이크로초 이내에 완료됩니다.
* 타임아웃 발생 시 SPI Flash 하드웨어 오류 가능성이 있습니다. */
ret_val = e1000_poll_eerd_eewr_done(hw, E1000_NVM_POLL_READ);
if (ret_val)
break;
/* EERD 레지스터의 상위 16비트에서 데이터 추출
* bit 31:16: 읽은 NVM 데이터 (16-bit 워드) */
data[i] = (E1000_READ_REG(hw, E1000_EERD) >>
E1000_NVM_RW_REG_DATA) & 0xFFFF;
}
/* NVM 세마포어 해제 — 다른 에이전트(BIOS, ME)가 접근 가능해짐 */
nvm->ops.release(hw);
return ret_val;
}
/* drivers/net/ethernet/intel/igb/igb_ethtool.c
* I350/I210 멀티포트 LOM — 포트별 NVM 영역 오프셋 계산
*
* 서버용 쿼드포트 LOM(I350)에서는 하나의 SPI Flash에
* 4개 포트의 NVM 영역이 연속 배치됩니다.
* 각 포트는 독립적인 Boot Configuration Word를 보유합니다.
*/
static int igb_get_eeprom(struct net_device *netdev,
struct ethtool_eeprom *eeprom, u8 *bytes)
{
struct igb_adapter *adapter = netdev_priv(netdev);
struct e1000_hw *hw = &adapter->hw;
u16 *eeprom_buff;
int first_word, last_word, eeprom_len;
int ret_val = 0;
u16 i;
if (eeprom->len == 0)
return -EINVAL;
eeprom->magic = adapter->pdev->vendor |
(adapter->pdev->device << 16);
first_word = eeprom->offset >> 1;
last_word = (eeprom->offset + eeprom->len - 1) >> 1;
eeprom_len = last_word - first_word + 1;
eeprom_buff = kmalloc_array(eeprom_len, sizeof(u16), GFP_KERNEL);
if (!eeprom_buff)
return -ENOMEM;
/* igb 드라이버의 NVM 읽기
* I350 쿼드포트에서 hw->bus.func가 PCI 함수 번호(0~3)를 나타내며,
* igb_read_nvm() 내부에서 포트별 NVM 오프셋을 자동 계산합니다.
*
* I350 NVM 레이아웃 (포트별 오프셋):
* Port 0: 오프셋 0x000 ~ 0x07F (공유 + 포트 0 전용)
* Port 1: 오프셋 0x080 ~ 0x0FF
* Port 2: 오프셋 0x100 ~ 0x17F
* Port 3: 오프셋 0x180 ~ 0x1FF
*
* 따라서 Port 2의 Boot Config Word(0x0F)는
* 실제 NVM 오프셋 0x10F에 위치합니다. */
if (hw->nvm.type == e1000_nvm_eeprom_spi)
ret_val = igb_read_nvm(hw, first_word, eeprom_len, eeprom_buff);
else {
for (i = 0; i < eeprom_len; i++) {
ret_val = igb_read_nvm(hw, first_word + i, 1,
&eeprom_buff[i]);
if (ret_val)
break;
}
}
if (ret_val) {
kfree(eeprom_buff);
return -EIO;
}
for (i = 0; i < eeprom_len; i++)
le16_to_cpus(&eeprom_buff[i]);
memcpy(bytes, (u8 *)eeprom_buff + (eeprom->offset & 1), eeprom->len);
kfree(eeprom_buff);
return ret_val;
}
/* drivers/net/ethernet/intel/ixgbe/ixgbe_common.c
* 10GbE NIC(X520/X540/X550)의 SPI Flash 비트뱅(Bit-Bang) 프로토콜
*
* 10GbE NIC는 EERD 레지스터 대신 SPI 프로토콜을 직접 구현하여
* Flash 메모리에 접근합니다. GPIO 핀을 통해 SPI 클럭(SCK),
* 데이터 입력(SI), 데이터 출력(SO), 칩 셀렉트(CS)를 제어합니다.
*/
s32 ixgbe_read_eeprom_bit_bang_generic(struct ixgbe_hw *hw, u16 offset,
u16 *data)
{
s32 status;
u16 word_in;
u8 read_opcode = IXGBE_EEPROM_READ_OPCODE_SPI;
hw->eeprom.ops.init_params(hw);
if (offset >= hw->eeprom.word_size) {
status = IXGBE_ERR_EEPROM;
goto out;
}
/* SPI Flash 접근 세마포어 획득 */
status = hw->eeprom.ops.acquire(hw);
if (status)
goto out;
/* SPI 주소 크기에 따라 명령어 결정
* 256-word 이상의 Flash는 확장 주소(A8) 비트가 필요합니다.
* 표준 READ (03h): 24-bit 주소
* 주소 비트 8을 opcode에 OR 처리 */
if (hw->eeprom.word_size > 256)
if (offset >= 256)
read_opcode |= IXGBE_EEPROM_A8_OPCODE_SPI;
/* SPI 비트뱅 시퀀스:
* 1. CS(Chip Select) LOW로 SPI Flash 선택
* 2. READ 명령어(03h) 전송 (8 클럭)
* 3. 주소 전송 (16-bit, MSB first)
* 4. 데이터 수신 (16-bit, MSB first)
* 5. CS HIGH로 전송 완료 */
ixgbe_standby_eeprom(hw);
/* SPI READ 명령 + 주소 전송 */
ixgbe_shift_out_eeprom_bits(hw, read_opcode,
IXGBE_EEPROM_OPCODE_BITS);
ixgbe_shift_out_eeprom_bits(hw, (u16)(offset * 2),
hw->eeprom.address_bits);
/* 16-bit 데이터 수신 — SPI MISO 라인에서 비트 단위로 읽기 */
word_in = ixgbe_shift_in_eeprom_bits(hw, 16);
*data = word_in;
ixgbe_standby_eeprom(hw);
hw->eeprom.ops.release(hw);
out:
return status;
}
| 드라이버 | 대상 NIC | NVM 접근 방식 | 세마포어 | 특징 |
|---|---|---|---|---|
e1000e | I217/I218/I219-LM | EERD 레지스터 | SWSM(Software Semaphore) | PCH 통합 MAC, SPI Flash 공유 |
igb | I350/I210/I211 | EERD + Flash 직접 | SW_FW_SYNC | 멀티포트 NVM 오프셋 자동 계산 |
ixgbe | X520/X540/X550 | SPI 비트뱅(Bit-Bang) | SWSM | GPIO 핀으로 SPI 프로토콜 직접 구현 |
i40e | X710/XL710/XXV710 | Admin Queue 커맨드 | NVM 잠금(AQ) | 펌웨어가 NVM 접근 중재, 직접 접근 불가 |
ice | E810/E830 | Admin Queue 커맨드 | NVM 잠금(AQ) | Shadow RAM 활용, 트랜잭션 기반 쓰기 |
# 현재 NIC가 사용하는 커널 드라이버 확인
$ ethtool -i eth0
driver: e1000e
version: 6.1.0-k
firmware-version: 0.5-4
bus-info: 0000:00:1f.6 # PCH 내장 NIC (BDF 00:1F.6 = 전형적인 LOM)
# PCI 장치별 바인딩된 드라이버 확인
$ lspci -k -s 00:1f.6
00:1f.6 Ethernet controller: Intel Corporation Ethernet Connection (14) I219-LM
Subsystem: Dell Device 0b1a
Kernel driver in use: e1000e
Kernel modules: e1000e
# NVM 워드 크기 확인 (드라이버 로그)
$ dmesg | grep -i "nvm\|eeprom\|flash"
[ 2.145] e1000e 0000:00:1f.6: NVM checksum is valid
[ 2.146] e1000e 0000:00:1f.6: NVM size = 64KB
ethtool NVM 접근의 커널 내부 호출 흐름
사용자 공간에서 ethtool -e(읽기) 또는 ethtool -E(쓰기)를 실행하면, 커널 내부에서는 ioctl(SIOCETHTOOL) → dev_ethtool() → ethtool_get_eeprom()/ethtool_set_eeprom()의 호출 체인을 거칩니다. 특히 쓰기 경로에서는 magic number 검증이 수행되어 실수로 잘못된 NIC의 NVM을 변경하는 것을 방지합니다.
/* include/uapi/linux/ethtool.h
* ethtool EEPROM 접근에 사용되는 구조체
*
* 사용자 공간의 ethtool 유틸리티가 이 구조체를 채워
* ioctl 시스템 호출로 커널에 전달합니다.
*/
struct ethtool_eeprom {
__u32 cmd; /* ETHTOOL_GEEPROM (0x0000000b) = 읽기
* ETHTOOL_SEEPROM (0x0000000c) = 쓰기 */
__u32 magic; /* 읽기 시: 드라이버가 vendor_id | (device_id << 16)으로 채움
* 쓰기 시: 사용자가 동일한 값을 전달해야 함 (검증용)
*
* 예: Intel I219-LM
* vendor_id = 0x8086 (Intel)
* device_id = 0x15BB (I219-LM v9)
* → magic = 0x15BB8086
*
* 잘못된 magic → -EINVAL 반환 (NVM 보호) */
__u32 offset; /* 읽기/쓰기 시작 바이트 오프셋
* 예: Word 0x0F = 바이트 오프셋 0x1E */
__u32 len; /* 읽기/쓰기 바이트 수 */
};
/* ethtool -e eth0 offset 0x1E length 2 실행 시:
* cmd = ETHTOOL_GEEPROM (0x0b)
* offset = 0x1E (30)
* len = 2
*
* ethtool -E eth0 magic 0x15BB8086 offset 0x1E value 0x0A 실행 시:
* cmd = ETHTOOL_SEEPROM (0x0c)
* magic = 0x15BB8086 (사용자 지정)
* offset = 0x1E
* len = 1
*/
/* net/ethtool/ioctl.c (커널 6.x)
* ethtool ioctl 메인 디스패처 — EEPROM 읽기 경로
*
* 사용자 공간의 ioctl(SIOCETHTOOL)이 소켓 계층을 거쳐
* dev_ethtool()에 도달하면, 명령어별로 핸들러를 분기합니다.
*/
int dev_ethtool(struct net *net, struct ifreq *ifr, void __user *useraddr)
{
/* ... 전처리 생략 ... */
u32 ethcmd;
if (copy_from_user(ðcmd, useraddr, sizeof(ethcmd)))
return -EFAULT;
switch (ethcmd) {
/* ... 다른 ethtool 명령들 ... */
case ETHTOOL_GEEPROM:
/* NVM 읽기 — 관리자 권한(CAP_NET_ADMIN) 불필요 */
rc = ethtool_get_eeprom(dev, useraddr);
break;
case ETHTOOL_SEEPROM:
/* NVM 쓰기 — CAP_NET_ADMIN 필수 (root 또는 적절한 capability)
* 일반 사용자는 NVM을 읽을 수는 있지만 쓸 수 없습니다. */
rc = ethtool_set_eeprom(dev, useraddr);
break;
}
return rc;
}
/* ethtool_get_eeprom() — NVM 읽기 핸들러 */
static int ethtool_get_eeprom(struct net_device *dev, void __user *useraddr)
{
struct ethtool_eeprom eeprom;
const struct ethtool_ops *ops = dev->ethtool_ops;
void __user *userbuf = useraddr + sizeof(eeprom);
u8 *data;
int ret;
if (!ops->get_eeprom || !ops->get_eeprom_len)
return -EOPNOTSUPP;
if (copy_from_user(&eeprom, useraddr, sizeof(eeprom)))
return -EFAULT;
/* EEPROM 크기 검증 */
if (eeprom.offset + eeprom.len > ops->get_eeprom_len(dev))
return -EINVAL;
data = kzalloc(eeprom.len, GFP_USER);
if (!data)
return -ENOMEM;
/* 드라이버의 get_eeprom 콜백 호출
* 여기서 e1000_get_eeprom(), igb_get_eeprom() 등이 실행됩니다.
* 드라이버는 magic 필드에 vendor_id | (device_id << 16)을 채웁니다. */
ret = ops->get_eeprom(dev, &eeprom, data);
if (ret)
goto out;
/* 결과를 사용자 공간으로 복사 */
if (copy_to_user(useraddr, &eeprom, sizeof(eeprom))) {
ret = -EFAULT;
goto out;
}
if (copy_to_user(userbuf, data, eeprom.len))
ret = -EFAULT;
out:
kfree(data);
return ret;
}
/* ethtool_set_eeprom() — NVM 쓰기 핸들러 */
static int ethtool_set_eeprom(struct net_device *dev, void __user *useraddr)
{
struct ethtool_eeprom eeprom;
const struct ethtool_ops *ops = dev->ethtool_ops;
void __user *userbuf = useraddr + sizeof(eeprom);
u8 *data;
int ret;
if (!ops->set_eeprom || !ops->get_eeprom_len)
return -EOPNOTSUPP;
if (copy_from_user(&eeprom, useraddr, sizeof(eeprom)))
return -EFAULT;
if (eeprom.offset + eeprom.len > ops->get_eeprom_len(dev))
return -EINVAL;
data = memdup_user(userbuf, eeprom.len);
if (IS_ERR(data))
return PTR_ERR(data);
/* 드라이버의 set_eeprom 콜백 호출
* 드라이버 내부에서 magic number를 검증합니다:
* if (eeprom->magic != (vendor_id | (device_id << 16)))
* return -EINVAL;
*
* 이 검증 덕분에 잘못된 NIC를 대상으로 NVM을 쓰는 것을 방지합니다.
* 예를 들어 I219-LM의 magic(0x15BB8086)으로 I350(0x15218086)의
* NVM을 쓰려고 하면 -EINVAL로 거부됩니다. */
ret = ops->set_eeprom(dev, &eeprom, data);
kfree(data);
return ret;
}
# ethtool NVM 읽기/쓰기 전체 세션 예제
# 1. NIC 정보 확인 — magic number 계산에 필요
$ ethtool -i eth0
driver: e1000e
bus-info: 0000:00:1f.6
$ lspci -n -s 00:1f.6
00:1f.6 0200: 8086:15bb (rev 10)
# vendor=0x8086, device=0x15bb → magic = 0x15BB8086
# 2. NVM 읽기 — Word 0x0F (Boot Configuration Word)
$ sudo ethtool -e eth0 offset 0x1E length 2
Offset Values
------ ------
0x001e: 0a 00
# 해석: Little-Endian → 0x000A = 0000 0000 0000 1010
# bit 1 = 1 → PXE Enable (LOM 비트 ON)
# bit 3 = 1 → LAN Enable
# 3. NVM 전체 백업 (수정 전 필수!)
$ sudo ethtool -e eth0 > /tmp/nvm_backup_eth0.hex
$ wc -l /tmp/nvm_backup_eth0.hex
257 /tmp/nvm_backup_eth0.hex
# 4. NVM 쓰기 — PXE 비활성화 (bit 1을 0으로)
# Word 0x0F = 0x0008 (LAN Enable만, PXE Disable)
# 바이트 오프셋 0x1E에 0x08, 0x1F에 0x00 쓰기
$ sudo ethtool -E eth0 magic 0x15BB8086 offset 0x1E value 0x08
$ sudo ethtool -E eth0 magic 0x15BB8086 offset 0x1F value 0x00
# 5. 쓰기 결과 확인
$ sudo ethtool -e eth0 offset 0x1E length 2
Offset Values
------ ------
0x001e: 08 00
# 0x0008 = PXE Disable, LAN Enable → 다음 재부팅부터 PXE 부팅 불가
# 6. 잘못된 magic으로 쓰기 시도 — 거부됨
$ sudo ethtool -E eth0 magic 0xDEADBEEF offset 0x1E value 0x0A
Cannot set EEPROM data: Invalid argument
# magic 불일치 → 커널 드라이버가 -EINVAL 반환
ethtool -E로 NVM을 쓸 때는 반드시 올바른 magic number를 지정해야 합니다. magic number는 vendor_id | (device_id << 16)으로 계산하며, lspci -n으로 확인할 수 있습니다. 이 보호 메커니즘은 잘못된 NIC의 NVM을 실수로 변경하는 것을 방지하지만, NVM 데이터 자체의 유효성은 검증하지 않습니다. 잘못된 값을 쓰면 NIC가 영구적으로 동작하지 않을 수 있으므로, 수정 전 반드시 전체 NVM을 백업하고 제조사 문서에서 정확한 비트 필드를 확인하십시오.
칩셋별 프리부트 모델 비교
같은 "PXE 부팅"이라도 실제 구현은 NIC 칩셋마다 크게 다릅니다. 어떤 제품은 NIC 자체 flash에 Option ROM을 넣고, 어떤 제품은 시스템 BIOS flash가 대신 보관하며, 어떤 제품은 아예 NIC 펌웨어가 FlexBoot 같은 프리부트 이미지를 직접 관리합니다. 따라서 OS에서 링크가 살아 있습니다는 사실만으로 프리부트 경로까지 정상이라고 판단하면 안 됩니다.
| 칩셋 계열 | 대표 예시 | 프리부트 코드 위치 | 영구 설정 위치 | PXE 실패 시 먼저 볼 지점 |
|---|---|---|---|---|
| Intel 독립형 컨트롤러 | I210, I211, I350 | 애드인 NIC는 로컬 flash, 임베디드/LOM 설계는 system BIOS flash 또는 보드 NVM | NIC NVM 또는 iNVM | 올바른 NVM 이미지, Option ROM 저장 위치, 전원 재인가 필요 여부 |
| Intel PCH/SoC LOM | I219-LM, I225-V, I226-LM | 대개 BIOS/UEFI flash 내부 네트워크 스택 또는 펌웨어 드라이버 | 플랫폼 SPI 영역, BIOS NVRAM, Setup 정책 | Network Stack, IPv4 PXE Support, CSM/UEFI 모드 분기 |
| Broadcom 서버 LOM | BCM5720, BCM57414, BCM57416 | 서버 펌웨어가 읽는 MBA/FlexBoot 이미지 | NVRAM 디렉터리 엔트리, BIOS/BMC 정책 | 포트별 MBA 설정, 프로토콜(PXE/iSCSI/FCoE), BMC Boot Override 충돌 |
| Realtek 데스크톱 NIC | RTL8111, RTL8125 | BIOS Option ROM 또는 UEFI 네트워크 드라이버 | EEPROM 또는 eFuse, BIOS 설정 | eFuse 설계 여부, BIOS Network Boot, Option ROM 활성화 정책 |
| Mellanox/NVIDIA 데이터센터 NIC | ConnectX-5, ConnectX-6, ConnectX-7 | NIC 펌웨어가 관리하는 FlexBoot/UEFI 이미지 | 펌웨어 구성 플래시 | BOOT_OPTION_ROM_EN, PXE/iSCSI/FCoE 모드, 포트별 부트 정책 |
| Marvell/Aquantia 고속 NIC | AQC107, AQC113 | OEM이 번들한 Option ROM 또는 UEFI 드라이버 | 보드별 펌웨어 패키지, OEM 도구 | OS 링크는 정상인데 pre-boot 코드가 미탑재인지, OEM BIOS 업데이트가 필요한지 |
Intel I210/I211 특이사항
I210은 PXE 문서에서 자주 언급되지만, 실제 bring-up과 장애 분석 관점에서는 별도로 떼어 설명하는 편이 정확합니다. 이유는 독립형 GbE 컨트롤러이면서도 pre-boot 저장 위치, NVM 이미지 종류, flashless/iNVM 운용 방식이 보드 설계에 따라 크게 달라지기 때문입니다.
| 항목 | I210 | I211 | 실무 의미 |
|---|---|---|---|
| 프리부트 지원 범위 | PXE, iSCSI Boot, UEFI 경로를 폭넓게 지원합니다. | PXE 중심이며 system BIOS flash와 기본 설정 경로를 전제로 보는 편이 안전합니다. | OS 드라이버가 정상 동작해도 pre-boot 기능 범위는 별도 검증이 필요합니다. |
| Option ROM 저장 위치 | 애드인 NIC는 로컬 flash, LOM/임베디드 설계는 system BIOS flash에 둘 수 있습니다. | 실무에서는 system BIOS flash 경로를 먼저 의심하는 편이 맞습니다. | 카드형 I210과 온보드 설계를 같은 절차로 점검하면 원인을 놓치기 쉽습니다. |
| NVM 이미지 분기 | I210-AT/IT는 copper PHY 계열, I210-IS/AS는 SGMII/SerDes 계열로 보는 편이 안전합니다. | 구성 자체는 단순하지만 기본 설정 제약이 상대적으로 큽니다. | 잘못된 이미지를 쓰면 OS 링크는 살아도 PXE DHCP가 실패하거나 부팅 메뉴가 사라질 수 있습니다. |
| flashless/iNVM 설계 | 가능하지만 수정 자유도가 낮고 보드 bring-up 절차가 민감합니다. | 제약이 더 크며 기본 설정 의존도가 높습니다. | "NIC는 보이는데 PXE만 안 됨" 증상의 대표 원인 후보입니다. |
| 설정 반영 시점 | NVM 또는 프리부트 이미지를 바꾼 뒤 완전 전원 재인가가 필요할 수 있습니다. | BIOS flash 업데이트 절차와 플랫폼 정책 영향이 큽니다. | 단순 reboot만 반복하면 pre-boot 상태가 바뀌지 않는 사례가 있습니다. |
# Intel I210/I211 계열 PXE 점검 순서
# 1. 실제 칩 variant와 드라이버 확인
$ lspci -nn | grep -i ethernet
$ ethtool -i eth0
# 2. Option ROM 가시성 확인
$ lspci -vv -s 03:00.0 | grep -i rom
# 3. Intel igb 계열이면 Boot Configuration Word 확인
$ sudo ethtool -e eth0 offset 0x1E length 2
# 4. 보드 문서와 NVM image 종류 대조
# - I210-AT/IT: copper PHY 계열 image
# - I210-IS/AS: SGMII/SerDes 계열 image
# image가 어긋나면 PXE 링크 또는 DHCP가 비정상일 수 있습니다.
# 5. NVM/ROM 갱신 후에는 reboot만 하지 말고
# AC 전원까지 완전히 끊는 power cycle이 필요한지 확인
$ sudo poweroff
주요 NIC 칩셋의 프리부트 제어 상세
앞서 설명한 NVM 구조와 접근 방식은 주로 Intel NIC를 중심으로 다루었습니다. 그러나 실무 환경에서는 Broadcom(Dell/HPE 서버), Realtek(데스크톱/소비자용 메인보드), Mellanox/NVIDIA(고성능 데이터센터)의 LOM도 널리 사용됩니다. 각 벤더의 NVM 구조와 PXE 부팅 제어 방식은 크게 다르며, 사용하는 커널 드라이버와 관리 도구도 다릅니다.
/* drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c
* Broadcom NetXtreme-C/E (bnxt) — HWRM 펌웨어 커맨드를 통한 NVM 접근
*
* Broadcom NIC는 Intel과 달리 OS 드라이버가 NVM 하드웨어에 직접 접근하지 않습니다.
* 대신 NIC 내장 펌웨어(ChiMP — Chip Management Processor)에
* HWRM(Hardware Resource Manager) 커맨드를 전송하여 NVM 읽기/쓰기를 요청합니다.
*/
static int bnxt_get_nvram(struct net_device *dev,
struct ethtool_eeprom *eeprom, u8 *buf)
{
struct bnxt *bp = netdev_priv(dev);
int rc;
eeprom->magic = bp->pdev->vendor | (bp->pdev->device << 16);
/* HWRM_NVM_READ 커맨드를 펌웨어에 전송
* bp->hwrm_cmd_ring을 통해 ChiMP 프로세서에 명령을 전달하고,
* 펌웨어가 Flash에서 데이터를 읽어 DMA 버퍼에 반환합니다.
*
* HWRM 커맨드 흐름:
* 드라이버 → HWRM Ring → ChiMP CPU → Flash Controller → Flash
* Flash → ChiMP CPU → DMA → Host Memory (buf)
*
* 이 간접 접근 방식의 장점:
* - Flash 잠금/동기화를 펌웨어가 관리 (드라이버 복잡도 감소)
* - Flash 종류(SPI, QSPI, NOR)에 무관하게 동일한 인터페이스
* - 펌웨어 업데이트 중에도 안전한 동시 접근 보장 */
rc = bnxt_hwrm_nvm_read(bp, eeprom->offset, eeprom->len, buf);
return rc;
}
/* Broadcom NVRAM 디렉터리 구조
* Intel의 플랫(flat) 워드 기반 NVM과 달리, Broadcom은 디렉터리 기반입니다.
* 각 NVM 항목은 타입(type)과 인스턴스(instance)로 식별됩니다. */
struct bnxt_nvm_directory_entry {
__le16 type; /* 항목 타입 */
__le16 ordinal; /* 같은 타입 내 순서 */
__le16 ext; /* 확장 타입 */
__le16 attr; /* 속성 (읽기 전용, 숨김 등) */
__le32 data_offset; /* Flash 내 데이터 오프셋 */
__le32 data_length; /* 데이터 길이 (바이트) */
__le32 checksum; /* CRC32 체크섬 */
};
/* PXE 관련 NVRAM 디렉터리 타입:
* 0x09: MBA Config (Multi Boot Agent) — PXE Enable/Disable, 부팅 프로토콜
* 0x0A: MBA Alt Config — 대체 MBA 설정 (포트별)
* 0x22: Boot Config — 부팅 순서, 재시도 횟수
* 0x70: VPD (Vital Product Data) — MAC 주소, 시리얼 번호
*/
/* drivers/net/ethernet/realtek/r8169_main.c
* Realtek RTL8111/RTL8125 — 레지스터 I/O를 통한 EEPROM 접근
*
* Realtek NIC는 Intel의 EERD나 Broadcom의 HWRM과 달리,
* 레지스터에 직접 비트 조작으로 EEPROM을 읽고 씁니다.
* 93C46/93C56 시리얼 EEPROM 또는 eFuse를 사용합니다.
*/
/* EEPROM 레지스터 비트 정의 */
#define RTL_EEPROM_CMD 0x50 /* EEPROM 명령 레지스터 */
#define RTL_93C46_CMD_READ 0x06 /* 93C46 읽기 명령 (3-bit) */
/* EEPROM 읽기 시퀀스 (비트뱅 프로토콜):
* 1. Cfg9346 레지스터에 0xC0 쓰기 → EEPROM 접근 모드 진입
* 2. 시작 비트(1) + 읽기 명령(10) + 주소(6-bit) 전송
* 3. 16-bit 데이터 수신 (MSB first)
* 4. Cfg9346 레지스터에 0x00 쓰기 → 접근 모드 종료 */
static u16 rtl_eeprom_read(struct rtl8169_private *tp, int addr)
{
u16 data = 0;
int i;
/* EEPROM 접근 모드 활성화 */
RTL_W8(tp, Cfg9346, Cfg9346_Unlock);
/* 시작 비트 + READ 명령 전송 */
rtl_eeprom_cmd(tp, RTL_93C46_CMD_READ | (addr & 0x3F));
/* 16-bit 데이터 비트 단위 수신 */
for (i = 15; i >= 0; i--) {
RTL_W8(tp, RTL_EEPROM_CMD, EE_CLK); /* 클럭 HIGH */
udelay(5);
if (RTL_R8(tp, RTL_EEPROM_CMD) & EE_DO)
data |= (1 << i); /* 데이터 비트 읽기 */
RTL_W8(tp, RTL_EEPROM_CMD, 0); /* 클럭 LOW */
udelay(5);
}
/* EEPROM 접근 모드 비활성화 */
RTL_W8(tp, Cfg9346, Cfg9346_Lock);
return data;
}
/* Realtek NIC의 PXE 관련 EEPROM 구조:
* Word 0x00~0x02: MAC 주소 (48-bit)
* Word 0x07: Autoload 설정 — PXE/WoL 관련 비트 포함
* bit 4: PXE Boot Enable (Realtek의 LOM 비트에 해당)
* bit 5: WoL Enable
* Word 0x08: LED 설정
* Word 0x09: 서브시스템 벤더 ID
*
* 참고: RTL8125 (2.5GbE)는 eFuse 기반으로 EEPROM 구조가 다릅니다.
* eFuse는 일회성 프로그래밍(OTP) 메모리이므로 쓰기 후 변경 불가합니다. */
# Broadcom NIC: bnxtnvm 도구로 PXE 설정 읽기/변경
# Dell PowerEdge 서버에서 Broadcom BCM5720 LOM 관리 예시
# NVM 디렉터리 목록 조회
$ sudo bnxtnvm -dev=eth0 -dir
Directory entries:
Type 0x09 (MBA Config) Offset: 0x10000 Length: 128
Type 0x0A (MBA Alt Config) Offset: 0x10100 Length: 64
Type 0x22 (Boot Config) Offset: 0x10200 Length: 256
Type 0x70 (VPD) Offset: 0x20000 Length: 512
# MBA(Multi Boot Agent) PXE 설정 확인
$ sudo bnxtnvm -dev=eth0 -cfg -mba
MBA Configuration:
PXE Boot: Enabled
Protocol: PXE (UNDI)
Speed: Auto-Negotiate
VLAN: None
Boot Retry: 3
# PXE 부팅 비활성화
$ sudo bnxtnvm -dev=eth0 -cfg -mba -pxe=disable
MBA PXE Boot: Disabled (effective after reboot)
# Broadcom B57udiag 도구 (EFI Shell 또는 DOS 환경)
Shell> B57udiag -cmd SetMBAConfig -pxe disable -nic 1
# Realtek NIC: PG Tool (Programmer Tool)로 EEPROM 읽기/변경
# 주의: Realtek PG Tool은 DOS/UEFI Shell 전용이며, Linux에서는 ethtool 사용
# Linux에서 ethtool로 Realtek NVM 읽기
$ ethtool -i eth0
driver: r8169
bus-info: 0000:03:00.0
$ sudo ethtool -e eth0 offset 0x0E length 2
Offset Values
------ ------
0x000e: 30 00
# Word 0x07 = 0x0030
# bit 4 (PXE Enable) = 1 → PXE 부팅 활성화
# bit 5 (WoL Enable) = 1
# Realtek PG Tool (DOS/UEFI Shell):
# PGE.exe: RTL8111용, PGEE.exe: RTL8125(2.5GbE)용
> PGE /r 7 # Word 7 읽기
> PGE /w 7 0010 # Word 7에 0x0010 쓰기 (WoL만 활성화, PXE 비활성화)
# Mellanox/NVIDIA ConnectX: mlxconfig 도구로 PXE 설정 관리
# ConnectX-5/6/7 NIC의 Option ROM 및 부팅 설정
# 현재 부팅 설정 조회
$ sudo mlxconfig -d /dev/mst/mt4125_pciconf0 query | grep -i boot
BOOT_OPTION_ROM_EN_P1 True(1)
BOOT_OPTION_ROM_EN_P2 True(1)
LEGACY_BOOT_PROTOCOL_P1 PXE(1)
LEGACY_BOOT_PROTOCOL_P2 PXE(1)
UEFI_HII_EN True(1)
BOOT_VLAN_EN_P1 False(0)
# Port 1의 PXE Option ROM 비활성화
$ sudo mlxconfig -d /dev/mst/mt4125_pciconf0 set BOOT_OPTION_ROM_EN_P1=False
# 부팅 프로토콜 변경 (PXE → iSCSI)
$ sudo mlxconfig -d /dev/mst/mt4125_pciconf0 set LEGACY_BOOT_PROTOCOL_P1=2
# 0=None, 1=PXE, 2=iSCSI, 3=Both, 4=FCoE
# 설정 적용 확인 (재부팅 필요)
$ sudo mlxconfig -d /dev/mst/mt4125_pciconf0 query | grep BOOT_OPTION
BOOT_OPTION_ROM_EN_P1 False(0)
BOOT_OPTION_ROM_EN_P2 True(1)
| 벤더 | 대표 칩/컨트롤러 | NVM 타입 | 커널 드라이버 | PXE 제어 필드 | 관리 도구 (Linux) | 관리 도구 (EFI/DOS) |
|---|---|---|---|---|---|---|
| Intel | I210, I219-LM, I350, X710 | SPI Flash / EEPROM / iNVM | e1000e, igb, i40e, ice | NVM Word 0x0F bit 1 | ethtool, eeupdate | eeupdate (EFI) |
| Broadcom | BCM5720, BCM57416 | NVRAM (디렉터리 기반) | tg3, bnxt_en | MBA Config (Type 0x09) | bnxtnvm, ethtool | B57udiag, UEFI HII |
| Realtek | RTL8111, RTL8125 | 93C46 EEPROM / eFuse | r8169 | Word 0x07 bit 4 | ethtool | PGE / PGEE (PG Tool) |
| Mellanox/NVIDIA | ConnectX-5/6/7 | Flash (FW 관리) | mlx5_core | BOOT_OPTION_ROM_EN | mlxconfig, mstconfig | FlexBoot (UEFI) |
| Marvell/Aquantia | AQC113, Prestera | Flash (FW 관리) | atlantic, prestera | FW Boot Config | atl_fwupd | Marvell FW Tools |
Realtek RTL8125(2.5GbE) 등 eFuse 기반 NIC에서는 EEPROM과 달리 eFuse가 OTP(One-Time Programmable) 메모리이므로, 한 번 프로그래밍된 PXE 설정을 소프트웨어로 변경할 수 없습니다. 이 경우 BIOS 설정에서 "Network Boot" 옵션으로 PXE를 제어해야 합니다. BIOS는 NIC의 eFuse 값과 무관하게 Option ROM 로드 여부를 결정할 수 있으므로, eFuse에 PXE가 활성화되어 있더라도 BIOS에서 비활성화하면 PXE 부팅이 차단됩니다.
원격 관리(BMC/IPMI/Redfish)를 통한 LOM 제어
서버 환경에서는 물리적으로 BIOS Setup에 접근하지 않고도 BMC(Baseboard Management Controller)를 통해 원격으로 LOM의 PXE 부팅 설정을 변경할 수 있습니다. 대규모 데이터센터에서 수천 대의 서버를 관리할 때 IPMI, 벤더별 BMC 도구(racadm, ilorest), 또는 Redfish API를 사용하여 PXE 부팅을 자동으로 설정하는 것이 표준 운영 방식입니다.
# IPMI를 통한 PXE 부팅 제어
# ipmitool은 IPMI(Intelligent Platform Management Interface) 표준 도구입니다.
# 다음 1회 부팅만 PXE로 설정 (일시적 — 재부팅 후 원래 순서로 복귀)
$ ipmitool -I lanplus -H 192.168.1.100 -U admin -P password \
chassis bootdev pxe
Set Boot Device to pxe
# 영구적으로 PXE 부팅 우선순위 설정 (옵션 persistent)
$ ipmitool -I lanplus -H 192.168.1.100 -U admin -P password \
chassis bootdev pxe options=persistent
Set Boot Device to pxe
# 현재 부팅 장치 설정 확인
$ ipmitool -I lanplus -H 192.168.1.100 -U admin -P password \
chassis bootparam get 5
Boot parameter version: 1
Boot parameter 5 is valid/unlocked
Boot parameter data: 8004000000
Boot Flags :
- Boot Flag Valid
- Options apply to only next boot
- BIOS PC Compatible (different from legacy) boot
- Boot Device Selector : Force PXE
- Console Redirection control : System Default
# IPMI raw 커맨드로 Boot Options Parameter 5 직접 설정
# 바이트 구조: [set selector] [block data...]
# 0x80 = Boot Flag Valid, 0x04 = Force PXE
$ ipmitool -I lanplus -H 192.168.1.100 -U admin -P password \
raw 0x00 0x08 0x05 0x80 0x04 0x00 0x00 0x00
# 부팅 장치 초기화 (BIOS 기본 순서로 복귀)
$ ipmitool -I lanplus -H 192.168.1.100 -U admin -P password \
chassis bootdev none
# Dell iDRAC: racadm을 통한 LOM PXE 설정
# Dell PowerEdge 서버에서 온보드 NIC의 PXE를 포트별로 제어
# 현재 NIC PXE 설정 조회
$ racadm get NIC.NICConfig.1.LegacyBootProto
LegacyBootProto=PXE
$ racadm get NIC.NICConfig.2.LegacyBootProto
LegacyBootProto=NONE
# NIC Port 1의 PXE 부팅 활성화
$ racadm set NIC.NICConfig.1.LegacyBootProto PXE
# NIC Port 2의 PXE 부팅 비활성화
$ racadm set NIC.NICConfig.2.LegacyBootProto NONE
# 옵션: PXE, iSCSI, FCoE, NONE
# BIOS 부팅 순서에 NIC 추가 (UEFI 모드)
$ racadm set BIOS.BiosBootSettings.UefiBootSeq \
"NIC.PxeDevice.1-1, HardDisk.List.1-1"
# 설정 적용을 위한 재부팅 예약
$ racadm jobqueue create BIOS.Setup.1-1 -r pwrcycle -s TIME_NOW
# 원격으로 현재 활성 NIC 부팅 옵션 전체 조회
$ racadm get NIC.NICConfig -t | grep -E "Boot|PXE"
# HPE iLO: ilorest를 통한 LOM PXE 설정
# HPE ProLiant 서버에서 온보드 NIC PXE 제어
# iLO에 로그인
$ ilorest login 192.168.1.101 -u admin -p password
# 현재 네트워크 부팅 설정 조회
$ ilorest get --selector Bios. | grep -i pxe
PXEBootPolicy=Auto
NetworkBootRetry=Enabled
NetworkBootRetryCount=3
# 네트워크 부팅 활성화
$ ilorest set PXEBootPolicy=Auto --selector Bios. --commit
# 1회 PXE 부팅 설정
$ ilorest rawpatch /tmp/pxe_once.json
# pxe_once.json 내용:
# {"Boot": {"BootSourceOverrideTarget": "Pxe",
# "BootSourceOverrideEnabled": "Once"}}
$ ilorest logout
# Redfish API를 통한 LOM PXE 제어 (벤더 무관 표준)
# DMTF Redfish는 IPMI를 대체하는 현대적 서버 관리 API입니다.
# 현재 부팅 설정 조회
$ curl -sk -u admin:password \
https://192.168.1.100/redfish/v1/Systems/1 \
| python3 -m json.tool | grep -A10 '"Boot"'
{
"Boot": {
"BootSourceOverrideEnabled": "Disabled",
"BootSourceOverrideTarget": "None",
"BootSourceOverrideTarget@Redfish.AllowableValues": [
"None", "Pxe", "Cd", "Usb", "Hdd", "BiosSetup",
"UefiTarget", "UefiHttp"
],
"UefiTargetBootSourceOverride": ""
}
}
# 다음 1회 부팅만 PXE로 설정
$ curl -sk -u admin:password -X PATCH \
-H "Content-Type: application/json" \
https://192.168.1.100/redfish/v1/Systems/1 \
-d '{"Boot": {
"BootSourceOverrideEnabled": "Once",
"BootSourceOverrideTarget": "Pxe"
}}'
# 영구적 PXE 부팅 설정
$ curl -sk -u admin:password -X PATCH \
-H "Content-Type: application/json" \
https://192.168.1.100/redfish/v1/Systems/1 \
-d '{"Boot": {
"BootSourceOverrideEnabled": "Continuous",
"BootSourceOverrideTarget": "Pxe"
}}'
# UEFI HTTP Boot 설정 (PXE 대신 HTTP 부팅)
$ curl -sk -u admin:password -X PATCH \
-H "Content-Type: application/json" \
https://192.168.1.100/redfish/v1/Systems/1 \
-d '{"Boot": {
"BootSourceOverrideEnabled": "Once",
"BootSourceOverrideTarget": "UefiHttp",
"HttpBootUri": "http://boot.example.com/efi/grubx64.efi"
}}'
# NIC 어댑터 정보 및 부팅 설정 조회
$ curl -sk -u admin:password \
https://192.168.1.100/redfish/v1/Systems/1/NetworkAdapters \
| python3 -m json.tool
# Python Redfish 라이브러리를 사용한 대규모 PXE 자동 설정
# 데이터센터에서 수백 대 서버를 일괄 PXE 부팅 모드로 전환하는 스크립트
import redfish
import csv
import sys
from concurrent.futures import ThreadPoolExecutor, as_completed
def set_pxe_boot(host, username, password, persistent=False):
"""단일 서버의 PXE 부팅 설정"""
try:
client = redfish.redfish_client(
base_url=f"https://{host}",
username=username,
password=password,
default_prefix="/redfish/v1"
)
client.login()
# 부팅 설정 변경
body = {
"Boot": {
"BootSourceOverrideEnabled": "Continuous" if persistent else "Once",
"BootSourceOverrideTarget": "Pxe"
}
}
response = client.patch("/redfish/v1/Systems/1", body=body)
if response.status in [200, 204]:
print(f"[OK] {host}: PXE boot {'persistent' if persistent else 'once'}")
return True
else:
print(f"[FAIL] {host}: HTTP {response.status}")
return False
except Exception as e:
print(f"[ERROR] {host}: {e}")
return False
finally:
try:
client.logout()
except:
pass
# CSV 파일에서 서버 목록 읽기
# 형식: hostname,bmc_ip,username,password
servers = []
with open("servers.csv") as f:
for row in csv.DictReader(f):
servers.append(row)
# 최대 20개 서버를 동시에 PXE 설정 (병렬 처리)
results = {"success": 0, "fail": 0}
with ThreadPoolExecutor(max_workers=20) as executor:
futures = {
executor.submit(
set_pxe_boot,
srv["bmc_ip"], srv["username"], srv["password"],
persistent=True
): srv["hostname"]
for srv in servers
}
for future in as_completed(futures):
hostname = futures[future]
if future.result():
results["success"] += 1
else:
results["fail"] += 1
print(f"\nComplete: {results['success']} OK, {results['fail']} FAIL")
| 제어 방식 | 도구 | 접근 조건 | 영속성 | 적합 환경 |
|---|---|---|---|---|
| In-Band (OS) | ethtool, eeupdate, mlxconfig | OS 실행 중, root 권한 | 영구 (NVM 직접 변경) | 단일 서버 유지보수 |
| BIOS Setup | BIOS 메뉴, UEFI Shell | POST 단계 (콘솔/KVM 필요) | 영구 (NVM 또는 BIOS NVRAM) | 초기 설치, 물리적 접근 가능 시 |
| IPMI | ipmitool | BMC 네트워크 연결 | 일시적(1회) 또는 영구 | 소규모 서버 팜 |
| 벤더 BMC | racadm, ilorest | BMC 네트워크 연결 | 영구 (BIOS/NVM 변경) | 벤더 동종 환경 |
| Redfish API | curl, Python redfish | BMC 네트워크 연결 | 일시적 또는 영구 | 대규모 멀티벤더 데이터센터 |
- 10대 미만: BIOS Setup 또는 IPMI
chassis bootdev pxe로 충분합니다. - 10~100대: 벤더 BMC 도구(racadm, ilorest) + 셸 스크립트로 일괄 설정합니다.
- 100대 이상: Redfish API + Python 스크립트로 병렬 자동화합니다. IPMI보다 Redfish를 권장하는 이유는 JSON 기반 표준 API로 벤더 간 호환성이 우수하고, HTTPS를 통한 보안 통신을 지원하기 때문입니다.
- 주의: BMC를 통한 부팅 설정 변경은 IPMI/Redfish의 "Boot Override" 기능으로, 이는 NVM의 LOM 비트를 직접 변경하지 않고 BIOS에 부팅 장치를 지시하는 방식입니다. 따라서 BMC 설정과 NVM 설정이 충돌할 경우 일반적으로 BMC의 Boot Override가 우선합니다.
UEFI HTTP Boot와 LOM 비트의 관계
전통적인 PXE 부팅은 BIOS Legacy 환경에서 Option ROM(UNDI 드라이버)을 통해 DHCP + TFTP 프로토콜로 부트 파일을 내려받습니다. 그러나 현대의 UEFI 환경에서는 PXE가 UEFI Network Stack을 통해 동작하거나, 아예 PXE를 대체하는 HTTP Boot가 도입되었습니다. 부팅 모드에 따라 LOM 비트의 역할과 영향이 크게 달라지므로, 각 모드별 차이를 이해하는 것이 중요합니다.
| 항목 | Legacy PXE (BIOS) | UEFI PXE | UEFI HTTP Boot |
|---|---|---|---|
| 부팅 환경 | 16-bit Real Mode | 64-bit UEFI | 64-bit UEFI |
| 네트워크 드라이버 | Option ROM (UNDI) | UEFI NIC 드라이버 (SNP) | UEFI NIC 드라이버 (SNP) |
| 프로토콜 스택 | PXE BC → UNDI → DHCP → TFTP | SNP → MNP → IP4 → UDP → MTFTP | SNP → MNP → IP4/IP6 → TCP → HTTP/HTTPS |
| 서버 주소 취득 | DHCP Option 66 (next-server) | DHCP Option 66 또는 DHCPv6 | DHCP Option 60 (HTTPClient) + URL |
| 부트 파일 전송 | TFTP (UDP, 블록 단위) | TFTP 또는 MTFTP | HTTP/HTTPS (TCP, 스트리밍) |
| 최대 파일 크기 | ~32MB (TFTP 제약) | ~32MB (TFTP 제약) | 무제한 (HTTP) |
| 보안 | 없음 | Secure Boot 연동 가능 | HTTPS + Secure Boot |
| LOM 비트 영향 | 직접 제어 (Option ROM 로드 결정) | 간접 영향 (UEFI 드라이버 별도) | 간접 영향 (Network Stack 설정) |
부팅 모드에 따라 LOM 비트의 역할이 달라지므로, PXE 부팅 설정 시 현재 시스템의 부팅 모드를 먼저 확인해야 합니다.
| 부팅 모드 | LOM 비트(NVM) 영향 | PXE 제어 위치 | 확인/설정 방법 |
|---|---|---|---|
| Legacy BIOS | 직접 제어 — LOM 비트=0이면 Option ROM이 로드되지 않아 PXE 부팅 불가 | NVM Word 0x0F bit 1 + BIOS "Onboard LAN Boot ROM" | ethtool -e, BIOS Setup, eeupdate |
| UEFI + CSM | Legacy 호환 시 영향 — CSM이 Option ROM을 Legacy 방식으로 로드할 때 LOM 비트 확인 | NVM LOM 비트 + BIOS CSM 설정 + Network Boot 옵션 | BIOS "CSM Configuration" → "Network" → Legacy/UEFI 선택 |
| 순수 UEFI (CSM 없음) | 간접 영향 — UEFI NIC 드라이버는 펌웨어에 내장되어 LOM 비트와 무관하게 로드 가능 | BIOS "Network Stack" + "IPv4/IPv6 PXE Support" UEFI 변수 | efibootmgr -v, BIOS "Network Boot" 메뉴 |
| UEFI HTTP Boot | 거의 무관 — HTTP Boot는 UEFI Network Stack 기반이며 Option ROM을 사용하지 않음 | BIOS "HTTP Boot" 옵션 + DHCP 서버 설정 (Option 60) | BIOS "HTTP Boot Configuration", Redfish API |
# UEFI 부팅 항목에서 네트워크 부팅 확인
$ efibootmgr -v
BootCurrent: 0001
Timeout: 5 seconds
BootOrder: 0001,0002,0003,0004
Boot0001* ubuntu HD(1,...)/File(\EFI\ubuntu\shimx64.efi)
Boot0002* UEFI PXE: IPv4 Intel(R) I219-LM
PciRoot(0x0)/Pci(0x1F,0x6)/MAC(001122334455,1)/
IPv4(0.0.0.0)
Boot0003* UEFI HTTP: IPv4 Intel(R) I219-LM
PciRoot(0x0)/Pci(0x1F,0x6)/MAC(001122334455,1)/
Uri(http://boot.example.com/efi/grubx64.efi)
Boot0004* UEFI PXE: IPv6 Intel(R) I219-LM
PciRoot(0x0)/Pci(0x1F,0x6)/MAC(001122334455,1)/
IPv6(...)
# UEFI PXE 부팅 항목이 없는 경우 (Network Stack 비활성화)
# → BIOS Setup에서 "Network Stack" 활성화 필요
# → LOM 비트와 무관한 UEFI 펌웨어 설정
# UEFI Shell에서 네트워크 인터페이스 확인
Shell> ifconfig -l
iface : eth0
name : Intel(R) I219-LM
mac : 00:11:22:33:44:55
ipv4 : 0.0.0.0 (not configured)
status : media present
# UEFI Shell에서 HTTP Boot URL 확인
Shell> httpboot
HTTP Boot URI: http://boot.example.com/efi/grubx64.efi
- 확장성: HTTP 서버(nginx, Apache)는 TFTP 서버보다 동시 연결 처리 능력이 훨씬 뛰어납니다. 수천 대 동시 부팅이 가능합니다.
- 보안: HTTPS를 사용하면 부트 이미지 전송이 TLS로 암호화되어 중간자 공격(Man-in-the-Middle)을 방지합니다.
- 파일 크기: TFTP의 블록 크기 제약(기본 512바이트, 최대 ~32MB)이 없어 대용량 이미지를 직접 전송할 수 있습니다.
- 인프라: 기존 웹 서버와 CDN 인프라를 활용할 수 있어 별도의 TFTP 서버 구축이 불필요합니다.
- 한계: UEFI 펌웨어가 HTTP Boot를 지원해야 하며, 2016년 이후 출시된 서버/워크스테이션 대부분이 지원합니다.
Secure Boot 환경에서의 LOM Option ROM 로드
UEFI Secure Boot가 활성화된 환경에서는 모든 실행 가능한 바이너리(UEFI 드라이버, Option ROM, 부트로더)가 펌웨어의 서명 데이터베이스(db)에 등록된 키로 서명되어 있어야 합니다. Secure Boot는 인증서와 해시(Hash)를 단계적으로 검증하는 신뢰 체인으로 동작하므로, 기존 Option ROM에서 취약점(Vulnerability)이 발견되면 dbx에 해당 서명이나 해시가 추가될 수 있습니다. 따라서 LOM 비트가 활성화되어 있고 BIOS 설정에서 PXE가 켜져 있더라도, NIC의 Option ROM이 유효한 서명을 포함하지 않으면 Secure Boot 펌웨어가 로드를 거부하여 PXE 부팅이 실패합니다.
| 서명 데이터베이스 | 약어 | 역할 | Option ROM과의 관계 |
|---|---|---|---|
| Platform Key | PK | 서명 계층의 최상위 루트 키. OEM(메인보드 제조사)이 설정 | PK로 KEK를 서명, 간접적으로 Option ROM 신뢰 체인 구성 |
| Key Exchange Key | KEK | db/dbx를 수정할 수 있는 키. OS 벤더(Microsoft 등)가 소유 | KEK 소유자가 db에 NIC 벤더 키를 추가/제거 가능 |
| Signature Database | db | 신뢰할 수 있는 서명 목록 (인증서, 해시) | Option ROM의 서명이 db에 있으면 로드 허용 |
| Forbidden Signatures | dbx | 차단할 서명/해시 목록 (블랙리스트) | 취약점이 발견된 Option ROM 해시를 등록하여 차단 |
/* drivers/firmware/efi/libstub/secureboot.c
* 커널의 EFI Stub에서 Secure Boot 상태를 감지하는 코드
*
* 커널이 UEFI를 통해 부팅될 때, EFI Stub은 Secure Boot 상태를
* 확인하여 커널의 보안 정책을 조정합니다.
* 이 정보는 부팅 파라미터로 커널에 전달됩니다.
*/
enum efi_secureboot_mode efi_get_secureboot(void)
{
efi_guid_t efi_variable_guid = EFI_GLOBAL_VARIABLE_GUID;
u32 attr;
unsigned long size;
efi_status_t status;
u8 secboot, setupmode, moksbstate;
/* EFI 변수 "SecureBoot" 읽기
* 0 = Secure Boot 비활성화
* 1 = Secure Boot 활성화 */
size = sizeof(secboot);
status = get_efi_var(L"SecureBoot", &efi_variable_guid,
NULL, &size, &secboot);
if (status == EFI_NOT_FOUND)
return efi_secureboot_mode_disabled;
if (status != EFI_SUCCESS)
return efi_secureboot_mode_unknown;
if (secboot == 0)
return efi_secureboot_mode_disabled;
/* "SetupMode" 확인
* SetupMode=1이면 Secure Boot가 켜져 있어도
* 서명 검증을 하지 않습니다 (초기 설정 모드). */
size = sizeof(setupmode);
status = get_efi_var(L"SetupMode", &efi_variable_guid,
NULL, &size, &setupmode);
if (status != EFI_SUCCESS)
return efi_secureboot_mode_unknown;
if (setupmode == 1)
return efi_secureboot_mode_disabled;
/* MokSBStateRT 확인 (Shim 부트로더의 MOK 상태)
* MOK(Machine Owner Key)로 Secure Boot 검증을 확장할 수 있습니다.
* 커스텀 iPXE ROM을 사용할 때 MOK에 키를 등록하면
* 표준 db에 없는 Option ROM도 로드 가능합니다. */
size = sizeof(moksbstate);
status = get_efi_var(L"MokSBStateRT", &efi_shim_guid,
NULL, &size, &moksbstate);
/* moksbstate=1이면 MOK 기반 Secure Boot 비활성화 */
return efi_secureboot_mode_enabled;
}
/* Secure Boot와 Option ROM의 관계:
*
* UEFI 펌웨어의 Option ROM 로드 순서:
* 1. PCI 열거 → NIC 발견 → LOM 비트 확인
* 2. LOM 비트=1 → Option ROM BAR 매핑
* 3. Option ROM 헤더의 PE/COFF 서명 추출
* 4. Secure Boot 활성화 시:
* ├─ 서명이 db에 존재 → ROM 로드 허용
* ├─ 서명이 dbx에 존재 → ROM 로드 거부 (블랙리스트)
* └─ 서명 미등록 → ROM 로드 거부
* 5. ROM 실행 → UNDI/PXE 초기화
*
* 주요 NIC 벤더의 Option ROM 서명 상태:
* Intel: Microsoft UEFI CA로 서명 → 대부분의 시스템에서 동작
* Broadcom: Microsoft UEFI CA로 서명 → 대부분의 시스템에서 동작
* Mellanox: 자체 서명 + Microsoft CA → 대부분의 시스템에서 동작
* 커스텀 iPXE: 미서명 → Secure Boot 환경에서 거부됨
*/
# Secure Boot 환경에서 커스텀 iPXE ROM을 사용하기 위한 서명 절차
# 1. Secure Boot 상태 확인
$ mokutil --sb-state
SecureBoot enabled
# 2. 현재 등록된 서명 키 확인
$ mokutil --list-enrolled | head -20
[key 1]
SHA1 Fingerprint: ab:cd:ef:...
Issuer: C=US, ST=Washington, L=Redmond, O=Microsoft Corporation, ...
Subject: C=US, ST=Washington, L=Redmond, O=Microsoft Corporation, ...
# → Microsoft UEFI CA가 기본 등록되어 있음
# 3. 자체 서명 키 생성 (MOK용)
$ openssl req -new -x509 -newkey rsa:2048 -keyout MOK.key \
-out MOK.crt -nodes -days 3650 \
-subj "/CN=Custom PXE ROM Key"
# 4. iPXE ROM에 서명
$ sbsign --key MOK.key --cert MOK.crt \
--output ipxe-signed.efi ipxe.efi
# 5. 서명 검증
$ sbverify --cert MOK.crt ipxe-signed.efi
Signature verification OK
# 6. MOK에 키 등록 (다음 재부팅 시 확인 화면 표시)
$ sudo mokutil --import MOK.crt
# 비밀번호 설정 후 재부팅 → MOK Manager에서 키 등록 확인
# 7. 재부팅 후 MOK 등록 확인
$ mokutil --list-enrolled | grep "Custom PXE ROM Key"
Subject: CN=Custom PXE ROM Key
| 증상 | Secure Boot | LOM 비트 | 가능한 원인 | 해결 방법 |
|---|---|---|---|---|
| PXE 부팅 메뉴 표시됨, 정상 동작 | ON | 1 | (정상) 벤더 ROM이 Microsoft CA로 서명됨 | 조치 불필요 |
| PXE 부팅 메뉴 자체가 나타나지 않음 | ON | 1 | Option ROM 서명이 db에 없거나 dbx에 등록됨 | Secure Boot 일시 비활성화 또는 ROM 키를 db/MOK에 등록 |
| PXE 부팅 메뉴 나타나지 않음 | OFF | 0 | LOM 비트가 비활성화됨 | BIOS Setup에서 LOM/PXE 활성화 또는 NVM 수정 |
| 커스텀 iPXE ROM이 로드되지 않음 | ON | 1 | iPXE가 미서명 또는 자체 서명(db 미등록) | sbsign으로 서명 후 MOK에 키 등록 |
| 펌웨어 업데이트 후 PXE 실패 | ON | 1 | dbx 업데이트로 기존 ROM 해시가 블랙리스트에 추가 | NIC 펌웨어 업데이트로 새 서명 적용 |
Secure Boot가 Option ROM 로드를 거부할 때 대부분의 UEFI 펌웨어는 에러 메시지 없이 조용히 다음 부팅 장치로 넘어갑니다. 따라서 "PXE가 갑자기 안 됨"이라는 증상이 나타나면 LOM 비트뿐만 아니라 Secure Boot 상태도 반드시 확인해야 합니다. 특히 dbx(Forbidden Signatures Database) 업데이트 후 기존에 동작하던 NIC의 Option ROM이 블랙리스트에 추가되어 PXE가 실패하는 사례가 있습니다. mokutil --sb-state와 mokutil --list-enrolled로 현재 상태를 확인하십시오.
LOM PXE 부팅 트러블슈팅 심화
PXE 부팅은 하드웨어(NIC, 케이블), 펌웨어(BIOS, Option ROM), 네트워크(DHCP, TFTP), 소프트웨어(NBP, 커널)의 4개 계층이 모두 정상 동작해야 성공합니다. 한 계층이라도 문제가 있으면 부팅이 실패하므로, 체계적인 단계별 진단이 필수입니다. 다음은 각 실패 단계별 상세 디버깅 절차입니다.
시나리오 1: NIC가 BIOS 부팅 메뉴에 나타나지 않는 경우
BIOS 부팅 장치 목록에 네트워크 부팅 옵션이 표시되지 않는 것은 가장 근본적인 PXE 실패입니다. 원인은 LOM 비트 비활성화, BIOS 설정 오류, Secure Boot 거부, 또는 하드웨어 결함일 수 있습니다.
# OS에서 NIC 하드웨어 존재 여부 확인
$ lspci | grep -i ethernet
00:1f.6 Ethernet controller: Intel Corporation Ethernet Connection (14) I219-LM
# → NIC가 PCI 버스에 보이면 하드웨어는 정상
# NIC가 lspci에 보이지 않는 경우:
# 1. BIOS에서 "Onboard LAN"이 Disabled → Enable로 변경
# 2. PCH의 LAN 컨트롤러가 BIOS에서 비활성화됨
# 3. 하드웨어 결함 (드물지만 가능)
# NVM의 LOM 비트 및 LAN Enable 확인
$ sudo ethtool -e eth0 offset 0x1E length 2
0x001e: 08 00
# 0x0008 = bit 3(LAN Enable)=1, bit 1(PXE Enable)=0
# → LAN은 활성화되어 있으나 PXE가 비활성화됨
# → NVM Word 0x0F의 bit 1을 1로 변경 필요
# Expansion ROM BAR 상태 확인
$ sudo lspci -vvv -s 00:1f.6 | grep -i "expansion\|rom"
Expansion ROM at feb00000 [disabled] [size=512K]
# [disabled] → LOM 비트가 0이거나 BIOS가 ROM을 비활성화함
# Secure Boot 환경에서 ROM 로드 거부 확인
$ dmesg | grep -i "secure\|verified\|signature"
[ 1.234] Secure boot enabled
# Secure Boot ON + 미서명 ROM → 무증상 거부 가능
시나리오 2: "Media test failure" 또는 "No link" 오류
PXE ROM은 로드되었으나 네트워크 링크가 확립되지 않아 DHCP 패킷을 전송할 수 없는 상태입니다.
# 물리 계층 링크 상태 확인
$ ethtool eth0 | grep -i "link\|speed\|duplex"
Speed: 1000Mb/s
Duplex: Full
Link detected: yes
# Link detected: no → 케이블 또는 스위치 포트 확인
# NVM의 Force Speed 설정 확인 (자동 협상 실패 원인)
$ sudo ethtool -e eth0 offset 0x1E length 2
# Word 0x0F bit 7:6 → Force Speed
# 00 = Auto-negotiate (권장)
# 01/10/11 = 강제 속도 → 스위치 설정과 불일치 시 링크 실패
# 스위치 포트와의 속도/듀플렉스 불일치 확인
$ ethtool eth0
Settings for eth0:
Supported link modes: 10baseT/Half 10baseT/Full
100baseT/Half 100baseT/Full
1000baseT/Full
Advertised link modes: 1000baseT/Full # ← 자동 협상이 아닌 강제 설정
# → 스위치가 100Mbps만 지원하면 링크 불가
# SFP/SFP+ 트랜시버 상태 (10GbE LOM)
$ ethtool -m eth0
Identifier : 0x03 (SFP)
Vendor name : Intel Corp
Laser output power : 0.5 mW (-3.0 dBm)
Module temperature : 35 C
# 온도 이상 또는 출력 0이면 SFP 교체 필요
시나리오 3: DHCP Discover 전송 실패 ("No offer received")
링크는 정상이나 DHCP 서버로부터 응답을 받지 못하는 경우입니다.
# DHCP 패킷 캡처 (다른 서버에서 모니터링)
$ sudo tcpdump -i eth0 -n port 67 or port 68 -vv
# DHCPDISCOVER가 보이지만 DHCPOFFER가 없음:
# → DHCP 서버 미동작 또는 해당 서브넷에 대한 scope 미설정
# → DHCP relay(ip helper-address) 미설정 (다른 VLAN인 경우)
# VLAN 태깅 문제 확인
# PXE 클라이언트는 기본적으로 VLAN 태그 없이 패킷을 전송합니다.
# 스위치 포트가 trunk/tagged 모드이면 DHCP 패킷이 드롭될 수 있습니다.
# → 스위치 포트를 access/untagged 모드로 설정하거나
# NVM에서 VLAN ID를 설정합니다.
# 802.1X 인증 확인
# NAC(Network Access Control)이 활성화된 환경에서는
# 802.1X 인증 전까지 모든 트래픽이 차단됩니다.
# PXE 클라이언트는 802.1X를 지원하지 않으므로:
# → 스위치에서 해당 포트의 802.1X를 비활성화하거나
# → MAC 인증 바이패스(MAB) 설정
# DHCP 서버 로그 확인 (서버 측)
$ journalctl -u isc-dhcp-server -f
# 또는
$ tail -f /var/log/dhcpd.log
시나리오 4: TFTP 전송 도중 타임아웃
# TFTP 서버 접근 가능 여부 확인
$ tftp 192.168.1.10 -c get pxelinux.0
Received 42635 bytes in 0.1 seconds
# TFTP 포트(69/UDP) 방화벽 확인
$ sudo iptables -L -n | grep 69
# TFTP는 UDP 69번 포트 + 임의 고번호 데이터 포트를 사용합니다.
# 방화벽에서 UDP 69뿐만 아니라 관련 conntrack 모듈도 필요합니다:
$ sudo modprobe nf_conntrack_tftp
# MTU 문제 확인 (점보 프레임 환경)
# PXE 클라이언트는 일반적으로 MTU 1500을 사용합니다.
# 중간 네트워크 장비의 MTU가 더 작으면 TFTP 패킷이 드롭됩니다.
$ ping -s 1472 -M do 192.168.1.10
# 응답 없음 → MTU 문제 확인
# TFTP 서버의 대용량 파일 전송 문제
# tftpd-hpa의 블록 크기 기본값은 512바이트입니다.
# 대용량 커널 이미지 전송 시 --blocksize 1468 옵션 사용:
# /etc/default/tftpd-hpa:
# TFTP_OPTIONS="--secure --blocksize 1468"
시나리오 5: NBP(pxelinux.0/grubx64.efi) 로드 실패
# BIOS/UEFI 아키텍처 불일치 확인
# Legacy BIOS에는 pxelinux.0(16-bit), UEFI에는 grubx64.efi/shimx64.efi 필요
# DHCP 서버 설정 확인:
# dhcpd.conf 예시:
# if option arch = 00:07 { # UEFI x64
# filename "grubx64.efi";
# } elsif option arch = 00:00 { # Legacy BIOS
# filename "pxelinux.0";
# }
# DHCP 응답의 filename 확인
$ sudo tcpdump -i eth0 -n port 67 -vv 2>&1 | grep "file"
# file "grubx64.efi"
# TFTP 서버에 파일이 존재하는지 확인
$ ls -la /srv/tftp/grubx64.efi
-rw-r--r-- 1 root root 1048576 ... grubx64.efi
# Secure Boot 환경에서 shimx64.efi를 먼저 로드해야 하는 경우
# shim → grubx64.efi → 커널 체인으로 서명 검증
# filename을 "shimx64.efi"로 변경
시나리오 6: 커널 부팅 후 NFS/iSCSI root 마운트 실패
# 커널 ip= 파라미터 확인 (커널 명령줄)
# 올바른 형식:
# ip=dhcp # DHCP 자동 설정
# ip=10.0.0.2::10.0.0.1:255.255.255.0::eth0:off
# # client:server:gw:mask:hostname:device:autoconf
# 커널 dmesg에서 네트워크 부팅 관련 로그 확인
$ dmesg | grep -E "IP-Config|NFS|nfsroot|DHCP|eth0"
[ 3.456] IP-Config: Guessing netmask 255.255.255.0
[ 3.457] IP-Config: Complete:
[ 3.457] device=eth0, addr=10.0.0.2, mask=255.255.255.0
[ 3.458] gateway=10.0.0.1, server=10.0.0.1
[ 3.459] IP-Config: eth0 complete (from 10.0.0.1)
[ 4.123] VFS: Cannot open root device "nfs" or unknown-block(0,255)
# → NFS 서버 접근 불가 또는 export 설정 오류
# NFS 서버 설정 확인
$ showmount -e 10.0.0.1
Export list for 10.0.0.1:
/srv/nfsroot 10.0.0.0/24
# → 클라이언트 IP가 허용 범위에 포함되는지 확인
# 커널 빌드 옵션 확인 (NFS와 NIC 드라이버가 built-in이어야 함)
$ zcat /proc/config.gz | grep -E "CONFIG_NFS_FS|CONFIG_ROOT_NFS|CONFIG_E1000E"
CONFIG_NFS_FS=y # NFS가 =m(모듈)이면 root 마운트 불가!
CONFIG_ROOT_NFS=y
CONFIG_E1000E=y # NIC 드라이버도 =y(built-in) 필수!
| 진단 단계 | 확인 명령어 | 정상 출력 | 실패 시 조치 |
|---|---|---|---|
| NIC PCI 인식 | lspci | grep -i ethernet | NIC 장치 표시 | BIOS "Onboard LAN" 활성화, 하드웨어 점검 |
| LOM 비트 확인 | ethtool -e eth0 offset 0x1E length 2 | bit 1 = 1 | NVM 수정 또는 BIOS PXE 설정 활성화 |
| Option ROM BAR | lspci -vvv -s BDF | grep ROM | [enabled] | LOM 비트 또는 Secure Boot 확인 |
| 링크 상태 | ethtool eth0 | grep Link | Link detected: yes | 케이블, SFP, 스위치 포트, speed 점검 |
| DHCP 패킷 | tcpdump -i eth0 port 67 or 68 | DISCOVER → OFFER | DHCP 서버, relay, VLAN, 802.1X 점검 |
| TFTP 전송 | tftp server -c get filename | 파일 수신 성공 | 방화벽, 파일 경로, MTU 점검 |
| NBP 아키텍처 | file /srv/tftp/grubx64.efi | PE32+ executable (EFI) | BIOS/UEFI 모드에 맞는 NBP 사용 |
| 커널 NFS root | dmesg | grep IP-Config | IP-Config: Complete | ip= 파라미터, NFS export, 드라이버 확인 |
- LED 확인 — NIC 포트의 Link LED가 켜져 있는지 물리적으로 확인합니다. LED가 꺼져 있으면 물리 계층(케이블, SFP, 스위치 포트) 문제입니다.
- BIOS 부팅 메뉴 — 부팅 장치 목록에 "PXE" 또는 "Network Boot" 항목이 있는지 확인합니다. 없으면 LOM 비트 또는 Secure Boot 문제입니다.
- DHCP 스니핑 — 같은 VLAN의 다른 서버에서
tcpdump port 67으로 PXE 클라이언트의 DHCPDISCOVER가 보이는지 확인합니다. 보이지 않으면 네트워크 격리(Isolation) 문제입니다. - TFTP 수동 테스트 — DHCP에서 지정된 TFTP 서버와 파일 경로로 수동 다운로드를 시도합니다.
- 시리얼 콘솔 — 커널 부팅 단계의 오류는 시리얼 콘솔(
console=ttyS0,115200)로 로그를 캡처합니다.
칩셋별 대표 장애 패턴
같은 "PXE 실패"라도 칩셋마다 자주 반복되는 실패 양상은 다릅니다. 아래 표는 앞서 본 일반 절차를 실제 NIC 계열에 대응시키기 위한 빠른 참조표입니다.
| 칩셋/계열 | 현장에서 자주 보는 증상 | 먼저 의심할 원인 | 우선 확인할 지점 |
|---|---|---|---|
| Intel I210 / I211 | OS에서는 NIC가 정상인데 PXE 메뉴가 없거나 DHCP가 전혀 나가지 않습니다. | 잘못된 NVM image, 로컬 flash 미탑재, system BIOS flash 경로 누락, power cycle 미실시 | lspci -vv | grep -i rom, ethtool -e, 보드 설계 문서, 완전 전원 재인가 여부 |
| Intel I219 / I225 / I226 | UEFI 부팅 메뉴에는 NIC가 보이는데 PXE 항목이 사라지거나 HTTP Boot만 보입니다. | BIOS의 Network Stack 또는 IPv4 PXE Support 비활성화, CSM/UEFI 모드 조합 문제 | BIOS Setup의 Network Boot 메뉴, efibootmgr -v, BIOS 업데이트 이력 |
| Broadcom BCM57xx / BCM574xx | 포트별로 어떤 포트는 PXE가 되고 어떤 포트는 안 됩니다. BMC에서 PXE를 걸었는데 기대한 포트가 아닙니다. | MBA/FlexBoot가 포트별로 다르게 설정됨, BMC Boot Override와 NIC NVRAM 정책 충돌 | bnxtnvm -cfg -mba, iDRAC/iLO 설정, 포트 번호와 실제 케이블 연결 대응표 |
| Realtek RTL8111 / RTL8125 | Windows나 Linux에서는 링크가 잘 잡히는데 BIOS PXE는 보드마다 동작 편차가 큽니다. | eFuse 기반 설계, OEM BIOS가 Option ROM/UEFI driver를 번들하지 않음, 보드 정책상 Network Boot 비활성 | BIOS의 Network Boot, OEM BIOS 릴리스 노트, ethtool -i, 필요 시 PG Tool |
| Mellanox/NVIDIA ConnectX | FlexBoot 메뉴는 뜨지만 PXE 대신 iSCSI/FCoE로 가거나, 원하는 포트가 아닌 다른 포트가 부팅을 시도합니다. | BOOT_OPTION_ROM_EN_Px, LEGACY_BOOT_PROTOCOL_Px, VLAN/port policy 불일치 | mlxconfig query, 포트별 부트 프로토콜, 스위치 access/trunk 설정 |
| Marvell/Aquantia | 고속 NIC는 OS에서 문제없는데 pre-boot는 전혀 보이지 않습니다. | OEM이 PXE용 Option ROM을 번들하지 않았거나 BIOS 패키지에 통합하지 않음 | OEM 펌웨어 패키지, UEFI 드라이버 탑재 여부, BIOS 업데이트 후 변화 |
PXE API 호출 메커니즘
; NBP에서 PXE API 호출 예제 (16-bit Real Mode Assembly)
; PXE API는 !PXE 또는 PXENV+ 구조체를 통해 호출
; !PXE 구조체 찾기 (ES:BX에 전달됨)
; BX = !PXE 구조체의 오프셋
; PXENV_TFTP_READ_FILE 호출 예제
mov bx, PXENV_TFTP_READ_FILE ; 함수 번호: 0x0023
mov di, tftp_read_params ; 파라미터 구조체 오프셋
push ds
push di
push bx
call far [pxe_entry_point] ; !PXE EntryPointSP
add sp, 6
; AX = 반환값 (0 = 성공)
; 파라미터 구조체
tftp_read_params:
.status: dw 0 ; 반환 상태
.filename: times 128 db 0 ; 파일명
.buffersize: dd 0 ; 버퍼 크기
.bufferaddr: dd 0 ; 버퍼 세그먼트:오프셋
.serverip: dd 0 ; 서버 IP
.gatewayip: dd 0 ; 게이트웨이 IP
.mcastip: dd 0 ; 멀티캐스트 IP
.tftpport: dw 69 ; TFTP 포트
.tftpblksize: dw 512 ; 블록 크기
PXE 리얼 모드 메모리 맵(Memory Map)
BIOS PXE 부팅 시 16-bit 리얼 모드의 메모리 레이아웃은 NBP 개발과 트러블슈팅에 핵심적인 정보입니다. PXE ROM, UNDI 드라이버, NBP 각각이 특정 메모리 영역을 사용합니다.
/* !PXE 구조체 (PXE 2.1 Spec) - NBP가 PXE API에 접근하는 핵심 */
struct pxe_t {
uint8_t Signature[4]; /* "!PXE" */
uint8_t StructLength; /* 구조체 크기 */
uint8_t StructCksum; /* 체크섬 (전체 합=0) */
uint8_t StructRev; /* 구조체 리비전 */
uint8_t reserved1;
SEGOFF16 UNDIROMID; /* UNDI ROM ID 구조체 포인터 */
SEGOFF16 BaseROMID; /* BC ROM ID 구조체 포인터 */
SEGOFF16 EntryPointSP; /* API 진입점 (스택 파라미터) */
SEGOFF16 EntryPointESP; /* API 진입점 (확장 스택) */
SEGOFF16 StatusCallout; /* 콜아웃 함수 포인터 */
uint8_t reserved2;
uint8_t SegDescCnt; /* 세그먼트 디스크립터 수 */
SEGSEL FirstSelector; /* UNDI 코드/데이터 세그먼트 */
/* ... 세그먼트 디스크립터들 ... */
};
/* PXENV+ 구조체 (하위 호환용, PXE 2.0 이전) */
struct pxenv_plus {
uint8_t Signature[6]; /* "PXENV+" */
uint16_t Version; /* PXE 버전 (예: 0x0201) */
uint8_t Length;
uint8_t Checksum;
SEGOFF16 RMEntry; /* 리얼 모드 API 진입점 */
uint32_t PMOffset; /* 프로텍티드 모드 오프셋 */
uint16_t PMSelector; /* 프로텍티드 모드 셀렉터 */
uint16_t StackSeg; /* 스택 세그먼트 */
uint16_t StackSize; /* 스택 크기 */
/* ... */
};
/* NBP에서 !PXE 구조체 탐색 순서:
* 1. ES:BX 레지스터 확인 (BIOS가 전달)
* 2. "!PXE" 시그니처 검증
* 3. 실패 시 "PXENV+" 탐색 (0x9FC00~0xA0000)
* 4. 체크섬 검증 (전체 바이트 합 = 0)
*/
PXELINUX 부트로더
PXELINUX 개요
PXELINUX는 SYSLINUX 프로젝트의 일부로, PXE 환경에서 가장 널리 사용되는 NBP입니다.
| 변종 | 파일 | 특징 |
|---|---|---|
| pxelinux.0 | ~40KB | 기본 BIOS PXE 부트로더. TFTP만 지원 |
| lpxelinux.0 | ~90KB | HTTP, FTP, TFTP 지원. DNS 해석 가능 |
| syslinux.efi | ~200KB | UEFI 모드 PXE 부트로더 |
설정 파일 탐색 순서
PXELINUX는 다음 순서로 설정 파일을 탐색합니다 (첫 번째 발견 파일 사용):
클라이언트 정보:
UUID: b8945908-d6a6-41a9-611d-74a6ab80b83d
MAC: 01-88-99-aa-bb-cc-dd
IP: 192.168.1.100 (hex: C0A80164)
PXELINUX 설정 파일 탐색 순서:
1. pxelinux.cfg/b8945908-d6a6-41a9-611d-74a6ab80b83d ← UUID
2. pxelinux.cfg/01-88-99-aa-bb-cc-dd ← MAC (01- 접두사)
3. pxelinux.cfg/C0A80164 ← 전체 IP (hex)
4. pxelinux.cfg/C0A8016 ← IP 앞 7자리
5. pxelinux.cfg/C0A801 ← IP 앞 6자리
6. pxelinux.cfg/C0A80 ← IP 앞 5자리
7. pxelinux.cfg/C0A8 ← 서브넷 (Class B)
8. pxelinux.cfg/C0A ← 앞 3자리
9. pxelinux.cfg/C0 ← 앞 2자리
10. pxelinux.cfg/C ← 앞 1자리
11. pxelinux.cfg/default ← 기본 설정
→ 이 메커니즘으로 호스트별/서브넷별/전체 기본 설정을 계층적으로 관리할 수 있습니다.
PXELINUX 설정 파일 예제
# /var/lib/tftpboot/pxelinux.cfg/default
# 전역 설정
DEFAULT vesamenu.c32 # 그래픽 메뉴 사용
PROMPT 0 # 자동 메뉴 진입
TIMEOUT 300 # 30초 타임아웃 (1/10초 단위)
ONTIMEOUT local # 타임아웃 시 로컬 부팅
# 메뉴 테마
MENU TITLE PXE Boot Menu
MENU BACKGROUND pxelinux.cfg/splash.png
MENU COLOR border 30;44 #40ffffff #a0000000 std
MENU COLOR title 1;36;44 #9033ccff #a0000000 std
MENU COLOR sel 7;37;40 #e0ffffff #20ffffff all
# 로컬 디스크 부팅
LABEL local
MENU LABEL Boot from ^Local Disk
MENU DEFAULT
LOCALBOOT 0
# Ubuntu 24.04 LTS 설치
LABEL ubuntu2404
MENU LABEL ^Ubuntu 24.04 LTS Install
KERNEL images/ubuntu/vmlinuz
INITRD images/ubuntu/initrd
APPEND root=/dev/ram0 ramdisk_size=1500000 ip=dhcp url=http://192.168.1.10/ubuntu-24.04-live-server-amd64.iso autoinstall ds=nocloud-net;s=http://192.168.1.10/autoinstall/
# RHEL 10 계열 설치 (Kickstart)
LABEL rhel10
MENU LABEL ^RHEL 10 Kickstart Install
KERNEL images/rhel/vmlinuz
INITRD images/rhel/initrd.img
APPEND inst.repo=http://192.168.1.10/rhel10/ inst.ks=http://192.168.1.10/ks/rhel10.cfg ip=dhcp
# Debian 13 설치 (Preseed)
LABEL debian13
MENU LABEL ^Debian 13 Trixie Netinstall
KERNEL images/debian/linux
INITRD images/debian/initrd.gz
APPEND auto=true priority=critical url=http://192.168.1.10/preseed/debian13.cfg interface=auto vga=788
# 디스크리스 리눅스 (NFS Root)
LABEL diskless
MENU LABEL ^Diskless Linux (NFS Root)
KERNEL vmlinuz-diskless
INITRD initrd-diskless.img
APPEND root=/dev/nfs nfsroot=192.168.1.10:/exports/diskless,vers=4,tcp ip=dhcp rw
# Memtest86+
LABEL memtest
MENU LABEL ^Memtest86+
KERNEL memtest86+
# iPXE 체인로딩
LABEL ipxe
MENU LABEL ^iPXE Advanced Boot
KERNEL ipxe.lkrn
APPEND dhcp && chain http://192.168.1.10/boot.ipxe
PXELINUX COM32 모듈
| 모듈 | 용도 |
|---|---|
menu.c32 | 텍스트 기반 부팅 메뉴 |
vesamenu.c32 | VESA 그래픽 부팅 메뉴 (배경 이미지 지원) |
chain.c32 | 디스크/파티션 체인로딩 |
memdisk | ISO/플로피/하드 디스크 이미지를 RAM에서 부팅 |
pxechn.c32 | 다른 PXE 서버로 체인로딩 |
reboot.c32 | 시스템 재부팅 |
poweroff.c32 | 시스템 종료 (APM/ACPI) |
hdt.c32 | 하드웨어 정보 표시 |
ifcpu64.c32 | CPU 아키텍처(32/64비트) 감지 분기 |
linux.c32 | Linux 커널 직접 로드 |
GRUB 네트워크 부팅
GRUB PXE 개요
GRUB2는 UEFI 환경에서 가장 널리 사용되는 네트워크 부트로더입니다. PXELINUX보다 기능이 풍부하고 UEFI Secure Boot를 지원합니다.
GRUB 네트워크 설정
# /var/lib/tftpboot/grub/grub.cfg - GRUB 네트워크 부팅 설정
set default=0
set timeout=30
# 변수 설정
set pxe_server="192.168.1.10"
set nfs_server="192.168.1.10"
# 메뉴 테마
loadfont unicode
set gfxmode=auto
insmod all_video
insmod gfxterm
terminal_output gfxterm
menuentry "Boot from Local Disk" {
exit
}
menuentry "Ubuntu 24.04 LTS Install" {
linux (tftp)/$pxe_server/images/ubuntu/vmlinuz \
ip=dhcp \
url=http://$pxe_server/ubuntu-24.04-live-server-amd64.iso \
autoinstall ds=nocloud-net\;s=http://$pxe_server/autoinstall/
initrd (tftp)/$pxe_server/images/ubuntu/initrd
}
menuentry "RHEL 10 Kickstart Install" {
linuxefi (tftp)/images/rhel/vmlinuz \
inst.repo=http://$pxe_server/rhel10/ \
inst.ks=http://$pxe_server/ks/rhel10.cfg \
ip=dhcp
initrdefi (tftp)/images/rhel/initrd.img
}
menuentry "Diskless Linux (NFS Root)" {
linux (tftp)/vmlinuz-diskless \
root=/dev/nfs \
nfsroot=$nfs_server:/exports/diskless,vers=4,tcp \
ip=dhcp rw
initrd (tftp)/initrd-diskless.img
}
menuentry "iPXE Chainload" {
chainloader (tftp)/ipxe.efi
}
GRUB EFI 네트워크 이미지 빌드
# GRUB UEFI 네트워크 부트 이미지 생성
# 필요한 모듈을 포함한 standalone EFI 이미지
# x86_64 UEFI
grub-mknetdir --net-directory=/var/lib/tftpboot --subdir=/grub
# 또는 수동으로 standalone 이미지 생성
grub-mkimage -O x86_64-efi \
-o grubx64.efi \
-p '(tftp)/grub' \
-d /usr/lib/grub/x86_64-efi \
efinet tftp http net normal linux linuxefi \
all_video boot configfile echo part_gpt part_msdos \
search search_fs_uuid search_label reboot halt
# ARM64 UEFI
grub-mkimage -O arm64-efi \
-o grubaa64.efi \
-p '(tftp)/grub' \
-d /usr/lib/grub/arm64-efi \
efinet tftp http net normal linux \
all_video boot configfile echo
# Secure Boot을 사용할 경우 shim 체인로딩
# shimx64.efi → grubx64.efi 순서로 로드
cp /usr/lib/shim/shimx64.efi.signed /var/lib/tftpboot/shimx64.efi
cp /usr/lib/grub/x86_64-efi-signed/grubnetx64.efi.signed /var/lib/tftpboot/grubx64.efi
iPXE 고급 부팅
iPXE 개요
iPXE는 gPXE의 후속 프로젝트로, 레거시 PXE의 한계를 극복한 오픈소스 네트워크 부트 펌웨어입니다.
| 기능 | 레거시 PXE | iPXE |
|---|---|---|
| 프로토콜 | TFTP만 | HTTP, HTTPS, iSCSI, AoE, FCoE, TFTP, FTP, NFS |
| DNS | 미지원 | 지원 |
| 스크립팅 | 없음 | 내장 스크립트 언어 |
| Wi-Fi | 미지원 | WPA2 지원 (실험적) |
| VLAN | 일부 NIC만 | 완전 지원 |
| SAN Boot | 미지원 | iSCSI, AoE, FCoE, Infiniband SRP |
| 암호화(Encryption) | 없음 | HTTPS/TLS 지원 (공식 문서 기준 TLSv1.0-1.2) |
| 인증 | 없음 | HTTP Basic/Digest, 클라이언트 인증서 |
| Embedded Script | 없음 | ROM에 스크립트 내장 가능 |
| 무선 네트워크 | 미지원 | 802.11 지원 (일부 칩셋) |
iPXE 체인로딩 전략
체인로딩 바이너리 선택
| 펌웨어 | 권장 이미지 | 의미 | 언제 바꾸는가 |
|---|---|---|---|
| Legacy BIOS | undionly.kpxe | 펌웨어가 제공한 UNDI 드라이버를 재사용 | 기본 PXE ROM 호환성을 최대화하고 싶을 때 |
| Legacy BIOS | ipxe.pxe | iPXE가 자체 NIC 드라이버 사용 | 펌웨어 UNDI 구현이 불안정하거나 기능이 부족할 때 |
| UEFI | ipxe.efi | 일반적인 UEFI 체인로딩용 EFI 애플리케이션 | 가장 보편적인 UEFI iPXE 진입점(Entry Point) |
| UEFI | snponly.efi | UEFI SNP 드라이버를 재사용 | 펌웨어 NIC 드라이버는 안정적이지만 iPXE 내장 드라이버 충돌을 피하고 싶을 때 |
undionly.kpxe, UEFI 환경에서 ipxe.efi를 기본 체인로딩 대상으로 제시합니다. UEFI에서 특정 NIC 드라이버 문제가 있으면 snponly.efi가 더 잘 맞는 경우가 있습니다.
iPXE 스크립트 예제
#!ipxe
# boot.ipxe - iPXE 메인 부팅 스크립트
# 변수 설정
set boot-server http://192.168.1.10
set menu-timeout 30000
set submenu-timeout ${menu-timeout}
# 네트워크 초기화
dhcp || goto netboot_failed
echo IP: ${ip} / Gateway: ${gateway} / DNS: ${dns}
# 아키텍처 감지
iseq ${platform} efi && goto efi_boot ||
goto bios_boot
:efi_boot
set boot-prefix ${boot-server}/efi
goto start
:bios_boot
set boot-prefix ${boot-server}/bios
goto start
:start
menu iPXE Boot Menu - ${ip}
item --gap -- -- OS Installation --
item ubuntu Ubuntu 24.04 LTS
item rhel RHEL 10 계열
item debian Debian 13 Trixie
item --gap -- -- Utilities --
item diskless Diskless Linux (NFS)
item iscsi iSCSI SAN Boot
item memtest Memtest86+
item --gap -- -- Other --
item shell iPXE Shell
item reboot Reboot
item exit Exit to BIOS/UEFI
choose --default ubuntu --timeout ${menu-timeout} selected
goto ${selected}
:ubuntu
kernel ${boot-server}/images/ubuntu/vmlinuz
initrd ${boot-server}/images/ubuntu/initrd
imgargs vmlinuz ip=dhcp url=${boot-server}/iso/ubuntu-24.04.iso autoinstall ds=nocloud-net;s=${boot-server}/autoinstall/
boot || goto failed
:rhel
kernel ${boot-server}/images/rhel/vmlinuz
initrd ${boot-server}/images/rhel/initrd.img
imgargs vmlinuz inst.repo=${boot-server}/rhel10/ inst.ks=${boot-server}/ks/rhel10.cfg ip=dhcp
boot || goto failed
:debian
kernel ${boot-server}/images/debian/linux
initrd ${boot-server}/images/debian/initrd.gz
imgargs linux auto=true priority=critical url=${boot-server}/preseed/debian13.cfg interface=auto
boot || goto failed
:diskless
kernel ${boot-server}/vmlinuz-diskless
initrd ${boot-server}/initrd-diskless.img
imgargs vmlinuz-diskless root=/dev/nfs nfsroot=192.168.1.10:/exports/diskless,vers=4 ip=dhcp rw
boot || goto failed
:iscsi
# iSCSI SAN 부팅
set iscsi-server 192.168.1.20
set iscsi-target iqn.2024-01.com.example:storage.lun0
sanhook iscsi:${iscsi-server}::::${iscsi-target} || goto failed
# iSCSI LUN에서 부팅
sanboot iscsi:${iscsi-server}::::${iscsi-target} || goto failed
:memtest
chain ${boot-server}/memtest86+.bin || goto failed
:shell
echo iPXE Shell. Type 'exit' to return to menu.
shell
goto start
:reboot
reboot
:exit
exit
:failed
echo Boot failed. Retrying in 5 seconds...
sleep 5
goto start
:netboot_failed
echo Network initialization failed!
echo Falling back to local boot...
sleep 3
exit 1
iPXE 고급 스크립팅
#!ipxe
# advanced-boot.ipxe - 고급 기능 데모
# === 환경 변수 활용 ===
echo Platform: ${platform} # pcbios 또는 efi
echo BuildArch: ${buildarch} # i386 또는 x86_64 또는 arm64
echo MAC: ${mac} # xx:xx:xx:xx:xx:xx
echo Serial: ${serial} # 시리얼 번호
echo UUID: ${uuid} # SMBIOS UUID
echo Manufacturer: ${manufacturer}
echo Product: ${product}
echo Chip: ${chip} # NIC 칩셋
# === 조건 분기 ===
# MAC 주소 기반 분기
iseq ${mac} 00:11:22:33:44:55 && goto special_host ||
# 서브넷 기반 분기
isset ${netX/gateway} && goto has_gateway || goto no_gateway
# 제조사 기반 분기
iseq ${manufacturer} "Dell Inc." && set vendor dell ||
iseq ${manufacturer} "HP" && set vendor hp ||
iseq ${manufacturer} "Lenovo" && set vendor lenovo ||
set vendor generic
# === HTTPS + 인증 ===
set cert-server https://pxe.example.com
# 클라이언트 인증서 (빌드 시 내장)
# imgfetch ${cert-server}/cert/${uuid}.pem
# === 동적 메뉴 (서버에서 생성) ===
# 서버가 MAC/UUID/arch에 따라 동적 스크립트 생성
chain ${cert-server}/api/boot?mac=${mac:hexhyp}&arch=${buildarch}&uuid=${uuid}&platform=${platform} ||
echo Dynamic menu failed, falling back...
# === VLAN 태깅 ===
# vcreate --tag 100 net0
# dhcp net0-100
# === 루프와 재시도 ===
set retry-count 0
:retry_loop
inc retry-count
iseq ${retry-count} 5 && goto give_up ||
echo Attempt ${retry-count} of 5...
dhcp && goto dhcp_ok ||
sleep 2
goto retry_loop
:dhcp_ok
echo DHCP succeeded on attempt ${retry-count}
:give_up
echo All retry attempts exhausted.
iPXE 빌드와 커스터마이징
# iPXE 소스 빌드
# 의존성 설치
sudo apt install build-essential liblzma-dev isolinux \
mtools mkisofs syslinux perl
# 소스 클론
git clone https://github.com/ipxe/ipxe.git
cd ipxe/src
# === 기본 빌드 ===
# BIOS용 PXE chainload 이미지 (UNDI 재사용)
make bin/undionly.kpxe
# BIOS용 standalone 이미지 (내장 드라이버 사용)
make bin/ipxe.pxe
# UEFI용 이미지
make bin-x86_64-efi/ipxe.efi
# ARM64 UEFI용
make bin-arm64-efi/ipxe.efi
# === 내장 스크립트와 함께 빌드 ===
cat > embed.ipxe << 'SCRIPT'
#!ipxe
dhcp
chain http://192.168.1.10/boot.ipxe
SCRIPT
make bin/undionly.kpxe EMBED=embed.ipxe
# === 빌드 옵션 커스터마이징 ===
# config/general.h 에서 기능 활성화/비활성화
# DOWNLOAD_PROTO_HTTPS - HTTPS 지원
# DOWNLOAD_PROTO_NFS - NFS 지원
# IMAGE_SCRIPT - 스크립트 지원
# NET_PROTO_IPV6 - IPv6 지원
# CONSOLE_CMD - 콘솔 명령
# VLAN_CMD - VLAN 명령
# NSLOOKUP_CMD - DNS 조회 명령
# PING_CMD - 핑 명령
# HTTPS 활성화 빌드
cat > custom_config.h << 'EOF'
#define DOWNLOAD_PROTO_HTTPS
#define IMAGE_TRUST_CMD
#define CERT_CMD
#define CONSOLE_CMD
#define NSLOOKUP_CMD
#define PING_CMD
#define VLAN_CMD
EOF
make bin/undionly.kpxe EMBED=embed.ipxe CONFIG=custom_config
# === USB/ISO 이미지 생성 ===
make bin/ipxe.usb # USB 부팅 이미지
make bin/ipxe.iso # CD/DVD ISO 이미지
make bin/ipxe.lkrn # Linux 커널 형식 (GRUB/PXELINUX에서 로드 가능)
Etherboot → gPXE → iPXE 계보
iPXE의 역사를 이해하면 다양한 레거시 환경에서 만나는 네트워크 부팅 소프트웨어의 관계를 파악할 수 있습니다.
| 프로젝트 | 시기 | 주요 특징 | 상태 |
|---|---|---|---|
| Etherboot | 1995-2006 | 최초의 오픈소스 네트워크 부팅. NIC별 드라이버 바이너리. Tagged image format | 종료 (gPXE로 전환) |
| gPXE | 2006-2010 | Etherboot 후속. HTTP, iSCSI, DNS 지원 추가. UNDI 호환 모드. GPL v2 | 종료 (iPXE로 포크) |
| iPXE | 2010-현재 | gPXE 포크. HTTPS, Wi-Fi, 스크립팅, 확장된 SAN boot, UEFI 지원. 공식 암호화 문서 기준 TLSv1.0-1.2 지원. GPL v2 | 활발한 개발 중 |
Netboot.xyz — iPXE 기반 인터넷 부팅
# Netboot.xyz 설정 (가장 간단한 PXE 구성법)
# 1. Netboot.xyz 이미지 다운로드
wget https://boot.netboot.xyz/ipxe/netboot.xyz.kpxe # BIOS용
wget https://boot.netboot.xyz/ipxe/netboot.xyz.efi # UEFI용
# TFTP에 배치
cp netboot.xyz.kpxe /var/lib/tftpboot/
cp netboot.xyz.efi /var/lib/tftpboot/
# 2. dnsmasq 설정
dhcp-match=set:bios,option:client-arch,0
dhcp-match=set:efi64,option:client-arch,7
dhcp-boot=tag:bios,netboot.xyz.kpxe
dhcp-boot=tag:efi64,netboot.xyz.efi
# 이것만으로 Ubuntu, Debian, CentOS, Fedora, Arch, FreeBSD,
# Windows PE, Memtest86+, SystemRescue, DBAN 등
# 수십 가지 OS를 네트워크에서 직접 설치 가능!
# 3. 자체 호스팅 Netboot.xyz (Docker)
docker run -d --name netbootxyz \
-p 3000:3000 \ # 웹 관리 인터페이스
-p 69:69/udp \ # TFTP
-p 8080:80 \ # HTTP 부팅 파일
-v /data/netboot:/config \
ghcr.io/netbootxyz/netbootxyz
UEFI PXE & HTTP Boot
UEFI PXE vs Legacy PXE
| 항목 | Legacy BIOS PXE | UEFI PXE | UEFI HTTP Boot |
|---|---|---|---|
| 실행 환경 | 16-bit Real Mode | Native (32/64-bit) | Native (32/64-bit) |
| 드라이버 | UNDI (Option ROM) | EFI SNP/MNP 드라이버 | EFI HTTP 프로토콜 |
| 부트 파일 | .0 (COM 바이너리) | .efi (PE32+ 바이너리) | .efi (PE32+ 바이너리) |
| 전송 프로토콜 | TFTP | TFTP | HTTP/HTTPS |
| 파일 크기 제한 | ~32MB (blksize 미확장) | 없음 | 없음 |
| Secure Boot | 불가 | 지원 | 지원 (HTTPS 시 TLS) |
| IPv6 | 미지원 | 지원 | 지원 |
| 속도 | 느림 (TFTP) | 느림 (TFTP) | 빠름 (HTTP/TCP) |
UEFI HTTP Boot 설정
# UEFI HTTP Boot를 위한 DHCP 설정 (ISC DHCPd)
# DHCP Option 60에 "HTTPClient"가 포함된 요청 처리
class "httpclients" {
match if substring (option vendor-class-identifier, 0, 10) = "HTTPClient";
# HTTP Boot URI를 BOOTP file 필드로 전달
if option client-arch = 00:10 {
# x64 UEFI HTTP Boot
filename "http://192.168.1.10/boot/shimx64.efi";
} elsif option client-arch = 00:13 {
# ARM64 UEFI HTTP Boot
filename "http://192.168.1.10/boot/shimaa64.efi";
}
}
# dnsmasq 설정
# HTTP Boot 클라이언트에 HTTP URL 제공
dhcp-match=set:HTTPClient,option:vendor-class,HTTPClient
dhcp-boot=tag:HTTPClient,"http://192.168.1.10/boot/shimx64.efi"
# === nginx HTTP Boot 서버 ===
# /etc/nginx/sites-available/pxe
server {
listen 80;
server_name pxe.example.com;
root /var/www/pxeboot;
autoindex on;
# EFI 바이너리 MIME 타입
types {
application/efi efi;
}
location /boot/ {
alias /var/lib/tftpboot/;
}
location /images/ {
alias /var/www/pxeboot/images/;
}
location /iso/ {
alias /var/www/pxeboot/iso/;
}
# iPXE 동적 스크립트 (PHP/Python 백엔드)
location /api/boot {
proxy_pass http://127.0.0.1:8080;
}
}
# === HTTPS HTTP Boot (TLS) ===
server {
listen 443 ssl;
server_name pxe.example.com;
ssl_certificate /etc/ssl/certs/pxe.crt;
ssl_certificate_key /etc/ssl/private/pxe.key;
ssl_protocols TLSv1.2 TLSv1.3;
root /var/www/pxeboot;
# ... (동일한 location 설정)
}
HTTP Boot의 DHCP 규칙
UEFI 2.11 기준으로 HTTP Boot 클라이언트는 PXEClient가 아니라 HTTPClient라는 Vendor Class를 사용합니다. DHCPv4에서는 Boot URI를 file 필드나 Option 67로 받을 수 있고, DHCPv6에서는 RFC 5970의 OPTION_BOOTFILE_URL(59)을 사용합니다.
| 항목 | DHCPv4 | DHCPv6 |
|---|---|---|
| 클라이언트 식별 | Option 60 = HTTPClient:Arch:.... | Option 16 Vendor Class = HTTPClient:Arch:.... |
| 부트 대상 전달 | file 필드 또는 Option 67에 URI 저장 | Option 59 BOOTFILE_URL |
| 아키텍처 전달 | Option 93 | Option 61 CLIENT_ARCH_TYPE |
| NIC 인터페이스 정보 | Option 94 | Option 62 NII |
| 추가 파라미터 | 벤더 옵션 또는 NBP 내부 처리 | Option 60 BOOTFILE_PARAM |
UEFI PXE Vendor Class 식별
| DHCP Option 60 (Vendor Class) | 의미 | 부트 파일 형식 |
|---|---|---|
PXEClient:Arch:00000:UNDI:... | BIOS PXE | 16-bit .0/.com |
PXEClient:Arch:00006:UNDI:... | UEFI x86 PXE | PE32 .efi |
PXEClient:Arch:00007:UNDI:... | UEFI x64 PXE | PE32+ .efi |
PXEClient:Arch:00011:UNDI:... | UEFI ARM64 PXE | PE32+ .efi |
HTTPClient:Arch:00015:UNDI:... | UEFI x86 HTTP Boot | PE32 .efi (HTTP/HTTPS URI) |
HTTPClient:Arch:00016:UNDI:... | UEFI x64 HTTP Boot | PE32+ .efi (HTTP URL) |
HTTPClient:Arch:00017:UNDI:... | UEFI EBC HTTP Boot | EBC EFI 애플리케이션 |
HTTPClient:Arch:00019:UNDI:... | UEFI ARM64 HTTP Boot | PE32+ .efi (HTTP URL) |
리눅스 커널 네트워크 부팅 파라미터
ip= 파라미터
커널이 부팅 시 네트워크를 설정하는 핵심 파라미터입니다. initramfs 안의 네트워크 설정과는 별개로, 커널 자체에 내장된 네트워크 초기화 코드(net/ipv4/ipconfig.c)가 처리합니다.
ip= 파라미터 형식:
ip=<client-ip>:<server-ip>:<gw-ip>:<netmask>:<hostname>:<device>:<autoconf>:<dns0-ip>:<dns1-ip>:<ntp0-ip>
예시:
ip=dhcp # 모든 NIC에서 DHCP
ip=192.168.1.100::192.168.1.1:255.255.255.0:myhost:eth0:off
ip=::::myhost:eth0:dhcp # eth0에서 DHCP, 호스트명 지정
ip=192.168.1.100::192.168.1.1:255.255.255.0::eth0:off:8.8.8.8:8.8.4.4
autoconf 값:
dhcp - DHCP만 사용
bootp - BOOTP만 사용
rarp - RARP만 사용
both - BOOTP와 RARP 모두 시도
on - DHCP, BOOTP, RARP 모두 시도
off - 정적 설정 (나머지 필드에서 IP 가져옴)
any - 사용 가능한 모든 프로토콜 시도
nfsroot= 파라미터
nfsroot= 파라미터 형식:
nfsroot=[server-ip:]root-dir[,nfs-options]
예시:
nfsroot=192.168.1.10:/exports/diskless,vers=4,tcp
nfsroot=/exports/diskless,nfsvers=3,rsize=8192,wsize=8192
nfsroot=192.168.1.10:/exports/diskless,nolock,tcp
주요 NFS 마운트 옵션:
vers=4 / nfsvers=3 - NFS 버전 지정
tcp / udp - 전송 프로토콜
rsize=N / wsize=N - 읽기/쓰기 버퍼 크기
nolock - NLM 락 비활성화 (diskless에서 권장)
ro / rw - 읽기 전용/읽기 쓰기
네트워크 부팅 관련 전체 커널 파라미터
| 파라미터 | 설명 | 예시 |
|---|---|---|
ip= | 커널 수준 네트워크 설정 | ip=dhcp |
nfsroot= | NFS 루트 파일시스템 | nfsroot=10.0.0.1:/nfs/root |
root= | 루트 장치/프로토콜 | root=/dev/nfs, root=iscsi:... |
netboot=nfs | NFS 네트워크 부팅 명시 | netboot=nfs |
BOOTIF= | PXE가 설정한 부팅 인터페이스 MAC | BOOTIF=01-00-11-22-33-44-55 |
rd.neednet=1 | dracut: initrd에서 네트워크 활성화 | rd.neednet=1 |
rd.iscsi.initiator= | dracut: iSCSI initiator 이름 | rd.iscsi.initiator=iqn.2024... |
rd.iscsi.target.name= | dracut: iSCSI target 이름 | rd.iscsi.target.name=iqn.2024... |
rd.iscsi.target.ip= | dracut: iSCSI target IP | rd.iscsi.target.ip=10.0.0.1 |
iscsi_target_name= | initramfs-tools: iSCSI target | iscsi_target_name=iqn.2024... |
rd.live.image | dracut: Live 이미지 부팅 | rd.live.image |
fetch= | casper: HTTP/NFS에서 squashfs 가져오기 | fetch=http://server/fs.squashfs |
커널 소스: net/ipv4/ipconfig.c
/* net/ipv4/ipconfig.c - 커널 네트워크 자동 설정 핵심 코드 */
/* ip= 파라미터 파싱 */
static int __init ic_proto_name(char *name)
{
if (!strcmp(name, "on") || !strcmp(name, "any"))
return IC_BOOTP | IC_RARP;
if (!strcmp(name, "off") || !strcmp(name, "none"))
return 0;
if (!strcmp(name, "dhcp"))
return IC_BOOTP | IC_USE_DHCP;
if (!strcmp(name, "bootp"))
return IC_BOOTP;
if (!strcmp(name, "rarp"))
return IC_RARP;
if (!strcmp(name, "both"))
return IC_BOOTP | IC_RARP;
return IC_BOOTP | IC_RARP;
}
/* DHCP/BOOTP 패킷 수신 처리 */
static void __init ic_bootp_recv(struct sk_buff *skb,
struct net_device *dev)
{
struct bootp_pkt *b = (struct bootp_pkt *)skb->data;
/* 트랜잭션 ID 확인 */
if (b->xid != ic_dev_xid)
goto drop;
/* IP 주소 할당 */
ic_myaddr = b->your_ip; /* yiaddr → 클라이언트 IP */
ic_servaddr = b->server_ip; /* siaddr → TFTP 서버 */
ic_gateway = b->relay_ip; /* giaddr → 게이트웨이 */
/* DHCP 옵션 파싱 */
ic_do_bootp_ext(b->exten, skb->len - sizeof(*b));
/* ... rootpath, hostname, DNS 등 추출 ... */
}
/* NFS 루트 마운트 (fs/nfs/nfsroot.c) */
static int __init nfs_root_setup(char *line)
{
/* nfsroot=server:/path,options 파싱 */
ROOT_DEV = Root_NFS; /* root=/dev/nfs 효과 */
/* ... */
}
NFS Root 디스크리스 부팅
디스크리스 부팅 개요
디스크리스(diskless) 부팅은 로컬 디스크 없이 네트워크 파일시스템을 루트로 사용하는 부팅 방식입니다. 씬 클라이언트, 클러스터 노드, 임베디드 시스템에서 널리 사용됩니다.
NFS 서버 설정
# NFS 서버 설정 (Ubuntu/Debian)
sudo apt install nfs-kernel-server
# 디스크리스 루트 파일시스템 준비
sudo mkdir -p /exports/diskless
# debootstrap으로 최소 rootfs 생성 (Debian stable: trixie)
sudo debootstrap --arch=amd64 trixie /exports/diskless \
http://deb.debian.org/debian
# 또는 기존 시스템 복사
# sudo rsync -axHAX --exclude='/proc/*' --exclude='/sys/*' \
# --exclude='/dev/*' --exclude='/tmp/*' / /exports/diskless/
# NFS 내보내기 설정
# /etc/exports
/exports/diskless 192.168.1.0/24(rw,sync,no_subtree_check,no_root_squash)
/exports/base 192.168.1.0/24(ro,sync,no_subtree_check)
/exports/overlay 192.168.1.0/24(rw,sync,no_subtree_check,no_root_squash)
# NFS 서비스 재시작
sudo exportfs -arv
sudo systemctl restart nfs-kernel-server
# 방화벽 (NFS v4: TCP 2049만 필요)
sudo firewall-cmd --add-service=nfs --permanent
sudo firewall-cmd --reload
디스크리스 클라이언트 rootfs 설정
# /exports/diskless/etc/fstab - 디스크리스 클라이언트용
# NFS root는 커널이 자동 마운트하므로 여기서는 추가 마운트만
proc /proc proc defaults 0 0
sysfs /sys sysfs defaults 0 0
tmpfs /tmp tmpfs defaults 0 0
tmpfs /run tmpfs defaults 0 0
tmpfs /var/log tmpfs defaults 0 0
# /exports/diskless/etc/network/interfaces
auto lo
iface lo inet loopback
# DHCP (커널 ip=dhcp로 이미 설정되지만, 사용자 공간에서도 필요)
auto eth0
iface eth0 inet dhcp
# 커널 및 initramfs 생성 (NFS root 지원 포함)
# chroot /exports/diskless
# apt install linux-image-amd64 initramfs-tools
# /exports/diskless/etc/initramfs-tools/initramfs.conf
MODULES=netboot
BOOT=nfs
# initramfs 재생성
# update-initramfs -u
# TFTP에 커널/initrd 복사
sudo cp /exports/diskless/boot/vmlinuz-* /var/lib/tftpboot/vmlinuz-diskless
sudo cp /exports/diskless/boot/initrd.img-* /var/lib/tftpboot/initrd-diskless.img
OverlayFS 디스크리스 구성
# initramfs 안에서 OverlayFS 설정하는 스크립트
# /exports/diskless/etc/initramfs-tools/scripts/init-bottom/overlay
#!/bin/sh
# NFS를 lower로, tmpfs를 upper로 사용하는 OverlayFS
# 재부팅 시 모든 변경 사항 초기화됨
PREREQ=""
prereqs() { echo "$PREREQ"; }
case $1 in prereqs) prereqs; exit 0;; esac
. /scripts/functions
# tmpfs 생성 (writable layer)
mkdir -p /overlay
mount -t tmpfs tmpfs /overlay -o size=2G
mkdir -p /overlay/upper /overlay/work
# OverlayFS 마운트
# ${rootmnt}는 NFS로 마운트된 read-only rootfs
mkdir -p /overlay/merged
mount -t overlay overlay /overlay/merged \
-o lowerdir=${rootmnt},upperdir=/overlay/upper,workdir=/overlay/work
# rootfs 교체
mkdir -p /overlay/merged/nfs-root
mount --move ${rootmnt} /overlay/merged/nfs-root
mount --move /overlay/merged ${rootmnt}
exit 0
iSCSI SAN 부팅
iSCSI 부팅 개요
iSCSI(Internet Small Computer Systems Interface)는 SCSI 명령을 TCP/IP로 전달하는 프로토콜입니다. PXE/iPXE로 초기 부팅 후 iSCSI LUN을 루트 디바이스로 사용하면, 로컬 디스크와 거의 동일한 블록 레벨 I/O 성능을 얻을 수 있습니다.
iSCSI Target 서버 설정
# targetcli를 사용한 iSCSI target 설정 (Linux LIO)
sudo apt install targetcli-fb
# LVM 또는 파일 기반 LUN 생성
sudo lvcreate -L 50G -n client-A vg0
# 또는: dd if=/dev/zero of=/var/lib/iscsi/client-A.img bs=1M count=51200
sudo targetcli
# targetcli 내부 명령:
/> backstores/block create client-A /dev/vg0/client-A
/> iscsi/ create iqn.2024-01.com.example:storage
/> iscsi/iqn.2024-01.com.example:storage/tpg1/luns/ create /backstores/block/client-A
/> iscsi/iqn.2024-01.com.example:storage/tpg1/acls/ create iqn.2024-01.com.example:client-A
# CHAP 인증 설정
/> iscsi/iqn.2024-01.com.example:storage/tpg1/acls/iqn.2024-01.com.example:client-A/ set auth userid=client-A
/> iscsi/iqn.2024-01.com.example:storage/tpg1/acls/iqn.2024-01.com.example:client-A/ set auth password=secret123
# 포털 설정 (기본 0.0.0.0:3260)
/> iscsi/iqn.2024-01.com.example:storage/tpg1/portals/ create 10.0.0.1
/> saveconfig
/> exit
iPXE iSCSI 부팅 스크립트
#!ipxe
# iscsi-boot.ipxe
dhcp
set iscsi-server 10.0.0.1
set iscsi-port 3260
set iscsi-target iqn.2024-01.com.example:storage
set iscsi-initiator iqn.2024-01.com.example:${mac:hexhyp}
# iSCSI 연결 및 SAN 부팅
set root-path iscsi:${iscsi-server}::${iscsi-port}:1:${iscsi-target}
sanhook ${root-path} || goto failed
sanboot ${root-path} || goto failed
:failed
echo iSCSI boot failed!
shell
대규모 프로비저닝 시스템
프로비저닝 도구 비교
| 도구 | 개발사 | 특징 | 규모 | 자동 설치 |
|---|---|---|---|---|
| Cobbler | 커뮤니티 | DHCP/DNS/TFTP 통합 관리, 프로파일 시스템 | 중-대 | Kickstart, Preseed |
| MAAS | Canonical | "Metal as a Service", 베어메탈 클라우드 | 대 | cloud-init, Curtin |
| Foreman | Theforeman | 라이프사이클 관리, Puppet/Ansible 통합 | 대 | Kickstart, Preseed |
| FOG Project | 커뮤니티 | 이미지 기반 배포, 디스크 클로닝 | 소-중 | 이미지 복원 |
| Tinkerbell | Equinix | 클라우드 네이티브, 워크플로 기반 | 대 | Workflow Actions |
| Ironic | OpenStack | OpenStack 베어메탈 프로비저닝 | 대 | IPA (Ironic Python Agent) |
| Razor | Puppet | 정책 기반 프로비저닝 | 중 | Kickstart, Preseed |
| Matchbox | CoreOS/Red Hat | CoreOS/Flatcar 전용, iPXE 통합 | 중-대 | Ignition |
| Netboot.xyz | 커뮤니티 | 인터넷 기반 멀티 OS 부팅 메뉴 | 소 | 다양 |
Kickstart 자동 설치 예제 (RHEL 10 계열)
# /var/www/pxeboot/ks/rhel10.cfg - Kickstart 파일
# RHEL 10 계열 무인 설치
# 설치 소스
url --url="http://192.168.1.10/rhel10/"
repo --name="AppStream" --baseurl="http://192.168.1.10/rhel10/AppStream/"
# 시스템 설정
text
lang en_US.UTF-8
keyboard us
timezone Asia/Seoul --utc
rootpw --iscrypted $6$rounds=656000$salt$hash...
selinux --enforcing
firewall --enabled --ssh
services --enabled=sshd
# 네트워크
network --bootproto=dhcp --device=link --activate --hostname=server01.example.com
# 디스크 파티셔닝
zerombr
clearpart --all --initlabel
autopart --type=lvm --fstype=xfs
# BIOS/UEFI 공용 예시: 위치를 MBR로 고정하지 않음
bootloader --append="crashkernel=auto"
# 패키지
%packages
@^minimal-environment
vim-enhanced
tmux
open-iscsi
nfs-utils
%end
# 설치 후 스크립트
%post --log=/root/ks-post.log
# SSH 키 배포
mkdir -p /root/.ssh
curl -o /root/.ssh/authorized_keys http://192.168.1.10/keys/admin.pub
chmod 600 /root/.ssh/authorized_keys
# 설치 상태 등록
primary_ip=$(hostname -I | awk '{print $1}')
curl -X POST http://192.168.1.10/api/register \
-d "hostname=$(hostname)&ip=${primary_ip}"
%end
# 설치 완료 후 재부팅
reboot
inst.ks=를 같은 방식으로 커널 인자에 추가하는 구성을 제시합니다. 혼합 BIOS/UEFI 환경 문서라면 BIOS 전용 표현인 --location=mbr를 기본 예제로 두기보다, 범용적인 bootloader --append=... 형태를 먼저 보여 주는 편이 안전합니다. Kickstart 파일은 배포 전에 ksvalidator -v RHEL10로 문법을 먼저 점검하는 편이 좋습니다.
cloud-init / Autoinstall 예제 (Ubuntu)
# /var/www/pxeboot/autoinstall/user-data
# Ubuntu 24.04 LTS Autoinstall 설정
#cloud-config
autoinstall:
version: 1
locale: en_US.UTF-8
keyboard:
layout: us
network:
version: 2
ethernets:
id0:
match:
driver: e1000*
dhcp4: true
storage:
layout:
name: lvm
identity:
hostname: ubuntu-server
username: admin
password: "$6$rounds=656000$..."
ssh:
install-server: true
authorized-keys:
- ssh-ed25519 AAAA... admin@example.com
packages:
- vim
- tmux
- nfs-common
- open-iscsi
late-commands:
# 첫 부팅 시 로컬 디스크에서 부팅하도록 설정
- curtin in-target --target=/target -- efibootmgr -o 0001
# 설치 후 알림
- "curl -X POST http://192.168.1.10/api/installed?host=$(hostname)"
autoinstall ds=nocloud-net;s=http://...를 넣고, HTTP 루트에 user-data와 meta-data를 함께 제공하는 패턴이 가장 일반적입니다. Ubuntu 24.04부터는 cloud-config를 사용할 때 최상위 autoinstall: 구조와 스키마 검증이 더 엄격해졌으므로, 예전의 느슨한 YAML을 그대로 재사용하면 설치 초반에 중단될 수 있습니다.
배포판별 자동설치 실무 메모
| 배포판 | 현재 문서 기준 | 권장 전달 방식 | 실무에서 자주 틀리는 지점 |
|---|---|---|---|
| RHEL 10 계열 | Kickstart + Anaconda | inst.repo= + inst.ks=, PXE 또는 UEFI HTTP Boot | BIOS 전용 --location=mbr 고정, 배포 전 ksvalidator 미실행 |
| Ubuntu 24.04 LTS | Subiquity + cloud-init Autoinstall | autoinstall ds=nocloud-net;s=... + HTTP user-data/meta-data | #cloud-config 헤더 누락, 최상위 autoinstall: 키 누락, 오래된 스키마 재사용 |
| Debian 13 stable (trixie) | Debian Installer + Preseed | auto=true priority=critical url=... | UEFI 환경에서 GPT/EFI 관련 partman 옵션 누락, interface=auto 미지정 |
배포판별 자동설치 실패 지점 빠른 점검표
PXE 단계는 통과했는데 무인 설치가 기대대로 이어지지 않는 경우, 배포판마다 먼저 봐야 할 로그와 커널 인자가 다릅니다. 아래 표는 현장에서 어디부터 줄여 들어갈지를 빠르게 고르기 위한 요약입니다.
| 배포판 | 자주 보이는 증상 | 가장 먼저 볼 로그/파일 | 바로 실행할 명령 | 우선 의심할 지점 |
|---|---|---|---|---|
| RHEL 10 계열 | 설치기가 올라오지만 저장소 인식, 디스크 분할, %pre/%post에서 멈춥니다. | /tmp/pre-anaconda-logs/, /tmp/anaconda.log, /tmp/storage.log, /tmp/program.log | inst.debug rd.debug 추가 후 Ctrl+Alt+F2, less /tmp/anaconda.log, less /tmp/storage.log | inst.repo= 또는 inst.ks= 오타, NIC 드라이버 미포함, 스토리지 장치명 가정, Kickstart 문법 미검증 |
| Ubuntu 24.04 LTS | 자동 설치로 가지 않고 대화형 설치기로 떨어지거나, 초반 스키마 오류로 중단됩니다. | /var/log/installer/, /var/log/installer/subiquity-server-debug.log, /var/log/installer/autoinstall-user-data, /var/log/cloud-init.log | cloud-init schema --config-file user-data --annotate, grep -n '^autoinstall:' user-data, grep -n '^#cloud-config' user-data | #cloud-config 헤더 누락, 최상위 autoinstall: 키 누락, meta-data 미제공, ds=nocloud-net;s= URL 경로 오류 |
| Debian 13 stable (trixie) | Preseed가 무시되어 질문 화면이 다시 나오거나, 네트워크 선택/EFI 분할에서 멈춥니다. | 설치 중 /var/log/syslog, /var/log/partman, 설치 후 /var/log/installer/ | cat /proc/cmdline, tail -f /var/log/syslog, 커널 인자에 interface=auto netcfg/dhcp_timeout=60 추가 | url= 도달 실패, Preseed 파일 문법 오류, EFI 파티션 항목 누락, 잘못된 NIC를 첫 인터페이스로 선택 |
inst.debug로 Anaconda 로그를 남기고, Ubuntu 계열은 PXE 전에 cloud-init schema로 YAML을 검증하고, Debian 계열은 설치기 셸에서 /proc/cmdline과 /var/log/syslog를 바로 확인하는 편이 가장 빠릅니다.
Secure PXE Boot
PXE 보안 위협과 대응
| 위협 | 설명 | 대응 |
|---|---|---|
| Rogue DHCP | 악성 DHCP 서버가 잘못된 부팅 서버로 유도 | DHCP Snooping, 802.1X, VLAN 분리 |
| TFTP 탈취 | NBP/커널 파일을 악성으로 교체 | HTTPS 전송 (iPXE/UEFI HTTP Boot) |
| MITM 공격 | 네트워크 중간에서 부팅 트래픽 변조 | TLS/HTTPS, IPSec, 802.1X |
| 악성 NBP | 서명되지 않은 부트로더 실행 | UEFI Secure Boot + Shim |
| 부팅 이미지 변조 | 커널/initrd 무결성 훼손 | IMA/EVM, dm-verity, 서명 검증 |
| 인증 없는 접근 | 아무 기기나 PXE 부팅 가능 | MAC 필터링, 802.1X NAC, CHAP |
Secure PXE Boot Chain
Secure PXE Boot 구성 절차
# 1. MOK (Machine Owner Key) 생성
openssl req -new -x509 -newkey rsa:2048 -keyout MOK.key -out MOK.crt \
-days 3650 -nodes -subj "/CN=PXE Boot Signing Key"
# 2. GRUB EFI 서명
sbsign --key MOK.key --cert MOK.crt --output grubx64.efi.signed grubx64.efi
# 3. 커널 서명
sbsign --key MOK.key --cert MOK.crt --output vmlinuz.signed vmlinuz
# 4. Shim에 MOK 등록 (각 클라이언트에서 1회)
# shimx64.efi가 처음 부팅 시 MokManager로 진입
# → MOK.cer(DER 형식) 등록
openssl x509 -in MOK.crt -outform der -out MOK.cer
# 5. TFTP에 배치
cp shimx64.efi.signed /var/lib/tftpboot/shimx64.efi
cp grubx64.efi.signed /var/lib/tftpboot/grubx64.efi
cp vmlinuz.signed /var/lib/tftpboot/vmlinuz
# 6. iPXE HTTPS 빌드 (CA 인증서 내장)
cd ipxe/src
make bin-x86_64-efi/ipxe.efi EMBED=boot.ipxe \
TRUST=/etc/ssl/certs/ca-certificates.crt \
CERT=client.crt PRIVKEY=client.key
# 7. 네트워크 보안
# 802.1X RADIUS 인증 (PXE 전 네트워크 접근 제어)
# DHCP Snooping: 신뢰할 수 있는 포트만 DHCP 응답 허용
# Dynamic ARP Inspection: ARP 스푸핑 방지
IPv6 PXE 부팅
IPv6 PXE 개요
IPv6에서는 BOOTP의 siaddr/file 모델보다 URL 기반 전달이 더 중요해집니다. UEFI는 IPv6 환경에서 netboot6(PXE over DHCPv6 + MTFTP)와 HTTP Boot를 모두 정의하며, DHCPv6 옵션 집합(RFC 5970)을 통해 부트 URI와 아키텍처 정보를 전달합니다.
| 항목 | IPv4 PXE | IPv6 PXE |
|---|---|---|
| 주소 할당 | DHCPv4 | DHCPv6, RA, 필요 시 SLAAC 병행 |
| 부트 위치 전달 | siaddr, file, Option 66/67 | Option 59 BOOTFILE_URL |
| 아키텍처 정보 | Option 93 | Option 61 CLIENT_ARCH_TYPE |
| NIC 인터페이스 식별 | Option 94 | Option 62 NII |
| 부트파일 예시 | filename "pxelinux.0" | tftp://[2001:db8::1]/grubx64.efi 또는 https://boot.example.com/shimx64.efi |
| 다음 서버 지정 | next-server 또는 siaddr | URI 안에 서버와 경로를 모두 포함 |
| 멀티캐스트 | 제한적 | 네이티브 지원 |
| BIOS 지원 | 지원 | 미지원 (UEFI만) |
DHCPv6 부트 옵션 세트
| RFC 5970 옵션 | 이름 | 역할 | 예시 |
|---|---|---|---|
| 59 | BOOTFILE_URL | 부트 이미지의 전체 URI 전달 | http://[2001:db8::10]/boot/shimx64.efi |
| 60 | BOOTFILE_PARAM | 부트 이미지에 전달할 부가 파라미터 | root=/dev/nfs, ip=dhcp |
| 61 | CLIENT_ARCH_TYPE | 클라이언트 아키텍처 전달 | x64 UEFI, ARM64 UEFI, HTTP Boot 등 |
| 62 | NII | Network Interface Identifier | UNDI/SNP 인터페이스 버전, 타입 |
# ISC DHCPv6 PXE 설정
# /etc/dhcp/dhcpd6.conf
subnet6 2001:db8::/64 {
range6 2001:db8::100 2001:db8::200;
# PXE 부트파일 URL (DHCPv6 Option 59)
option dhcp6.bootfile-url "tftp://[2001:db8::1]/grubx64.efi";
# HTTP Boot
# option dhcp6.bootfile-url "http://[2001:db8::1]/boot/shimx64.efi";
}
# dnsmasq IPv6 PXE
dhcp-range=::100,::200,constructor:eth0,ra-names,slaac,64,12h
dhcp-option=option6:bootfile-url,"tftp://[2001:db8::1]/grubx64.efi"
멀티캐스트 부팅과 성능 최적화
멀티캐스트 TFTP
동일한 이미지를 수백 대의 서버에 동시에 전송해야 할 때, 유니캐스트 TFTP는 심각한 병목(Bottleneck)이 됩니다. 멀티캐스트를 사용하면 네트워크 대역폭(Bandwidth)을 절약하면서 대규모 동시 배포가 가능합니다.
| 방식 | 도구 | 장점 | 단점 |
|---|---|---|---|
| 유니캐스트 TFTP | tftpd-hpa | 간단, 호환성 최고 | 노드 수 × 대역폭 |
| 멀티캐스트 TFTP | atftpd (mtftp) | 1:N 동시 전송 | PXE ROM 지원 필요 |
| UDPcast | udp-sender/receiver | 대규모 이미지 배포 | 커스텀 리시버 필요 |
| HTTP | nginx/Apache | TCP 신뢰성, 빠른 속도 | iPXE 또는 UEFI HTTP Boot 필요 |
| BitTorrent | Murder, opentracker | P2P, 대규모에 최적 | 초기 시드 필요, 복잡성 |
전송 프로토콜별 성능 비교
테스트 환경: 100MB vmlinuz + 50MB initrd, 1Gbps 네트워크
┌──────────────────────┬──────────────┬─────────────────┬──────────────────┐
│ 프로토콜 │ 단일 클라이언트│ 10대 동시 │ 100대 동시 │
├──────────────────────┼──────────────┼─────────────────┼──────────────────┤
│ TFTP (512B blk) │ ~120초 │ ~1200초 (직렬) │ 사실상 불가 │
│ TFTP (1468B blk) │ ~25초 │ ~250초 │ ~2500초 │
│ TFTP (1468B, win=64) │ ~8초 │ ~80초 │ ~800초 │
│ HTTP (nginx) │ ~2초 │ ~3초 │ ~15초 │
│ HTTPS (nginx) │ ~3초 │ ~5초 │ ~20초 │
│ 멀티캐스트 TFTP │ ~25초 │ ~30초 │ ~40초 │
│ UDPcast │ ~3초 │ ~5초 │ ~10초 │
└──────────────────────┴──────────────┴─────────────────┴──────────────────┘
* 실제 성능은 네트워크 환경, 서버 성능에 따라 크게 다를 수 있음
nginx PXE 서버 최적화
# /etc/nginx/conf.d/pxe-optimized.conf
server {
listen 80;
server_name pxe.example.com;
root /var/www/pxeboot;
# 동시 접속 최적화
keepalive_timeout 30;
keepalive_requests 100;
# sendfile + tcp_nopush로 커널 수준 최적화
sendfile on;
tcp_nopush on;
tcp_nodelay on;
# 대용량 파일 전송 최적화
directio 4m; # 4MB 이상 파일은 DirectIO
aio threads; # 비동기 I/O
output_buffers 2 1m; # 출력 버퍼
# 바이트 범위 요청 (iPXE가 사용)
max_ranges 1;
# 캐싱 헤더
location ~* \.(efi|img|vmlinuz|iso)$ {
expires 1h;
add_header Cache-Control "public, no-transform";
}
# 접속 제한 (DoS 방지)
limit_conn_zone $binary_remote_addr zone=pxe:10m;
limit_conn pxe 10;
}
UDPcast 멀티캐스트 배포
UDPcast는 대규모 동시 이미지 배포에 최적화된 멀티캐스트/브로드캐스트 도구입니다. PXE + iPXE와 결합하여 수백 대의 서버에 동시에 디스크 이미지를 전송할 수 있습니다.
# UDPcast 설치 및 사용
# 서버 설치 (Debian/Ubuntu)
sudo apt install udpcast
# === 1. 디스크 이미지 멀티캐스트 전송 ===
# 송신측 (서버): 디스크 이미지 전송
udp-sender --file /images/ubuntu-golden.img \
--mcast-addr 239.1.1.1 \
--portbase 9000 \
--min-receivers 10 \ # 최소 10대 수신 대기 후 시작
--max-wait 60 \ # 최대 60초 대기
--blocksize 1456 \ # 블록 크기 (MTU 최적화)
--fec 8x8/64 \ # Forward Error Correction
--autostart 30 \ # 30초 후 자동 시작
--log /var/log/udpcast.log
# 수신측 (클라이언트): initrd 안에서 실행
udp-receiver --mcast-addr 239.1.1.1 \
--portbase 9000 \
--file /dev/sda \ # 직접 디스크에 기록
--nosync # 완료 시 sync 생략 (빠른 재부팅)
# === 2. 파이프를 통한 실시간 압축 전송 ===
# 송신측
lz4 -c /dev/sda | udp-sender --pipe "cat" \
--mcast-addr 239.1.1.1 --portbase 9000 --min-receivers 5
# 수신측
udp-receiver --pipe "lz4 -d > /dev/sda" \
--mcast-addr 239.1.1.1 --portbase 9000
# === 3. iPXE 스크립트에서 UDPcast 연동 ===
# initrd에 udp-receiver를 포함시키고, 커널 파라미터로 멀티캐스트 주소 전달
# imgargs vmlinuz ip=dhcp udpcast_addr=239.1.1.1 udpcast_port=9000
atftpd 멀티캐스트 TFTP
# atftpd: 멀티캐스트 TFTP 서버 (RFC 2090)
sudo apt install atftpd
# /etc/default/atftpd 설정
USE_INETD=false
OPTIONS="--daemon --port 69 \
--tftpd-timeout 300 \
--retry-timeout 5 \
--maxthread 200 \
--bind-address 0.0.0.0 \
--verbose 5 \
--logfile /var/log/atftpd.log \
--mtftp 239.1.1.1,1758 \
--mtftp-port 1758 \
/var/lib/tftpboot"
# 멀티캐스트 TFTP 동작 원리:
# 1. 첫 번째 클라이언트가 RRQ 전송
# 2. 서버가 멀티캐스트 그룹으로 DATA 전송 시작
# 3. 이후 클라이언트들은 진행 중인 멀티캐스트 스트림에 합류
# 4. 놓친 블록은 유니캐스트로 재요청
# 주의: PXE ROM이 MTFTP를 지원해야 함 (많은 ROM이 미지원)
sudo systemctl restart atftpd
DHCP 릴레이와 다중 서브넷
DHCP 릴레이 개요
PXE 클라이언트의 DHCPDISCOVER는 브로드캐스트이므로, 라우터를 넘어갈 수 없습니다. 다중 서브넷 환경에서는 DHCP 릴레이 에이전트(ip helper-address)가 필요합니다.
# Cisco/Juniper 라우터 DHCP Relay 설정
# Cisco IOS
interface GigabitEthernet0/1
description Subnet-A
ip address 192.168.1.1 255.255.255.0
ip helper-address 10.0.0.1
interface GigabitEthernet0/2
description Subnet-B
ip address 192.168.2.1 255.255.255.0
ip helper-address 10.0.0.1
# Linux DHCP Relay Agent
sudo apt install isc-dhcp-relay
# /etc/default/isc-dhcp-relay
SERVERS="10.0.0.1"
INTERFACES="eth1 eth2"
OPTIONS="-a" # Agent Information (Option 82) 추가
sudo systemctl enable --now isc-dhcp-relay
# ISC DHCP 서버: 서브넷별 설정
# giaddr로 클라이언트 서브넷 식별
subnet 192.168.1.0 netmask 255.255.255.0 {
range 192.168.1.100 192.168.1.200;
option routers 192.168.1.1;
next-server 10.0.0.1;
filename "pxelinux.0";
}
subnet 192.168.2.0 netmask 255.255.255.0 {
range 192.168.2.100 192.168.2.200;
option routers 192.168.2.1;
next-server 10.0.0.1;
filename "pxelinux.0";
}
DHCP Option 82 (Relay Agent Information)
DHCP Option 82(RFC 3046)는 릴레이 에이전트가 DHCP 패킷에 추가하는 정보로, 클라이언트가 연결된 물리적 위치(스위치 포트, VLAN)를 DHCP 서버에 알려줍니다. 대규모 PXE 환경에서 자동 프로비저닝에 핵심적입니다.
DHCP Option 82 구조 (RFC 3046):
┌──────────────────────────────────────────────────────┐
│ Option 82 (Relay Agent Information) │
│ Type: 82 (0x52), Length: variable │
│ │
│ Sub-option 1: Circuit ID (물리적 포트 식별) │
│ - 스위치명 + 포트 번호 │
│ - 예: "Gi0/1" (GigabitEthernet 0/1) │
│ - 예: "Ethernet1/0/24" (스택 스위치) │
│ │
│ Sub-option 2: Remote ID (원격 장비 식별) │
│ - 릴레이 에이전트(스위치)의 MAC 또는 식별자 │
│ - 예: "00:1A:2B:3C:4D:5E" (스위치 MAC) │
│ │
│ Sub-option 5: Link Selection (선택적) │
│ - DHCP 서버가 사용할 서브넷 지정 │
│ Sub-option 6: Subscriber ID (선택적) │
│ - 가입자 식별 (ISP 환경) │
└──────────────────────────────────────────────────────┘
활용 시나리오:
- 스위치 포트 기반 자동 프로비저닝: "Gi0/1에 연결된 서버 → Web 프로파일"
- 랙/위치 기반 IP 할당: 포트 정보로 물리적 위치 파악
- 보안 감사: 어떤 포트에서 DHCP 요청이 왔는지 추적
# ISC DHCP에서 Option 82 활용 예제
# Option 82 서브옵션 정의
option agent.circuit-id code 1 = text;
option agent.remote-id code 2 = text;
# Circuit ID 기반 PXE 프로파일 자동 할당
class "rack1-servers" {
# 랙1의 스위치 포트 Gi0/1~Gi0/24에서 온 요청
match if substring(option agent.circuit-id, 0, 4) = "Gi0/";
}
# 클래스별 다른 부트 파일/OS 프로파일 할당
pool {
allow members of "rack1-servers";
range 192.168.1.100 192.168.1.124;
next-server 10.0.0.1;
filename "profiles/rack1/pxelinux.0";
}
# Cisco 스위치에서 Option 82 설정
# ip dhcp snooping
# ip dhcp snooping vlan 100
# ip dhcp snooping information option ← Option 82 삽입 활성화
# interface GigabitEthernet0/1
# ip dhcp snooping trust ← DHCP 서버 쪽 포트
트러블슈팅
일반적인 문제와 해결
| 증상 | 원인 | 해결 |
|---|---|---|
| PXE-E51: No DHCP or proxyDHCP offers were received | DHCP 서버 미응답 | DHCP 서비스 확인, 방화벽(Firewall) UDP 67/68 열기, VLAN/포트 확인 |
| PXE-E53: No boot filename received | DHCP에 filename 미설정 | filename 또는 Option 67 설정 확인 |
| PXE-E32: TFTP open timeout | TFTP 서버 접근 불가 | TFTP 서비스 확인, 방화벽 UDP 69 열기, next-server IP 확인 |
| PXE-E36: Error received from TFTP server | 파일 없음 또는 권한 오류 | 파일 경로/권한 확인: ls -la /var/lib/tftpboot/ |
| PXE-E3B: TFTP Error - File not found | 부트파일 경로 오류 | 정확한 파일명 확인, 대소문자 주의 |
| PXE-E61: Media test failure, check cable | 네트워크 물리적 문제 | 케이블, 스위치 포트, 링크 상태 확인 |
| NBP 로드 후 검은 화면 | 아키텍처 불일치 | BIOS→.0 파일, UEFI→.efi 파일 확인 |
| GRUB: error: no suitable video mode found | 그래픽 모드 문제 | terminal_output console 사용 |
| "Booting from ROM" 후 멈춤 | PXE ROM 비활성화 | BIOS에서 NIC PXE/Network Boot 활성화 |
| UEFI HTTP Boot 실패 | HTTP 서버 문제 | URL 접근 가능 확인, Content-Type 헤더 확인 |
실패 메시지별 즉시 대응표
현장에서는 에러 코드 하나만 보고도 점검 순서를 줄여야 합니다. 아래 표는 콘솔에 보이는 문자열을 기준으로 즉시 어디를 봐야 하는지를 압축한 대응표입니다.
| 실패 메시지 | 문제가 난 계층 | 즉시 의미 | 첫 확인 명령/행동 |
|---|---|---|---|
PXE-E51: No DHCP or proxyDHCP offers were received | 네트워크 제어면 | 클라이언트가 DHCPDISCOVER는 보냈지만 OFFER를 못 받았거나, relay/proxyDHCP 경로가 끊겼습니다. | tcpdump -i eth0 port 67 or 68, DHCP 서버 로그, VLAN/access 포트 확인 |
PXE-E53: No boot filename received | DHCP 정책 | IP는 받았지만 filename, Option 67, Boot URI 중 적절한 값이 오지 않았습니다. | DHCP 응답의 file 필드와 Option 67, 아키텍처별 분기 규칙 확인 |
PXE-E32: TFTP open timeout | TFTP 경로 | next-server에는 도달하지 못하거나 UDP 69 이후 데이터 포트가 막혔습니다. | tftp server -c get pxelinux.0, 방화벽, nf_conntrack_tftp, MTU 확인 |
PXE-E36 또는 PXE-E3B | TFTP 파일/권한 | 파일명, 경로, 권한, 심볼릭 링크, 대소문자 중 하나가 틀렸습니다. | ls -la /var/lib/tftpboot, 서버 로그, 아키텍처별 파일명 확인 |
PXE-E61: Media test failure, check cable | 물리 계층 | 링크가 올라오지 않았거나 speed/duplex 협상이 어긋났습니다. | 케이블, SFP, 스위치 포트, ethtool eth0, 강제 속도 설정 확인 |
Booting from ROM... 후 멈춤 | Option ROM / NBP 전환 | ROM은 로드됐지만 NBP 실행 또는 다음 단계 핸드오프에서 멈췄습니다. | BIOS/UEFI 모드와 NBP 파일 형식 일치 여부, Secure Boot, ROM 크기/서명 확인 |
NBP is too big to fit in free base memory | Legacy BIOS 메모리 | 16비트 BIOS 가용 메모리 한계 때문에 NBP 또는 초기 모듈 조합이 너무 큽니다. | PXELINUX 모듈 축소, iPXE 체인로딩 전환, 불필요한 COM32 모듈 제거 |
Could not boot image: Exec format error | 아키텍처/포맷 | BIOS에 .efi, UEFI에 BIOS NBP, 또는 CPU 아키텍처가 다른 이미지를 넘겼습니다. | file grubx64.efi, Option 93/HTTPClient 분기, efibootmgr -v 확인 |
start_image() returned Invalid Parameter | UEFI 로더 체인 | UEFI가 EFI application은 받았지만 이미지 형식, 의존 파일, 서명, 메모리 조건이 맞지 않습니다. | shimx64.efi와 grubx64.efi 조합, Secure Boot 상태, 서버 파일 무결성 확인 |
VFS: Cannot open root device "nfs" | 커널 이후 rootfs | PXE 자체는 성공했지만 커널 내 네트워크 재초기화 또는 NFS export 단계에서 실패했습니다. | dmesg | grep IP-Config, NFS export, built-in NIC 드라이버, ip= 파라미터 확인 |
디버깅 도구와 명령어
# === DHCP 트래픽 캡처 ===
# tcpdump로 DHCP 패킷 모니터링
sudo tcpdump -i eth0 -vvv port 67 or port 68 or port 4011
# Wireshark 필터
# bootp || dhcp || tftp
# === TFTP 테스트 ===
# TFTP 클라이언트로 직접 파일 요청
tftp 192.168.1.10
tftp> get pxelinux.0
tftp> quit
# curl로 TFTP 테스트
curl tftp://192.168.1.10/pxelinux.0 -o /tmp/pxelinux.0
# === DHCP 서버 로그 ===
# ISC DHCP
sudo journalctl -u isc-dhcp-server -f
# dnsmasq
sudo journalctl -u dnsmasq -f
# 또는 dnsmasq --log-dhcp 옵션
# === TFTP 서버 로그 ===
sudo journalctl -u tftpd-hpa -f
# === iPXE 디버깅 ===
# iPXE 쉘에서 직접 디버깅
iPXE> dhcp net0
iPXE> show net0/ip
iPXE> show net0/netmask
iPXE> show net0/gateway
iPXE> show net0/dns
iPXE> nslookup pxe.example.com
iPXE> ping 192.168.1.10
iPXE> imgfetch tftp://192.168.1.10/pxelinux.0
iPXE> chain http://192.168.1.10/boot.ipxe
# === NFS 디버깅 ===
# NFS 마운트 테스트
sudo mount -t nfs 192.168.1.10:/exports/diskless /mnt -o vers=4,tcp
ls /mnt/
# NFS 서버 내보내기 확인
showmount -e 192.168.1.10
# rpcinfo (NFS v3)
rpcinfo -p 192.168.1.10
# === iSCSI 디버깅 ===
# iSCSI target 탐색
iscsiadm -m discovery -t sendtargets -p 10.0.0.1:3260
# iSCSI 로그인
iscsiadm -m node -T iqn.2024-01.com.example:storage -p 10.0.0.1 --login
# 세션 확인
iscsiadm -m session -P 3
# === 커널 네트워크 부팅 디버깅 ===
# 커널 커맨드 라인에 추가:
# ip=dhcp debug loglevel=7
# 시리얼 콘솔로 부팅 로그 확인:
# console=ttyS0,115200n8
캡처 포인트 선택 가이드
PXE 장애 분석에서 가장 흔한 실수는 잘못된 위치에서 패킷을 잡는 것입니다. DHCPDISCOVER가 안 보인다고 바로 서버 문제로 가면 안 되고, 먼저 어느 지점에서 패킷이 사라지는지 확인해야 합니다.
| 캡처 위치 | 이 위치에서 알 수 있는 것 | 추천 도구 | 언제 우선 선택할지 |
|---|---|---|---|
| 클라이언트와 같은 access 포트의 미러/span | 클라이언트가 실제로 DHCPDISCOVER, ARP, TFTP RRQ를 내보내는지 알 수 있습니다. | tcpdump, Wireshark | 아예 DHCP가 안 보이거나 Media test failure 직전 단계가 의심될 때 |
| DHCP 서버 인터페이스 | DISCOVER가 서버까지 왔는지, OFFER가 나갔는지 즉시 구분할 수 있습니다. | tcpdump, journalctl | PXE-E51, relay, scope 오동작이 의심될 때 |
| TFTP/HTTP 서버 인터페이스 | 클라이언트가 실제로 파일 요청을 하는지, 요청 파일명이 무엇인지 알 수 있습니다. | tcpdump, nginx/access log, TFTP 로그 | PXE-E32, PXE-E3B, 잘못된 filename이 의심될 때 |
| 스위치 uplink 또는 relay 구간 | VLAN 태그, relay 삽입, ACL, DHCP snooping/Option 82 영향을 볼 수 있습니다. | 스위치 포트 미러, 네트워크 탭 | 클라이언트와 서버 양쪽에서 각각 정상인데 중간에서만 사라질 때 |
| BMC KVM 또는 시리얼 콘솔 | 펌웨어 메뉴, Secure Boot 무증상 실패, 커널 이후 rootfs 실패 로그를 볼 수 있습니다. | iDRAC/iLO KVM, SOL, console=ttyS0 | 패킷은 정상인데 화면상 부팅이 멈출 때 |
| iPXE shell / UEFI Shell | DNS, URL, 파일 접근, 인터페이스 상태를 클라이언트 측에서 직접 검증할 수 있습니다. | dhcp, ping, imgfetch, ifconfig -l | 서버는 정상인데 특정 펌웨어나 특정 NIC만 실패할 때 |
PXE 패킷 분석 예제
# tcpdump 출력 예제: 정상적인 PXE 부팅 시퀀스
# 1. DHCPDISCOVER
12:00:00.001 IP 0.0.0.0.68 > 255.255.255.255.67: BOOTP/DHCP, Request
Client-Ethernet-Address 00:11:22:33:44:55
Vendor-Class: PXEClient:Arch:00000:UNDI:002001
Parameter-Request: Subnet-Mask(1), Router(3), DNS(6), Domain(15),
TFTP-Server(66), Boot-File(67), Vendor-Class(60), Client-Arch(93)
# 2. DHCPOFFER
12:00:00.005 IP 192.168.1.1.67 > 192.168.1.100.68: BOOTP/DHCP, Reply
Your-IP: 192.168.1.100
Server-IP: 192.168.1.10 ← TFTP 서버
Boot-File: pxelinux.0 ← NBP 파일명
# 3. DHCPREQUEST + DHCPACK (생략)
# 4. TFTP RRQ
12:00:00.050 IP 192.168.1.100.2070 > 192.168.1.10.69: TFTP, RRQ "pxelinux.0"
octet blksize 1468 tsize 0
# 5. TFTP OACK + DATA
12:00:00.051 IP 192.168.1.10.41234 > 192.168.1.100.2070: TFTP, OACK
blksize=1468 tsize=42780
12:00:00.052 IP 192.168.1.100.2070 > 192.168.1.10.41234: TFTP, ACK block 0
12:00:00.053 IP 192.168.1.10.41234 > 192.168.1.100.2070: TFTP, DATA block 1
... (29 더 많은 DATA 블록) ...
# 6. PXELINUX가 설정 파일 요청
12:00:01.100 IP 192.168.1.100.2071 > 192.168.1.10.69: TFTP, RRQ
"pxelinux.cfg/01-00-11-22-33-44-55" ← MAC 기반 설정 시도
12:00:01.101 IP 192.168.1.10.69 > 192.168.1.100.2071: TFTP, ERROR
code=1 File not found ← 없으면 다음 시도
12:00:01.102 IP 192.168.1.100.2072 > 192.168.1.10.69: TFTP, RRQ
"pxelinux.cfg/C0A80164" ← IP 기반 시도
12:00:01.103 IP 192.168.1.10.69 > 192.168.1.100.2072: TFTP, ERROR
code=1 File not found
12:00:01.104 IP 192.168.1.100.2073 > 192.168.1.10.69: TFTP, RRQ
"pxelinux.cfg/default" ← 기본 설정
12:00:01.105 IP 192.168.1.10.41235 > 192.168.1.100.2073: TFTP, DATA block 1
... (설정 파일 전송) ...
# 7. 커널 + initrd 다운로드
12:00:02.000 IP 192.168.1.100.2074 > 192.168.1.10.69: TFTP, RRQ "vmlinuz"
... (대용량 전송) ...
12:00:10.000 IP 192.168.1.100.2075 > 192.168.1.10.69: TFTP, RRQ "initrd.img"
... (대용량 전송) ...
실전 구성 예제
최소 PXE 서버 구성 (5분 설정)
#!/bin/bash
# minimal-pxe-setup.sh - dnsmasq 기반 최소 PXE 서버 설정
set -e
PXE_IFACE="eth0"
PXE_IP="192.168.1.10"
PXE_RANGE="192.168.1.100,192.168.1.200"
TFTP_ROOT="/var/lib/tftpboot"
# 1. 패키지 설치
sudo apt update
sudo apt install -y dnsmasq pxelinux syslinux-common
# 2. TFTP 디렉토리 구성
sudo mkdir -p ${TFTP_ROOT}/pxelinux.cfg
# SYSLINUX 파일 복사
sudo cp /usr/lib/PXELINUX/pxelinux.0 ${TFTP_ROOT}/
sudo cp /usr/lib/syslinux/modules/bios/{ldlinux.c32,menu.c32,libutil.c32,vesamenu.c32,libcom32.c32} ${TFTP_ROOT}/
# 3. dnsmasq 설정
sudo tee /etc/dnsmasq.d/pxe.conf << EOF
# PXE 서버 설정
interface=${PXE_IFACE}
bind-interfaces
# DHCP
dhcp-range=${PXE_RANGE},255.255.255.0,12h
dhcp-option=option:router,${PXE_IP}
dhcp-option=option:dns-server,8.8.8.8
# TFTP
enable-tftp
tftp-root=${TFTP_ROOT}
# PXE
dhcp-boot=pxelinux.0
# 로깅
log-dhcp
log-facility=/var/log/dnsmasq-pxe.log
EOF
# 4. 기본 메뉴 설정
sudo tee ${TFTP_ROOT}/pxelinux.cfg/default << 'EOF'
DEFAULT menu.c32
PROMPT 0
TIMEOUT 300
MENU TITLE PXE Boot Menu
LABEL local
MENU LABEL Boot from Local Disk
MENU DEFAULT
LOCALBOOT 0
EOF
# 5. 서비스 시작
sudo systemctl disable --now systemd-resolved 2>/dev/null || true
sudo systemctl enable --now dnsmasq
echo "PXE 서버 준비 완료!"
echo "커널/initrd를 ${TFTP_ROOT}/에 복사하고 pxelinux.cfg/default를 편집하세요."
전체 스택 구성 (DHCP + TFTP + HTTP + NFS)
#!/bin/bash
# full-pxe-stack.sh - 프로덕션급 PXE 서버 전체 스택
# === 1. dnsmasq (DHCP + TFTP + ProxyDHCP) ===
sudo tee /etc/dnsmasq.d/pxe-full.conf << 'EOF'
# DHCP
dhcp-range=192.168.1.100,192.168.1.200,255.255.255.0,12h
dhcp-option=option:router,192.168.1.1
dhcp-option=option:dns-server,8.8.8.8,8.8.4.4
# TFTP
enable-tftp
tftp-root=/var/lib/tftpboot
# 아키텍처별 자동 분기
dhcp-match=set:bios,option:client-arch,0
dhcp-match=set:efi64,option:client-arch,7
dhcp-match=set:efiaa64,option:client-arch,11
dhcp-match=set:ipxe,175
# iPXE 체인로딩
dhcp-boot=tag:bios,tag:!ipxe,undionly.kpxe
dhcp-boot=tag:efi64,tag:!ipxe,ipxe.efi
dhcp-boot=tag:efiaa64,tag:!ipxe,ipxe-arm64.efi
dhcp-boot=tag:ipxe,http://192.168.1.10/boot.ipxe
log-dhcp
EOF
# === 2. nginx (HTTP 부팅 서버) ===
sudo tee /etc/nginx/sites-available/pxe << 'EOF'
server {
listen 80;
server_name pxe.example.com 192.168.1.10;
root /var/www/pxeboot;
autoindex on;
sendfile on;
tcp_nopush on;
location /boot/ {
alias /var/lib/tftpboot/;
}
}
EOF
sudo ln -sf /etc/nginx/sites-available/pxe /etc/nginx/sites-enabled/
sudo systemctl reload nginx
# === 3. NFS 서버 ===
echo "/exports/diskless 192.168.1.0/24(rw,sync,no_subtree_check,no_root_squash)" \
| sudo tee -a /etc/exports
sudo exportfs -arv
# === 4. iPXE 메인 스크립트 ===
sudo tee /var/www/pxeboot/boot.ipxe << 'IPXE'
#!ipxe
set boot-url http://192.168.1.10
menu Network Boot Menu
item ubuntu Ubuntu 24.04 LTS Install
item diskless Diskless Linux
item local Local Boot
choose --default local --timeout 30000 target && goto ${target} || goto local
:ubuntu
kernel ${boot-url}/images/ubuntu/vmlinuz ip=dhcp autoinstall ds=nocloud-net;s=${boot-url}/autoinstall/
initrd ${boot-url}/images/ubuntu/initrd
boot
:diskless
kernel ${boot-url}/vmlinuz-diskless root=/dev/nfs nfsroot=192.168.1.10:/exports/diskless,vers=4 ip=dhcp rw
initrd ${boot-url}/initrd-diskless.img
boot
:local
exit
IPXE
QEMU/KVM PXE 테스트 환경
QEMU/KVM을 사용하면 물리 서버 없이도 PXE 부팅을 테스트할 수 있습니다. QEMU에는 iPXE ROM이 내장되어 있어 별도 설치 없이 네트워크 부팅이 가능합니다.
# === 1. 기본 QEMU PXE 부팅 (가장 간단) ===
# QEMU의 내장 iPXE ROM으로 PXE 부팅
# -boot n: 네트워크 부팅 우선
qemu-system-x86_64 \
-m 2048 \
-boot n \
-nic user,tftp=/var/lib/tftpboot,bootfile=pxelinux.0 \
-nographic # 시리얼 콘솔 모드
# === 2. 브리지 네트워크 + 실제 PXE 서버 ===
# 실제 네트워크의 DHCP/PXE 서버에 연결
sudo qemu-system-x86_64 \
-m 4096 \
-cpu host -enable-kvm \
-boot order=n \
-device virtio-net-pci,netdev=br0,mac=52:54:00:12:34:56 \
-netdev bridge,id=br0,br=virbr0 \
-drive file=test-disk.qcow2,format=qcow2,if=virtio \
-vnc :1
# === 3. UEFI PXE 부팅 테스트 ===
# OVMF (Open Virtual Machine Firmware) 사용
sudo apt install ovmf
qemu-system-x86_64 \
-m 4096 \
-cpu host -enable-kvm \
-bios /usr/share/OVMF/OVMF_CODE.fd \
-boot order=n \
-device virtio-net-pci,netdev=net0 \
-netdev bridge,id=net0,br=virbr0 \
-drive file=test-uefi.qcow2,format=qcow2,if=virtio
# === 4. iPXE ROM 커스텀 빌드로 교체 ===
# 커스텀 iPXE ROM을 QEMU에서 사용
cd ipxe/src
make bin/8086100e.rom EMBED=embed.ipxe # Intel e1000 ROM
qemu-system-x86_64 \
-m 2048 \
-boot n \
-device e1000,netdev=net0,romfile=bin/8086100e.rom \
-netdev bridge,id=net0,br=virbr0
# === 5. 내부 DHCP 없이 테스트 (user 모드) ===
# QEMU user-mode 네트워킹으로 간단 테스트
qemu-system-x86_64 \
-m 2048 \
-boot n \
-nic user,model=virtio-net-pci,\
tftp=/var/lib/tftpboot,\
bootfile=pxelinux.0,\
dhcpstart=10.0.2.100
# === 6. libvirt/virsh로 PXE VM 생성 ===
virt-install \
--name pxe-test \
--ram 4096 \
--vcpus 2 \
--disk size=20,format=qcow2 \
--network bridge=virbr0,model=virtio \
--pxe \
--boot network,hd \
--os-variant ubuntu24.04 \
--graphics vnc
# === 7. 디버깅: QEMU 모니터에서 네트워크 확인 ===
# Ctrl+Alt+2 로 QEMU 모니터 진입
# (qemu) info network # 네트워크 장치 목록
# (qemu) info pci # PCI 장치 (NIC 확인)
# (qemu) hostfwd_add tcp::5555-:22 # SSH 포트 포워딩
romfile= 옵션으로 직접 빌드한 iPXE ROM을 사용하세요. virtio-net-pci 드라이버가 성능이 가장 좋고, iPXE도 virtio 드라이버를 내장하고 있습니다.
QEMU와 실장비의 차이
QEMU는 PXE 설정 자체를 검증하는 데는 매우 유용하지만, 실제 NIC 칩셋의 pre-boot 문제를 완전히 재현하지는 못합니다. 따라서 QEMU에서 성공했다고 해서 실장비의 PXE가 보장되지는 않습니다.
| 검증 항목 | QEMU/KVM | 실장비 | 의미 |
|---|---|---|---|
| DHCP/TFTP/HTTP 설정 | 잘 재현됩니다. | 그대로 검증됩니다. | 부트 파일명, 경로, iPXE 스크립트 분기 검증에 가장 적합합니다. |
| PHY 링크 협상 | 거의 재현되지 않습니다. | 케이블, 스위치, SFP, 자동 협상 문제가 모두 드러납니다. | Media test failure, 강제 속도 불일치 같은 문제는 실장비에서만 보이는 경우가 많습니다. |
| NIC NVM / Option ROM | 내장 iPXE 또는 지정한 romfile만 사용합니다. | 칩셋별 NVM, BIOS flash, FlexBoot, iNVM이 실제로 관여합니다. | I210, Broadcom MBA, ConnectX FlexBoot 문제는 QEMU만으로 판단하면 안 됩니다. |
| UEFI Secure Boot | OVMF로 기본 경로는 볼 수 있습니다. | OEM db/dbx, 서명된 NIC ROM, 벤더 HII까지 영향받습니다. | OVMF에서 되는데 서버에서만 실패하면 OEM 펌웨어 정책을 먼저 의심해야 합니다. |
| VLAN / 802.1X / DHCP Relay | 단순 NAT나 bridge 구성에서는 제한적으로만 재현됩니다. | 실제 access/trunk, NAC, relay 정책이 모두 작동합니다. | 네트워크 인프라 문제는 가상 환경보다 실장비 캡처가 더 중요합니다. |
# 추천 검증 순서
# 1. QEMU user-mode
# → 부트파일명, iPXE 스크립트, 커널 인자 기본 검증
# 2. QEMU + bridge + OVMF
# → 실제 DHCP/HTTP/TFTP 서버와 UEFI 경로 검증
# 3. 실장비 + 시리얼 콘솔
# → NIC NVM, 링크 협상, 스위치 정책, Secure Boot, BMC 영향 검증
# 4. 칩셋별 도구
# → Intel: ethtool / eeupdate
# → Broadcom: bnxtnvm / B57udiag
# → Mellanox/NVIDIA: mlxconfig
# → Realtek: BIOS 설정 + 필요 시 PG Tool
Preseed 자동 설치 예제 (Debian 13)
# /var/www/pxeboot/preseed/debian13.cfg
# Debian 13 Trixie 무인 설치 (Preseed)
# 로케일 및 키보드
d-i debian-installer/locale string en_US.UTF-8
d-i keyboard-configuration/xkb-keymap select us
# 네트워크
d-i netcfg/choose_interface select auto
d-i netcfg/get_hostname string debian-server
d-i netcfg/get_domain string example.com
# 미러 (로컬 미러 사용 시)
d-i mirror/country string manual
d-i mirror/http/hostname string 192.168.1.10
d-i mirror/http/directory string /debian
d-i mirror/http/proxy string
# 시간대
d-i time/zone string Asia/Seoul
d-i clock-setup/utc boolean true
d-i clock-setup/ntp boolean true
# 파티셔닝 (UEFI + GPT, 전체 디스크 LVM)
d-i partman-efi/non_efi_system boolean true
d-i partman-partitioning/choose_label select gpt
d-i partman-partitioning/default_label string gpt
d-i partman-auto/method string lvm
d-i partman-lvm/device_remove_lvm boolean true
d-i partman-md/device_remove_md boolean true
d-i partman-lvm/confirm boolean true
d-i partman-lvm/confirm_nooverwrite boolean true
d-i partman-auto/choose_recipe select atomic
d-i partman-partitioning/confirm_write_new_label boolean true
d-i partman/choose_partition select finish
d-i partman/confirm boolean true
d-i partman/confirm_nooverwrite boolean true
# 루트 비밀번호 (해시)
d-i passwd/root-password-crypted password $6$rounds=656000$...
# 사용자 계정
d-i passwd/user-fullname string Admin User
d-i passwd/username string admin
d-i passwd/user-password-crypted password $6$rounds=656000$...
# 패키지
d-i pkgsel/include string openssh-server vim tmux nfs-common open-iscsi
tasksel tasksel/first multiselect standard, ssh-server
d-i pkgsel/upgrade select full-upgrade
popularity-contest popularity-contest/participate boolean false
# 부트로더 (GRUB)
d-i grub-installer/only_debian boolean true
d-i grub-installer/bootdev string default
# 설치 완료 후
d-i finish-install/reboot_in_progress note
d-i preseed/late_command string \
mkdir -p /target/root/.ssh; \
wget -O /target/root/.ssh/authorized_keys http://192.168.1.10/keys/admin.pub; \
chmod 600 /target/root/.ssh/authorized_keys; \
in-target systemctl enable ssh
preseed/url 또는 그 축약형 url=을 사용합니다. UEFI 서버를 기본 대상으로 둘 때는 partman-efi/non_efi_system과 GPT 라벨 선택을 같이 넣어 두는 편이 BIOS/UEFI 혼합 환경에서 덜 헷갈립니다.
Raspberry Pi 네트워크 부팅
# Raspberry Pi 4는 UEFI가 아닌 자체 네트워크 부팅 메커니즘 사용
# BCM2711 SoC의 부트 ROM이 직접 DHCP + TFTP 수행
# 1. Raspberry Pi의 시리얼 번호 확인
# Pi에서: cat /proc/cpuinfo | grep Serial
# 예: Serial : 00000000abcd1234 → 마지막 8자리: abcd1234
# 2. TFTP 디렉토리 구조 (시리얼 번호 기반)
/var/lib/tftpboot/
├── abcd1234/ # Pi 시리얼 번호
│ ├── start4.elf # GPU 펌웨어
│ ├── fixup4.dat # GPU 메모리 수정
│ ├── bcm2711-rpi-4-b.dtb
│ ├── config.txt # 부팅 설정
│ ├── cmdline.txt # 커널 커맨드 라인
│ └── kernel8.img # ARM64 커널
└── overlays/ # DT 오버레이
# 3. config.txt (네트워크 루트)
cat > /var/lib/tftpboot/abcd1234/config.txt << 'EOF'
arm_64bit=1
kernel=kernel8.img
initramfs initrd.img followkernel
EOF
# 4. cmdline.txt
echo "console=serial0,115200 console=tty1 root=/dev/nfs \
nfsroot=192.168.1.10:/exports/pi-root,vers=4,tcp ip=dhcp rw \
rootwait" > /var/lib/tftpboot/abcd1234/cmdline.txt
# 5. dnsmasq 설정 (Pi 전용)
# Pi는 Vendor Class를 보내지 않으므로, MAC 또는 태그로 분기
dhcp-host=dc:a6:32:xx:xx:xx,set:rpi
dhcp-boot=tag:rpi,abcd1234/start4.elf
pxe-service=0,"Raspberry Pi Boot"
Windows WDS/MDT 참고
Windows 배포 서비스 (WDS)
Windows Deployment Services(WDS)는 Microsoft의 PXE 기반 OS 배포 솔루션입니다. 리눅스 PXE 환경과의 호환성을 이해하는 것이 중요합니다.
| 항목 | Linux PXE | Windows WDS |
|---|---|---|
| DHCP | ISC DHCP / dnsmasq | Windows DHCP Server |
| TFTP | tftpd-hpa / dnsmasq | WDS TFTP 내장 |
| BIOS NBP | pxelinux.0 | wdsnbp.com → pxeboot.com |
| UEFI NBP | grubx64.efi | wdsmgfw.efi → bootmgfw.efi |
| 이미지 형식 | vmlinuz + initrd | WIM (Windows Imaging) |
| 자동 설치 | Kickstart/Preseed | unattend.xml |
| 멀티캐스트 | atftpd/UDPcast | WDS 멀티캐스트 내장 |
Linux + Windows 듀얼 PXE 환경
# iPXE 스크립트로 Linux/Windows 선택
#!ipxe
menu Dual Boot PXE Menu
item linux Linux Installation
item windows Windows Installation (WDS)
choose --default linux --timeout 20000 target && goto ${target}
:linux
chain http://192.168.1.10/linux-boot.ipxe
:windows
# WDS 서버로 체인로딩
# iPXE가 WDS의 wdsnbp.com을 로드하여 Windows 설치 시작
set next-server 192.168.1.20 # WDS 서버
chain tftp://${next-server}/boot/x64/wdsnbp.com
리눅스 커널 소스 분석
네트워크 부팅 관련 커널 소스 파일
| 파일 | 역할 |
|---|---|
net/ipv4/ipconfig.c | 커널 IP 자동 설정 (DHCP/BOOTP/RARP). ip= 파라미터 처리 |
fs/nfs/nfsroot.c | NFS 루트 마운트(Mount). nfsroot= 파라미터 처리 |
init/do_mounts.c | 루트 파일시스템 마운트 (root=/dev/nfs 감지) |
drivers/firmware/efi/libstub/ | EFI Stub: UEFI에서 직접 커널 부팅 |
drivers/net/ethernet/ | 이더넷 드라이버 (부팅 시 네트워크 초기화) |
include/linux/nfs_fs.h | NFS 클라이언트 헤더 |
drivers/scsi/iscsi_tcp.c | iSCSI TCP 트랜스포트 |
drivers/infiniband/ulp/iser/ | iSCSI over RDMA (iSER) |
네트워크 부팅 관련 커널 설정
# make menuconfig에서 네트워크 부팅에 필요한 옵션
# Networking → Networking options
CONFIG_IP_PNP=y # IP: kernel level autoconfiguration
CONFIG_IP_PNP_DHCP=y # IP: DHCP support
CONFIG_IP_PNP_BOOTP=y # IP: BOOTP support
CONFIG_IP_PNP_RARP=y # IP: RARP support
# File systems → Network File Systems
CONFIG_NFS_FS=y # NFS client support (built-in, NOT module!)
CONFIG_NFS_V3=y # NFS client support for NFS version 3
CONFIG_NFS_V4=y # NFS client support for NFS version 4
CONFIG_ROOT_NFS=y # Root file system on NFS
# Block devices → Network block devices
CONFIG_BLK_DEV_NBD=m # Network Block Device
CONFIG_ISCSI_TCP=m # iSCSI Initiator over TCP/IP
# 네트워크 드라이버 (부팅에 사용하는 NIC는 built-in 필수!)
CONFIG_E1000E=y # Intel(R) PRO/1000 PCI-Express
CONFIG_IGB=y # Intel(R) 82575/82576 PCI-Express
CONFIG_IXGBE=y # Intel(R) 10GbE PCI Express
CONFIG_MLX5_CORE=y # Mellanox ConnectX
CONFIG_R8169=y # Realtek RTL8169
CONFIG_BNX2X=y # Broadcom NetXtreme II 10Gb
# initramfs 사용 시 NFS/iSCSI를 모듈로 컴파일해도 됨
# 단, initramfs에 해당 모듈을 포함해야 함
커널 IP 자동 설정 흐름
/* net/ipv4/ipconfig.c - 커널 네트워크 자동 설정 전체 흐름 */
/*
* 호출 순서:
* start_kernel()
* → kernel_init()
* → kernel_init_freeable()
* → do_basic_setup()
* → prepare_namespace()
* → ip_auto_config() ← ip= 파라미터 처리
* → mount_root() ← root= 파라미터 처리
* → nfs_root_setup() ← root=/dev/nfs 시
*/
static int __init ip_auto_config(void)
{
/* 1. 네트워크 인터페이스 탐색 */
ic_open_devs(); /* 모든 이더넷 디바이스 열기 */
/* 2. DHCP/BOOTP/RARP 중 활성화된 프로토콜로 IP 설정 */
if (ic_enable) {
/* DHCP 패킷 송신 */
ic_bootp_init();
/* 응답 대기 (최대 ~60초) */
ic_dynamic();
/* IP, 게이트웨이, DNS, NFS root path 등 설정 */
ic_setup_if(); /* 인터페이스 IP 설정 */
ic_setup_routes(); /* 라우팅 테이블 설정 */
}
/* 3. 결과 출력 */
pr_info("IP-Config: Complete:\n");
pr_info(" device=%s, hwaddr=%pM, ipaddr=%pI4, "
"mask=%pI4, gw=%pI4\n",
ic_dev->name, ic_dev->dev_addr,
&ic_myaddr, &ic_netmask, &ic_gateway);
pr_info(" host=%s, domain=%s, nis-domain=%s\n",
utsname()->nodename, ic_domain, utsname()->domainname);
pr_info(" bootserver=%pI4, rootserver=%pI4, rootpath=%s\n",
&ic_servaddr, &root_server_addr, root_server_path);
return 0;
}
클라우드 & 베어메탈 PXE 패턴
클라우드 환경의 PXE 부팅
퍼블릭 클라우드의 베어메탈 서비스와 프라이빗 클라우드 인프라에서 PXE는 서버 프로비저닝의 핵심 메커니즘입니다.
| 플랫폼 | PXE 방식 | 자동화 | 특징 |
|---|---|---|---|
| Equinix Metal | iPXE + Custom OS | API + iPXE 스크립트 | 사용자 iPXE 스크립트 URL 지정 가능. 커스텀 OS 부팅 지원 |
| AWS Outposts | PXE + EC2 이미지 | SSM + CloudFormation | 온프레미스 AWS 인프라에서 PXE 부팅 |
| Oracle BM | iPXE | OCI API | 베어메탈 인스턴스에 iPXE 스크립트 제공 |
| Hetzner | DHCP + iPXE | Robot API | rescue 시스템 + installimage 스크립트 |
| OpenStack Ironic | PXE/iPXE + IPA | Nova API | Ironic Python Agent로 디스크 기록 |
| MAAS (Canonical) | PXE + cloud-init | REST API + CLI | 자동 발견 → commissioning → 배포 |
| Tinkerbell (Equinix) | iPXE + Workflow | gRPC API | 클라우드 네이티브, Kubernetes 기반 |
클라우드 베어메탈 iPXE 커스텀 부팅 예제
# Equinix Metal 스타일: 사용자 제공 iPXE 스크립트로 커스텀 OS 부팅
#!ipxe
# 서버의 메타데이터 조회 (클라우드 API에서 제공)
# Equinix Metal은 metadata.packet.net 제공
# chain https://metadata.packet.net/ipxe?uuid=${uuid}&mac=${mac}
# 또는 직접 스크립트 작성:
set base-url https://my-pxe-server.example.com
# 호스트 메타데이터 기반 분기
chain ${base-url}/api/v1/boot?uuid=${uuid}&serial=${serial}&mac=${mac:hexhyp} || goto fallback
:fallback
# Flatcar Container Linux 부팅 (CoreOS 후속)
set coreos-url https://stable.release.flatcar-linux.net/amd64-usr/current
kernel ${coreos-url}/flatcar_production_pxe.vmlinuz \
flatcar.first_boot=1 \
flatcar.config.url=${base-url}/ignition/${uuid}.json \
ip=dhcp
initrd ${coreos-url}/flatcar_production_pxe_image.cpio.gz
boot
// Ignition 설정 예제 (Flatcar/CoreOS 자동 프로비저닝)
// /var/www/pxeboot/ignition/default.json
{
"ignition": { "version": "3.4.0" },
"storage": {
"filesystems": [{
"device": "/dev/sda1",
"format": "ext4",
"path": "/",
"wipeFilesystem": true
}]
},
"systemd": {
"units": [{
"name": "docker.service",
"enabled": true
}]
},
"passwd": {
"users": [{
"name": "core",
"sshAuthorizedKeys": ["ssh-ed25519 AAAA... admin@example.com"],
"groups": ["docker", "sudo"]
}]
}
}
OpenStack Ironic PXE 배포 흐름
# OpenStack Ironic 베어메탈 노드 등록 및 배포
# 1. 노드 등록
openstack baremetal node create \
--driver ipmi \
--driver-info ipmi_address=10.0.0.100 \
--driver-info ipmi_username=admin \
--driver-info ipmi_password=secret \
--driver-info deploy_kernel=file:///var/lib/ironic/ipa-kernel \
--driver-info deploy_ramdisk=file:///var/lib/ironic/ipa-ramdisk \
--property cpus=32 --property memory_mb=65536 \
--property local_gb=500
# 2. 포트(NIC) 등록
openstack baremetal port create \
--node <node-uuid> \
00:11:22:33:44:55 # PXE 부팅용 MAC 주소
# 3. 이미지 기반 배포
openstack baremetal node deploy <node-uuid> \
--instance-info image_source=<glance-image-uuid> \
--instance-info root_gb=50
# Ironic이 자동으로:
# 1) IPMI로 서버 전원 ON
# 2) PXE 부팅 → IPA 로드
# 3) IPA가 이미지 다운로드 → 디스크 기록
# 4) PXE에서 로컬 부팅으로 전환
# 5) 서버 재부팅 → OS 가동
흔한 실수
BIOS 모드 클라이언트에 EFI 바이너리(
.efi)를, UEFI 클라이언트에 BIOS 바이너리(.0)를 제공하면 부팅 실패합니다.
DHCP Option 93을 확인하여 올바른 바이너리를 제공하세요.
TFTP는
tftp-root를 기준으로 경로를 해석합니다. 절대 경로(/var/lib/tftpboot/pxelinux.0)가 아니라 상대 경로(pxelinux.0)를 사용해야 합니다.
PXE에 필요한 포트: UDP 67/68(DHCP), UDP 69(TFTP), UDP 4011(ProxyDHCP), TCP 2049(NFS v4), TCP 3260(iSCSI), TCP 80/443(HTTP/HTTPS). 특히 TFTP는 데이터 전송 시 랜덤 고번호 포트를 사용하므로 conntrack 모듈이 필요합니다:
modprobe nf_conntrack_tftp
NFS 루트 부팅 시 NIC 드라이버와 NFS 클라이언트가 커널에 built-in(
=y)이거나, initramfs에 모듈이 포함되어야 합니다. 모듈(=m)만 있고 initramfs에 없으면 네트워크를 활성화할 수 없어 루트를 마운트할 수 없습니다.
iPXE 체인로딩 설정에서 iPXE와 일반 PXE를 구분하지 않으면 무한 루프가 발생합니다. ISC dhcpd 예제는 보통
user-class = "iPXE"를, dnsmasq 예제는 Option 175를 자주 사용합니다. 어느 방식을 쓰든 "이미 iPXE에 들어온 클라이언트"를 한 번만 식별해 HTTP 스크립트로 넘겨야 합니다.
PXELINUX를 사용할 때
ldlinux.c32, libutil.c32, libcom32.c32 등 필수 COM32 모듈을 TFTP 루트에 복사하지 않으면 메뉴가 표시되지 않습니다. SYSLINUX 버전과 모듈 버전이 일치해야 합니다.
표준 및 공식 문서
PXE는 구현 편차가 큰 분야라서 블로그 글보다 표준 문서와 공식 구현 문서를 같이 보는 편이 안전합니다. 아래 문서들은 이 페이지(Page)를 보강하면서 직접 대조한 기준 자료입니다.
- RFC 2131 — DHCP 본체 사양
- RFC 2132 — DHCP Options and BOOTP Vendor Extensions. Option 66/67 해석 근거
- RFC 4578 — PXE 관련 DHCP 옵션(Option 93/94/97) 정의
- RFC 5970 — DHCPv6 부트 옵션(59/60/61/62) 정의
- RFC 7440 — TFTP windowsize 옵션
- IANA Processor Architecture Types — 최신 아키텍처 코드 레지스트리
- UEFI 2.11: Network Protocols — PXE Base Code, netboot6, HTTP Boot 규정
- iPXE 공식 DHCP 가이드 — BIOS/UEFI 체인로딩 패턴
- iPXE 공식 Chainloading 가이드 —
undionly.kpxe,ipxe.efi,snponly.efi선택 기준 - iPXE Crypto 문서 — HTTPS/TLS, 인증서, 암호 알고리즘 지원 범위
- Intel I210/I211 FAQ — pre-boot ROM 저장 위치, iNVM, SGMII 이미지, power cycle 주의점
- Intel Ethernet Controllers and PHYs Brochure — I210/I211 SKU와 인터페이스, PXE/iSCSI Boot 지원 범위
- Linux kernel nfsroot 문서 —
root=/dev/nfs,nfsroot=,ip=구문 - RHEL 10 Kickstart 문서 — PXE / UEFI HTTP Boot에서
inst.ks=사용법 - RHEL 10 Kickstart 생성 문서 —
ksvalidator -v RHEL10검증 흐름 - RHEL 10 설치 트러블슈팅 문서 —
inst.debug,/tmp/pre-anaconda-logs/,/tmp/anaconda.log위치 - Ubuntu Autoinstall Quick Start —
user-data,meta-data,ds=nocloud-net전달 방식 - Ubuntu Autoinstall Reference — 24.04 이후 최상위
autoinstall:및 스키마 검증 규칙 - cloud-init user-data 디버깅 문서 —
cloud-init schema --config-file ... --annotate검증 절차 - Ubuntu Server Netboot 문서 — UEFI/BIOS PXE에서 live server installer 부팅 절차
- Debian stable 릴리스 정보 — 2026-03-07 기준 stable이 Debian 13 (trixie)임을 확인
- Debian Installer Preseed 가이드 —
preseed/url,auto=true,priority=critical커널 인자 - Debian example-preseed / EFI 가이드 — UEFI용 GPT/EFI
partman옵션 예시 - Debian 13 설치 가이드 — 설치 중
/var/log/, 설치 후/var/log/installer/확인 지점 - Linux 커널 문서 — IP Sysctl — 커널 네트워크 파라미터 중 DHCP/IP autoconfiguration 관련 설정
- Linux 커널 문서 — NFS Root —
ip=커널 파라미터와 NFS 루트 마운트 절차 - 커널 소스 — net/ipv4/ipconfig.c — 커널 내장 DHCP/BOOTP/RARP 자동설정 구현
- 커널 소스 — efi-stub-helper.c — UEFI HTTP Boot 시 EFI Stub의 네트워크 핸드오프 코드
- LWN — UEFI HTTP Boot (2014) — UEFI HTTP Boot 사양 도입 배경과 Linux 지원 분석
- LWN — Network booting with iPXE (2021) — iPXE 스크립팅과 최신 네트워크 부팅 워크플로 소개
- RFC 1350 — TFTP 프로토콜 사양 (Trivial File Transfer Protocol)
- RFC 2347 — TFTP Option Extension. blksize, tsize 등 옵션 협상 프레임워크
관련 문서
이 주제와 관련된 다른 문서를 더 깊이 이해하고 싶다면 다음을 참고하세요.