initial commit

This commit is contained in:
srw 2025-10-01 03:47:56 +00:00
commit 3e748f9303
9 changed files with 539 additions and 0 deletions

20
LICENSE Normal file
View File

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2024 Shane Wadleigh
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

6
README.md Normal file
View File

@ -0,0 +1,6 @@
# ansible-role-redis-k8s
Ansible role for deploying a statefulset for redis and sentinel
- https://redis.io/
- https://github.com/redis

98
defaults/main.yml Normal file
View File

@ -0,0 +1,98 @@
---
redis_deployment_name: redis
redis_namespace: "{{ redis_deployment_name }}"
redis_image: docker.io/redis
redis_image_tag: latest
# KUBERNETES SETTINGS
redis_labels:
app.kubernetes.io/name: "{{ redis_deployment_name }}"
app.kubernetes.io/managed-by: ansible
redis_selector:
app.kubernetes.io/name: "{{ redis_deployment_name }}"
# requiredDuringSchedulingIgnoredDuringExecution,preferredDuringSchedulingIgnoredDuringExecution
redis_anti_affinity_type: requiredDuringSchedulingIgnoredDuringExecution
# DoNotSchedule, ScheduleAnyway
redis_topology_constraint: DoNotSchedule
redis_replicas: 3
redis_min_available: 2
redis_max_skew: 1
redis_max_unavailable: 1
redis_strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: "{{ redis_max_unavailable }}"
# security and resource limits
# redis_pod_security_context:
# runAsNonRoot: true
# fsGroup: 1000
# seccompProfile: { type: RuntimeDefault }
# redis_container_security_context:
# allowPrivilegeEscalation: false
# readOnlyRootFilesystem: true
# runAsNonRoot: true
# seccompProfile: { type: RuntimeDefault }
# REDIS SETTINGS
redis_port: 6379
redis_parallel_syncs: 1
redis_protected_mode: "no"
redis_append_only: "yes"
redis_max_memory: 256mb
redis_max_memory_policy: allkeys-lru
redis_storage_class: local-path
redis_storage_size: "1Gi"
redis_container_resources:
requests:
cpu: "100m"
memory: "256Mi"
limits:
cpu: "1"
memory: "1Gi"
redis_command_off: |
/bin/sh -lc '
cp /config/redis.conf /conf/redis.conf
if hostname | grep -q -- "-0$"; then
sed -i "/^replicaof /d" /conf/redis.conf
fi
exec redis-server /conf/redis.conf
'
redis_command: >
/bin/sh -lc |
cp /config/redis.conf /conf/redis.conf
if hostname | grep -q -- '-0$'; then
sed -i '/^replicaof /d' /conf/redis.conf
fi
exec redis-server /conf/redis.conf
# redis_server_env:
# OAUTH2_PROVIDER_APPLICATION_MODEL: "{{ redis_oauth_application }}"
# SENTINEL SETTINGS
redis_sentinel_port: 26379
redis_sentinel_quorum: 2
redis_sentinel_down_after_ms: 10000
redis_sentinel_failover_timeout: 10000
redis_sentinel_master: "mymaster"
redis_sentinel_container_resources:
requests:
cpu: "50m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "512Mi"
redis_sentinel_command: >
/bin/sh -lc exec redis-sentinel /config/sentinel.conf

28
handlers/main.yml Normal file
View File

@ -0,0 +1,28 @@
---
- name: REDIS HANDLER check for existing deployment {{ redis_deployment_name }}
listen: redis-restart
kubernetes.core.k8s_info:
api_version: apps/v1
kind: StatefulSet
name: "{{ redis_deployment_name }}"
namespace: "{{ redis_namespace }}"
register: redis_deploy_info
failed_when: false
changed_when: false
- name: REDIS HANDLER restart deployment {{ redis_deployment_name }}
listen: redis-restart
kubernetes.core.k8s:
api_version: apps/v1
kind: StatefulSet
name: "{{ redis_deployment_name }}"
namespace: "{{ redis_namespace }}"
state: patched
definition:
spec:
template:
metadata:
annotations:
kubectl.kubernetes.io/restartedAt: "{{ lookup('pipe', 'date -u +%Y-%m-%dT%H:%M:%SZ') }}"
when:
- redis_deploy_info.resources | length > 0

40
meta/main.yml Normal file
View File

@ -0,0 +1,40 @@
---
dependencies: []
galaxy_info:
role_name: redis_k8s
author: srw
description: Ansible role for deploying redis and sentinel to kubernetes
company: "NMD, LLC"
license: "license (BSD, MIT)"
min_ansible_version: "2.10"
platforms:
- name: Fedora
versions:
- all
- name: Debian
versions:
- buster
- bullseye
- bookworm
- trixie
- name: Ubuntu
versions:
- bionic
- focal
- jammy
- name: Alpine
version:
- all
- name: ArchLinux
versions:
- all
galaxy_tags:
- server
- system
- containers
- kubernetes
- k8s
- k3s
- rke2
- redis

