- 后端
- 配置中心
- 运维
【免费下载链接】confd
Manage local application configuration files using templates and data from etcd or consul
在 confd 中,后端节点(etcd、consul 等)通常通过-node参数或配置文件中的nodes数组硬编码。当集群节点频繁扩缩容、或希望配置随 DNS 自动变化时,confd 提供了另一条路径:使用 DNS SRV 记录声明后端节点。阅读本文后,你将掌握-srv-domain、-srv-record、-scheme三个参数的正确用法,理解 confd 是如何把 SRV 记录解析为节点列表的(含源码级实现),并知道这一机制在配置文件与命令行参数两种场景下的优先级与限制。
基本用法:用 -srv-domain 声明后端节点
核心文档 docs/dns-srv-records.md 给出的最基本用法是:SRV 记录用于声明后端节点,只需加上-srv-domain标志。
以 etcd 为例,先在 DNS 中发布 SRV 记录并用dig验证:
dig SRV _etcd._tcp.confd.io期望返回如下应答段:
... ;; ANSWER SECTION: _etcd._tcp.confd.io. 300 IN SRV 1 100 4001 etcd.confd.io.其中1 100 4001 etcd.confd.io.依次表示优先级(priority)、权重(weight)、端口(port)、目标主机(target)。随后启动 confd:
confd -backend etcd -srv-domain confd.ioconsul 同理:
dig SRV _consul._tcp.confd.io... ;; ANSWER SECTION: _consul._tcp.confd.io. 300 IN SRV 1 100 8500 consul.confd.io.confd -backend consul -srv-domain confd.io注意两条 SRV 记录的端口恰好对应各自后端的默认服务端口:etcd 为 4001,consul 为 8500。这个端口值会在运行时被 confd 直接使用,因此 DNS 记录的端口必须与后端实际监听端口一致。
SRV 记录名是怎么构造出来的
只给-srv-domain时,confd 并不会直接拿域名去查询,而是按_<backend>._tcp.<domain>.的规则自动拼出完整的 SRV 记录名。这一点可以在 config.go 中确认:
if config.SRVDomain != "" && config.SRVRecord == "" { config.SRVRecord = fmt.Sprintf("_%s._tcp.%s.", config.Backend, config.SRVDomain) }也就是说:
-backend etcd -srv-domain confd.io等价于查询_etcd._tcp.confd.io.;-backend consul -srv-domain confd.io等价于查询_consul._tcp.confd.io.;- 这与文档示例中
dig SRV查询的名字一一对应。
如果你发布的 SRV 记录名不符合这个约定(例如 etcd v3 集群常用的_etcd-client._tcp.example.com),可以直接使用-srv-record标志显式指定完整记录名,跳过自动拼接:
confd -backend etcdv3 -srv-record _etcd-client._tcp.example.com-srv-record的说明见 config.go 中的 flag 定义与 docs/command-line-flags.md。
解析实现:从 SRV 记录到节点列表
自动拼接出记录名之后,initConfig()会调用getBackendNodesFromSRV()完成解析,相关代码位于 config.go:
// Update BackendNodes from SRV records. if config.Backend != "env" && config.SRVRecord != "" { log.Info("SRV record set to " + config.SRVRecord) srvNodes, err := getBackendNodesFromSRV(config.SRVRecord) if err != nil { return errors.New("Cannot get nodes from SRV records " + err.Error()) } switch config.Backend { case "etcd": vsm := make([]string, len(srvNodes)) for i, v := range srvNodes { vsm[i] = config.Scheme + "://" + v } srvNodes = vsm } config.BackendNodes = srvNodes }解析函数本体在 config.go:
func getBackendNodesFromSRV(record string) ([]string, error) { nodes := make([]string, 0) // Ignore the CNAME as we don't need it. _, addrs, err := net.LookupSRV("", "", record) if err != nil { return nodes, err } for _, srv := range addrs { host := strings.TrimRight(srv.Target, ".") port := strconv.FormatUint(uint64(srv.Port), 10) nodes = append(nodes, net.JoinHostPort(host, port)) } return nodes, nil }从源码结构看,可以提炼出几个关键行为:
- 基于 Go 标准库解析:调用
net.LookupSRV("", "", record),service 与 proto 参数留空,因为完整记录名已经包含了服务与协议前缀(_etcd._tcp.)。 - 只用 target 与 port 两个字段:SRV 记录中的 priority 和 weight 在这里不参与排序或加权选择,confd 只是把每条记录翻译成
host:port字符串。 - 去掉目标主机末尾的点:DNS 应答中的 FQDN 以
.结尾(如etcd.confd.io.),代码用strings.TrimRight(srv.Target, ".")去掉,保证生成的节点地址可直接用于连接。 - 解析失败即启动失败:
net.LookupSRV出错(域名不存在、DNS 不可达等)时,initConfig()直接返回Cannot get nodes from SRV records ...错误,confd 进程启动即终止(参见 confd.go 中initConfig()出错后log.Fatal的处理)。这意味着 SRV 查询是一次性的启动期行为——启动成功后节点列表即固定,不会周期性地重新解析 DNS。
解析得到的节点会写入config.BackendNodes,随后由 backends/client.go 中的backends.New()分发给具体后端:启动日志中的Backend source(s) set to ...打印的就是这份列表,可用于确认 SRV 解析结果是否符合预期。
-scheme:SRV 节点 URL 的协议前缀
SRV 记录只给出主机和端口,协议(http 或 https)需要单独指定。confd 的默认值是http,通过-scheme修改:
confd -scheme https -srv-domain confd.io从源码看,-scheme的作用方式因后端而异(config.go 的 flag 注释明确其用途是 "the backend URI scheme for nodes retrieved from DNS SRV records"):
- etcd 后端:在 config.go 中,每个
host:port节点都会被加上scheme://前缀,例如https://etcd.confd.io:4001,再传给 etcdv3 客户端; - consul 后端:节点保持
host:port形态,config.Scheme作为独立参数传给 backends/consul 的New()构造函数(见 backends/client.go),由 consul 客户端自行拼接 URL; - 其他后端(etcdv3、zookeeper、redis 等):节点保持
host:port形态,客户端协议由后端自身决定。
-scheme与-srv-domain可以组合使用,且两者都可以通过 confd 配置文件设置,见下一节。
配置文件方式:srv_domain、srv_record 与 scheme
命令行参数不是唯一入口。confd 配置文件(默认/etc/confd/confd.toml,可用-config-file指定)同样支持 SRV 相关配置项,完整的键值说明见 docs/configuration-guide.md:
| 配置键 | 类型 | 说明 |
|---|---|---|
srv_domain | string | 资源记录名(对应-srv-domain) |
srv_record | string | 完整 SRV 记录名(对应-srv-record) |
scheme | string | SRV 节点 URL 的协议,http或https(对应-scheme) |
nodes | array of strings | 后端节点列表;SRV 解析结果会写入此处,通常无需手工配置 |
config.go 中对应的结构体字段带有 TOML tag:
SRVDomain string `toml:"srv_domain"` SRVRecord string `toml:"srv_record"`Scheme与BackendNodes则定义在 backends/config.go:
BackendNodes util.Nodes `toml:"nodes"` Scheme string `toml:"scheme"`一个启用 SRV 的完整配置示例(参考 docs/configuration-guide.md 的示例并保留 SRV 相关键):
backend = "etcd" confdir = "/etc/confd" log-level = "debug" interval = 600 scheme = "https" srv_domain = "etcd.example.com"配置的加载优先级在 config.go 的注释中明确说明:先设默认值,再被 confd 配置文件覆盖,再被环境变量覆盖,最后被命令行 flag 覆盖。因此当命令行同时给出-srv-domain时,它会覆盖配置文件中的srv_domain。需要特别留意的是:自动拼接逻辑只在SRVRecord为空时生效,如果配置文件已设置srv_record,-srv-domain不会再生成新的记录名。
注意事项与排查要点
结合源码,使用 SRV 机制时有几点值得提前知道:
- 后端默认节点是兜底值。当 SRV 未启用且
BackendNodes为空时,config.go 会为各后端填入本地默认地址:consul 为127.0.0.1:8500,etcd 为http://127.0.0.1:4001(或环境变量ETCDCTL_PEERS中的地址),etcdv3 为127.0.0.1:2379,redis 为127.0.0.1:6379,vault 为http://127.0.0.1:8200,zookeeper 为127.0.0.1:2181。SRV 解析成功时这些默认值不会生效。 env后端不适用 SRV。解析条件包含config.Backend != "env"(config.go),环境变量后端没有网络节点概念。- 解析只发生在启动期。
net.LookupSRV只在initConfig()中执行一次,运行期间集群成员变化(DNS 记录更新)不会自动反映到 confd,需要重启进程重新解析。 - priority 与 weight 不影响节点选择。confd 按 DNS 返回顺序依次收集全部 SRV 记录生成节点列表,不做优先级排序或按权重分配流量;多节点场景下,节点间的负载选择由具体后端客户端(如 etcd client 的多端点机制)负责。
- 排查顺序:先
dig SRV <record>确认记录存在且 target/port 正确,再观察 confd 启动日志中的两条关键信息——SRV record set to <record>(确认实际查询的记录名,注意自动拼接结果是否如预期)与Backend source(s) set to ...(确认最终节点列表)。若报Cannot get nodes from SRV records ...,说明 DNS 查询失败,应检查记录名拼写与 DNS 可达性。 - etcd 后端的特殊处理:etcd 是仓库中唯一在 SRV 结果上拼接
scheme://前缀的后端(config.go);且当前实现中etcd与etcdv3后端共用 etcdv3 客户端(backends/client.go 的注释),发布 SRV 记录时请确保端口与 etcd client 端口一致。
延伸:模板函数 lookupSRV
除了启动期解析后端节点,confd 还在模板函数中暴露了lookupSRV,用于在模板渲染时查询任意 SRV 记录,完整文档见 docs/templates.md:
{{range lookupSRV "mail" "tcp" "example.com"}} target: {{.Target}} port: {{.Port}} priority: {{.Priority}} weight: {{.Weight}} {{end}}其实现位于 resource/template/template_funcs.go,与启动期解析的一个显著区别是:模板版LookupSRV会对结果集排序(按 target、port、priority、weight 拼接后的字典序),文档注释说明这样做的目的是减少因记录顺序变化导致的多余配置重载。适合在生成的配置文件中动态引用上游服务列表的场景。
小结
confd 的 SRV 机制链路清晰:-srv-domain(或配置文件srv_domain)按_<backend>._tcp.<domain>.规则拼出记录名,-srv-record可显式覆盖;net.LookupSRV将每条记录翻译为host:port;-scheme决定 etcd 节点 URL 的协议前缀(consul 则作为独立参数传入客户端);解析结果写入BackendNodes并分发给具体后端。掌握这条链路后,你可以把 confd 的部署从"硬编码节点列表"升级为"由 DNS 记录驱动的动态拓扑",并通过启动日志中的SRV record set to与Backend source(s) set to两条信息快速验证配置是否生效。
- 后端
- 配置中心
- 运维
【免费下载链接】confd
Manage local application configuration files using templates and data from etcd or consul
相关推荐
Ceph 通过 DNS SRV 记录发现 Monitor:mon-lookup-dns 配置与实现解析
Ceph 通过 DNS SRV 记录发现 Monitor:mon lookup dns 配置与实现解析 导读 在 Ceph 集群中,客户端(librados、r
存储分布式文件系统对象存储后端高可用Apache APISIX 基于 DNS 的服务发现:配置、SRV 记录与源码级原理解析
Apache APISIX 基于 DNS 的服务发现:配置、SRV 记录与源码级原理解析 导读 本文以 Apache APISIX 官方文档 docs/en/l
API网关后端云原生微服务APISIX DNS 服务发现实战:配置解析、SRV 记录语义与源码级原理
APISIX DNS 服务发现实战:配置解析、SRV 记录语义与源码级原理 APISIX 的 DNS 服务发现( discovery.dns )允许网关直接通过
后端微服务云原生
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考