Add Redis Sentinel support

Co-authored-by: Romain <rtribotte@users.noreply.github.com>
This commit is contained in:
Baptiste Mayelle 2024-01-02 16:16:05 +01:00 committed by GitHub
parent 60123a8f3f
commit 3dfaa3d5fa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 543 additions and 12 deletions

View file

@ -229,3 +229,166 @@ providers:
```bash tab="CLI"
--providers.redis.tls.insecureSkipVerify=true
```
### `sentinel`
_Optional_
Defines the Sentinel configuration used to interact with Redis Sentinel.
#### `masterName`
_Required_
`masterName` is the name of the Sentinel master.
```yaml tab="File (YAML)"
providers:
redis:
sentinel:
masterName: my-master
```
```toml tab="File (TOML)"
[providers.redis.sentinel]
masterName = "my-master"
```
```bash tab="CLI"
--providers.redis.sentinel.masterName=my-master
```
#### `username`
_Optional_
`username` is the username for Sentinel authentication.
```yaml tab="File (YAML)"
providers:
redis:
sentinel:
username: user
```
```toml tab="File (TOML)"
[providers.redis.sentinel]
username = "user"
```
```bash tab="CLI"
--providers.redis.sentinel.username=user
```
#### `password`
_Optional_
`password` is the password for Sentinel authentication.
```yaml tab="File (YAML)"
providers:
redis:
sentinel:
password: password
```
```toml tab="File (TOML)"
[providers.redis.sentinel]
password = "password"
```
```bash tab="CLI"
--providers.redis.sentinel.password=password
```
#### `latencyStrategy`
_Optional, Default=false_
`latencyStrategy` defines whether to route commands to the closest master or replica nodes
(mutually exclusive with RandomStrategy and ReplicaStrategy).
```yaml tab="File (YAML)"
providers:
redis:
sentinel:
latencyStrategy: true
```
```toml tab="File (TOML)"
[providers.redis.sentinel]
latencyStrategy = true
```
```bash tab="CLI"
--providers.redis.sentinel.latencyStrategy=true
```
#### `randomStrategy`
_Optional, Default=false_
`randomStrategy` defines whether to route commands randomly to master or replica nodes
(mutually exclusive with LatencyStrategy and ReplicaStrategy).
```yaml tab="File (YAML)"
providers:
redis:
sentinel:
randomStrategy: true
```
```toml tab="File (TOML)"
[providers.redis.sentinel]
randomStrategy = true
```
```bash tab="CLI"
--providers.redis.sentinel.randomStrategy=true
```
#### `replicaStrategy`
_Optional, Default=false_
`replicaStrategy` Defines whether to route all commands to replica nodes
(mutually exclusive with LatencyStrategy and RandomStrategy).
```yaml tab="File (YAML)"
providers:
redis:
sentinel:
replicaStrategy: true
```
```toml tab="File (TOML)"
[providers.redis.sentinel]
replicaStrategy = true
```
```bash tab="CLI"
--providers.redis.sentinel.replicaStrategy=true
```
#### `useDisconnectedReplicas`
_Optional, Default=false_
`useDisconnectedReplicas` defines whether to use replicas disconnected with master when cannot get connected replicas.
```yaml tab="File (YAML)"
providers:
redis:
sentinel:
useDisconnectedReplicas: true
```
```toml tab="File (TOML)"
[providers.redis.sentinel]
useDisconnectedReplicas = true
```
```bash tab="CLI"
--providers.redis.sentinel.useDisconnectedReplicas=true
```

View file

@ -906,6 +906,27 @@ Password for authentication.
`--providers.redis.rootkey`:
Root key used for KV store. (Default: ```traefik```)
`--providers.redis.sentinel.latencystrategy`:
Defines whether to route commands to the closest master or replica nodes (mutually exclusive with RandomStrategy and ReplicaStrategy). (Default: ```false```)
`--providers.redis.sentinel.mastername`:
Name of the master.
`--providers.redis.sentinel.password`:
Password for Sentinel authentication.
`--providers.redis.sentinel.randomstrategy`:
Defines whether to route commands randomly to master or replica nodes (mutually exclusive with LatencyStrategy and ReplicaStrategy). (Default: ```false```)
`--providers.redis.sentinel.replicastrategy`:
Defines whether to route all commands to replica nodes (mutually exclusive with LatencyStrategy and RandomStrategy). (Default: ```false```)
`--providers.redis.sentinel.usedisconnectedreplicas`:
Use replicas disconnected with master when cannot get connected replicas. (Default: ```false```)
`--providers.redis.sentinel.username`:
Username for Sentinel authentication.
`--providers.redis.tls.ca`:
TLS CA

