feat: add basic garage s3 storage role
This commit is contained in:
parent
69bc95b992
commit
5377c34709
11 changed files with 344 additions and 0 deletions
|
|
@ -16,9 +16,11 @@
|
||||||
- curl
|
- curl
|
||||||
- gnupg
|
- gnupg
|
||||||
- lsb-release
|
- lsb-release
|
||||||
|
- apache2-utils # for htpasswd
|
||||||
state: present
|
state: present
|
||||||
when: ansible_os_family == "Debian"
|
when: ansible_os_family == "Debian"
|
||||||
|
|
||||||
|
|
||||||
- name: Install convenience packages
|
- name: Install convenience packages
|
||||||
ansible.builtin.apt:
|
ansible.builtin.apt:
|
||||||
name:
|
name:
|
||||||
|
|
|
||||||
113
roles/garage/README.md
Normal file
113
roles/garage/README.md
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
Garage
|
||||||
|
======
|
||||||
|
|
||||||
|
Ansible role to deploy Garage S3-compatible object storage using Docker Compose.
|
||||||
|
|
||||||
|
Requirements
|
||||||
|
------------
|
||||||
|
|
||||||
|
- Docker and Docker Compose installed on the target host
|
||||||
|
- Ansible collection: `community.docker`
|
||||||
|
- Traefik reverse proxy (for external access)
|
||||||
|
|
||||||
|
Role Variables
|
||||||
|
--------------
|
||||||
|
|
||||||
|
Key variables defined in `defaults/main.yml`:
|
||||||
|
|
||||||
|
**Base Configuration:**
|
||||||
|
- `docker_compose_base_dir`: Base directory for Docker Compose files (default: `/etc/docker/compose`)
|
||||||
|
- `docker_volume_base_dir`: Base directory for Docker volumes (default: `/srv/data`)
|
||||||
|
|
||||||
|
**Garage Configuration:**
|
||||||
|
- `garage_service_name`: Service name (default: `garage`)
|
||||||
|
- `garage_image`: Garage Docker image (default: `dxflrs/garage:v2.1.0`)
|
||||||
|
- `garage_s3_domain`: Domain for S3 API endpoint (default: `storage.local.test`)
|
||||||
|
- `garage_web_domain`: Domain for S3 web endpoint (default: `web.storage.local.test`)
|
||||||
|
- `garage_webui_domain`: Domain for web console (default: `console.storage.local.test`)
|
||||||
|
|
||||||
|
**Garage Storage Configuration:**
|
||||||
|
- `garage_replication_factor`: Replication factor (default: `1`)
|
||||||
|
- `garage_compression_level`: Compression level (default: `1`)
|
||||||
|
- `garage_db_engine`: Database engine (default: `lmdb`)
|
||||||
|
- `garage_s3_region`: S3 region (default: `us-east-1`)
|
||||||
|
|
||||||
|
**Garage Ports:**
|
||||||
|
- `garage_s3_api_port`: S3 API port (default: `3900`)
|
||||||
|
- `garage_s3_web_port`: S3 web port (default: `3902`)
|
||||||
|
- `garage_admin_port`: Admin API port (default: `3903`)
|
||||||
|
- `garage_rpc_port`: RPC port (default: `3901`)
|
||||||
|
|
||||||
|
**Garage Security:**
|
||||||
|
- `garage_rpc_secret`: RPC secret for node communication
|
||||||
|
- `garage_admin_token`: Admin API token
|
||||||
|
- `garage_metrics_token`: Metrics API token
|
||||||
|
|
||||||
|
**Garage WebUI Configuration:**
|
||||||
|
- `garage_webui_enabled`: Enable web UI (default: `true`)
|
||||||
|
- `garage_webui_image`: WebUI Docker image (default: `khairul169/garage-webui:latest`)
|
||||||
|
- `garage_webui_port`: WebUI port (default: `3909`)
|
||||||
|
- `garage_webui_username`: WebUI username (default: `admin`)
|
||||||
|
- `garage_webui_password`: WebUI password in plaintext (default: `admin`)
|
||||||
|
|
||||||
|
**Traefik Configuration:**
|
||||||
|
- `garage_traefik_network`: Traefik network name (default: `proxy`)
|
||||||
|
- `garage_internal_network`: Internal network name (default: `internal`)
|
||||||
|
- `garage_use_ssl`: Enable SSL (default: `true`)
|
||||||
|
|
||||||
|
Dependencies
|
||||||
|
------------
|
||||||
|
|
||||||
|
This role requires:
|
||||||
|
- Traefik reverse proxy to be configured and the `proxy` network to be created
|
||||||
|
- `htpasswd` utility (from `apache2-utils` package) for generating bcrypt password hashes
|
||||||
|
|
||||||
|
Example Playbook
|
||||||
|
----------------
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- hosts: storage_servers
|
||||||
|
roles:
|
||||||
|
- role: garage
|
||||||
|
vars:
|
||||||
|
garage_s3_domain: "storage.example.com"
|
||||||
|
garage_rpc_secret: "your-secure-rpc-secret"
|
||||||
|
garage_admin_token: "your-admin-token"
|
||||||
|
garage_webui_enabled: true
|
||||||
|
garage_webui_username: "admin"
|
||||||
|
garage_webui_password: "secure-password"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** The WebUI password is specified in plaintext and will be automatically hashed using bcrypt during deployment. The role uses `htpasswd` to generate a secure bcrypt hash that is then properly escaped for use in Docker Compose.
|
||||||
|
|
||||||
|
Post-Installation
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
After deployment, you need to configure the Garage cluster:
|
||||||
|
|
||||||
|
1. Connect to the node and get the node ID:
|
||||||
|
```bash
|
||||||
|
docker exec -ti garage /garage node id
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Configure the node layout:
|
||||||
|
```bash
|
||||||
|
docker exec -ti garage /garage layout assign -z dc1 -c 1G <node-id>
|
||||||
|
docker exec -ti garage /garage layout apply --version 1
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Create a key for S3 access:
|
||||||
|
```bash
|
||||||
|
docker exec -ti garage /garage key create my-key
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Create a bucket:
|
||||||
|
```bash
|
||||||
|
docker exec -ti garage /garage bucket create my-bucket
|
||||||
|
docker exec -ti garage /garage bucket allow my-bucket --read --write --key my-key
|
||||||
|
```
|
||||||
|
|
||||||
|
License
|
||||||
|
-------
|
||||||
|
|
||||||
|
MIT-0
|
||||||
46
roles/garage/defaults/main.yml
Normal file
46
roles/garage/defaults/main.yml
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
#SPDX-License-Identifier: MIT-0
|
||||||
|
---
|
||||||
|
# defaults file for garage
|
||||||
|
|
||||||
|
# Base directory configuration (inherited from base role or defined here)
|
||||||
|
docker_compose_base_dir: /etc/docker/compose
|
||||||
|
docker_volume_base_dir: /srv/data
|
||||||
|
|
||||||
|
# Garage-specific configuration
|
||||||
|
garage_service_name: garage
|
||||||
|
garage_docker_compose_dir: "{{ docker_compose_base_dir }}/{{ garage_service_name }}"
|
||||||
|
garage_docker_volume_dir: "{{ docker_volume_base_dir }}/{{ garage_service_name }}"
|
||||||
|
|
||||||
|
# Garage service configuration
|
||||||
|
garage_image: "dxflrs/garage:v2.1.0"
|
||||||
|
garage_s3_domain: "storage.local.test"
|
||||||
|
garage_web_domain: "web.storage.local.test"
|
||||||
|
garage_webui_domain: "console.storage.local.test"
|
||||||
|
|
||||||
|
# Garage WebUI configuration
|
||||||
|
garage_webui_enabled: true
|
||||||
|
garage_webui_image: "khairul169/garage-webui:latest"
|
||||||
|
garage_webui_port: 3909
|
||||||
|
# WebUI basic auth credentials (plaintext, will be hashed automatically)
|
||||||
|
garage_webui_username: "admin"
|
||||||
|
garage_webui_password: "admin"
|
||||||
|
|
||||||
|
# Garage ports
|
||||||
|
garage_s3_api_port: 3900
|
||||||
|
garage_s3_web_port: 3902
|
||||||
|
garage_admin_port: 3903
|
||||||
|
garage_rpc_port: 3901
|
||||||
|
|
||||||
|
# Garage configuration
|
||||||
|
garage_replication_factor: 1
|
||||||
|
garage_compression_level: 1
|
||||||
|
garage_db_engine: "lmdb"
|
||||||
|
garage_s3_region: "us-east-1"
|
||||||
|
garage_rpc_secret: "changeme_rpc_secret"
|
||||||
|
garage_admin_token: "changeme_admin_token"
|
||||||
|
garage_metrics_token: "changeme_metrics_token"
|
||||||
|
|
||||||
|
# Traefik configuration
|
||||||
|
garage_traefik_network: "proxy"
|
||||||
|
garage_internal_network: "internal"
|
||||||
|
garage_use_ssl: true
|
||||||
3
roles/garage/handlers/main.yml
Normal file
3
roles/garage/handlers/main.yml
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
#SPDX-License-Identifier: MIT-0
|
||||||
|
---
|
||||||
|
# handlers file for garage
|
||||||
35
roles/garage/meta/main.yml
Normal file
35
roles/garage/meta/main.yml
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
#SPDX-License-Identifier: MIT-0
|
||||||
|
galaxy_info:
|
||||||
|
author: your name
|
||||||
|
description: your role description
|
||||||
|
company: your company (optional)
|
||||||
|
|
||||||
|
# If the issue tracker for your role is not on github, uncomment the
|
||||||
|
# next line and provide a value
|
||||||
|
# issue_tracker_url: http://example.com/issue/tracker
|
||||||
|
|
||||||
|
# Choose a valid license ID from https://spdx.org - some suggested licenses:
|
||||||
|
# - BSD-3-Clause (default)
|
||||||
|
# - MIT
|
||||||
|
# - GPL-2.0-or-later
|
||||||
|
# - GPL-3.0-only
|
||||||
|
# - Apache-2.0
|
||||||
|
# - CC-BY-4.0
|
||||||
|
license: license (GPL-2.0-or-later, MIT, etc)
|
||||||
|
|
||||||
|
min_ansible_version: 2.1
|
||||||
|
|
||||||
|
# If this a Container Enabled role, provide the minimum Ansible Container version.
|
||||||
|
# min_ansible_container_version:
|
||||||
|
|
||||||
|
galaxy_tags: []
|
||||||
|
# List tags for your role here, one per line. A tag is a keyword that describes
|
||||||
|
# and categorizes the role. Users find roles by searching for tags. Be sure to
|
||||||
|
# remove the '[]' above, if you add tags to this list.
|
||||||
|
#
|
||||||
|
# NOTE: A tag is limited to a single word comprised of alphanumeric characters.
|
||||||
|
# Maximum 20 tags per role.
|
||||||
|
|
||||||
|
dependencies: []
|
||||||
|
# List your role dependencies here, one per line. Be sure to remove the '[]' above,
|
||||||
|
# if you add dependencies to this list.
|
||||||
45
roles/garage/tasks/main.yml
Normal file
45
roles/garage/tasks/main.yml
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
#SPDX-License-Identifier: MIT-0
|
||||||
|
---
|
||||||
|
# tasks file for garage
|
||||||
|
|
||||||
|
- name: Create docker compose directory
|
||||||
|
file:
|
||||||
|
path: "{{ garage_docker_compose_dir }}"
|
||||||
|
state: directory
|
||||||
|
mode: '0755'
|
||||||
|
|
||||||
|
- name: Create garage meta data directory
|
||||||
|
file:
|
||||||
|
path: "{{ garage_docker_volume_dir }}/meta"
|
||||||
|
state: directory
|
||||||
|
mode: '0755'
|
||||||
|
|
||||||
|
- name: Create garage data directory
|
||||||
|
file:
|
||||||
|
path: "{{ garage_docker_volume_dir }}/data"
|
||||||
|
state: directory
|
||||||
|
mode: '0755'
|
||||||
|
|
||||||
|
- name: Generate garage configuration file
|
||||||
|
template:
|
||||||
|
src: garage.toml.j2
|
||||||
|
dest: "{{ garage_docker_compose_dir }}/garage.toml"
|
||||||
|
mode: '0644'
|
||||||
|
|
||||||
|
- name: Generate bcrypt hash for webui password using htpasswd
|
||||||
|
ansible.builtin.shell: |
|
||||||
|
htpasswd -nbBC 10 "{{ garage_webui_username }}" "{{ garage_webui_password }}"
|
||||||
|
register: _garage_webui_password_hash
|
||||||
|
changed_when: false
|
||||||
|
when: garage_webui_enabled
|
||||||
|
|
||||||
|
- name: Create docker-compose file for garage
|
||||||
|
template:
|
||||||
|
src: docker-compose.yml.j2
|
||||||
|
dest: "{{ garage_docker_compose_dir }}/docker-compose.yml"
|
||||||
|
mode: '0644'
|
||||||
|
|
||||||
|
- name: Start garage container
|
||||||
|
community.docker.docker_compose_v2:
|
||||||
|
project_src: "{{ garage_docker_compose_dir }}"
|
||||||
|
state: present
|
||||||
62
roles/garage/templates/docker-compose.yml.j2
Normal file
62
roles/garage/templates/docker-compose.yml.j2
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
services:
|
||||||
|
{{ garage_service_name }}:
|
||||||
|
container_name: {{ garage_service_name }}
|
||||||
|
image: {{ garage_image }}
|
||||||
|
restart: always
|
||||||
|
volumes:
|
||||||
|
- {{ garage_docker_volume_dir }}/meta:/var/lib/garage/meta
|
||||||
|
- {{ garage_docker_volume_dir }}/data:/var/lib/garage/data
|
||||||
|
- {{ garage_docker_compose_dir }}/garage.toml:/etc/garage.toml
|
||||||
|
networks:
|
||||||
|
- {{ garage_traefik_network }}
|
||||||
|
- {{ garage_internal_network }}
|
||||||
|
labels:
|
||||||
|
- traefik.enable=true
|
||||||
|
- traefik.docker.network={{ garage_traefik_network }}
|
||||||
|
# S3 API endpoint
|
||||||
|
- traefik.http.routers.{{ garage_service_name }}.rule=Host(`{{ garage_s3_domain }}`)
|
||||||
|
{% if garage_use_ssl %}
|
||||||
|
- traefik.http.routers.{{ garage_service_name }}.entrypoints=websecure
|
||||||
|
- traefik.http.routers.{{ garage_service_name }}.tls=true
|
||||||
|
{% else %}
|
||||||
|
- traefik.http.routers.{{ garage_service_name }}.entrypoints=web
|
||||||
|
{% endif %}
|
||||||
|
- traefik.http.routers.{{ garage_service_name }}.service={{ garage_service_name }}-api
|
||||||
|
- traefik.http.routers.{{ garage_service_name }}.priority=50
|
||||||
|
- traefik.http.services.{{ garage_service_name }}-api.loadbalancer.server.port={{ garage_s3_api_port }}
|
||||||
|
|
||||||
|
{% if garage_webui_enabled %}
|
||||||
|
{{ garage_service_name }}-webui:
|
||||||
|
container_name: {{ garage_service_name }}-webui
|
||||||
|
image: {{ garage_webui_image }}
|
||||||
|
restart: always
|
||||||
|
depends_on:
|
||||||
|
- {{ garage_service_name }}
|
||||||
|
environment:
|
||||||
|
API_BASE_URL: "http://{{ garage_service_name }}:{{ garage_admin_port }}"
|
||||||
|
S3_ENDPOINT_URL: "http://{{ garage_service_name }}:{{ garage_s3_api_port }}"
|
||||||
|
AUTH_USER_PASS: '{{ _garage_webui_password_hash.stdout | replace("$", "$$") }}'
|
||||||
|
volumes:
|
||||||
|
- {{ garage_docker_compose_dir }}/garage.toml:/etc/garage.toml:ro
|
||||||
|
networks:
|
||||||
|
- {{ garage_traefik_network }}
|
||||||
|
- {{ garage_internal_network }}
|
||||||
|
labels:
|
||||||
|
- traefik.enable=true
|
||||||
|
- traefik.docker.network={{ garage_traefik_network }}
|
||||||
|
- traefik.http.routers.{{ garage_service_name }}-console.rule=Host(`{{ garage_webui_domain }}`)
|
||||||
|
{% if garage_use_ssl %}
|
||||||
|
- traefik.http.routers.{{ garage_service_name }}-console.entrypoints=websecure
|
||||||
|
- traefik.http.routers.{{ garage_service_name }}-console.tls=true
|
||||||
|
{% else %}
|
||||||
|
- traefik.http.routers.{{ garage_service_name }}-console.entrypoints=web
|
||||||
|
{% endif %}
|
||||||
|
- traefik.http.routers.{{ garage_service_name }}-console.service={{ garage_service_name }}-console
|
||||||
|
- traefik.http.routers.{{ garage_service_name }}-console.priority=10
|
||||||
|
- traefik.http.services.{{ garage_service_name }}-console.loadbalancer.server.port={{ garage_webui_port }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
networks:
|
||||||
|
{{ garage_internal_network }}:
|
||||||
|
{{ garage_traefik_network }}:
|
||||||
|
external: true
|
||||||
26
roles/garage/templates/garage.toml.j2
Normal file
26
roles/garage/templates/garage.toml.j2
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
metadata_dir = "/var/lib/garage/meta"
|
||||||
|
data_dir = "/var/lib/garage/data"
|
||||||
|
|
||||||
|
db_engine = "{{ garage_db_engine }}"
|
||||||
|
|
||||||
|
replication_factor = {{ garage_replication_factor }}
|
||||||
|
|
||||||
|
compression_level = {{ garage_compression_level }}
|
||||||
|
|
||||||
|
rpc_bind_addr = "[::]:{{ garage_rpc_port }}"
|
||||||
|
rpc_public_addr = "127.0.0.1:{{ garage_rpc_port }}"
|
||||||
|
rpc_secret = "{{ garage_rpc_secret }}"
|
||||||
|
|
||||||
|
[s3_api]
|
||||||
|
s3_region = "{{ garage_s3_region }}"
|
||||||
|
api_bind_addr = "[::]:{{ garage_s3_api_port }}"
|
||||||
|
root_domain = ".s3.{{ garage_s3_domain }}"
|
||||||
|
|
||||||
|
[s3_web]
|
||||||
|
bind_addr = "[::]:{{ garage_s3_web_port }}"
|
||||||
|
root_domain = ".{{ garage_web_domain }}"
|
||||||
|
|
||||||
|
[admin]
|
||||||
|
api_bind_addr = "[::]:{{ garage_admin_port }}"
|
||||||
|
admin_token = "{{ garage_admin_token }}"
|
||||||
|
metrics_token = "{{ garage_metrics_token }}"
|
||||||
3
roles/garage/tests/inventory
Normal file
3
roles/garage/tests/inventory
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
#SPDX-License-Identifier: MIT-0
|
||||||
|
localhost
|
||||||
|
|
||||||
6
roles/garage/tests/test.yml
Normal file
6
roles/garage/tests/test.yml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
#SPDX-License-Identifier: MIT-0
|
||||||
|
---
|
||||||
|
- hosts: localhost
|
||||||
|
remote_user: root
|
||||||
|
roles:
|
||||||
|
- garage
|
||||||
3
roles/garage/vars/main.yml
Normal file
3
roles/garage/vars/main.yml
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
#SPDX-License-Identifier: MIT-0
|
||||||
|
---
|
||||||
|
# vars file for garage
|
||||||
Loading…
Add table
Add a link
Reference in a new issue