Deploy Redis Operator, Redis Replication Cluster and Redis Sentinel
Deploy the Redis operator and replication cluster
Genestack primarily makes use of the popular opensource Redis in-memory database to support various services that utilize Taskflow and Jobboard functionality. One such service is Octavia, which uses Redis to track tasks across the Octavia cluster in a HA fashion ensuring that tasks can still be completed in the event of a partial outage of the Octavia system.
In order to take advantage of the Redis system in a HA way we deploy Redis Replication and Redis Sentinel to handle the needs of a clustered, HA Redis deployment.
Tip
As noted in the Sentinel Docs we must deploy the Redis Operator and Replication cluster prior to deploying Sentinel. Below are the steps to achieve this.
If cluster_name was anything other than cluster.local you should pass that as a parameter to the installer
Run the redis-operator deployment Script /opt/genestack/bin/install-redis-operator.sh You can include cluster_name paramater from the output of $CLUSTER_NAME. If no paramaters are provided, the system will deploy with cluster.local as the cluster name.
#!/bin/bash
# Description: Fetches the version for SERVICE_NAME_DEFAULT from the specified
# YAML file and executes a helm upgrade/install command with dynamic values files.
# Disable SC2124 (unused array), SC2145 (array expansion issue), SC2294 (eval)
# shellcheck disable=SC2124,SC2145,SC2294
# Service
SERVICE_NAME_DEFAULT="redis-operator"
SERVICE_NAMESPACE="redis-systems"
# Helm
HELM_REPO_NAME_DEFAULT="ot-helm"
HELM_REPO_URL_DEFAULT="https://ot-container-kit.github.io/helm-charts"
# Base directories provided by the environment
GENESTACK_BASE_DIR="${GENESTACK_BASE_DIR:-/opt/genestack}"
GENESTACK_OVERRIDES_DIR="${GENESTACK_OVERRIDES_DIR:-/etc/genestack}"
# Define service-specific override directories based on the framework
SERVICE_BASE_OVERRIDES="${GENESTACK_BASE_DIR}/base-helm-configs/${SERVICE_NAME_DEFAULT}"
SERVICE_CUSTOM_OVERRIDES="${GENESTACK_OVERRIDES_DIR}/helm-configs/${SERVICE_NAME_DEFAULT}"
# Read the desired chart version from VERSION_FILE
VERSION_FILE="${GENESTACK_OVERRIDES_DIR}/helm-chart-versions.yaml"
# Read the desired chart version from VERSION_FILE
if [ ! -f "$VERSION_FILE" ]; then
echo "Error: helm-chart-versions.yaml not found at $VERSION_FILE" >&2
exit 1
fi
# Extract version dynamically using the SERVICE_NAME_DEFAULT variable
SERVICE_VERSION=$(grep "^[[:space:]]*${SERVICE_NAME_DEFAULT}:" "$VERSION_FILE" | sed "s/.*${SERVICE_NAME_DEFAULT}: *//")
if [ -z "$SERVICE_VERSION" ]; then
echo "Error: Could not extract version for '$SERVICE_NAME_DEFAULT' from $VERSION_FILE" >&2
exit 1
fi
echo "Found version for $SERVICE_NAME_DEFAULT: $SERVICE_VERSION"
# Load chart metadata from custom override YAML if defined
for yaml_file in "${SERVICE_CUSTOM_OVERRIDES}"/*.yaml; do
if [ -f "$yaml_file" ]; then
HELM_REPO_URL=$(yq eval '.chart.repo_url // ""' "$yaml_file")
HELM_REPO_NAME=$(yq eval '.chart.repo_name // ""' "$yaml_file")
SERVICE_NAME=$(yq eval '.chart.service_name // ""' "$yaml_file")
break # use the first match and stop
fi
done
# Fallback to defaults if variables not set
: "${HELM_REPO_URL:=$HELM_REPO_URL_DEFAULT}"
: "${HELM_REPO_NAME:=$HELM_REPO_NAME_DEFAULT}"
: "${SERVICE_NAME:=$SERVICE_NAME_DEFAULT}"
# Determine Helm chart path
if [[ "$HELM_REPO_URL" == oci://* ]]; then
# OCI registry path
HELM_CHART_PATH="$HELM_REPO_URL/$HELM_REPO_NAME/$SERVICE_NAME"
else
# --- Helm Repository and Execution ---
helm repo add "$HELM_REPO_NAME" "$HELM_REPO_URL"
helm repo update
HELM_CHART_PATH="$HELM_REPO_NAME/$SERVICE_NAME"
fi
# Debug output
echo "[DEBUG] HELM_REPO_URL=$HELM_REPO_URL"
echo "[DEBUG] HELM_REPO_NAME=$HELM_REPO_NAME"
echo "[DEBUG] SERVICE_NAME=$SERVICE_NAME"
echo "[DEBUG] HELM_CHART_PATH=$HELM_CHART_PATH"
# Prepare an array to collect -f arguments
overrides_args=()
# Include all YAML files from the BASE configuration directory
# NOTE: Files in this directory are included first.
if [[ -d "$SERVICE_BASE_OVERRIDES" ]]; then
echo "Including base overrides from directory: $SERVICE_BASE_OVERRIDES"
for file in "$SERVICE_BASE_OVERRIDES"/*.yaml; do
# Check that there is at least one match
if [[ -e "$file" ]]; then
echo " - $file"
overrides_args+=("-f" "$file")
fi
done
else
echo "Warning: Base override directory not found: $SERVICE_BASE_OVERRIDES"
fi
# Include all YAML files from the custom SERVICE configuration directory
# NOTE: Files here have the highest precedence.
if [[ -d "$SERVICE_CUSTOM_OVERRIDES" ]]; then
echo "Including overrides from service config directory: $SERVICE_CUSTOM_OVERRIDES"
for file in "$SERVICE_CUSTOM_OVERRIDES"/*.yaml; do
if [[ -e "$file" ]]; then
echo " - $file"
overrides_args+=("-f" "$file")
fi
done
else
echo "Warning: Service overrides directory not found: $SERVICE_CUSTOM_OVERRIDES"
fi
echo
# Collect all --set arguments, executing commands and quoting safely
set_args=()
helm_command=(
helm upgrade --install "$SERVICE_NAME_DEFAULT" "$HELM_CHART_PATH"
--version "${SERVICE_VERSION}"
--namespace="$SERVICE_NAMESPACE"
--timeout 120m
--create-namespace
"${overrides_args[@]}"
"${set_args[@]}"
# Post-renderer configuration
--post-renderer "$GENESTACK_OVERRIDES_DIR/kustomize/kustomize.sh"
--post-renderer-args "$SERVICE_NAME_DEFAULT/overlay"
"$@"
)
echo "Executing Helm command (arguments are quoted safely):"
printf '%q ' "${helm_command[@]}"
echo
# Execute the command directly from the array
"${helm_command[@]}"
Run the redis-replication deployment Script /opt/genestack/bin/install-redis-replication.sh.
#!/bin/bash
# Description: Fetches the version for SERVICE_NAME_DEFAULT from the specified
# YAML file and executes a helm upgrade/install command with dynamic values files.
# Disable SC2124 (unused array), SC2145 (array expansion issue), SC2294 (eval)
# shellcheck disable=SC2124,SC2145,SC2294
# Service
SERVICE_NAME_DEFAULT="redis-replication"
SERVICE_NAMESPACE="redis-systems"
# Helm
HELM_REPO_NAME_DEFAULT="ot-helm"
HELM_REPO_URL_DEFAULT="https://ot-container-kit.github.io/helm-charts"
# Base directories provided by the environment
GENESTACK_BASE_DIR="${GENESTACK_BASE_DIR:-/opt/genestack}"
GENESTACK_OVERRIDES_DIR="${GENESTACK_OVERRIDES_DIR:-/etc/genestack}"
# Define service-specific override directories based on the framework
SERVICE_BASE_OVERRIDES="${GENESTACK_BASE_DIR}/base-helm-configs/${SERVICE_NAME_DEFAULT}"
SERVICE_CUSTOM_OVERRIDES="${GENESTACK_OVERRIDES_DIR}/helm-configs/${SERVICE_NAME_DEFAULT}"
# Read the desired chart version from VERSION_FILE
VERSION_FILE="${GENESTACK_OVERRIDES_DIR}/helm-chart-versions.yaml"
# Read the desired chart version from VERSION_FILE
if [ ! -f "$VERSION_FILE" ]; then
echo "Error: helm-chart-versions.yaml not found at $VERSION_FILE" >&2
exit 1
fi
# Extract version dynamically using the SERVICE_NAME_DEFAULT variable
SERVICE_VERSION=$(grep "^[[:space:]]*${SERVICE_NAME_DEFAULT}:" "$VERSION_FILE" | sed "s/.*${SERVICE_NAME_DEFAULT}: *//")
if [ -z "$SERVICE_VERSION" ]; then
echo "Error: Could not extract version for '$SERVICE_NAME_DEFAULT' from $VERSION_FILE" >&2
exit 1
fi
echo "Found version for $SERVICE_NAME_DEFAULT: $SERVICE_VERSION"
# Load chart metadata from custom override YAML if defined
for yaml_file in "${SERVICE_CUSTOM_OVERRIDES}"/*.yaml; do
if [ -f "$yaml_file" ]; then
HELM_REPO_URL=$(yq eval '.chart.repo_url // ""' "$yaml_file")
HELM_REPO_NAME=$(yq eval '.chart.repo_name // ""' "$yaml_file")
SERVICE_NAME=$(yq eval '.chart.service_name // ""' "$yaml_file")
break # use the first match and stop
fi
done
# Fallback to defaults if variables not set
: "${HELM_REPO_URL:=$HELM_REPO_URL_DEFAULT}"
: "${HELM_REPO_NAME:=$HELM_REPO_NAME_DEFAULT}"
: "${SERVICE_NAME:=$SERVICE_NAME_DEFAULT}"
# Determine Helm chart path
if [[ "$HELM_REPO_URL" == oci://* ]]; then
# OCI registry path
HELM_CHART_PATH="$HELM_REPO_URL/$HELM_REPO_NAME/$SERVICE_NAME"
else
# --- Helm Repository and Execution ---
helm repo add "$HELM_REPO_NAME" "$HELM_REPO_URL"
helm repo update
HELM_CHART_PATH="$HELM_REPO_NAME/$SERVICE_NAME"
fi
# Debug output
echo "[DEBUG] HELM_REPO_URL=$HELM_REPO_URL"
echo "[DEBUG] HELM_REPO_NAME=$HELM_REPO_NAME"
echo "[DEBUG] SERVICE_NAME=$SERVICE_NAME"
echo "[DEBUG] HELM_CHART_PATH=$HELM_CHART_PATH"
# Prepare an array to collect -f arguments
overrides_args=()
# Include all YAML files from the BASE configuration directory
# NOTE: Files in this directory are included first.
if [[ -d "$SERVICE_BASE_OVERRIDES" ]]; then
echo "Including base overrides from directory: $SERVICE_BASE_OVERRIDES"
for file in "$SERVICE_BASE_OVERRIDES"/*.yaml; do
# Check that there is at least one match
if [[ -e "$file" ]]; then
echo " - $file"
overrides_args+=("-f" "$file")
fi
done
else
echo "Warning: Base override directory not found: $SERVICE_BASE_OVERRIDES"
fi
# Include all YAML files from the custom SERVICE configuration directory
# NOTE: Files here have the highest precedence.
if [[ -d "$SERVICE_CUSTOM_OVERRIDES" ]]; then
echo "Including overrides from service config directory: $SERVICE_CUSTOM_OVERRIDES"
for file in "$SERVICE_CUSTOM_OVERRIDES"/*.yaml; do
if [[ -e "$file" ]]; then
echo " - $file"
overrides_args+=("-f" "$file")
fi
done
else
echo "Warning: Service overrides directory not found: $SERVICE_CUSTOM_OVERRIDES"
fi
echo
# Collect all --set arguments, executing commands and quoting safely
set_args=()
helm_command=(
helm upgrade --install "$SERVICE_NAME_DEFAULT" "$HELM_CHART_PATH"
--version "${SERVICE_VERSION}"
--namespace="$SERVICE_NAMESPACE"
--timeout 120m
--create-namespace
"${overrides_args[@]}"
"${set_args[@]}"
# Post-renderer configuration
--post-renderer "$GENESTACK_OVERRIDES_DIR/kustomize/kustomize.sh"
--post-renderer-args "$SERVICE_NAME_DEFAULT/overlay"
"$@"
)
echo "Executing Helm command (arguments are quoted safely):"
printf '%q ' "${helm_command[@]}"
echo
# Execute the command directly from the array
"${helm_command[@]}"
Verify Redis Operator and Replication cluster readiness with the following command
Deploy the Redis Sentinel
Run the redis-sentinel deployment Script /opt/genestack/bin/install-redis-sentinel.sh
#!/bin/bash
# Description: Fetches the version for SERVICE_NAME_DEFAULT from the specified
# YAML file and executes a helm upgrade/install command with dynamic values files.
# Disable SC2124 (unused array), SC2145 (array expansion issue), SC2294 (eval)
# shellcheck disable=SC2124,SC2145,SC2294
# Service
SERVICE_NAME_DEFAULT="redis-sentinel"
SERVICE_NAMESPACE="redis-systems"
# Helm
HELM_REPO_NAME_DEFAULT="ot-helm"
HELM_REPO_URL_DEFAULT="https://ot-container-kit.github.io/helm-charts/"
# Base directories provided by the environment
GENESTACK_BASE_DIR="${GENESTACK_BASE_DIR:-/opt/genestack}"
GENESTACK_OVERRIDES_DIR="${GENESTACK_OVERRIDES_DIR:-/etc/genestack}"
# Define service-specific override directories based on the framework
SERVICE_BASE_OVERRIDES="${GENESTACK_BASE_DIR}/base-helm-configs/${SERVICE_NAME_DEFAULT}"
SERVICE_CUSTOM_OVERRIDES="${GENESTACK_OVERRIDES_DIR}/helm-configs/${SERVICE_NAME_DEFAULT}"
# Read the desired chart version from VERSION_FILE
VERSION_FILE="${GENESTACK_OVERRIDES_DIR}/helm-chart-versions.yaml"
# Read the desired chart version from VERSION_FILE
if [ ! -f "$VERSION_FILE" ]; then
echo "Error: helm-chart-versions.yaml not found at $VERSION_FILE" >&2
exit 1
fi
# Extract version dynamically using the SERVICE_NAME_DEFAULT variable
SERVICE_VERSION=$(grep "^[[:space:]]*${SERVICE_NAME_DEFAULT}:" "$VERSION_FILE" | sed "s/.*${SERVICE_NAME_DEFAULT}: *//")
if [ -z "$SERVICE_VERSION" ]; then
echo "Error: Could not extract version for '$SERVICE_NAME_DEFAULT' from $VERSION_FILE" >&2
exit 1
fi
echo "Found version for $SERVICE_NAME_DEFAULT: $SERVICE_VERSION"
# Load chart metadata from custom override YAML if defined
for yaml_file in "${SERVICE_CUSTOM_OVERRIDES}"/*.yaml; do
if [ -f "$yaml_file" ]; then
HELM_REPO_URL=$(yq eval '.chart.repo_url // ""' "$yaml_file")
HELM_REPO_NAME=$(yq eval '.chart.repo_name // ""' "$yaml_file")
SERVICE_NAME=$(yq eval '.chart.service_name // ""' "$yaml_file")
break # use the first match and stop
fi
done
# Fallback to defaults if variables not set
: "${HELM_REPO_URL:=$HELM_REPO_URL_DEFAULT}"
: "${HELM_REPO_NAME:=$HELM_REPO_NAME_DEFAULT}"
: "${SERVICE_NAME:=$SERVICE_NAME_DEFAULT}"
# Determine Helm chart path
if [[ "$HELM_REPO_URL" == oci://* ]]; then
# OCI registry path
HELM_CHART_PATH="$HELM_REPO_URL/$HELM_REPO_NAME/$SERVICE_NAME"
else
# --- Helm Repository and Execution ---
helm repo add "$HELM_REPO_NAME" "$HELM_REPO_URL"
helm repo update
HELM_CHART_PATH="$HELM_REPO_NAME/$SERVICE_NAME"
fi
# Debug output
echo "[DEBUG] HELM_REPO_URL=$HELM_REPO_URL"
echo "[DEBUG] HELM_REPO_NAME=$HELM_REPO_NAME"
echo "[DEBUG] SERVICE_NAME=$SERVICE_NAME"
echo "[DEBUG] HELM_CHART_PATH=$HELM_CHART_PATH"
# Prepare an array to collect -f arguments
overrides_args=()
# Include all YAML files from the BASE configuration directory
# NOTE: Files in this directory are included first.
if [[ -d "$SERVICE_BASE_OVERRIDES" ]]; then
echo "Including base overrides from directory: $SERVICE_BASE_OVERRIDES"
for file in "$SERVICE_BASE_OVERRIDES"/*.yaml; do
# Check that there is at least one match
if [[ -e "$file" ]]; then
echo " - $file"
overrides_args+=("-f" "$file")
fi
done
else
echo "Warning: Base override directory not found: $SERVICE_BASE_OVERRIDES"
fi
# Include all YAML files from the custom SERVICE configuration directory
# NOTE: Files here have the highest precedence.
if [[ -d "$SERVICE_CUSTOM_OVERRIDES" ]]; then
echo "Including overrides from service config directory: $SERVICE_CUSTOM_OVERRIDES"
for file in "$SERVICE_CUSTOM_OVERRIDES"/*.yaml; do
if [[ -e "$file" ]]; then
echo " - $file"
overrides_args+=("-f" "$file")
fi
done
else
echo "Warning: Service overrides directory not found: $SERVICE_CUSTOM_OVERRIDES"
fi
echo
# Collect all --set arguments, executing commands and quoting safely
set_args=()
helm_command=(
helm upgrade --install "$SERVICE_NAME_DEFAULT" "$HELM_CHART_PATH"
--version "${SERVICE_VERSION}"
--namespace="$SERVICE_NAMESPACE"
--timeout 120m
--create-namespace
"${overrides_args[@]}"
"${set_args[@]}"
# Post-renderer configuration
--post-renderer "$GENESTACK_OVERRIDES_DIR/kustomize/kustomize.sh"
--post-renderer-args "$SERVICE_NAME_DEFAULT/overlay"
"$@"
)
echo "Executing Helm command (arguments are quoted safely):"
printf '%q ' "${helm_command[@]}"
echo
# Execute the command directly from the array
"${helm_command[@]}"
Verify Sentinel readiness with the following command
Monitoring Redis and Redis Sentinel
Redis metrics are collected by the native OpenTelemetry redis receiver, not by a
redis_exporter sidecar. The receiver issues the Redis INFO command against each
discovered pod and converts the response into redis.* metrics, which the collector's
prometheusremotewrite exporter writes into Prometheus as redis_* series.
This keeps Redis consistent with the other infrastructure services in Genestack (memcached, MariaDB, RabbitMQ), where collection is defined entirely in the OpenTelemetry collector configuration rather than in per-service exporters.
How collection is wired
Everything lives in
base-helm-configs/opentelemetry-kube-stack/opentelemetry-kube-stack-helm-overrides.yaml:
| Component | Purpose |
|---|---|
receiver_creator/redis |
Discovers the redis-replication-<n> pods and scrapes INFO on port 6379. |
receiver_creator/redis_sentinel |
Discovers the redis-sentinel-sentinel-<n> pods and scrapes the Sentinel INFO on port 26379. |
tcpcheck |
Connects to each Redis and Sentinel member to provide the up/down signal, replacing the exporter's redis_up gauge. |
transform/redis_labels |
Projects pod metadata onto the datapoints as the namespace, pod, redis_server and redis_component labels. |
transform/redis_tcpcheck_labels |
Derives the same namespace / pod / redis_component labels for the tcpcheck series. |
Both receivers are discovered through the k8s_observer extension, which is scoped to
the openstack and redis-systems namespaces.
Note
The discovery rules match on pod endpoints (type == "pod") and append the
Redis port explicitly, rather than matching on a container port. This is
deliberate: the redis-operator only declares a containerPort on the Redis
container when hostPort is set, which Genestack does not configure, so there are
no type == "port" endpoints for 6379/26379 to match against. A rule written
against port silently discovers nothing.
If you rename the Redis custom resources or change the replica count, update the
name matches expressions in the two receiver_creator blocks and the tcpcheck
target list to match your environment. You can confirm the pod and
headless-service names with:
Verifying collection
After the collector rolls out, confirm the receivers are producing data:
# the collector logs the receivers it started for each discovered endpoint
kubectl --tail=-1 -n monitoring logs -l app.kubernetes.io/name=opentelemetry-kube-stack-deployment-collector \
| grep -Ei 'redis|tcpcheck'
Then confirm the series landed in Prometheus, for example by querying
redis_uptime_seconds_total, redis_sentinel_masters and tcpcheck_status_ratio.
Dashboards
Genestack ships two Grafana dashboards, both written against the OpenTelemetry metric names:
etc/grafana-dashboards/redis_metrics.json— Redis Overview (replication cluster health).etc/grafana-dashboards/redis_sentinel_metrics.json— Redis Sentinel (Sentinel health).
Import them using the standard Grafana Dashboard Import workflow.
Metric name changes
If you have existing alerts or dashboards written against redis_exporter metric names, note that the OpenTelemetry receiver uses the OpenTelemetry semantic names, which the remote-write exporter normalizes differently:
| redis_exporter | OpenTelemetry redis receiver |
|---|---|
redis_up |
tcpcheck_status_ratio (from the tcpcheck receiver) |
redis_connected_clients |
redis_clients_connected |
redis_connected_slaves |
redis_slaves_connected |
redis_uptime_in_seconds |
redis_uptime_seconds_total |
redis_evicted_keys_total |
redis_keys_evicted_total |
redis_memory_max_bytes |
redis_maxmemory_bytes |
redis_instance_info{role="master"} |
redis_role{role="primary"} |
redis_memory_used_bytes |
redis_memory_used_bytes (unchanged) |
redis_db_keys |
redis_db_keys (unchanged) |
redis_commands_processed_total |
redis_commands_processed_total (unchanged) |
redis_keyspace_hits_total / redis_keyspace_misses_total |
unchanged |
redis_sentinel_masters |
redis_sentinel_masters (unchanged) |
Sentinel coverage limitation
The OpenTelemetry redis receiver only parses the Sentinel INFO output. It does not
issue the SENTINEL masters command, so the following per-master series that
redis_exporter provides have no receiver equivalent:
redis_sentinel_master_statusredis_sentinel_master_ok_sentinelsredis_sentinel_master_ok_slavesredis_sentinel_master_slavesredis_sentinel_known_sentinels
In their place the Sentinel dashboard reports the Sentinel-level health signals the receiver does provide: masters monitored, TILT state and TILT event rate, and the script queue depth.
If you specifically need the per-master quorum and failover detail, you can enable the
redis_exporter sidecar alongside the OpenTelemetry receiver by creating
/etc/genestack/helm-configs/redis-sentinel/redis-exporter-overrides.yaml:
redisExporter:
enabled: true
image: quay.io/opstree/redis-exporter
tag: "v1.44.0"
serviceMonitor:
enabled: true
interval: 30s
scrapeTimeout: 10s
namespace: monitoring
Then re-run the installer: