已有网站旁边,怎样安全地加一个新服务

一台机器上已经有别的网站在对外服务时,新应用最容易出的问题不是自己起不来,而是把旁边的站点一起碰倒。端口被抢走、整份配置被覆盖、为了做一件小事停掉共用的入口,都会让原来正常的访问中断。旁边的人不会关心你的新首页有多完整。他们只会发现自己的地址突然打不开,然后开始追查是谁在同一台机器上动过手。

下面用三个虚构对象把每一步的期望说死。甲站点的正文只有一个“青”字,乙站点的正文只有一个“墨”字,新应用的正文只有一句“新窗已开,旧灯未动”。回答故意短。短才核对得了:句子换成了另一个,就是串站;状态码和变更前不同,就是越界。真实页面通常更长,方法并不因此改变。你仍然要事先选定一个稳定的标记,而不是每次凭印象说“看起来像原来的页面”。

名字也固定下来,避免后文一会儿用端口、一会儿用感觉指代。甲是 jia.example,乙是 yi.example,新应用是 xin.example。这三个名字只存在于这篇的例子里。公网的 80 和 443 继续由已经在运行的入口监听。新应用只使用本机回环上的 18081。数据库不占用任何公网端口。

本篇目标: 在不改动既有站点的前提下,加一个只属于自己的服务入口,并在切换之后核对邻居是否仍然正常。

新服务只增加自己的入口
新服务只增加自己的入口放大图解 ↗

先把已经在运行的东西写成一张清单

动手之前,值得先看清现状,而不是先创建目录。清单不是归档,是一张对照底片。变更结束时,除了新域名多出来的那几行,其余行都应该和底片一致。有差异就先解释差异,再宣布上线。底片不需要写成正式文书,但每一行都要能在变更之后原样再测一次。测不了的句子,例如“网站整体正常”,不要写进去。

底片至少分成四栏。第一栏是监听者:谁占着 80,谁占着 443,进程名字和它读的配置目录分别是什么。如果 80 和 443 不是同一个程序,两行都要写,因为你稍后发出的重载可能只到达其中之一。第二栏是站点:甲、乙各自的主机名,配置文件的路径,以及你承诺这次不打开的那些文件。第三栏是回答:对每个主机名记录状态码,并记下正文里那个稳定标记。甲应当是 200 和“青”,乙应当是 200 和“墨”。第四栏是边界:18081 此刻有没有监听者,数据库端口有没有出现在宿主机上,磁盘还剩多少,内存还剩多少。

状态码要包含那些看起来不成功、其实本来就如此的结果。给甲准备一个确定不存在的路径,记下它返回 404,以及 404 页面上没有出现新应用那句话。给乙准备一个会跳转的路径,记下 301 或 302,以及跳转目标的主机名仍然是乙。如果你只记录“能打开”,上线之后就无法分辨一个 404 是新引入的,还是原本就这样。跳转也一样。很多人跟着跳转走了两步,把别人的落地页记成了自己的成绩,于是真正被改掉的那一次 302 从记录里消失了。

新名字今天的回答也要记,哪怕它还不属于你。若区域里已经有一条通配记录指向这台机器,xin.example 此刻就可能落到默认站点,正文是“青”或“墨”,而不是失败。这条“变更前的新名字”以后要和变更后的句子对比。你若只在上线后看它,会以为自己从零创造了一个地址,实际上你是从默认站点手里把这个名字接了过来。接过来是允许的,但必须是你知道的。

进程也不要只写“网站服务正常”。写下服务名称、主进程号,以及机器上你不打算碰的邻居:乙也许有一个只在本机监听的作业队列,甲也许有一个定时续签证书的任务。这些名字写在清单上,是为了阻止后面的步骤“顺手”重启它们。顺手是这种上线里最常见的越界。首页仍返回“墨”,队列却已经停了,只看正文的人发现不了。主进程号还有一个用处:平滑重载之后它应当还在;若号变了,你执行的就不是重载。

端口空闲不是感觉。在清单上写明你用哪一种观察:一条只查询监听套接字的命令,输出里没有 18081,才算空闲。今天空着,不代表可以把应用绑到所有网卡。空闲只说明这一刻没有冲突,绑定范围要另写一行,计划写成只接受本机连接。若 18081 已经有人使用,就换一个更高的端口,并回到这一行改数字。不要把原来的占用者停掉。那个人不在这次变更里。

配置目录的写入者也要留一行。有的机器上入口配置由人手工维护,有的机器上另有发布任务会按模板重新生成整目录。不知道写入者是谁,文件就可能放进一个下次会被盖掉或删掉的位置。这一格先写“未知”可以,但在重载之前必须变成一个名字,或变成一句明确的话:变更窗口内没有人重写这个目录。后文会单独处理怎样问这件事。清单上先把格子留出来,避免做到一半才发现自己站在别人的生成结果上面。

共享配置文件本身也要有一个不变的记号。对入口的主配置算一次摘要,把摘要的前几位和文件长度写进底片。你的计划是不改这个文件。结束时再算一次。摘要相同,才能说明“只增加了一个站点文件”这句话是真的,而不是你记得自己没改。目录列表同样抄下来,包括文件名和是否为链接。上线后应当只多出一个以 xin.example 命名的文件,其余名字一个不多、一个不少。

最后给底片写上时间,精确到分钟。没有时间,下午看到的 404 就无法判断是不是早上就存在。核对时另起一列,不要在原数字上涂改。涂改过的底片不能再当对照。也写下操作者是谁。同一台机器上若下午还有别人登录,出了差异时你才知道该先问谁,而不是默认是自己的站点文件。

公网的八十和四百四十三继续交给原来的入口

机器上若已经有入口程序在监听 80 和 443,这两扇门就继续由它负责。新应用不去监听这两处,也不要在旁边再启动第二个 Web 服务器,声称它“只服务我的域名”。公网地址上的同一个端口,常规情况下只能有一个监听者。后启动的那个,要么立刻失败,要么在某次重启之后把先来的挤掉。立刻失败还算幸运,因为你当场看得见。挤掉则常常要等到下一次维护才暴露。到那天甲不再返回“青”,而你已经不记得自己动过端口。

同一扇门上放多个站点,靠的是名字,不是再占一个端口。访客连上 443 之后,在加密握手里带上要访问的主机名,入口用这个名字选择证书和站点块。甲、乙和新应用可以共用这一个监听,只要 server_name 彼此不同。你增加的是一块新规则,不是第二套争夺套接字的程序。已经放好的私钥、甲和乙的访问规则、它们的日志位置,都留在原来的地方。

因此这篇的主路径是:新应用只在本机回环的 18081 上回答,入口匹配到 xin.example 之后再转过去。从公网看这台机器,仍然只看得到 80 和 443。18081 不出现在对外的网卡上,数据库端口也不出现。新服务能接受的外部路径只有原来那一扇门,而且只有主机名对得上的请求才会被转进来。这样,新应用自己的缺陷最多让这一个名字返回错误,而不会让甲和乙失去监听者。

有人会觉得应用自己就能做证书和跳转,让它直接听 443 更省事。在空机器上可以另行设计。在甲和乙已经使用 443 的机器上,这个省事等于把入口换成你的应用,或让两个程序轮流占有它。绑定动作不看域名,它只看地址和端口。域名的区分发生在已经成功占住端口的那个程序内部。你的应用若没有实现甲和乙的全部规则,它一旦占住 443,这两站就从公网上消失。即使它启动失败,原来的入口也会因为端口仍被占用或已经被你停掉而无法回来。

更隐蔽的一种共享是端口重用。某些系统上,两个程序可以同时声明监听同一端口,请求被分到两边,表面谁也没报错。你若只看到“我的程序启动成功”,就可能忽略 443 上现在有两个监听者。甲的部分请求会进到不懂“青”的进程里。所以清单上的监听者要看数量,不只看有没有。只要 80 或 443 的监听数量比底片多,就先停掉你新加的那个对外监听,回到“只听回环高端口”的计划。不要试图用重用来做隔离。重用是分发,不是边界。

重载入口时,端口的主人应当仍是原来的主进程。要避免的是另一类操作:停掉入口,改由新应用监听 443,再把甲和乙反向代理回去。那不叫在旁边加一个邻居,那叫把整栋楼的大门换成你自己的锁。新应用一旦起不来,三站一起不可用。影响面应当收在 xin.example 上,而不是收在“谁握着 443”这件事上。上线前再看一次监听者那一栏。进程名和底片不同,就先停下来找出谁改过,不要在已经偏离的底片上继续加站点。

80 和 443 还不是同一件事,尽管常常由同一个程序承担。80 上可能只有一条把甲和乙送去加密地址的规则,443 上才有证书和正文。你的新站点如果两面都要,就在入口里加两块都属于 xin.example 的规则:一块在 80 上只做跳转,一块在 443 上才转发到 18081。不要为了让新应用自己处理跳转而让它去绑 80。80 同样是整台机器的门。也不要把“所有未加密请求都跳转”写进公共配置去替换甲原来的行为。甲在 80 上也许本来就返回“青”,也许本来就跳转。底片里那一行说了算。

新的站点文件只回答自己的四个问题

新文件从空白写起,只回答四个问题。它匹配哪个主机名。证书放在哪两个路径。访问和错误记到哪两个文件。除了证书校验要用的临时路径,其余请求转到哪个本机地址。答完就停。没有提到的路径不要写,没有提到的邻居不要出现。四个问题之外的每一行,都是以后退回时要额外核对的风险。

主机名用精确名字 xin.example,不要用通配,也不要写正则。精确名字足够,而且匹配顺序明确。Nginx 选择站点时,先看精确名字,再看以星号开头的最长通配,再看以星号结尾的最长通配,最后才按出现顺序试正则。Nginx:server names 一个新站点没有理由去抢“最长通配”或“第一条正则”的位置。那两种写法容易把 jia.example 或尚未清点的子域名一起卷进来。你若只是想让新句子出现,精确名字是影响面最小的声明。

通配还有一个容易记错的边界:星号只代替一段标签。写成对 example 的通配,不会自动包含你没打算服务的那些更深的名字,也不会自动等于根名字本身。Nginx 里还有一种点开头的特殊写法,可以同时盖住根名字和一级通配。这些规则对这篇都不需要。需要的是新文件里看得到、也只看得到 xin.example。名字若还有中文显示形式,配置里写的是它的国际写法,而不是浏览器里给人看的那一串汉字。写错写法时,握手可能选错证书,或干脆进了默认站点。把你实际写进配置的那一串抄进清单,和域名服务商页面上的国际写法对一次。

80 上的那一块同样只写 xin.example。它的唯一职责是把这个名字送往加密地址,目标主机名仍是 xin.example,不要送去甲,也不要送去裸的地址。跳转用 301 还是 302,选一种并写进核对表。变更前后,甲和乙在 80 上的状态码必须保持底片的样子。如果你发现自己正在编辑那份已经包含甲和乙跳转规则的旧文件,就退出它,把新规则放到新文件里。

