【ETCD】【源码阅读】Validate 函数解析

时间:2024-12-10 20:37:08

Validate 函数逐行解析

Validate 函数是 Config 结构体的一个方法,主要作用是检查 ETCD 配置是否符合预期,确保所有必需的配置项都被正确设置。如果有不合法或不一致的配置,函数会返回相应的错误。

函数签名:
func (cfg *Config) Validate() error
  • cfg:指向 Config 结构体的指针。
  • 返回值:返回一个 error,如果配置有效,返回 nil,否则返回详细的错误信息。

逐行解析:

1. 设置日志配置:
if err := cfg.setupLogging(); err != nil {
    return err
}
  • cfg.setupLogging():设置日志记录器。这个方法配置了日志的输出方式、日志等级等。
  • 如果配置日志时出错,返回错误。
2. 检查绑定 URL(监听端口):
if err := checkBindURLs(cfg.ListenPeerUrls); err != nil {
    return err
}
if err := checkBindURLs(cfg.ListenClientUrls); err != nil {
    return err
}
if err := checkBindURLs(cfg.ListenClientHttpUrls); err != nil {
    return err
}
  • checkBindURLs:检查提供的 URL 是否有效,确保监听的端口可用。
  • 检查了 ListenPeerUrls(集群节点间通信)、ListenClientUrls(客户端通信)、ListenClientHttpUrls(HTTP 客户端通信)是否有效。
3. 警告:单端口模式:
if len(cfg.ListenClientHttpUrls) == 0 {
    cfg.logger.Warn("Running http and grpc server on single port. This is not recommended for production.")
}
  • 如果没有为 HTTP 和 gRPC 分配单独的端口(即在一个端口上运行),发出警告。这种模式不推荐用于生产环境,因为它可能影响性能和安全性。
4. 检查 ListenMetricsUrls
if err := checkBindURLs(cfg.ListenMetricsUrls); err != nil {
    return err
}
  • checkBindURLs(cfg.ListenMetricsUrls):检查用于暴露 ETCD 指标的 URL 是否有效。

5. 检查 AdvertisePeerUrlsAdvertiseClientUrls
if err := checkHostURLs(cfg.AdvertisePeerUrls); err != nil {
    addrs := cfg.getAdvertisePeerURLs()
    return fmt.Errorf(`--initial-advertise-peer-urls %q must be "host:port" (%w)`, strings.Join(addrs, ","), err)
}
if err := checkHostURLs(cfg.AdvertiseClientUrls); err != nil {
    addrs := cfg.getAdvertiseClientURLs()
    return fmt.Errorf(`--advertise-client-urls %q must be "host:port" (%w)`, strings.Join(addrs, ","), err)
}
  • checkHostURLs:检查 AdvertisePeerUrlsAdvertiseClientUrls 是否为有效的 host:port 格式。
  • 如果不符合格式,返回详细错误信息,说明这两个 URL 必须是 host:port 格式。
6. 检查冲突的启动标志:
nSet := 0
for _, v := range []bool{cfg.Durl != "", cfg.InitialCluster != "", cfg.DNSCluster != "", len(cfg.DiscoveryCfg.Endpoints) > 0} {
    if v {
        nSet++
    }
}
  • nSet:检查与集群启动相关的标志是否同时设置。nSet 计数器用于检查是否存在冲突的标志(例如,多个不同的启动方式)。
if cfg.ClusterState != ClusterStateFlagNew && cfg.ClusterState != ClusterStateFlagExisting {
    return fmt.Errorf("unexpected clusterState %q", cfg.ClusterState)
}
  • cfg.ClusterState:检查集群状态是否为有效值(newexisting)。如果无效,返回错误。
if nSet > 1 {
    return ErrConflictBootstrapFlags
}
  • 如果设置了多个标志(如 --discovery, --initial-cluster),返回冲突错误。
7. 检查 v2 和 v3 发现设置是否同时启用:
v2discoveryFlagsExist := cfg.Dproxy != ""
v3discoveryFlagsExist := len(cfg.DiscoveryCfg.Endpoints) > 0 ||
    cfg.DiscoveryCfg.Token != "" ||
    cfg.DiscoveryCfg.Secure.Cert != "" ||
    cfg.DiscoveryCfg.Secure.Key != "" ||
    cfg.DiscoveryCfg.Secure.Cacert != "" ||
    cfg.DiscoveryCfg.Auth.Username != "" ||
    cfg.DiscoveryCfg.Auth.Password != ""
  • 检查 v2 和 v3 发现设置是否同时启用。如果两个设置同时存在,返回错误。