View file

@ -906,6 +906,27 @@ Password for authentication.
`TRAEFIK_PROVIDERS_REDIS_ROOTKEY`:
Root key used for KV store. (Default: ```traefik```)
`TRAEFIK_PROVIDERS_REDIS_SENTINEL_LATENCYSTRATEGY`:
Defines whether to route commands to the closest master or replica nodes (mutually exclusive with RandomStrategy and ReplicaStrategy). (Default: ```false```)
`TRAEFIK_PROVIDERS_REDIS_SENTINEL_MASTERNAME`:
Name of the master.
`TRAEFIK_PROVIDERS_REDIS_SENTINEL_PASSWORD`:
Password for Sentinel authentication.
`TRAEFIK_PROVIDERS_REDIS_SENTINEL_RANDOMSTRATEGY`:
Defines whether to route commands randomly to master or replica nodes (mutually exclusive with LatencyStrategy and ReplicaStrategy). (Default: ```false```)
`TRAEFIK_PROVIDERS_REDIS_SENTINEL_REPLICASTRATEGY`:
Defines whether to route all commands to replica nodes (mutually exclusive with LatencyStrategy and RandomStrategy). (Default: ```false```)
`TRAEFIK_PROVIDERS_REDIS_SENTINEL_USEDISCONNECTEDREPLICAS`:
Use replicas disconnected with master when cannot get connected replicas. (Default: ```false```)
`TRAEFIK_PROVIDERS_REDIS_SENTINEL_USERNAME`:
Username for Sentinel authentication.
`TRAEFIK_PROVIDERS_REDIS_TLS_CA`:
TLS CA

View file

@ -247,6 +247,14 @@
cert = "foobar"
key = "foobar"
insecureSkipVerify = true
[providers.redis.sentinel]
masterName = "foobar"
username = "foobar"
password = "foobar"
latencyStrategy = true
randomStrategy = true
replicaStrategy = true
useDisconnectedReplicas = true
[providers.http]
endpoint = "foobar"
pollInterval = "42s"

View file

@ -275,6 +275,14 @@ providers:
cert: foobar
key: foobar
insecureSkipVerify: true
sentinel:
masterName: foobar
username: foobar
password: foobar
latencyStrategy: true
randomStrategy: true
replicaStrategy: true
useDisconnectedReplicas: true
http:
endpoint: foobar
pollInterval: 42s

5
go.mod
View file