不要从甲或乙的文件复制一份再改域名。复制会把别人的假设带进来。某个路径被转到另一个进程,某种文件被缓存一年,某段访问被写进别人的日志,某个请求头被改写成甲的主机名。你以为只换了名字,读者和新旧站点却一起承受这些残留。更糟的是复制品里可能仍有甲的网站根。那时 xin.example 返回的是“青”,状态码还是 200。只看状态码的人会宣布成功。正文标记就是用来抓住这种成功的。

公共片段也不要为了新站点去改。若甲和乙共同引用一份头部片段、一份压缩设置或一份限制上传大小的片段,那些片段保持不动。新站点需要的不同超时、不同上传上限,写在新文件的 server 块里面。写到 http 层或写到被别人引用的片段里,影响面就不再是 xin.example。你甚至可以在新文件里用更严格的上限,这只约束新句子所在的站点。不要为了图省事去放宽全局上限。全局放宽会让甲和乙同时接受更大的请求体,磁盘和连接的风险一起变大。

文件写完后,自己读一遍,用四个问题当过滤器。某一行若不能对应到主机名、证书、日志或上游,就删掉它,除非它是证书校验路径那一个例外。例外也只匹配 xin.example 这一块,不写到默认站点里。读的时候特别看有没有 jia、yi、青、墨这些不该出现的字。新文件在上线前不应当认识它的邻居。

直接运行的应用只绑定本机回环

若新应用是直接跑在宿主机上的进程,而不是放在容器的独立网络里,它的监听地址就写成回环地址 127.0.0.1,端口用清单上那一个空闲高端口。不要写成所有网卡,也不要写成某一块对外网卡的地址。高端口并不因为数字大就安全。绑定范围才决定公网能不能直接摸到它。进程一旦在所有网卡上接受连接,入口的站点规则就被旁路了:别人可以不带你的证书、不经过你的主机名检查,直接向那个端口说话。

只绑回环还有一个排错上的好处。你在这台机器上访问 127.0.0.1:18081,应当拿到“新窗已开,旧灯未动”。从另一台机器访问这台机器的公网地址加 18081,应当连接失败。两条都要做。第一条证明程序起来了,第二条证明它没有自己开一扇公网的门。只做第一条,你会把“本机能打开”误当成“边界正确”。防火墙碰巧丢掉公网包时,第二条也可能暂时成立,所以第二条是必要的,但不是充分的。充分的证据是监听列表里那个地址列显示的是回环,而不是通配。通配加上外部防火墙,表示边界建在了另一层,别人一改防火墙,应用就露在外面。

应用若按主机名拒绝请求,本机探测时就要带上 Host,值为 xin.example。不带主机名时它可能返回默认页或拒绝页,你就会以为程序坏了,转去改入口配置。入口这时还没参与。先让应用在回环上、在正确的主机名下说出那句新话,再去写入口的转发。这个顺序把两类故障拆开:进程与依赖是一类,站点文件是另一类。混在一起改,最后好了也不知道是哪一类好了。

也不要把应用绑到入口正在使用的地址上,指望“端口不同就没事”。端口不同确实能同时监听,可你已经多开了一个对外的口。有的反向代理例子喜欢把上游写成公网地址,让请求从机器出去再回来。那样会绕到 443,重新进入入口,再被转到应用,形成环,或者在地址转换上走一条你没打算支持的回头路。上游就写 127.0.0.1 和 18081。它应当短、本地、不依赖外部解析。上游若写成 xin.example,而这个名字已经解析到本机的公网地址,你就在用公网路径访问一个本该本地的服务,证书和来源地址都会变成另一回事。

裸进程不需要为了监听 18081 而使用超级用户。需要特权的是 80 和 443,那两处已经由入口占用,入口也已经以它原来的方式降权或分权。新应用若以超级用户运行,只是为了“省得处理权限”,它就能读取甲和乙的文件。这不是旁边加一个服务,这是给新代码发了一把整机的钥匙。运行账户应当是专为 xin.example 建立的普通账户,家目录和工作目录都在它自己的树里。

改绑定之后要重启的是新应用自己,不是入口。应用还没被任何站点文件引用时,重启它不会影响“青”和“墨”。这是你最自由的一段时间。等到入口已经把流量转过来,再去改监听地址,就会让 xin.example 出现一段失败。所以绑定地址在第一次健康检查之前定稿。定稿后写进清单。后面若发现监听列变成了通配,先视为回退条件,而不是视为“先上线再收紧”。

容器发布端口时把宿主机一侧收在回环上

容器里的回环和宿主机的回环不是同一张网。进程在容器里绑定 127.0.0.1,只表示它接受来自该容器内部的连接。宿主机上的入口访问宿主机的 127.0.0.1:18081 时,到不了那个进程。于是有人把容器网络改成和宿主机共用,让两边的回环重合。共用之后,容器里的进程可以直接尝试绑定宿主机的 80 和 443,也可以看见只对宿主机本地开放的其他端口。隔离被整段拆掉了。这篇不使用共用网络的做法。

可用的拆法是两层地址各写各的。容器内的进程监听容器网络里的通配地址和一个内部端口,例如 8080。这个通配只在容器自己的网络命名空间里生效,并不等于宿主机的所有网卡。宿主机发布端口时,把发布地址写成 127.0.0.1,把宿主机端口写成 18081,把容器端口写成 8080。入口仍然只转发到宿主机的 127.0.0.1:18081。公网网卡上不出现 18081,也不出现 8080。

发布行最常见的笔误,是只写了两个端口、没有写宿主机地址。那一行的含义往往是向所有网卡发布。应用因此绕过入口,直接暴露在公网上。笔误不会让程序起不来,所以健康检查仍然能在本机看到新句子,你更容易把它放过。核对时要看发布表,而不是只看容器在不在运行。发布表里应当能读到宿主机地址是回环。读不到地址、只能读到端口时,按暴露处理,先改发布行,再继续。

不要同时发布一个给入口用的回环端口,再发布一个“临时调试”的公网端口。临时口经常留下来。清单上的边界栏应当只有 18081 这一条与新应用相关的宿主机端口,并且它绑定在回环。调试若必须从你的办公机直连进程,使用一条临时的、会随会话结束而关闭的转发,而不是改发布范围。会话结束之后,再查一次监听列表,确认没有留下新的对外端口。

容器里若还有启动脚本会自行安装入口程序并去监听 80,那是镜像选错了使用方式。一个已经自带入口、打算独占 80 和 443 的打包方式,不适合放到已经有甲和乙的机器上。应当选用只启动应用进程的方式,或在启动参数里明确关掉它自带的对外监听。关掉之后用发布表验证,而不是相信说明文字。说明文字常常描述的是“这台容器独占一台机器”的场景。你的场景不是那个。

入口配置里的上游端口要写宿主机发布出来的 18081,不要写成容器内部的 8080。从入口所在的网络命名空间看,8080 并不存在,除非你又做了别的共享。写错时的现象是:容器自己的日志说它在服务,入口的错误日志说连接被拒绝。这不是甲和乙的故障。不要因此重载全局,也不要改它们的上游。改的是新文件里的那一个端口数字,或发布行上的映射,二者以你清单记录的为准,不要同时改成两个新数字。

数据库不要映射任何宿主机端口

数据库只给新应用用,就不要把它的端口映射到宿主机。既不要映射到所有网卡,也不要为了“本机连上去看一眼”而映射到宿主机回环。回环上的数据库端口仍然能被这台机器上的其他账户、以及其他使用宿主机网络的进程碰到。甲和乙的程序若被攻破或只是配置写错,不应当多一条通往新库的路;反过来,新应用也不应当因为图方便而把库放在谁都能试的地址上。

应用通过只属于这次部署的内部网络,用服务名访问数据库。服务名例如就叫 db,它不需要是公网能解析的名字。连接信息放在新应用目录里权限收紧的环境文件中。站点配置文件里不写这些内容。入口不应当认识数据库。若你在新站点文件里看到了数据库端口或库名,说明两层被搅在了一起,删掉那几行,让入口只知道 18081。

管理库需要临时查看时,用一条随会话关闭的通道进到容器或进到内部网络,用完就退出。不要把通道改成常驻服务,也不要写进开机启动。退出之后核对两件事:宿主机监听列表里仍然没有数据库端口,内部网络上的数据库进程仍在,应用还能在本机拿到新句子。只核对“我刚才能连上”是不够的,那只能证明通道开通过,不能证明通道已经消失。

库的账户也按这一次来建。应用使用的账户只能访问这个新库,不能成为管理全部库的超级账户,更不能复用甲或乙已经在用的账户。复用账户看起来省掉了一次初始化,实际上让两个站点的数据边界消失:任何一边的连接信息都能读到另一边。初始化脚本若会创建多份示例库,上线前删掉不需要的那几份,或换一个只创建单库的初始化。不要把“示例里附带的管理台”发布出去。管理台是另一个需要单独决策的服务,不是数据库的当然部分。

数据文件放在独立的数据卷里,卷的名字里带上 xin.example,避免和甲、乙或其他项目的默认卷名相撞。默认卷名经常只是 db 或 data。同一台宿主机上,短名字很容易在下一次部署里被当成“那个没人用的旧卷”删掉。名字长而具体,清理的人必须有意对着它下手。数据卷不要挂到入口的网站根下面。入口若能直接读到库文件,一份错误的根目录配置就会把数据文件当成静态资源送给访客。

备份时只导出这个新库,导出文件放到应用目录里一个不对外的位置,而不是放到任何网站根、上传目录或会被入口别名指到的地方。导出文件的权限收到只有运行账户能读。这篇后文会把备份的粒度单独说完。这里的要求更窄:为了让应用能回答那句新话,不需要、也不应该打开一扇数据库的公网门。若发现发布文件里出现了数据库的端口映射,先删映射,再启动。不要先启动再指望防火墙补上。

文件名单独可辨并且能被一次拿走

站点文件的名字里写上 xin.example,放在入口已经会加载的配置目录里。一个文件只放这一次的站点块,包括 80 上的跳转和 443 上的转发。不要把新规则追加到一份已经很长的旧文件末尾。追加时很快,删除时要在几百行里找,而且很容易把甲的一段一起删掉或留一半。退回应当是移走一个文件,而不是编辑一个共享文本。

目录若以通配方式加载其中的全部文件,你只要放入新文件,不需要修改主配置。这是最好的情况,因为主配置的摘要可以保持不变。若目录的惯例是“可用站点”和“已启用站点”分开,源文件放在可用区,启用区只放指向它的链接。链接的名字同样带 xin.example。退回时去掉的是启用区里的那一个链接,源文件可以留着待查。不要把新文件复制进一份别人也在复制的总文件。复制会产生两份以后会分叉的真理。

