2 Commits

Author SHA1 Message Date
383a89aef7 feat(valkey): add ansible role 2024-11-04 17:13:36 +01:00
ccc4f52d56 feat(redis): add ansible role 2024-11-02 09:57:00 +01:00
23 changed files with 549 additions and 0 deletions

View File

@ -4,5 +4,13 @@
- [`elasticsearch`](roles/elasticsearch/README.md): deploy
elasticsearch (OSS) in a docker container
- [`redis`](roles/redis/README.md): deploy and configure redis,
a fast cache, vector search and NoSQL database.
- [`mariadb`](roles/mariadb/README.md): deploy mariadb
in a docker container
- [`valkey`](roles/valkey/README.md): deploy and configure valkey,
an open source in-memory data store under BSD license, forked
from redis.

View File

@ -14,5 +14,7 @@ repository: https://git.finally.coffee/finallycoffee/databases
issues: https://codeberg.org/finallycoffee/ansible-collection-databases/issues
tags:
- elasticsearch
- redis
- mariadb
- valkey
- docker

6
playbooks/valkey.yml Normal file
View File

@ -0,0 +1,6 @@
---
- name: Deploy and configure valkey
hosts: "{{ valkey_hosts | default('valkey', true) }}"
become: "{{ valkey_become | default(true, true) }}"
roles:
- finallycoffee.databases.valkey

47
roles/redis/README.md Normal file
View File

