Validate
函数逐行解析
Validate
函数是 Config
结构体的一个方法,主要作用是检查 ETCD 配置是否符合预期,确保所有必需的配置项都被正确设置。如果有不合法或不一致的配置,函数会返回相应的错误。
函数签名:
go
func (cfg *Config) Validate() error
cfg
:指向Config
结构体的指针。- 返回值 :返回一个
error
,如果配置有效,返回nil
,否则返回详细的错误信息。
逐行解析:
1. 设置日志配置:
go
if err := cfg.setupLogging(); err != nil {
return err
}
cfg.setupLogging()
:设置日志记录器。这个方法配置了日志的输出方式、日志等级等。- 如果配置日志时出错,返回错误。
2. 检查绑定 URL(监听端口):
go
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. 警告:单端口模式:
go
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
:
go
if err := checkBindURLs(cfg.ListenMetricsUrls); err != nil {
return err
}
checkBindURLs(cfg.ListenMetricsUrls)
:检查用于暴露 ETCD 指标的 URL 是否有效。
5. 检查 AdvertisePeerUrls
和 AdvertiseClientUrls
:
go
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
:检查AdvertisePeerUrls
和AdvertiseClientUrls
是否为有效的host:port
格式。- 如果不符合格式,返回详细错误信息,说明这两个 URL 必须是
host:port
格式。
6. 检查冲突的启动标志:
go
nSet := 0
for _, v := range []bool{cfg.Durl != "", cfg.InitialCluster != "", cfg.DNSCluster != "", len(cfg.DiscoveryCfg.Endpoints) > 0} {
if v {
nSet++
}
}
nSet
:检查与集群启动相关的标志是否同时设置。nSet
计数器用于检查是否存在冲突的标志(例如,多个不同的启动方式)。
go
if cfg.ClusterState != ClusterStateFlagNew && cfg.ClusterState != ClusterStateFlagExisting {
return fmt.Errorf("unexpected clusterState %q", cfg.ClusterState)
}
cfg.ClusterState
:检查集群状态是否为有效值(new
或existing
)。如果无效,返回错误。
go
if nSet > 1 {
return ErrConflictBootstrapFlags
}
- 如果设置了多个标志(如
--discovery
,--initial-cluster
),返回冲突错误。
7. 检查 v2 和 v3 发现设置是否同时启用:
go
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 发现设置是否同时启用。如果两个设置同时存在,返回错误。
go
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
是否同时设置:
go
if (cfg.DiscoveryCfg.Token != "") != (len(cfg.DiscoveryCfg.Endpoints) > 0) {
return errors.New("both --discovery-token and --discovery-endpoints must be set")
}
cfg.DiscoveryCfg.Token
和cfg.DiscoveryCfg.Endpoints
必须同时设置,若一个未设置则返回错误。
9. 检查心跳和选举超时配置:
go
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
是否设置:
go
if cfg.ListenClientUrls != nil && cfg.AdvertiseClientUrls == nil {
return ErrUnsetAdvertiseClientURLsFlag
}
- 如果设置了
ListenClientUrls
,但未设置AdvertiseClientUrls
,则返回错误。
11. 检查自动压缩模式:
go
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. 校验分布式追踪配置:
go
if cfg.ExperimentalEnableDistributedTracing {
if err := validateTracingConfig(cfg.ExperimentalDistributedTracingSamplingRatePerMillion); err != nil {
return fmt.Errorf("distributed tracing configurition is not valid: (%w)", err)
}
}
- 如果启用了分布式追踪,验证其配置是否有效。
13. 检查租约检查点配置:
go
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
配置:
go
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
配置:
go
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 启动时的配置,确保配置项的合理性,避免潜在的错误或冲突。