一般来说,我们要搭建一个正式的pxe自动装机系统,需要装 dnsmasq 做 dhcp + tftp ,需要编译 ipxe 来获得 undionly.kpxe ,需要 http 服务器来提供资源下载,repo 同步服务来提供 repo。组件非常多,也比较麻烦。
当然,这么多也是有必要的,因为可以持续提供一个稳定的装机系统。
场景一换,如果我们在本地机房里,什么都没有,想搭一套环境的步骤就比较繁复了。
PyPXE 就是非常简单的一个程序,居然自己实现了用于 PXE 的 dhcp、tftp 和 http 全部的功能,而且支持 iPXE。
太牛逼了,前提啊,PyPXE 是基于 Python 2.7 的,Python 3.x是运行不了的。
想让它跑起来还必须做一定的修改,步骤如下:
一、下载PyPXE
git clone https://github.com/pypxe/PyPXE.git
cd PyPXE
下载就行了,不用安装。
二、手动生成config.json配置文件
{
"DHCP_SERVER_IP": "192.168.85.27",
"DHCP_FILESERVER": "192.168.85.27",
"DHCP_OFFER_BEGIN": "192.168.85.200",
"DHCP_OFFER_END": "192.168.85.250",
"DHCP_SUBNET": "255.255.255.0",
"DHCP_ROUTER": "192.168.85.1",
"DHCP_DNS": "114.114.114.114",
"DHCP_SERVER_PORT": 67,
"DHCP_BROADCAST": "",
"DHCP_MODE_PROXY": false,
"DHCP_WHITELIST": false,
"HTTP_PORT": 80,
"LEASES_FILE": "",
"MODE_DEBUG": "dhcp",
"MODE_VERBOSE": "",
"NBD_BLOCK_DEVICE": "",
"NBD_COPY_TO_RAM": false,
"NBD_COW": true,
"NBD_COW_IN_MEM": false,
"NBD_PORT": 10809,
"NBD_SERVER_IP": "0.0.0.0",
"NBD_WRITE": false,
"NETBOOT_DIR": "netboot",
"NETBOOT_FILE": "boot.http.ipxe",
"STATIC_CONFIG": "",
"SYSLOG_PORT": 514,
"SYSLOG_SERVER": null,
"USE_DHCP": true,
"USE_HTTP": true,
"USE_IPXE": true,
"USE_TFTP": true
}
上面json文件无法加注解,我们把它分三部分
本机配置,本机的地址都是 192.168.85.27
dhcp 的配置,开始192.168.85.200,结束192.68.85.250,掩码255.255.255.0,网关192.168.85.1,DNS114.114.114.114
第三部分不用动
三、下载ISO并修改ipxe脚本
cd netboot
wget http://mirrors.163.com/rocky/8/isos/x86_64/Rocky-8.4-x86_64-dvd1.iso
mkdir rocky8.iso
mount -o loop Rocky-8.4-x86_64-dvd1.iso rocky8.iso
cat << EOF >> boot.http.ipxe
#!ipxe
:start
menu PXE Boot Options
item shell iPXE shell
item Rocky8 Install rocky8
item exit Exit to BIOS
choose --default rocky8 --timeout 5000 option && goto ${option}
:shell
shell
:rocky8
set root http://192.168.85.27/rocky8.iso
initrd ${root}/images/pxeboot/initrd.img
kernel ${root}/images/pxeboot/vmlinuz inst.repo=${root}/ initrd=initrd.img ip=dhcp
boot
:exit
exit
EOF
三、修改源代码
运行一下:
python -m pypxe.server --config config.json --debug all --verbose all
如果我们起一台机器或者虚机,会报第一个错:
UnicodeDecodeError: ‘ascii’ codec can’t decode byte 0xc0 in position 0: ordinal not in range(128)

这个是代码报错,我们需要修改一下
vi pypxe/dhcp.py
def tlv_encode(self, tag, value):
'''Encode a TLV option.'''
# 注释掉下面的两行,我们不需要打印出我们一定能看懂的字符,都按bytes处理即可
#if type(value) is str:
# value = value.encode('ascii')
value = bytes(value)
return struct.pack('BB', tag, len(value)) + value
然后我们需要修改第二个地方,理由是这个 PyPXE 会判断 Client 发过来的 dhcp 请求,它只实现了针对PXE-Client的 Vendor-class:
所以我们也要屏蔽一下,否则按照正常过程
客户端dhcp –> PyPXE 后,PyPXE 送回客户 ipxe 脚本,然后客户安装,当加载了vmlinuz和initrd之后会进入anaconda-linux进行系统安装,过程中会再次向DHCP服务器申请IP地址, 这个时候他向DHCP Server发出的discover申请是得不到回复的,因此安装过程将被打断。
vi pypxe/dhcp.py
def validate_req(self, client_mac):
# client request is valid only if contains Vendor-Class = PXEClient
'''代码整个注释掉,直接返回 True
if self.whitelist and self.get_mac(client_mac) not in self.get_namespaced_static('dhcp.binding'):
self.logger.info('Non-whitelisted client request received from {0}'.format(self.get_mac(client_mac)))
return False
if 60 in self.options[client_mac] and 'PXEClient'.encode() in self.options[client_mac][60][0]:
self.logger.info('PXE client request received from {0}'.format(self.get_mac(client_mac)))
return True
self.logger.info('Non-PXE client request received from {0}'.format(self.get_mac(client_mac)))
return False
'''
return True
这样修改后,就可以正常安装了。
服务器启动:

客户端启动pxe开始安装,看下面,系统的ipxe dhcp一次,然后chainload.kpxe 又一次,anaconda 又一次,最少会发三次或更多的dhcp请求。

用 VNC 连进去可以看到安装画面,如果是 kickstart 就是全自动安装了。
