CentOS 8上搭建PXE网络装机环境:dnsmasq+nginx实现CentOS 7/8双系统自动化安装

发布时间:2026/10/17 21:49:59
CentOS 8上搭建PXE网络装机环境:dnsmasq+nginx实现CentOS 7/8双系统自动化安装 前几天又到了一批新测试机。这批机器里一部分要给老业务跑 CentOS 7一部分要装 CentOS 8 当新环境。要是一台台插 U 盘光是刻盘、改启动顺序、盯着安装界面就能耗掉一整天。所以这次我直接在一台 CentOS 8 服务器上把 PXE 自动化安装环境搭了起来DHCP、TFTP、HTTP 三个服务协同同一套引导菜单里同时提供 CentOS 7 和 CentOS 8 两个入口网卡一引导就能自动进入安装。本文记录这套环境的完整搭建过程、每一份关键配置以及我实测中踩过的坑和排错思路。无论你是刚接触网络装机还是已经在用 PXE 的老手这套双系统共存的方案应该都能给你一些参考。1. 先搞懂PXE的完整链路再动手不迟1.1 PXE装机到底分几步走PXE 本质上不是一个工具而是一套协议组合。很多人配置半天没成功就是因为它涉及的服务太多搞不清每一段到底是谁在干活。我习惯把它拆成四个阶段来理解。第一阶段是 DHCP 发现。客户端开机的瞬间网卡固件向网络广播“我需要一个 IP”DHCP 服务不仅要回 IP还要顺带告诉它两个关键信息引导文件叫什么名字filename以及去哪里下载这个文件next-server。第二阶段是 TFTP 下载。客户端拿到地址后通过 TFTP 协议从 next-server 下载引导程序。传统 BIOS 机器拿到的是 pxelinux.0它相当于一个小型操作系统负责后续的加载流程。pxelinux.0 会继续去寻找它的配置文件默认是 pxelinux.cfg/default也支持按 MAC 或 IP 匹配专属配置后面批量自动化会用到这个特性。第三阶段是内核加载。配置菜单里写了 vmlinuz 和 initrd.img 的路径pxelinux.0 会把这两个文件从 TFTP 服务下载下来然后启动内核。注意这个阶段客户端其实还没有完整的安装环境它只是把安装器的内核和内存盘拉了起来。第四阶段才是真正开始“装系统”。内核起来后运行的 anaconda 安装器会从 pre-defined 的 HTTP 地址拉取安装源repo和应答文件ks.cfg。repo 是软件包仓库ks.cfg 是我们的自动化安装剧本。用生活化的类比就是DHCP 像是一个前台接待它告诉你怎么走TFTP 像是传菜员它只送菜单和餐前小菜HTTP 才是真正的厨房不管是 CentOS 7 还是 CentOS 8 的“食材”都从这里出。搞明白这张分工图后面排错就是按图索骥。1.2 为什么我选“dnsmasq nginx”这个组合我见过不少教程推荐用 isc-dhcp-server tftp-server httpd 三个独立服务来搭。这套组合当然没问题但配置分散在三四个配置文件里哪一环挂了都得单独查。这次我做双系统共存不想为这种事情分心所以选了 dnsmasq nginx。dnsmasq 一个进程同时搞定 DHCP 和 TFTP。它在小规模内网里非常常见配置文件语法简单还内置了 tftp 服务。配合 nginx 做静态文件服务装机和提供安装源都足够稳定。相比 httpdnginx 配置更短对静态文件的支持也毫不含糊。如果你已经有生产级 DHCP 服务器在运行不想动它那完全可以只保留现有的 DHCP把 dnsmasq 里的 DHCP 关掉只开 TFTP这也是可行的。但要注意别让两个 DHCP 服务在同一个网段互相打架否则客户端收到的 IP 很混乱PXE 也起不来。1.3 网络和目录规划动工前的关键表格在一条命令都没执行之前我先把整个环境的地址和目录规划清楚。这次使用的环境是纯内网服务端 IP 固定在 192.168.1.10客户端和它处在同一个二层网段。角色协议/端口服务值/位置DHCP/TFTP67/udp、69/udpdnsmasq地址池 192.168.1.100 - 200HTTP 镜像站80/tcpnginx根目录 /data/pxeCentOS 7 安装源HTTPISO 挂载/data/pxe/centos/7CentOS 8 安装源HTTPISO 挂载/data/pxe/centos/8应答文件HTTPnginx/data/pxe/ks/TFTP 根69/udpdnsmasq/var/lib/tftpboot这里多数读者容易忽略的是TFTP 根目录和 HTTP 根目录是两套东西。TFTP 里放的是引导文件和内核HTTP 里放的是安装镜像和 ks。两个根不要混在一起不然你会在“明明文件存在却加载不到”的诡异问题里浪费很久。2. 服务端搭建DHCP/TFTP/HTTP一次跑通2.1 安装软件包先避开 syslinux 这个坑在 CentOS 8 上装包的指令很直接dnf install -y dnsmasq nginx这里有个坑必须提前说网上很多老教程会让你yum install syslinux但 CentOS 8 默认仓库里已经没有 syslinux 了直接执行大概率会得到“Unable to find a match”的报错。pxelinux.0 这个关键的引导文件就来自 syslinux 包所以你还需要先用 EPEL 把它的仓库补上dnf install -y epel-release dnf install -y syslinux安装完成后确认这几个文件是否就位ls -l /usr/share/syslinux/pxelinux.0 /usr/share/syslinux/vesamenu.c32另外CentOS 8 默认没有现成的 /var/lib/tftpboot 目录需要自己创建后续 dnsmasq 的 tftp-root 就指向这里。2.2 dnsmasq 配置逐行拆解我最推荐的写法是直接把配置追加到 /etc/dnsmasq.conf 末尾。这样最省心不用去考虑 conf-dir 是否生效的问题。内容如下# 局域网内已有其他 DNS 服务时可以加 port0 禁用 dnsmasq 的 DNS # port0 # 指定 DHCP 地址池和租期12h 足够一次安装 dhcp-range192.168.1.100,192.168.1.200,12h # 给客户端分配的网关与 DNS dhcp-optionoption:router,192.168.1.1 dhcp-optionoption:dns-server,192.168.1.10 # 关键告诉客户端引导文件名和 TFTP 服务端地址 dhcp-bootpxelinux.0,192.168.1.10 # 开启内嵌 TFTP并指定根目录 enable-tftp tftp-root/var/lib/tftpboot解释几个容易踩坑的点。dhcp-bootpxelinux.0,192.168.1.10这里的第二个参数是 next-server 地址也就是客户端去哪个服务器下载引导文件。如果你的客户端显示“TFTP open timeout”一半以上的情况是这里没写对或防火墙把 69 端口挡了。TFTP 根目录默认路径是 /var/lib/tftpboot这不是我随手选的。CentOS 8 的 SELinux 对这个路径有内置的访问策略dnsmasq 去读它比较顺畅。如果你换成自定义路径后面还要额外处理 SELinux 类型白白增加工作量。配置写完后用下面的命令检查语法并启动dnsmasq --test systemctl enable --now dnsmasq如果启动失败第一时间看 journalctl -u dnsmasq错误信息一般都会提示具体行号。2.3 nginx 镜像站与 ISO 挂载把安装镜像放到 HTTP 服务下之前需要先挂载 ISO。我习惯在 /data/iso 下保存原始镜像文件然后统一用 loop 方式挂载到 HTTP 根目录mkdir -p /data/iso /data/pxe/centos/7 /data/pxe/centos/8 mount -o loop,ro /data/iso/CentOS-7-x86_64-DVD.iso /data/pxe/centos/7 mount -o loop,ro /data/iso/CentOS-8.5.2111-x86_64-dvd1.iso /data/pxe/centos/8注意看两个镜像内部的目录结构这是双系统共存方案的难点之一。CentOS 7 的 DVD 根目录下直接就是 Packages 和 repodata所以 HTTP 源直接指到 /data/pxe/centos/7 就能用。CentOS 8 的 DVD 根目录下没有独立的 repodata而是把软件仓库拆成了 BaseOS 和 AppStream 两个子目录。也就是说CentOS 8 的安装源必须精确指向 /data/pxe/centos/8/BaseOS/x86_64/os/ 这一层AppStream 需要单独在 ks 里再声明一次。nginx 的配置很简单一个 server 块就够server { listen 80; server_name _; root /data/pxe; autoindex on; charset utf-8; }autoindex on是为了调试方便。装到一半发现找不到镜像源时直接在浏览器里打开 http://192.168.1.10/centos/8/BaseOS/x86_64/os/ 就能看到目录结构问题出在哪一目了然。光挂载不够重启后这些 loop 挂载会丢失。我一般把挂载命令写进一个脚本再交给 systemd 来管理或者直接写进 /etc/fstab。如果你选择 fstab示例是/data/iso/CentOS-7-x86_64-DVD.iso /data/pxe/centos/7 iso9660 loop,ro 0 0 /data/iso/CentOS-8.5.2111-x86_64-dvd1.iso /data/pxe/centos/8 iso9660 loop,ro 0 0注意文件名必须和实际完全一致否则重启后镜像源就“消失”了到时候排查起来很痛苦。2.4 Firewalld 和 SELinux 两个拦路虎服务都装好后火墙和 SELinux 是很多人最终被卡住的地方。CentOS 8 默认 firewalld 是开启的需要放行三个服务firewall-cmd --permanent --add-servicedhcp firewall-cmd --permanent --add-servicetftp firewall-cmd --permanent --add-servicehttp firewall-cmd --reloadfirewalld 的 tftp 服务默认对应 69/udp并且它预置的规则对 /var/lib/tftpboot 目录的开场是友好的这也是我坚持用默认 TFTP 根的原因。SELinux 方面如果 HTTP 根目录用的是自定义路径需要显式把目录标记为 Web 服务可读chcon -R -t httpd_sys_content_t /data/pxe如果要让这个标记在重启后依然生效用 semanage 写入持久化规则semanage fcontext -a -t httpd_sys_content_t /data/pxe(/.*)? restorecon -Rv /data/pxe如果安装过这些命令但 semanage 提示不存在先dnf install -y policycoreutils-python-utils。一旦 SELinux 拦截别急着一下把 enforcing 关掉先看审计日志grep avc /var/log/audit/audit.log | tail -20看到类似 denied 的记录再结合是哪个进程被拦基本就知道是目录标记还是端口策略的问题。3. 两代系统共存的PXE引导目录与启动菜单3.1 引导文件从哪里来PXE 引导的第一步是下载 pxelinux.0但这个文件单独一个还不行它还需要一堆配套的 .c32 模块特别是菜单界面需要的 vesamenu.c32。缺了这些客户端可能会停在黑屏或者直接报错找不到文件。把需要的文件一次性复制到 TFTP 根目录mkdir -p /var/lib/tftpboot/pxelinux.cfg cp /usr/share/syslinux/pxelinux.0 /var/lib/tftpboot/ cp /usr/share/syslinux/*.c32 /var/lib/tftpboot/然后从两个 ISO 里拷贝内核和内存盘。注意路径差异CentOS 7 的 vmlinuz 和 initrd.img 通常位于 ISO 的 isolinux 目录下而 CentOS 8 的位于 images/pxeboot 目录下。实际操作时按这个思路拷贝即可如果发现文件不在预期路径挂载 ISO 后 find 一下就能找到。3.2 TFTP 根目录和 HTTP 目录的布局摆放好后整个目录结构大致长这样/var/lib/tftpboot/ ├── pxelinux.0 ├── ldlinux.c32 ├── libcom32.c32 ├── libutil.c32 ├── vesamenu.c32 ├── centos/ │ ├── 7/ │ │ ├── vmlinuz │ │ └── initrd.img │ └── 8/ │ ├── vmlinuz │ └── initrd.img └── pxelinux.cfg/ └── default到这里很多人会问为什么 TFTP 里也要建 centos/7 和 centos/8 的目录这是因为启动菜单里的 kernel 和 initrd 路径是相对于 TFTP 根的。我们可以把两代系统的内核放在不同子目录里这样菜单里指向哪套就不会乱。nginx 的 /data/pxe 目录布局相对简单就是两个镜像挂载点加上 ks 目录/data/pxe/ ├── centos/ │ ├── 7/ # ISO 挂载点 │ └── 8/ # ISO 挂载点 └── ks/ ├── ks7.cfg └── ks8.cfg3.3 default 菜单一套PXE同时装7和8一切准备就绪后核心文件就是 /var/lib/tftpboot/pxelinux.cfg/default。我的配置如下default vesamenu.c32 prompt 0 timeout 300 menu title PXE Boot Menu label centos7 menu label Install CentOS 7 x86_64 kernel centos/7/vmlinuz append initrdcentos/7/initrd.img inst.repohttp://192.168.1.10/centos/7 kshttp://192.168.1.10/ks/ks7.cfg label centos8 menu label Install CentOS 8 x86_64 kernel centos/8/vmlinuz append initrdcentos/8/initrd.img inst.repohttp://192.168.1.10/centos/8/BaseOS/x86_64/os/ kshttp://192.168.1.10/ks/ks8.cfg label local menu label Boot from local disk localboot 0两个细节必须提醒。第一kernel和initrd后面的路径不要带前导斜杠也就是写centos/7/vmlinuz而不是/centos/7/vmlinuz。虽然部分版本能容忍但带斜杠的写法更容易在 TFTP 阶段触发文件找不到的问题。第二timeout 300的单位是 0.1 秒也就是 30 秒后自动执行默认项。默认项是第一个 label centos7如果你不想让装机过程中意外重装可以在菜单里把default localboot 0提到最前面或者从 BIOS 启动顺序上做文章。后面的实战部分我会专门讲这个坑。4. 安装自动化核心KS应答文件4.1 CentOS 7的ks要点与示例PXE 只是把安装器拉了起来真正决定“装什么、怎么分区、装完做什么”的是 ks 应答文件。没有 ksPXE 就只相当于把光盘放进了光驱还是要人工点。这次我采用一个分工原则安装源由内核参数 inst.repo 负责ks 文件里只写分区和软件包选择不写 url 指令。原因是 anaconda 一旦在 ks 里发现 url 配置会以它为准如果和内核参数不一致轻则安装源切换重则卡在奇怪的步骤上。CentOS 7 的 ks7.cfg 如下install text lang en_US.UTF-8 keyboard us rootpw Pssw0rd network --bootprotodhcp --devicelink --activate firewall --disabled selinux --disabled timezone Asia/Shanghai --utc bootloader --locationmbr zerombr clearpart --all --initlabel part /boot --fstypexfs --size1024 part swap --size4096 part / --fstypexfs --grow --size1 %packages ^minimal %end几个容易踩的地方rootpw后面直接跟明文密码测试环境图省事可以这么用但生产环境建议先执行openssl passwd -6 你的密码生成加密串再写成rootpw --iscrypted $6$...。^minimal里的^表示它对应一个环境组不是普通软件包组。CentOS 7 的最小化安装环境组 id 是 minimal这里不要写成minimal否则安装器可能只装一个简化版基础环境连很多常用命令都不带。--devicelink表示选择当前正在 PXE 引导的网卡避免服务器有多块网卡时 anaconda 选错设备导致网络起不来后面拉不到 ks 或者 repo。text指定安装界面使用字符模式。PXE 装机时如果客户端内存不足 2G图形安装环境经常直接崩text 模式会稳得多。4.2 CentOS 8的ks差异与AppStream处理CentOS 8 的 ks 整体逻辑和 7 差不多但有两处差异非常关键直接决定你能不能装成功。第一安装源结构不同。8 把软件包拆成了 BaseOS 和 AppStream 两个仓库。前文已经说过inst.repo 参数指向 BaseOS 目录而 AppStream 需要在 ks 中显式补充否则安装器会试图访问外网默认镜像。内网环境没有外网时表现就是装到一半卡在“Downloading packages”或者直接提示无法找到仓库。第二环境组 id 变了。CentOS 8 最小化安装的环境组是minimal-environment所以%packages里要写^minimal-environment。我用的 ks8.cfg 如下install text lang en_US.UTF-8 keyboard us rootpw Pssw0rd network --bootprotodhcp --devicelink --activate firewall --disabled selinux --disabled timezone Asia/Shanghai --utc repo --nameAppStream --baseurlhttp://192.168.1.10/centos/8/AppStream/x86_64/os/ bootloader --locationmbr zerombr clearpart --all --initlabel autopart --typelvm %packages ^minimal-environment %end这里我没有写 url 指令BaseOS 完全由内核参数 inst.repo 提供AppStream 则通过 repo 指令单独补充。如果你把整个 ks 文件放到 nginx 下面记得先在本机用 curl 验证两个仓库根地址都能正常列出文件。4.3 ks 里加一段 %post让装机结果更可控自动化安装不只是把系统装上通常还希望它在第一次重启前就把一些初始终结掉。ks 的%post段落会在安装完成后、系统首次启动前于目标系统环境内执行非常适合做这类收尾工作。例如我可以顺手清理 SSH 主机密钥并关闭 NetworkManager 的随机连接%post # 移除安装时生成的主机密钥让首次启动重新生成 rm -f /etc/ssh/ssh_host_* # 确保防火墙默认关闭仅限内部装机网络 systemctl disable firewalld %end注意%post默认在 chroot 环境中执行也就是你操作的是目标磁盘上的“新系统”不是安装器的临时系统。想在这里做网络请求就用%post --interpreter /bin/bash但不建议让这一步过分依赖外网毕竟自动化装机的意义之一就是摆脱外网依赖。5. 实测中的坑与定位手法5.1 卡在DHCP循环的排查链路最常见的故障画面客户端网卡启动后一直显示类似“No DHCP or proxyDHCP offers received”然后过一会儿超时重启。遇到这种情况按链路从底层往上查。先看服务端 dnsmasq 是不是真的活着systemctl status dnsmasq ss -lunp | grep 6767 端口没有监听多半是 dnsmasq 启动失败用dnsmasq --test和journalctl -u dnsmasq排配置错误。如果端口正常再看防火墙有没有放行 67/udp 和 69/udp。很多人在服务端本机测试都正常但客户端就是拿不到地址最后发现 firewalld 里 tftp 服务没加。还有一种容易被忽略的情况客户端和服务端不在同一个广播域。如果中间跨了 VLAN交换机上必须配置 DHCP 中继把广播请求转向服务器。测试环境最简单的方式就是先全部丢同一个二层等链路通了再考虑跨网段。想抓包确认的话在服务端执行tcpdump -i any port 67 or port 68 -n能看到 DHCP Discover 广播却收不到 Offer基本就是 DHCP 服务本身没响应反过来完全没包就是客户端根本没把请求送到服务器网段。5.2 TFTP加载不了引导文件的定位思路DHCP 正常了客户端拿到了 IP却报“TFTP open timeout”或者“PXE-E11: ARP timeout”。这多半是 TFTP 链路的问题。首先验证 tftp-root 下确实有 pxelinux.0并且文件名大小写完全正确。Linux 下文件名是区分大小写的pxelinux.0 写成 Pxelinux.0 就加载失败。其次检查防火墙 69/udp。再一个隐蔽问题是dnsmasq 的 tftp 服务只允许访问 tftp-root 下的文件如果 pxelinux.0 被放在 tftp-root 之外的目录即使 HTTP 那边能看到也没用。如果你改了自定义 tftp-root记得确认目录的 SELinux 上下文是否允许 tftp 服务读取。最简单的是先用默认路径跑通了再折腾自定义路径。5.3 镜像源连不上的定位思路内核已经加载anaconda 界面也出来了最后卡在“Unable to find install source”或者“Error downloading packages”这是最让人抓狂的。排查思路其实就一条主线看 HTTP 到没到。服务端先确认目录挂载正常ls /data/pxe/centos/7 ls /data/pxe/centos/8/BaseOS/x86_64/os/然后模拟客户端视角验证仓库元数据curl -I http://192.168.1.10/centos/7/repodata/repomd.xml curl -I http://192.168.1.10/centos/8/BaseOS/x86_64/os/repodata/repomd.xml如果返回 403 而不是 200优先查 SELinux 上下文用之前提到的 chcon 或 semanage 把 /data/pxe 标记为 httpd_sys_content_t。如果返回 404说明路径写错了仔细比对 inst.repo 参数和目录层级。还有一个高频问题是内存不够导致图形安装环境崩溃。如果你在菜单里没有指定 text 模式客户端又是 1G 内存的小虚拟机anaconda 可能直接没有界面输出或者闪退。这时在 append 里显式加上inst.text或者依赖 ks 里的 text 指令基本能解决。5.4 UEFI客户机的处理方式前面写的菜单默认跑的是传统 BIOS 的 pxelinux.0。但现在很多服务器默认是 UEFI 引导UEFI 机器不会执行 pxelinux.0你需要提供 EFI 格式的引导文件。dnsmasq 可以按客户端的固件架构返回不同的引导文件名这是最优雅的做法dhcp-matchset:efi-x86_64,option:client-arch,7 dhcp-boottag:efi-x86_64,efi/BOOT/BOOTX64.EFI,192.168.1.10 dhcp-boottag:!efi-x86_64,pxelinux.0,192.168.1.10option:client-arch,7对应 x86_64 UEFI。然后我把 CentOS 8 ISO 里的 EFI 目录复制到 TFTP 根cp -r /data/pxe/centos/8/EFI /var/lib/tftpboot/efi修改 /var/lib/tftpboot/efi/BOOT/grub.cfg加上双系统菜单核心内容和 pxelinux 的 default 差不多只是语法换成了 grub 的 menuentry。注意如果启动时提示找不到 grub.cfg把 ISO 里 boot/grub2 目录一并拷到 TFTP 根对应的位置即可这是 CentOS 8 的 grub EFI 镜像搜索路径决定的。5.5 批量自动化与装机后重启循环PXE 的价值在于批量而不只是省一张 U 盘。这里分享一个我实际用得很爽的小技巧利用 pxelinux 的配置文件优先级按客户端 IP 自动选择安装系统。PXE 协议规定客户端在读取 default 之前会先尝试读取按 MAC 地址命名的文件再尝试按 IP 十六进制命名的文件。比如客户端 IP 是 192.168.1.100十六进制就是 C0A80164它对应的配置文件是 /var/lib/tftpboot/pxelinux.cfg/C0A80164。我可以在里面写default centos8 prompt 0 timeout 1 label centos8 kernel centos/8/vmlinuz append initrdcentos/8/initrd.img inst.repohttp://192.168.1.10/centos/8/BaseOS/x86_64/os/ kshttp://192.168.1.10/ks/ks8.cfg这样这台机器开机直接装 CentOS 8完全不用人点菜单。批量新机器上线时我用脚本把 IP 段和系统版本的对应关系批量生成配置文件效率非常高。最后说说装机完成后的重启循环。很多机器把 Network Boot 放在第一启动项装完系统重启后又进入 PXE 引导如果 default 菜单超时后又默认开始安装就会掉进装完重启再装的死循环。我的处理原则是把 PXE 引导当作“一次性流程”来用常规服务器 BIOS 里硬盘启动顺序保持第一只有需要新装系统的那一台才通过启动菜单临时选择网络引导。如果你确实要实现真正全自动那就要在 DHCP 层面按 MAC 区分只有“待装机名单”里的机器才返回 PXE 引导参数装完的机器不做响应。这个思路实现起来也不难就是在 dnsmasq 里维护一份 dhcp-host 名单然后结合 tag 控制 dhcp-boot。实际跑过几轮之后我最大的体会是PXE 环境本身不难调最难的是被“多个协议混在一起”的错觉吓住。只要你心里始终清楚当前卡在哪一层——DHCP、TFTP、HTTP 这三段各自负责什么再配合抓包日志排错思路就会非常清晰。另外就是尽量让配置“朴素”一点能用默认路径就用默认路径能少装一个服务就少装一个环境越简单后续批量装机时的故障率越低。这套双系统方案我已经在内部连续用了小半年装过几十台机器稳定可靠。如果你也打算在 CentOS 8 上搭一套同时支持 CentOS 7 和 8 的 PXE 环境直接照这套配置起步再根据你的实际网段和机器型号微调应该能少走很多弯路。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询