@ -0,0 +1,47 @@
# `finallycoffee.databases.redis` ansible role
Redis is the self-proclaimed world's fastest data platform for caching,
vector search and NoSQL databases. Since version 7.2.4, it is no longer
considered "Free and open source software" (FOSS), with redis switching
their license to the "Serverside public license" (SSPL).
Setting the `redis_version` to higher than `7.2.4` means you will deploy
the SSPL-licensed version to redis.
## Configuration
All container-related options to the `docker_container` ansible module
are available under the `redis_container_*` namespace, for example use
`redis_container_ports: [ '127.0.0.1:6379:6370/tcp' ]` to map the
containers port 6379 to the docker host.
Redis-related config options are either available in the `redis_config_*`
namespace or can be specified by setting them as a dictionary in
`redis_config`
### Authentication and authorization
Redis ACL can be specified as an array in the `redis_config_user` variable
- see [the redis documentation](https://github.com/redis/redis/blob/unstable/redis.conf#L869)
for the format. Per default, the `default` user is able to connect without
any password. To require a password and use a different user, override
the variable, for example `redis_config_user: [ 'username on +@all -DEBUG ~* >secret' ]`.
## Redis on a unix socket
To make redis available on a unix socket, a directory must be supplied in which the
socket lives:
```yaml
redis_container_socket: /var/run/redis.sock
redis_container_volumes:
- "/path/to/socket/on/host/redis.sock:{{ redis_container_socket }}:z"
redis_config_unixsocket: "{{ redis_container_socket }}"
```
## Container specific information
Redis publishes their official container image in both a debian-based and an
alpine-based variant. Which image should be used can be configured in
`redis_container_image_flavour`, which defaults to `alpine`, which is smaller
in size but also includes less related / debugging tools. To use the debian-
based image, unset the flavour using `redis_container_image_flavour: ~`.

View File

@ -0,0 +1,41 @@
---
redis_config_bind:
- "127.0.0.1"
- "-::1"
redis_config_protected_mode: true
redis_config_port: 6379
redis_config_unixsocket: ~
redis_config_unixsocketperm: '700'
redis_config_user:
- "default on +@all -DEBUG ~* nopass"
redis_config_databases: 16
redis_config_supervised: false
redis_config_daemonize: false
redis_config_dbfilename: dump.rdb
redis_config_dir: "{{ redis_data_path }}"
redis_config_save: "3600 1 300 100 60 10000"
redis_config_appendfsync: everysec
redis_base_config:
bind: "{{ redis_config_bind | join(' ') }}"
"protected-mode": "{{ redis_config_protected_mode | bool | ternary('yes', 'no') }}"
port: "{{ redis_config_port }}"
user: "{{ redis_config_user }}"
databases: "{{ redis_config_databases }}"
daemonize: "{{ redis_config_daemonize | bool | ternary('yes', 'no') }}"
supervised: "{{ redis_config_supervised | bool | ternary('yes', 'no') }}"
save: "{{ redis_config_save }}"
dbfilename: "{{ redis_config_dbfilename }}"
dir: "{{ redis_config_dir }}"
appendfsync: "{{ redis_config_appendfsync }}"
redis_config: ~
redis_merged_config: >-2
{{ redis_base_config
| combine(({
'unixsocket': redis_config_unixsocket,
'unixsocketperm': redis_config_unixsocketperm,
})
if (redis_config_unixsocket | default(false, true)) else {},
recursive=True)
| combine(redis_config | default({}, true), recursive=True) }}

View File

@ -0,0 +1,49 @@
---
redis_container_image_registry: docker.io
redis_container_image_namespace: ~
redis_container_image_name: redis
redis_container_image_tag: ~
redis_container_image_flavour: alpine
redis_container_image_source: pull
redis_container_image_force_source: >-2
{{ redis_container_image_tag | default(false, true) | bool }}
redis_container_image: >-2
{{
([
redis_container_image_registry | default([], true),
redis_container_image_namespace | default([], true),
redis_container_image_name,
] | flatten | join('/'))
+ ':' +
(redis_container_image_tag | default(
redis_version + (
((redis_container_image_flavour is string)
and (redis_container_image_flavour | length > 0))
| ternary('-' + (redis_container_image_flavour | default('')), '')
),
true,
))
}}
redis_container_name: "redis{{ redis_instance_suffix }}"
redis_container_env: ~
redis_container_user: >-2
{{ redis_run_user_id }}:{{ redis_run_group_id }}
redis_container_ports: ~
redis_container_labels: ~
redis_container_volumes: ~
redis_container_merged_volumes: >-2
{{ redis_container_base_volumes
+ redis_container_volumes | default([], true) }}
redis_container_command:
- "redis-server"
- "{{ redis_config_file }}"
redis_container_networks: ~
redis_container_etc_hosts: ~
redis_container_dns_servers: ~
redis_container_restart_policy: "unless-stopped"
redis_container_state: >-2
{{ (redis_state == 'present') | ternary('started', 'absent') }}
redis_container_base_volumes:
- "{{ redis_config_file }}:{{ redis_config_file }}:ro"
- "{{ redis_data_path }}:{{ redis_data_path }}:rw"

View File

@ -0,0 +1,15 @@
---
redis_version: "7.2.4"
redis_state: "present"
redis_instance: ~
redis_instance_suffix: >-2
{{ ((redis_instance is string) and (redis_instance | length > 0))
| ternary('-' + (redis_instance | default('', true)), '') }}
redis_user: >-2
redis{{ redis_instance_suffix }}
redis_config_path: "/etc/redis"
redis_config_file: >-2
{{ redis_config_path }}/redis{{ redis_instance_suffix }}.conf
redis_data_path: "/var/lib/redis{{ redis_instance_suffix }}"
redis_deployment_method: docker

View File

@ -0,0 +1,10 @@
---
redis_run_user_id: >-2
{{ redis_user_info.uid | default(redis_user, true) }}
redis_run_group_id: >-2
{{ redis_user_info.group | default(redis_user, true) }}
redis_user_system: true
redis_user_create_home: false
redis_user_groups: ~
redis_user_append_groups: >-2
{{ redis_user_groups | default(true, false) | bool }}

View File

@ -0,0 +1,11 @@
---
- name: Ensure redis container '{{ redis_container_name }}' is restarted
community.docker.docker_container:
name: "{{ redis_container_name }}"
state: "{{ redis_container_state }}"
restart: true
listen: redis-restart
when:
- redis_deployment_method == 'docker'
- redis_state == 'present'
ignore_errors: "{{ ansible_check_mode }}"

10
roles/redis/meta/main.yml Normal file
View File

@ -0,0 +1,10 @@
---
allow_duplicates: true
dependencies: []
galaxy_info:
role_name: redis
description: >-2
Deploy and configure redis, a fast caching, vector-search and NoSQL database.
galaxy_tags:
- redis
- docker

View File

@ -0,0 +1,26 @@
---
- name: Ensure container image '{{ redis_container_image }}' is {{ redis_state }}
community.docker.docker_image:
name: "{{ redis_container_image }}"
state: "{{ redis_state }}"
source: "{{ redis_container_image_source }}"
force_source: "{{ redis_container_image_force_source }}"
register: redis_container_image_info
until: redis_container_image_info is success
retries: 5
delay: 3
- name: Ensure container '{{ redis_container_name }}' is {{ redis_container_state }}
community.docker.docker_container:
name: "{{ redis_container_name }}"
image: "{{ redis_container_image }}"
env: "{{ redis_container_env | default(omit, true) }}"
user: "{{ redis_container_user }}"
ports: "{{ redis_container_ports | default(omit, true) }}"
labels: "{{ redis_container_labels | default(omit, true) }}"
command: "{{ redis_container_command }}"
volumes: "{{ redis_container_merged_volumes }}"
networks: "{{ redis_container_networks | default(omit, true) }}"
etc_hosts: "{{ redis_container_etc_hosts | default(omit, true) }}"
dns_servers: "{{ redis_container_dns_servers | default(omit, true) }}"
state: "{{ redis_container_state }}"

View File

@ -0,0 +1,68 @@
---
- name: Ensure state is valid
ansible.builtin.fail:
msg: >-2
Unsupported state '{{ redis_state }}'.
Supported states are {{ redis_states | join(', ') }}
when: redis_state not in redis_states
- name: Ensure deployment method is valid
ansible.builtin.fail:
msg: >-2
Unsupported deployment method '{{ redis_deployment_method }}'!
Supported methods are {{ redis_deployment_method | join(', ') }}
when: redis_deployment_method not in redis_deployment_methods
- name: Ensure redis user '{{ redis_user }}' is {{ redis_state }}
ansible.builtin.user:
name: "{{ redis_user }}"
state: "{{ redis_state }}"
system: "{{ redis_user_system }}"
create_home: "{{ redis_user_create_home }}"
groups: "{{ redis_user_groups | default(omit, true) }}"
append: "{{ redis_user_append_groups | default(omit, true) }}"
register: redis_user_info
- name: Ensure redis config file '{{ redis_config_file }}' is {{ redis_state }}
ansible.builtin.file:
path: "{{ redis_config_file }}"
state: "{{ redis_state }}"
when: redis_state == 'absent'
- name: Ensure redis host directories are {{ redis_state }}
ansible.builtin.file:
path: "{{ path.name }}"
state: >-2
{{ (redis_state == 'present') | ternary('directory', 'absent') }}
owner: "{{ path.owner | default(redis_run_user_id) }}"
group: "{{ path.group | default(redis_run_group_id) }}"
mode: "{{ path.mode | default('0755') }}"
loop:
- name: "{{ redis_config_path }}"
- name: "{{ redis_data_path }}"
loop_control:
loop_var: "path"
label: "{{ path.name }}"
- name: Ensure redis config file '{{ redis_config_file }}' is {{ redis_state }}
ansible.builtin.copy:
content: |+2
{% for tuple in (redis_merged_config | dict2items) %}
{% if tuple.value is string or tuple.value is number %}
{{ tuple.key }} {{ tuple.value }}
{% else %}
{% for value in tuple.value %}
{{ tuple.key }} {{ value }}
{% endfor %}
{% endif %}
{% endfor %}
dest: "{{ redis_config_file }}"
owner: "{{ redis_run_user_id }}"
group: "{{ redis_run_group_id }}"
mode: "0640"
when: redis_state == 'present'
notify: redis-restart
- name: Deploy redis using {{ redis_deployment_method }}
ansible.builtin.include_tasks:
file: "deploy-{{ redis_deployment_method }}.yml"

View File

@ -0,0 +1,6 @@
---
redis_states:
- present
- absent
redis_deployment_methods:
- docker

13
roles/valkey/README.md Normal file
View File

@ -0,0 +1,13 @@
# `finallycoffee.databases.valkey` ansible role
Valkey is an open source (BSD 3 licensed), high-performance in-memory key/value
data store, ideal for workloads like caching or message queues. It has been
forked from redis 7.2.4 before redis license was changed to SSPL.
Valkey offers compatibility to redis and can be used as a drop-in replacement
for redis.
## Configuration
For the configuration, see the [`redis` role configuration](../redis/README.md#configuration),
and swap the `redis_` prefix of all variables for the `valkey_` prefix.

View File

@ -0,0 +1,41 @@
---
valkey_config_bind:
- "127.0.0.1"
- "-::1"
valkey_config_protected_mode: true
valkey_config_port: 6379
valkey_config_unixsocket: ~
valkey_config_unixsocketperm: '700'
valkey_config_user:
- "default on +@all -DEBUG ~* nopass"
valkey_config_databases: 16
valkey_config_supervised: false
valkey_config_daemonize: false
valkey_config_dbfilename: dump.rdb
valkey_config_dir: "{{ valkey_data_path }}"
valkey_config_save: "3600 1 300 100 60 10000"
valkey_config_appendfsync: everysec
valkey_base_config:
bind: "{{ valkey_config_bind | join(' ') }}"
"protected-mode": "{{ valkey_config_protected_mode | bool | ternary('yes', 'no') }}"
port: "{{ valkey_config_port }}"
user: "{{ valkey_config_user }}"
databases: "{{ valkey_config_databases }}"
daemonize: "{{ valkey_config_daemonize | bool | ternary('yes', 'no') }}"
supervised: "{{ valkey_config_supervised | bool | ternary('yes', 'no') }}"
save: "{{ valkey_config_save }}"
dbfilename: "{{ valkey_config_dbfilename }}"
dir: "{{ valkey_config_dir }}"
appendfsync: "{{ valkey_config_appendfsync }}"
valkey_config: ~
valkey_merged_config: >-2
{{ valkey_base_config
| combine(({
'unixsocket': valkey_config_unixsocket,
'unixsocketperm': valkey_config_unixsocketperm,
})
if (valkey_config_unixsocket | default(false, true)) else {},
recursive=True)
| combine(valkey_config | default({}, true), recursive=True) }}

View File

@ -0,0 +1,49 @@
---
valkey_container_image_registry: docker.io
valkey_container_image_namespace: valkey
valkey_container_image_name: valkey
valkey_container_image_tag: ~
valkey_container_image_flavour: alpine
valkey_container_image_source: pull
valkey_container_image_force_source: >-2
{{ valkey_container_image_tag | default(false, true) | bool }}
valkey_container_image: >-2
{{
([
valkey_container_image_registry | default([], true),
valkey_container_image_namespace | default([], true),
valkey_container_image_name,
] | flatten | join('/'))
+ ':' +
(valkey_container_image_tag | default(
valkey_version + (
((valkey_container_image_flavour is string)
and (valkey_container_image_flavour | length > 0))
| ternary('-' + (valkey_container_image_flavour | default('')), '')
),
true,
))
}}
valkey_container_name: "valkey{{ valkey_instance_suffix }}"
valkey_container_env: ~
valkey_container_user: >-2
{{ valkey_run_user_id }}:{{ valkey_run_group_id }}
valkey_container_ports: ~
valkey_container_labels: ~
valkey_container_volumes: ~
valkey_container_merged_volumes: >-2
{{ valkey_container_base_volumes
+ valkey_container_volumes | default([], true) }}
valkey_container_command:
- "valkey-server"
- "{{ valkey_config_file }}"
valkey_container_networks: ~
valkey_container_etc_hosts: ~
valkey_container_dns_servers: ~
valkey_container_restart_policy: "unless-stopped"
valkey_container_state: >-2
{{ (valkey_state == 'present') | ternary('started', 'absent') }}
valkey_container_base_volumes:
- "{{ valkey_config_file }}:{{ valkey_config_file }}:ro"
- "{{ valkey_data_path }}:{{ valkey_data_path }}:rw"

View File

@ -0,0 +1,15 @@
---
valkey_version: "8.0.1"
valkey_state: "present"
valkey_instance: ~
valkey_instance_suffix: >-2
{{ ((valkey_instance is string) and (valkey_instance | length > 0))
| ternary('-' + (valkey_instance | default('', true)), '') }}
valkey_user: >-2
valkey{{ valkey_instance_suffix }}
valkey_config_path: "/etc/valkey"
valkey_config_file: >-2
{{ valkey_config_path }}/valkey{{ valkey_instance_suffix }}.conf
valkey_data_path: "/var/lib/valkey{{ valkey_instance_suffix }}"
valkey_deployment_method: docker

View File

@ -0,0 +1,10 @@
---
valkey_run_user_id: >-2
{{ valkey_user_info.uid | default(valkey_user, true) }}
valkey_run_group_id: >-2
{{ valkey_user_info.group | default(valkey_user, true) }}
valkey_user_system: true
valkey_user_create_home: false
valkey_user_groups: ~
valkey_user_append_groups: >-2
{{ valkey_user_groups | default(true, false) | bool }}

View File

@ -0,0 +1,11 @@
---
- name: Ensure valkey container '{{ valkey_container_name }}' is restarted
community.docker.docker_container:
name: "{{ valkey_container_name }}"
state: "{{ valkey_container_state }}"
restart: true
listen: valkey-restart
when:
- valkey_deployment_method == 'docker'
- valkey_state == 'present'
ignore_errors: "{{ ansible_check_mode }}"

View File

@ -0,0 +1,11 @@
---
allow_duplicates: true
dependencies: []
galaxy_info:
role_name: valkey
description: >-2
An open source, in-memory datastore under BSD 3 license
galaxy_tags:
- valkey
- redis
- docker

View File

@ -0,0 +1,26 @@
---
- name: Ensure container image '{{ valkey_container_image }}' is {{ valkey_state }}
community.docker.docker_image:
name: "{{ valkey_container_image }}"
state: "{{ valkey_state }}"
source: "{{ valkey_container_image_source }}"
force_source: "{{ valkey_container_image_force_source }}"
register: valkey_container_image_info
until: valkey_container_image_info is success
retries: 5
delay: 3
- name: Ensure container '{{ valkey_container_name }}' is {{ valkey_container_state }}
community.docker.docker_container:
name: "{{ valkey_container_name }}"
image: "{{ valkey_container_image }}"
env: "{{ valkey_container_env | default(omit, true) }}"
user: "{{ valkey_container_user }}"
ports: "{{ valkey_container_ports | default(omit, true) }}"
labels: "{{ valkey_container_labels | default(omit, true) }}"
command: "{{ valkey_container_command }}"
volumes: "{{ valkey_container_merged_volumes }}"
networks: "{{ valkey_container_networks | default(omit, true) }}"
etc_hosts: "{{ valkey_container_etc_hosts | default(omit, true) }}"
dns_servers: "{{ valkey_container_dns_servers | default(omit, true) }}"
state: "{{ valkey_container_state }}"

View File

@ -0,0 +1,68 @@
---
- name: Ensure state is valid
ansible.builtin.fail:
msg: >-2
Unsupported state '{{ valkey_state }}'.
Supported states are {{ valkey_states | join(', ') }}
when: valkey_state not in valkey_states
- name: Ensure deployment method is valid
ansible.builtin.fail:
msg: >-2
Unsupported deployment method '{{ valkey_deployment_method }}'!
Supported methods are {{ valkey_deployment_method | join(', ') }}
when: valkey_deployment_method not in valkey_deployment_methods
- name: Ensure valkey user '{{ valkey_user }}' is {{ valkey_state }}
ansible.builtin.user:
name: "{{ valkey_user }}"
state: "{{ valkey_state }}"
system: "{{ valkey_user_system }}"
create_home: "{{ valkey_user_create_home }}"
groups: "{{ valkey_user_groups | default(omit, true) }}"
append: "{{ valkey_user_append_groups | default(omit, true) }}"
register: valkey_user_info
- name: Ensure valkey config file '{{ valkey_config_file }}' is {{ valkey_state }}
ansible.builtin.file:
path: "{{ valkey_config_file }}"
state: "{{ valkey_state }}"
when: valkey_state == 'absent'
- name: Ensure valkey host directories are {{ valkey_state }}
ansible.builtin.file:
path: "{{ path.name }}"
state: >-2
{{ (valkey_state == 'present') | ternary('directory', 'absent') }}
owner: "{{ path.owner | default(valkey_run_user_id) }}"
group: "{{ path.group | default(valkey_run_group_id) }}"
mode: "{{ path.mode | default('0755') }}"
loop:
- name: "{{ valkey_config_path }}"
- name: "{{ valkey_data_path }}"
loop_control:
loop_var: "path"
label: "{{ path.name }}"
- name: Ensure valkey config file '{{ valkey_config_file }}' is {{ valkey_state }}
ansible.builtin.copy:
content: |+2
{% for tuple in (valkey_merged_config | dict2items) %}
{% if tuple.value is string or tuple.value is number %}
{{ tuple.key }} {{ tuple.value }}
{% else %}
{% for value in tuple.value %}
{{ tuple.key }} {{ value }}
{% endfor %}
{% endif %}
{% endfor %}
dest: "{{ valkey_config_file }}"
owner: "{{ valkey_run_user_id }}"
group: "{{ valkey_run_group_id }}"
mode: "0640"
when: valkey_state == 'present'
notify: valkey-restart
- name: Deploy valkey using {{ valkey_deployment_method }}
ansible.builtin.include_tasks:
file: "deploy-{{ valkey_deployment_method }}.yml"

View File

@ -0,0 +1,6 @@
---
valkey_states:
- present
- absent
valkey_deployment_methods:
- docker