@ -37,7 +37,7 @@ require (
github.com/klauspost/compress v1.17.1
github.com/kvtools/consul v1.0.2
github.com/kvtools/etcdv3 v1.0.2
github.com/kvtools/redis v1.0.2
github.com/kvtools/redis v1.1.0
github.com/kvtools/valkeyrie v1.0.0
github.com/kvtools/zookeeper v1.0.2
github.com/mailgun/ttlmap v0.0.0-20170619185759-c1c17f74874f
@ -189,7 +189,6 @@ require (
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.20.0 // indirect
github.com/go-openapi/swag v0.19.14 // indirect
github.com/go-redis/redis/v8 v8.11.5 // indirect
github.com/go-resty/resty/v2 v2.7.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/go-zookeeper/zk v1.0.3 // indirect
@ -278,6 +277,7 @@ require (
github.com/nrdcg/nodion v0.1.0 // indirect
github.com/nrdcg/porkbun v0.2.0 // indirect
github.com/nzdjb/go-metaname v1.0.0 // indirect
github.com/onsi/ginkgo v1.16.5 // indirect
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.2 // indirect
@ -294,6 +294,7 @@ require (
github.com/prometheus/procfs v0.8.0 // indirect
github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/qtls-go1-20 v0.3.4 // indirect
github.com/redis/go-redis/v9 v9.2.1 // indirect
github.com/sacloud/api-client-go v0.2.8 // indirect
github.com/sacloud/go-http v0.1.6 // indirect
github.com/sacloud/iaas-api-go v1.11.1 // indirect

12
go.sum
View file

@ -287,6 +287,10 @@ github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/buger/goterm v1.0.0 h1:ZB6uUlY8+sjJyFGzz2WpRqX2XYPeXVgtZAOJMwOsTWM=
github.com/buger/goterm v1.0.0/go.mod h1:16STi3LquiscTIHA8SXUNKEa/Cnu4ZHBH8NsCaWgso0=
github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
@ -741,8 +745,6 @@ github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/go-playground/validator/v10 v10.9.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY=
github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I=
github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
@ -1169,8 +1171,8 @@ github.com/kvtools/consul v1.0.2 h1:ltPgs4Ld09Xaa7zrOJ/TewBYKAsr11/LRFpErdkb8AA=
github.com/kvtools/consul v1.0.2/go.mod h1:bFnzfGJ5ZIRRXCBGBmwhJlLdEWOlrjOcS1WjyAQzaJA=
github.com/kvtools/etcdv3 v1.0.2 h1:EB0mAtzqe1folE7m7Q6wnCXcGwaOmrYmsVmF3hNsTKI=
github.com/kvtools/etcdv3 v1.0.2/go.mod h1:Xr6DbwqjuCEcXAIWmXxw0DX+N5BhuvablXgN90XeqMM=
github.com/kvtools/redis v1.0.2 h1:D3GjGGtssJF2w8mniWtIxcT/YX9YnRc4jNCm0hrVygQ=
github.com/kvtools/redis v1.0.2/go.mod h1:wuUNwwKOHi2TYxDxj1sGF74Jdg0jywydnatXtnOR3hA=
github.com/kvtools/redis v1.1.0 h1:nXRAyh2nsaWiJyrX449/qHMc3SvGUqRqRXcrA/MplEo=
github.com/kvtools/redis v1.1.0/go.mod h1:cqg3esJOIYMQ1qy5LVIbPZz9kuiBBcFREP2N5b9+Dn0=
github.com/kvtools/valkeyrie v1.0.0 h1:LAITop2wPoYCMitR24GZZsW0b57hmI+ePD18VRTtOf0=
github.com/kvtools/valkeyrie v1.0.0/go.mod h1:bDi/OdhJCSbGPMsCgUQl881yuEweKCSItAtTBI+ZjpU=
github.com/kvtools/zookeeper v1.0.2 h1:uK0CzQa+mtKGxDDH+DeqXo2HC1Kx4hWXZ7pX/zS4aTo=
@ -1586,6 +1588,8 @@ github.com/rancher/go-rancher-metadata v0.0.0-20200311180630-7f4c936a06ac/go.mod
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/redis/go-redis/v9 v9.2.1 h1:WlYJg71ODF0dVspZZCpYmoF1+U1Jjk9Rwd7pq6QmlCg=
github.com/redis/go-redis/v9 v9.2.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
github.com/richardartoul/molecule v1.0.1-0.20221107223329-32cfee06a052 h1:Qp27Idfgi6ACvFQat5+VJvlYToylpM/hcyLBI3WaKPA=
github.com/richardartoul/molecule v1.0.1-0.20221107223329-32cfee06a052/go.mod h1:uvX/8buq8uVeiZiFht+0lqSLBHF+uGV8BrTv8W/SIwk=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=

View file

@ -0,0 +1,19 @@
[global]
checkNewVersion = false
sendAnonymousUsage = false
[log]
level = "DEBUG"
[entryPoints.web]
address = ":8000"
[api]
insecure = true
[providers.redis]
rootKey = "traefik"
endpoints = ["{{ .RedisAddress }}"]
[providers.redis.sentinel]
masterName = "mymaster"

View file

@ -4,12 +4,18 @@ import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io/fs"
"net"
"net/http"
"os"
"path/filepath"
"strings"
"text/template"
"time"
"github.com/fatih/structs"
"github.com/go-check/check"
"github.com/kvtools/redis"
"github.com/kvtools/valkeyrie"
@ -23,24 +29,36 @@ import (
// Redis test suites.
type RedisSuite struct {
BaseSuite
kvClient store.Store
redisAddr string
kvClient store.Store
redisEndpoints []string
}
func (s *RedisSuite) TearDownSuite(c *check.C) {
s.composeDown(c)
for _, filename := range []string{"sentinel1.conf", "sentinel2.conf", "sentinel3.conf"} {
err := os.Remove(filepath.Join(".", "resources", "compose", "config", filename))
if err != nil && !errors.Is(err, fs.ErrNotExist) {
c.Fatal("unable to clean configuration file for sentinel: ", err)
}
}
}
func (s *RedisSuite) setupStore(c *check.C) {
s.createComposeProject(c, "redis")
s.composeUp(c)
s.redisAddr = net.JoinHostPort(s.getComposeServiceIP(c, "redis"), "6379")
s.redisEndpoints = []string{}
s.redisEndpoints = append(s.redisEndpoints, net.JoinHostPort(s.getComposeServiceIP(c, "redis"), "6379"))
kv, err := valkeyrie.NewStore(
context.Background(),
redis.StoreName,
[]string{s.redisAddr},
s.redisEndpoints,
&redis.Config{},
)
if err != nil {
c.Fatal("Cannot create store redis")
c.Fatal("Cannot create store redis: ", err)
}
s.kvClient = kv
@ -52,7 +70,173 @@ func (s *RedisSuite) setupStore(c *check.C) {
func (s *RedisSuite) TestSimpleConfiguration(c *check.C) {
s.setupStore(c)
file := s.adaptFile(c, "fixtures/redis/simple.toml", struct{ RedisAddress string }{s.redisAddr})
file := s.adaptFile(c, "fixtures/redis/simple.toml", struct{ RedisAddress string }{
RedisAddress: strings.Join(s.redisEndpoints, ","),
})
defer os.Remove(file)
data := map[string]string{
"traefik/http/routers/Router0/entryPoints/0": "web",
"traefik/http/routers/Router0/middlewares/0": "compressor",
"traefik/http/routers/Router0/middlewares/1": "striper",
"traefik/http/routers/Router0/service": "simplesvc",
"traefik/http/routers/Router0/rule": "Host(`kv1.localhost`)",
"traefik/http/routers/Router0/priority": "42",
"traefik/http/routers/Router0/tls": "true",
"traefik/http/routers/Router1/rule": "Host(`kv2.localhost`)",
"traefik/http/routers/Router1/priority": "42",
"traefik/http/routers/Router1/tls/domains/0/main": "aaa.localhost",
"traefik/http/routers/Router1/tls/domains/0/sans/0": "aaa.aaa.localhost",
"traefik/http/routers/Router1/tls/domains/0/sans/1": "bbb.aaa.localhost",
"traefik/http/routers/Router1/tls/domains/1/main": "bbb.localhost",
"traefik/http/routers/Router1/tls/domains/1/sans/0": "aaa.bbb.localhost",
"traefik/http/routers/Router1/tls/domains/1/sans/1": "bbb.bbb.localhost",
"traefik/http/routers/Router1/entryPoints/0": "web",
"traefik/http/routers/Router1/service": "simplesvc",
"traefik/http/services/simplesvc/loadBalancer/servers/0/url": "http://10.0.1.1:8888",
"traefik/http/services/simplesvc/loadBalancer/servers/1/url": "http://10.0.1.1:8889",
"traefik/http/services/srvcA/loadBalancer/servers/0/url": "http://10.0.1.2:8888",
"traefik/http/services/srvcA/loadBalancer/servers/1/url": "http://10.0.1.2:8889",
"traefik/http/services/srvcB/loadBalancer/servers/0/url": "http://10.0.1.3:8888",
"traefik/http/services/srvcB/loadBalancer/servers/1/url": "http://10.0.1.3:8889",
"traefik/http/services/mirror/mirroring/service": "simplesvc",
"traefik/http/services/mirror/mirroring/mirrors/0/name": "srvcA",
"traefik/http/services/mirror/mirroring/mirrors/0/percent": "42",
"traefik/http/services/mirror/mirroring/mirrors/1/name": "srvcB",
"traefik/http/services/mirror/mirroring/mirrors/1/percent": "42",
"traefik/http/services/Service03/weighted/services/0/name": "srvcA",
"traefik/http/services/Service03/weighted/services/0/weight": "42",
"traefik/http/services/Service03/weighted/services/1/name": "srvcB",
"traefik/http/services/Service03/weighted/services/1/weight": "42",
"traefik/http/middlewares/compressor/compress": "true",
"traefik/http/middlewares/striper/stripPrefix/prefixes/0": "foo",
"traefik/http/middlewares/striper/stripPrefix/prefixes/1": "bar",
"traefik/http/middlewares/striper/stripPrefix/forceSlash": "true",
}
for k, v := range data {
err := s.kvClient.Put(context.Background(), k, []byte(v), nil)
c.Assert(err, checker.IsNil)
}
cmd, display := s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
// wait for traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 2*time.Second,
try.BodyContains(`"striper@redis":`, `"compressor@redis":`, `"srvcA@redis":`, `"srvcB@redis":`),
)
c.Assert(err, checker.IsNil)
resp, err := http.Get("http://127.0.0.1:8080/api/rawdata")
c.Assert(err, checker.IsNil)
var obtained api.RunTimeRepresentation
err = json.NewDecoder(resp.Body).Decode(&obtained)
c.Assert(err, checker.IsNil)
got, err := json.MarshalIndent(obtained, "", " ")
c.Assert(err, checker.IsNil)
expectedJSON := filepath.FromSlash("testdata/rawdata-redis.json")
if *updateExpected {
err = os.WriteFile(expectedJSON, got, 0o666)
c.Assert(err, checker.IsNil)
}
expected, err := os.ReadFile(expectedJSON)
c.Assert(err, checker.IsNil)
if !bytes.Equal(expected, got) {
diff := difflib.UnifiedDiff{
FromFile: "Expected",
A: difflib.SplitLines(string(expected)),
ToFile: "Got",
B: difflib.SplitLines(string(got)),
Context: 3,
}
text, err := difflib.GetUnifiedDiffString(diff)
c.Assert(err, checker.IsNil)
c.Error(text)
}
}
func (s *RedisSuite) setupSentinelStore(c *check.C) {
s.setupSentinelConfiguration(c, []string{"26379", "36379", "46379"})
s.createComposeProject(c, "redis_sentinel")
s.composeUp(c)
s.redisEndpoints = []string{
net.JoinHostPort(s.getComposeServiceIP(c, "sentinel1"), "26379"),
net.JoinHostPort(s.getComposeServiceIP(c, "sentinel2"), "36379"),
net.JoinHostPort(s.getComposeServiceIP(c, "sentinel3"), "46379"),
}
kv, err := valkeyrie.NewStore(
context.Background(),
redis.StoreName,
s.redisEndpoints,
&redis.Config{
Sentinel: &redis.Sentinel{
MasterName: "mymaster",
},
},
)
if err != nil {
c.Fatal("Cannot create store redis sentinel")
}
s.kvClient = kv
// wait for redis
err = try.Do(60*time.Second, try.KVExists(kv, "test"))
c.Assert(err, checker.IsNil)
}
func (s *RedisSuite) setupSentinelConfiguration(c *check.C, ports []string) {
for i, port := range ports {
templateValue := struct{ SentinelPort string }{SentinelPort: port}
// Load file
templateFile := "resources/compose/config/sentinel_template.conf"
tmpl, err := template.ParseFiles(templateFile)
c.Assert(err, checker.IsNil)
folder, prefix := filepath.Split(templateFile)
fileName := fmt.Sprintf("%s/sentinel%d.conf", folder, i+1)
tmpFile, err := os.Create(fileName)
c.Assert(err, checker.IsNil)
defer tmpFile.Close()
model := structs.Map(templateValue)
model["SelfFilename"] = tmpFile.Name()
err = tmpl.ExecuteTemplate(tmpFile, prefix, model)
c.Assert(err, checker.IsNil)
err = tmpFile.Sync()
c.Assert(err, checker.IsNil)
}
}
func (s *RedisSuite) TestSentinelConfiguration(c *check.C) {
s.setupSentinelStore(c)
file := s.adaptFile(c, "fixtures/redis/sentinel.toml", struct{ RedisAddress string }{
RedisAddress: strings.Join(s.redisEndpoints, `","`),
})
defer os.Remove(file)
data := map[string]string{

View file

@ -0,0 +1,5 @@
port {{ .SentinelPort }}
dir "/tmp"
sentinel resolve-hostnames yes
sentinel monitor mymaster master 6380 2
sentinel deny-scripts-reconfig yes

View file

@ -0,0 +1,61 @@
version: "3.8"
services:
master:
image: redis
container_name: redis-master
command: redis-server --port 6380
ports:
- 6380:6380
healthcheck:
test: redis-cli -p 6380 ping
node1:
image: redis
container_name: redis-node-1
ports:
- 6381:6381
command: redis-server --port 6381 --slaveof redis-master 6380
healthcheck:
test: redis-cli -p 6381 ping
node2:
image: redis
container_name: redis-node-2
ports:
- 6382:6382
command: redis-server --port 6382 --slaveof redis-master 6380
healthcheck:
test: redis-cli -p 6382 ping
sentinel1:
image: redis
container_name: redis-sentinel-1
ports:
- 26379:26379
command: redis-sentinel /usr/local/etc/redis/conf/sentinel1.conf
healthcheck:
test: redis-cli -p 26379 ping
volumes:
- ./resources/compose/config:/usr/local/etc/redis/conf
sentinel2:
image: redis
container_name: redis-sentinel-2
ports:
- 36379:26379
command: redis-sentinel /usr/local/etc/redis/conf/sentinel2.conf
healthcheck:
test: redis-cli -p 36379 ping
volumes:
- ./resources/compose/config:/usr/local/etc/redis/conf
sentinel3:
image: redis
container_name: redis-sentinel-3
ports:
- 46379:26379
command: redis-sentinel /usr/local/etc/redis/conf/sentinel3.conf
healthcheck:
test: redis-cli -p 46379 ping
volumes:
- ./resources/compose/config:/usr/local/etc/redis/conf
networks:
default:
name: traefik-test-network
external: true

View file

@ -2,6 +2,7 @@ package redis
import (
"context"
"errors"
"fmt"
"github.com/kvtools/redis"
@ -20,6 +21,20 @@ type Provider struct {
Username string `description:"Username for authentication." json:"username,omitempty" toml:"username,omitempty" yaml:"username,omitempty" loggable:"false"`
Password string `description:"Password for authentication." json:"password,omitempty" toml:"password,omitempty" yaml:"password,omitempty" loggable:"false"`
DB int `description:"Database to be selected after connecting to the server." json:"db,omitempty" toml:"db,omitempty" yaml:"db,omitempty"`
Sentinel *Sentinel `description:"Enable Sentinel support." json:"sentinel,omitempty" toml:"sentinel,omitempty" yaml:"sentinel,omitempty"`
}
// Sentinel holds the Redis Sentinel configuration.
type Sentinel struct {
MasterName string `description:"Name of the master." json:"masterName,omitempty" toml:"masterName,omitempty" yaml:"masterName,omitempty" export:"true"`
Username string `description:"Username for Sentinel authentication." json:"username,omitempty" toml:"username,omitempty" yaml:"username,omitempty" export:"true"`
Password string `description:"Password for Sentinel authentication." json:"password,omitempty" toml:"password,omitempty" yaml:"password,omitempty" export:"true"`
LatencyStrategy bool `description:"Defines whether to route commands to the closest master or replica nodes (mutually exclusive with RandomStrategy and ReplicaStrategy)." json:"latencyStrategy,omitempty" toml:"latencyStrategy,omitempty" yaml:"latencyStrategy,omitempty" export:"true"`
RandomStrategy bool `description:"Defines whether to route commands randomly to master or replica nodes (mutually exclusive with LatencyStrategy and ReplicaStrategy)." json:"randomStrategy,omitempty" toml:"randomStrategy,omitempty" yaml:"randomStrategy,omitempty" export:"true"`
ReplicaStrategy bool `description:"Defines whether to route all commands to replica nodes (mutually exclusive with LatencyStrategy and RandomStrategy)." json:"replicaStrategy,omitempty" toml:"replicaStrategy,omitempty" yaml:"replicaStrategy,omitempty" export:"true"`
UseDisconnectedReplicas bool `description:"Use replicas disconnected with master when cannot get connected replicas." json:"useDisconnectedReplicas,omitempty" toml:"useDisconnectedReplicas,omitempty" yaml:"useDisconnectedReplicas,omitempty" export:"true"`
}
// SetDefaults sets the default values.
@ -44,5 +59,26 @@ func (p *Provider) Init() error {
}
}
if p.Sentinel != nil {
switch {
case p.Sentinel.LatencyStrategy && !(p.Sentinel.RandomStrategy || p.Sentinel.ReplicaStrategy):
case p.Sentinel.RandomStrategy && !(p.Sentinel.LatencyStrategy || p.Sentinel.ReplicaStrategy):
case p.Sentinel.ReplicaStrategy && !(p.Sentinel.RandomStrategy || p.Sentinel.LatencyStrategy):
return errors.New("latencyStrategy, randomStrategy and replicaStrategy options are mutually exclusive, please use only one of those options")
}
clusterClient := p.Sentinel.LatencyStrategy || p.Sentinel.RandomStrategy
config.Sentinel = &redis.Sentinel{
MasterName: p.Sentinel.MasterName,
Username: p.Sentinel.Username,
Password: p.Sentinel.Password,
ClusterClient: clusterClient,
RouteByLatency: p.Sentinel.LatencyStrategy,
RouteRandomly: p.Sentinel.RandomStrategy,
ReplicaOnly: p.Sentinel.ReplicaStrategy,
UseDisconnectedReplicas: p.Sentinel.UseDisconnectedReplicas,
}
}
return p.Provider.Init(redis.StoreName, "redis", config)
}