资讯动态

Redis内网域名解析实战:用DNS服务告别IP硬编码

发布时间:2026/10/28 3:20:41 来源:尧图企业网站定制
上周半夜收到一条消息对方说他们把那套 Redis 从旧机房整体挪到了新机房IP 段全换了结果第二天早上十七个业务服务的配置文件挨个改改到第八个的时候手抖改错了一个端口号线上报警响了四十分钟。这事听起来挺蠢但在 IP 直连的场景里几乎是必然会发生的只要地址是硬编码的任何一次搬迁、扩容、故障切换都会变成一次跨团队的配置同步运动。这篇东西讲的就是怎么把这件事一次性解决掉——在内网搭一套自己的 DNS 服务给 Redis 配上自定义域名让业务侧永远只认redis-01.cache.lab.local这种名字机房搬迁、主从切换、IP 调整全部在 DNS 层消化掉。核心关键词是 DNS 服务、自定义域名、Redis、内网域名解析这几样东西凑在一起看起来简单真做起来坑不少。内容适合两类人看一类是手里管着几套中间件、被 IP 变更折腾过的运维或后端另一类是刚接手一个“配置文件里全是 10.x.x.x”的老项目、想理清楚这块该怎么改的开发者。不需要你对 BIND 有多熟但至少要能看懂 Linux 的网络配置和 Redis 的基本连接参数。下面从为什么要做、怎么选型、怎么落地、怎么排错一路讲下去中间会穿插一些我自己踩过的坑能让你少走点弯路。1. 给Redis配域名的真实收益与方案取舍1.1 硬编码IP的隐性成本比想象中高很多人觉得 IP 直连没什么问题“反正内网地址又不会变”。但只要系统活过一年你就会发现它一定会变机房搬迁、网段规划调整、云上可用区迁移、主从角色互换、容器化改造后 Pod 网段重排任何一件事都会让那串数字失效。而 IP 一旦写进配置文件它就不再是一个技术细节而是一份散落在 N 个仓库、M 份配置中心条目、K 个脚本里的隐式契约。这份契约的维护成本是隐性的但很贵。改一次 IP你要找出所有引用点要协调各个业务团队的发版窗口要评估哪些服务是灰度发、哪些是全量发还要防止某个边缘服务漏改导致半夜报警。更麻烦的是故障切换场景主库挂了要切到从库如果客户端写死的是主库 IP切换就意味着一次配置变更加一次重启而重启本身又可能带来雪崩。用域名的话改一条 A 记录TTL 到期后新连接自动落到新地址业务代码一行不动。还有一个容易被忽略的点可读性。10.20.30.21:6379和order-cache-01.cache.lab.local:6379后者一眼就能看出这是订单业务的缓存主节点前者要去翻资产表。当你在凌晨三点看日志排障的时候这个差别很要命。1.2 四条可选路线摆在一起比一比给 Redis 配域名不是只有一条路实际能落地的方案大概有四种各自适用场景差别很大选错了后面会很别扭。方案实现方式变更生效速度运维成本适用规模本地 hosts 文件每台机器维护/etc/hosts极慢需逐台改机器越多越崩溃单机调试、临时验证内网权威 DNS自建 BIND/dnsmasq/CoreDNS快改记录即可一次性投入长期低十台以上机器、多团队配置中心 客户端 SDKNacos/Apollo 下发地址快但依赖 SDK中需改造业务已经统一配置中心的团队服务注册发现Consul/Etcd 客户端解析最快秒级高组件多大规模微服务、动态扩缩容hosts方案我建议只在两种情况下用一是在正式上 DNS 之前做一次小范围验证确认解析链路没问题二是单机开发环境图个方便。千万别在超过五台机器的环境里用 hosts 管 Redis 地址那玩意儿改起来比改配置文件还痛苦而且极容易出现“某台机器上还留着一条旧记录”的幽灵问题——因为hosts文件的优先级高于 DNS残留记录会静默覆盖掉正确的解析结果排查起来非常费劲。配置中心方案的好处是能做灰度、能做版本回滚、能带元数据但缺点是业务代码得引入对应 SDK老项目改造成本不低。服务注册发现最适合容器化、实例频繁上下线的场景但如果你的 Redis 是几台固定的物理机或虚机上 Consul 属于杀鸡用牛刀。1.3 为什么最后选了内网权威DNS我自己的判断标准很简单如果地址变更的频率低于每周一次但影响面涉及多个团队那 DNS 就是性价比最高的解。它不侵入业务代码所有语言所有框架天然支持变更动作收敛到一个地方出问题也能用dig这种通用工具排查。具体到 Redis 这个场景内网权威 DNS 还有一个额外好处它可以和 Redis 的高可用组件配合。比如你用 Sentinel 做故障转移客户端连的是sentinel-01.cache.lab.local:26379这种域名Sentinel 返回的主节点地址也可以通过 Redis 自身的replica-announce-ip参数改造成域名整条链路里就不再有裸 IP 了。这部分后面第 4 章会详细展开是很多人做完 DNS 之后发现“怎么还是 IP”的关键所在。需要说清楚的是这套方案不适合什么场景如果你的 Redis 实例是按小时级别动态创建销毁的那 DNS 的记录管理会成为负担这时候应该用服务发现或者云厂商的托管方案如果你的团队只有两个人、三台机器那老老实实写 IP 加个注释更省事别为了架构而架构。2. 整体架构与几个必须先定下来的决策2.1 组件清单和一次连接请求的完整路径整套东西其实就三个角色DNS 服务端负责应答、Redis 服务端被解析的目标、客户端发起连接的一方。但要把链路走通中间还牵扯到操作系统的名称解析栈这一段经常被忽略也是大部分“DNS 明明配了却解析不了”的根源。客户端发起redis-01.cache.lab.local:6379连接时实际发生的过程是这样的应用程序调用getaddrinfo()glibc 根据/etc/nsswitch.conf里的hosts:行决定查询顺序通常是files dns也就是先查/etc/hosts没有再走 DNS。走 DNS 时会读取/etc/resolv.conf里的nameserver列表和search域向第一个 nameserver 发查询超时默认 5 秒后切下一个。拿到 A 记录后再发起 TCP 连接。这条链路上任何一环出问题表现都是“连不上”但原因可能完全不同。所以我一般在验证的时候不会只用ping而是用getent hosts 域名因为它走的就是应用程序实际使用的那套 NSS 路径能真实反映应用会拿到什么结果。dig只验证 DNS 服务器本身答得对不对验证不了客户端侧的解析栈。至于部署形态最小可用版本是一台 DNS 服务器 若干 Redis 节点 N 台客户端。但一台 DNS 就是单点所以生产上至少要两台主备或者双主都行。这个后面第 6 章会讲。2.2 域名规划后缀、层级和命名的讲究域名规划这事看着随意其实埋着雷。第一件事是后缀选什么。绝对不要用公网真实存在的域名也不要用.local——.local是 mDNS多播 DNS的保留后缀在 macOS 和一些启用了 avahi/nss-mdns 的 Linux 上会被本地多播解析器接管导致解析行为诡异。我吃过这个亏在一台开发机上dig能查到但应用连不上查了半天发现是 mDNS 抢在了前面。推荐用.internal、.lan、.intra这类不会被公网解析的后缀或者用公司自有域名下的一个子域比如cache.lab.local、redis.prod.internal。前者简单直接后者便于分层管理。第二件事是层级设计。建议按“业务 环境 角色 序号”来组织比如order-cache-01.prod.internal—— 订单业务生产环境缓存主节点order-cache-02.prod.internal—— 订单业务生产环境缓存从节点order-cache.prod.internal—— 订单缓存的统一入口指向 VIP 或哨兵order-cache-01.test.internal—— 测试环境对应节点这么设计的好处是看到域名就知道它在哪、干什么的而且可以用泛解析或者通配符记录来减少维护量。但通配符记录要慎用*.prod.internal全部指向同一个 IP 这种做法在排障时会掩盖真实问题你会分不清到底是这个服务不存在还是它真的指向那台机器。第三件事是命名里别带会变的信息。我见过有人用redis-10-20-30-21.prod.internal这种把 IP 编进名字的做法初衷是方便定位结果机器一换 IP 名字就对不上了反而制造混乱。名字应该描述角色不描述位置。2.3 TTL、连接池和故障切换之间的三角关系这一节是整篇文章里最容易被忽略、但实际影响最大的部分。很多人配完 DNS 之后发现“切换了记录业务还是连的旧地址”就是因为没搞懂 TTL 和连接池的关系。关键认知DNS 的 TTL 只影响“新建连接”的解析不影响已经建立的 TCP 长连接。Redis 客户端普遍使用连接池一条连接建好之后可能存活几十分钟甚至几小时期间无论 DNS 记录怎么变这条连接都不会重新解析也不会自动断开。所以 TTL 设置要遵循一个原则TTL 要略小于客户端的连接池最大存活时间max lifetime。比如 TTL 设 60 秒连接池的 max-lifetime 设 300 秒那么一次记录变更最坏情况下 5 分钟内全部生效反过来如果 TTL 设 300 秒、连接池不设上限很多客户端默认就是无限那切换可能要等到进程重启才生效DNS 就白做了。具体数值建议TTL 设 30~60 秒连接池 max-lifetime 设 5~10 分钟连接池的 idle-timeout 设 30~60 秒。TTL 不要设太短比如 5 秒那会导致 DNS 查询量暴增而且很多操作系统的解析器本身有缓存短 TTL 没什么意义。还有一个坑JVM 有自己的 DNS 缓存绕过了系统 TTL。Java 的InetAddress缓存由networkaddress.cache.ttl控制不同 JDK 版本和是否有 SecurityManager 会带来不同默认值有些环境下默认是永久缓存。所以 Java 系的应用必须显式设置启动参数加-Dnetworkaddress.cache.ttl60 -Dnetworkaddress.cache.negative.ttl5或者在应用启动最早阶段调用java.security.Security.setProperty(networkaddress.cache.ttl, 60)。这一条不做前面所有 TTL 设计都是空的。3. 从零搭一套内网DNS服务3.1 BIND9的最小可用配置以最常见的 BIND9 为例Debian/Ubuntu 系apt install bind9 bind9-utils dnsutilsRHEL 系yum install bind bind-utils。装完之后不要急着启动先把配置文件改对。/etc/bind/named.conf.options里的核心是这几项options { directory /var/cache/bind; listen-on port 53 { 127.0.0.1; 10.20.30.11; }; listen-on-v6 { none; }; allow-query { 10.20.30.0/24; 127.0.0.1; }; recursion yes; allow-recursion { 10.20.30.0/24; 127.0.0.1; }; dnssec-validation no; forwarders { 10.20.30.1; }; };逐项解释一下每一项我都踩过对应的坑。listen-on一定要显式写内网 IP 和 127.0.0.1不要图省事写any。默认配置里 BIND 只监听 127.0.0.1你装完发现其他机器查不到八成就是这个原因。allow-query限制查询来源网段防止被内网其他不该用的人蹭也降低被当成 DNS 放大器的风险。如果只做权威解析recursion no;更安全但这里有个大坑如果你打算让客户端把/etc/resolv.conf直接指向这台机器那它必须能递归解析公网域名否则客户端配上去之后所有外网域名全挂。我见过不止一次“加了台内网 DNS结果全公司上不了外网”的事故就是recursion no加forwarders空导致的。我的建议是这台机器同时承担递归和权威两个角色recursion yes但用allow-recursion严格限制来源。forwarders指向上游可用 DNS如果内网完全隔离就留空。dnssec-validation no;这条特别说一下。BIND 默认开启 DNSSEC 验证在纯内网、无法访问根服务器的环境里会导致启动慢、查询超时甚至SERVFAIL。内网权威区自己做 DNSSEC 的团队很少直接关掉最省心。3.2 区域文件怎么写从SOA到A记录先在/etc/bind/named.conf.local里声明区域zone cache.lab.local { type master; file /etc/bind/db.cache.lab.local; allow-transfer { 10.20.30.12; }; };allow-transfer是给从库用的如果暂时单机可以先去掉但留着也无妨至少限制到指定 IP。然后是区域文件/etc/bind/db.cache.lab.local$TTL 60 $ORIGIN cache.lab.local. IN SOA ns1.cache.lab.local. hostmaster.cache.lab.local. ( 2024052001 ; serial 3600 ; refresh 600 ; retry 604800 ; expire 60 ; minimum ) IN NS ns1.cache.lab.local. ns1 IN A 10.20.30.11 redis-01 IN A 10.20.30.21 redis-02 IN A 10.20.30.22 redis-03 IN A 10.20.30.23 sentinel-01 IN A 10.20.30.31 sentinel-02 IN A 10.20.30.32 sentinel-03 IN A 10.20.30.33几个要点值得展开。serial 号的格式是YYYYMMDDnn每次改区域文件必须递增否则从库不会同步。我习惯写成日期加两位序号比如2024052001、2024052002。刷新前忘了改 serial 是最常见的人为失误改完记录rndc reload之后发现从库没更新查半天就是这个原因。有个小技巧在部署脚本里自动根据日期生成 serial能规避掉一大半问题。SOA 里的 minimum 和 $TTL 的关系在 RFC 2308 之后SOA 的最后一个字段表示的是否定应答的缓存时间也就是 NXDOMAIN 缓存多久而不是整个区域的最小 TTL。两个都设 60语义清晰。$ORIGIN的作用是定义相对名称的基准有了它redis-01会被自动补全成redis-01.cache.lab.local.。注意末尾那个点区域文件里的完全限定名必须以点结尾漏了点会变成redis-01.cache.lab.local.cache.lab.local.这个错我年轻时犯过查了一下午。写完先做语法检查再重载别直接 reloadnamed-checkconf /etc/bind/named.conf named-checkzone cache.lab.local /etc/bind/db.cache.lab.local systemctl reload namednamed-checkzone会告诉你 serial 是否合法、记录格式有没有问题比直接 reload 后看日志快得多。3.3 轻量替代dnsmasq和CoreDNS怎么选BIND 功能全但配置繁琐。如果你的需求只是“把几个域名解析到固定 IP”dnsmasq 更合适配置文件一行搞定# /etc/dnsmasq.d/redis.conf address/redis-01.cache.lab.local/10.20.30.21 address/redis-02.cache.lab.local/10.20.30.22 local-ttl60注意address的匹配规则是域名及其所有子域address/cache.lab.local/10.20.30.21会把整个域都指向同一个 IP所以精确到主机名更安全。local-ttl控制本地应答的 TTL不设的话默认是 0也就是不缓存每次都要问量大了会有压力。还有一个更结构化的写法用 hosts 格式的文件# /etc/dnsmasq.d/redis.hosts 10.20.30.21 redis-01.cache.lab.local 10.20.30.22 redis-02.cache.lab.local然后主配置里加addn-hosts/etc/dnsmasq.d/redis.hosts。这种写法的好处是格式和/etc/hosts一致运维同学不用学新语法而且可以复用现有的资产管理脚本自动生成。如果环境是 Kubernetes那基本就是 CoreDNS 的天下了。在 CoreDNS 的 ConfigMap 里加一段cache.lab.local:53 { hosts { 10.20.30.21 redis-01.cache.lab.local 10.20.30.22 redis-02.cache.lab.local fallthrough } }或者用forward把整个域转发给你自建的 BINDcache.lab.local:53 { forward . 10.20.30.11 cache 30 }这里要提醒一句容器的 DNS 行为和宿主机不一样。Docker 默认会把容器的/etc/resolv.conf指向127.0.0.11这个内置解析器再由它转发到宿主机配置的 DNS。如果你的内网 DNS 只在宿主机/etc/resolv.conf里配了容器里可能查不到需要在 Docker daemon 配置里显式指定dns参数。K8s 里则是节点上的kubelet把/etc/resolv.conf传给 CoreDNS 作为上游如果节点用的是 systemd-resolved 的127.0.0.53CoreDNS 会转发失败因为那个地址在 Pod 网络里不可达。这种情况必须在节点上把真实 DNS 地址写进/run/systemd/resolve/resolv.conf并在 kubelet 里指定。3.4 客户端接入与解析验证的正确姿势DNS 服务起来之后客户端要做的就是把/etc/resolv.conf里加上这台服务器nameserver 10.20.30.11 nameserver 10.20.30.12 search cache.lab.local options ndots:2 timeout:2 attempts:2search域的作用是让你能直接用短名redis-01访问系统会自动补上后缀。但它也是坑的来源如果ndots设得太大默认是 1系统会先尝试把redis-01.cache.lab.local当成相对名去拼redis-01.cache.lab.local.cache.lab.local查一遍失败后再查原名。每次多一次无效查询延迟增加日志里还会出现一堆 NXDOMAIN。解决办法是把ndots调小或者在代码里写全限定名并带上结尾的点。很多人改完/etc/resolv.conf发现没生效八成是碰上了 systemd-resolved。现代发行版里/etc/resolv.conf通常是指向/run/systemd/resolve/stub-resolv.conf的软链接里面只有一行nameserver 127.0.0.53你手动改的内容重启后就被覆盖。正确的做法是改/etc/systemd/resolved.conf里的DNS字段然后systemctl restart systemd-resolved用resolvectl status确认。要临时验证也可以直接resolvectl query redis-01.cache.lab.local。验证顺序我一般是这样走的每一步都能定位到不同层次的问题# 1. DNS 服务器本身答得对不对 dig 10.20.30.11 redis-01.cache.lab.local short # 2. 客户端解析栈拿到的结果最贴近应用实际 getent hosts redis-01.cache.lab.local # 3. 端口通不通排除解析之外的问题 redis-cli -h redis-01.cache.lab.local -p 6379 ping第一步通了第二步不通说明客户端侧配置有问题resolv.conf、nsswitch、systemd-resolved前两步都通第三步不通那问题在 Redis 服务端或者网络策略上跟 DNS 没关系了。4. Redis侧的配合改造与客户端配置4.1 监听地址、保护模式与访问控制DNS 配好了不代表 Redis 就能用域名访问服务端这边也得配合。redis.conf里最关键的三个参数bind 10.20.30.21 127.0.0.1 protected-mode yes port 6379bind决定了 Redis 监听在哪些地址上。默认只监听 127.0.0.1其他机器连不上改成0.0.0.0虽然能连但等于把所有网卡都暴露出去。正确做法是显式列出内网 IP 加环回地址域名解析到哪个 IP这里就绑哪个。protected-mode是 Redis 3.2 之后引入的保护机制当它开启、且没有设置密码、且没有配置 bind 时Redis 会拒绝来自非环回地址的连接。很多人在测试环境遇到“明明 bind 了还是连不上”或者“telnet 端口通但一执行命令就断开”就是撞在这个机制上。生产环境不要关它而是配上密码或者 ACL。Redis 6 之后推荐用 ACL 替代简单的requirepassACL SETUSER app_order on 强密码串 ~order:* read write keyspace -dangerous这条命令创建了一个只能操作order:前缀键、只有读写权限、禁掉FLUSHALL、CONFIG这类危险命令的账号。配合域名使用的时候这个账号会被写进业务配置一旦域名指向的节点被误换成测试库至少 ACL 还能挡住一部分越权操作。再补一点如果 Redis 和客户端之间跨了安全组或者防火墙记得放行的是目标 IP 6379不是域名。防火墙不认域名这是个常识但每年都有人在这一步卡住反复确认 DNS 配得对不对。4.2 让Sentinel和Cluster也返回域名这是整篇文章里最“反直觉”的部分。很多人以为把客户端连接地址改成域名就完事了结果发现 Sentinel 模式下客户端还是连到了 IP 上——因为Sentinel 返回的主节点地址是由 Redis 实例自己上报的默认是 IP不是你连 Sentinel 时用的域名。原因在于 Sentinel 通过INFO replication获取从节点信息从节点上报的地址默认取本机网卡 IP。要让它上报域名需要在每个 Redis 实例的配置里加replica-announce-ip redis-02.cache.lab.local replica-announce-port 6379老版本Redis 5 之前这个参数叫slave-announce-ip升级的时候会被自动兼容但配置里最好改成新名字。设置完成后INFO replication里master_host和slaveN_ip字段就会显示域名Sentinel 下发给客户端的也是域名。这里有个副作用要提前知道本机用 127.0.0.1 连上去看INFO replication显示的也是域名有些监控脚本会因此判断异常。另外如果域名解析依赖的那台 DNS 挂了主从复制本身也会受影响所以 Redis 节点的/etc/hosts里最好也保留一份静态记录做兜底。Cluster 模式类似Redis 6.2 起提供了相关参数cluster-announce-hostname redis-03.cache.lab.local cluster-preferred-endpoint-type hostnamecluster-preferred-endpoint-type决定CLUSTER SLOTS、CLUSTER SHARDS返回的是 IP 还是主机名可选值有ip、hostname、unknown-endpoint。不同版本支持程度有差异上线前务必用redis-cli cluster slots实际看一眼返回值别只看文档。我遇到过客户端库版本较老、不认 hostname 类型的 endpoint导致 MOVED 重定向后连不上最后只能在客户端侧做地址映射。4.3 主流客户端的连接配置示例配置项的名字各框架差别不小这里列几个常见的都是可以直接抄的。Spring Boot 3.x注意 2.x 和 3.x 的配置前缀不同2.x 是spring.redis3.x 改成了spring.data.redis这个变化坑过不少人spring: data: redis: host: redis-01.cache.lab.local port: 6379 password: ${REDIS_PASSWORD} timeout: 2000ms lettuce: pool: max-active: 16 max-idle: 8 min-idle: 2 max-wait: 1000ms shutdown-timeout: 200msLettuce 的连接池默认没有 max-lifetime 的概念需要自己通过ClientOptions或者自定义RedisConnectionFactory来设置。更省事的做法是在基础设施层用commons-pool2的GenericObjectPoolConfig配上maxWait和minEvictableIdleTime间接控制连接存活时间。Python 的 redis-pyimport redis pool redis.ConnectionPool( hostredis-01.cache.lab.local, port6379, password***, db0, socket_connect_timeout1, socket_timeout2, health_check_interval30, max_connections20, ) client redis.Redis(connection_poolpool)health_check_interval这个参数值得设它会让连接池在取连接时做一次 PING能及时发现已经被服务端断掉的死连接减少ConnectionError的概率。注意 DNS 解析发生在建立连接时池里已有的连接不会重新解析所以还是回到 TTL 和连接生命周期的配合问题。Go 的 go-redisrdb : redis.NewClient(redis.Options{ Addr: redis-01.cache.lab.local:6379, Password: os.Getenv(REDIS_PASSWORD), DB: 0, DialTimeout: time.Second, ReadTimeout: 2 * time.Second, WriteTimeout: 2 * time.Second, MaxRetries: 2, })Go 这边有个细节标准库的纯 Go 解析器本身不维护 DNS 缓存每次Dial都会实时查询所以 TTL 对它是立即生效的。但如果编译时启用了 cgo就会走 glibc 的解析路径行为又不一样了。想确认当前二进制用的是哪套可以设GODEBUGnetdnsgo或netdnscgo强制指定排查解析相关问题时这个开关很好用。5. 排查实录解析对了却连不上的那些情况5.1 分层排查的固定路径排这类问题最忌讳东一榔头西一棒子我一般按固定顺序过一遍通常五分钟内能定位。第一层看解析是否成功dig DNS服务器 域名 short有没有返回期望的 IP。没有返回就往下看是 NXDOMAIN 还是 SERVFAIL。NXDOMAIN 说明区域文件里压根没这条记录或者名字拼错了SERVFAIL 说明服务器自己出问题了去看named的日志常见是区域文件语法错误、serial 没递增、或者 DNSSEC 验证失败。第二层看客户端拿到的是不是这个结果getent hosts 域名。如果dig有结果而getent没有说明客户端侧的 NSS 配置有问题或者 systemd-resolved 在捣乱或者/etc/hosts里有冲突记录。第三层看TCP 层通不通nc -vz 域名 6379或者redis-cli -h 域名 ping。这一步失败就跟 DNS 无关了去看 Redis 的 bind 配置、protected-mode、防火墙规则、安全组。第四层看应用行为如果命令行能连、应用连不上那基本是应用侧的连接池配置、DNS 缓存、或者依赖库版本问题。这时候抓包最直接tcpdump -i any -n port 53看应用到底发了什么 DNS 查询、有没有拿到应答。5.2 六类高频故障与处理第一类/etc/hosts残留记录静默覆盖。现象是某些机器解析结果和其他机器不一致dig查 DNS 服务器是对的但getent拿到的是旧 IP。原因是nsswitch.conf里hosts: files dnsfiles 优先。处理办法是全局搜一遍 hosts 文件把历史遗留的 Redis 记录清掉并在规范里明确禁止在 hosts 里写业务地址。第二类改了区域文件但没生效。八成是 serial 号没改从库不会同步或者是改完没 reloadrndc reload或者systemctl reload named都行。还有一个隐藏原因是区域文件属主或权限不对BIND 以bind用户运行读不到文件会静默使用旧数据日志里只有一行不起眼的警告。第三类内网 DNS 加上去之后外网域名全解析不了。前面提过是recursion no或者forwarders没配。要么开启递归并限制来源要么在客户端保留原来的公网 DNS 作为备用 nameserver。第四类Java 应用改了 DNS 记录但一直连旧地址。JVM 的 DNS 缓存问题networkaddress.cache.ttl必须显式设置。另外注意这个属性要在 JVM 启动参数里设代码里设的话必须赶在任何网络操作之前否则不生效。第五类K8s 环境里 Pod 解析不到内网域名。CoreDNS 的上游配置问题。检查/etc/coredns/Corefile或者 ConfigMap 里的forward .指向确认那个地址在 Pod 网络里可达。如果节点用 systemd-resolved把127.0.0.53换成/run/systemd/resolve/resolv.conf里的真实地址。第六类Sentinel 切换后客户端连到了旧主库。这种情况通常不是 DNS 的问题而是 Sentinel 客户端缓存了旧的拓扑。检查客户端的sentinel配置是否正确订阅了切换事件以及 Redis 侧有没有配replica-announce-ip。如果配了域名但客户端报“无法解析”那就是客户端所在机器的 DNS 配置有问题跟 Sentinel 无关。5.3 常见问题速查表现象最可能原因验证命令处理方式Name or service not known解析失败DNS 不可达或无记录dig DNS 域名检查 nameserver 配置和区域文件Connection refused解析正常但端口未监听nc -vz IP 6379检查 Redis bind 和防火墙部分机器解析结果不一致hosts 文件残留getent hosts 域名清理/etc/hosts改记录后从库不更新serial 未递增dig 从库 域名修改 serial 后 reload应用连旧 IPJVM 或客户端 DNS 缓存抓包看 53 端口设置networkaddress.cache.ttlSERVFAIL区域文件语法错或 DNSSEC 失败named-checkzone修正文件关闭 dnssec-validation容器内解析失败Docker/K8s DNS 未透传容器内cat /etc/resolv.conf配置 daemon.json 或 CoreDNS6. 上线之后的稳定性与变更管理6.1 别让DNS自己变成新的单点前面折腾半天是为了消除 IP 变更带来的耦合如果 DNS 服务器本身挂了导致全公司业务连不上 Redis那就是把一个单点换成了另一个更致命的单点。所以至少两台是底线用 BIND 的主从master/slave或者 dnsmasq keepalived 做 VIP 都行。BIND 主从配置不复杂从库的named.conf.local里把type master改成type slave加一行masters { 10.20.30.11; };主库那边开allow-transfer指向从库 IP。客户端的/etc/resolv.conf里两个 nameserver 都写上去glibc 会自动在超时后切换。需要提醒的是glibc 的切换是串行的第一个 nameserver 超时默认 5 秒才试第二个。所以如果主 DNS 挂了但网络还能通比如进程假死客户端每次解析都要等 5 秒业务感知就是“偶发超时”。解决办法是把options timeout:1 attempts:2加上把单次超时压到 1 秒代价是网络抖动时可能误判。另一个办法是在前面挂一个本地缓存比如systemd-resolved或者nscd把解析压力挡在本地。监控方面至少要盯三个指标DNS 服务的查询 QPS 和响应延迟、区域传送是否成功从库的 serial 是否跟上主库、以及从客户端视角定期做一次探测解析用一个定时任务每分钟dig一次关键域名比对结果。最后这个特别有用能提前发现“服务还在但解析错了”的情况。6.2 变更流程与灰度顺序DNS 变更最怕的是“改完立刻全量生效出问题回滚也要等 TTL”。所以我的习惯是把 TTL 分层稳定期的记录 TTL 设长一点300 秒准备变更前 24 小时先临时调短到 30 秒变更窗口结束后再调回去。这样既控制了平时的查询量又保证了变更时的灵活性。变更顺序也有讲究。切换主库这种操作正确顺序是新主库就绪 → 验证新主库可读写 → 修改 DNS 记录 → 观察一段时间 → 下线旧主库。绝对不要先下线旧库再改 DNS中间会有一段完全不可用的窗口。还有一点经验域名切换要做灰度别指望一次全量。如果是多台客户端先改一台低优先级的服务验证确认新连接确实落到了新地址用redis-cli client list在服务端看来源 IP或者用CLIENT INFO看连接详情再逐步推开。我在一次切换里就是因为没做灰度改完之后发现有台老服务器的/etc/resolv.conf里第一个 nameserver 是个早就下线的地址每次解析都等 5 秒超时结果那台机器上的服务 P99 直接从 20ms 飙到 5 秒。6.3 安全边界上的几个注意点内网 DNS 虽然在内网但也不是随便开放就行。allow-query限制网段是最基本的allow-recursion更关键——开放递归的 DNS 服务器可以被用来做放大攻击即使在内网也可能被已经失陷的机器利用。如果这台机器只做权威解析直接recursion no最干净。区域传送allow-transfer也要限制到从库 IP。有些不严谨的配置用了any等于把整个内网的拓扑结构暴露给任何能发起 AXFR 查询的人资产梳理的成本直接降到零。Redis 这边的安全边界跟域名无关但同样重要不要因为“有了域名就好管理”就放松 ACL不要用同一个账号给所有业务用CONFIG、FLUSHALL、KEYS这类命令该禁就禁。域名的价值在于解耦地址不在于提升安全性这两件事别混为一谈。最后说一个我觉得挺有用的做法把 DNS 区域文件纳入版本管理用 Git 管起来每次变更走一次提交提交信息里写清楚改了什么、为什么改。这样出问题的时候能快速 diff 出最近的变化比翻聊天记录靠谱得多。区域文件的格式本身很规整很容易做自动化校验写个脚本在 CI 里跑named-checkzone就能挡住大部分低级错误。这套东西我在几个不同规模的环境里都落地过最小的只有三台机器、一个区域文件最大的涉及几十个域名和多台 DNS 服务器。经验是别一上来就追求完整的高可用架构先把最小闭环跑通——一台 DNS、一条 A 记录、一个能连上的客户端跑通了再往上加从库、加监控、加自动化。很多方案死在过度设计上而不是死在功能不足上。另外如果你现在的环境里 Redis 地址还是散落在各个配置文件里别想着一次性全改完挑一个不那么核心的业务先试点把 DNS 这套链路和运维流程跑顺了再推给核心业务中间踩的坑代价会小很多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