广告位 · 页头(后台-模块管理-新建模块后替换此区域)

技术栈替换时如何做好DNS适配与平滑迁移?

技术栈替换远不止改代码和换依赖,真正影响线上稳定性的,是域名解析链路是否跟着新架构一起切换。旧服务的DNS记录、系统缓存、上游代理配置、容器网络里的解析规则,任何一截没有同步调整,都可能让流量打到已下线的实例,或者让新服务始终收不到请求。替换期间暴露的DNS问题,往往比代码缺陷更隐蔽,因为它们只在特定缓存过期或特定容器重启后才出现。

技术栈替换时如何做好DNS适配与平滑迁移?

先明确一个前提:DNS适配不是简单地改一条记录,而是要围绕解析链路、缓存策略、服务发现和回滚能力做整体设计。下面把替换过程中需要关注的几个关键环节拆开说明。

一、先梳理替换范围:哪些DNS配置会跟着变

技术栈替换可能涉及应用框架、运行平台、中间件、部署方式等多个维度,DNS相关配置也会分散在多个位置。常见的变化点包括:应用代码里写死的外部API域名、反向代理或网关中的上游地址、容器编排系统自动生成的Service域名、本地/etc/resolv.conf以及各环境的hosts文件。如果替换前没有把这些点列出来,很容易出现新代码连旧地址或旧配置指新域名的情况。

以从虚拟机迁移到Kubernetes为例,原来的应用可能通过内部IP或固定主机名访问数据库;迁移后数据库变成了StatefulSet或托管RDS,访问入口要从IP改为Service域名或云数据库终端节点。这个过程中不仅要改代码里的连接串,还要保证CoreDNS或kube-proxy能正确解析新的名称。尤其在混合部署阶段,旧虚拟机和新容器同时存在,DNS必须能同时覆盖两种网络。

下面是一段基础CoreDNS配置,用来处理集群内默认域名的解析,并在未命中时转发到上游。

.:53 {
    errors
    health
    kubernetes cluster.local in-addr.arpa ip6.arpa {
       pods insecure
       fallthrough in-addr.arpa ip6.arpa
    }
    forward . /etc/resolv.conf
    cache 30
    loop
    reload
    loadbalance
}

这段配置里cache 30表示缓存30秒,替换期间如果需要快速让新记录生效,可以临时把缓存时间调低,或者通过reload触发配置重载。当然,缓存不是唯一的坑,TTL和系统级缓存同样关键,下一节会展开。

二、解析层级与TTL:让旧记录优雅失效

一条DNS记录从权威服务器到应用真正使用,中间会经过操作系统缓存、本地DNS服务器、容器运行时等多个层级。不同层级对TTL的处理并不一致,有的组件会强行覆盖TTL,有的会在TTL到期后再延迟刷新。替换外部域名时,如果只改了权威解析,却没有考虑中间缓存,就可能出现一部分节点已经拿到新地址,另一部分还在用旧地址。

推荐的策略是在切换前先把TTL降到较低值,比如60秒或30秒,观察一段时间确保解析稳定后,再执行正式切换。切换完成后,如果旧记录已经被缓存,TTL短,最坏情况也只会持续几十秒。Linux上可以使用dig +ttlid或dig +short查看当前解析结果,也可以用systemd-resolve --flush-caches强制刷新本地systemd-resolved缓存。

验证DNS时,最好同时从应用容器内部和宿主机分别执行查询,因为容器可能使用不同的DNS服务器。例如在Kubernetes Pod内执行:

kubectl exec -it order-api-7b5c8d6f4-8x2kp -- dig +short order-db.prod.svc.cluster.local

如果Pod内没有dig,可以用nslookup或getent hosts替代。重点是对比容器内解析结果与宿主机解析结果是否一致,避免只验证了一端就判断切换成功。

三、服务发现与DNS的衔接:CoreDNS和Kubernetes Service

在Kubernetes里,Service对象不仅是负载均衡入口,也直接决定了DNS名称的生成规则。一个命名空间为prod、名字为order-api的Service,会生成order-api.prod.svc.cluster.local这样一条内部域名。当Pod重建、IP变化时,CoreDNS会根据Endpoints实时更新解析结果,应用无需修改连接地址。

技术栈替换时,如果服务从注册中心模式切换到Kubernetes原生Service,或者反过来,就需要特别注意名称解析的一致性。注册中心通常使用服务名直接做负载均衡,而Kubernetes Service的名称是有命名空间隔离的。双栈运行期间,建议保留两套名称的映射关系,避免旧客户端按注册中心短名调用时找不到地址。下面是一个基础Service定义:

apiVersion: v1
kind: Service
metadata:
  name: order-api
  namespace: prod
spec:
  selector:
    app: order-api
  ports:
    - port: 80
      targetPort: 8080
  type: ClusterIP

如果新的部署还在用Nginx做反向代理,上游地址可以指向Service域名,而不是直接写Pod IP。这样Pod扩缩容、滚动更新时,Nginx不用频繁修改配置。示例如下:

upstream order_backend {
    server order-api.prod.svc.cluster.local:80;
    keepalive 32;
}
server {
    listen 80;
    location /api/orders {
        proxy_pass http://order_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

这里proxy_pass后面接的是上游组名,配合upstream里的server指令,Nginx会基于DNS解析结果建立连接。注意Nginx默认在启动时解析一次域名,如果Service对应的ClusterIP发生变化,可能需要配置resolve或使用动态DNS模块。更稳妥的做法是让Nginx也跑在集群内部,通过CoreDNS实时解析。

四、灰度切换与回滚:用权重和解析策略控风险

替换技术栈时,不建议一步把全部流量切到新服务。更安全的做法是让新旧服务同时在线,通过权重或流量切分逐步放量。在DNS层面,可以准备两套域名,例如old-order.prod.svc.cluster.local和new-order.prod.svc.cluster.local,然后在Nginx上游里配置权重。

示例配置把90%流量交给旧服务,10%流量导给新服务,同时设置了健康检查参数:

upstream order_backend {
    server old-order.prod.svc.cluster.local:80 weight=90 max_fails=2 fail_timeout=30s;
    server new-order.prod.svc.cluster.local:80 weight=10 max_fails=2 fail_timeout=30s;
    keepalive 32;
}

观察新服务的错误率、延迟和资源使用后,可以逐步调高权重。若出现异常,第一时间把新服务的weight改为0,流量就会全部回到旧服务。这里要注意,DNS解析到的旧服务地址必须仍然有效,否则回滚只是把流量指向了一个同样不可用的地址。

除了Nginx权重,还可以结合Ingress或服务网格做更细粒度的切分。如果使用CoreDNS的template插件,甚至可以自定义解析规则,根据请求来源返回不同IP。但这类配置调试成本较高,替换期间尽量保持简单直接,能用上游权重解决就不要在DNS层做复杂策略。

切换完成后,不要立刻删除旧域名或旧记录。至少保留一到两个发布周期,确认没有延迟调用或定时任务还在使用旧地址后,再逐步下线。同时记录所有变更,包括TTL、上游配置、回滚操作和验证命令,方便后续复盘。

广告位 · 文章底部(后台-模块管理替换此区域)