命名时看一下排序。有的入口按文件名顺序读入,并且把某个地址上读到的第一块监听当成隐式的默认站点。你的文件若以数字零开头,或用了会排到 default 前面的名字,就可能在你没有写默认标记的情况下变成默认站点。未知主机名的请求会开始返回新句子,甲或乙原来承担的默认职责被你接走。文件名用完整的域名,通常会排在一个叫 default 的文件之后,但不要靠猜。放入之前先看目录里现有的顺序,放入之后用后文的默认站点检查验证,而不是用字母表自我安慰。

不要使用会被人当成样例而覆盖的名字,例如单纯的 default、backup、new 或 test。这样的名字在别人排错时最容易被打开、被清空、被当成垃圾删掉。xin.example.conf 这种名字要求对方先承认他在碰你的站点。反过来,你也不要去整理目录里那些你觉得命名难看的旧文件。整理是另一次变更,有它自己的底片。这次的目录差异应当可以一句话说完:多了一个 xin.example 的文件或链接。

文件头部用注释写上三行就够:这个文件只服务 xin.example,上游是 127.0.0.1:18081,退回方式是移走本文件并重载。注释不是第二份配置。真正生效的仍是指令。注释的作用是让下一个打开它的人不去翻甲的文件找上下文。不要在注释里粘贴环境文件的内容,也不要把内部主机的真实地址写进一份会被人拷走的配置。上游使用回环和端口,已经足够具体。

若入口支持以片段方式被主文件引用,仍然保持“主文件只增加一行指向新文件”的最小改动,并在改之前留下主文件的副本。更好的办法是主文件里本来就有一行目录通配,你完全不碰它。如果你发现必须修改主文件才能让新文件被读到,把这个事实写进清单的风险栏。退回时除了移走新文件,还要恢复主文件。只移走新文件会让主文件引用一个不存在的路径,下一次测试直接失败,入口拒绝重载。那种失败至少还安全,可它会诱使你在紧张时改回主文件的别的部分。事先留副本,就是为了让这一步成为复制回去,而不是现场重写。

这次不要改入口的全局设置

入口的全局层决定所有站点共用的行为:同时能接多少连接、允许的请求体有多大、压缩是否打开、加密套件的底线、日志的默认格式、名字解析用的地址。这些行即使放在主配置靠前的位置,也影响甲和乙。新站点需要不同的值时,放到新文件的 server 块里,并且只放那些允许在 server 层覆盖的项。不能在 server 层覆盖、又必须改变的项,说明这次上线的范围已经超出“加一个站点”。把它拆成另一次变更,单独约时间,单独做底片。

上传大小是最常被放进全局的一项。新应用若要接收一张图片,而全局上限太小,正确的做法是在新文件里为这个站点写下更大的上限,而不是把全局改大。全局改大之后,甲和乙也会接受同样大的请求体。它们的程序也许没有准备好,磁盘却已经准备被占满。反过来,不要在全局把上限改小来“帮新应用省磁盘”。甲也许依赖原来的大小在提交一份正当的表单,底片里不一定有这条路径,你就会在上线后收到一个看起来像甲自己坏掉的故障。

压缩、缓存头和跨站相关的头部也一样。乙的静态资源也许依赖原来的压缩方式。你在 http 层换了压缩等级或关掉了某种类型,乙的页面标记仍是“墨”,字节和耗时却变了。这种变化不一定表现为状态码错误,但已经超出你的影响面。新文件里可以为自己的响应加头部。不要去改甲和乙已经在用的公共头部片段。还要注意一条继承规则:在某个 location 里新写一条头部,有的入口会因此不再继承 server 层的头部。你若从别人的文件抄来一个只为了加一行的 location,可能把安全相关的头部弄丢。新文件的 location 尽量少,转发用一个,证书校验用一个。少,才看得完。

加密协议的最低版本属于整台入口的门,不属于单个新站点。升高等级会让仍使用旧客户端的甲的访客失败,尽管甲的配置文件一个字都没改。这种现象在只测新句子的人眼里不存在。底片若没有包含“用甲的方式握手”,你至少不要在这次主动改它。新站点沿用入口已经接受的协议集合。等甲和乙的主人自己决定升级时,那是他们的变更。

名字解析同样不要改全局。有人为了让上游使用一个内部名字,把入口的解析器换成另一台,结果甲文件里原本依赖系统解析的上游一起改道。这篇的上游是字面的回环地址,入口不需要为了你去解析任何新东西。应用容器内部如何找到名为 db 的数据库,是容器网络的事,与入口的解析器无关。保持这两套名字系统分开,全局配置就可以完全不碰。

改完新文件后,用差异工具只比较你的新文件,并确认主配置和甲、乙的文件没有差异。不要用“我记得没打开过那些文件”代替差异。编辑器的自动保存、格式化工具的全目录运行、以及错误的批量替换,都会在你没注意时改掉邻居。批量替换尤其危险。你想把示例里的域名换成 xin.example,替换范围却是整个配置目录,于是甲的 server_name 也被换了。替换之前先把范围收成那一个新文件的路径。若已经发生,立即用底片里的摘要找出被改的文件,从你事先留下的副本恢复,而不是手工把域名改回去。手工改回去很容易漏掉第二处。

访问日志和错误日志按站点分开

新文件里明确写出自己的访问日志和错误日志,路径都在 xin.example 自己的目录下。不要省略这两行,从而落入入口的全局日志。全局日志不是不能用,而是一旦共用,你的请求和甲、乙的请求写在同一条流里。出问题时你无法用“这个文件在增长”来判断是谁在被访问,也容易在清理时按大小把别人的排障材料一起删掉。

访问日志至少要能看出这些事实:主机名、请求路径、状态码、字节数、耗时、上游状态码、上游耗时。主机名用来确认这条记录真的属于 xin.example,而不是你看错了文件。上游状态码用来区分入口自己生成的错误和应用返回的错误。入口找不到上游时,访问日志里常常是 502,上游栏是拒绝或超时;应用自己返回 404 时,上游栏也会是 404。这两种 404 或 502 对退回的意义不同。前者要查转发目标,后者要查应用,都不要先去改甲。

错误日志记录入口在加载和转发时的抱怨:证书文件打不开、上游地址无效、权限不够。给这个文件单独设置级别,不要为了新站点把全局级别改成调试。调试级别会让甲和乙的错误日志在同一小时里暴涨,占满磁盘。你需要细节时,只把新文件的错误日志调细,看完再调回去。调回去也是一次对单文件的变更,走同样的测试和重载。不要让“临时调细”过夜。过夜的调试日志是磁盘事故的常见开头。

有的请求不该留下正文。环境文件里的口令、用户提交的凭据、完整的请求体,都不写进访问日志。默认的短格式通常只含路径和头部里的少数字段,保持这种克制。不要为了省事开启“记录全部头部”的格式。全部头部里可能有会话标识。那个格式若写在全局,甲和乙的访客也会被你记下来。这是越界,不只是技术上的不谨慎。新站点自己的日志同样避免全量头部。需要排错主机名时,一条 Host 就够了。

分开之后做一次对得上号的试验。向 xin.example 请求一次新句子,甲的日志文件大小和最后修改时间应当不变,新的访问日志增加一行,并且这一行里看得到 xin.example 和 200。再向甲请求一次,新日志不增长,甲的日志增长。这两下交叉试验比任何“我们已隔离”的说明都有用。若交叉失败,说明你写错了日志路径,或仍在写入一个被甲引用的共享文件。在宣布上线前改路径。改的仍是新文件。

日志文件的所有权要允许入口进程写入,但不需要让所有登录用户可读。目录给入口写入、给运行应用的账户读取通常就够。不要把日志目录 chmod 成谁都能写,否则任何一个本地账户都能篡改你的排障记录,或用链接把日志指到系统的敏感文件上。若入口以受限账户运行,事先用这个账户的身份确认它能在该目录创建文件。确认方法是看重载后的错误日志是否抱怨权限,而不是先把目录放开。

内存临时空间和日志总量都要写得下数字

上限不是为了把应用卡死,是为了让它在无人值守时不能占满整台机器。甲和乙与你分享磁盘、内存和文件描述符。你的新进程若慢慢把内存吃完,内核会开始杀死进程,被杀死的不一定是你。你的日志若无限增长,甲的数据库可能因为写不进检查点而失败。这两种失败在甲的页面上表现成“墨”不见了,根因却在你的目录里。所以上限要在上线前写成数字,而不是写成“适当”“合理”这种无法核对的词。

内存用这次部署自己的限制,不要用整机的全局策略去卡所有进程。先在练习或在未接流量时看应用空闲时占用多少,再看一次请求那句新话时占用多少,取一个高于峰值、又远低于整机可用量的值。写不出测量时,就选一个你愿意在它被杀死时接受的上限,并写明“这是猜测,第一小时要看有没有被杀死”。猜测可以暂时存在,但不能伪装成测量。限制应当作用在新应用这个服务上。它被限制杀死时,xin.example 返回错误,甲和乙仍在。若你改的是整机的内存策略,这个区分就没有了。

日志的上限用单文件大小乘以保留份数,把乘积写进清单。例如单文件到了一定大小就滚动,只留固定的几份,旧份压缩。总字节数应当是你看着磁盘剩余量可以接受的一个分数,而不是“先记着,磁盘报警再说”。报警到来时,甲已经在受害。滚动方式也要选对。有的程序只在打开时确定文件位置,你把文件换名后它仍往已删除的旧描述符里写,磁盘用量不下降,新文件却是空的。入口自己通常会在收到重开信号时重新打开日志。新应用若把日志写在容器里,使用它自己的滚动,或让收集方式适配它的重开习惯。不要假设所有程序都和入口一样听话。

临时目录和上传目录同样要有天花板。新应用解压、导出、接收图片,都会在某个临时位置长大。这个位置放在 xin.example 自己的卷上,不要放在与甲的数据库同一条会撑满的关键分区上,若机器只有一块盘,就用账户配额或应用自身的大小限制挡住它。接收请求的入口侧也可以为这个站点单独限制请求体,这与前面说的“不要改全局上传上限”是同一决定的两面:限制写在新文件里,数字写在清单里。

进程若被内存限制杀死后立即无限重启,每一次启动都会再写一遍启动日志,磁盘仍然会被吃满。重启策略应当有退避,并且在一段时间内失败到一定次数就停住,而不是以最快速度循环。停住之后,xin.example 保持失败,等待人来看,这是可以接受的。用不断重启去掩盖失败,会把一次应用故障变成一次全机磁盘故障。第一小时里看的不是“它会自己拉起来吗”,而是“它拉起来的频率是否在把磁盘往死里写”。