if v2discoveryFlagsExist && v3discoveryFlagsExist {
    return errors.New("both v2 discovery settings (discovery, discovery-proxy) " +
        "and v3 discovery settings (discovery-token, discovery-endpoints, discovery-cert, " +
        "discovery-key, discovery-cacert, discovery-user, discovery-password) are set")
}
8. 检查 --discovery-token--discovery-endpoints 是否同时设置:
if (cfg.DiscoveryCfg.Token != "") != (len(cfg.DiscoveryCfg.Endpoints) > 0) {
    return errors.New("both --discovery-token and --discovery-endpoints must be set")
}
  • cfg.DiscoveryCfg.Tokencfg.DiscoveryCfg.Endpoints 必须同时设置,若一个未设置则返回错误。
9. 检查心跳和选举超时配置:
if cfg.TickMs == 0 {
    return fmt.Errorf("--heartbeat-interval must be >0 (set to %dms)", cfg.TickMs)
}
if cfg.ElectionMs == 0 {
    return fmt.Errorf("--election-timeout must be >0 (set to %dms)", cfg.ElectionMs)
}
if 5*cfg.TickMs > cfg.ElectionMs {
    return fmt.Errorf("--election-timeout[%vms] should be at least as 5 times as --heartbeat-interval[%vms]", cfg.ElectionMs, cfg.TickMs)
}
if cfg.ElectionMs > maxElectionMs {
    return fmt.Errorf("--election-timeout[%vms] is too long, and should be set less than %vms", cfg.ElectionMs, maxElectionMs)
}
  • 检查心跳间隔和选举超时是否合理。如果心跳间隔大于选举超时的五分之一,或者选举超时过长,则返回错误。
10. 检查 --advertise-client-urls 是否设置:
if cfg.ListenClientUrls != nil && cfg.AdvertiseClientUrls == nil {
    return ErrUnsetAdvertiseClientURLsFlag
}
  • 如果设置了 ListenClientUrls,但未设置 AdvertiseClientUrls,则返回错误。
11. 检查自动压缩模式:
switch cfg.AutoCompactionMode {
case CompactorModeRevision, CompactorModePeriodic:
case "": 
    return errors.New("undefined auto-compaction-mode")
default:
    return fmt.Errorf("unknown auto-compaction-mode %q", cfg.AutoCompactionMode)
}
  • 检查自动压缩模式是否有效。如果未定义或设置了无效值,则返回错误。
12. 校验分布式追踪配置:
if cfg.ExperimentalEnableDistributedTracing {
    if err := validateTracingConfig(cfg.ExperimentalDistributedTracingSamplingRatePerMillion); err != nil {
        return fmt.Errorf("distributed tracing configurition is not valid: (%w)", err)
    }
}
  • 如果启用了分布式追踪,验证其配置是否有效。
13. 检查租约检查点配置:
if !cfg.ExperimentalEnableLeaseCheckpointPersist && cfg.ExperimentalEnableLeaseCheckpoint {
    cfg.logger.Warn("Detected that checkpointing is enabled without persistence. Consider enabling experimental-enable-lease-checkpoint-persist")
}

if cfg.ExperimentalEnableLeaseCheckpointPersist && !cfg.ExperimentalEnableLeaseCheckpoint {
    return fmt.Errorf("setting experimental-enable-lease-checkpoint-persist requires experimental-enable-lease-checkpoint")
}
  • 检查点配置:如果启用了租约检查点,但没有持久化存储,发出警告。
14. 检查 TLS 配置:
minVersion, err := tlsutil.GetTLSVersion(cfg.TlsMinVersion)
if err != nil {
    return err
}
maxVersion, err := tlsutil.GetTLSVersion(cfg.TlsMaxVersion)
if err != nil {
    return err
}
  • 检查 TLS 最小和最大版本配置是否合法。
15. 校验 TLS 版本冲突:

go
if maxVersion != 0 && minVersion > maxVersion {
    return fmt.Errorf("min version (%s) is greater than max version (%s)", cfg.TlsMinVersion, cfg.TlsMaxVersion)
}
  • 如果设置了 TLS 最小版本大于最大版本,则返回错误。
16. 检查 TLS 1.3 配置:
if minVersion == tls.VersionTLS13 && len(cfg.CipherSuites) > 0 {
    return fmt.Errorf("cipher suites cannot be configured when only TLS1.3 is enabled")
}
  • 如果启用了 TLS 1.3,不允许设置密码套件。
返回值:

如果所有配置项都通过验证,函数将返回 nil,否则返回相应的错误。

总结:

Validate 函数主要用于验证 ETCD 启动时的配置,确保配置项的合理性,避免潜在的错误或冲突。