feat: Redis cluster support.#6295
Open
daanvinken wants to merge 4 commits intorunatlantis:mainfrom
Open
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Adds Redis Cluster support and improves operational safety by enhancing health checks and making apply-lock enforcement fail closed when the lock backend is unreachable.
Changes:
- Introduces Redis
Config+NewWithConfig()to auto-select single-node vs cluster client and adds username/cluster address configuration. - Updates lock key migration to use
SCAN(cluster-compatible) and addsDatabase.Ping()for/healthz503 behavior. - Adds Kubernetes service-link env var sanitization to avoid
ATLANTIS_REDIS_PORT=tcp://...parsing issues and updates apply-lock error behavior/tests.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| server/user_config.go | Adds config fields for Redis username and cluster addresses. |
| server/server.go | Wires Redis config into server creation and makes /healthz depend on DB reachability. |
| server/events/apply_command_runner.go | Fails closed on apply-lock check errors and posts user-facing PR comment. |
| server/events/apply_command_runner_test.go | Updates expectations for fail-closed apply-lock behavior. |
| server/core/redis/redis.go | Adds Config, cluster support, SCAN migration, Ping(), and Cmdable client abstraction. |
| server/core/redis/redis_test.go | Adds unit tests for NewWithConfig behavior and edge cases. |
| server/core/db/db.go | Extends Database interface with Ping(). |
| server/core/db/mocks/mock_database.go | Updates mock to implement Ping(). |
| server/core/boltdb/boltdb.go | Implements Ping() for BoltDB. |
| cmd/server.go | Adds flags for Redis username/cluster addresses and sanitizes K8s service-link env collisions. |
| cmd/server_test.go | Adds tests for Kubernetes service-link sanitization. |
| runatlantis.io/docs/server-configuration.md | Documents new Redis username and cluster address options. |
1451213 to
df9221b
Compare
daanvinken
commented
Mar 9, 2026
| // and returns 503 if the database is unreachable. | ||
| func (s *Server) Healthz(w http.ResponseWriter, _ *http.Request) { | ||
| w.Header().Set("Content-Type", "application/json") | ||
| if err := s.database.Ping(); err != nil { |
Author
There was a problem hiding this comment.
Note that this also works for BoltdB and performs a simple read.
e6213a5 to
1d19bc8
Compare
Author
|
@jamengual @javking07 do you mind having a look here? |
Contributor
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 12 out of 12 changed files in this pull request and generated 7 comments.
Comments suppressed due to low confidence (1)
server/server.go:535
- The
locking-db-typeswitch has nodefaultcase, so an unsupported value leavesdatabasenil and the server continues initializing. With the new/healthzbehavior this can now cause a nil dereference (and even without it, later DB usage will be unsafe). Add adefaultthat returns an error for unknown db types (or otherwise enforce thatdatabaseis always initialized).
switch dbtype := userConfig.LockingDBType; dbtype {
case "redis":
var clusterAddrs []string
if userConfig.RedisClusterAddresses != "" {
rawClusterAddrs := strings.Split(userConfig.RedisClusterAddresses, ",")
for _, addr := range rawClusterAddrs {
trimmed := strings.TrimSpace(addr)
if trimmed == "" {
continue
}
clusterAddrs = append(clusterAddrs, trimmed)
}
}
switch {
case len(clusterAddrs) > 0:
logger.Info(fmt.Sprintf("Utilizing Redis DB in cluster mode, addresses: %s", userConfig.RedisClusterAddresses))
default:
logger.Info(fmt.Sprintf("Utilizing Redis DB in single-node mode, host: %s, port: %d", userConfig.RedisHost, userConfig.RedisPort))
}
database, err = redis.NewWithConfig(redis.Config{
Hostname: userConfig.RedisHost,
Port: userConfig.RedisPort,
Password: userConfig.RedisPassword,
Username: userConfig.RedisUsername,
TLSEnabled: userConfig.RedisTLSEnabled,
InsecureSkipVerify: userConfig.RedisInsecureSkipVerify,
DB: userConfig.RedisDB,
ClusterAddresses: clusterAddrs,
})
if err != nil {
return nil, err
}
case "boltdb":
logger.Info("Utilizing BoltDB")
database, err = boltdb.New(userConfig.DataDir)
if err != nil {
return nil, err
}
}
1d19bc8 to
748f230
Compare
Add support for Redis Cluster deployments alongside the existing single-node mode. The mode is auto-detected from configuration: when --redis-cluster-addresses is set, cluster mode is used. - Introduce Config struct and NewWithConfig() constructor - Deprecate old New() function (wraps NewWithConfig) - Change RedisDB.client type to redis.Cmdable interface - Replace Keys() with Scan() in lock key migration for cluster compat - Add --redis-username flag for ACL-based authentication - Add --redis-cluster-addresses flag for cluster node addresses - Update server-configuration.md with new flag documentation Signed-off-by: Daan Vinken <daanvinken@tythus.com>
Kubernetes automatically creates environment variables for services in the same namespace. When a service named e.g. "atlantis-redis" exists, K8s sets ATLANTIS_REDIS_PORT=tcp://10.x.x.x:6379 which viper picks up via the ATLANTIS_ env prefix and fails to parse as an integer. Detect these URL-formatted values on int flags before unmarshalling and reset them to their defaults. Signed-off-by: Daan Vinken <daanvinken@tythus.com>
Signed-off-by: Daan Vinken <daanvinken@tythus.com>
- Trim whitespace from comma-separated cluster addresses - Check Set/Del errors during lock key migration - Use interface assertion for Close() instead of type switch - Include connection address in ping error message - Add 5s timeout to Ping() context - Log sanitized cluster addresses instead of raw input - TrimSpace in NewWithConfig address filter - Fix docs: redis-password is optional Signed-off-by: Daan Vinken <daanvinken@tythus.com>
748f230 to
e62e255
Compare
4 tasks
adkafka
approved these changes
Mar 26, 2026
Contributor
|
This would be really wonderful to be able to get horizontal autoscaling going. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
what
Configstruct +NewWithConfig()constructor, deprecate oldNew()RedisDB.clienttoredis.Cmdableinterface (common toClientandClusterClient)Keys()withScan()in lock key migration (Scan fans out across cluster nodes, Keys doesn't)--redis-usernameflag for Redis 6+ ACL auth--redis-cluster-addressesflag accepting comma-separated node addresses/healthznow returns 503 when database is unreachable (addedPing()toDatabaseinterface)ATLANTIS_REDIS_PORT=tcp://...parsed as int)why
--redis-deploymentflag)Keys()is incompatible with Redis Cluster — only queries the connected node.Scan()via go-redisClusterClientfans out across all nodes.Cmdableinterface is the narrowest type that covers both client types. Tradeoff:Close()requires a type switch sinceCmdabledoesn't expose it.ATLANTIS_REDIS_PORT=tcp://10.x.x.x:6379for services in the same namespace; Viper picks it up via theATLANTIS_prefix and fails to parse as int.tests
NewWithConfig: single-node, cluster, empty-address edge casessanitizeKubernetesServiceLinks: URL detection and reset to defaultsTestApplyCommandRunner_IsLockedto verify fail-closed behaviorWhen Redis cluster down:

New logging:
references