这些数字都写在新应用的启动配置或服务定义里,不写进甲和乙的单元。改完之后用服务的状态查看确认限制字段真的出现在运行中的对象上,而不是只出现在你没保存的草稿里。草稿里的上限等于没有上限。若运行中的对象显示不受限制,先不要把入口指过去。没有上限的进程可以先在回环上接受你的探测,但不应当接受持续的外部流量。

权限收到刚好够用

运行新应用的账户只拥有 xin.example 这棵目录。站点配置由入口的账户读取,环境文件由应用账户读取。入口不需要读环境文件,应用不需要读甲和乙的网站根,也不需要读入口的主配置。能用普通账户完成的绑定,就不要把应用放进超级用户组。前面已经说过高端口不需要特权。这里补上目录这一侧:特权不只来自监听,还来自组成员身份。把应用账户加进“可以读所有网站”的组,效果和用超级用户差不多。

环境文件的权限设成只有所有者可读写。目录设成所有者可进出、同组最多可读、其他人不可写。证书私钥让入口的账户能读,不要让所有登录用户能读。常见的做法是私钥属于入口所在的组,组外不可读。新应用的账户不在这个组里。它不需要私钥,因为加密停在入口。若有人把私钥复制进应用目录“方便一起备份”,应用目录的备份就会带走入口的身份。证书文件可以按站点分开放,备份新站点时只备份新站点的那一对。

出权限错误时,一次只放宽一个路径,放宽到刚好能通过刚才失败的那一次读取或写入,然后停。不要对父目录递归放开。递归会把你没检查过的子目录一起变成谁都可以写,其中可能有甲的上传、乙的日志或你自己的环境文件。错误日志会写出被拒绝的那条路径。对着那条路径改。若错误日志没有路径,先提高这一份错误日志的详细程度,仍只改新文件,看清路径再动手。

容器的文件系统也可以收紧。除了数据卷、临时目录和只读的环境文件,根文件系统以只读方式运行。这样应用即使被骗去写一个系统路径,也写不进镜像里的其他位置。不要为了让安装脚本省事而在运行期把整台容器改成可写,再把可写留到上线之后。安装若必须写文件,把它放进初始化阶段,写完就结束,运行阶段仍回到只读加数据卷。数据卷的挂载点不要选在容器里的系统配置目录上,以免应用的数据盖住容器里的程序。

入口要读的站点文件和证书,权限应当在重载之前就正确。测试命令若以和正式服务相同的账户运行,它能提前发现“打不开证书”。若测试以超级用户运行、正式服务以受限账户运行,测试会通过,重载后工人进程却报权限错误。新站点因此失败。更麻烦的是,有的入口在这种情况下会继续用旧工人服务旧连接,新工人则反复退出。表面看甲还在,实际上你已经进入一种不稳定。所以测试的身份要和运行的身份一致,或至少在测试后用运行身份试读那两个证书路径和两个日志目录。

最后做一次负面检查。用应用账户尝试列出甲的网站根,应当失败。用一个无关的登录账户尝试读取环境文件和私钥,应当失败。这两次失败是你想要的结果,写进核对记录。很多人只记录放宽之后的成功,不记录边界仍然有效。以后权限被一次递归改掉时,你就没有“它曾经正确地失败过”这条基线。失败的那两行和“青”“墨”一样,是底片的一部分。

本机连续拿到新句子之后才把流量指过来

入口还没有引用新应用时,先在回环上向 18081 要正文。你要看见的是“新窗已开,旧灯未动”,状态是 200,主机名是 xin.example。这一步不经过 443,也不经过公网解析。它证明的是进程、环境文件、数据库和绑定地址。它证明不了证书,证明不了站点文件,证明不了域名。那些留到后面各自的步骤。混在一起证明,失败时你会从错误的一层开始改。

一次成功不够。应用可能在第一次请求时刚好完成初始化,第二次才因为连接数、权限或库的账户而失败。连续三次,每次隔开几秒,三次都拿到同一句新话,再进入下一步。若应用提供单独的就绪路径,三次都打在就绪路径上,并再打一次真正返回新句子的首页。就绪路径只表示依赖可用,首页才表示访客会看见的那句话已经能生成。两者都要。不要把进程仍在运行当成就绪。进程在,可能只是它还在等数据库、在建表,或停在一个等待你输入的安装向导上。

安装向导若会在第一次访问时创建管理员,就在回环上、在你自己的浏览器或命令里走完它,不要等公网访客来走。访客走完后,初始口令可能留在别人的屏幕上,数据目录里会留下半次安装。你之后再想重来,就要分辨哪些是向导写的、哪些是别人已经改的。向导要求填写对外地址时,填写 https 加 xin.example,不要填写 127.0.0.1:18081。填了回环,等入口接上之后,页面里的绝对链接仍会把人送回一个外部无法访问的地址。那是应用配置问题,不是甲的问题。在把流量指过来之前改掉它,再重新要一次新句子。

健康检查失败时,只看新应用的日志和数据库的状态。不要重载入口,不要重启甲和乙,不要修改他们的文件来“对比试验”。入口此时甚至可以还没有你的站点文件。保持它不参与,故障面就只有你刚刚启动的那些进程。日志若指出环境文件无法读取、数据卷不可写、或监听的端口和清单不一致,改完这一项就重新做连续三次。不要一次改三项再看结果。三项一起改,成功了也不知道该留哪一项。

等待要有上限。到了你事先写好的分钟数仍拿不到新句子,就停止等待,按失败处理这一次启动,而不是把站点文件先放进去“也许入口能帮上忙”。入口帮不上数据库账户错误,也帮不上数据卷权限。它只会把还没准备好的上游暴露成 xin.example 的 502。若这个名字先前落到默认站点并返回“青”,你还会用 502 替换掉一个本来有人在看的默认页。这也是为什么新名字的变更前回答要写在底片里。你是在替换一种已知的旧行为,不是在空白上画画。

容器的启动顺序也写进定义,而不是靠人眼盯着日志去点第二个按钮。数据库先通过它自己的检查,应用再启动。应用的重启不应当把数据库一起重启。有的编排把两者写在同一个重启单元里,结果你只想重拉应用,库也被关掉,甲若碰巧与这个编排共享引擎资源,还会感受到一次无谓的负载抖动。即使甲的进程没被停,整机的突然压力也是影响。独立的单元、独立的重启,是小影响面的一部分。

只有连续三次稳定之后,才把站点文件放进会被加载的位置。这个顺序可以倒着记住:没有新句子,就没有新文件;没有新文件,就没有重载。很多人把文件先放进去,再一边重载一边改应用,于是每一次应用的失败都变成一次入口配置的变动。入口的工人被反复替换,甲和乙在你排错的几分钟里反复经历重载。他们的正文也许还在,长连接和上传却在吃苦。把排错留在回环这一侧,入口就可以一直静着。

配置测试通过再做平滑重载

站点文件就位之后,先运行入口自己的测试,不重载。测试应当使用和正式服务相同的程序与相同的配置前缀。它会检查语法、被包含的文件是否存在,以及证书路径能不能被打开。测试失败时,输出里会指向文件和行。你只改那一行,再测。测试没有通过,就不要发重载。正在运行的工人应当继续使用旧配置,甲和乙因此感受不到你的语法错误。

测试通过不等于上游是好的,也不等于证书上的名字就是 xin.example。测试大多不连接 18081,不验证正文是不是那句新话,也不对比证书里的使用者名称。这些仍由健康检查、正文核对和证书核对各自负责。把测试当成“一切正常”的印章,是范围错误。测试的印章只盖在“入口愿意加载这份文本”上。它拒绝加载时,你得到的是保护;它愿意加载时,你只得到进入下一步的资格。

重载选择平滑的那一种:主进程读入新配置,启动新的工人,旧工人把手上的请求做完再退出。主进程号应当与底片相同。主进程号若变了,你执行的是重启,不是重载。重启会打断甲和乙正在进行的传输。Nginx 的文档把这一种重新加载描述为主进程重读配置并优雅结束旧工人。Nginx:控制 系统的服务管理器里,对应的是 reload 而不是 restart。这两个词只差几个字母,影响面差整台机器上的全部站点。发出命令前读一遍你将要执行的那一行。

不要使用会重启所有容器、所有站点单元或整台机器的命令来让一个文件生效。若你发现自己正在输入的命令会影响清单上那些不相关的名字,停下来,换成只针对入口主进程的重载。重载之后立刻再看监听者。80 和 443 仍由原来的程序持有,数量不增加。18081 仍只在回环上。这三样任何一样变了,都先停止后续步骤,按后文的退回处理,而不是继续测新句子。

重载之后用入口的完整配置导出再确认一次:导出的文本里出现 xin.example,出现 18081,出现你的证书路径,并且这些字符串只出现在你的文件被包含的那一段。甲的 server_name 仍是甲,乙仍是乙。导出比“目录里有这个文件”更可靠,因为文件可以放在目录里却没被包含,也可以被包含两次。包含两次时,入口会抱怨冲突的名字,实际生效的是先载入的那一块。你改的是后一块,于是怎么改都像没生效。导出能让这种错位被看见。

若测试在正式机器上因为证书权限而失败,不要把证书改成所有人可读来换取测试通过。回到权限那一节,让运行身份能读、其他人不能读,然后再测。为了让测试变绿而放宽私钥,是用一个长期的暴露交换一个短期的绿灯。测试可以多跑几次,私钥不应当为了工具的方便而改变读者范围。

平滑重载期间,甲和乙的短请求应当保持底片上的状态码。你可以在重载的同一分钟里请求一次“青”和一次“墨”。若它们失败,先看你是不是误发了重启,或误改了全局文件。不要把这种失败解释成“重载总会抖一下”然后继续。平滑重载的设计目的就是让这种抖动不发生。发生了,就是异常,值得在这一分钟停下来。

名字没对上时请求会进默认站点

入口在一块监听上选定默认站点的规则,和选定具名站点的规则不是一回事。具名站点靠 server_name 的匹配顺序。没有任何名字对上时,请求交给这一个地址和端口上的默认站点。默认站点要么是你明确标了 default_server 的那一块,要么就是该监听上最先被载入的那一块。Nginx:listen 它是监听的属性,不是某个域名的属性。新文件不应当带上默认标记,也不应当靠排序变成第一块。

名字写错时,现象往往不是你的站点报错,而是别人的正文出现在你的域名上。把 xin.example 误写成少一个字母的样子,请求对不上任何精确名字,于是落到默认站点。若默认站点是甲,你就会在新域名上看到“青”,状态码还是 200。这是这篇反复用短标记的原因。200 证明不了站对了。只有新句子证明转发到了新应用,只有“青”和“墨”各自留在自己的名字上,才证明你没有把默认站点接走。

用三个请求把这件事测实。第一个请求使用正确的 xin.example,期望新句子。第二个请求使用一个显然不存在的主机名,期望它的正文和状态与底片里的默认站点完全一致,不出现新句子。第三个请求不带主机名,或使用地址本身去访问,期望同样落在原来的默认站点上。第三个请求专门防止你只在有名字的时候是对的、在缺少 Host 的时候把新站点变成了兜底。缺少 Host 的请求在公网上少,在健康检查和某些旧客户端上并不少。

不要用正则去“顺便”匹配带不带前缀的各种写法。正则按配置中的出现顺序尝试,第一条命中的生效,而且区分大小写与否取决于你写的标记。精确名字和通配对大小写不敏感,正则默认敏感。你从网上抄来的一条正则可能匹配到超出 xin.example 的范围,或因为大小写而匹配不到你自己。一个新站点用一条精确名字,把大小写问题留给入口已有的精确匹配规则。国际写法的名字同样精确写入,不要用正则去描述它。

冲突的名字要当错误处理。若导出的配置里 xin.example 出现在两块 server 中,入口通常会留下冲突警告,并让先载入的那块生效。你后续的修改若落在不生效的那块上,就会出现“文件明明改了,外面却不变”的错觉。然后你会去改全局、改默认站点、甚至重载甲,试图推动变化。先删掉重复的那一块,直到导出里只剩一处。重复常常来自:文件和它的副本同时放在被加载的目录里,或链接和实体同时被加载。目录列表应当让这种双份显形。

默认站点原来是谁,写在底片里。上线后仍是谁,用那个不存在的主机名再验证一次。新句子若出现在不存在的主机名上,说明你抢到了默认位置。立即退回新文件,检查文件名排序和有没有写下默认标记,然后才允许再次放入。不要用“未知名字看到新应用也没关系”来接受这件事。未知名字里包括配错的甲的子域名、包括通配解析扫到这台机器的名字、也包括明天别人打算使用的名字。兜底是整台机器的策略,不是新站点的红利。

加密的那一面还有名字指示。客户端在握手时声明它要的主机名,入口才选得到正确的证书。没有声明时,入口出示默认站点的证书。你若把新站点做成了 443 的默认站点,这些没有声明的连接就会拿到 xin.example 的证书,甲的一些旧客户端或监控会开始看到名字不符。即便具名访问的甲仍然返回“青”,你也已经改变了默认握手。所以 443 上的默认标记和 80 上的默认标记要分别检查,一块正确不能代表另一块正确。

绑定地址和转发目标写错时分开认

绑定错误和转发错误的症状不同,不要用同一种修改去试。应用或发布绑定宽了,症状是:从别的机器也能直接打开高端口,监听列表或发布表显示通配或对外地址,新句子可以不经过 443 就出现。这时入口可能完全正确。你要收紧的是应用的监听或容器的发布行,然后确认公网高端口不再接受连接。不要去改甲的防火墙规则来遮住一个你多开的口。遮住是另一层的偶然,发布表才是这次的事实。

转发目标写错,症状几乎相反:本机直接访问 18081 仍能看到新句子,通过 xin.example 却是 502、404 或超时,甲和乙依旧正常。入口的错误日志会写它试图连接的地址。把那个地址和清单上的 127.0.0.1:18081 对一下。常见的错法包括:写成了容器内部端口、写成了数据库端口、写成了 80 或 443 从而把入口转回入口自己、写成了一个需要公网解析的名字。每一种都只改新文件里的上游那一行,改完测试并平滑重载,再只重试 xin.example。不要同时改应用的监听端口。两个数字一起改,你会失去“哪一边才是清单上的原计划”的参照。

还有一种错法把上游写成宿主机的公网名。请求从入口出发,解析到这台机器的外部地址,再进入 443,再次匹配 xin.example,再次转发,形成环。症状是超时或错误次数迅速上涨,新应用的日志里却看不到正常的页面请求,或者看到异常地多。甲的短请求也许仍快,因为它们不走这条环。处理方式仍是把上游改回字面回环,而不是加大超时让环转得更久。加大超时会让环占用更多工人,那时候甲才会开始痛。痛出现之前就改回短的本地上游。

地址族也会拆成两个事实。你可能只检查了网际协议第四版的监听,第六版仍以通配方式听在同一个端口上。从只具备第四版的办公机测试,会认为端口已关闭;从具备第六版的网络看,端口是开的。监听列表要把两个地址族都看完。容器发布若只约束了第四版,同样要确认第六版没有另开一扇。这篇的目标状态很单薄,单薄才容易核对:对外网卡上,无论哪个地址族,都没有 18081,也没有数据库端口。

应用绑定在容器回环上、发布却指向容器的通配端口时,症状是发布存在、健康检查却被拒绝。这不是入口的错,也不是甲的错。把容器内进程改到容器网络的通配地址,保持宿主机发布地址为回环。两个通配不要谈拢成一句“通配是坏的”。容器命名空间里的通配,和宿主机上的通配,是两句不同的话。清单上用“宿主机地址”和“容器内地址”两行把它们分开,避免下次又省成一个词。

写错之后的最小修复永远只动一行。修复后重复那个能区分症状的请求:绑定问题重复“从外面碰高端口”,转发问题重复“从入口要新句子,同时直连 18081”。两条都恢复预期,才算这一节的错误已经结束。若你在修复中重启了入口的主进程,就超出了这一节,要按重载那一节的标准重新核对甲和乙。不要让一次上游笔误演化成一次全机重启。

不要重启整台机器也不要重启所有容器

重启整台机器会让甲、乙、新应用以及所有未写入你清单的任务一起停下再起来。起来的顺序由系统和各个单元自己的依赖决定,不一定等于它们昨天运行的顺序。甲也许依赖的本地服务比入口晚几秒,于是在这几秒里“青”变成了错误页。你的新站点即使配置全对,也会被记成这次重启的责任人。况且重启把太多变量同时改变:缓存清空了,连接断了,定时任务补跑了。出了差异时,你无法判断是站点文件造成的,还是重启本身造成的。

重启全部容器是同一类命令的缩小版,缩小得不够。它仍会停掉不属于你的数据库、队列和入口。有的编排里入口也在容器中。你重启所有容器,等于重启了门。正确的范围只有两个单元:新应用,以及在它确实依赖时的那个新数据库。入口使用重载,不进入重启名单。甲和乙的单元连重启名单的边缘都不要碰到。

系统的软件包更新有时会在安装结束时自动重启服务。你若在同一次维护里既添加站点又更新入口的软件包,就无法知道行为变化来自文件还是来自版本。这次不要一起做。软件包留到单独的窗口,并在那个窗口使用它自己的底片。若你发现系统已经替你重启了入口,先核对“青”和“墨”,再决定是否继续。不要在一个已经非计划重启过的现场上继续叠加新文件。先把现场重新记成一张新底片,确认邻居稳定,然后再只做加站点这一件事。

长连接对重启尤其敏感。乙的页面正文可以仍是“墨”,一条正在上传的连接或一条保持的管理通道却被重启切断。你的核对若只看短请求,就看不见这种伤害。平滑重载的意义正是让这些连接由旧工人送完。选择重启等于放弃这个意义。若你不确定某个管理命令会不会杀工人,先在文档或命令的帮助里确认它的动词,或在练习环境对一个无关的入口试一次并观察主进程号。不要在正式机器上用甲的连接做这次学习。

内核更新、磁盘检修这一类确实需要重启整机的工作,不属于“加一个站点”。它们要单独公告、单独选择时间,并且不与新站点的第一次上线绑在一起。绑定在一起时,任何失败都会让人想用更大的重启去解决,于是范围越来越大。把今天的成功定义收小:入口主进程号不变,机器的启动时间不变,容器列表里除了你新加的单元没有别人被重新创建。这三句能在命令输出里核对。核对不过,就还没有做完“小影响面”这件事。

已经错误地重启了整机时,不要接着改配置来“利用这次重启”。先按底片把邻居测完,记录哪些差异来自重启本身,等它们恢复,再决定新站点文件是留是撤。在混乱里继续编辑,是把一次事故变成两套同时存在的改动。退回时你将不知道该回到哪一层。安静地测完,比立刻动手更有用。

重载之后按行核对邻居而不是写一句全部正常

新句子出现,只说明 xin.example 这一条路径通了。邻居要按底片逐行再测。每一行左边是变更前的状态码和标记,右边是变更后的。甲的首页仍是 200 和“青”,甲的不存在路径仍是原来的 404,乙的首页仍是 200 和“墨”,乙的跳转仍指向乙,跳转的状态码种类不变。新名字不再写在邻居的表里。它单独有四行:首页是新句子,一篇正文路径,一个静态资源,登录或就绪路径。四行新路径加上全部旧行,才构成这次的核对。用“全部正常”代替这些行,下周你就无法回忆到底看过谁。

请求时不要自动跟随跳转。跟随之后,你记录的是落地页的状态,不是这条路径本身的状态。乙的 302 若被你改成了直接 200,跟随跳转的工具仍可能显示最终的“墨”,于是你错过了越界。把状态码和第一行正文分开打印。正文只取开头的标记,避免把整页噪声抄进记录。时间、缓存年龄、加密会话编号这一类本来就会变的字段,不要放进必须相同的列。必须相同的是:状态码、标记、跳转目标的主机名、证书上的名字。

证书要单独看,不要假设页面能开就说明证书没被换。对甲和乙分别查看证书里的名字与到期日,与底片一致。新证书应当只在 xin.example 上出现。若甲的握手开始出示新站点的证书,说明默认站点或证书路径被你碰过,这不是“稍微混了一下”,是必须退回的混用。页面正文仍可能是“青”,因为加密层选错证书时,里面的内容有时仍来自正确的站点块,有时不。两种情况都不要接受。

核对的时钟分两段。重载一结束就测邻居,因为他们不依赖新进程的启动速度。新句子则在连续健康检查已经通过之后再测。若新句子失败而邻居正常,停留在新文件和 18081 上排错,不要为了安慰自己去重启邻居。若邻居失败而新句子正常,先假定是你的文件或你的重载方式造成的,打开差异和导出配置。只有新文件完全解释不了邻居的变化时,才去看是不是同一分钟里发生了另一件事,例如甲自己的证书刚好到期。两件事可以同时存在,但不要混成一个修复动作。

从这台机器上用显式主机名访问入口,可以排除域名解析的干扰,专门检查站点块。从另一台机器访问,才检查解析、防火墙和公网路径。邻居的公网路径你本不该改动,所以外网核对应当和底片一样。若外网失败而本机主机名成功,故障在解析或防火墙上,不在应用里。不要重启新应用来治疗解析。若本机主机名已经失败,先不要出公网,公网只会给你同一个错误的更多样本。

