欢迎光临
我们一直在努力

K8s CoreDNS 定制化:自定义域名解析与上游转发策略

K8s CoreDNS 定制化:自定义域名解析与上游转发策略

DNS 是 K8s 集群的"电话簿"。书页折了一个角,整个集群都在打错电话。

一、场景痛点

微服务 A 调用微服务 B,通过 b-service.default.svc.cluster.local 访问。某天运维把 B 迁移到了另一个 K8s 集群,在入口网关做了流量转发。但 A 里面的代码写死了 b-service 这个 Service 名——域名解析还是指向了老集群的空 Service,所有调用 5 秒超时。

方案有两个:

  • 改代码,把域名从 b-service 改成 b-service.new-cluster.example.com ——需要改 15 个微服务,协调 3 个团队,上线窗口排到两周后
  • 在 CoreDNS 里加一条自定义解析规则,把 b-service 重写为外部域名 ——5 分钟改完,即刻生效
  • 这就是 CoreDNS 定制化的价值。K8s 自带的 DNS 只解决"集群内 Service 发现",但当你的架构发展到混合云、多集群、外部服务集成时,你需要 DNS 层具备路由重写、上游转发、条件解析的能力。

    CoreDNS 的 Corefile 插件体系就是为这些场景设计的。

    二、底层机制与原理剖析

    2.1 CoreDNS 的插件链模型

    插件链按顺序执行。如果 kubernetes 插件匹配到了 Service,直接返回结果,不经过 rewrite。如果没匹配到,继续走 rewrite → forward。

    关键:插件顺序决定了解析优先级。把 rewrite 放在 kubernetes 前面可以实现"优先自定义规则,没命中再走 Service 发现"。

    2.2 Corefile 的核心配置段

    # CoreDNS 的标准 Corefile 结构
    .:53 {
    # Zone 定义: "." 表示处理所有域的查询

    # 1. 基础插件:日志、缓存、健康检查
    errors # 将错误输出到标准输出
    health { # 健康检查端点 :8080/health
    lameduck 5s # 优雅关闭前等待 5 秒
    }
    ready # 就绪探测端点 :8181/ready

    # 2. 核心解析插件(按优先级排序)
    kubernetes cluster.local in-addr.arpa ip6.arpa {
    pods insecure
    fallthrough in-addr.arpa ip6.arpa
    ttl 30
    }

    # 3. 域名重写(自定义规则)
    rewrite stop {
    # 规则定义…
    }

    # 4. 上游转发
    forward . /etc/resolv.conf {
    max_concurrent 1000
    }

    # 5. 缓存
    cache 30
    loop
    reload
    loadbalance
    }

    2.3 请求流转的完整路径

    Pod 发起 DNS 查询 b-service.default.svc.cluster.local

    ├─ Step 1: 检查 Pod 的 /etc/resolv.conf
    │ └─ nameserver 指向 kube-dns (CoreDNS) Service ClusterIP

    ├─ Step 2: 请求到达 CoreDNS
    │ └─ 匹配 Corefile 中的插件链

    ├─ Step 3: kubernetes 插件检查
    │ ├─ b-service 在 default namespace 的 Service 列表?
    │ │ ├─ 是 → 返回 ClusterIP 10.96.1.5
    │ │ └─ 否 → fallthrough 到 rewrite 插件
    │ │
    ├─ Step 4: rewrite 插件处理
    │ ├─ 匹配 rewrite 规则?
    │ │ ├─ 是 → 改写域名,继续下一个插件
    │ │ └─ 否 → fallthrough

    ├─ Step 5: forward 插件转发
    │ └─ 转发到上游 DNS 服务器 (/etc/resolv.conf)

    └─ Step 6: 返回 IP 地址给 Pod

    三、生产级代码实现

    3.1 四大典型场景的 CoreDNS 配置

    # ============================================================
    # ConfigMap: coredns-custom
    # K8s 1.18+ 推荐方式: 在 coredns ConfigMap 同级目录添加自定义配置
    # ============================================================

    apiVersion: v1
    kind: ConfigMap
    metadata:
    name: coredns
    namespace: kube-system
    data:
    # ======== 核心 Corefile(主配置文件) ========
    Corefile: |
    .:53 {
    errors
    health {
    lameduck 5s
    }
    ready

    # K8s 集群内域名解析
    # cluster.local 是默认的集群域名后缀
    kubernetes cluster.local in-addr.arpa ip6.arpa {
    pods insecure
    fallthrough in-addr.arpa ip6.arpa
    ttl 30
    }

    # 引入所有 Server Block 文件
    # 每个场景一个独立的 Server Block 文件
    import custom/*.server

    # 自定义域名重写规则
    import custom/*.rewrite

    # 上游 DNS 转发
    forward . /etc/resolv.conf {
    max_concurrent 1000
    policy sequential # 按顺序尝试上游 DNS
    }

    cache 30
    loop
    reload
    loadbalance
    }


    # ============================================================
    # 场景 1: 域名重写(internal DNS alias)
    # 文件: custom/rewrite.rewrite
    #
    # 问题: 服务从 K8s 内迁移到了外部,但 15 个微服务代码里写死了老域名
    # 解决: DNS 层做域名重写,不需要改业务代码
    # ============================================================

    # 1.1 把集群内域名重写为外部域名(不带 stop 关键字,继续走 forward)
    rewrite name b-service.default.svc.cluster.local b-service.prod.example.com

    # 1.2 环境感知的重写(根据请求来源 namespace)
    rewrite name regex (.*)\\.default\\.svc\\.cluster\\.local {1}.production.example.com

    # 1.3 把老系统的域名映射到新的微服务
    rewrite name legacy-api.internal.local api-gateway.default.svc.cluster.local


    # ============================================================
    # 场景 2: 上游转发策略(多上游 + 分区解析)
    # 文件: custom/forward.server
    #
    # 问题: 部分外部域名需要走特定的 DNS 服务器(如公司内网 DNS)
    # 解决: 按域名分区,不同域走不同的上游
    # ============================================================

    # 2.1 公司内网域名走内网 DNS
    internal.example.com:53 {
    errors
    cache 30
    forward . 10.0.1.53 10.0.1.54 {
    max_concurrent 1000
    policy round_robin # 轮询上游 DNS 服务器
    }
    }

    # 2.2 外部域名走阿里云公共 DNS(100.100.2.136 加密 DNS)
    external-vendor.example.com:53 {
    errors
    cache 60
    forward . 100.100.2.136 100.100.2.138 {
    tls_servername dns.aliyun.com
    # TLS 加密 DNS 查询,防止中间人劫持
    max_concurrent 500
    }
    }

    # 2.3 默认外部域名走上游
    .:53 {
    errors
    cache 30
    forward . 223.5.5.5 119.29.29.29 { # 阿里 + 腾讯公共 DNS
    max_concurrent 1000
    policy sequential
    # sequential: 第一个失败才尝试第二个,避免不同 DNS 返回结果不一致
    }
    }


    # ============================================================
    # 场景 3: 条件解析(不同 namespace 走不同解析路径)
    # 文件: custom/conditional.rewrite
    #
    # 问题: staging 环境的服务需要调用 staging 的外部 API
    # 解决: 根据请求来源 namespace,返回不同的解析结果
    # ============================================================

    # 3.1 staging namespace 的服务调用外部 staging API
    rewrite name api.external-service.example.com api.staging.example.com

    # 3.2 使用 response rewrite 实现视网段返回
    # CoreDNS 不原生支持按来源 IP 路由,通过 template 插件实现:
    template IN A {
    # 当查询 *.staging.svc.cluster.local 时
    match "^(.*)\\.staging\\.svc\\.cluster\\.local\\.$"
    # 返回固定的 staging 网关 IP
    answer "{{ .Name }} 60 IN A 10.96.100.1"
    fallthrough
    }


    # ============================================================
    # 场景 4: 自定义 DNS 劫持(测试/调试场景)
    # 文件: custom/override.server
    #
    # 问题: 需要将某个外部 API 的流量劫持到本地的 mock 服务做测试
    # 解决: 通过 hosts 插件返回 mock 服务的 ClusterIP
    # ============================================================

    hosts {
    # 将第三方支付网关劫持到集群内的 mock 服务
    10.96.50.10 payment-gateway.external-bank.example.com
    10.96.50.10 api.payment.external-bank.example.com

    # 将外部分析服务劫持到内网测试实例
    10.96.50.20 analytics-collector.saas-vendor.example.com

    fallthrough # 未匹配的域名继续走下游插件
    }

    3.2 运维脚本:DNS 解析诊断工具

    #!/bin/bash
    # ============================================================
    # dns-diagnose.sh
    # K8s DNS 解析诊断脚本
    # 从 Pod 内部测试 DNS 解析链路,定位问题环节
    # ============================================================

    set -euo pipefail

    NAMESPACE="${1:-default}"
    TEST_DOMAIN="${2:-b-service.default.svc.cluster.local}"
    COREDNS_SVC_IP="${3:-10.96.0.10}" # kube-dns Service ClusterIP

    echo "============================================"
    echo "K8s DNS 诊断: ${TEST_DOMAIN}"
    echo "Namespace: ${NAMESPACE}"
    echo "============================================"

    # Step 1: 检查 /etc/resolv.conf 配置
    echo ""
    echo "[1/7] 检查 Pod DNS 配置 (/etc/resolv.conf)"
    echo "——————————————-"
    cat /etc/resolv.conf
    NDOTS=$(grep "options" /etc/resolv.conf | grep -oP "ndots:\\d+" || echo "ndots:5")
    echo ""
    echo " 注意: ${NDOTS},如果域名中的点号 < ndots,会先尝试 search domain 后缀"

    # Step 2: 使用 nslookup 解析
    echo ""
    echo "[2/7] nslookup 解析 (通过 /etc/resolv.conf)"
    echo "——————————————-"
    nslookup "${TEST_DOMAIN}" 2>&1 || echo " 解析失败"

    # Step 3: 直接查询 CoreDNS
    echo ""
    echo "[3/7] 直接查询 CoreDNS (${COREDNS_SVC_IP})"
    echo "——————————————-"
    nslookup "${TEST_DOMAIN}" "${COREDNS_SVC_IP}" 2>&1 || echo " CoreDNS 查询失败"

    # Step 4: 查询上游 DNS
    UPSTREAM_DNS=$(grep "^nameserver" /etc/resolv.conf | head -1 | awk '{print $2}')
    echo ""
    echo "[4/7] 查询上游 DNS (${UPSTREAM_DNS})"
    echo "——————————————-"
    nslookup "${TEST_DOMAIN}" "${UPSTREAM_DNS}" 2>&1 || echo " 上游 DNS 查询失败"

    # Step 5: 绕开 search domain 做精确查询
    echo ""
    echo "[5/7] 精确查询 (加末尾点号,绕开 search domain)"
    echo "——————————————-"
    nslookup "${TEST_DOMAIN}." "${COREDNS_SVC_IP}" 2>&1 || echo " 精确查询失败"

    # Step 6: 使用 dig 查看解析详情
    echo ""
    echo "[6/7] dig 详情 (查询时间 + 查询链)"
    echo "——————————————-"
    dig "${TEST_DOMAIN}" @"${COREDNS_SVC_IP}" +noall +answer +stats 2>&1 || echo " dig 查询失败"

    # Step 7: CoreDNS metrics 检查(需要从 CoreDNS Pod 或 metrics 端点)
    echo ""
    echo "[7/7] CoreDNS 指标检查"
    echo "——————————————-"
    # 检查 CoreDNS 的 Prometheus metrics
    echo " CoreDNS metrics 暴露在 :9153/metrics"
    echo " 关键指标:"
    echo " coredns_dns_requests_total – 总请求数"
    echo " coredns_dns_responses_total – 总响应数"
    echo " coredns_dns_request_duration_seconds – 请求延迟"
    echo " coredns_forward_requests_total – 转发请求数"
    echo " coredns_forward_responses_total – 转发响应数"
    echo " coredns_cache_hits_total – 缓存命中数"
    echo " coredns_cache_misses_total – 缓存未命中数"

    echo ""
    echo "============================================"
    echo "诊断完成"
    echo "============================================"

    3.3 验证配置

    # 使用 test-pod 验证 DNS 解析
    apiVersion: v1
    kind: Pod
    metadata:
    name: dns-test
    namespace: default
    spec:
    containers:
    – name: test
    image: busybox:1.36
    command:
    – sleep
    – "3600"
    resources:
    limits:
    memory: "128Mi"
    cpu: "100m"
    restartPolicy: Never

    # 执行验证命令
    # kubectl exec -it dns-test — nslookup b-service.default.svc.cluster.local
    # kubectl exec -it dns-test — nslookup external-api.example.com
    # kubectl exec -it dns-test — nslookup internal-service.corp.local

    3.4 Python 客户端:DNS 解析监控

    """
    DNS 解析监控工具
    定期探测关键域名的解析延迟,异常时告警

    监控维度:
    1. 解析成功/失败计数
    2. 解析延迟分布 (P50/P95/P99)
    3. CoreDNS 上游转发延迟
    4. 域名解析结果变化检测(IP 漂移)
    """
    import asyncio
    import socket
    import time
    from dataclasses import dataclass, field
    from collections import defaultdict

    @dataclass
    class DNSProbeResult:
    domain: str
    resolved_ips: list[str]
    latency_ms: float
    success: bool
    error: str = ""
    nameserver: str = ""
    timestamp: float = field(default_factory=time.time)

    class DNSMonitor:
    """
    K8s DNS 解析监控器

    监控目标:
    1. 集群内 Service 域名解析
    2. 外部域名解析(经 CoreDNS forward)
    3. 跨环境域名解析
    """

    def __init__(self, nameserver: str = "10.96.0.10"):
    self.nameserver = nameserver # CoreDNS ClusterIP

    # 关键域名列表(需要持续监控)
    self.critical_domains = [
    # 集群内 Service
    "kubernetes.default.svc.cluster.local",
    "kube-dns.kube-system.svc.cluster.local",
    # 外部依赖
    "api.openai.com",
    "api.github.com",
    # 内部服务
    "redis-master.default.svc.cluster.local",
    "postgres-rw.database.svc.cluster.local",
    ]

    # 指标存储
    self.metrics: dict[str, list[DNSProbeResult]] = defaultdict(list)

    async def probe_domain(self, domain: str) -> DNSProbeResult:
    """
    探测域名的 DNS 解析
    使用 socket 做解析,测量端到端延迟

    注意: 这会使用 /etc/resolv.conf 中的 nameserver,
    即 CoreDNS 的 ClusterIP
    """
    start = time.perf_counter()
    try:
    # 使用 asyncio 的 getaddrinfo 做异步 DNS 解析
    loop = asyncio.get_event_loop()
    addrs = await loop.getaddrinfo(
    domain,
    None,
    family=socket.AF_INET,
    proto=socket.IPPROTO_TCP
    )

    elapsed = time.perf_counter() – start

    ips = list(set(addr[4][0] for addr in addrs))

    return DNSProbeResult(
    domain=domain,
    resolved_ips=ips,
    latency_ms=elapsed * 1000,
    success=True,
    nameserver=self.nameserver
    )

    except Exception as e:
    elapsed = time.perf_counter() – start
    return DNSProbeResult(
    domain=domain,
    resolved_ips=[],
    latency_ms=elapsed * 1000,
    success=False,
    error=str(e),
    nameserver=self.nameserver
    )

    async def run_probe_cycle(self) -> dict[str, DNSProbeResult]:
    """执行一轮全量探测"""
    tasks = [self.probe_domain(d) for d in self.critical_domains]
    results = await asyncio.gather(*tasks)

    # 存储结果
    for result in results:
    self.metrics[result.domain].append(result)
    # 只保留最近 100 条记录
    if len(self.metrics[result.domain]) > 100:
    self.metrics[result.domain] = self.metrics[result.domain][-100:]

    return {r.domain: r for r in results}

    def check_for_ip_drift(self, domain: str) -> bool:
    """
    检测 DNS 解析结果的 IP 是否发生变化
    IP 漂移可能是 DNS 劫持或 CoreDNS 配置错误
    """
    results = self.metrics.get(domain, [])
    if len(results) < 2:
    return False

    # 获取最近两个解析结果
    latest_ips = set(results[-1].resolved_ips)
    previous_ips = set(results[-2].resolved_ips)

    return latest_ips != previous_ips

    def report(self) -> str:
    """生成 DNS 监控报告"""
    lines = ["\\n📊 DNS 解析监控报告", "=" * 50]

    for domain in self.critical_domains:
    results = self.metrics.get(domain, [])
    if not results:
    lines.append(f"\\n {domain}: 无数据")
    continue

    success_count = sum(1 for r in results if r.success)
    latencies = [r.latency_ms for r in results if r.success]

    if latencies:
    latencies.sort()
    p50 = latencies[len(latencies) // 2]
    p95 = latencies[int(len(latencies) * 0.95)]
    p99 = latencies[int(len(latencies) * 0.99)]
    else:
    p50 = p95 = p99 = 0

    status = "✅" if success_count / len(results) > 0.99 else "⚠️"

    lines.append(
    f"\\n{status} {domain}"
    f"\\n 成功率: {success_count}/{len(results)} ({success_count/len(results)*100:.1f}%)"
    f"\\n 延迟: P50={p50:.1f}ms P95={p95:.1f}ms P99={p99:.1f}ms"
    )

    if self.check_for_ip_drift(domain):
    lines.append(" ⚠️ IP 地址已发生变化,请检查!")

    return "\\n".join(lines)

    async def main():
    monitor = DNSMonitor()

    # 持续探测,每 30 秒一次
    while True:
    await monitor.run_probe_cycle()
    print(monitor.report())
    await asyncio.sleep(30)

    if __name__ == "__main__":
    asyncio.run(main())

    四、边界分析与架构权衡

    4.1 什么时候该用 CoreDNS 定制化,什么时候不该用?

    该用的场景:

  • 跨集群服务迁移(不改代码的情况下切换域名指向)
  • 混合云 DNS 分区(内网域名走内网 DNS,外部域名走公共 DNS)
  • 测试环境劫持(把外部依赖重定向到 mock 服务)
  • DNS 层面的灰度发布(按 namespace 返回不同版本的服务 IP)
  • 不该用的场景:

  • 作为 API 网关的替代品(DNS 只能解析域名,不能做协议转换、限流、认证)
  • 频繁变化的动态路由(DNS 有缓存,TTL 内修改不生效)
  • 跨地区智能路由(GeoDNS 更适合,CoreDNS 不原生支持按客户端 IP 路由)
  • 4.2 DNS 缓存导致的问题

    CoreDNS 默认缓存 30 秒。如果你的服务切换了 IP 但 Pod 还在用缓存,会有 30 秒的"盲视期"。对于滚动更新来说不成问题(新旧 Pod 并存),但对于紧急切换场景,你需要:

    # 关键服务的 DNS 记录使用低 TTL
    kubernetes cluster.local {
    ttl 5 # 非默认的 5 秒 TTL,而不是 30 秒
    }

    # 或者在 Service 上使用 annotation 指定 TTL
    metadata:
    annotations:
    external-dns.alpha.kubernetes.io/ttl: "5"

    4.3 性能影响

    每条 rewrite 规则都会增加约 1-5 微秒的解析时间。如果配置了 50+ 条 rewrite 规则,P99 解析延迟可能从 1ms 涨到 5ms。

    建议:

    • rewrite 规则总数 < 20 条
    • 使用正则匹配而不是逐条精确匹配
    • 监控 coredns_dns_request_duration_seconds 指标

    4.4 安全注意事项

    DNS 重写可能导致中间人攻击风险:如果攻击者能修改 ConfigMap,可以将 payment.example.com 重写为攻击者控制的 IP。

    防护措施:

    • CoreDNS ConfigMap 的修改必须走 GitOps + PR review
    • 对关键域名(支付、认证)使用 DNSSEC 验证
    • 启用 CoreDNS 的 acl 插件限制查询来源

    五、总结

    CoreDNS 定制化的核心思想是在 DNS 层解决问题,而不是在应用层打补丁。DNS 是 K8s 最上层的基础设施——改一次配置,所有 Pod 自动生效。相比于让 15 个微服务改代码、协调发布时间窗口,DNS 层的修改是最高效的。

    三个最重要的配置模式:

  • rewrite:域名别名、内部域名到外部域名的映射
  • forward:按域分流到不同的上游 DNS 服务器
  • hosts/template:精确控制特定域名的解析结果
  • 记住一个原则:DNS 修改是全局生效的,改之前先在测试环境验证,改之后看 CoreDNS metrics 确认生效了。别在凌晨 3 点的生产环境直接改 Corefile——这种事,干一次就够了。

    赞(0)
    未经允许不得转载:171主机测评 » K8s CoreDNS 定制化:自定义域名解析与上游转发策略
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址