WIP This project is only for learning purposes.
Redis-Cluster Operator.
The redis cluster is composed by:
- redis-cluster statefulset. The data plane.
- Operator. A software operator that configures the cluster.
Steps:
For each node, execute cluster meet among all IPs.
SERVICE=redis-cluster
getent hosts $SERVICE
# cartesian product IPs x IPs
redis-cli -h $IP1 -p $REDIS_PORT cluster meet $IP2 $REDIS_PORT
# sleep to allow configuration propagate
sleep $ClusterHealingTimeSeconds
# for each redis node:
redis-cli -h $IP -p $PORT cluster nodes
# Get the IPs and check that are correct...
# if they are not do the meet, for all pods
redis-cli -h $IP1 -p $PORT cluster meet $IP2
There are 16384 slots, they have to be shared among nodes.
Bash example with very slow implementation:
for slot in {0..5461}; do redis-cli -p 7000 CLUSTER ADDSLOTS $slot > /dev/null; done;
for slot in {5462..10923}; do redis-cli -p 7001 CLUSTER ADDSLOTS $slot > /dev/null; done;
for slot in {10924..16383}; do redis-cli -p 7002 CLUSTER ADDSLOTS $slot > /dev/null; done;
Better implementation:
redis-cli -p 7000 CLUSTER ADDSLOTS 0 1 2 3 4 5 ... 5461
I don’t assign slaves because I suppose --replicas =
0=, but if it is needed, note that slaves are assigned using
“cluster replicate”. Example:
# Not implemented
# redis-cli -c -p <port_of_slave> cluster replicate <node_id_of_its_master>
redis-cli -p 7003 cluster replicate 0db8943cccb9b8c3963af78019baf8d2db827f14
Among the golang libraries that have the necessary functionalities, I compare the most used against the redis.io recommeded:
- go-redis: https://github.com/go-redis/redis
- radix: https://github.com/mediocregopher/radix
Radix pros:
- Recommended by redis.io.
- Better performance.
- Minimalistic approach. You write the command text.
Example:
client, err := redis.Dial("tcp", "localhost:6379")
if err != nil {
panic(err)
}
foo, err := client.Cmd("GET", "foo").Str()
if err != nil {
panic(err)
}
// Checking Err field directly
err = client.Cmd("SET", "foo", "bar", "EX", 3600).Err
if err != nil {
// handle err
}
Go-redis pros:
- More github users and activity.
- Exposes Redis commands as function calls. Avoiding the need to parse commands at run-time.
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // no password set
DB: 0, // use default DB
})
err := rdb.Set(ctx, "key", "value", 0).Err()
if err != nil {
panic(err)
}
Election: I choose go-redis/redis
because a operator does not need redis performance and simplicity andclarity in
the code is important.
Lot of examples, but that assign redis slots only this one:
Links:
KubeDB uses PodDisruptionBudget to ensure that majority of these cluster replicas are available during voluntary disruptions so that quorum is maintained and no data loss is occurred.
https://kubedb.com (backup, clone)
https://kubedb.com/docs/v2020.11.12/guides/redis/
An important concept:
RedisVersion CR: https://kubedb.com/docs/v2020.11.12/guides/redis/concepts/catalog/
The redis part: https://github.com/kubedb/redis/
- Install
All the commands I use to build the operator.
Clean all:
OP=redis-operator
OP_KIND=Redis
OP_GROUP=redis
OPERATOR_IMAGE_REPO=redisoperator
Clean all:
echo "Warning! deleting all!"
# rm -rf $HOME/src/$OP
# OP is the operator name
mkdir -p $HOME/src/$OP
cd $HOME/src/$OP
operator-sdk init --domain=danieldorado.github.io --repo=github.com/danieldorado/$OP
Create CRD and Controller.
main.go
initializes the Manager.
operator-sdk create api --group $OP_GROUP --version v1alpha1 --kind $OP_KIND --resource=true --controller=true
Fill scaffolds.
https://sdk.operatorframework.io/docs/building-operators/golang/tutorial/#define-the-api
Properties:
- Size: redis nodes number.
Invoke the controller-gen
to generate *.deepcopy.go
that implements runtime.Object
interface:
make generate
Generate CRD manifests:
make manifests
Manifest: config/crd/bases/redis.danieldorado.github.io_redis.yaml
Use OpenAPI to validate: https://sdk.operatorframework.io/docs/building-operators/golang/tutorial/#openapi-validation
Kubebuilder is a prerequisite.
make docker-build docker-push IMG=danieladf/$OPERATOR_IMAGE_REPO:0.0.2
Create and edit the role patch: config/default/role_patch.yml patch config/rbac/role.yaml
Alternatively to deploy you can run local: make run
make install
make deploy IMG=danieladf/$OPERATOR_IMAGE_REPO:0.0.2
- Deployed in:
namespace/redis-operator-system created
oc project default
oc apply -f config/samples/redis_v1alpha1_redis.yaml
Clean all:
make uninstall
oc delete project/redis-operator-system
Controller test:
go test ./controllers/ -v -ginkgo.v
Using envtest with ginkgo and gomega.
- All nodes deleted at same time.
import (
"github.com/operator-framework/operator-lib/status"
)
type MyAppStatus struct {
// Conditions represent the latest available observations of an object's state
Conditions status.Conditions `json:"conditions"`
}
In-memory expectations.
Guarantees.
Does not work because of mutating fields added. Use a annotation hash with our fields.
hash := HashObject(expected)
expected.Annotations[ResourceHash] = hash
Other redis controllers that we need for the redis operator.
Examples:
- Backup controller
controller/redis_backup_controller.go
- Restore controller
controller/redis_backup_controller.go