一般来说,我们要搭建一个正式的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)

Float Left

这个是代码报错,我们需要修改一下

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 脚本,然后客户安装,当加载了vmlinuzinitrd之后会进入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

这样修改后,就可以正常安装了。

服务器启动:

Float Left

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

Float Left

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

Float Left