把原始输出留在记录里,包括命令、时间和关键的一两行结果。不要只留一句转述。转述会把 302 说成“能打开”,把证书名字不符说成“有点警告”。原始的一行足够短,也足够以后比较。至少在重载后的当时和十来分钟之后各做一轮邻居核对。第二轮用来抓住那些“重载瞬间被旧工人遮住、新工人才暴露”的错误。两轮之间不要再改配置。你若改了,第二轮就不再是对第一轮的复核。

静态资源和登录路径不要省略。首页是新句子,样式或脚本却从甲的目录里取,页面会看起来半对半错。单独请求那个静态地址,确认它的主机名仍是 xin.example,内容不是甲的文件。登录路径则用来看应用有没有把人送到 18081、送到甲、或在两个名字之间来回。送到回环地址,说明应用心目中的外部地址填错了,回到健康检查之前的那一项去改应用,而不是去改甲的跳转。

失败时只移走这一份配置

退回的动作和进入时一样小。确认要退回之后,把启用目录里的新文件或新链接移出入口会加载的位置,不要删掉文件内容。再运行测试。测试通过,就平滑重载。然后按核对表重测甲和乙,他们应当回到与底片一致。再测 xin.example:若它变更前落在默认站点,现在应当重新回到那种旧回答;若它变更前无法解析或被拒绝,现在不应当再返回新句子。新应用的进程可以停下。数据目录先留着。

不要用恢复整台机器的备份来撤回一个新网站。整机恢复会把甲和乙一起带回更早的时刻,他们在你上线之后写入的内容会消失。那种备份是磁盘级灾难的工具,不是一个站点文件的撤销键。你今天需要的撤销键就是把一个文件移出加载目录。若主配置曾经为了包含它而多写了一行,就把主配置从你留下的副本换回去,再测试、再重载。不要在主配置里临时注释一半。注释是编辑,编辑会产生第三种状态。

移走文件之后,用导出的完整配置确认 xin.example 不再出现,18081 不再出现。目录里还留着一份停用的副本没有关系,只要它不被加载。被加载的标志不是“它在某文件夹里”,而是导出文本里还有它。有人把停用目录也放进了通配路径,于是移到停用目录等于没有移走。导出能揭穿这件事。揭穿之后,把停用副本放到入口根本不会扫描的位置,例如应用自己的家目录里一个明确叫停用的文件夹。

数据目录不要在退回的同一分钟删除。第一次启动可能留下半初始化的库,你也许还想保留现场给下一次修复。先把目录改名为带有时间的停用名字,观察一段时间,确认没有进程还打开其中的文件,再决定是否销毁。改名比删除宽容。删除之后,下一次启动若指向同一路径,会得到一个空库,你可能把空库当成“它自己清空了自己”而去查应用的逻辑。改名能让这种误会无法发生,因为旧数据还在旁边,新启动若再创建,只能创建在新的空路径上,而且那也应当是你明确让它做的。

日志在退回后保留。失败的访问日志和错误日志是下一次避免同一行配置的依据。不要因为退回而执行清理全部日志、清理全部未使用镜像或压缩整个磁盘的动作。那些动作的影响面比退回大。日志里若含有不该记录的凭据,单独处理这一份日志的权限或内容,而不是借机扫描并改写甲的日志。

退回完成的定义同样要逐行写:入口导出中没有新站点,邻居与底片一致,18081 不再监听或仍只在回环且你明知进程已停,数据目录已改名而不是已消失,主进程号在这次退回的重载中保持不变。缺任何一条,就还没有退完。不要在缺条的情况下开始下一次上线尝试。两次尝试叠在同一张脏底片上,差异就无法归因。

若新名字已经被告诉给会来访问的人,退回前先通知他们这个名字会暂时失败或会回到旧的默认页。突然把新句子换成“青”,对访客是另一种串站。通知是退回步骤的一部分,不是礼貌的附加。没有人使用这个名字时,也在记录里写明“尚未对访客公布”,这样以后读记录的人知道当时不需要通知。

备份要能退回这一站而不是把邻居带回昨天

在放入站点文件之前,备份的对象是“你即将获得改变配置的能力”,不是把整机抄一遍。具体地说:保存配置目录的文件名列表,保存主配置和甲、乙文件的摘要,保存你即将覆盖的任何文件的副本。你即将新增的文件还不存在,所以没有内容可备份,它的退回就是删除或移走。这份列表和摘要放到应用目录之外的管理位置,不要放到网站根里。放到网站根里,就可能被某个站点当成文件提供给访客。

新应用第一次成功说出新句子之后,再导出一份只含新库的备份。导出发生在你宣布名字可用之前。这样若随后的站点配置失败,你至少还能把应用数据单独放回,而不去碰甲的库。导出文件的名字带上 xin.example 和时间。权限只有所有者可读。路径不在任何 server 的根目录、别名或上传目录之下。做完导出,用一个只读的检查确认文件不是空的,并且头部像一份数据库导出,而不是一条错误信息被重定向进了文件。空文件和错误信息在大小上有时都不像“零”,不打开看就不知道。

恢复演练不要在这台正在服务的机器上对原路径做。在练习环境或另一套空目录里恢复,期望恢复之后能再次生成那句新话,或至少能看到你写入的那一行标记数据。演练用的数据库监听也只在练习环境的内部网络里,不映射到公网。演练成功的标志是标记在,而不是“导入命令返回了零”。命令返回零,可能只表示它执行了,不表示表里有你要的行。

整机快照若已经由机器的管理人定期做,你不需要为了加一个站点再做一次,更不需要在失败时要求对方回滚快照。快照会把邻居的数据一起带回快照时刻。向管理人说明你的退回不依赖快照,可以避免对方在慌乱中按下那个过大的按钮。你自己的小备份与对方的大快照并存是正常的。两者的用途不同,不要互相替代。

配置副本和库导出不要放在同一个会随容器删除而消失的临时层里。容器被你退回并移除时,可写层里的东西会跟着消失。那正是你最需要备份的时刻。把它们放在独立卷或管理用的目录,并且这个位置写进那一页记录。以后的人不必猜“备份当时落在哪个容器里”。也避免放在个人的下载目录里不加说明。下载目录会被清理工具按日期处理掉。

备份本身不要成为新的长期服务。不需要为了这一次上线在宿主机上新开一个对所有库做导出的定时任务。那样的任务若写错范围,就会把甲的库也导出到一个权限过宽的目录。你只在变更的节点上导出新库。若应用以后确实需要每日备份,那是上线完成之后的另一次设计,有自己的目标目录、自己的权限和自己的恢复演练,不夹带在今天的站点文件里。

上线结束后,把“备份在哪里、演练过没有、演练看见了什么标记”写成三行,附在核对表后面。没有演练过,就写没有,不要把导出等同于可恢复。很多导出文件要到恢复那天才暴露路径、权限或版本不匹配。你在练习环境花一次小恢复,换的是正式退回时不用猜。

证书私钥和校验目录都只用新站点的路径

新站点的证书和私钥放在以 xin.example 命名的目录里,站点文件里的路径指向这一对,不指向甲或乙的文件。即使两个名字将来可能出现在同一张证书上,这次也不要去替换邻居正在使用的那一对文件。替换邻居的文件,等于让他们的握手依赖你的更新是否成功。你的更新失败时,他们的站点会在下一次握手时失败。路径分开之后,你的失败最多让 xin.example 出示不了证书。

取得证书时,校验用的临时文件只写在新站点自己的目录。80 上那一块为 xin.example 单独开一个只匹配校验路径的 location,根目录指向这个新目录。其余路径仍按计划跳转到加密地址。不要把校验根目录设成甲的网站根,理由哪怕是“入口本来就有权写那里”。你有权写,不代表这次应该写。校验文件出现在甲的目录里,甲的内容管理会看到一个它不认识的文件,严重时你覆盖了甲的同名路径。

若 80 上的全部路径都被一条跳转规则吞掉,校验请求也会被送去 443,校验因此失败。所以校验路径要写在跳转之前,并且只存在于 xin.example 这一块,不要写进默认站点,也不要写进甲。校验结束后,那个临时目录里不应当留下多余的脚本。校验文件是短命的。不要把这个目录做成可上传的公共盘。

证书发下来之后,阅读它的名字列表和日期,确认里面有 xin.example,没有 jia.example,除非你本来就在做一张明确的多名字证书并且甲的主人同意替换。这篇的范围是一张只服务新名字的证书。阅读使用入口或系统的查看工具,对着文件看,而不是对着站点文件里的路径假设“路径对了内容就对”。路径可以对,文件却是上一份失败的空壳或是别人的证书副本。

替换证书文件时用先写临时文件、再改名的方式,避免入口在重载的那一瞬间读到半截。不要先把原文件截断再往里写。截断之后若写失败,下一次重载会加载一份空的或残缺的证书,xin.example 的握手失败。甲的路径若是分开的,甲仍正常。这正是路径分开要买到的结果:你的替换事故停在你的名字上。替换之后再测试并平滑重载,然后只对 xin.example 做握手检查,对甲做底片上的握手检查。

续签的钩子也只处理这一对路径。钩子的动作是:放入新文件、测试入口、平滑重载。测试失败就不要重载,并保留上一份能用的证书。钩子不要复制“所有证书到统一目录时覆盖同名文件”这种会波及邻居的步骤,不要重启整机,不要重载一个范围不明的脚本。续签失败应当只让你收到告警,而不是让甲的证书文件被一只失败的脚本清空。把钩子先在练习环境跑一次,故意给它一个无法通过测试的配置,确认它会停在重载之前。

私钥权限在续签之后可能被工具重置成过于宽松或过于严格。宽松会让其他账户可读,严格会让入口的下一次重载失败。续签钩子的末尾检查一次权限,把它调回“入口可读、其他人不可读”,再测试。不要为了让钩子好写而把私钥目录设成所有人可写。所有人可写的证书目录,等于允许本地的任意账户在下一次重载前替换你的站点身份。

根目录别名和上传目录不要借邻居的树

若新应用的全部页面都由 18081 生成,新的 server 块就可以不设网站根,或把根指到一个空的、只属于 xin.example 的目录。不要指到甲的树。一个从甲复制来的根,加上一条“文件不存在再转发”的规则,会让 xin.example 先把甲的文件说出去。甲若有一张叫作首页的静态页,访客看到的是“青”或者甲的完整页面,转发根本没发生。状态码是 200。你会对着应用的日志纳闷为什么没有请求。请求停在了入口的静态文件阶段。

别名比根更容易写错边界。别名把一个网址前缀直接映射到文件系统的某个位置。末尾斜线多一个或少一个,映射就会滑到父目录。父目录里若能看见甲的上传或其他站点的主目录,你就用网址把邻居的树公开了。新站点若确实要由入口直接提供静态文件,别名的目标必须是 xin.example 目录内部的一个专门文件夹,并且用一个目录列表命令确认那个文件夹里没有指向外部的链接。链接会让看起来“在目录内”的路径读到目录外。