178
tasks/deploy.yml Normal file
View File

@ -0,0 +1,178 @@
---
- name: REDIS DEPLOY
tags:
- redis
- redis-deploy
block:
- name: REDIS DEPLOY PodDisruptionBudget
kubernetes.core.k8s:
state: "{{ redis_state | d('present') }}"
definition:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: "{{ redis_deployment_name }}"
namespace: "{{ redis_namespace }}"
labels: "{{ redis_labels }}"
spec:
minAvailable: "{{ redis_min_available }}"
selector:
matchLabels: "{{ redis_selector }}"
- name: REDIS DEPLOY Create Redis StatefulSet with Sentinel Sidecar
kubernetes.core.k8s:
state: "{{ redis_state | d('present') }}"
server_side_apply:
field_manager: ansible
force_conflicts: true
wait: true
wait_timeout: 600
definition:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: "{{ redis_deployment_name }}"
namespace: "{{ redis_namespace }}"
labels: "{{ redis_labels }}"
spec:
serviceName: redis
replicas: "{{ redis_replicas }}"
strategy: "{{ redis_strategy }}"
selector:
matchLabels: "{{ redis_selector }}"
template:
metadata:
labels: "{{ redis_labels }}"
annotations:
rollout.redis/token: "{{ redis_rollout_token | d(omit) }}"
spec:
topologySpreadConstraints:
- maxSkew: "{{ redis_max_skew }}"
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: "{{ redis_topology_constraint }}"
labelSelector:
matchLabels: "{{ redis_selector }}"
affinity:
podAntiAffinity:
"{{ redis_anti_affinity_type }}":
- labelSelector:
matchLabels: "{{ redis_selector }}"
topologyKey: kubernetes.io/hostname
securityContext: "{{ redis_pod_security_context | d(omit) }}"
initContainers:
- name: copy-sentinel-config
image: "{{ redis_image }}:{{ redis_image_tag }}"
command:
- /bin/sh
- -c
- |
cp /conf-ro/sentinel.conf /conf-rw/sentinel.conf
volumeMounts:
- name: conf-ro
mountPath: /conf-ro
readOnly: true
- name: conf-rw
mountPath: /conf-rw
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "128Mi"
cpu: "100m"
containers:
# REDIS
- name: redis
image: "{{ redis_image }}:{{ redis_image_tag }}"
securityContext: "{{ redis_container_security_context | d(omit) }}"
resources: "{{ redis_container_resources | d(omit) }}"
args:
- redis-server
- /conf/redis.conf
ports:
- name: redis
containerPort: "{{ redis_port }}"
volumeMounts:
- name: conf-ro
mountPath: /conf/redis.conf
subPath: redis.conf
readOnly: true
- name: data
mountPath: /data
livenessProbe:
tcpSocket:
port: redis
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
exec:
command:
- redis-cli
- -p
- "{{ redis_port | string }}"
- ping
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
envFrom:
- secretRef:
name: "{{ redis_deployment_name }}-secrets"
# SENTINEL
- name: redis-sentinel
image: "{{ redis_image }}:{{ redis_image_tag }}"
securityContext: "{{ redis_container_security_context | d(omit) }}"
resources: "{{ redis_sentinel_container_resources | d(omit) }}"
args:
- redis-sentinel
- /conf/sentinel.conf
ports:
- name: redis-sentinel
containerPort: "{{ redis_sentinel_port }}"
volumeMounts:
- name: conf-rw
mountPath: /conf
livenessProbe:
tcpSocket:
port: redis-sentinel
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
exec:
command:
- redis-cli
- -p
- "{{ redis_sentinel_port | string }}"
- ping
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
envFrom:
- secretRef:
name: "{{ redis_deployment_name }}-secrets"
volumes:
- name: conf-ro
configMap:
name: "{{ redis_deployment_name }}-configs"
items:
- key: redis.conf
path: redis.conf
- key: sentinel.conf
path: sentinel.conf
- name: conf-rw
emptyDir: {}
volumeClaimTemplates:
- metadata:
name: data
labels: "{{ elasticsearch_labels }}"
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: "{{ redis_storage_class }}"
resources:
requests:
storage: "{{ redis_storage_size }}"

91
tasks/environment.yml Normal file
View File

