diff --git a/configuration/configuration.go b/configuration/configuration.go index e4d4311d5f..f25aadf925 100644 --- a/configuration/configuration.go +++ b/configuration/configuration.go @@ -271,11 +271,23 @@ type FileChecker struct { Threshold int `yaml:"threshold,omitempty"` } +type RedisSentinel struct { + // MasterName specifies the name of the master sentinel. + MasterName string `yaml:"masterName,omitempty"` + + // Addresses specifies the addresses of the sentinels. + Addresses []string `yaml:"addresses,omitempty"` +} + // Redis configures the redis pool available to the registry webapp. type Redis struct { // Addr specifies the the redis instance available to the application. Addr string `yaml:"addr,omitempty"` + // Sentinel specifies the sentinel instance available to the application. + // If this is set, the Addr field is ignored. + Sentinel RedisSentinel `yaml:"sentinel,omitempty"` + // Usernames can be used as a finer-grained permission control since the introduction of the redis 6.0. Username string `yaml:"username,omitempty"` diff --git a/registry/handlers/app.go b/registry/handlers/app.go index 2983176b85..1171649674 100644 --- a/registry/handlers/app.go +++ b/registry/handlers/app.go @@ -487,12 +487,12 @@ func (app *App) configureEvents(configuration *configuration.Configuration) { } func (app *App) configureRedis(cfg *configuration.Configuration) { - if cfg.Redis.Addr == "" { + if cfg.Redis.Addr == "" && (len(cfg.Redis.Sentinel.Addresses) == 0 || cfg.Redis.Sentinel.MasterName == "") { dcontext.GetLogger(app).Infof("redis not configured") return } - app.redis = app.createPool(cfg.Redis) + app.redis = app.createRedisClient(cfg.Redis) // Enable metrics instrumentation. if err := redisotel.InstrumentMetrics(app.redis); err != nil { @@ -514,25 +514,45 @@ func (app *App) configureRedis(cfg *configuration.Configuration) { })) } -func (app *App) createPool(cfg configuration.Redis) *redis.Client { - return redis.NewClient(&redis.Options{ - Addr: cfg.Addr, - OnConnect: func(ctx context.Context, cn *redis.Conn) error { - res := cn.Ping(ctx) - return res.Err() - }, - Username: cfg.Username, - Password: cfg.Password, - DB: cfg.DB, - MaxRetries: 3, - DialTimeout: cfg.DialTimeout, - ReadTimeout: cfg.ReadTimeout, - WriteTimeout: cfg.WriteTimeout, - PoolFIFO: false, - MaxIdleConns: cfg.Pool.MaxIdle, - PoolSize: cfg.Pool.MaxActive, - ConnMaxIdleTime: cfg.Pool.IdleTimeout, - }) +func (app *App) createRedisClient(cfg configuration.Redis) *redis.Client { + // This function assumes that cfg.Addresses is not empty, which is checked by the caller. + if len(cfg.Sentinel.Addresses) > 0 && cfg.Sentinel.MasterName != "" { + return redis.NewFailoverClient(&redis.FailoverOptions{ + MasterName: cfg.Sentinel.MasterName, + SentinelAddrs: cfg.Sentinel.Addresses, + OnConnect: nil, + Username: cfg.Username, + Password: cfg.Password, + DB: cfg.DB, + MaxRetries: 3, + DialTimeout: cfg.DialTimeout, + ReadTimeout: cfg.ReadTimeout, + WriteTimeout: cfg.WriteTimeout, + PoolFIFO: false, + MaxIdleConns: cfg.Pool.MaxIdle, + PoolSize: cfg.Pool.MaxActive, + ConnMaxIdleTime: cfg.Pool.IdleTimeout, + }) + } else { + return redis.NewClient(&redis.Options{ + Addr: cfg.Addr, + OnConnect: func(ctx context.Context, cn *redis.Conn) error { + res := cn.Ping(ctx) + return res.Err() + }, + Username: cfg.Username, + Password: cfg.Password, + DB: cfg.DB, + MaxRetries: 3, + DialTimeout: cfg.DialTimeout, + ReadTimeout: cfg.ReadTimeout, + WriteTimeout: cfg.WriteTimeout, + PoolFIFO: false, + MaxIdleConns: cfg.Pool.MaxIdle, + PoolSize: cfg.Pool.MaxActive, + ConnMaxIdleTime: cfg.Pool.IdleTimeout, + }) + } } // configureLogHook prepares logging hook parameters.