上传目录属于应用的数据卷,不属于甲的上传目录。不要为了复用空间而把甲的上传挂进新容器。复用之后,两个应用可以改同一批文件,也可以读到对方访客提交的内容。入口若要分担静态文件的发送,只分担你自己树里的、可以公开的那些,例如样式和图片的构建结果。用户上传若需要登录才可见,就仍由应用判断之后再发送,不要用一条静态别名把上传目录整个暴露。

缓存时间也不要抄邻居的。乙也许给字体设了一年的缓存,因为那些文件名里带有版本。你的新句子所在的页面若被设成同样的一年,你下一次修改句子,访客会在很长时间里看见旧句子,你会以为转发没有生效,然后去改入口、改默认站点,越改越远。页面用短缓存或不缓存,带版本的静态文件才用长缓存。这些头部写在新文件里,只覆盖新名字。写完用一次请求查看响应头,确认缓存策略确实来自你写的那一块。

做一个专门的负面试验:请求 xin.example 上一个只在甲的树里才存在的路径。期望是新应用的 404 或你自己的错误页,而不是甲的文件内容,也不是“青”。再请求甲上一个只在新应用里才存在的路径,期望仍是甲原来的 404,而不是新句子。这两下证明树没有交叉。若交叉了,先看根、别名和挂载,而不是看应用代码。代码再正确,入口提前把文件送出去,代码也不会执行。

空的网站根目录不要放在一个会被你事后拿来存放导出文件或私钥的父目录上。根在父、文件在子,网址有时仍能通过相对路径撞到不该公开的兄弟文件。让根指向一个里面只有公开静态文件的叶子目录。私钥、环境文件、数据库导出和停用配置都放在这个叶子之外。用一个从外部发起的请求去要这些文件的名字,期望 404,而且 404 的正文里不要把真实的文件系统路径回显出来。回显路径会帮别人画出你的目录结构。

镜像数据卷和网络使用不会撞车的名字

编排里的项目名、数据卷名和网络名都带上 xin.example,不用 db、web、app 这种短名字。短名字在宿主机上会和已有项目相撞。相撞的结果不是启动失败这么幸运,而可能是你的应用连上了别人的卷,或者你的清理删掉了别人的网络。启动之后列出卷和网络,确认新应用只挂在你刚创建的那一个网络上,数据库也只在这个网络里,并且这个网络上没有甲或乙的容器。

不要把宿主机的容器控制套接字挂进新应用。那个套接字相当于把整台宿主机的容器管理权交给了新进程。新应用没有理由需要它。也不要把入口的整个配置目录挂进容器里当网站根或当工作目录。那样做通常是为了图一个路径方便,结果是应用进程能读取甚至修改甲和乙的站点文件。挂载表应当短到可以在核对时全文读完:环境文件只读、数据卷一处、也许还有一份只读的静态构建结果。出现不认识的挂载,先卸掉再上线。

镜像使用明确的版本标记,不用“最新”这种会在别人拉取时变内容的标记。上线窗口里,你不希望同一次重试拿到另一个版本的程序。版本标记写进清单。构建发生在单独的目录,目录里只有这个应用的源码和构建文件,不要把家目录或整台机器的配置目录当成构建上下文。构建上下文会被送去构建程序,范围过大时,甲的文件可能进入镜像层。镜像层一旦进入,以后的清理和权限都更难推理。构建结束用镜像的标识确认你运行的就是刚刚构建的那一个。

环境文件不进入镜像,不进入构建上下文,不进入会被提交到公开仓库的目录。它只在运行时以只读方式挂进容器。站点文件里不写环境文件的内容。命令行历史里也不要出现把口令直接传给进程的那种写法,因为同一台机器上的其他账户有时能看见进程列表。需要传递时,用文件或由服务管理器注入,并确认进程列表里只看得到文件路径,看不到值。

清理镜像、停止看起来闲置的容器、清空构建缓存,都不要放在这次上线里。那些对象可能仍被甲的发布流程使用。即使真的没用,也是另一次有清单的清理:列出将被删除的名字,对照仍在运行的单元,确认没有 xin.example 以外的东西,然后再删。清理的影响面常常比加一个站点更大,而且不会表现为你的新句子失败,而会表现为几天后甲无法重新构建。那种延迟让人很难把原因指回你今天的“顺手”。

网络上的出口也想一下。新应用若只需要让入口连进来、自己去连数据库,它不需要被放到一个能访问甲的内部管理网络的位置。多加入一个网络,就是多一组它能主动打开的地址。保持只有一个应用网络。你若以后确实要让它访问一个外部依赖,再单独加,并在记录里写明依赖的名字,而不是先加入一个“什么都能到”的网络备用。备用的连通性是没有边界的权限。

慢请求和失败页不要占用邻居的容量

80 和 443 的工人和连接数是甲、乙和你一起用的。新应用若响应很慢,或不接受连接却让入口一直等,工人会被 xin.example 的请求占住。占到一定程度,甲的短请求也要排队。页面上仍可能最终返回“青”,只是变慢,你的核对若只看状态码,就看不见。所以在新文件里为这个站点写下连接超时、读取超时和发送超时,数字短到能反映“应用应当很快说出那句新话”。不要为了掩盖上游问题把超时放到极大,更不要把极大的超时写进全局。

可以在新的 server 块里限制同时转发到 18081 的连接数。超过的请求在入口快速失败,而不是无限堆积。快速失败只影响 xin.example。这是一种对邻居的礼貌,也是一种对你自己的保护:堆积的请求会在应用恢复的那一瞬间一起压上去,把刚恢复的进程再次压垮。限制的数字按你测量的正常并发来选,写进清单。没有测量时,先选一个保守的小数字,第一小时如果经常触顶,再单独调整这一行。调整仍走测试和重载。

失败页不要指向甲或乙。有的配置在 502 时内部跳到一个“统一错误页”,而那个错误页是甲的路径。于是新应用一故障,访客就看见“青”,你还以为转发串了。更糟的是访客以为自己在使用甲。错误页使用入口自带的简短响应,或使用你放在 xin.example 空目录里的一页静态说明。那一页上不要链接到邻居,以免在故障时把流量送到别人那里。先在练习里停掉 18081,请求一次,确认你看见的是自己的失败页,同时甲仍是“青”。

请求体大小、请求速率也可以只在新站点上限制。一个还没有被证实稳定的新应用,不应当允许单个客户端以任意速度上传,把共享磁盘写满。限制写在新文件里。触发限制时,访问日志里应当有相应的状态码,你能据此判断是限制生效了,还是应用自己拒绝了。这两种拒绝的处理不同:前者调整或接受限制,后者看应用日志。不要看到拒绝就先把限制从全局删掉。全局里也许根本没有你的限制,删掉的是乙需要的那一条。

入口与上游之间的失败次数也可以让这一条上游暂时停止尝试,以便工人尽快返回错误,而不是对一个已经拒绝连接的端口反复握手。这个设置写在只被新站点使用的上游定义里,不要写在甲也在用的上游上。甲的上游若被你加上激进的失败阈值,甲会在几次偶然超时之后被整段停用。那是你为了保护邻居而伤害了邻居。专用上游定义虽然多写几行,但这几行把“停止尝试”的范围锁死在 18081。

做一次很小的压力区分。在练习环境里让新应用故意睡眠数秒,同时连续请求甲。甲的状态码和大致耗时应当仍接近底片。若甲明显跟着变慢,说明工人或连接被你共享的方式耗尽了,回到这一节的限制上收紧,而不是去给甲的程序加速。给甲加速不是你的变更。正式环境里不要为了做这个实验而压新站点,练习环境就是用来把睡眠和限制看清楚的。

先弄清还有谁会改写配置目录

清单上那一格“谁会写入配置目录”,在重载前要有答案。答案来自询问机器的管理人、查看定时任务、以及阅读目录里文件的注释,而不是来自猜测。常见的写作者有三类。第一类是人手,大家约定只有在这个窗口才编辑。第二类是一个会重新生成整目录的发布器,它的真理在另一份模板或数据库里,目录只是生成结果。第三类是证书续签之外的配置管理,它会按固定周期把目录改回它认为正确的样子。

若是第二类,你手工放入的 xin.example.conf 会在下一次生成时消失,或被标成多余而删掉。新句子随即消失,若这个名字仍解析到本机,请求会落到默认站点,访客重新看见“青”。你若没做变更前的记录,会以为是入口坏了。对这种目录,正确的做法是把新站点写进那个发布器的源,让它生成出与你手写内容等价的文件,而不是和它对着干。写进源的那一次,仍要保证生成结果里只有你的站点块被新增,甲和乙的生成结果与底片一致。生成器的差异往往比手写更大,更要导出全文来比。

若是第三类,先找到它的周期。把你的变更放在它刚运行完之后,并在它下一次运行之后再核对一次。不要关掉别人的定时任务来保证你的文件留下来。关掉任务会让甲的证书或甲的站点一起停止被照管。你的文件若反复被改回去,说明真理不在目录里,回到上一类的做法,去改源。与配置管理对抗,通常以你的文件失败告终,有时还会让对方的下一次运行报错并跳过全部站点。那种跳过会伤害甲和乙。

人手共享的目录,在你编辑期间通知同一批人:这一小时不要重载、不要覆盖 conf 目录。通知里写明你只增加哪一个文件名。别人若同时修改甲的文件,你的底片就会失效。发现同时修改时,停下来,重新对甲做底片,再继续你的单文件变更。不要试图把两个人的编辑揉进同一次重载。揉在一起的重载无法退回一半。

有的发布器会在生成时执行一次入口重载。你要知道这件事,避免和它的重载叠在同一分钟。两个重载本来应当都能平滑进行,但若它生成的是半成品而你又放进了文件,中间状态会变复杂。错开几分钟,并在双方都完成后做一轮完整核对。核对仍是那张表,不因为“发布器说成功了”而省略“青”和“墨”。

把答案写成一句放进记录的话。例如:目录由人手维护,本次窗口内不再有别人重载。或:目录由某发布器生成,新站点已进入它的源,生成差异只有 xin.example。没有这句,以后的人会在文件消失时从应用代码里找原因,浪费一个晚上。配置消失是入口层的故障,用导出和目录列表就能确认,不需要先怀疑数据库。

在练习环境里把青墨和新句子看明白

正式改动之前,在一套没有真实访客的环境里把三个回答先搭出来。一个进程只返回“青”,一个只返回“墨”,一个只返回“新窗已开,旧灯未动”。前两个交给练习用的入口,分别绑定 jia.example 和 yi.example。第三个只听练习机回环上的一个高端口。练习机的 80 和 443 可以由这个练习入口独占,因为它上面没有真正的邻居。你要模拟的是流程,不是去制造一次真实的全机风险。