@ -0,0 +1,91 @@
---
- name: REDIS ENVIRONMENT SETUP
tags:
- redis
- redis-env
block:
- name: REDIS ENVIRONMENT create configmaps
kubernetes.core.k8s:
state: "{{ redis_state | d('present') }}"
server_side_apply:
field_manager: ansible
force_conflicts: true
definition:
apiVersion: v1
kind: ConfigMap
metadata:
name: "{{ redis_deployment_name }}-configs"
namespace: "{{ redis_namespace }}"
labels: "{{ redis_labels }}"
data:
redis.conf: |
dir /data
bind 0.0.0.0
port {{ redis_port }}
protected-mode {{ redis_protected_mode }}
appendonly {{ redis_append_only }}
maxmemory {{ redis_max_memory }}
maxmemory-policy {{ redis_max_memory_policy }}
# --- Auth (server password) ---
requirepass ${REDIS_PASSWORD}
# Replicas authenticate to the master with the same password:
masterauth ${REDIS_PASSWORD}
# Optional replication safety knobs (recommended if you want write safety):
# min-replicas-to-write 1
# min-replicas-max-lag 5
# Optional announcements (usually not required inside K8s):
# replica-read-only yes
# replica-announce-ip ${POD_IP}
# replica-announce-port {{ redis_port }}
sentinel.conf: |
port {{ redis_sentinel_port }}
protected-mode {{ redis_protected_mode }}
sentinel resolve-hostnames yes
sentinel monitor {{ redis_sentinel_master }} redis-0.redis.{{ redis_namespace }}.svc.cluster.local {{ redis_port }} 2
sentinel down-after-milliseconds {{ redis_sentinel_master }} {{ redis_sentinel_down_after_ms }}
sentinel failover-timeout {{ redis_sentinel_master }} {{ redis_sentinel_failover_timeout }}
sentinel parallel-syncs {{ redis_sentinel_master }} {{ redis_parallel_syncs }}
# Auth for Sentinel -> Redis (master/replicas)
sentinel auth-pass {{ redis_sentinel_master }} ${REDIS_PASSWORD}
# If you want to protect the sentinel port itself, uncomment and make clients AUTH:
# requirepass ${REDIS_PASSWORD}
- name: REDIS ENVIRONMENT base64 encode secrets
no_log: true
ansible.builtin.set_fact:
redis_secrets_b64: "{{ redis_secrets_b64 | default({}) | combine({item.key: (item.value | b64encode)}) }}"
loop: "{{ redis_secrets | dict2items }}"
# - name: REDIS ENVIRONMENT verify secrets
# debug:
# msg:
# - "{{ redis_secrets }}"
# - "{{ redis_secrets_b64 }}"
- name: REDIS ENVIRONMENT create secrets
no_log: true
kubernetes.core.k8s:
state: "{{ redis_state | d('present') }}"
server_side_apply:
field_manager: ansible
force_conflicts: true
definition:
apiVersion: v1
kind: Secret
metadata:
name: "{{ redis_deployment_name }}-secrets"
namespace: "{{ redis_namespace }}"
labels: "{{ redis_labels }}"
type: Opaque
data: "{{ redis_secrets_b64 }}"
notify:
- redis-restart
when:
- redis_secrets is defined

34
tasks/main.yml Normal file
View File

@ -0,0 +1,34 @@
---
- name: REDIS KUBERNETES DEPLOYMENT
delegate_to: localhost
connection: local
become: false
tags: never
block:
- name: REDIS create namespace {{ redis_namespace }}
tags: [redis, redis-env, redis-services, redis-deploy]
kubernetes.core.k8s:
state: present
definition:
apiVersion: v1
kind: Namespace
metadata:
name: "{{ redis_namespace }}"
- name: REDIS setup environment
ansible.builtin.include_tasks: environment.yml
tags:
- redis
- redis-env
- name: REDIS create services
ansible.builtin.include_tasks: services.yml
tags:
- redis
- redis-services
- name: REDIS create statefulset
ansible.builtin.include_tasks: deploy.yml
tags:
- redis
- redis-deploy

44
tasks/services.yml Normal file
View File

@ -0,0 +1,44 @@
---
- name: REDIS SERVICES
tags:
- redis
- redis-services
block:
- name: REDIS SERVICES create headless service
kubernetes.core.k8s:
state: "{{ redis_state | d('present') }}"
definition:
apiVersion: v1
kind: Service
metadata:
name: redis
namespace: "{{ redis_namespace }}"
labels: "{{ redis_labels }}"
spec:
clusterIP: None
publishNotReadyAddresses: true
selector: "{{ redis_selector }}"
ports:
- name: redis
port: "{{ redis_port }}"
targetPort: "{{ redis_port }}"
- name: REDIS SERVICES create sentinel headless service
kubernetes.core.k8s:
state: "{{ redis_state | d('present') }}"
definition:
apiVersion: v1
kind: Service
metadata:
name: redis-sentinel
namespace: "{{ redis_namespace }}"
labels: "{{ redis_labels }}"
spec:
clusterIP: None
publishNotReadyAddresses: true
selector: "{{ redis_selector }}"
ports:
- name: redis-sentinel
port: "{{ redis_sentinel_port }}"
targetPort: "{{ redis_sentinel_port }}"