<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>版本兼容矩阵 on Zero Day Notes</title>
        <link>https://blog.5772447.xyz/tags/%E7%89%88%E6%9C%AC%E5%85%BC%E5%AE%B9%E7%9F%A9%E9%98%B5/</link>
        <description>Recent content in 版本兼容矩阵 on Zero Day Notes</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>zh</language>
        <lastBuildDate>Sun, 02 Aug 2026 07:00:00 +0800</lastBuildDate><atom:link href="https://blog.5772447.xyz/tags/%E7%89%88%E6%9C%AC%E5%85%BC%E5%AE%B9%E7%9F%A9%E9%98%B5/index.xml" rel="self" type="application/rss+xml" /><item>
            <title>升级PVS服务器：一次无盘终端批量启动失败的排查与回退</title>
            <link>https://blog.5772447.xyz/posts/d8a4384d/</link>
            <pubDate>Sun, 02 Aug 2026 07:00:00 +0800</pubDate>
            <guid>https://blog.5772447.xyz/posts/d8a4384d/</guid>
            <description>&lt;h2 id=&#34;问题背景&#34;&gt;&lt;a href=&#34;#%e9%97%ae%e9%a2%98%e8%83%8c%e6%99%af&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;问题背景&#xD;&#xA;&lt;/h2&gt;&lt;p&gt;我们的机房与培训教室共用一套无盘系统（Citrix Provisioning Services，下称 PVS），由两台 PVS 服务器组成一个 Farm，通过网络流式推送同一份系统镜像给两百多台终端，终端本地不装硬盘系统，开机走 PXE 从服务器拉起。这套系统跑了好几年，一直停留在旧的 LTSR 版本上，厂商支持周期临近结束，安全部门要求年内升级到新 LTSR。&lt;/p&gt;&#xA;&lt;p&gt;按官方文档的说法，同一个 Farm 内可以做滚动升级（rolling upgrade）：先升级其中一台服务器，另一台继续对外提供服务，业务不中断，升完第一台再升第二台。我们照着这个思路排了变更：周三晚上八点升级 PVS01，PVS02 保持旧版本兜底，第二天观察一天没问题，周四晚上再升 PVS02。&lt;/p&gt;&#xA;&lt;p&gt;变更当晚 PVS01 升级过程本身很顺利，Farm 数据库自动升级完成，控制台能正常打开，vDisk 列表和终端列表都在，抽测三台终端开机也正常。我们判断变更成功，收工。真正的问题在第二天早上八点集中爆发。&lt;/p&gt;&#xA;&lt;h2 id=&#34;故障现象&#34;&gt;&lt;a href=&#34;#%e6%95%85%e9%9a%9c%e7%8e%b0%e8%b1%a1&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;故障现象&#xD;&#xA;&lt;/h2&gt;&lt;p&gt;早上八点前后，教室和办公区陆续有人报&amp;quot;电脑开不了机&amp;quot;。到现场看，屏幕停在 PVS 的启动界面上，最后一行是 &lt;code&gt;Contacting Login Server...&lt;/code&gt;，卡住十几秒后跳出 &lt;code&gt;Login Server not available&lt;/code&gt; 或直接黑屏重启，循环往复。&lt;/p&gt;&#xA;&lt;p&gt;关键特征有三个，也正是它们把我们一度带偏：&lt;/p&gt;&#xA;&lt;p&gt;第一，&lt;strong&gt;不是全挂，大约一半终端受影响&lt;/strong&gt;。同一个教室里，前排三台能正常进系统，后排两台起不来；重启若干次之后，原本起不来的偶尔又能起来一次。这种&amp;quot;随机性&amp;quot;让第一反应是网络抖动或 DHCP 问题。&lt;/p&gt;&#xA;&lt;p&gt;第二，&lt;strong&gt;已经开着的机器完全不受影响&lt;/strong&gt;。前一晚没关机的几台终端整夜运行、业务正常，说明服务器端的流式推送（streaming）通道本身是通的，问题只发生在开机启动阶段。&lt;/p&gt;&#xA;&lt;p&gt;第三，&lt;strong&gt;故障范围与地理位置无关&lt;/strong&gt;。不同楼层、不同接入交换机下都有成功和失败的机器混杂，排除了单台交换机或单个 VLAN 的问题。&lt;/p&gt;&#xA;&lt;p&gt;因为是早高峰，影响面直接扩散到两个教室的上课，压力很大。&lt;/p&gt;&#xA;&lt;h2 id=&#34;排查过程&#34;&gt;&lt;a href=&#34;#%e6%8e%92%e6%9f%a5%e8%bf%87%e7%a8%8b&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;排查过程&#xD;&#xA;&lt;/h2&gt;&lt;p&gt;无盘终端的启动链路比普通 PC 长得多，必须按顺序逐段验证，不能凭感觉跳。我们先在白板上把链路画出来：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&#xA;&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;1&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;2&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;3&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;4&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;终端上电 → 网卡 PXE → DHCP 拿 IP + Option 66/67&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        → 从 Option 66 指定的 TFTP 服务器下载 bootstrap 文件&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        → bootstrap 里写死了 PVS 服务器列表，向其中一台的 Stream Service 登录&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        → 服务器分配 vDisk → 开始流式读盘 → 进系统&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;第一段，DHCP。&lt;/strong&gt; 在故障终端所在网段抓包，看到 DHCPOFFER 正常返回，IP、掩码、网关都对，Option 66 指向 &lt;code&gt;10.20.1.30&lt;/code&gt;，Option 67 是 &lt;code&gt;ARDBP32.BIN&lt;/code&gt;。DHCP 没问题。这里要注意一个细节：Option 66 指向的 &lt;code&gt;10.20.1.30&lt;/code&gt; &lt;strong&gt;既不是 PVS01 也不是 PVS02&lt;/strong&gt;，而是当年为了统一 PXE 管理，单独搭的一台第三方 TFTP 服务器。这个细节后面成了关键。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;第二段，TFTP。&lt;/strong&gt; 抓包看到 bootstrap 文件下载成功，TFTP 传输完整，没有超时和重传。到这里一切正常。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;第三段，登录 Stream Service。&lt;/strong&gt; 抓包看到终端向 UDP 6910 端口发出登录请求，服务器&lt;strong&gt;有响应，但立刻返回了一个错误包&lt;/strong&gt;，随后终端超时。这一步是断点。&lt;/p&gt;&#xA;&lt;p&gt;进一步比对成功和失败的机器，规律终于浮出来：抓包里失败的终端，请求打到的是 &lt;code&gt;10.20.1.11&lt;/code&gt;（PVS01，已升级）；成功的终端，请求打到的是 &lt;code&gt;10.20.1.12&lt;/code&gt;（PVS02，未升级）。所谓&amp;quot;随机一半&amp;quot;，正是 bootstrap 里配置了两台服务器做冗余，终端按算法随机挑一台登录的结果。&lt;/p&gt;&#xA;&lt;p&gt;方向锁定 PVS01。登上去翻 Stream Service 日志，找到明确记录：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&#xA;&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;1&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Login request rejected: unsupported client protocol version&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;到这一步，根因基本清楚了：终端拿到的 bootstrap 是&lt;strong&gt;旧版本&lt;/strong&gt;的，跟升级后的 PVS01 协议版本不匹配，被拒绝。&lt;/p&gt;&#xA;&lt;p&gt;那为什么是旧版本？我们到 PVS01 本地看 &lt;code&gt;C:\ProgramData\Citrix\Provisioning Services\Tftpboot\ARDBP32.BIN&lt;/code&gt;，文件时间戳是昨晚，确实被升级程序更新过。再登到那台独立 TFTP 服务器 &lt;code&gt;10.20.1.30&lt;/code&gt; 上看同名文件——时间戳是&lt;strong&gt;三年前&lt;/strong&gt;。&lt;/p&gt;&#xA;&lt;p&gt;真相大白：升级程序只更新了 PVS 服务器本地那份 bootstrap，而终端实际从独立 TFTP 服务器取文件，那份是当年手工拷过去的，从此再没人动过。升级把服务器端协议版本抬高了，客户端 bootstrap 却停在原地。&lt;/p&gt;&#xA;&lt;p&gt;顺手还验证了另一个更麻烦的问题：我们把新 bootstrap 手工拷到 TFTP 服务器上试了一台机器，结果这台机器&lt;strong&gt;反过来在登录 PVS02 时失败了&lt;/strong&gt;——新 bootstrap 同样不被旧版服务器接受。也就是说，只要 Farm 处于&amp;quot;一台新、一台旧&amp;quot;的混合版本状态，无论用新旧哪份 bootstrap，都必然有一半终端起不来。所谓跨大版本的滚动升级窗口期，实际上是不可用的。&lt;/p&gt;&#xA;&lt;h2 id=&#34;解决方案&#34;&gt;&lt;a href=&#34;#%e8%a7%a3%e5%86%b3%e6%96%b9%e6%a1%88&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;解决方案&#xD;&#xA;&lt;/h2&gt;&lt;p&gt;早高峰优先止血，不追求一步到位：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;把流量全部压到未升级的 PVS02&lt;/strong&gt;。在 TFTP 服务器上保留旧 bootstrap，同时在 PVS01 上停掉 Stream Service，让所有终端只能登录 PVS02。执行后五分钟内，故障终端重启即可正常进系统，上课恢复。此时集群失去冗余，但功能完整。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;当晚补完另一半升级&lt;/strong&gt;。把 PVS02 也升到同版本，Farm 脱离混合状态。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;用控制台重新生成并分发 bootstrap&lt;/strong&gt;。在 Console 里执行 &lt;code&gt;Configure Bootstrap&lt;/code&gt;，重新写入两台服务器 IP 生成新文件，然后&lt;strong&gt;手工同步到 &lt;code&gt;10.20.1.30&lt;/code&gt; 的 TFTP 目录&lt;/strong&gt;，并把这一步固化进升级检查单。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;灰度升级终端侧软件&lt;/strong&gt;。vDisk 里的 Target Device Software 版本也需要跟服务器匹配。我们用 vDisk 的 versioning 功能建了一个 maintenance 版本，只在里面升级 Target Device Software，先放一台终端验证，通过后再 promote 为 Production，保留上一版本随时可回退。&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;全部完成后，两台服务器恢复冗余，随机挑哪台登录都能正常启动。&lt;/p&gt;&#xA;&lt;h2 id=&#34;根因分析&#34;&gt;&lt;a href=&#34;#%e6%a0%b9%e5%9b%a0%e5%88%86%e6%9e%90&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;根因分析&#xD;&#xA;&lt;/h2&gt;&lt;p&gt;表面根因是 bootstrap 文件版本不同步，深层根因有两条：&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;一是启动链路的所有权被切割了。&lt;/strong&gt; PVS 的开机链路横跨 DHCP、TFTP、Stream Service 三个组件，升级程序只对自己管辖的文件负责，管不到企业自建的第三方 TFTP 服务器。那份三年前手工拷贝的 bootstrap 属于&amp;quot;没有主人的配置&amp;quot;，既不在升级流程里，也不在任何一份文档中。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;二是把&amp;quot;支持滚动升级&amp;quot;理解错了。&lt;/strong&gt; 官方所说的滚动升级，适用范围是同一大版本内的累积更新；跨 LTSR 大版本时，客户端协议版本本身发生了变化，混合版本 Farm 在升级窗口期内是不受支持的状态。我们套用了一个前提不成立的方案，并把&amp;quot;抽测三台正常&amp;quot;当成了验证通过——而那三台恰好都随机登录到了未升级的 PVS02 上。&lt;/p&gt;&#xA;&lt;h2 id=&#34;预防措施&#34;&gt;&lt;a href=&#34;#%e9%a2%84%e9%98%b2%e6%8e%aa%e6%96%bd&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;预防措施&#xD;&#xA;&lt;/h2&gt;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;变更前画完整链路图，逐点列出版本与存放位置&lt;/strong&gt;：DHCP Option 66/67 指向哪里、bootstrap 实际由谁提供、文件版本、服务器列表、Target Device Software 版本、vDisk 格式版本。任何一处&amp;quot;不知道谁在维护&amp;quot;的配置都必须先认领。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;跨大版本升级不做原地滚动，改并行 Farm 灰度&lt;/strong&gt;：新建一套新版本 Farm，用 DHCP 作用域分批把终端切过去，出问题改回 Option 66 即可整体回退，比原地升级安全得多。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;验证样本必须覆盖全部分支&lt;/strong&gt;：有负载均衡或多服务器的场景，抽测时要强制指定每一台服务器各测一遍，不能依赖随机分配。这次的教训就是抽测三台全落在旧服务器上。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;每次变更预置五分钟回退方案&lt;/strong&gt;：本次的回退动作是&amp;quot;停掉新服务器的 Stream Service&amp;quot;，一条命令、影响可控。变更单上没写回退步骤的，不允许开始执行。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;补监控&lt;/strong&gt;：对 TFTP 请求数、Stream Service 登录失败计数做采集，开机失败率突增时告警，避免只能靠用户报障发现。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;总结&#34;&gt;&lt;a href=&#34;#%e6%80%bb%e7%bb%93&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;总结&#xD;&#xA;&lt;/h2&gt;&lt;p&gt;这次故障的技术点并不深，一个文件版本不匹配而已，但它踩中了变更管理里最典型的两个坑：一是链路上存在无人认领的配置，升级工具覆盖不到，文档里也查不到；二是把厂商在特定前提下的能力（滚动升级）当成了普适保证，跨版本时前提早已不成立。&lt;/p&gt;&#xA;&lt;p&gt;排查过程本身反倒是最有价值的部分——面对&amp;quot;一半好一半坏&amp;quot;的随机现象，不去猜网络抖动，而是把启动链路拆成 DHCP、TFTP、登录三段逐段抓包验证，很快就能把断点收敛到某一跳上；再对比成功与失败样本的差异（打到哪台服务器），根因几乎是自己浮出来的。对于这类跨多个组件的长链路系统，&lt;strong&gt;画图、分段、比对&lt;/strong&gt;永远比经验直觉更可靠。&lt;/p&gt;&#xA;</description>
        </item></channel>
</rss>