先记录练习底片:两个名字的状态码和正文,高端口尚未被入口引用。然后只增加一份 xin.example 的站点文件,上游指向那个高端口。测试,平滑重载。三个请求应当分别返回青、墨和新句子。再请求一个不存在的名字,应当仍返回你指定的默认内容,而不是新句子。这一步把“加文件”和“抢默认”分成两个可见的结果。

然后故意把 server_name 写错一个字母,重载,用正确的 xin.example 去请求。你应当看见默认内容,而不是新句子。应用的日志里不应当出现这次请求,因为它没被转发。再把名字改回,重载,新句子回来,青和墨在整个过程中不变。这个来回比阅读匹配规则更有用。你亲眼看见写错名字时入口不会报“没有这个站点”,它只会安静地交给默认站点。正式环境里你就不会再把 200 当成成功。

下一步让新应用停掉,但保留站点文件并重载。xin.example 应当变成你设计的失败页或 502,青和墨仍在。若青或墨也变了,说明失败页或上游定义共用了邻居的块,练习判为未完成,回到只属于自己的那一节去改。然后再做反向的一步:站点文件移走,应用仍在回环上提供新句子。此时通过入口访问 xin.example 应当回到默认或失败,直接访问高端口仍能看到新句子。这一步证明退回文件不会顺带杀死进程,也证明进程还在不等于名字还在。

最后把容器的错误发布练一次。故意把高端口发布到所有网卡,从另一台机器确认能直接拿到新句子,再改回只发布在回环,确认另一台机器失败而练习入口仍能拿到新句子。这个对比让“发布表上的地址”变成你认得的信号。正式环境里你就不会只看容器是否在运行。

练习的最后一页是空白核对表。你已经用它填过一轮,知道哪些格子会使人犹豫。把犹豫的格子改得更具体,例如写成“不存在的主机名返回的标记是什么”,而不是“默认站点正常吗”。表格若超过一页,就删掉不能在练习里实际执行的句子。超过一页的表,在正式窗口里会被跳过。练习环境可以反复推倒。推倒时同样只移走新文件,不要养成“重装整套练习机”的习惯。习惯会带到正式环境里去。

练习不需要公网域名。用客户端的主机名映射或显式的解析参数,把三个名字指到练习入口即可。不要把练习名字写进正式的域名解析。也别把练习入口暂时绑到正式机器的 80 上“更真实一点”。那会直接威胁甲和乙。真实感来自步骤完整,不来自借用正式的门。

解析记录放到本地证明完成之后

站点文件、健康检查和本机显式主机名都已经通过之后,才去改域名解析。解析是让别人能够走到你这扇门的最后一步,不是排错工具。提前把 xin.example 指到机器上,访客和各种扫描就会在你还没准备好时进来。若这个名字原先落到默认站点,你等于提前把他们从旧页面上挪走,而新页面可能还是 502。先在服务器上用显式方式把主机名送到入口,证明站点块本身正确,再让公共解析跟着走。

只增加或只修改 xin.example 这一条记录,不改甲和乙的记录,不改区域的默认记录,不改通配。通配若已经存在并指向这台机器,你在底片里应当已经见过它的效果:未知名字会进来。不要在这次顺手删除通配。删除通配会改变所有未单独列出的名字,那是区域级的变更。你的新记录应当是一条更具体的精确名字,精确名字优先于通配,于是只有 xin.example 改变去向。改完用查询工具确认:xin.example 指向你期望的地址,jia.example 与 yi.example 的答案与底片一字不差。

解析的生效时间受缓存影响。有的递归解析会把旧答案保留一段时间。在这段时间里,一部分访客仍走旧的默认站点,一部分已经看到新句子。这不是串站,是缓存。你的核对要记录查询发生的时间和答案,而不是在缓存消失前宣布失败并去改站点文件。站点文件已经用显式主机名证明过了。解析层的不一致,就停在解析层等待或刷新,不要回头重载入口来“推动域名”。入口不负责递归解析的缓存。

若你只能改到一处你并不完全理解的解析面板,先在练习域名上做同样的点击,确认你知道哪一个按钮会改到通配、哪一个只改一条。正式面板上不探索。探索性的点击经常创造出第二套记录,两套记录互相覆盖,现象是间歇性地看到“青”和新句子。间歇性最容易诱使人去重启整机。重启不会修好两条互相矛盾的解析。

新记录指向的应当是入口已经在听的那台机器的既有地址,而不是一个新申请的、还没有入口监听的地址。这篇的前提是旁边已有网站。你不需要为了新站点申请新的公网入口地址,除非管理人有别的架构要求。申请新地址会带来新的防火墙、新的默认站点和新的证书问题,范围立刻变大。保持地址不变,只增加名字,是和“保持 80 与 443 的主人不变”相配的做法。

解析改完,从一台不在这台服务器上的机器请求新句子,并再请求一次甲和乙。这一轮是公网路径的第一轮完整验收。它不替代前面的本机验收。两轮都留下原始状态码和标记。若公网的甲失败而本机的甲正常,去看解析有没有被你误改,而不是去看新应用的数据库。数据库不会只在公网上弄坏甲。

第一小时看的是速率和重复抽样

宣布可用之后的一小时,不要继续编辑。编辑会毁掉你观察基线的机会。这一小时里只看几样会累积的量:新错误日志每分钟增加多少行,新访问日志里 502 和 499 的比例,应用进程有没有被杀死后重启,数据卷的可用空间下降得快不快,入口的主进程号有没有变。这些量用两次或三次采样比较,不用感觉。第一次在开始时,第二次在十多分钟后,第三次在一小时附近。

邻居在这一小时里至少再抽两轮。轮次之间若你什么都没改,而甲的标记变了,就要认真对待,因为它不是你的编辑节奏能解释的。先看导出配置里甲的文件有没有在这小时被别人或被定时任务改写,再看证书到期日。不要先重启。重启会把一小时里积累的线索打断。若只是 xin.example 的错误在增加,甲仍稳定,就把新站点按退回标准撤下或让应用停在失败状态,修完再走一遍放入文件的步骤。不要在错误增加时去放宽全局限制,试图把症状压下去。

内存若持续上升、请求量却不高,当成一个还没解决的问题,不要留给夜里。你的上限应当在它伤害甲之前杀死或拦住新应用。若上限没有生效,说明它没写进运行中的对象,这是上线未完成,而不是一个可以观察过夜的小瑕疵。磁盘同样处理。可用空间的下降速度若快到会在一天内危及甲的写入,就停新应用、保留现场、看是哪一个文件在长。常见的是没有滚动的日志,或一个失败任务在反复写临时文件。

这一小时里拒绝两类“顺手”。一类是看到未使用镜像就清理,一类是看到甲的某个警告就顺便修。警告可能已经存在很多天,写在你的底片之外。你今天修它,就把自己的变更和甲的变更绑在了一起。把警告抄进记录的旁注,交给甲的主人。你的记录正文只保留与 xin.example 相关的采样数字。

若第一小时里触发了退回,退回完成后再开始计时,不要把失败前的采样和退回后的采样拼成一条“总体还好”的曲线。两条曲线分开写。总体还好是一种无法复核的摘要。你需要的是:在站点文件存在的时段里,邻居的每一轮抽样都与底片一致。做不到,就还没有到可以离开机器的时候。

离开之前看一眼续签和发布器的下一次运行时间。若它会在你离开后的几分钟内运行,就等到它运行完再抽一轮,然后再离开。把“离开时的目录列表摘要”写进记录。夜里若文件消失,你能知道消失发生在你离开之后,范围缩小到定时任务,而不是缩小不到任何地方。

把一页记录留下来下次只填空

把今天用过的格子收成一页,不超过一页。超过的部分在正式窗口里会被跳过,练习时已经验证过这一点。一页上只留会被填空的项目:时间、操作者、新名字、回环端口、站点文件路径、环境文件路径、证书路径、日志路径、数据卷名、网络名、内存上限、日志总上限、测试是否通过、重载后主进程号是否保持、邻居每一行的前后状态、新句子是否出现、不存在的主机名落到哪里、公网高端口是否拒绝、解析是否只改了一条、备份文件在哪里、演练是否看见标记、退回时要移走的文件名、配置目录的写入者是谁。

下面是一份已经填过的例子,名字仍是虚构的。时间是练习窗口的一个下午。操作者只写角色,不写真实的人。新名字 xin.example,端口 18081,只绑在回环。文件是入口加载目录中的 xin.example.conf。甲在前后都是 200 和“青”,不存在的路径都是 404。乙在前后都是 200 和“墨”,跳转仍指向乙。不存在的主机名前后都落到甲,正文是“青”,说明默认站点没被接走。xin.example 在变更前因为通配也落到甲,变更后是新句子。这个“变更前不是空白”被明确写出来了。公网访问 18081 失败。解析只增加了一条精确记录,甲和乙的查询输出与底片相同。备份是一份新库导出,练习恢复时看见了新句子里的标记。退回动作是移走 xin.example.conf 后测试并重载。写入者是人手,窗口内没有定时生成。

一页放在管理目录,不放在网站根,不放在会被发布器覆盖的配置目录里。站点文件头部的三行注释只指向这页的位置,不要把整页复制进配置。复制进配置的表会过时,过时的表比没有表更糟,因为它让人以为端口还是旧的。配置里只留生效的指令和那三行如何退回的话。

下次上线时先复印空白的一页,再开始看监听者。不要从记忆里重做一份新表。记忆会漏掉“变更前的新名字”和“不存在主机名”这两行,而这两行正是默认站点事故的证据。填空时若某一格不适用,写上不适用的原因,不要留空。留空看起来像忘记测。不适用则说明你判断过。

整页的完成定义可以收成一句:除了 xin.example 从旧回答变成了新句子,以及机器上多出了你记录过的那一组属于它的文件和单元,底片的每一行都没有变。这句话里的“记录过”很重要。多出来的东西若没被记录,就不是完成,是还有你没承认的变更。承认的方式就是把它写进这一页,或把它撤掉,直到多出来的集合和这一页一致。

这一页也不替代邻居主人的通知。若这台机器有明确的维护约定,就按约定提前说明:你会在哪个小时增加一个名字,不会重启入口,不会改他们的文件。说明要短,短到对方能判断要不要在那个小时避开自己的发布。你不需要把新应用的功能讲给他们听。他们需要的是影响面。影响面应当是:没有影响,若有,也只发生在 xin.example,并且你可以移走一个文件退回。

做完之后把练习环境里那三个小进程停掉,留下空白表和这篇用过的站点文件样本即可。样本放在不会被正式入口加载的地方。下一次可以用样本对照,但不能把样本原样拷进正式目录而不改名字。原样拷进去,练习名字就会出现在正式入口上,成为一个你没打算公开的站点,甚至成为一个新的默认站点。样本的价值是提醒那四个问题,不是成为第五份正在服务的配置。

见字如晤

图解